89 lines
3 KiB
TypeScript
89 lines
3 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { StarRating } from "@/components/ui/star-rating"
|
|
import { getApiUrl } from "@/lib/api-config"
|
|
|
|
interface EntityRatingProps {
|
|
entityType: "show" | "song" | "venue" | "tour" | "performance"
|
|
entityId: number
|
|
compact?: boolean
|
|
}
|
|
|
|
export function EntityRating({ entityType, entityId, compact = false }: EntityRatingProps) {
|
|
const [userRating, setUserRating] = useState(0)
|
|
const [averageRating, setAverageRating] = useState(0)
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
useEffect(() => {
|
|
// Fetch average rating
|
|
fetch(`${getApiUrl()}/social/ratings/average?${entityType}_id=${entityId}`)
|
|
.then(res => res.json())
|
|
.then(data => setAverageRating(data))
|
|
.catch(err => console.error("Failed to fetch avg rating", err))
|
|
|
|
// Fetch user rating (if logged in)
|
|
// TODO: Implement fetching user's existing rating
|
|
}, [entityType, entityId])
|
|
|
|
const handleRate = async (score: number) => {
|
|
const token = localStorage.getItem("token")
|
|
if (!token) {
|
|
alert("Please log in to rate.")
|
|
return
|
|
}
|
|
|
|
setLoading(true)
|
|
try {
|
|
const body: any = { score }
|
|
body[`${entityType}_id`] = entityId
|
|
|
|
const res = await fetch(`${getApiUrl()}/social/ratings`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(body)
|
|
})
|
|
|
|
if (!res.ok) throw new Error("Failed to submit rating")
|
|
|
|
const data = await res.json()
|
|
setUserRating(data.score)
|
|
|
|
// Re-fetch average to keep it lively
|
|
fetch(`${getApiUrl()}/social/ratings/average?${entityType}_id=${entityId}`)
|
|
.then(res => res.json())
|
|
.then(setAverageRating)
|
|
} catch (err) {
|
|
console.error(err)
|
|
alert("Error submitting rating")
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
if (compact) {
|
|
return (
|
|
<div className="flex items-center gap-1.5 opacity-80 hover:opacity-100 transition-opacity">
|
|
<StarRating value={userRating} onChange={handleRate} size="sm" />
|
|
{averageRating > 0 && (
|
|
<span className="text-[10px] text-muted-foreground font-mono">
|
|
{averageRating.toFixed(1)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 border-l pl-4">
|
|
<div className="flex flex-col">
|
|
<span className="text-sm font-medium">Rating:</span>
|
|
<span className="text-xs text-muted-foreground">Avg: {averageRating.toFixed(1)}</span>
|
|
</div>
|
|
<StarRating value={userRating} onChange={handleRate} />
|
|
</div>
|
|
)
|
|
}
|