"use client"; import { useState, useMemo } from "react"; import { useParams, useRouter } from "next/navigation"; import Image from "next/image"; import { api } from "@/lib/api"; import { useAudio } from "@/lib/audio-context"; import { GradientSpinner } from "@/components/ui/GradientSpinner"; import { Play, Pause, Music, Shuffle, Save, ListPlus } from "lucide-react"; import { cn } from "@/utils/cn"; import { toast } from "sonner"; import { useMixQuery } from "@/hooks/useQueries"; interface MixTrack { id: string; title: string; duration: number; albumId: string; album: { title: string; coverUrl?: string; artist: { id: string; name: string; }; }; } export default function MixPage() { const params = useParams(); const router = useRouter(); const mixId = params.id as string; const { playTracks, addToQueue, currentTrack, isPlaying, pause, resume } = useAudio(); const { data: mix, isLoading } = useMixQuery(mixId); const [isSaving, setIsSaving] = useState(false); // Calculate total duration const totalDuration = useMemo(() => { if (!mix?.tracks) return 0; return mix.tracks.reduce((sum: number, track: MixTrack) => sum + (track.duration || 0), 0); }, [mix?.tracks]); const formatTotalDuration = (seconds: number) => { const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); if (hours > 0) { return `about ${hours} hr ${mins} min`; } return `${mins} min`; }; const formatDuration = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, "0")}`; }; // Check if this mix is currently playing const mixTrackIds = useMemo(() => { return new Set(mix?.tracks?.map((track: MixTrack) => track.id) || []); }, [mix?.tracks]); const isThisMixPlaying = useMemo(() => { if (!isPlaying || !currentTrack || !mix?.tracks?.length) return false; return mixTrackIds.has(currentTrack.id); }, [isPlaying, currentTrack, mixTrackIds, mix?.tracks?.length]); const formatTracksForPlayback = (tracks: MixTrack[]) => { return tracks.map((track) => ({ id: track.id, title: track.title, artist: { name: track.album.artist.name, id: track.album.artist.id, }, album: { title: track.album.title, coverArt: track.album.coverUrl, id: track.albumId, }, duration: track.duration, })); }; const handlePlayMix = () => { if (!mix?.tracks || mix.tracks.length === 0) return; // If this mix is playing, toggle pause/resume if (isThisMixPlaying) { if (isPlaying) { pause(); } else { resume(); } return; } const tracks = formatTracksForPlayback(mix.tracks); playTracks(tracks, 0); }; const handlePlayTrack = (index: number) => { if (!mix?.tracks || mix.tracks.length === 0) return; const tracks = formatTracksForPlayback(mix.tracks); playTracks(tracks, index); }; const handleShuffle = () => { if (!mix?.tracks) return; const tracks = formatTracksForPlayback(mix.tracks); const shuffled = [...tracks].sort(() => Math.random() - 0.5); playTracks(shuffled, 0); }; const handleAddToQueue = (track: MixTrack) => { const formattedTrack = { id: track.id, title: track.title, artist: { name: track.album.artist.name, id: track.album.artist.id, }, album: { title: track.album.title, coverArt: track.album.coverUrl, id: track.albumId, }, duration: track.duration, }; addToQueue(formattedTrack); toast.success(`Added ${track.title} to queue`); }; const handleSaveAsPlaylist = async () => { if (!mix) return; setIsSaving(true); try { const result = await api.saveMixAsPlaylist(mixId); toast.success(`Saved as "${result.name}" playlist!`); window.dispatchEvent(new Event("playlist-created")); setTimeout(() => { router.push(`/playlist/${result.id}`); }, 1000); } catch (error: unknown) { console.error("Failed to save mix as playlist:", error); const err = error as { status?: number; data?: { playlistId?: string } }; if (err?.status === 409) { toast.info("You've already saved this mix as a playlist."); if (err?.data?.playlistId) { setTimeout(() => { router.push(`/playlist/${err.data!.playlistId}`); }, 1000); } } else if (error instanceof Error) { toast.error(error.message); } else { toast.error("Failed to save mix as playlist"); } } finally { setIsSaving(false); } }; if (isLoading) { return (
Mix not found
Mix
{mix.description}
)}{track.title}
{track.album.artist.name}
{track.album.title}
{/* Duration + Actions */}This mix is empty