fediversion/frontend/components/songs/rate-performance-dialog.tsx
fullsizemalt b4cddf41ea feat: Initialize Fediversion multi-band platform
- Fork elmeg-demo codebase for multi-band support
- Add data importer infrastructure with base class
- Create band-specific importers:
  - phish.py: Phish.net API v5
  - grateful_dead.py: Grateful Stats API
  - setlistfm.py: Dead & Company, Billy Strings (Setlist.fm)
- Add spec-kit configuration for Gemini
- Update README with supported bands and architecture
2025-12-28 12:39:28 -08:00

109 lines
3.9 KiB
TypeScript

"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Star } from "lucide-react"
import { getApiUrl } from "@/lib/api-config"
import { useAuth } from "@/contexts/auth-context"
interface RatePerformanceProps {
performanceId: number
performanceDate: string
venue: string
currentRating?: number
onRatingSubmit?: () => void
}
export function RatePerformanceDialog({ performanceId, performanceDate, venue, currentRating, onRatingSubmit }: RatePerformanceProps) {
const [rating, setRating] = useState(currentRating || 0)
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const { user } = useAuth()
const handleRate = async () => {
if (!user) return
setLoading(true)
const token = localStorage.getItem("token")
try {
const res = await fetch(`${getApiUrl()}/social/ratings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify({
performance_id: performanceId,
score: rating
})
})
if (res.ok) {
setOpen(false)
if (onRatingSubmit) onRatingSubmit()
}
} catch (e) {
console.error(e)
} finally {
setLoading(false)
}
}
// If not logged in, just show static star or nothing?
// We'll let Button trigger login flow or disabled.
if (!user) {
return (
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 disabled:opacity-50" disabled title="Log in to rate">
<Star className="h-4 w-4 text-muted-foreground" />
</Button>
)
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0" title="Rate this version">
<Star className={`h-4 w-4 ${currentRating ? "fill-yellow-500 text-yellow-500" : "text-muted-foreground"}`} />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Rate Performance</DialogTitle>
<DialogDescription>
{performanceDate} at {venue}
</DialogDescription>
</DialogHeader>
<div className="flex justify-center gap-2 py-6">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
type="button"
onClick={() => setRating(star)}
className={`p-1 transition-transform hover:scale-110 focus:outline-none ${rating >= star ? "text-yellow-500" : "text-muted-foreground/30 hover:text-yellow-500/70"
}`}
>
<Star className={`h-8 w-8 ${rating >= star ? "fill-current" : ""}`} />
</button>
))}
</div>
<div className="text-center font-bold text-lg mb-4 text-muted-foreground">
{rating > 0 ? `${rating} Stars` : "Tap to rate"}
</div>
<DialogFooter>
<Button type="submit" onClick={handleRate} disabled={loading || rating === 0}>
{loading ? "Submitting..." : "Submit Rating"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}