Changeset 9c1dcc7 for frontend/src/pages
- Timestamp:
- 02/07/26 19:52:09 (5 months ago)
- Branches:
- main
- Children:
- 8675f75
- Parents:
- 8a47b30
- Location:
- frontend/src/pages
- Files:
-
- 2 edited
-
LandingPage.tsx (modified) (5 diffs)
-
SongDetail.tsx (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/pages/LandingPage.tsx
r8a47b30 r9c1dcc7 1 1 import { useEffect, useRef, useState } from "react"; 2 import { useNavigate } from "react-router-dom"; 2 3 import axiosInstance from "../api/axiosInstance"; 3 4 import AlbumResult from "../components/search/AlbumResult"; 4 5 import SongResult from "../components/search/SongResult"; 5 6 import UserResult from "../components/search/UserResult"; 7 import { usePlayer } from "../context/playerContext"; 6 8 import type { 7 9 Album, … … 11 13 } from "../utils/types"; 12 14 15 // Convert a regular YouTube URL to an embeddable URL 16 const toEmbedUrl = (url: string): string => { 17 try { 18 const parsed = new URL(url); 19 if ( 20 (parsed.hostname === "www.youtube.com" || 21 parsed.hostname === "youtube.com") && 22 parsed.searchParams.has("v") 23 ) { 24 return `https://www.youtube.com/embed/${parsed.searchParams.get("v")}`; 25 } 26 if (parsed.hostname === "youtu.be") { 27 return `https://www.youtube.com/embed${parsed.pathname}`; 28 } 29 return url; 30 } catch { 31 return url; 32 } 33 }; 34 13 35 const CATEGORIES: { value: SearchCategory; label: string }[] = [ 14 36 { value: "songs", label: "Songs" }, … … 19 41 20 42 const LandingPage = () => { 43 const navigate = useNavigate(); 44 const { play, currentSong } = usePlayer(); 21 45 const [songs, setSongs] = useState<Song[]>([]); 22 46 const [loading, setLoading] = useState<boolean>(true); … … 327 351 <div 328 352 key={song.id} 353 onClick={() => navigate(`/songs/${song.id}`)} 329 354 className="bg-[#282828] rounded-xl overflow-hidden cursor-pointer shadow-lg transition-all duration-300 ease-in-out hover:-translate-y-2 hover:shadow-2xl group" 330 355 > … … 340 365 /> 341 366 <div className="absolute top-0 left-0 w-full h-full bg-black/60 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300"> 342 <div className="w-15 h-15 rounded-full bg-[#1db954] flex items-center justify-center text-2xl text-black shadow-[0_4px_12px_rgba(29,185,84,0.5)]"> 343 ▶ 344 </div> 367 {song.link ? ( 368 <button 369 onClick={(e) => { 370 e.stopPropagation(); 371 play({ 372 id: song.id, 373 title: song.title, 374 artist: song.releasedBy, 375 cover: song.cover, 376 embedUrl: toEmbedUrl(song.link!), 377 }); 378 }} 379 className={`w-14 h-14 rounded-full flex items-center justify-center text-xl shadow-[0_4px_12px_rgba(29,185,84,0.5)] transition-all cursor-pointer ${ 380 currentSong?.id === song.id 381 ? "bg-white text-[#1db954]" 382 : "bg-[#1db954] text-black hover:scale-105" 383 }`} 384 aria-label={ 385 currentSong?.id === song.id 386 ? "Now playing" 387 : "Play song" 388 } 389 > 390 {currentSong?.id === song.id ? ( 391 <svg 392 className="w-6 h-6" 393 fill="currentColor" 394 viewBox="0 0 24 24" 395 > 396 <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" /> 397 </svg> 398 ) : ( 399 <svg 400 className="w-6 h-6" 401 fill="currentColor" 402 viewBox="0 0 24 24" 403 > 404 <path d="M8 5v14l11-7z" /> 405 </svg> 406 )} 407 </button> 408 ) : ( 409 <div className="w-14 h-14 rounded-full bg-[#1db954] flex items-center justify-center text-xl text-black shadow-[0_4px_12px_rgba(29,185,84,0.5)]"> 410 ▶ 411 </div> 412 )} 345 413 </div> 346 414 </div> -
frontend/src/pages/SongDetail.tsx
r8a47b30 r9c1dcc7 3 3 import axiosInstance from "../api/axiosInstance"; 4 4 import { useAuth } from "../context/authContext"; 5 import { usePlayer } from "../context/playerContext"; 5 6 import type { SongDetail as SongDetailType } from "../utils/types"; 6 7 … … 62 63 const { id } = useParams<{ id: string }>(); 63 64 const { user } = useAuth(); 65 const { play, currentSong } = usePlayer(); 64 66 const [song, setSong] = useState<SongDetailType | null>(null); 65 67 const [loading, setLoading] = useState(true); … … 72 74 const response = await axiosInstance.get(`/songs/${id}/details`); 73 75 setSong(response.data); 76 // Don't autoplay - user must click play button 74 77 } catch (err) { 75 78 console.error("Error fetching song details:", err); … … 207 210 208 211 {/* Action buttons */} 209 {/* todo: add add to playlist button */}210 212 <div className="flex items-center gap-3 mt-4"> 213 {/* Play button */} 214 {song.link && ( 215 <button 216 onClick={() => 217 play({ 218 id: song.id, 219 title: song.title, 220 artist: song.releasedBy, 221 cover: song.cover, 222 embedUrl: toEmbedUrl(song.link!), 223 }) 224 } 225 className={`flex items-center gap-2 px-6 py-3 rounded-full text-sm font-semibold transition-all cursor-pointer ${ 226 currentSong?.id === song.id 227 ? "bg-white text-[#1db954]" 228 : "bg-[#1db954] text-black hover:bg-[#1ed760] hover:scale-105" 229 }`} 230 > 231 {currentSong?.id === song.id ? ( 232 <> 233 <svg 234 className="w-5 h-5" 235 fill="currentColor" 236 viewBox="0 0 24 24" 237 > 238 <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" /> 239 </svg> 240 Now Playing 241 </> 242 ) : ( 243 <> 244 <svg 245 className="w-5 h-5" 246 fill="currentColor" 247 viewBox="0 0 24 24" 248 > 249 <path d="M8 5v14l11-7z" /> 250 </svg> 251 Play Song 252 </> 253 )} 254 </button> 255 )} 256 211 257 {user && ( 212 258 <button … … 248 294 </div> 249 295 250 {/* YouTube embed*/}296 {/* Play button — plays in persistent mini-player */} 251 297 {song.link && ( 252 298 <section className="mb-10"> 253 <div className="rounded-xl overflow-hidden shadow-lg aspect-video bg-black"> 254 <iframe 255 src={toEmbedUrl(song.link)} 256 title={song.title} 257 className="w-full h-full" 258 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 259 allowFullScreen 260 /> 261 </div> 299 {currentSong?.id === song.id ? ( 300 <div className="bg-[#1a1a2e]/60 rounded-xl p-4 flex items-center gap-3"> 301 <div className="w-10 h-10 rounded-full bg-[#1db954] flex items-center justify-center text-black animate-pulse"> 302 <svg 303 className="w-5 h-5" 304 fill="currentColor" 305 viewBox="0 0 24 24" 306 > 307 <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" /> 308 </svg> 309 </div> 310 <p className="text-[#1db954] font-medium"> 311 Now playing in mini-player below 312 </p> 313 </div> 314 ) : ( 315 <button 316 onClick={() => 317 play({ 318 id: song.id, 319 title: song.title, 320 artist: song.releasedBy, 321 cover: song.cover, 322 embedUrl: toEmbedUrl(song.link!), 323 }) 324 } 325 className="w-full bg-[#1a1a2e]/60 hover:bg-[#1a1a2e]/80 rounded-xl p-4 flex items-center gap-3 transition-colors cursor-pointer group" 326 > 327 <div className="w-10 h-10 rounded-full bg-[#1db954] flex items-center justify-center text-black group-hover:scale-105 transition-transform"> 328 <svg 329 className="w-5 h-5" 330 fill="currentColor" 331 viewBox="0 0 24 24" 332 > 333 <path d="M8 5v14l11-7z" /> 334 </svg> 335 </div> 336 <p className="text-white font-medium">Play this song</p> 337 </button> 338 )} 262 339 </section> 263 340 )}
Note:
See TracChangeset
for help on using the changeset viewer.
