import { useEffect, useState } from 'react'; import { usePlayerStore } from '../store/playerStore'; import { useQueueSessionStore } from '../store/queueSessionStore'; import { useUIStore } from '@/stores/ui'; import { cn } from '@/lib/utils'; import { X, GripVertical, ListMusic, Sparkles, Share2 } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { EmptyState } from '@/components/ui/empty-state'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useAuthStore } from '@/features/auth/store/authStore'; import { tracksApi } from '@/services/api/tracks'; import { queueApi } from '@/services/api/queue'; import toast from '@/utils/toast'; import type { Track as PlayerTrack } from '../types'; import type { Track as ApiTrack } from '@/features/tracks/types/track'; interface PlayerQueueProps { isOpen: boolean; onClose: () => void; currentTrackId?: string; onPlay: (track: any) => void; } function mapApiTrackToPlayerTrack(t: ApiTrack): PlayerTrack { const apiTrack = t as ApiTrack & { stream_manifest_url?: string; cover_art_path?: string }; return { id: t.id, title: t.title, artist: t.artist, duration: t.duration ?? 0, url: apiTrack.stream_manifest_url || `/api/v1/tracks/${t.id}/download`, cover: apiTrack.cover_art_path, genre: t.genre, like_count: t.like_count, }; } function mapSessionItemToPlayerTrack(item: { id: string; track?: { id: string; title?: string; artist?: string; duration?: number; cover_art_path?: string; genre?: string; like_count?: number } }): PlayerTrack | null { const t = item.track; if (!t) return null; return { id: t.id, title: t.title ?? '', artist: t.artist ?? '', duration: t.duration ?? 0, url: `/api/v1/tracks/${t.id}/download`, cover: t.cover_art_path, genre: t.genre, like_count: t.like_count, }; } export function PlayerQueue({ isOpen, onClose, onPlay }: PlayerQueueProps) { const { queue, currentIndex, removeFromQueue, clearQueue, setQueueFromSession } = usePlayerStore(); const { sidebarOpen } = useUIStore(); const queryClient = useQueryClient(); const { activeSessionToken, isCreator, setSession, clearSession } = useQueueSessionStore(); const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const [sessionItemIds, setSessionItemIds] = useState>(new Map()); // trackId -> itemId for remove // Detect ?session=TOKEN in URL (join via share link) useEffect(() => { const params = new URLSearchParams(window.location.search); const token = params.get('session'); if (token && isAuthenticated) { setSession(token, false); // Remove from URL without full reload params.delete('session'); const newUrl = window.location.pathname + (params.toString() ? `?${params}` : ''); window.history.replaceState({}, '', newUrl); } }, [isAuthenticated, setSession]); const { data: recommendations = [], isLoading: recommendationsLoading } = useQuery({ queryKey: ['track-recommendations'], queryFn: () => tracksApi.getRecommendations({ limit: 10 }), enabled: isOpen && isAuthenticated && queue.length === 0 && !activeSessionToken, staleTime: 60_000, }); const playerTracks = recommendations.map(mapApiTrackToPlayerTrack); // Poll session when active (v0.203 Lot D1) const { data: sessionData } = useQuery({ queryKey: ['queue-session', activeSessionToken], queryFn: () => queueApi.getQueueSession(activeSessionToken!), enabled: !!activeSessionToken && isOpen, refetchInterval: 8000, }); useEffect(() => { if (sessionData?.items && activeSessionToken) { const tracks = sessionData.items .map((it) => mapSessionItemToPlayerTrack(it)) .filter((t): t is PlayerTrack => t != null); const idMap = new Map(); sessionData.items.forEach((it) => { const t = (it as { id: string; track?: { id: string } }).track; if (t) idMap.set(t.id, it.id); }); setSessionItemIds(idMap); setQueueFromSession?.(tracks); } }, [sessionData, activeSessionToken, setQueueFromSession]); const shareMutation = useMutation({ mutationFn: async () => { const res = await queueApi.createQueueSession(); const token = res.session.share_token; for (const track of queue) { await queueApi.addToSessionQueue(token, track.id); } const fullUrl = `${window.location.origin}/?session=${token}`; await navigator.clipboard.writeText(fullUrl); setSession(token, true); return fullUrl; }, onSuccess: () => { toast.success('Lien copié ! Partagez la queue avec vos amis.'); }, onError: () => { toast.error('Impossible de créer la session partagée.'); }, }); const displayQueue: PlayerTrack[] = activeSessionToken && sessionData?.items?.length ? ( sessionData.items .map((it) => mapSessionItemToPlayerTrack(it)) .filter((t): t is PlayerTrack => t != null) ) : queue; if (!isOpen) return null; return (
{/* Header */}

Play Queue

{displayQueue.length} Tracks {activeSessionToken && ( Queue partagée )}
{isAuthenticated && displayQueue.length > 0 && !activeSessionToken && ( )} {activeSessionToken && isCreator && ( )}
{/* Content */}
{displayQueue.length === 0 ? (
{playerTracks.length > 0 ? ( <>

À écouter ensuite

{playerTracks.map((track) => (
onPlay(track)} >

{track.title}

{track.artist}

))}

Click to play

) : recommendationsLoading ? (
Loading suggestions…
) : ( } title="Your queue is empty" description="Add tracks to keep the vibe going." size="sm" className="border-0 shadow-none bg-transparent" /> )}
) : (
{displayQueue.map((track, index) => { const isCurrent = index === currentIndex; const isPast = index < currentIndex; return (
{/* Drag Handle (Simulated) */}
{/* Number/Status */}
{isCurrent ? (
) : ( index + 1 )}
{/* Info */}
!isCurrent && onPlay(track)} >

{track.title}

{track.artist}

{/* Actions */}
); })}
)}
{/* Backdrop for explicit dismissal on mobile if needed */}
); }