From 11ab0943f6857dd7789607db999a78de8c705588 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 28 Apr 2026 16:32:51 +0200 Subject: [PATCH] feat: name current view in role-switcher pill, add Mentors sidebar entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switcher trigger now shows the current view's icon + label with a chevron (e.g. "Admin View ⌄") instead of the vague "Switch View". Dropdown adds a header, marks the current view with a checkmark, and lists each accessible alternative explicitly. - Adds a "Mentors" entry to the admin sidebar between Juries and Awards so the existing /admin/mentors page is reachable from nav. --- src/components/layouts/admin-sidebar.tsx | 6 +++ src/components/layouts/role-switcher.tsx | 50 +++++++++++++++--------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/components/layouts/admin-sidebar.tsx b/src/components/layouts/admin-sidebar.tsx index 3597dd3..8d1f999 100644 --- a/src/components/layouts/admin-sidebar.tsx +++ b/src/components/layouts/admin-sidebar.tsx @@ -30,6 +30,7 @@ import { LogOut, ChevronRight, BookOpen, + GraduationCap, Handshake, History, Trophy, @@ -85,6 +86,11 @@ const navigation: NavItem[] = [ href: '/admin/juries', icon: Scale, }, + { + name: 'Mentors', + href: '/admin/mentors', + icon: GraduationCap, + }, { name: 'Awards', href: '/admin/awards', diff --git a/src/components/layouts/role-switcher.tsx b/src/components/layouts/role-switcher.tsx index 6aea070..a0507b7 100644 --- a/src/components/layouts/role-switcher.tsx +++ b/src/components/layouts/role-switcher.tsx @@ -4,7 +4,8 @@ import { useMemo } from 'react' import Link from 'next/link' import { useSession } from 'next-auth/react' import { - ArrowRightLeft, + Check, + ChevronDown, Eye, Handshake, LayoutDashboard, @@ -16,6 +17,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Button } from '@/components/ui/button' @@ -33,12 +36,8 @@ export const ROLE_SWITCH_OPTIONS: Record = { AWARD_MASTER: { label: 'Award Master', path: '/award-master', icon: Trophy }, } -/** - * Returns the list of dashboards the current user can switch to, - * excluding the one matching `currentBasePath`. Deduplicates admin paths - * (SUPER_ADMIN + PROGRAM_ADMIN both go to /admin). - */ export function useRoleSwitcher(currentBasePath: string): { + currentOption: RoleSwitchOption | null switchableRoles: Array<[string, RoleSwitchOption]> isImpersonating: boolean } { @@ -46,32 +45,47 @@ export function useRoleSwitcher(currentBasePath: string): { const userRoles = (session?.user?.roles as UserRole[] | undefined) ?? [] const isImpersonating = !!session?.user?.impersonating - const switchableRoles = useMemo(() => { - return Object.entries(ROLE_SWITCH_OPTIONS) - .filter(([role, opt]) => userRoles.includes(role as UserRole) && opt.path !== currentBasePath) + const { currentOption, switchableRoles } = useMemo(() => { + const accessible = Object.entries(ROLE_SWITCH_OPTIONS) + .filter(([role]) => userRoles.includes(role as UserRole)) .filter((entry, i, arr) => arr.findIndex(([, o]) => o.path === entry[1].path) === i) + const current = accessible.find(([, opt]) => opt.path === currentBasePath)?.[1] ?? null + const others = accessible.filter(([, opt]) => opt.path !== currentBasePath) + return { currentOption: current, switchableRoles: others } }, [userRoles, currentBasePath]) - return { switchableRoles, isImpersonating } + return { currentOption, switchableRoles, isImpersonating } } /** - * Top-right "Switch View" pill. Hidden for single-role users and during - * impersonation. Shows a popover listing alternate dashboards. + * Top-right view-switcher pill. Trigger names the current view; the dropdown + * lists alternative views. Hidden for single-view users and during impersonation. */ export function RoleSwitcherPill({ currentBasePath }: { currentBasePath: string }) { - const { switchableRoles, isImpersonating } = useRoleSwitcher(currentBasePath) - if (switchableRoles.length === 0 || isImpersonating) return null + const { currentOption, switchableRoles, isImpersonating } = useRoleSwitcher(currentBasePath) + if (switchableRoles.length === 0 || isImpersonating || !currentOption) return null + + const CurrentIcon = currentOption.icon return ( - - + + + Switch to another view + + + + {currentOption.label} + + + {switchableRoles.map(([role, opt]) => (