157 lines
4.5 KiB
TypeScript
157 lines
4.5 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname, useRouter } from 'next/navigation';
|
|
import { EDITABLE_SECTIONS } from '@/lib/content/sections';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
export function AdminShell({ children }: { children: React.ReactNode }) {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
|
|
async function logout() {
|
|
await fetch('/api/admin/logout', { method: 'POST' });
|
|
router.replace('/admin/login');
|
|
router.refresh();
|
|
}
|
|
|
|
return (
|
|
<div dir="ltr" className="flex min-h-screen bg-base-900 text-slate-200">
|
|
{/* Sidebar */}
|
|
<aside className="hidden w-64 shrink-0 flex-col border-e border-white/8 bg-base-900/60 p-4 md:flex">
|
|
<Link href="/admin" className="mb-6 flex items-center gap-2 px-2">
|
|
<span className="grid h-8 w-8 place-items-center rounded-lg bg-electric/15 font-mono text-sm font-bold text-electric">
|
|
SA
|
|
</span>
|
|
<span className="text-sm font-semibold text-white">Content CMS</span>
|
|
</Link>
|
|
|
|
<nav className="flex flex-col gap-0.5">
|
|
<SideLink href="/admin" active={pathname === '/admin'}>
|
|
Dashboard
|
|
</SideLink>
|
|
<div className="mt-3 px-3 pb-1 font-mono text-[0.6rem] uppercase tracking-wider text-slate-600">
|
|
Sections
|
|
</div>
|
|
{EDITABLE_SECTIONS.map((s) => {
|
|
const href = `/admin/sections/${s.key}`;
|
|
return (
|
|
<SideLink key={s.key} href={href} active={pathname === href}>
|
|
{s.label.en}
|
|
</SideLink>
|
|
);
|
|
})}
|
|
<div className="mt-3 px-3 pb-1 font-mono text-[0.6rem] uppercase tracking-wider text-slate-600">
|
|
Content
|
|
</div>
|
|
<SideLink href="/admin/posts" active={pathname.startsWith('/admin/posts')}>
|
|
Journal articles
|
|
</SideLink>
|
|
</nav>
|
|
|
|
<div className="mt-auto flex flex-col gap-1 pt-4">
|
|
<a
|
|
href="/"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="rounded-lg px-3 py-2 text-sm text-slate-400 transition-colors hover:bg-white/[0.04] hover:text-white"
|
|
>
|
|
View site ↗
|
|
</a>
|
|
<button
|
|
type="button"
|
|
onClick={logout}
|
|
className="rounded-lg px-3 py-2 text-start text-sm text-slate-400 transition-colors hover:bg-white/[0.04] hover:text-white"
|
|
>
|
|
Log out
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Mobile top bar */}
|
|
<div className="flex min-w-0 grow flex-col">
|
|
<header className="flex items-center justify-between border-b border-white/8 px-5 py-3 md:hidden">
|
|
<Link href="/admin" className="text-sm font-semibold text-white">
|
|
Content CMS
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
onClick={logout}
|
|
className="text-sm text-slate-400 hover:text-white"
|
|
>
|
|
Log out
|
|
</button>
|
|
</header>
|
|
|
|
{/* Mobile section selector */}
|
|
<nav className="flex gap-2 overflow-x-auto border-b border-white/8 px-5 py-2 md:hidden">
|
|
<MobileChip href="/admin" active={pathname === '/admin'} label="Dashboard" />
|
|
{EDITABLE_SECTIONS.map((s) => (
|
|
<MobileChip
|
|
key={s.key}
|
|
href={`/admin/sections/${s.key}`}
|
|
active={pathname === `/admin/sections/${s.key}`}
|
|
label={s.label.en}
|
|
/>
|
|
))}
|
|
<MobileChip
|
|
href="/admin/posts"
|
|
active={pathname.startsWith('/admin/posts')}
|
|
label="Articles"
|
|
/>
|
|
</nav>
|
|
|
|
<main className="grow px-6 py-6 sm:px-8">{children}</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SideLink({
|
|
href,
|
|
active,
|
|
children,
|
|
}: {
|
|
href: string;
|
|
active: boolean;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className={cn(
|
|
'rounded-lg px-3 py-2 text-sm transition-colors',
|
|
active
|
|
? 'bg-electric/12 font-medium text-electric'
|
|
: 'text-slate-400 hover:bg-white/[0.04] hover:text-white',
|
|
)}
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
function MobileChip({
|
|
href,
|
|
active,
|
|
label,
|
|
}: {
|
|
href: string;
|
|
active: boolean;
|
|
label: string;
|
|
}) {
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className={cn(
|
|
'shrink-0 rounded-full border px-3 py-1 text-xs transition-colors',
|
|
active
|
|
? 'border-electric/40 bg-electric/10 text-electric'
|
|
: 'border-white/10 text-slate-400',
|
|
)}
|
|
>
|
|
{label}
|
|
</Link>
|
|
);
|
|
}
|