diff --git a/backend/api/routes.py b/backend/api/routes.py index 22f55a9..dc1c811 100644 --- a/backend/api/routes.py +++ b/backend/api/routes.py @@ -317,14 +317,18 @@ async def get_playlist(id: str): # Safely extract album album_name = extract_album_name(track, playlist_data.get('title', 'Single')) + video_id = track.get('videoId') + if not video_id: + continue + formatted_tracks.append({ "title": track.get('title', 'Unknown Title'), "artist": artist_names, "album": album_name, "duration": track.get('duration_seconds', track.get('length_seconds', 0)), "cover_url": cover_url, - "id": track.get('videoId'), - "url": f"https://music.youtube.com/watch?v={track.get('videoId')}" + "id": video_id, + "url": f"https://music.youtube.com/watch?v={video_id}" }) # Get Playlist Cover (usually highest res) diff --git a/frontend/app/playlist/page.tsx b/frontend/app/playlist/page.tsx index 18958e7..a73c925 100644 --- a/frontend/app/playlist/page.tsx +++ b/frontend/app/playlist/page.tsx @@ -1,5 +1,7 @@ "use client"; +import Link from 'next/link'; +import Image from 'next/image'; import { usePlayer } from "@/context/PlayerContext"; import { Play, Pause, Clock, Heart, MoreHorizontal, Plus } from "lucide-react"; import { useEffect, useState, Suspense } from "react"; @@ -127,7 +129,16 @@ function PlaylistContent() {
{/* Header */}
- {playlist.title} +
+ {playlist.title} +
Playlist

{playlist.title}

@@ -179,7 +190,7 @@ function PlaylistContent() {
- {playlist.tracks.map((track, i) => { + {playlist.tracks.filter(t => t.id).map((track, i) => { const isCurrent = currentTrack?.id === track.id; const isLiked = likedTracks.has(track.id); return ( @@ -198,7 +209,15 @@ function PlaylistContent() {
- +
+ {track.title} +
{/* Changed from truncate to line-clamp-2 for readability */} {track.title} diff --git a/frontend/components/CoverImage.tsx b/frontend/components/CoverImage.tsx index fd7d986..7bcc4e2 100644 --- a/frontend/components/CoverImage.tsx +++ b/frontend/components/CoverImage.tsx @@ -32,6 +32,8 @@ function getGradient(text: string): string { return gradients[Math.abs(hash) % gradients.length]; } +import Image from "next/image"; + export default function CoverImage({ src, alt, className = "", fallbackText }: CoverImageProps) { const [hasError, setHasError] = useState(false); const [isLoading, setIsLoading] = useState(true); @@ -53,24 +55,26 @@ export default function CoverImage({ src, alt, className = "", fallbackText }: C } return ( - <> +
{isLoading && (
)} - {alt} setIsLoading(false)} onError={() => { setHasError(true); setIsLoading(false); }} /> - +
); } diff --git a/frontend/components/Sidebar.tsx b/frontend/components/Sidebar.tsx index 91ab94a..f3a299e 100644 --- a/frontend/components/Sidebar.tsx +++ b/frontend/components/Sidebar.tsx @@ -161,15 +161,14 @@ export default function Sidebar() { {/* Albums */} {showAlbums && albums.map((album) => ( - +
-
- {album.cover_url ? ( - e.currentTarget.style.display = 'none'} /> - ) : ( - 💿 - )} -
+

{album.title}

Album • {album.creator || 'Spotify'}

diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index a2c7c4e..1834fd6 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -36,6 +36,14 @@ const nextConfig = { protocol: 'https', hostname: 'lh3.googleusercontent.com', }, + { + protocol: 'https', + hostname: 'yt3.googleusercontent.com', + }, + { + protocol: 'https', + hostname: 'yt3.ggpht.com', + }, { protocol: 'https', hostname: 'placehold.co',