156 lines
5.0 KiB
TypeScript
156 lines
5.0 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
|
|
}
|
|
|
|
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 “{state.currentTrack?.title}” 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 justify-between px-3 py-2.5 rounded-md hover:bg-muted/50 transition-colors text-left"
|
|
>
|
|
<div>
|
|
<div className="font-medium text-sm">{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" />
|
|
) : added === p.id ? (
|
|
<CheckCircle className="h-4 w-4 text-green-500" />
|
|
) : 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>
|
|
)
|
|
}
|