fediversion/frontend/app/shows/page.tsx
fullsizemalt 60456c4737
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
feat(frontend): Enforce strict mode and refactor pages
2025-12-30 22:29:16 -08:00

245 lines
9.8 KiB
TypeScript

"use client"
import { useEffect, useState, Suspense } from "react"
import { getApiUrl } from "@/lib/api-config"
import { Loader2, Music2 } from "lucide-react"
import { useSearchParams, useRouter, usePathname } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { DateGroupedList } from "@/components/shows/date-grouped-list"
import { FilterPills } from "@/components/shows/filter-pills"
import { BandGrid } from "@/components/shows/band-grid"
import { Show, Vertical, PaginatedResponse, Venue } from "@/types/models"
function ShowsContent() {
const searchParams = useSearchParams()
const router = useRouter()
const pathname = usePathname()
// --- State ---
const activeView = searchParams.get("view") || "recent"
const bandsParam = searchParams.get("bands")
const selectedBands = bandsParam ? bandsParam.split(",") : []
const [shows, setShows] = useState<Show[]>([])
const [verticals, setVerticals] = useState<Vertical[]>([])
const [loading, setLoading] = useState(true)
const [loadingVerticals, setLoadingVerticals] = useState(true)
// --- Data Fetching: Verticals (Always) ---
useEffect(() => {
setLoadingVerticals(true)
fetch(`${getApiUrl()}/verticals/`)
.then(res => {
if (!res.ok) throw new Error("Failed to fetch verticals")
return res.json()
})
.then(data => {
setVerticals(data)
setLoadingVerticals(false)
})
.catch(err => {
console.error(err)
setLoadingVerticals(false)
})
}, [])
// --- Data Fetching: Shows (Dependent on View) ---
useEffect(() => {
if (activeView === "bands") {
// Don't fetch shows if we are just browsing bands
setLoading(false)
return
}
setLoading(true)
const params = new URLSearchParams()
// Add band filters
if (selectedBands.length > 0) {
selectedBands.forEach(b => params.append("vertical_slugs", b))
}
// Add view-specific params
if (activeView === "upcoming") {
params.set("status", "upcoming")
} else if (activeView === "my-feed") {
// My Feed implies specific tiers
// We use the same tiers as FeedFilter used: HEADLINER, MAIN_STAGE, SUPPORTING
["HEADLINER", "MAIN_STAGE", "SUPPORTING"].forEach(t => params.append("tiers", t))
// Also we might want to default to "past" shows for feed? Or all?
// "My Feed" usually means recent updates.
// Let's explicitly ask for "past" shows (recent history) unless user wants upcoming feed?
// For now, let's show PAST shows in My Feed (History), maybe add Upcoming toggle later?
// Or just show all? `read_shows` sorts by date desc.
// Let's default to Recent (Past) for Feed.
// params.set("status", "past")
// Actually, if we leave status blank, it returns all (sorted by date desc if modified, but check `read_shows`)
// `read_shows` default sorts DESC.
// Let's assume user wants recent feed.
} else {
// Recent (Default)
params.set("status", "past")
}
fetch(`${getApiUrl()}/shows/?${params.toString()}`)
.then(res => {
// If 401 (Unauthorized) for My Feed, we might get empty list or error
if (res.status === 401 && activeView === "my-feed") {
return { data: [] as Show[], meta: { total: 0, limit: 0, offset: 0 } }
}
if (!res.ok) throw new Error("Failed to fetch shows")
return res.json()
})
.then((data: PaginatedResponse<Show>) => {
setShows(data.data || [])
setLoading(false)
})
.catch(err => {
console.error(err)
setShows([]) // Clear on error
setLoading(false)
})
}, [activeView, bandsParam]) // bandsParam is the dependency
// --- Handlers ---
const updateUrl = (view: string, bands: string[]) => {
const params = new URLSearchParams()
if (view !== "recent") params.set("view", view)
if (bands.length > 0) params.set("bands", bands.join(","))
// Push state
router.push(`${pathname}${params.toString() ? `?${params.toString()}` : ''}`)
}
const handleTabChange = (val: string) => {
updateUrl(val, selectedBands)
}
const handleToggleBand = (slug: string) => {
let newBands = [...selectedBands]
if (newBands.includes(slug)) {
newBands = newBands.filter(b => b !== slug)
} else {
newBands.push(slug)
}
// If we are on "bands" tab and select a band, switch to "recent" to show results?
// User plan says: "Clicking a band adds it to the active filter and switches to 'Recent' view."
if (activeView === "bands" && !selectedBands.includes(slug)) {
updateUrl("recent", newBands)
} else {
// Otherwise just update filters (e.g. if unchecking, stay on grid? or if adding from elsewhere?)
// If checking from grid -> go to recent.
// If unchecking -> stay?
// Let's just follow the rule: Select -> Go to Recent. Unselect -> Stay.
if (!selectedBands.includes(slug)) {
updateUrl("recent", newBands)
} else {
updateUrl(activeView, newBands)
}
}
}
const handleRemoveBand = (slug: string) => {
const newBands = selectedBands.filter(b => b !== slug)
updateUrl(activeView, newBands)
}
const handleClearBands = () => {
updateUrl(activeView, [])
}
// --- Render Helpers ---
const renderShowList = () => {
if (loading) {
return (
<div className="flex justify-center py-20">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)
}
if (shows.length === 0) {
return (
<div className="text-center py-20 text-muted-foreground">
<Music2 className="h-12 w-12 mx-auto mb-4 opacity-20" />
<p>No shows found matching your criteria.</p>
</div>
)
}
return <DateGroupedList shows={shows} />
}
return (
<div className="container py-6 max-w-5xl">
<div className="sticky top-0 z-10 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 pb-4 -mx-4 px-4 md:mx-0 md:px-0">
<Tabs value={activeView} onValueChange={handleTabChange} className="w-full">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-4">
<TabsList className="grid w-full md:w-auto grid-cols-4 h-11">
<TabsTrigger value="recent" className="flex items-center gap-2">
Recent
</TabsTrigger>
<TabsTrigger value="my-feed" className="flex items-center gap-2">
My Feed
</TabsTrigger>
<TabsTrigger value="upcoming" className="flex items-center gap-2">
Upcoming
</TabsTrigger>
<TabsTrigger value="bands" className="flex items-center gap-2">
By Band
</TabsTrigger>
</TabsList>
</div>
<FilterPills
selectedBands={selectedBands}
verticals={verticals}
onRemove={handleRemoveBand}
onClear={handleClearBands}
/>
<div className="mt-2 min-h-[500px]">
<TabsContent value="recent" className="m-0">
{renderShowList()}
</TabsContent>
<TabsContent value="my-feed" className="m-0">
{/* Maybe add auth check banner here if shows is empty and user not logged in? */}
{/* For now, just render list */}
{renderShowList()}
</TabsContent>
<TabsContent value="upcoming" className="m-0">
{renderShowList()}
</TabsContent>
<TabsContent value="bands" className="m-0">
{loadingVerticals ? (
<div className="flex justify-center py-20">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : (
<BandGrid
verticals={verticals}
selectedBands={selectedBands}
onToggle={handleToggleBand} // Note: logic inside handleToggle moves to Recent
/>
)}
</TabsContent>
</div>
</Tabs>
</div>
</div>
)
}
export default function ShowsPage() {
return (
<Suspense fallback={<div className="container py-6 flex justify-center"><Loader2 className="h-8 w-8 animate-spin" /></div>}>
<ShowsContent />
</Suspense>
)
}