From 838fbe1b50fca8d374dc786dbf1bb753c4f0c8ac Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Sun, 5 Apr 2026 15:45:04 +0200 Subject: [PATCH] feat(panel): group sidebar nav so admins see both admin and player views Introduces NavGroup structure with labeled sections in the sidebar. Admins see "Administration" and "Player" groups; players see a flat list unchanged. Extracts SidebarNavItem, SidebarNavSection, and SidebarUserProfile components from the monolithic sidebarContent blob. Co-Authored-By: Claude Opus 4.6 (1M context) --- panel/src/components/Layout.tsx | 260 ++++++++++++++++++++++---------- 1 file changed, 178 insertions(+), 82 deletions(-) diff --git a/panel/src/components/Layout.tsx b/panel/src/components/Layout.tsx index 5cd5a97..73c8504 100644 --- a/panel/src/components/Layout.tsx +++ b/panel/src/components/Layout.tsx @@ -27,25 +27,165 @@ interface NavItem { icon: React.ComponentType<{ className?: string }>; } -const adminNavItems: NavItem[] = [ - { path: "/admin", label: "Dashboard", icon: LayoutDashboard }, - { path: "/admin/users", label: "Users", icon: Users }, - { path: "/admin/items", label: "Items", icon: Package }, - { path: "/admin/classes", label: "Classes", icon: GraduationCap }, - { path: "/admin/quests", label: "Quests", icon: Scroll }, - { path: "/admin/lootdrops", label: "Lootdrops", icon: Gift }, - { path: "/admin/moderation", label: "Moderation", icon: Shield }, - { path: "/admin/transactions", label: "Transactions", icon: ArrowLeftRight }, - { path: "/admin/settings", label: "Settings", icon: Settings }, - { path: "/games", label: "Games", icon: Gamepad2 }, +interface NavGroup { + label: string | null; + items: NavItem[]; +} + +const adminNavGroups: NavGroup[] = [ + { + label: "Administration", + items: [ + { path: "/admin", label: "Dashboard", icon: LayoutDashboard }, + { path: "/admin/users", label: "Users", icon: Users }, + { path: "/admin/items", label: "Items", icon: Package }, + { path: "/admin/classes", label: "Classes", icon: GraduationCap }, + { path: "/admin/quests", label: "Quests", icon: Scroll }, + { path: "/admin/lootdrops", label: "Lootdrops", icon: Gift }, + { path: "/admin/moderation", label: "Moderation", icon: Shield }, + { path: "/admin/transactions", label: "Transactions", icon: ArrowLeftRight }, + { path: "/admin/settings", label: "Settings", icon: Settings }, + ], + }, + { + label: "Player", + items: [ + { path: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, + { path: "/games", label: "Games", icon: Gamepad2 }, + { path: "/leaderboards", label: "Leaderboards", icon: Trophy }, + ], + }, ]; -const playerNavItems: NavItem[] = [ - { path: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, - { path: "/games", label: "Games", icon: Gamepad2 }, - { path: "/leaderboards", label: "Leaderboards", icon: Trophy }, +const playerNavGroups: NavGroup[] = [ + { + label: null, + items: [ + { path: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, + { path: "/games", label: "Games", icon: Gamepad2 }, + { path: "/leaderboards", label: "Leaderboards", icon: Trophy }, + ], + }, ]; +function SidebarNavItem({ + path, + label, + icon: Icon, + active, + showLabel, + onNavigate, +}: NavItem & { active: boolean; showLabel: boolean; onNavigate: (path: string) => void }) { + return ( + + ); +} + +function SidebarNavSection({ + group, + index, + showLabels, + isActive, + onNavigate, +}: { + group: NavGroup; + index: number; + showLabels: boolean; + isActive: (path: string) => boolean; + onNavigate: (path: string) => void; +}) { + return ( +
0 && "mt-4")}> + {group.label && showLabels && ( +
+ {group.label} +
+ )} + {!showLabels && index > 0 && ( +
+ )} +
+ {group.items.map((item) => ( + + ))} +
+
+ ); +} + +function SidebarUserProfile({ + user, + showLabels, + collapsed, + mobileOpen, + logout, + onToggleCollapse, +}: { + user: AuthUser; + showLabels: boolean; + collapsed: boolean; + mobileOpen: boolean; + logout: () => Promise; + onToggleCollapse: () => void; +}) { + const avatarUrl = user.avatar + ? `https://cdn.discordapp.com/avatars/${user.discordId}/${user.avatar}.png?size=64` + : null; + + return ( +
+ {showLabels && ( +
+ {avatarUrl ? ( + {user.username} + ) : ( +
+ {user.username[0]?.toUpperCase()} +
+ )} +
+
{user.username}
+
+
+ )} +
+ + +
+
+ ); +} + export default function Layout({ user, logout, @@ -60,11 +200,8 @@ export default function Layout({ const location = useLocation(); const navigate = useNavigate(); - const navItems = user.role === "admin" ? adminNavItems : playerNavItems; - - const avatarUrl = user.avatar - ? `https://cdn.discordapp.com/avatars/${user.discordId}/${user.avatar}.png?size=64` - : null; + const navGroups = user.role === "admin" ? adminNavGroups : playerNavGroups; + const showLabels = !collapsed || mobileOpen; // Close mobile drawer on route change useEffect(() => { @@ -83,63 +220,6 @@ export default function Layout({ setMobileOpen(false); } - const sidebarContent = ( - <> - - -
- {(!collapsed || mobileOpen) && ( -
- {avatarUrl ? ( - {user.username} - ) : ( -
- {user.username[0]?.toUpperCase()} -
- )} -
-
{user.username}
-
-
- )} -
- - {/* Collapse toggle only on desktop */} - -
-
- - ); - return (
{/* Mobile header bar */} @@ -165,10 +245,8 @@ export default function Layout({
- {sidebarContent} + + + setCollapsed((c) => !c)} + />