From c9a22de2a98d4c2709334be458b23a3f04b1b421 Mon Sep 17 00:00:00 2001
From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com>
Date: Wed, 24 Dec 2025 17:08:36 -0800
Subject: [PATCH] feat: Add Upcoming Shows feature (backend+frontend) and
Special Features section
---
backend/routers/shows.py | 11 +++
frontend/app/shows/page.tsx | 18 +++-
frontend/app/shows/upcoming/page.tsx | 121 +++++++++++++++++++++++++++
frontend/app/videos/page.tsx | 38 +++++++++
4 files changed, 184 insertions(+), 4 deletions(-)
create mode 100644 frontend/app/shows/upcoming/page.tsx
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" && (
+
+
+
+ )}
+
+
+
+