Files
blackheart-website/src/app/admin/layout.tsx
diana.dolgolyova 84b0bc4d60 feat: add master classes section with registration system
- New master classes section on landing page with upcoming events grid
- Admin CRUD for master classes (image, slots, trainer, style, cost, location)
- User signup modal (name + Instagram required, Telegram optional)
- Admin registration management: view, add, edit, delete with quick-contact links
- Customizable success message for signup confirmation
- Auto-filter past events, Russian date formatting, duration auto-calculation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:29:06 +03:00

149 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import {
LayoutDashboard,
Sparkles,
Users,
BookOpen,
Star,
Calendar,
DollarSign,
HelpCircle,
Phone,
FileText,
Globe,
LogOut,
Menu,
X,
ChevronLeft,
} from "lucide-react";
const NAV_ITEMS = [
{ href: "/admin", label: "Дашборд", icon: LayoutDashboard },
{ href: "/admin/meta", label: "SEO / Мета", icon: Globe },
{ href: "/admin/hero", label: "Главный экран", icon: Sparkles },
{ href: "/admin/about", label: "О студии", icon: FileText },
{ href: "/admin/team", label: "Команда", icon: Users },
{ href: "/admin/classes", label: "Направления", icon: BookOpen },
{ href: "/admin/master-classes", label: "Мастер-классы", icon: Star },
{ href: "/admin/schedule", label: "Расписание", icon: Calendar },
{ href: "/admin/pricing", label: "Цены", icon: DollarSign },
{ href: "/admin/faq", label: "FAQ", icon: HelpCircle },
{ href: "/admin/contact", label: "Контакты", icon: Phone },
];
export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname();
const router = useRouter();
const [sidebarOpen, setSidebarOpen] = useState(false);
// Don't render admin shell on login page
if (pathname === "/admin/login") {
return <>{children}</>;
}
async function handleLogout() {
await fetch("/api/logout", { method: "POST" });
router.push("/admin/login");
}
function isActive(href: string) {
if (href === "/admin") return pathname === "/admin";
return pathname.startsWith(href);
}
return (
<div className="flex min-h-screen bg-neutral-950 text-white">
{/* Mobile overlay */}
{sidebarOpen && (
<div
className="fixed inset-0 z-40 bg-black/60 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
{/* Sidebar */}
<aside
className={`fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-white/10 bg-neutral-900 transition-transform lg:sticky lg:top-0 lg:h-screen lg:translate-x-0 ${
sidebarOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
<Link href="/admin" className="text-lg font-bold">
BLACK HEART
</Link>
<button
onClick={() => setSidebarOpen(false)}
className="lg:hidden text-neutral-400 hover:text-white"
>
<X size={20} />
</button>
</div>
<nav className="flex-1 overflow-y-auto p-3 space-y-1">
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
const active = isActive(item.href);
return (
<Link
key={item.href}
href={item.href}
onClick={() => setSidebarOpen(false)}
className={`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-colors ${
active
? "bg-gold/10 text-gold font-medium"
: "text-neutral-400 hover:text-white hover:bg-white/5"
}`}
>
<Icon size={18} />
{item.label}
</Link>
);
})}
</nav>
<div className="border-t border-white/10 p-3 space-y-1">
<Link
href="/"
target="_blank"
className="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm text-neutral-400 hover:text-white hover:bg-white/5 transition-colors"
>
<ChevronLeft size={18} />
Открыть сайт
</Link>
<button
onClick={handleLogout}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm text-neutral-400 hover:text-red-400 hover:bg-white/5 transition-colors"
>
<LogOut size={18} />
Выйти
</button>
</div>
</aside>
{/* Main content */}
<div className="flex-1 flex flex-col min-w-0">
{/* Top bar (mobile) */}
<header className="flex items-center gap-3 border-b border-white/10 px-4 py-3 lg:hidden">
<button
onClick={() => setSidebarOpen(true)}
className="text-neutral-400 hover:text-white"
>
<Menu size={24} />
</button>
<span className="font-bold">BLACK HEART</span>
</header>
<main className="flex-1 p-4 sm:p-6 lg:p-8">{children}</main>
</div>
</div>
);
}