diff --git a/frontend/app/[vertical]/page.tsx b/frontend/app/[vertical]/page.tsx
new file mode 100644
index 0000000..65216a2
--- /dev/null
+++ b/frontend/app/[vertical]/page.tsx
@@ -0,0 +1,60 @@
+import { notFound } from "next/navigation"
+import { VERTICALS } from "@/contexts/vertical-context"
+
+interface Props {
+ params: { vertical: string }
+}
+
+export function generateStaticParams() {
+ return VERTICALS.map((v) => ({
+ vertical: v.slug,
+ }))
+}
+
+export default function VerticalPage({ params }: Props) {
+ const vertical = VERTICALS.find((v) => v.slug === params.vertical)
+
+ if (!vertical) {
+ notFound()
+ }
+
+ return (
+
+
+
+ {vertical.emoji}
+ {vertical.name}
+
+
+ Explore setlists, rate performances, and connect with the {vertical.name} community.
+
+
+
+
+
+ )
+}
diff --git a/frontend/app/[vertical]/shows/page.tsx b/frontend/app/[vertical]/shows/page.tsx
new file mode 100644
index 0000000..0ef11b5
--- /dev/null
+++ b/frontend/app/[vertical]/shows/page.tsx
@@ -0,0 +1,76 @@
+import { VERTICALS } from "@/contexts/vertical-context"
+import { notFound } from "next/navigation"
+import { getApiUrl } from "@/lib/api-config"
+
+interface Props {
+ params: { vertical: string }
+}
+
+export function generateStaticParams() {
+ return VERTICALS.map((v) => ({
+ vertical: v.slug,
+ }))
+}
+
+async function getShows(verticalSlug: string) {
+ try {
+ const res = await fetch(`${getApiUrl()}/shows?vertical=${verticalSlug}`, {
+ next: { revalidate: 60 }
+ })
+ if (!res.ok) return []
+ return res.json()
+ } catch {
+ return []
+ }
+}
+
+export default async function ShowsPage({ params }: Props) {
+ const vertical = VERTICALS.find((v) => v.slug === params.vertical)
+
+ if (!vertical) {
+ notFound()
+ }
+
+ const shows = await getShows(vertical.slug)
+
+ return (
+
+
+
+ {vertical.emoji}
+ {vertical.name} Shows
+
+
+
+ {shows.length === 0 ? (
+
+
No shows imported yet for {vertical.name}.
+
Run the data importer to populate shows.
+
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/frontend/app/[vertical]/songs/page.tsx b/frontend/app/[vertical]/songs/page.tsx
new file mode 100644
index 0000000..2c8b699
--- /dev/null
+++ b/frontend/app/[vertical]/songs/page.tsx
@@ -0,0 +1,75 @@
+import { VERTICALS } from "@/contexts/vertical-context"
+import { notFound } from "next/navigation"
+import { getApiUrl } from "@/lib/api-config"
+
+interface Props {
+ params: { vertical: string }
+}
+
+export function generateStaticParams() {
+ return VERTICALS.map((v) => ({
+ vertical: v.slug,
+ }))
+}
+
+async function getSongs(verticalSlug: string) {
+ try {
+ const res = await fetch(`${getApiUrl()}/songs?vertical=${verticalSlug}`, {
+ next: { revalidate: 60 }
+ })
+ if (!res.ok) return []
+ return res.json()
+ } catch {
+ return []
+ }
+}
+
+export default async function SongsPage({ params }: Props) {
+ const vertical = VERTICALS.find((v) => v.slug === params.vertical)
+
+ if (!vertical) {
+ notFound()
+ }
+
+ const songs = await getSongs(vertical.slug)
+
+ return (
+
+
+
+ {vertical.emoji}
+ {vertical.name} Songs
+
+
+
+ {songs.length === 0 ? (
+
+
No songs imported yet for {vertical.name}.
+
Run the data importer to populate songs.
+
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/frontend/app/[vertical]/venues/page.tsx b/frontend/app/[vertical]/venues/page.tsx
new file mode 100644
index 0000000..0e67a9e
--- /dev/null
+++ b/frontend/app/[vertical]/venues/page.tsx
@@ -0,0 +1,75 @@
+import { VERTICALS } from "@/contexts/vertical-context"
+import { notFound } from "next/navigation"
+import { getApiUrl } from "@/lib/api-config"
+
+interface Props {
+ params: { vertical: string }
+}
+
+export function generateStaticParams() {
+ return VERTICALS.map((v) => ({
+ vertical: v.slug,
+ }))
+}
+
+async function getVenues(verticalSlug: string) {
+ try {
+ const res = await fetch(`${getApiUrl()}/venues?vertical=${verticalSlug}`, {
+ next: { revalidate: 60 }
+ })
+ if (!res.ok) return []
+ return res.json()
+ } catch {
+ return []
+ }
+}
+
+export default async function VenuesPage({ params }: Props) {
+ const vertical = VERTICALS.find((v) => v.slug === params.vertical)
+
+ if (!vertical) {
+ notFound()
+ }
+
+ const venues = await getVenues(vertical.slug)
+
+ return (
+
+
+
+ {vertical.emoji}
+ {vertical.name} Venues
+
+
+
+ {venues.length === 0 ? (
+
+
No venues imported yet for {vertical.name}.
+
Run the data importer to populate venues.
+
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx
index 790feca..873952d 100644
--- a/frontend/app/layout.tsx
+++ b/frontend/app/layout.tsx
@@ -5,6 +5,7 @@ import { Navbar } from "@/components/layout/navbar";
import { cn } from "@/lib/utils";
import { PreferencesProvider } from "@/contexts/preferences-context";
import { AuthProvider } from "@/contexts/auth-context";
+import { VerticalProvider } from "@/contexts/vertical-context";
import { ThemeProvider } from "@/components/theme-provider";
import { Footer } from "@/components/layout/footer";
import Script from "next/script";
@@ -20,8 +21,8 @@ const jetbrainsMono = JetBrains_Mono({
});
export const metadata: Metadata = {
- title: "Elmeg",
- description: "A Place to talk Goose",
+ title: "Fediversion",
+ description: "The ultimate HeadyVersion platform for all jam bands",
};
export default function RootLayout({
@@ -48,13 +49,15 @@ export default function RootLayout({
disableTransitionOnChange
>
-
-
-
- {children}
-
-
-
+
+
+
+
+ {children}
+
+
+
+