feat(admin): expandable sidebar nav for admin sections

Adds an "Admin Console" link to the global sidebar (visible only to
ADMIN users). When the active route is under /admin/*, the link
expands into a sub-list of every section, mirroring the in-shell
section sidebar — same as Settings, where the global nav points to
the section landing and the shell drives in-context navigation.

Collapsed-sidebar mode keeps the icon-only Admin button without the
sub-list (consistent with the Bug Report and Settings buttons).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-09 17:00:07 +02:00
1 parent a34cd8cf4d
commit 73965cd1ac
1 file changed
+76
+76
View File
@@ -15,6 +15,7 @@ import {
MoreVertical,
Pill,
Settings,
Shield,
Sun,
Target,
Trophy,
@@ -27,6 +28,7 @@ import { Logo } from "@/components/ui/logo";
import { useAuth, useLogout } from "@/hooks/use-auth";
import { useTheme } from "@/components/providers";
import { useTranslations } from "@/lib/i18n/context";
import { ADMIN_SECTIONS } from "@/components/admin/admin-shell";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
Tooltip,
@@ -212,6 +214,9 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
export function SidebarNav() {
const pathname = usePathname();
const { t } = useTranslations();
const { user } = useAuth();
const isAdmin = user?.role === "ADMIN";
const onAdminPage = pathname.startsWith("/admin");
const [collapsed, setCollapsed] = useState(() => {
if (typeof window === "undefined") return false;
try {
@@ -387,6 +392,27 @@ export function SidebarNav() {
{t("nav.bugreport")}
</TooltipContent>
</Tooltip>
{isAdmin && (
<Tooltip>
<TooltipTrigger asChild>
<Link
href="/admin"
aria-current={onAdminPage ? "page" : undefined}
className={cn(
"flex items-center justify-center rounded-lg p-2.5 transition-colors",
onAdminPage
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<Shield className="h-4 w-4" />
</Link>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
{t("nav.admin")}
</TooltipContent>
</Tooltip>
)}
<Tooltip>
<TooltipTrigger asChild>
<Link
@@ -424,6 +450,56 @@ export function SidebarNav() {
<Bug className="h-4 w-4" />
{t("nav.bugreport")}
</Link>
{isAdmin && (
<>
<Link
href="/admin"
aria-current={onAdminPage ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
onAdminPage
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<Shield className="h-4 w-4" />
{t("nav.admin")}
</Link>
{/* Expandable section list — only visible while
navigating inside `/admin/*`. Mirrors the in-shell
sidebar so the user can jump between sections from
the global nav too. */}
{onAdminPage && (
<ul
className="space-y-1 pl-3"
aria-label={t("admin.shell.sectionsNav")}
>
{ADMIN_SECTIONS.map((section) => {
const sectionPath = `/admin/${section.slug}`;
const isActive = pathname.startsWith(sectionPath);
const Icon = section.icon;
return (
<li key={section.slug}>
<Link
href={sectionPath}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex items-center gap-2 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors",
isActive
? "bg-primary/10 text-primary"
: "text-muted-foreground hover:bg-accent hover:text-foreground",
)}
>
<Icon className="h-3.5 w-3.5" />
{t(section.titleKey)}
</Link>
</li>
);
})}
</ul>
)}
</>
)}
<Link
href="/settings/account"
aria-current={