jefflix-website/components/music/playlist-picker.tsx

171 lines
5.7 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Button } from '@/components/ui/button'
import { ListPlus, Plus, Loader2, CheckCircle } from 'lucide-react'
import { useMusicPlayer } from './music-provider'
interface Playlist {
id: string
name: string
songCount: number
coverArt: string
}
export function PlaylistPicker({
open,
onOpenChange,
}: {
open: boolean
onOpenChange: (open: boolean) => void
}) {
const { state } = useMusicPlayer()
const [playlists, setPlaylists] = useState<Playlist[]>([])
const [loading, setLoading] = useState(false)
const [adding, setAdding] = useState<string | null>(null)
const [added, setAdded] = useState<string | null>(null)
const [creating, setCreating] = useState(false)
const [newName, setNewName] = useState('')
useEffect(() => {
if (!open) return
setLoading(true)
setAdded(null)
fetch('/api/music/playlists')
.then((r) => r.json())
.then((d) => setPlaylists(d.playlists || []))
.catch(() => {})
.finally(() => setLoading(false))
}, [open])
const songId = state.currentTrack?.id
if (!songId) return null
const addToPlaylist = async (playlistId: string) => {
setAdding(playlistId)
try {
await fetch(`/api/music/playlist/${playlistId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ songId }),
})
setAdded(playlistId)
} catch {}
setAdding(null)
}
const createPlaylist = async () => {
if (!newName.trim()) return
setCreating(true)
try {
await fetch('/api/music/playlist/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: newName.trim(), songId }),
})
setAdded('new')
setNewName('')
} catch {}
setCreating(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<ListPlus className="h-5 w-5" />
Add to Playlist
</DialogTitle>
<DialogDescription>
Add &ldquo;{state.currentTrack?.title}&rdquo; to an existing playlist or create a new one.
</DialogDescription>
</DialogHeader>
{loading ? (
<div className="flex justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : (
<>
<ScrollArea className="max-h-[300px]">
<div className="space-y-1">
{playlists.map((p) => (
<button
key={p.id}
onClick={() => addToPlaylist(p.id)}
disabled={adding !== null}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-md hover:bg-muted/50 transition-colors text-left"
>
<div className="flex-shrink-0 w-10 h-10 rounded overflow-hidden bg-muted">
{p.coverArt ? (
<img
src={`/api/music/cover/${p.coverArt}?size=80`}
alt={p.name}
className="w-full h-full object-cover"
loading="lazy"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<ListPlus className="h-4 w-4 text-muted-foreground" />
</div>
)}
</div>
<div className="flex-1 min-w-0">
<div className="font-medium text-sm truncate">{p.name}</div>
<div className="text-xs text-muted-foreground">{p.songCount} songs</div>
</div>
{adding === p.id ? (
<Loader2 className="h-4 w-4 animate-spin flex-shrink-0" />
) : added === p.id ? (
<CheckCircle className="h-4 w-4 text-green-500 flex-shrink-0" />
) : null}
</button>
))}
{playlists.length === 0 && (
<p className="text-sm text-muted-foreground text-center py-4">
No playlists yet. Create one below.
</p>
)}
</div>
</ScrollArea>
<div className="flex gap-2 pt-2 border-t border-border">
<input
type="text"
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder="New playlist name..."
className="flex-1 px-3 py-2 text-sm rounded-md border border-border bg-background focus:outline-none focus:ring-2 focus:ring-ring"
onKeyDown={(e) => e.key === 'Enter' && createPlaylist()}
/>
<Button
size="sm"
onClick={createPlaylist}
disabled={!newName.trim() || creating}
>
{creating ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
</Button>
</div>
{added === 'new' && (
<p className="text-sm text-green-500 flex items-center gap-1">
<CheckCircle className="h-3.5 w-3.5" />
Playlist created with song added
</p>
)}
</>
)}
</DialogContent>
</Dialog>
)
}