NGU-Web/src/pages/admin/adminNav.js
Zaldimmar 6a69084de2 Replace the front page with an admin-driven home
The home page is rebuilt from scratch and configured from a new
Front page tab in the admin, backed by migration 017 and served by
GET /api/front-page.

Hero: brand, photos (crossfading slideshow with progress and pause)
or livestream (YouTube/Facebook/Vimeo embed with a LIVE badge),
switched by hand. After it, bands the admin can reorder, retitle or
hide: a countdown to the next event (series-aware), the National
Retreats carousel, a numbers band (typed in or counted from the
database), a horizontal rail of featured timeline entries, and a
"Find your way in" pathfinder replacing the old connect section.

The CRUD engine gains a `singleton` flag: the entity has one row,
made by its migration, and create and delete are refused. The list
screen opens that row and the editor drops the slug, back link and
delete. shapeSeries moves to shape.js so /front-page can share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 05:37:42 -05:00

120 lines
4.3 KiB
JavaScript

/* ═══════════════════════════════════════════════════════════════
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";
}