fediversion/frontend/components/playlists/add-to-playlist-dialog.tsx
fullsizemalt 7b8ba4b54c
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
feat: User Personalization, Playlists, Recommendations, and DSO Importer
2025-12-29 16:28:43 -08:00

148 lines
5.6 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { ListMusic, Plus, Loader2 } from "lucide-react"
import { useToast } from "@/components/ui/use-toast"
import { getApiUrl } from "@/lib/api-config"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
interface AddToPlaylistDialogProps {
performanceId: number
songTitle: string
}
export function AddToPlaylistDialog({ performanceId, songTitle }: AddToPlaylistDialogProps) {
const [open, setOpen] = useState(false)
const [playlists, setPlaylists] = useState<any[]>([])
const [loading, setLoading] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [selectedPlaylistId, setSelectedPlaylistId] = useState<string>("")
const [notes, setNotes] = useState("")
const { toast } = useToast()
useEffect(() => {
if (open && playlists.length === 0) {
setLoading(true)
const token = localStorage.getItem("token")
if (!token) return
fetch(`${getApiUrl()}/playlists/mine`, {
headers: { Authorization: `Bearer ${token}` }
})
.then(res => res.json())
.then(data => setPlaylists(data))
.catch(err => console.error(err))
.finally(() => setLoading(false))
}
}, [open, playlists.length])
const handleSubmit = async () => {
if (!selectedPlaylistId) return
setSubmitting(true)
const token = localStorage.getItem("token")
try {
const res = await fetch(`${getApiUrl()}/playlists/${selectedPlaylistId}/performances/${performanceId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
},
body: JSON.stringify({ notes: notes || undefined })
})
if (res.ok) {
toast({ title: "Added to playlist" })
setOpen(false)
} else if (res.status === 400) {
toast({ title: "Already in playlist", variant: "default" })
} else {
throw new Error("Failed to add")
}
} catch (error) {
toast({ title: "Error", description: "Could not add to playlist", variant: "destructive" })
} finally {
setSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground hover:text-primary" title="Add to Playlist">
<ListMusic className="h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Add to Playlist</DialogTitle>
<DialogDescription>
Add &quot;{songTitle}&quot; to one of your collections.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="space-y-2">
<Label htmlFor="playlist">Select Playlist</Label>
<Select onValueChange={setSelectedPlaylistId} value={selectedPlaylistId}>
<SelectTrigger>
<SelectValue placeholder="Select a playlist" />
</SelectTrigger>
<SelectContent>
{loading ? (
<div className="flex items-center justify-center p-2">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
) : playlists.length === 0 ? (
<div className="p-2 text-sm text-muted-foreground text-center">No playlists found</div>
) : (
playlists.map((p) => (
<SelectItem key={p.id} value={p.id.toString()}>
{p.name}
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="notes">Notes (Optional)</Label>
<Input
id="notes"
placeholder="Why did you pick this version?"
value={notes}
onChange={(e) => setNotes(e.target.value)}
/>
</div>
</div>
<DialogFooter>
<Button onClick={handleSubmit} disabled={submitting || !selectedPlaylistId}>
{submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Add to Playlist
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}