Add playful 404 page and enhanced empty states

This commit is contained in:
fullsizemalt 2025-12-25 21:56:15 -08:00
parent 45608bfdfb
commit 16828b65b0
2 changed files with 124 additions and 34 deletions

View file

@ -1,43 +1,127 @@
"use client"
import Link from "next/link" import Link from "next/link"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Home, ArrowLeft, Search } from "lucide-react" import { Home, Search, Shuffle, ArrowLeft, Music, Disc3 } from "lucide-react"
import { useState, useEffect } from "react"
const GOOSE_QUOTES = [
"Looks like this page flew the coop!",
"Honk if you're lost too!",
"This page has migrated to parts unknown.",
"The jam you seek is not in this location.",
"404: Page not found. But hey, at least the vibes are good.",
"This URL took a wrong turn at Tumble.",
"Flodown? More like FlowGONE.",
"Seems this page is still in the Dripfield.",
"The set break got a little too long...",
"Whoops! Someone put this page in the wrong set.",
]
const SONG_SUGGESTIONS = [
{ title: "Tumble", slug: "tumble" },
{ title: "Arcadia", slug: "arcadia" },
{ title: "Hungersite", slug: "hungersite" },
{ title: "Atlas Dogs", slug: "atlas-dogs" },
{ title: "Rockdale", slug: "rockdale" },
]
export default function NotFound() { export default function NotFound() {
const [quote, setQuote] = useState("")
const [suggestion, setSuggestion] = useState(SONG_SUGGESTIONS[0])
const [isSpinning, setIsSpinning] = useState(false)
useEffect(() => {
// Random quote on mount
setQuote(GOOSE_QUOTES[Math.floor(Math.random() * GOOSE_QUOTES.length)])
setSuggestion(SONG_SUGGESTIONS[Math.floor(Math.random() * SONG_SUGGESTIONS.length)])
}, [])
const shuffleQuote = () => {
setIsSpinning(true)
setTimeout(() => {
setQuote(GOOSE_QUOTES[Math.floor(Math.random() * GOOSE_QUOTES.length)])
setSuggestion(SONG_SUGGESTIONS[Math.floor(Math.random() * SONG_SUGGESTIONS.length)])
setIsSpinning(false)
}, 300)
}
return ( return (
<div className="min-h-[60vh] flex flex-col items-center justify-center text-center px-4"> <div className="flex flex-col items-center justify-center min-h-[70vh] text-center px-4">
<div className="space-y-6 max-w-md"> {/* Animated Goose/Music Icon */}
<div className="text-8xl font-bold text-muted-foreground/30">404</div> <div className="relative mb-6 group cursor-pointer" onClick={shuffleQuote}>
<div className={`transition-transform duration-300 ${isSpinning ? 'rotate-180 scale-110' : ''}`}>
<div className="space-y-2"> <Disc3 className="h-32 w-32 text-primary/20 group-hover:text-primary/40 transition-colors" />
<h1 className="text-2xl font-bold">Page Not Found</h1> </div>
<p className="text-muted-foreground"> <span className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-6xl group-hover:scale-110 transition-transform">
The page you&apos;re looking for doesn&apos;t exist or may have been moved. 🪿
</p> </span>
</div> </div>
<div className="flex flex-col sm:flex-row gap-3 justify-center pt-4"> {/* 404 Header */}
<Button asChild variant="default"> <div className="mb-4">
<span className="text-8xl font-black bg-gradient-to-r from-primary via-purple-500 to-pink-500 bg-clip-text text-transparent">
404
</span>
</div>
{/* Playful Quote */}
<p className="text-xl font-medium mb-2 max-w-md min-h-[2em] transition-opacity duration-300"
style={{ opacity: isSpinning ? 0 : 1 }}>
{quote}
</p>
<p className="text-muted-foreground text-sm mb-8">
Click the goose for a new message
</p>
{/* Action Buttons */}
<div className="flex flex-wrap gap-3 justify-center mb-8">
<Link href="/"> <Link href="/">
<Home className="mr-2 h-4 w-4" /> <Button size="lg" className="gap-2 font-semibold">
Go Home <Home className="h-4 w-4" />
</Link> Back to Safety
</Button> </Button>
<Button asChild variant="outline">
<Link href="/shows">
<Search className="mr-2 h-4 w-4" />
Browse Shows
</Link> </Link>
<Link href="/shows">
<Button variant="outline" size="lg" className="gap-2">
<Search className="h-4 w-4" />
Find a Show
</Button>
</Link>
<Button variant="ghost" size="lg" className="gap-2" onClick={shuffleQuote}>
<Shuffle className="h-4 w-4" />
Shuffle Quote
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground pt-4"> {/* Song Suggestion Card */}
Looking for a specific show? Try the{" "} <div className="bg-muted/50 rounded-xl p-6 max-w-sm border border-border/50">
<Link href="/shows" className="underline hover:text-foreground"> <p className="text-sm text-muted-foreground mb-3">
shows page While you&apos;re here, maybe check out:
</Link>{" "}
or use the search.
</p> </p>
<Link
href={`/songs/${suggestion.slug}`}
className="flex items-center gap-3 p-3 rounded-lg bg-background hover:bg-primary/5 border border-border/50 transition-all hover:border-primary/20 group"
>
<div className="h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
<Music className="h-5 w-5 text-primary" />
</div> </div>
<div className="text-left">
<p className="font-semibold group-hover:text-primary transition-colors">{suggestion.title}</p>
<p className="text-xs text-muted-foreground">Random song suggestion</p>
</div>
</Link>
</div>
{/* Back Link */}
<button
onClick={() => window.history.back()}
className="mt-8 text-sm text-muted-foreground hover:text-primary transition-colors flex items-center gap-1 group"
>
<ArrowLeft className="h-3 w-3 group-hover:-translate-x-1 transition-transform" />
Take me back where I came from
</button>
</div> </div>
) )
} }

View file

@ -243,7 +243,13 @@ export default async function ShowDetailPage({ params }: { params: Promise<{ slu
))} ))}
</div> </div>
) : ( ) : (
<p className="text-muted-foreground text-sm">No setlist data available.</p> <div className="flex flex-col items-center justify-center py-8 text-center">
<Music2 className="h-12 w-12 text-muted-foreground/30 mb-4" />
<p className="text-muted-foreground font-medium">No Setlist Documented</p>
<p className="text-sm text-muted-foreground/70 mt-1 max-w-sm">
This show&apos;s setlist hasn&apos;t been added yet. Early Goose shows (2014-2016) often weren&apos;t documented.
</p>
</div>
)} )}
</CardContent> </CardContent>
</Card> </Card>