/* ═══════════════════════════════════════════════════════════════ ADMIN NAVIGATION — src/pages/admin/adminNav.js Lifted out of AdminLayout because two things read it now: the header tabs and the card grid on the home page. Adding an entity stays one entry here plus a descriptor — there's still no second list to keep in step, it just isn't inside the layout file any more. `blurb` is only read by the home cards. The header ignores it. `superOnly` hides an entry from anyone below superadmin. It hides, nothing more: the route still has to be guarded and the API still has to say no. Treating a filtered menu as access control is how you end up with a URL that works. Three areas, three titles. The area is derived from the path rather than declared per route, so a page added under /admin/panel/… inherits the right title without registering anything. ═══════════════════════════════════════════════════════════════ */ import { isSuper } from "../../lib/roles.ts"; export const ADMIN_HOME = "/admin/home"; export const ADMIN_PANEL = "/admin/panel"; export const AREA_TITLES = { home: "NGU Admin Home", cms: "NGU Admin CMS", panel: "NGU Admin Panel", }; /* The CMS tabs. Teams and Awards live under Organizations because that's where they belong conceptually — a team is part of an org, an award is given by one — even though each is its own table and its own page. */ export const CMS_NAV = [ { to: "/admin/events", label: "Events", blurb: "Retreats, conferences and gatherings, with their sections and rosters.", }, { to: "/admin/organizations", label: "Organizations", blurb: "Regions, chapters and partners — plus the teams and awards they own.", children: [ { to: "/admin/organizations", label: "All organizations" }, { to: "/admin/teams", label: "Teams" }, { to: "/admin/awards", label: "Awards" }, ], }, { to: "/admin/people", label: "People", blurb: "Bios, contact details, affiliations and awards received.", }, // Its own tab rather than a child of anything: a timeline entry can // point at an event, an organization, an award, a person or a team, // so filing it under one of them would be arbitrary. { to: "/admin/timeline", label: "Timeline", blurb: "What the history page shows, and the order it shows it in.", }, // One record, not a list — the tab opens it directly. { to: "/admin/front_page", label: "Front page", blurb: "The hero, its photos or livestream, and which bands the home page draws.", }, ]; /* Forms are submissions coming in rather than content going out, so they get their own group in the header and their own block on the home page. A group with no `to` of its own opens its first child, so clicking the word Forms goes somewhere rather than nowhere. */ export const FORMS_NAV = { label: "Forms", blurb: "Whatever the public site has sent us.", separated: true, children: [ { to: "/admin/feedback", label: "Website feedback", blurb: "The triage queue for the feedback form.", }, ], }; /* Accounts and server state, not content — which is why it sits outside the CMS rather than as another tab within it. */ export const PANEL_NAV = { to: ADMIN_PANEL, label: "Panel", blurb: "Accounts, sessions and the state of the server.", separated: true, superOnly: true, }; export const NAV = [...CMS_NAV, FORMS_NAV, PANEL_NAV]; /* What this user may see. Call it with the user from useAuth. */ export function navFor(user) { return NAV.filter((item) => !item.superOnly || isSuper(user)); } /* A tab owns its own page and everything below it, so editing /admin/teams/ngu-board keeps Teams lit. */ export const matches = (pathname, to) => Boolean(to) && (pathname === to || pathname.startsWith(`${to}/`)); export const target = (item) => item.to ?? item.children?.[0]?.to; export function areaFor(pathname) { if (matches(pathname, ADMIN_HOME)) return "home"; if (matches(pathname, ADMIN_PANEL)) return "panel"; return "cms"; }