690 files changed
+12045
-33943
No files matched your search
@@ -0,0 +1,50 @@
|
||||
import type { BottomNavItem } from "@/components/navigation/bottom-nav";
|
||||
|
||||
/**
|
||||
* Single source of truth for the merged staff/owner/super-admin bottom nav
|
||||
* and desktop sidebar (see docs/implementation-plan.md and the Phase 11
|
||||
* "merged navigation" brief). Owner is a permission on a staff account, not
|
||||
* a separate app — Owner and SuperAdmin nav reuse the exact same personal
|
||||
* routes (`/staff/current`, `/staff/clients`, `/staff/settings`) a barber
|
||||
* uses, rather than duplicating them under `/owner`. This is what lets
|
||||
* owners reach barber tools and business tools from one menu with zero new
|
||||
* routes.
|
||||
*
|
||||
* A Super Administrator has no StaffProfile by construction (see
|
||||
* isSuperAdmin/getActingBusinessContext) — they get no "Now" tab, since
|
||||
* there is no personal schedule to show, per the brief's explicit "do not
|
||||
* fabricate a personal barber schedule for the Super Administrator".
|
||||
*/
|
||||
export type NavRole = "staff" | "owner" | "superadmin";
|
||||
|
||||
const CALENDAR: BottomNavItem = { href: "/staff", label: "Calendar", icon: "calendar-days" };
|
||||
const OWNER_CALENDAR: BottomNavItem = { href: "/owner/calendar", label: "Calendar", icon: "calendar-days" };
|
||||
const NOW: BottomNavItem = { href: "/staff/current", label: "Now", icon: "scissors" };
|
||||
const CLIENTS: BottomNavItem = { href: "/staff/clients", label: "Clients", icon: "users" };
|
||||
const MANAGE: BottomNavItem = { href: "/owner", label: "Manage", icon: "briefcase" };
|
||||
const SETTINGS: BottomNavItem = { href: "/staff/settings", label: "Settings", icon: "settings" };
|
||||
|
||||
const NAV_ITEMS_BY_ROLE: Record<NavRole, BottomNavItem[]> = {
|
||||
staff: [CALENDAR, NOW, CLIENTS, SETTINGS],
|
||||
owner: [OWNER_CALENDAR, NOW, CLIENTS, MANAGE, SETTINGS],
|
||||
superadmin: [OWNER_CALENDAR, CLIENTS, MANAGE, SETTINGS],
|
||||
};
|
||||
|
||||
export function getNavItems(role: NavRole): BottomNavItem[] {
|
||||
return NAV_ITEMS_BY_ROLE[role];
|
||||
}
|
||||
|
||||
/**
|
||||
* Longest-matching-href wins, rather than "every item whose href is a
|
||||
* pathname prefix" (the previous per-component logic) — that scheme let
|
||||
* `/staff/current` mark both "Calendar" (href `/staff`) and "Now" active at
|
||||
* once, since `/staff/current` also starts with `/staff`. Sorting by href
|
||||
* length and taking the first match makes only the most specific item win,
|
||||
* which is what makes nested routes (e.g. `/owner/settings/loyalty`,
|
||||
* `/staff/clients/[id]`) activate exactly one, correct primary tab.
|
||||
*/
|
||||
export function resolveActiveHref(pathname: string, items: BottomNavItem[]): string | null {
|
||||
const matches = items.filter((item) => pathname === item.href || pathname.startsWith(`${item.href}/`));
|
||||
if (matches.length === 0) return null;
|
||||
return matches.reduce((longest, item) => (item.href.length > longest.href.length ? item : longest)).href;
|
||||
}
|
||||
Reference in new issue
Block a user