59001dbc5f
Stack on 201: PG 17 + Next.js 15 (Docker) + nginx (/, /php-ref/) Home: StatStrip (8 metrics), LiveActivity feed, refined Hero aurora Admin: 80+ menu catch-all renderer + read-only legacy table queries Auth/CRUD: fix narrowing in 6 action routes, fix wr_last varchar(19), fix back() new URL on missing referer Verify: 50/50 PASS across 5 iterations of login + comment + good + scrap Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
2.1 KiB
TypeScript
46 lines
2.1 KiB
TypeScript
import { redirect } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { getCurrentSiteUser } from '@/lib/page-data';
|
|
import { ADMIN_MENU } from '@/lib/admin-menu';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
const user = await getCurrentSiteUser();
|
|
if (!user) redirect('/login?next=/admin');
|
|
if ((user.level ?? 0) < 10) redirect('/?error=permission');
|
|
return (
|
|
<div className="grid items-start gap-4 lg:grid-cols-[260px_1fr]">
|
|
<aside className="rounded-2xl border border-neutral-100 bg-white p-3 shadow-[0_1px_2px_rgba(0,0,0,0.02)] lg:sticky lg:top-[180px] lg:max-h-[calc(100vh-200px)] lg:overflow-y-auto">
|
|
<div className="px-2 py-2">
|
|
<div className="text-[11px] font-bold uppercase tracking-widest text-brand-600">SLOT ADMIN</div>
|
|
<div className="mt-1 text-[14px] font-bold">관리자 콘솔</div>
|
|
<div className="text-[11px] text-neutral-text-soft">{user.nick} · Lv.{user.level}</div>
|
|
</div>
|
|
{ADMIN_MENU.map((g) => (
|
|
<details key={g.code} open className="group mt-2">
|
|
<summary className="flex cursor-pointer items-center gap-1.5 rounded-lg px-2 py-1.5 text-[12px] font-bold text-neutral-700 hover:bg-brand-50">
|
|
<span className="text-base">{g.icon}</span>
|
|
<span>{g.label}</span>
|
|
<span className="ml-auto text-[10px] text-neutral-400 transition group-open:rotate-90">▸</span>
|
|
</summary>
|
|
<ul className="m-0 grid gap-0.5 p-0 pl-2 list-none">
|
|
{g.items.map((it) => (
|
|
<li key={it.slug}>
|
|
<Link
|
|
href={'/admin' + (it.slug ? '/' + it.slug : '')}
|
|
className="block rounded-lg px-2.5 py-1.5 text-[12.5px] text-neutral-700 hover:bg-brand-50 hover:text-brand-700"
|
|
>
|
|
{it.label}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</details>
|
|
))}
|
|
</aside>
|
|
<section className="min-w-0">{children}</section>
|
|
</div>
|
|
);
|
|
}
|