- Timestamp:
- 02/06/26 20:05:09 (5 months ago)
- Branches:
- main
- Children:
- 591919d
- Parents:
- 2ce7c1e
- Location:
- frontend/src
- Files:
-
- 3 edited
-
components/Sidebar.tsx (modified) (2 diffs)
-
pages/LandingPage.tsx (modified) (5 diffs)
-
utils/types.ts (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/components/Sidebar.tsx
r2ce7c1e rb071fb4 1 interface SidebarProps { 2 isOpen: boolean; 3 onClose: () => void; 4 } 1 import { useEffect, useState } from "react"; 2 import axiosInstance from "../api/axiosInstance"; 3 import type { BasicPlaylist, BasicSong, SidebarProps } from "../utils/types"; 5 4 6 5 const Sidebar = ({ isOpen, onClose }: SidebarProps) => { 7 // mock data for recently listened songs 8 const recentlyListened = [ 9 { id: 1, title: "Song One", artist: "Artist A", cover: "/favicon.png" }, 10 { id: 2, title: "Song Two", artist: "Artist B", cover: "/favicon.png" }, 11 { id: 3, title: "Song Three", artist: "Artist C", cover: "/favicon.png" }, 12 { id: 4, title: "Song Four", artist: "Artist D", cover: "/favicon.png" }, 13 { id: 5, title: "Song Five", artist: "Artist E", cover: "/favicon.png" }, 14 ]; 6 const [recentlyListened, setRecentlyListened] = useState<BasicSong[]>([]); 7 const [playlists, setPlaylists] = useState<BasicPlaylist[]>([]); 15 8 16 // mock data for my playlists 17 const playlists = [ 18 { id: 1, name: "My Favorites", songCount: 25 }, 19 { id: 2, name: "Workout Mix", songCount: 18 }, 20 { id: 3, name: "Chill Vibes", songCount: 32 }, 21 { id: 4, name: "Party Hits", songCount: 45 }, 22 ]; 23 9 useEffect(() => { 10 const fetchData = async () => { 11 try { 12 const data = await axiosInstance.get<BasicSong[]>("/songs/recent"); 13 setRecentlyListened(data.data); 14 } catch (error) { 15 console.error("Error fetching recently listened songs:", error); 16 // todo: show toast 17 } 18 try { 19 const data = 20 await axiosInstance.get<BasicPlaylist[]>("/playlists/user"); 21 setPlaylists(data.data); 22 } catch (error) { 23 console.error("Error fetching playlists:", error); 24 // todo: show toast 25 } 26 }; 27 fetchData(); 28 }, []); 24 29 return ( 25 30 <div … … 65 70 > 66 71 <img 67 src={song.cover }72 src={song.cover || "/favicon.png"} 68 73 alt={song.title} 69 74 className="w-10 h-10 rounded object-cover" -
frontend/src/pages/LandingPage.tsx
r2ce7c1e rb071fb4 1 import { useEffect, use State } from "react";1 import { useEffect, useRef, useState } from "react"; 2 2 import axiosInstance from "../api/axiosInstance"; 3 3 import AlbumResult from "../components/search/AlbumResult"; … … 29 29 const [searchLoading, setSearchLoading] = useState(false); 30 30 const [hasSearched, setHasSearched] = useState(false); 31 32 // playlist dropdown state 33 const [openPlaylistDropdown, setOpenPlaylistDropdown] = useState< 34 number | null 35 >(null); 36 const playlistDropdownRefs = useRef<{ [key: number]: HTMLDivElement | null }>( 37 {}, 38 ); 31 39 32 40 useEffect(() => { … … 43 51 fetchSongs(); 44 52 }, []); 53 54 // Handle click outside for playlist dropdown 55 useEffect(() => { 56 const handleClickOutside = (event: MouseEvent) => { 57 if (openPlaylistDropdown !== null) { 58 const ref = playlistDropdownRefs.current[openPlaylistDropdown]; 59 if (ref && !ref.contains(event.target as Node)) { 60 setOpenPlaylistDropdown(null); 61 } 62 } 63 }; 64 65 document.addEventListener("mousedown", handleClickOutside); 66 return () => { 67 document.removeEventListener("mousedown", handleClickOutside); 68 }; 69 }, [openPlaylistDropdown]); 70 71 const toggleLike = async (songId: number) => { 72 try { 73 await axiosInstance.post(`/musical-entity/${songId}/like`); 74 setSongs((prevSongs) => 75 prevSongs.map((song) => 76 song.id === songId 77 ? { ...song, isLikedByCurrentUser: !song.isLikedByCurrentUser } 78 : song, 79 ), 80 ); 81 } catch (error) { 82 console.error("Error toggling like:", error); 83 } 84 }; 85 86 const togglePlaylistDropdown = (songId: number) => { 87 setOpenPlaylistDropdown((prev) => (prev === songId ? null : songId)); 88 }; 89 90 const handleAddToPlaylist = (songId: number, playlistName: string) => { 91 console.log(`Adding song ${songId} to ${playlistName}`); 92 // TODO: Implement actual API call 93 setOpenPlaylistDropdown(null); 94 }; 95 96 const handleCreateNewPlaylist = (songId: number) => { 97 console.log(`Creating new playlist for song ${songId}`); 98 // TODO: Implement actual playlist creation 99 setOpenPlaylistDropdown(null); 100 }; 45 101 46 102 const performSearch = async (query: string, category: SearchCategory) => { … … 289 345 </div> 290 346 </div> 291 <div className="p-4 flex flex-col items-center">347 <div className="p-4"> 292 348 <h3 className="text-lg font-semibold mb-2 text-white overflow-hidden text-ellipsis whitespace-nowrap"> 293 349 {song.title} … … 299 355 {song.releasedBy} 300 356 </p> 357 <div className="flex items-center justify-between mt-4 pt-3 border-t border-white/10"> 358 {/* Like button */} 359 <button 360 onClick={(e) => { 361 e.stopPropagation(); 362 toggleLike(song.id); 363 }} 364 className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer" 365 aria-label={ 366 song.isLikedByCurrentUser 367 ? "Unlike song" 368 : "Like song" 369 } 370 > 371 {song.isLikedByCurrentUser ? ( 372 <svg 373 className="w-6 h-6 fill-[#1db954]" 374 viewBox="0 0 24 24" 375 > 376 <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" /> 377 </svg> 378 ) : ( 379 <svg 380 className="w-6 h-6" 381 fill="none" 382 stroke="currentColor" 383 viewBox="0 0 24 24" 384 > 385 <path 386 strokeLinecap="round" 387 strokeLinejoin="round" 388 strokeWidth={2} 389 d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" 390 /> 391 </svg> 392 )} 393 </button> 394 395 {/* Add to playlist button */} 396 <div 397 className="relative" 398 ref={(el) => { 399 if (el) 400 playlistDropdownRefs.current[song.id] = el; 401 }} 402 > 403 <button 404 onClick={(e) => { 405 e.stopPropagation(); 406 togglePlaylistDropdown(song.id); 407 }} 408 className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer" 409 aria-label="Add to playlist" 410 > 411 <svg 412 className="w-6 h-6" 413 fill="none" 414 stroke="currentColor" 415 viewBox="0 0 24 24" 416 > 417 <path 418 strokeLinecap="round" 419 strokeLinejoin="round" 420 strokeWidth={2} 421 d="M12 4v16m8-8H4" 422 /> 423 </svg> 424 </button> 425 426 {/* Playlist dropdown */} 427 {openPlaylistDropdown === song.id && ( 428 <div className="absolute right-0 bottom-full mb-2 w-48 bg-gray-700 rounded-lg shadow-lg py-1 z-50"> 429 <div className="px-3 py-2 text-xs text-gray-400 border-b border-white/10"> 430 Add to playlist 431 </div> 432 <button 433 onClick={(e) => { 434 e.stopPropagation(); 435 handleAddToPlaylist( 436 song.id, 437 "Playlist 1", 438 ); 439 }} 440 className="w-full text-left px-4 py-2 text-sm text-white hover:bg-gray-600 transition-colors" 441 > 442 Playlist 1 443 </button> 444 <button 445 onClick={(e) => { 446 e.stopPropagation(); 447 handleAddToPlaylist( 448 song.id, 449 "Playlist 2", 450 ); 451 }} 452 className="w-full text-left px-4 py-2 text-sm text-white hover:bg-gray-600 transition-colors" 453 > 454 Playlist 2 455 </button> 456 <button 457 onClick={(e) => { 458 e.stopPropagation(); 459 handleCreateNewPlaylist(song.id); 460 }} 461 className="w-full text-left px-4 py-2 text-sm text-[#1db954] hover:bg-gray-600 transition-colors border-t border-white/10 flex items-center gap-2" 462 > 463 <svg 464 className="w-4 h-4" 465 fill="none" 466 stroke="currentColor" 467 viewBox="0 0 24 24" 468 > 469 <path 470 strokeLinecap="round" 471 strokeLinejoin="round" 472 strokeWidth={2} 473 d="M12 4v16m8-8H4" 474 /> 475 </svg> 476 Create new playlist 477 </button> 478 </div> 479 )} 480 </div> 481 </div> 301 482 </div> 302 483 </div> -
frontend/src/utils/types.ts
r2ce7c1e rb071fb4 57 57 58 58 export type SearchCategory = "songs" | "albums" | "artists" | "users"; 59 60 export interface SidebarProps { 61 isOpen: boolean; 62 onClose: () => void; 63 } 64 65 export interface BasicSong { 66 id: number; 67 title: string; 68 artist: string; 69 cover?: string; 70 } 71 72 export interface BasicPlaylist { 73 id: number; 74 name: string; 75 songCount: number; 76 }
Note:
See TracChangeset
for help on using the changeset viewer.
