refactor(layouts): shared RoleSwitcherPill across dashboards (§D.6)
Extract ROLE_SWITCH_OPTIONS + switchableRoles computation from the two
duplicated copies (role-nav.tsx + admin-sidebar.tsx) into a single
src/components/layouts/role-switcher.tsx module.
Adds a RoleSwitcherPill component placed top-right of every dashboard:
- Hidden for single-role users
- Hidden during impersonation
- Same visual + click target across /jury, /mentor, /applicant,
/observer, /award-master AND /admin (admin layout gains a small
top-bar to host the pill)
Removes the duplicate role-switcher items from the admin sidebar's
bottom user-menu — one source of truth instead of three.
Plan: docs/superpowers/plans/2026-04-28-pr6-multi-role-and-workspace-previews.md
This commit is contained in:
@@ -50,6 +50,7 @@ import { UserAvatar } from '@/components/shared/user-avatar'
|
||||
import { NotificationBell } from '@/components/shared/notification-bell'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import { useRoleSwitcher } from './role-switcher'
|
||||
|
||||
interface AdminSidebarProps {
|
||||
user: {
|
||||
@@ -157,14 +158,6 @@ const roleLabels: Record<string, string> = {
|
||||
AWARD_MASTER: 'Award Master',
|
||||
}
|
||||
|
||||
// Role switcher config — maps roles to their dashboard views
|
||||
const ROLE_SWITCH_OPTIONS: Record<string, { label: string; path: string; icon: typeof LayoutDashboard }> = {
|
||||
JURY_MEMBER: { label: 'Jury View', path: '/jury', icon: Scale },
|
||||
MENTOR: { label: 'Mentor View', path: '/mentor', icon: Handshake },
|
||||
OBSERVER: { label: 'Observer View', path: '/observer', icon: Eye },
|
||||
AWARD_MASTER: { label: 'Award Master', path: '/award-master', icon: Trophy },
|
||||
}
|
||||
|
||||
export function AdminSidebar({ user }: AdminSidebarProps) {
|
||||
const pathname = usePathname()
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||
@@ -186,11 +179,10 @@ export function AdminSidebar({ user }: AdminSidebarProps) {
|
||||
const isSuperAdmin = user.role === 'SUPER_ADMIN'
|
||||
const roleLabel = roleLabels[user.role || ''] || 'User'
|
||||
|
||||
// Roles the user can switch to (non-admin roles they hold)
|
||||
const userRoles = (session?.user?.roles as UserRole[] | undefined) ?? []
|
||||
const switchableRoles = Object.entries(ROLE_SWITCH_OPTIONS).filter(
|
||||
([role]) => userRoles.includes(role as UserRole)
|
||||
)
|
||||
// Roles the user can switch to — shared logic. Admin sidebar dropdown
|
||||
// no longer renders these; the RoleSwitcherPill in the layout's top-bar
|
||||
// handles role switching for admins, matching every other dashboard.
|
||||
const { switchableRoles } = useRoleSwitcher('/admin')
|
||||
|
||||
// Build dynamic admin nav with current edition's apply page
|
||||
const dynamicAdminNav = adminNavigation.map((item) => {
|
||||
@@ -374,46 +366,9 @@ export function AdminSidebar({ user }: AdminSidebarProps) {
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{switchableRoles.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator className="my-1" />
|
||||
{switchableRoles.length <= 2 ? (
|
||||
// Flat list for 1-2 roles
|
||||
switchableRoles.map(([, opt]) => (
|
||||
<DropdownMenuItem key={opt.path} asChild>
|
||||
<Link
|
||||
href={opt.path as Route}
|
||||
className="flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-2"
|
||||
>
|
||||
<opt.icon className="h-4 w-4 text-muted-foreground" />
|
||||
<span>{opt.label}</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
) : (
|
||||
// Submenu for 3+ roles
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="flex items-center gap-2.5 rounded-md px-2 py-2">
|
||||
<ArrowRightLeft className="h-4 w-4 text-muted-foreground" />
|
||||
<span>Switch View</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="min-w-[160px]">
|
||||
{switchableRoles.map(([, opt]) => (
|
||||
<DropdownMenuItem key={opt.path} asChild>
|
||||
<Link
|
||||
href={opt.path as Route}
|
||||
className="flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-2"
|
||||
>
|
||||
<opt.icon className="h-4 w-4 text-muted-foreground" />
|
||||
<span>{opt.label}</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Role switcher items moved to the layout's top-bar
|
||||
RoleSwitcherPill — single source of truth across all
|
||||
dashboards. */}
|
||||
|
||||
<DropdownMenuSeparator className="my-1" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user