Changeset 9c1dcc7 for frontend/src/components
- Timestamp:
- 02/07/26 19:52:09 (5 months ago)
- Branches:
- main
- Children:
- 8675f75
- Parents:
- 8a47b30
- Location:
- frontend/src/components
- Files:
-
- 1 added
- 2 edited
-
MiniPlayer.tsx (added)
-
Sidebar.tsx (modified) (4 diffs)
-
search/SongResult.tsx (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/components/Sidebar.tsx
r8a47b30 r9c1dcc7 1 1 import { useEffect, useState } from "react"; 2 import { useNavigate } from "react-router-dom"; 2 3 import axiosInstance from "../api/axiosInstance"; 3 4 import { useAuth } from "../context/authContext"; 5 import { usePlayer } from "../context/playerContext"; 4 6 import type { BasicPlaylist, BasicSong, SidebarProps } from "../utils/types"; 7 8 const toEmbedUrl = (url: string): string => { 9 try { 10 const parsed = new URL(url); 11 if ( 12 (parsed.hostname === "www.youtube.com" || 13 parsed.hostname === "youtube.com") && 14 parsed.searchParams.has("v") 15 ) { 16 return `https://www.youtube.com/embed/${parsed.searchParams.get("v")}`; 17 } 18 if (parsed.hostname === "youtu.be") { 19 return `https://www.youtube.com/embed${parsed.pathname}`; 20 } 21 return url; 22 } catch { 23 return url; 24 } 25 }; 5 26 6 27 const Sidebar = ({ isOpen, onClose }: SidebarProps) => { 7 28 const { user } = useAuth(); 29 const navigate = useNavigate(); 30 const { play, currentSong } = usePlayer(); 8 31 const [recentlyListened, setRecentlyListened] = useState<BasicSong[]>([]); 9 32 const [playlists, setPlaylists] = useState<BasicPlaylist[]>([]); … … 74 97 <div 75 98 key={song.id} 76 className="flex items-center gap-3 p-2 rounded-lg hover:bg-white/5 cursor-pointer transition-colors" 99 onClick={() => navigate(`/songs/${song.id}`)} 100 className="flex items-center gap-3 p-2 rounded-lg hover:bg-white/5 cursor-pointer transition-colors group relative" 77 101 > 78 102 <img … … 80 104 alt={song.title} 81 105 className="w-10 h-10 rounded object-cover" 106 onError={(e) => { 107 (e.target as HTMLImageElement).src = "/favicon.png"; 108 }} 82 109 /> 83 110 <div className="flex-1 min-w-0"> … … 89 116 </p> 90 117 </div> 118 {song.link && ( 119 <button 120 onClick={(e) => { 121 e.stopPropagation(); 122 play({ 123 id: song.id, 124 title: song.title, 125 artist: song.artist, 126 cover: song.cover, 127 embedUrl: toEmbedUrl(song.link!), 128 }); 129 }} 130 className={`p-1.5 rounded-full transition-all opacity-0 group-hover:opacity-100 ${ 131 currentSong?.id === song.id 132 ? "bg-white text-[#1db954]" 133 : "bg-[#1db954] text-black hover:scale-110" 134 }`} 135 aria-label={ 136 currentSong?.id === song.id ? "Now playing" : "Play song" 137 } 138 > 139 {currentSong?.id === song.id ? ( 140 <svg 141 className="w-4 h-4" 142 fill="currentColor" 143 viewBox="0 0 24 24" 144 > 145 <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" /> 146 </svg> 147 ) : ( 148 <svg 149 className="w-4 h-4" 150 fill="currentColor" 151 viewBox="0 0 24 24" 152 > 153 <path d="M8 5v14l11-7z" /> 154 </svg> 155 )} 156 </button> 157 )} 91 158 </div> 92 159 ))} -
frontend/src/components/search/SongResult.tsx
r8a47b30 r9c1dcc7 1 import { useNavigate } from "react-router-dom"; 2 import { usePlayer } from "../../context/playerContext"; 1 3 import type { Song } from "../../utils/types"; 2 4 … … 5 7 } 6 8 9 const toEmbedUrl = (url: string): string => { 10 try { 11 const parsed = new URL(url); 12 if ( 13 (parsed.hostname === "www.youtube.com" || 14 parsed.hostname === "youtube.com") && 15 parsed.searchParams.has("v") 16 ) { 17 return `https://www.youtube.com/embed/${parsed.searchParams.get("v")}`; 18 } 19 if (parsed.hostname === "youtu.be") { 20 return `https://www.youtube.com/embed${parsed.pathname}`; 21 } 22 return url; 23 } catch { 24 return url; 25 } 26 }; 27 7 28 const SongResult = ({ song }: SongResultProps) => { 29 const navigate = useNavigate(); 30 const { play, currentSong } = usePlayer(); 31 8 32 return ( 9 <div className="flex items-center gap-4 p-3 rounded-lg hover:bg-white/5 cursor-pointer transition-colors group"> 33 <div 34 onClick={() => navigate(`/songs/${song.id}`)} 35 className="flex items-center gap-4 p-3 rounded-lg hover:bg-white/5 cursor-pointer transition-colors group" 36 > 10 37 <img 11 38 src={song.cover || "/favicon.png"} … … 22 49 </p> 23 50 </div> 24 <span className="text-xs text-gray-500 uppercase tracking-wider ">51 <span className="text-xs text-gray-500 uppercase tracking-wider mr-2"> 25 52 {song.genre} 26 53 </span> 54 {song.link && ( 55 <button 56 onClick={(e) => { 57 e.stopPropagation(); 58 play({ 59 id: song.id, 60 title: song.title, 61 artist: song.releasedBy, 62 cover: song.cover, 63 embedUrl: toEmbedUrl(song.link!), 64 }); 65 }} 66 className={`p-2 rounded-full transition-all opacity-0 group-hover:opacity-100 ${ 67 currentSong?.id === song.id 68 ? "bg-white text-[#1db954]" 69 : "bg-[#1db954] text-black hover:scale-110" 70 }`} 71 aria-label={currentSong?.id === song.id ? "Now playing" : "Play song"} 72 > 73 {currentSong?.id === song.id ? ( 74 <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"> 75 <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" /> 76 </svg> 77 ) : ( 78 <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"> 79 <path d="M8 5v14l11-7z" /> 80 </svg> 81 )} 82 </button> 83 )} 27 84 </div> 28 85 );
Note:
See TracChangeset
for help on using the changeset viewer.
