// Which permission opens which page: the route guard in components/auth/AccessGate.tsx uses this so a role // can't open a screen by typing its address (the sidebar only hides the link). Derived from the sidebar's own // list, so a page and its menu entry can never disagree; the API enforces the same codes on every request, so this // is a courtesy that replaces a wall of "Forbidden" toasts with one clear message — never the protection. import { navItems } from "@/components/Layouts/AppSidebar" interface RouteRule { prefix: string /** Any one of these opens the page. */ codes: string[] } /** Pages with no menu entry of their own, but that belong to a permission anyway. */ const EXTRA_RULES: RouteRule[] = [ { prefix: "/dashboard/settings/audit-logs", codes: ["settings.audit-logs"] }, ] function buildRules(): RouteRule[] { const rules: RouteRule[] = [...EXTRA_RULES] for (const item of navItems) { // The landing page and Help are open to everyone with a role (they hold no data of their own). if (item.code !== "dashboard" && item.code !== "help") rules.push({ prefix: item.href, codes: [item.code] }) for (const child of item.children ?? []) { rules.push({ prefix: child.href, codes: [child.code, ...(child.altCodes ?? [])] }) } } // Longest prefix first, so "/dashboard/procurement/purchase-orders/approvals" beats ".../purchase-orders". return rules.sort((a, b) => b.prefix.length - a.prefix.length) } let cached: RouteRule[] | null = null /** * The permission codes that open `pathname` (any one suffices), or null when the page is unguarded * (the landing page, Help, and anything not in the menu: those are protected by the API alone). */ export function requiredCodesFor(pathname: string): string[] | null { cached ??= buildRules() const path = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname const rule = cached.find((r) => path === r.prefix || path.startsWith(r.prefix + "/")) return rule ? rule.codes : null }