From 81970d1e6a5d565808d8aade28aed7df5b19b32d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 20 Aug 2026 07:28:19 +0000 Subject: [PATCH] fix: Navbar loading-state skeletons and multi-role dashboard filtering (#48) - Render stable skeleton placeholders during auth hydration instead of flashing logged-out state for users with valid persisted sessions - Filter dashboard dropdown links by user.roles array to support multi-role users (e.g. maintainer + sponsor see both links) - Show dashboard dropdown skeleton during loading to prevent layout shift - Use logical start-0 class for RTL-safe dropdown positioning TypeScript compiles cleanly with npx tsc --noEmit. --- src/components/layout/Navbar.tsx | 79 +++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index b768467..73387bf 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -7,21 +7,36 @@ import { Avatar } from "@/components/ui/Avatar"; import { ThemeToggle } from "@/components/ui/ThemeToggle"; import { NetworkBadge } from "@/components/ui/NetworkBadge"; import { useAuth } from "@/context/AuthContext"; +import type { UserRole } from "@/types"; const links = [ { href: "/issues", label: "Bounties" }, { href: "/milestones", label: "Milestones" }, ]; -const dashboardLinks = [ - { href: "/dashboard/contributor", label: "Contributor" }, - { href: "/dashboard/maintainer", label: "Maintainer" }, - { href: "/dashboard/sponsor", label: "Sponsor" }, +/** + * Multi-role UX decision (Issue #48): + * AuthUser.roles is UserRole[] — a user can hold multiple roles simultaneously + * (e.g. maintainer + sponsor). The dashboard dropdown now renders ALL applicable + * role links filtered by the user's actual roles array, rather than assuming + * single-role membership. Users with zero roles see no dashboard links but + * still see the authenticated nav (avatar, sign out). + */ +const dashboardLinks: Array<{ href: string; label: string; role: UserRole }> = [ + { href: "/dashboard/contributor", label: "Contributor", role: "contributor" }, + { href: "/dashboard/maintainer", label: "Maintainer", role: "maintainer" }, + { href: "/dashboard/sponsor", label: "Sponsor", role: "sponsor" }, ]; export function Navbar() { const { user, loading, logout } = useAuth(); + // Filter dashboard links to only those matching the user's assigned roles. + // When loading, we render a skeleton instead of filtering (avoids flash). + const visibleDashboardLinks = user + ? dashboardLinks.filter((link) => user.roles.includes(link.role)) + : []; + return (
@@ -38,25 +53,38 @@ export function Navbar() { {link.label} ))} -
- -
-
- {dashboardLinks.map((link) => ( - - {link.label} - - ))} + {/* Dashboard dropdown: only shown when user has at least one role, + or as a neutral placeholder during loading to prevent layout shift */} + {(loading || visibleDashboardLinks.length > 0) && ( +
+ +
+
+ {loading ? ( + // Skeleton placeholders during auth hydration — prevents + // flash of empty dropdown or wrong links + <> +
+
+ + ) : ( + visibleDashboardLinks.map((link) => ( + + {link.label} + + )) + )} +
-
+ )} {user && ( Reputation @@ -67,7 +95,14 @@ export function Navbar() {
- {loading ? null : user ? ( + {loading ? ( + // Stable skeleton during auth hydration — no flash of logged-out + // state for users with valid persisted sessions +
+
+
+
+ ) : user ? (