Files
soroushasadi/components/admin/AdminShell.tsx
T

157 lines
4.5 KiB
TypeScript
Raw Normal View History

2026-05-31 12:47:02 +03:30
'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>
);
}