fediversion/frontend/app/performances/page.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

142 lines
7.1 KiB
TypeScript

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Trophy, Star, Calendar, MapPin, ExternalLink } from "lucide-react"
import Link from "next/link"
import { getApiUrl } from "@/lib/api-config"
interface TopPerformance {
performance: {
id: number
position: number
set_name: string
notes?: string
youtube_link?: string
}
song: {
id: number
title: string
}
show: {
id: number
date: string
}
venue: {
id: number
name: string
city: string
state?: string
}
avg_score: number
rating_count: number
}
async function getTopPerformances(): Promise<TopPerformance[]> {
try {
const res = await fetch(`${getApiUrl()}/leaderboards/performances/top?limit=50`, {
cache: 'no-store'
})
if (!res.ok) return []
return res.json()
} catch (e) {
console.error('Failed to fetch top performances:', e)
return []
}
}
export default async function PerformancesPage() {
const performances = await getTopPerformances()
return (
<div className="flex flex-col gap-6">
<div className="text-center py-8">
<h1 className="text-4xl font-bold tracking-tight flex items-center justify-center gap-3">
<Trophy className="h-10 w-10 text-yellow-500" />
Top Performances
</h1>
<p className="text-muted-foreground mt-2 text-lg">
The highest-rated jams as voted by the community
</p>
</div>
{performances.length > 0 ? (
<div className="space-y-3">
{performances.map((item, index) => (
<Link key={item.performance.id} href={`/performances/${item.performance.id}`}>
<Card className={`hover:bg-accent/50 transition-colors cursor-pointer ${index === 0 ? 'border-2 border-yellow-500 bg-gradient-to-r from-yellow-50 to-orange-50 dark:from-yellow-900/10 dark:to-orange-900/10' :
index === 1 ? 'border-gray-300 dark:border-gray-600' :
index === 2 ? 'border-amber-600 dark:border-amber-700' : ''
}`}>
<CardContent className="p-4">
<div className="flex items-center gap-4">
{/* Rank */}
<div className="text-3xl font-bold w-12 text-center shrink-0">
{index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : <span className="text-muted-foreground">{index + 1}</span>}
</div>
{/* Song & Show Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-bold text-lg truncate">{item.song.title}</span>
{item.performance.youtube_link && (
<Badge variant="outline" className="text-red-500 border-red-300">
<ExternalLink className="h-3 w-3 mr-1" />
Video
</Badge>
)}
</div>
<div className="flex items-center gap-4 text-sm text-muted-foreground mt-1">
<span className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
{new Date(item.show.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</span>
<span className="flex items-center gap-1">
<MapPin className="h-4 w-4" />
{item.venue.name}
{item.venue.city && ` - ${item.venue.city}`}
{item.venue.state && `, ${item.venue.state}`}
</span>
</div>
{item.performance.notes && (
<p className="text-xs text-primary mt-1 italic">
{item.performance.notes}
</p>
)}
</div>
{/* Rating */}
<div className="text-right shrink-0">
<div className="flex items-center gap-1 text-yellow-600">
<Star className="h-5 w-5 fill-current" />
<span className="font-bold text-xl">{item.avg_score.toFixed(1)}</span>
</div>
<div className="text-xs text-muted-foreground">
{item.rating_count} {item.rating_count === 1 ? 'rating' : 'ratings'}
</div>
</div>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
) : (
<Card className="p-12 text-center">
<CardContent>
<Trophy className="h-16 w-16 mx-auto mb-4 text-muted-foreground/30" />
<h2 className="text-xl font-bold">No rated performances yet</h2>
<p className="text-muted-foreground mt-2">
Be the first to rate a performance! Browse shows and rate your favorite jams.
</p>
<Link href="/shows" className="text-primary hover:underline mt-4 inline-block">
Browse Shows
</Link>
</CardContent>
</Card>
)}
</div>
)
}