elmeg-demo/frontend/app/page.tsx
fullsizemalt 06dc8889b5 feat: Add Heady Versions (performances) page
- /performances page with top-rated performance leaderboard
- Added to Browse dropdown in navbar
- Updated home page CTA to feature Heady Versions
- Medal icons for top 3 performances
2025-12-21 15:44:09 -08:00

241 lines
8.9 KiB
TypeScript

import { ActivityFeed } from "@/components/feed/activity-feed"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import Link from "next/link"
import { Trophy, Music, MapPin, Calendar, ChevronRight, Star } from "lucide-react"
import { getApiUrl } from "@/lib/api-config"
interface Show {
id: number
date: string
venue?: {
id: number
name: string
city?: string
state?: string
}
tour?: {
id: number
name: string
}
}
interface Song {
id: number
title: string
performance_count?: number
avg_rating?: number
}
async function getRecentShows(): Promise<Show[]> {
try {
const res = await fetch(`${getApiUrl()}/shows/recent?limit=8`, {
cache: 'no-store',
next: { revalidate: 60 }
})
if (!res.ok) return []
return res.json()
} catch (e) {
console.error('Failed to fetch recent shows:', e)
return []
}
}
async function getTopSongs(): Promise<Song[]> {
try {
const res = await fetch(`${getApiUrl()}/stats/top-songs?limit=5`, {
cache: 'no-store',
next: { revalidate: 300 }
})
if (!res.ok) return []
return res.json()
} catch (e) {
console.error('Failed to fetch top songs:', e)
return []
}
}
async function getStats() {
try {
const [showsRes, songsRes, venuesRes] = await Promise.all([
fetch(`${getApiUrl()}/shows?limit=1`, { cache: 'no-store' }),
fetch(`${getApiUrl()}/songs?limit=1`, { cache: 'no-store' }),
fetch(`${getApiUrl()}/venues?limit=1`, { cache: 'no-store' })
])
// These endpoints return arrays, we need to get counts differently
// For now we'll just show the data we have
return { shows: 0, songs: 0, venues: 0 }
} catch (e) {
return { shows: 0, songs: 0, venues: 0 }
}
}
export default async function Home() {
const [recentShows, topSongs] = await Promise.all([
getRecentShows(),
getTopSongs()
])
return (
<div className="flex flex-col gap-8">
{/* Hero Section */}
<section className="flex flex-col items-center gap-4 py-12 text-center md:py-20 bg-gradient-to-b from-background to-accent/20 rounded-3xl border">
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl md:text-6xl">
Elmeg
</h1>
<p className="max-w-[600px] text-lg text-muted-foreground">
The ultimate community archive for Goose history.
<br />
Discover shows, rate performances, and find the <strong>heady versions</strong>.
</p>
<div className="flex gap-4">
<Link href="/performances">
<Button size="lg" className="gap-2">
<Trophy className="h-4 w-4" />
Heady Versions
</Button>
</Link>
<Link href="/shows">
<Button variant="outline" size="lg">
Browse Shows
</Button>
</Link>
</div>
</section>
{/* Recent Shows */}
<section className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold flex items-center gap-2">
<Calendar className="h-6 w-6 text-blue-500" />
Recent Shows
</h2>
<Link href="/shows" className="text-sm text-muted-foreground hover:underline flex items-center gap-1">
View all shows <ChevronRight className="h-4 w-4" />
</Link>
</div>
{recentShows.length > 0 ? (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{recentShows.map((show) => (
<Link key={show.id} href={`/shows/${show.id}`}>
<Card className="h-full hover:bg-accent/50 transition-colors cursor-pointer">
<CardContent className="p-4">
<div className="font-semibold">
{new Date(show.date).toLocaleDateString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric'
})}
</div>
{show.venue && (
<div className="text-sm text-muted-foreground mt-1">
{show.venue.name}
</div>
)}
{show.venue?.city && (
<div className="text-xs text-muted-foreground">
{show.venue.city}{show.venue.state ? `, ${show.venue.state}` : ''}
</div>
)}
{show.tour && (
<div className="text-xs text-primary mt-2">
{show.tour.name}
</div>
)}
</CardContent>
</Card>
</Link>
))}
</div>
) : (
<Card className="p-8 text-center text-muted-foreground">
<p>No shows yet. Check back soon!</p>
</Card>
)}
</section>
<div className="grid gap-8 lg:grid-cols-3">
{/* Top Songs */}
<section className="space-y-4 lg:col-span-1">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold flex items-center gap-2">
<Star className="h-5 w-5 text-yellow-500" />
Top Songs
</h2>
<Link href="/songs" className="text-sm text-muted-foreground hover:underline flex items-center gap-1">
All songs <ChevronRight className="h-4 w-4" />
</Link>
</div>
<Card>
<CardContent className="p-0">
{topSongs.length > 0 ? (
<ul className="divide-y">
{topSongs.map((song, idx) => (
<li key={song.id}>
<Link
href={`/songs/${song.id}`}
className="flex items-center gap-3 p-3 hover:bg-accent/50 transition-colors"
>
<span className="text-lg font-bold text-muted-foreground w-6 text-center">
{idx + 1}
</span>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{song.title}</div>
{song.performance_count && (
<div className="text-xs text-muted-foreground">
{song.performance_count} performances
</div>
)}
</div>
</Link>
</li>
))}
</ul>
) : (
<div className="p-4 text-center text-muted-foreground text-sm">
No songs yet
</div>
)}
</CardContent>
</Card>
</section>
{/* Activity Feed */}
<section className="space-y-4 lg:col-span-2">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold">Recent Activity</h2>
<Link href="/leaderboards" className="text-sm text-muted-foreground hover:underline flex items-center gap-1">
View all <ChevronRight className="h-4 w-4" />
</Link>
</div>
<ActivityFeed />
</section>
</div>
{/* Quick Links */}
<section className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Link href="/shows" className="block p-6 border rounded-xl hover:bg-accent transition-colors group">
<Music className="h-8 w-8 mb-2 text-blue-500 group-hover:scale-110 transition-transform" />
<h3 className="font-bold">Shows</h3>
<p className="text-sm text-muted-foreground">Browse the complete archive</p>
</Link>
<Link href="/venues" className="block p-6 border rounded-xl hover:bg-accent transition-colors group">
<MapPin className="h-8 w-8 mb-2 text-green-500 group-hover:scale-110 transition-transform" />
<h3 className="font-bold">Venues</h3>
<p className="text-sm text-muted-foreground">Find your favorite spots</p>
</Link>
<Link href="/songs" className="block p-6 border rounded-xl hover:bg-accent transition-colors group">
<Music className="h-8 w-8 mb-2 text-purple-500 group-hover:scale-110 transition-transform" />
<h3 className="font-bold">Songs</h3>
<p className="text-sm text-muted-foreground">Explore the catalog</p>
</Link>
<Link href="/leaderboards" className="block p-6 border rounded-xl hover:bg-accent transition-colors group">
<Trophy className="h-8 w-8 mb-2 text-yellow-500 group-hover:scale-110 transition-transform" />
<h3 className="font-bold">Leaderboards</h3>
<p className="text-sm text-muted-foreground">Top rated everything</p>
</Link>
</section>
</div>
)
}