mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
f2329c9a63
The nav-entry seam was the wrong shape for what actually attaches here. A subscription surface is about the install, not about a trip: /admin is already behind an admin check, already grouped, and already where somebody looks to change how their instance works. A top-level nav entry put it beside My Trips for a screen opened once a month by one person. Two lines in AdminPage rather than an overlay replacing the file: the tab list gets the extra entries appended per group, and the body renders whichever one is active. Appended rather than inserted because PageSidebar needs a group's tabs contiguous. The route and nav-entry seams stay. They are the right answer for a surface that genuinely is a page, and both remain empty here.
99 lines
4.0 KiB
TypeScript
99 lines
4.0 KiB
TypeScript
import type { ReactElement } from 'react'
|
|
import type { LucideIcon } from 'lucide-react'
|
|
|
|
/**
|
|
* The attachment point for surfaces that only exist on a centrally administered
|
|
* install.
|
|
*
|
|
* Empty here, and empty in every build of this repository. An installation whose
|
|
* operator ships additional screens replaces this file at build time; nothing
|
|
* else in the client changes, because everything below reads from these two
|
|
* exports rather than naming a screen directly.
|
|
*
|
|
* Why it is a file and not a plugin: what belongs here is not a third-party
|
|
* extension living behind a sandbox boundary. It is part of the product for the
|
|
* people who get that product operated for them, and it needs the same
|
|
* components, the same tokens and the same overlays as everything around it. A
|
|
* frame cannot open a dialog past its own edge.
|
|
*
|
|
* Two rules keep this honest:
|
|
* - Never import from here conditionally at a call site. The arrays are always
|
|
* read, and they are simply empty, so the public build takes exactly the same
|
|
* code path as any other.
|
|
* - Nothing here may be required for TREK to work. Everything below is
|
|
* additive: routes that are not registered, nav entries that are not shown.
|
|
*/
|
|
|
|
export interface ManagedRoute {
|
|
/** Router path, e.g. `/subscription`. Must not collide with an existing one. */
|
|
path: string
|
|
/** Rendered inside the app's ProtectedRoute wrapper, like every other screen. */
|
|
element: ReactElement
|
|
/**
|
|
* Refuse the route to anyone but an admin, the same way /admin does.
|
|
*
|
|
* Hiding the nav entry is not access control — the path is still typeable, and
|
|
* a screen about how the install is run should not open for a traveller who
|
|
* was invited to one trip. Set this alongside `adminOnly` on the matching nav
|
|
* entry; the two answer different questions.
|
|
*/
|
|
adminOnly?: boolean
|
|
}
|
|
|
|
export interface ManagedNavItem {
|
|
/** Stable id for React keys; prefixed by the consumer, so keep it bare. */
|
|
id: string
|
|
path: string
|
|
label: string
|
|
/** lucide-react, like every other icon in the app — see the theme README. */
|
|
Icon: LucideIcon
|
|
/**
|
|
* Hide the entry from everyone but an admin.
|
|
*
|
|
* Not cosmetic. What attaches here is the operator's side of the install, and
|
|
* a trip's other travellers are on this instance as guests of the person who
|
|
* runs it — showing them an entry about how it is run is noise at best and
|
|
* confusing at worst. The route behind it still checks for itself; this only
|
|
* decides who is offered the door.
|
|
*/
|
|
adminOnly?: boolean
|
|
}
|
|
|
|
/** Filter for the two nav bars. Exported so both apply the same rule. */
|
|
export function visibleManagedNavItems(isAdmin: boolean): ManagedNavItem[] {
|
|
return managedNavItems.filter((item) => !item.adminOnly || isAdmin)
|
|
}
|
|
|
|
export interface ManagedAdminTab {
|
|
/** Tab id, also the deep-link value of ?tab= on /admin. Keep it bare. */
|
|
id: string
|
|
label: string
|
|
/** lucide-react, like every other icon in the app. */
|
|
Icon: LucideIcon
|
|
/**
|
|
* Which group heading the tab sits under. The admin sidebar groups tabs and
|
|
* requires a group's tabs to be contiguous, so this is appended to the end of
|
|
* whichever group it names rather than inserted mid-list.
|
|
*/
|
|
group?: 'users' | 'config' | 'integration' | 'maintenance'
|
|
/** Rendered in the tab body. /admin already requires an admin to get here. */
|
|
element: ReactElement
|
|
}
|
|
|
|
/** Extra protected routes. Spread into the router next to the built-in ones. */
|
|
export const managedRoutes: ManagedRoute[] = []
|
|
|
|
/**
|
|
* Extra tabs in Admin → the left sidebar.
|
|
*
|
|
* This is where a surface belongs when it is about the install rather than about
|
|
* a trip: /admin is already behind an admin check, already grouped, and already
|
|
* the place somebody looks when they want to change how their instance works.
|
|
* A top-level nav entry would put it next to My Trips for a screen opened once
|
|
* a month by one person.
|
|
*/
|
|
export const managedAdminTabs: ManagedAdminTab[] = []
|
|
|
|
/** Extra entries for the desktop navbar and the mobile tab bar. */
|
|
export const managedNavItems: ManagedNavItem[] = []
|