143 lines
7.0 KiB
TypeScript
143 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import {
|
|
CalendarDays,
|
|
Database,
|
|
FileText,
|
|
Globe,
|
|
LayoutDashboard,
|
|
Mail,
|
|
Megaphone,
|
|
Menu,
|
|
Palette,
|
|
Settings,
|
|
Shield,
|
|
Users,
|
|
X
|
|
} from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { useState } from "react";
|
|
import { signOut } from "next-auth/react";
|
|
import { AnimatedPage } from "@/components/layout/animated-page";
|
|
|
|
const NAV_ITEMS = [
|
|
{ href: "/admin/uebersicht", label: "Dashboard", icon: LayoutDashboard },
|
|
{ href: "/admin/termine", label: "Termine", icon: CalendarDays },
|
|
{ href: "/admin/kalender", label: "Kalender", icon: Users },
|
|
{ href: "/admin/email-templates", label: "E-Mails", icon: Mail },
|
|
{ href: "/admin/branding", label: "Branding", icon: Palette },
|
|
{ href: "/admin/rechtliches", label: "Rechtliches", icon: Shield },
|
|
{ href: "/admin/instant-meeting", label: "Instant Meeting", icon: Megaphone },
|
|
{ href: "/admin/backup", label: "Backup", icon: Database },
|
|
{ href: "/admin/einstellungen", label: "Einstellungen", icon: Settings }
|
|
];
|
|
|
|
export function AdminNav() {
|
|
const pathname = usePathname();
|
|
|
|
return (
|
|
<nav className="flex-1 py-4 px-3 space-y-1">
|
|
{NAV_ITEMS.map((item) => {
|
|
const isActive = pathname === item.href || pathname.startsWith(`${item.href}/`);
|
|
const Icon = item.icon;
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
"flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-bold transition-all",
|
|
isActive
|
|
? "bg-indigo-50 text-indigo-600"
|
|
: "text-slate-500 hover:bg-slate-50 hover:text-slate-900"
|
|
)}
|
|
>
|
|
<Icon className={cn("h-4 w-4", isActive ? "text-indigo-600" : "text-slate-400")} />
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
export function AdminLayoutClientShell({ children }: { children: React.ReactNode }) {
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
const pathname = usePathname();
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 font-sans">
|
|
<aside className="fixed inset-y-0 left-0 z-30 hidden w-60 flex-col border-r border-slate-200 bg-white lg:flex">
|
|
<Link href="/admin/uebersicht" className="h-16 flex items-center gap-2 px-6 border-b border-slate-100">
|
|
<div className="w-7 h-7 rounded-lg flex items-center justify-center" style={{ backgroundColor: "var(--accent)" }}>
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-lg font-black text-slate-900 tracking-tight">admin<span className="text-indigo-600">.</span></h1>
|
|
</Link>
|
|
<AdminNav />
|
|
<div className="p-3 border-t border-slate-100 space-y-2">
|
|
<Link href="/buchen" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-bold text-slate-500 hover:text-slate-900 hover:bg-slate-50 transition-all">
|
|
<Globe className="h-4 w-4" /> Buchung
|
|
</Link>
|
|
<button onClick={() => signOut({ callbackUrl: "/anmelden" })} className="flex w-full items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-bold text-slate-400 hover:text-red-600 hover:bg-red-50 transition-all">
|
|
<FileText className="h-4 w-4" /> Abmelden
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<div className="lg:hidden sticky top-0 z-30 flex items-center gap-3 border-b border-slate-200 bg-white px-4 h-14">
|
|
<button onClick={() => setMobileMenuOpen(!mobileMenuOpen)} className="rounded-lg p-1.5 text-slate-500 hover:bg-slate-100">
|
|
{mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
|
</button>
|
|
<Link href="/admin/uebersicht" className="flex items-center gap-2">
|
|
<div className="w-6 h-6 rounded-md flex items-center justify-center" style={{ backgroundColor: "var(--accent)" }}>
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-sm font-black text-slate-900">admin<span className="text-indigo-600">.</span></h1>
|
|
</Link>
|
|
<span className="text-xs font-bold text-slate-400 ml-auto">
|
|
{NAV_ITEMS.find((item) => pathname === item.href || pathname.startsWith(`${item.href}/`))?.label ?? "Admin"}
|
|
</span>
|
|
</div>
|
|
|
|
{mobileMenuOpen && (
|
|
<div className="lg:hidden fixed inset-0 z-20">
|
|
<div className="absolute inset-0 bg-slate-950/40" onClick={() => setMobileMenuOpen(false)} />
|
|
<div className="absolute left-0 top-0 bottom-0 w-64 bg-white border-r border-slate-200 shadow-2xl animate-in slide-in-from-left duration-200">
|
|
<div className="h-14 flex items-center border-b border-slate-100 px-4">
|
|
<Link href="/admin/uebersicht" className="flex items-center gap-2" onClick={() => setMobileMenuOpen(false)}>
|
|
<div className="w-6 h-6 rounded-md flex items-center justify-center" style={{ backgroundColor: "var(--accent)" }}>
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-sm font-black text-slate-900">admin<span className="text-indigo-600">.</span></h1>
|
|
</Link>
|
|
</div>
|
|
<AdminNav />
|
|
<div className="p-3 border-t border-slate-100 space-y-2">
|
|
<Link href="/buchen" onClick={() => setMobileMenuOpen(false)} className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-bold text-slate-500 hover:text-slate-900 hover:bg-slate-50">
|
|
<Globe className="h-4 w-4" /> Buchung
|
|
</Link>
|
|
<button onClick={() => signOut({ callbackUrl: "/anmelden" })} className="flex w-full items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-bold text-slate-400 hover:text-red-600 hover:bg-red-50">
|
|
<FileText className="h-4 w-4" /> Abmelden
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<main className="min-h-screen lg:pl-60">
|
|
<div className="w-full max-w-6xl mx-auto p-4 lg:p-8">
|
|
<AnimatedPage key={pathname}>{children}</AnimatedPage>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|