Fix duplicate keys, optimize images, and update Next.js config
This commit is contained in:
parent
d50c2721d2
commit
7a58c2357d
5 changed files with 52 additions and 18 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<div className="h-full overflow-y-auto no-scrollbar bg-gradient-to-b from-gray-900 via-[#121212] to-[#121212]">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row items-center md:items-end gap-6 p-8 bg-gradient-to-b from-transparent to-black/20 pt-20 text-center md:text-left">
|
||||
<img src={playlist.cover_url} alt={playlist.title} className="w-52 h-52 md:w-60 md:h-60 shadow-2xl rounded-md object-cover" />
|
||||
<div className="relative w-52 h-52 md:w-60 md:h-60 shadow-2xl rounded-md overflow-hidden shrink-0">
|
||||
<Image
|
||||
src={playlist.cover_url}
|
||||
alt={playlist.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="(max-width: 768px) 208px, 240px"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 w-full md:w-auto">
|
||||
<span className="text-sm font-bold uppercase hidden md:block">Playlist</span>
|
||||
<h1 className="text-2xl md:text-6xl font-black tracking-tight text-white mb-2 md:mb-4 line-clamp-2 leading-tight">{playlist.title}</h1>
|
||||
|
|
@ -179,7 +190,7 @@ function PlaylistContent() {
|
|||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
{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() {
|
|||
</span>
|
||||
|
||||
<div className="flex items-center gap-3 min-w-0 overflow-hidden">
|
||||
<img src={track.cover_url} className="w-10 h-10 rounded shadow-sm object-cover shrink-0" alt="" />
|
||||
<div className="relative w-10 h-10 rounded shadow-sm overflow-hidden shrink-0">
|
||||
<Image
|
||||
src={track.cover_url}
|
||||
alt={track.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="40px"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col min-w-0 pr-2">
|
||||
{/* Changed from truncate to line-clamp-2 for readability */}
|
||||
<span className={`font-semibold text-base leading-tight line-clamp-2 break-words ${isCurrent ? 'text-green-500' : 'text-white'}`}>{track.title}</span>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<div className={`relative overflow-hidden ${className} bg-[#282828]`}>
|
||||
{isLoading && (
|
||||
<div
|
||||
className={`bg-gradient-to-br ${gradient} flex items-center justify-center text-white animate-pulse ${className}`}
|
||||
className={`absolute inset-0 bg-gradient-to-br ${gradient} flex items-center justify-center text-white animate-pulse z-10`}
|
||||
>
|
||||
<span className="opacity-50">♪</span>
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
className={`${className} ${isLoading ? 'hidden' : ''}`}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className={`object-cover ${isLoading ? 'opacity-0' : 'opacity-100'} transition-opacity duration-300`}
|
||||
onLoad={() => setIsLoading(false)}
|
||||
onError={() => {
|
||||
setHasError(true);
|
||||
setIsLoading(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,15 +161,14 @@ export default function Sidebar() {
|
|||
|
||||
{/* Albums */}
|
||||
{showAlbums && albums.map((album) => (
|
||||
<Link href={`/ search ? q = ${encodeURIComponent(album.title)} `} key={album.id}>
|
||||
<Link href={`/search?q=${encodeURIComponent(album.title)}`} key={album.id}>
|
||||
<div className="flex items-center gap-3 p-2 rounded-md hover:bg-[#1a1a1a] cursor-pointer">
|
||||
<div className="w-12 h-12 bg-[#282828] rounded flex items-center justify-center overflow-hidden relative">
|
||||
{album.cover_url ? (
|
||||
<img src={album.cover_url} alt="" className="w-full h-full object-cover" onError={(e) => e.currentTarget.style.display = 'none'} />
|
||||
) : (
|
||||
<span className="text-xl">💿</span>
|
||||
)}
|
||||
</div>
|
||||
<CoverImage
|
||||
src={album.cover_url}
|
||||
alt={album.title}
|
||||
className="w-12 h-12 rounded object-cover"
|
||||
fallbackText="💿"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-medium truncate">{album.title}</h3>
|
||||
<p className="text-sm text-spotify-text-muted truncate">Album • {album.creator || 'Spotify'}</p>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue