Phase 3. 인증 및 공통 레이아웃 작업

This commit is contained in:
2026-06-09 23:37:33 +09:00
parent a93a6fa8e1
commit dbf9b3e180
17 changed files with 1092 additions and 71 deletions
+84
View File
@@ -0,0 +1,84 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import {
ArrowUpCircle,
ClipboardList,
GraduationCap,
LayoutDashboard,
LogOut,
} from "lucide-react";
import { auth, signOut } from "@/auth";
import { Button } from "@/components/ui/button";
const navItems = [
{ href: "/", label: "대시보드", icon: LayoutDashboard },
{ href: "/maestros", label: "마에스트로", icon: GraduationCap },
{ href: "/extension-requests", label: "연장 신청", icon: ClipboardList },
{ href: "/upgrade-requests", label: "업그레이드", icon: ArrowUpCircle },
];
export default async function AdminLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await auth();
if (!session) {
redirect("/login");
}
return (
<div className="min-h-screen bg-muted/40">
<aside className="fixed inset-y-0 left-0 hidden w-64 border-r bg-background md:block">
<div className="flex h-16 items-center border-b px-5">
<Link className="text-lg font-semibold" href="/">
choco-admin
</Link>
</div>
<nav className="space-y-1 p-3">
{navItems.map((item) => {
const Icon = item.icon;
return (
<Link
className="flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
href={item.href}
key={item.href}
>
<Icon className="size-4" aria-hidden="true" />
{item.label}
</Link>
);
})}
</nav>
</aside>
<div className="md:pl-64">
<header className="sticky top-0 z-10 flex h-16 items-center justify-between border-b bg-background px-5">
<div>
<p className="text-sm font-medium">{session.user.name}</p>
<p className="text-xs text-muted-foreground">
ID {session.user.adminID}
</p>
</div>
<form
action={async () => {
"use server";
await signOut({ redirectTo: "/login" });
}}
>
<Button size="sm" type="submit" variant="outline">
<LogOut className="size-4" aria-hidden="true" />
</Button>
</form>
</header>
<main className="p-5">{children}</main>
</div>
</div>
);
}
+55
View File
@@ -0,0 +1,55 @@
import { Database, ShieldCheck, Table2 } from "lucide-react";
import { auth } from "@/auth";
const checks = [
{
label: "Next.js 16",
value: "App Router",
icon: ShieldCheck,
},
{
label: "Prisma",
value: "MariaDB introspected",
icon: Database,
},
{
label: "TanStack Table",
value: "Installed",
icon: Table2,
},
];
export default async function Home() {
const session = await auth();
return (
<section className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-normal"></h1>
<p className="mt-1 text-sm text-muted-foreground">
{session?.user.name} .
</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{checks.map((item) => {
const Icon = item.icon;
return (
<article
className="rounded-lg border bg-card p-5 text-card-foreground"
key={item.label}
>
<div className="mb-4 flex size-9 items-center justify-center rounded-md bg-muted">
<Icon className="size-5" aria-hidden="true" />
</div>
<h2 className="text-base font-medium">{item.label}</h2>
<p className="mt-1 text-sm text-muted-foreground">{item.value}</p>
</article>
);
})}
</div>
</section>
);
}