- 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
109 lines
3.9 KiB
TypeScript
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>
|
|
)
|
|
}
|