feat: admin panel with SQLite, auth, and calendar-style schedule editor

Complete admin panel for content management:
- SQLite database with better-sqlite3, seed script from content.ts
- Simple password auth with HMAC-signed cookies (Edge + Node compatible)
- 9 section editors: meta, hero, about, team, classes, schedule, pricing, FAQ, contact
- Team CRUD with image upload and drag reorder
- Schedule editor with Google Calendar-style visual timeline (colored blocks, overlap detection, click-to-add)
- All public components refactored to accept data props from DB (with fallback to static content)
- Middleware protecting /admin/* and /api/admin/* routes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 16:59:12 +03:00
parent d5afaf92ba
commit 27c1348f89
44 changed files with 3709 additions and 69 deletions

View File

@@ -1,16 +1,25 @@
import { Users, Layers, MapPin } from "lucide-react";
import { siteContent } from "@/data/content";
import { SectionHeading } from "@/components/ui/SectionHeading";
import { Reveal } from "@/components/ui/Reveal";
import type { SiteContent } from "@/types/content";
const stats = [
{ icon: <Users size={22} />, value: "16", label: "тренеров" },
{ icon: <Layers size={22} />, value: "6", label: "направлений" },
{ icon: <MapPin size={22} />, value: "2", label: "зала в Минске" },
];
interface AboutStats {
trainers: number;
classes: number;
locations: number;
}
export function About() {
const { about } = siteContent;
interface AboutProps {
data: SiteContent["about"];
stats: AboutStats;
}
export function About({ data: about, stats }: AboutProps) {
const statItems = [
{ icon: <Users size={22} />, value: String(stats.trainers), label: "тренеров" },
{ icon: <Layers size={22} />, value: String(stats.classes), label: "направлений" },
{ icon: <MapPin size={22} />, value: String(stats.locations), label: "зала в Минске" },
];
return (
<section id="about" className="section-glow relative section-padding bg-neutral-100 dark:bg-[#080808]">
@@ -33,7 +42,7 @@ export function About() {
{/* Stats */}
<Reveal>
<div className="mx-auto mt-14 grid max-w-3xl grid-cols-3 gap-4 sm:gap-8">
{stats.map((stat, i) => (
{statItems.map((stat, i) => (
<div
key={i}
className="group flex flex-col items-center gap-3 rounded-2xl border border-neutral-200 bg-white/50 p-6 transition-all duration-300 hover:border-gold/30 sm:p-8 dark:border-white/[0.06] dark:bg-white/[0.02] dark:hover:border-gold/20"