diff --git a/frontend/app/bands/page.tsx b/frontend/app/bands/page.tsx
new file mode 100644
index 0000000..62d77a2
--- /dev/null
+++ b/frontend/app/bands/page.tsx
@@ -0,0 +1,21 @@
+import { Metadata } from "next"
+import { BandsGrid } from "@/components/bands/bands-grid"
+
+export const metadata: Metadata = {
+ title: "Bands | Fediversion",
+ description: "Browse all bands in the Fediversion archive"
+}
+
+export default function BandsPage() {
+ return (
+
+
+
Bands
+
+ Select a band to explore their archive of shows, songs, and performances.
+
+
+
+
+ )
+}
diff --git a/frontend/components/bands/bands-grid.tsx b/frontend/components/bands/bands-grid.tsx
new file mode 100644
index 0000000..c62ed0c
--- /dev/null
+++ b/frontend/components/bands/bands-grid.tsx
@@ -0,0 +1,108 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import Link from "next/link"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { getApiUrl } from "@/lib/api-config"
+import { Loader2, ChevronRight, Music, Calendar, MapPin, ListMusic } from "lucide-react"
+
+interface Vertical {
+ id: number
+ name: string
+ slug: string
+ description: string | null
+ color: string | null
+ show_count?: number
+ song_count?: number
+ venue_count?: number
+}
+
+export function BandsGrid() {
+ const [verticals, setVerticals] = useState([])
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ async function fetchVerticals() {
+ try {
+ const res = await fetch(`${getApiUrl()}/verticals`)
+ if (res.ok) {
+ const data = await res.json()
+ setVerticals(data)
+ }
+ } catch (error) {
+ console.error("Failed to fetch verticals", error)
+ } finally {
+ setLoading(false)
+ }
+ }
+ fetchVerticals()
+ }, [])
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+ {verticals.map((vertical) => (
+
+
+
+
+
+
+ {vertical.name.substring(0, 2).toUpperCase()}
+
+
+ {vertical.name}
+
+
+
+
+
+
+ {vertical.description && (
+
+ {vertical.description}
+
+ )}
+
+ e.stopPropagation()}
+ >
+
+ Shows
+
+ e.stopPropagation()}
+ >
+
+ Songs
+
+ e.stopPropagation()}
+ >
+
+ Venues
+
+
+
+
+
+ ))}
+
+ )
+}
diff --git a/frontend/components/layout/navbar.tsx b/frontend/components/layout/navbar.tsx
index c60b357..d261be1 100644
--- a/frontend/components/layout/navbar.tsx
+++ b/frontend/components/layout/navbar.tsx
@@ -17,6 +17,7 @@ import { useAuth } from "@/contexts/auth-context"
import { useVertical } from "@/contexts/vertical-context"
const browseLinks = [
+ { href: "/bands", label: "Bands" },
{ href: "/shows", label: "Shows" },
{ href: "/venues", label: "Venues" },
{ href: "/songs", label: "Songs" },
@@ -55,7 +56,7 @@ export function Navbar() {
{browseLinks.map((link) => (
-
+
{link.label}
))}