mc-dashboard/components/Navbar.tsx

62 lines
2.2 KiB
TypeScript
Raw Normal View History

"use client";
import { useSession, signOut } from "next-auth/react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { ThemeToggle } from "@/components/ThemeToggle";
export function Navbar() {
const { data: session } = useSession();
Role-based access: admin vs superadmin - Credentials provider now resolves two distinct accounts from env: SUPERADMIN_USERNAME / SUPERADMIN_PASSWORD → role "superadmin" ADMIN_USERNAME / ADMIN_PASSWORD → role "admin" The role is carried through the JWT and Session callbacks so the UI and API can gate on it. Types extended via types/next-auth.d.ts. - lib/auth.ts exports requireRole(minRole) and sessionRole(session). - /api/players POST rejects "op" and "deop" with 403 unless the caller is superadmin. All other player actions (whitelist add/remove, ban, pardon) remain available to both roles. - lib/use-role.ts (client hook) exposes role / isSuperadmin / authed for UI gating without duplicating session typing. - PlayerManager: Add-OP button and per-row Deop action are hidden or disabled for non-superadmin; when a regular admin is viewing the Ops tab, a banner explains the read-only state. - PlayerDrawer: Make Op / Deop button disabled with tooltip for non-superadmin; whitelist, ban, pardon unchanged. - Navbar: subtle role pill next to the user name ("Super" for superadmin amber-tinted, "Admin" for admin). - Migration note: the existing ADMIN_* credentials now log in as the restricted admin role. Set SUPERADMIN_USERNAME + SUPERADMIN_PASSWORD in .env.local to retain operator-management ability. A placeholder superadmin account was generated in .env.local; the password is in the commit terminal output only, not the repo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:52:05 -06:00
const role = (session?.user as { role?: "admin" | "superadmin" } | undefined)?.role;
return (
<header className="border-b border-border bg-card">
2026-04-13 05:30:23 -06:00
<a
href="#main"
className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:rounded focus:bg-primary focus:text-primary-foreground focus:px-3 focus:py-1.5 focus:text-sm focus:font-medium"
>
Skip to content
</a>
<div className="max-w-5xl mx-auto flex items-center justify-between px-3 sm:px-6 py-2.5 sm:py-3">
<Link href="/" className="font-bold text-primary text-base sm:text-lg tracking-tight">
HurkiCorgi MC
</Link>
<div className="flex items-center gap-1 sm:gap-2">
<ThemeToggle />
{session ? (
<>
Role-based access: admin vs superadmin - Credentials provider now resolves two distinct accounts from env: SUPERADMIN_USERNAME / SUPERADMIN_PASSWORD → role "superadmin" ADMIN_USERNAME / ADMIN_PASSWORD → role "admin" The role is carried through the JWT and Session callbacks so the UI and API can gate on it. Types extended via types/next-auth.d.ts. - lib/auth.ts exports requireRole(minRole) and sessionRole(session). - /api/players POST rejects "op" and "deop" with 403 unless the caller is superadmin. All other player actions (whitelist add/remove, ban, pardon) remain available to both roles. - lib/use-role.ts (client hook) exposes role / isSuperadmin / authed for UI gating without duplicating session typing. - PlayerManager: Add-OP button and per-row Deop action are hidden or disabled for non-superadmin; when a regular admin is viewing the Ops tab, a banner explains the read-only state. - PlayerDrawer: Make Op / Deop button disabled with tooltip for non-superadmin; whitelist, ban, pardon unchanged. - Navbar: subtle role pill next to the user name ("Super" for superadmin amber-tinted, "Admin" for admin). - Migration note: the existing ADMIN_* credentials now log in as the restricted admin role. Set SUPERADMIN_USERNAME + SUPERADMIN_PASSWORD in .env.local to retain operator-management ability. A placeholder superadmin account was generated in .env.local; the password is in the commit terminal output only, not the repo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:52:05 -06:00
{role && (
<span
className={`hidden sm:inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide border ${
role === "superadmin"
? "border-amber-500/40 bg-amber-500/10 text-amber-300"
: "border-border bg-muted text-muted-foreground"
}`}
title={`Signed in as ${session.user?.name} (${role})`}
>
{role === "superadmin" ? "Super" : "Admin"}
</span>
)}
<Button variant="ghost" size="sm" render={<Link href="/admin" />}>
Admin
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => signOut({ callbackUrl: "/" })}
className="text-muted-foreground"
>
Logout
</Button>
</>
) : (
<Button variant="ghost" size="sm" render={<Link href="/login" />}>
Login
</Button>
)}
</div>
</div>
</header>
);
}