159 lines
6.3 KiB
TypeScript
159 lines
6.3 KiB
TypeScript
"use client";
|
||
|
||
import { useState, ReactNode } from "react";
|
||
import Link from "next/link";
|
||
import { usePathname } from "next/navigation";
|
||
import {
|
||
LayoutDashboard,
|
||
Users,
|
||
Building2,
|
||
Shield,
|
||
LogOut,
|
||
Menu,
|
||
X,
|
||
Fingerprint,
|
||
Activity,
|
||
Bell,
|
||
Mail,
|
||
} from "lucide-react";
|
||
import { useLanguage } from "@/components/language-provider";
|
||
import { useAuth } from "@/components/auth/auth-provider";
|
||
import { RouteGuard } from "@/components/auth/route-guard";
|
||
import { Button } from "@/components/ui/button";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
const navItems = [
|
||
{ key: "overview", labelFa: "نمایش کلی", labelEn: "Overview", href: "/admin", icon: LayoutDashboard },
|
||
{ key: "users", labelFa: "کاربران", labelEn: "Users", href: "/admin/users", icon: Users },
|
||
{ key: "members", labelFa: "اعضا", labelEn: "Members", href: "/admin/members", icon: Users },
|
||
{ key: "invitations", labelFa: "دعوتها", labelEn: "Invitations", href: "/admin/invitations", icon: Mail },
|
||
{ key: "orgs", labelFa: "سازمان\u200cها", labelEn: "Organizations", href: "/admin/organizations", icon: Building2 },
|
||
{ key: "products", labelFa: "محصولها", labelEn: "Products", href: "/admin/products", icon: Shield },
|
||
{ key: "apps", labelFa: "اپلیکیشنها", labelEn: "Applications", href: "/admin/applications", icon: Shield },
|
||
{ key: "notifications", labelFa: "اعلانات", labelEn: "Notifications", href: "/admin/notifications", icon: Bell },
|
||
{ key: "sessions", labelFa: "نشست\u200cها", labelEn: "Sessions", href: "/admin/sessions", icon: Activity },
|
||
{ key: "events", labelFa: "رویدادهای امنیتی", labelEn: "Security Events", href: "/admin/events", icon: Activity },
|
||
{ key: "security", labelFa: "امنیت", labelEn: "Security", href: "/admin/security", icon: Fingerprint },
|
||
];
|
||
|
||
export function AdminLayout({ children, className }: { children: ReactNode; className?: string }) {
|
||
const { lang, toggle, dir } = useLanguage();
|
||
const { logout } = useAuth();
|
||
const pathname = usePathname();
|
||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||
|
||
const t = (fa: string, en: string) => (lang === "fa" ? fa : en);
|
||
|
||
return (
|
||
<RouteGuard>
|
||
<div className="flex h-screen overflow-hidden bg-ink text-slate-300">
|
||
{sidebarOpen && (
|
||
<div
|
||
className="fixed inset-0 z-40 bg-black/50 lg:hidden"
|
||
onClick={() => setSidebarOpen(false)}
|
||
/>
|
||
)}
|
||
|
||
<aside
|
||
className={cn(
|
||
"fixed inset-y-0 z-50 flex h-full w-64 flex-col overflow-y-auto bg-ink-soft border-l border-white/10 transition-transform duration-300 lg:translate-x-0",
|
||
sidebarOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0",
|
||
dir === "rtl" ? "lg:border-r" : "lg:border-l",
|
||
)}
|
||
>
|
||
<div className="flex items-center justify-between p-4 border-b border-white/10">
|
||
<div className="flex items-center gap-2.5">
|
||
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-brand-gradient text-sm font-bold text-white">
|
||
<Fingerprint className="h-5 w-5" />
|
||
</span>
|
||
<span className="font-semibold text-white">
|
||
{process.env.NEXT_PUBLIC_SITE_NAME || "MyAccount Hamsoo"}
|
||
</span>
|
||
</div>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="lg:hidden"
|
||
onClick={() => setSidebarOpen(false)}
|
||
>
|
||
<X className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
|
||
<nav className="flex-1 p-4 space-y-1">
|
||
{navItems.map((item) => {
|
||
const Icon = item.icon;
|
||
const isActive = pathname === item.href;
|
||
return (
|
||
<Link
|
||
key={item.key}
|
||
href={item.href}
|
||
className={cn(
|
||
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition",
|
||
isActive
|
||
? "bg-brand-500/15 text-brand-200"
|
||
: "text-slate-400 hover:text-white hover:bg-white/5",
|
||
)}
|
||
>
|
||
<Icon className="h-4 w-4" />
|
||
{t(item.labelFa, item.labelEn)}
|
||
</Link>
|
||
);
|
||
})}
|
||
</nav>
|
||
|
||
<div className="border-t border-white/10 p-4">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<span className="text-xs text-slate-500">{lang === "fa" ? "زبان" : "Language"}</span>
|
||
<button
|
||
onClick={toggle}
|
||
className="px-2 py-1 rounded text-xs bg-white/5 hover:bg-white/10"
|
||
>
|
||
{lang === "fa" ? "EN" : "FA"}
|
||
</button>
|
||
</div>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="w-full"
|
||
onClick={async () => {
|
||
await logout();
|
||
window.location.href = `/login?next=${encodeURIComponent(pathname)}`;
|
||
}}
|
||
>
|
||
<LogOut className="h-4 w-4" />
|
||
{t("خروج", "Logout")}
|
||
</Button>
|
||
</div>
|
||
</aside>
|
||
|
||
<main className={cn("flex-1 overflow-auto", dir === "rtl" ? "lg:mr-72" : "lg:ml-72")}>
|
||
<header className="sticky top-0 z-10 flex items-center justify-between border-b border-white/10 bg-ink/80 px-6 py-4 backdrop-blur">
|
||
<div className="flex items-center gap-4">
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="lg:hidden"
|
||
onClick={() => setSidebarOpen(true)}
|
||
>
|
||
<Menu className="h-5 w-5" />
|
||
</Button>
|
||
<h1 className="text-xl font-bold text-white">
|
||
{navItems.find((i) => pathname === i.href)
|
||
? t(
|
||
navItems.find((i) => pathname === i.href)?.labelFa || "",
|
||
navItems.find((i) => pathname === i.href)?.labelEn || "",
|
||
)
|
||
: t("نمایش کلی", "Overview")}
|
||
</h1>
|
||
</div>
|
||
</header>
|
||
<div className="p-6">
|
||
{children}
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</RouteGuard>
|
||
);
|
||
}
|