- 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
135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { ReviewCard } from "@/components/reviews/review-card"
|
|
import { ReviewForm } from "@/components/reviews/review-form"
|
|
import { getApiUrl } from "@/lib/api-config"
|
|
|
|
interface Review {
|
|
id: number
|
|
user_id: number
|
|
blurb?: string | null
|
|
content?: string | null
|
|
score?: number | null
|
|
created_at: string
|
|
user?: {
|
|
id: number
|
|
username: string
|
|
display_name?: string | null
|
|
avatar_bg_color?: string
|
|
avatar_text?: string | null
|
|
} | null
|
|
}
|
|
|
|
export type EntityType = "show" | "venue" | "song" | "performance" | "tour" | "year"
|
|
|
|
interface EntityReviewsProps {
|
|
entityType: EntityType
|
|
entityId: number
|
|
entityName?: string // e.g., "Arcadia"
|
|
entityContext?: string // e.g., "Sat, Mar 14, 2015"
|
|
initialReviews?: Review[]
|
|
}
|
|
|
|
export function EntityReviews({
|
|
entityType,
|
|
entityId,
|
|
entityName,
|
|
entityContext,
|
|
initialReviews = []
|
|
}: EntityReviewsProps) {
|
|
const [reviews, setReviews] = useState<Review[]>(initialReviews)
|
|
|
|
// Fetch reviews on mount if not provided (or to refresh)
|
|
useEffect(() => {
|
|
if (initialReviews.length === 0) {
|
|
fetchReviews()
|
|
}
|
|
}, [entityType, entityId])
|
|
|
|
const fetchReviews = async () => {
|
|
try {
|
|
const queryParam = entityType === 'year' ? 'year' : `${entityType}_id`
|
|
|
|
const res = await fetch(`${getApiUrl()}/reviews/?${queryParam}=${entityId}`)
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setReviews(data)
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to fetch reviews", err)
|
|
}
|
|
}
|
|
|
|
const handleSubmit = async (data: { blurb: string; content: string; score: number }) => {
|
|
const token = localStorage.getItem("token")
|
|
if (!token) {
|
|
alert("Please log in to leave a review.")
|
|
return
|
|
}
|
|
|
|
try {
|
|
const body: any = { ...data }
|
|
if (entityType === 'year') {
|
|
body.year = entityId
|
|
} else {
|
|
body[`${entityType}_id`] = entityId
|
|
}
|
|
|
|
const res = await fetch(`${getApiUrl()}/reviews/`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(body)
|
|
})
|
|
|
|
if (!res.ok) throw new Error("Failed to post review")
|
|
|
|
const newReview = await res.json()
|
|
setReviews(prev => [newReview, ...prev])
|
|
alert("Review posted!")
|
|
} catch (err) {
|
|
console.error(err)
|
|
alert("Error posting review")
|
|
}
|
|
}
|
|
|
|
// Build title with context
|
|
const getTitle = () => {
|
|
if (entityName && entityContext) {
|
|
return `Write a Review for ${entityName}`
|
|
}
|
|
if (entityName) {
|
|
return `Write a Review for ${entityName}`
|
|
}
|
|
return "Write a Review"
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6 pt-6 border-t">
|
|
<h2 className="text-2xl font-bold">Reviews</h2>
|
|
|
|
<ReviewForm
|
|
onSubmit={handleSubmit}
|
|
title={getTitle()}
|
|
subtitle={entityContext}
|
|
/>
|
|
|
|
<div className="space-y-4">
|
|
{reviews.length === 0 ? (
|
|
<p className="text-muted-foreground">No reviews yet. Be the first!</p>
|
|
) : (
|
|
reviews.map(review => (
|
|
<ReviewCard
|
|
key={review.id}
|
|
review={review}
|
|
/>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|