elmeg-demo/frontend/components/social/entity-rating.tsx

162 lines
5.8 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { RatingInput, RatingBadge } from "@/components/ui/rating-input"
import { getApiUrl } from "@/lib/api-config"
import { useAuth } from "@/contexts/auth-context"
import { Sparkles, TrendingUp } from "lucide-react"
interface EntityRatingProps {
entityType: "show" | "song" | "venue" | "tour" | "performance"
entityId: number
compact?: boolean
ratingCount?: number
rank?: number // Rank of this performance vs others
isHeady?: boolean // Is this a top-rated "heady" version
}
export function EntityRating({
entityType,
entityId,
compact = false,
ratingCount,
rank,
isHeady = false
}: EntityRatingProps) {
const { user, token } = useAuth()
const [userRating, setUserRating] = useState<number | null>(null)
const [averageRating, setAverageRating] = useState(0)
const [loading, setLoading] = useState(false)
const [hasRated, setHasRated] = useState(false)
useEffect(() => {
// Fetch average rating
fetch(`${getApiUrl()}/social/ratings/average?${entityType}_id=${entityId}`)
.then(res => res.ok ? res.json() : 0)
.then(data => setAverageRating(data || 0))
.catch(() => setAverageRating(0))
// Fetch user's rating if logged in
const storedToken = localStorage.getItem("token")
if (storedToken) {
fetch(`${getApiUrl()}/social/ratings/me?${entityType}_id=${entityId}`, {
headers: { Authorization: `Bearer ${storedToken}` }
})
.then(res => res.ok ? res.json() : null)
.then(data => {
if (data?.score) {
setUserRating(data.score)
setHasRated(true)
}
})
.catch(() => { })
}
}, [entityType, entityId])
const handleRate = async (score: number) => {
const storedToken = localStorage.getItem("token")
if (!storedToken) {
alert("Please log in to rate.")
return
}
setLoading(true)
try {
const body: Record<string, unknown> = { score }
body[`${entityType}_id`] = entityId
const res = await fetch(`${getApiUrl()}/social/ratings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${storedToken}`
},
body: JSON.stringify(body)
})
if (!res.ok) throw new Error("Failed to submit rating")
const data = await res.json()
setUserRating(data.score)
setHasRated(true)
// Re-fetch average
fetch(`${getApiUrl()}/social/ratings/average?${entityType}_id=${entityId}`)
.then(res => res.ok ? res.json() : averageRating)
.then(setAverageRating)
} catch (err) {
console.error(err)
alert("Error submitting rating")
} finally {
setLoading(false)
}
}
if (compact) {
return (
<div className="flex items-center gap-2">
{isHeady && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-yellow-500/10 text-yellow-600 border border-yellow-500/20 text-xs font-medium">
<Sparkles className="h-3 w-3" />
Heady
</span>
)}
{averageRating > 0 && (
<RatingBadge value={averageRating} />
)}
{rank && (
<span className="text-xs text-muted-foreground">
#{rank}
</span>
)}
</div>
)
}
return (
<div className="border rounded-lg p-4 bg-card">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">Your Rating</span>
{isHeady && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-yellow-500/10 text-yellow-600 border border-yellow-500/20 text-xs font-medium">
<Sparkles className="h-3 w-3" />
Heady Version
</span>
)}
</div>
<div className="flex items-center gap-3">
{rank && (
<span className="flex items-center gap-1 text-xs text-muted-foreground">
<TrendingUp className="h-3 w-3" />
Ranked #{rank}
</span>
)}
{averageRating > 0 && (
<span className="text-xs text-muted-foreground">
Community: <span className="font-medium text-foreground">{averageRating.toFixed(1)}</span>
{ratingCount && ` (${ratingCount})`}
</span>
)}
</div>
</div>
<RatingInput
value={userRating || 0}
onChange={handleRate}
showSlider={true}
/>
{loading && (
<p className="text-xs text-muted-foreground mt-2 animate-pulse">
Submitting...
</p>
)}
{hasRated && !loading && userRating && (
<p className="text-xs text-green-600 mt-2">
Your rating: {userRating.toFixed(1)}/10
</p>
)}
</div>
)
}