241 lines
8.9 KiB
TypeScript
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
|
|
times_played?: number
|
|
avg_rating?: number
|
|
}
|
|
|
|
async function getRecentShows(): Promise<Show[]> {
|
|
try {
|
|
const res = await fetch(`${getApiUrl()}/shows?limit=8&sort=date&order=desc`, {
|
|
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()}/songs?limit=5&sort=times_played&order=desc`, {
|
|
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 connect with fans.
|
|
</p>
|
|
<div className="flex gap-4">
|
|
<Link href="/leaderboards">
|
|
<Button size="lg" className="gap-2">
|
|
<Trophy className="h-4 w-4" />
|
|
View Leaderboards
|
|
</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.times_played && (
|
|
<div className="text-xs text-muted-foreground">
|
|
{song.times_played} 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>
|
|
)
|
|
}
|