Files
admin-panel-hokm/components/Shell.tsx
T
2026-08-07 09:40:16 +03:30

169 lines
5.3 KiB
TypeScript

"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import {
LayoutDashboard,
Trophy,
ShoppingBag,
Users,
ReceiptText,
Image as ImageIcon,
Frame as FrameIcon,
Smile,
Medal,
MessageSquare,
LogOut,
Menu,
X,
Loader2,
} from "lucide-react";
import { clearAuth, isAuthed } from "@/lib/api";
const NAV = [
{ href: "/", label: "داشبورد", icon: LayoutDashboard },
{ href: "/tournaments", label: "تورنومنت‌ها", icon: Trophy },
{ href: "/shop", label: "فروشگاه", icon: ShoppingBag },
{ href: "/users", label: "کاربران", icon: Users },
{ href: "/transactions", label: "تراکنش‌ها", icon: ReceiptText },
{ href: "/carpets", label: "فرش‌ها", icon: ImageIcon },
{ href: "/frames", label: "قاب‌ها", icon: FrameIcon },
{ href: "/avatars", label: "شخصیت‌ها", icon: Smile },
{ href: "/rank-tiers", label: "رتبه‌ها", icon: Medal },
{ href: "/chat", label: "چت", icon: MessageSquare },
];
export default function Shell({
title,
subtitle,
children,
}: {
title: string;
subtitle?: string;
children: React.ReactNode;
}) {
const pathname = usePathname();
const router = useRouter();
const [ready, setReady] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
if (!isAuthed()) {
router.replace("/login");
} else {
setReady(true);
}
}, [router]);
// با تغییرِ مسیر، منوی موبایل را ببند.
useEffect(() => {
setOpen(false);
}, [pathname]);
if (!ready) {
return (
<div className="min-h-screen grid place-items-center">
<div className="flex flex-col items-center gap-3 text-[var(--gold)]">
<Loader2 size={28} className="animate-spin" />
<span className="text-sm">در حال بارگذاری…</span>
</div>
</div>
);
}
// basePath را حذف و اسلشِ انتهایی را نرمال می‌کنیم تا مسیرِ فعال (مثلِ
// /transactions و /transactions/ برابر) درست تشخیص داده شود.
const cur = (pathname.replace(/^\/panel/, "") || "/").replace(/\/+$/, "") || "/";
const aside = (
<aside className="w-56 shrink-0 border-l border-[#16305a] bg-[var(--bg)] flex flex-col h-full">
<div className="px-5 py-5 flex items-center justify-between border-b border-[#16305a]">
<div className="flex items-center gap-2">
<Trophy className="text-[var(--gold)]" size={22} />
<span className="font-bold text-[var(--gold)]">حکم‌شو</span>
</div>
<button
className="lg:hidden text-[#a9c2e4] hover:text-[var(--gold)]"
onClick={() => setOpen(false)}
aria-label="بستن منو"
>
<X size={20} />
</button>
</div>
<nav className="flex-1 p-3 space-y-1 overflow-y-auto">
{NAV.map((n) => {
const active = cur === n.href;
const Icon = n.icon;
return (
<Link
key={n.href}
href={n.href}
className={`relative flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm transition ${
active
? "bg-[var(--panel)] text-[var(--gold)] font-bold"
: "text-[#a9c2e4] hover:bg-[var(--panel-2)]"
}`}
>
{active && (
<span className="absolute right-0 top-1/2 -translate-y-1/2 w-1 h-6 rounded-full bg-[var(--gold)]" />
)}
<Icon size={18} />
{n.label}
</Link>
);
})}
</nav>
<button
onClick={() => {
clearAuth();
router.replace("/login");
}}
className="m-3 flex items-center gap-2 px-3 py-2.5 rounded-xl text-sm text-[#f0a0a0] hover:bg-[var(--panel-2)]"
>
<LogOut size={18} /> خروج
</button>
</aside>
);
return (
<div className="min-h-screen flex">
{/* دسکتاپ: سایدبارِ ثابت کنارِ محتوا */}
<div className="hidden lg:block shrink-0">{aside}</div>
{/* موبایل: منوی کشویی با پس‌زمینه‌ی تیره */}
{open && (
<div
className="fixed inset-0 bg-black/50 z-30 lg:hidden"
onClick={() => setOpen(false)}
aria-hidden
/>
)}
<div
className={`fixed inset-y-0 right-0 z-40 w-56 transition-transform duration-200 lg:hidden ${
open ? "translate-x-0" : "translate-x-full"
}`}
>
{aside}
</div>
<main className="flex-1 min-w-0">
<header className="px-6 py-4 border-b border-[#16305a] bg-[var(--bg)]/60 backdrop-blur sticky top-0 z-10 flex items-center gap-3">
<button
className="lg:hidden text-[#a9c2e4] hover:text-[var(--gold)]"
onClick={() => setOpen(true)}
aria-label="باز کردن منو"
>
<Menu size={22} />
</button>
<div>
<h1 className="text-lg font-bold">{title}</h1>
{subtitle && <p className="text-xs text-[#8ab4e8] mt-0.5">{subtitle}</p>}
</div>
</header>
<div className="p-6">{children}</div>
</main>
</div>
);
}