"use client"; import { useState, useEffect, useRef } from "react"; import { useParams, useRouter } from "next/navigation"; import { ArrowLeft, Play, Pause, Download, Loader2, ExternalLink, Music2, Volume2, VolumeX, } from "lucide-react"; import { api } from "@/lib/api"; import { useToast } from "@/lib/toast-context"; import { cn } from "@/utils/cn"; import { GradientSpinner } from "@/components/ui/GradientSpinner"; // Deezer icon component const DeezerIcon = ({ className }: { className?: string }) => ( ); // Types for Deezer playlist interface DeezerTrack { deezerId: string; title: string; artist: string; artistId: string; album: string; albumId: string; durationMs: number; previewUrl: string | null; coverUrl: string | null; } interface DeezerPlaylistFull { id: string; title: string; description: string | null; creator: string; imageUrl: string | null; trackCount: number; tracks: DeezerTrack[]; isPublic: boolean; source: string; url: string; } export default function DeezerPlaylistDetailPage() { const params = useParams(); const router = useRouter(); const { toast } = useToast(); const playlistId = params.id as string; // State const [playlist, setPlaylist] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [isImporting, setIsImporting] = useState(false); // Preview playback state const [playingTrackId, setPlayingTrackId] = useState(null); const [isPreviewPlaying, setIsPreviewPlaying] = useState(false); const [previewVolume, setPreviewVolume] = useState(0.5); const [isMuted, setIsMuted] = useState(false); const audioRef = useRef(null); // Fetch playlist data useEffect(() => { async function fetchPlaylist() { setIsLoading(true); setError(null); try { const data = await api.get( `/browse/playlists/${playlistId}` ); setPlaylist(data); } catch (err) { const message = err instanceof Error ? err.message : "Failed to load playlist"; setError(message); } finally { setIsLoading(false); } } fetchPlaylist(); }, [playlistId]); // Cleanup audio on unmount useEffect(() => { return () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } }; }, []); // Handle preview playback const handlePlayPreview = (track: DeezerTrack) => { if (!track.previewUrl) { toast.error("No preview available for this track"); return; } // If clicking the same track, toggle play/pause if (playingTrackId === track.deezerId) { if (isPreviewPlaying && audioRef.current) { audioRef.current.pause(); setIsPreviewPlaying(false); } else if (audioRef.current) { audioRef.current.play(); setIsPreviewPlaying(true); } return; } // Stop current preview if (audioRef.current) { audioRef.current.pause(); } // Play new preview const audio = new Audio(track.previewUrl); audio.volume = isMuted ? 0 : previewVolume; audioRef.current = audio; audio.onended = () => { setPlayingTrackId(null); setIsPreviewPlaying(false); }; audio.onerror = () => { toast.error("Failed to play preview"); setPlayingTrackId(null); setIsPreviewPlaying(false); }; audio.play(); setPlayingTrackId(track.deezerId); setIsPreviewPlaying(true); }; // Stop preview const stopPreview = () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } setPlayingTrackId(null); setIsPreviewPlaying(false); }; // Toggle mute const toggleMute = () => { if (audioRef.current) { audioRef.current.volume = isMuted ? previewVolume : 0; } setIsMuted(!isMuted); }; // Handle import/download const handleImport = () => { if (!playlist) return; // Navigate to import page with the Deezer URL router.push( `/import/spotify?url=${encodeURIComponent(playlist.url)}` ); }; // Format duration const formatDuration = (ms: number) => { const minutes = Math.floor(ms / 60000); const seconds = Math.floor((ms % 60000) / 1000); return `${minutes}:${seconds.toString().padStart(2, "0")}`; }; // Calculate total duration const totalDuration = playlist?.tracks.reduce( (sum, track) => sum + track.durationMs, 0 ) || 0; const formatTotalDuration = (ms: number) => { const hours = Math.floor(ms / 3600000); const mins = Math.floor((ms % 3600000) / 60000); if (hours > 0) { return `about ${hours} hr ${mins} min`; } return `${mins} min`; }; if (isLoading) { return ( ); } if (error || !playlist) { return ( router.back()} className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors mb-8" > Back Playlist not found {error || "This playlist may be private or no longer available."} router.push("/browse/playlists")} className="px-6 py-2.5 rounded-full bg-white text-black text-sm font-medium hover:scale-105 transition-transform" > Browse playlists ); } return ( {/* Hero Section */} {/* Cover Art */} {playlist.imageUrl ? ( ) : ( )} {/* Playlist Info */} Deezer Playlist {playlist.title} {playlist.description && ( {playlist.description} )} {playlist.creator} • {playlist.trackCount} songs {totalDuration > 0 && ( <> , {formatTotalDuration(totalDuration)} > )} {/* Action Bar */} {/* Download/Import Button */} {isImporting ? ( ) : ( )} {isImporting ? "Importing..." : "Download & Create Playlist"} {/* Volume Control (when playing preview) */} {playingTrackId && ( {isMuted ? ( ) : ( )} Stop Preview )} {/* Spacer */} {/* Open in Deezer */} Open in Deezer {/* Track Listing */} {playlist.tracks.length > 0 ? ( {/* Table Header */} # Title Album Duration {/* Track Rows */} {playlist.tracks.map((track, index) => { const isCurrentlyPlaying = playingTrackId === track.deezerId; const hasPreview = !!track.previewUrl; return ( hasPreview && handlePlayPreview(track) } className={cn( "grid grid-cols-[40px_1fr_auto] md:grid-cols-[40px_minmax(200px,4fr)_minmax(100px,1fr)_80px] gap-4 px-4 py-2 rounded-md transition-colors group", hasPreview ? "hover:bg-white/5 cursor-pointer" : "opacity-60 cursor-not-allowed", isCurrentlyPlaying && "bg-white/10" )} > {/* Track Number / Play Icon */} {hasPreview ? ( <> {isCurrentlyPlaying && isPreviewPlaying ? ( ) : ( index + 1 )} > ) : ( {index + 1} )} {/* Title + Artist */} {track.coverUrl ? ( ) : ( )} {track.title} {track.artist} {/* Album (hidden on mobile) */} {track.album} {/* Duration */} {formatDuration(track.durationMs)} ); })} ) : ( No tracks found This playlist appears to be empty )} {/* Preview indicator */} {playingTrackId && ( Playing 30s preview )} ); }
{error || "This playlist may be private or no longer available."}
Deezer Playlist
{playlist.description}
{track.title}
{track.artist}
{track.album}
This playlist appears to be empty