From 012ad64a0df835815145b890aeb1017bc343350c Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 27 Apr 2026 21:02:28 +0800 Subject: [PATCH] feat: add admin layout with sidebar, auth guard, and navbar link - Admin layout with server-side auth guard (redirects non-admins) - Reusable sidebar component with active state detection - Navbar profile dropdown shows admin link for admin users - Admin routes are noindexed (robots: false) - /admin redirects to /admin/users --- app/(admin)/admin/page.tsx | 5 ++++ app/(admin)/layout.tsx | 44 ++++++++++++++++++++++++++++++ components/admin/admin-sidebar.tsx | 40 +++++++++++++++++++++++++++ components/navbar.tsx | 24 ++++++++++++++++ lib/routes.ts | 18 ++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 app/(admin)/admin/page.tsx create mode 100644 app/(admin)/layout.tsx create mode 100644 components/admin/admin-sidebar.tsx diff --git a/app/(admin)/admin/page.tsx b/app/(admin)/admin/page.tsx new file mode 100644 index 0000000..cbb15d0 --- /dev/null +++ b/app/(admin)/admin/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation" + +export default function AdminPage() { + redirect("/admin/users") +} diff --git a/app/(admin)/layout.tsx b/app/(admin)/layout.tsx new file mode 100644 index 0000000..14e9756 --- /dev/null +++ b/app/(admin)/layout.tsx @@ -0,0 +1,44 @@ +import { redirect } from "next/navigation" +import { headers } from "next/headers" +import { auth } from "@/lib/auth" +import { AdminSidebar } from "@/components/admin/admin-sidebar" +import type { Metadata } from "next" + +export const metadata: Metadata = { + title: { template: "%s | Admin — DeckyVault", default: "Admin — DeckyVault" }, + robots: { index: false, follow: false }, +} + +export default async function AdminLayout({ + children, +}: { + children: React.ReactNode +}) { + const session = await auth.api.getSession({ + headers: await headers(), + }) + + if (!session || session.user.role !== "admin") { + redirect("/") + } + + return ( +
+
+
+

Admin

+

+ Manage users, hardware, and games +

+
+
+ +
+
+ +
{children}
+
+
+
+ ) +} diff --git a/components/admin/admin-sidebar.tsx b/components/admin/admin-sidebar.tsx new file mode 100644 index 0000000..dd837ae --- /dev/null +++ b/components/admin/admin-sidebar.tsx @@ -0,0 +1,40 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" +import { UsersIcon, CpuIcon, Gamepad2Icon } from "lucide-react" + +const adminNavItems = [ + { href: "/admin/users", label: "Users", icon: UsersIcon }, + { href: "/admin/hardware", label: "Hardware", icon: CpuIcon }, + { href: "/admin/games", label: "Games", icon: Gamepad2Icon }, +] + +export function AdminSidebar() { + const pathname = usePathname() + + return ( + + ) +} diff --git a/components/navbar.tsx b/components/navbar.tsx index de5fd93..00fa1d7 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -11,6 +11,7 @@ import { Gamepad2Icon, LogOut, MenuIcon, + ShieldIcon, User, XIcon, } from "lucide-react" @@ -302,6 +303,19 @@ export default function Navbar() { {route.title} ))} + {session.user.role === "admin" && ( + <> +
+ setUserMenuOpen(false)} + className="w-full flex items-center gap-2 px-3 py-2 text-sm text-primary/80 hover:text-primary hover:bg-primary/5 transition-colors cursor-pointer" + > + + Admin + + + )}