diff --git a/backend/routers/shows.py b/backend/routers/shows.py index 2da4246..9f07a42 100644 --- a/backend/routers/shows.py +++ b/backend/routers/shows.py @@ -48,6 +48,17 @@ def read_recent_shows( shows = session.exec(query).all() return shows +@router.get("/upcoming", response_model=List[ShowRead]) +def read_upcoming_shows( + limit: int = Query(default=50, le=100), + session: Session = Depends(get_session) +): + """Get upcoming shows ordered by date ascending""" + from datetime import datetime + query = select(Show).where(Show.date > datetime.now()).order_by(Show.date.asc()).limit(limit) + shows = session.exec(query).all() + return shows + @router.get("/{slug}", response_model=ShowRead) def read_show(slug: str, session: Session = Depends(get_session)): show = session.exec(select(Show).where(Show.slug == slug)).first() diff --git a/frontend/app/shows/page.tsx b/frontend/app/shows/page.tsx index 1577d87..25aef4e 100644 --- a/frontend/app/shows/page.tsx +++ b/frontend/app/shows/page.tsx @@ -76,10 +76,20 @@ function ShowsContent() { return (
-

Shows

-

- Browse the complete archive of performances. -

+
+
+

Shows

+

+ Browse the complete archive of performances. +

+
+ + + +
diff --git a/frontend/app/shows/upcoming/page.tsx b/frontend/app/shows/upcoming/page.tsx new file mode 100644 index 0000000..92edc67 --- /dev/null +++ b/frontend/app/shows/upcoming/page.tsx @@ -0,0 +1,121 @@ +"use client" + +import { useEffect, useState, Suspense } from "react" +import { getApiUrl } from "@/lib/api-config" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import Link from "next/link" +import { Calendar, MapPin, Loader2, ArrowLeft } from "lucide-react" +import { Skeleton } from "@/components/ui/skeleton" +import { Button } from "@/components/ui/button" + +interface Show { + id: number + slug?: string + date: string + venue: { + id: number + name: string + city: string + state: string + } +} + +function UpcomingShowsContent() { + const [shows, setShows] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + // Fetch upcoming shows + fetch(`${getApiUrl()}/shows/upcoming?limit=100`) + .then(res => res.json()) + .then(data => { + // Already sorted ascending by backend, but ensure just in case + setShows(data) + }) + .catch(console.error) + .finally(() => setLoading(false)) + }, []) + + if (loading) { + return ( +
+
+ + +
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+ ) + } + + return ( +
+
+ + + +
+

Upcoming Shows

+

+ See where the flock is headed next. +

+
+
+ + {shows.length === 0 ? ( +
+

No upcoming shows found.

+

Check back later for tour announcements!

+
+ ) : ( +
+ {shows.map((show) => ( + + + + + {new Date(show.date).toLocaleDateString(undefined, { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + })} + + + +
+ + + {show.venue?.name}, {show.venue?.city}, {show.venue?.state} + +
+
+
+ ))} +
+ )} +
+ ) +} + +function LoadingFallback() { + return ( +
+ +
+ ) +} + +export default function UpcomingShowsPage() { + return ( + }> + + + ) +} diff --git a/frontend/app/videos/page.tsx b/frontend/app/videos/page.tsx index 2165780..1710d39 100644 --- a/frontend/app/videos/page.tsx +++ b/frontend/app/videos/page.tsx @@ -141,6 +141,44 @@ export default function VideosPage() { )}
+ {/* Special Features Section */} +
+
+ +

Special Features

+
+
+
+
+

Show Upon Time

+ Documentary +
+

+ An intimate look behind the scenes of Goose's journey. +

+ + {expandedVideoId === "doc-show-upon-time" && ( +
+