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>
This commit is contained in:
2026-03-15 18:29:06 +03:00
parent 6981376171
commit 84b0bc4d60
14 changed files with 1573 additions and 3 deletions

View File

@@ -0,0 +1,52 @@
import { NextRequest, NextResponse } from "next/server";
import { getMcRegistrations, addMcRegistration, updateMcRegistration, deleteMcRegistration } from "@/lib/db";
export async function GET(request: NextRequest) {
const title = request.nextUrl.searchParams.get("title");
if (!title) {
return NextResponse.json({ error: "title parameter is required" }, { status: 400 });
}
const registrations = getMcRegistrations(title);
return NextResponse.json(registrations);
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { masterClassTitle, name, instagram, telegram } = body;
if (!masterClassTitle || !name || !instagram) {
return NextResponse.json({ error: "masterClassTitle, name, instagram are required" }, { status: 400 });
}
const id = addMcRegistration(masterClassTitle.trim(), name.trim(), instagram.trim(), telegram?.trim() || undefined);
return NextResponse.json({ ok: true, id });
} catch {
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
export async function PUT(request: NextRequest) {
try {
const body = await request.json();
const { id, name, instagram, telegram } = body;
if (!id || !name || !instagram) {
return NextResponse.json({ error: "id, name, instagram are required" }, { status: 400 });
}
updateMcRegistration(id, name.trim(), instagram.trim(), telegram?.trim() || undefined);
return NextResponse.json({ ok: true });
} catch {
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
export async function DELETE(request: NextRequest) {
const idStr = request.nextUrl.searchParams.get("id");
if (!idStr) {
return NextResponse.json({ error: "id parameter is required" }, { status: 400 });
}
const id = parseInt(idStr, 10);
if (isNaN(id)) {
return NextResponse.json({ error: "Invalid id" }, { status: 400 });
}
deleteMcRegistration(id);
return NextResponse.json({ ok: true });
}

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { getSection, setSection, SECTION_KEYS } from "@/lib/db";
import { siteContent } from "@/data/content";
import { revalidatePath } from "next/cache";
type Params = { params: Promise<{ key: string }> };
@@ -10,9 +11,16 @@ export async function GET(_request: NextRequest, { params }: Params) {
return NextResponse.json({ error: "Invalid section key" }, { status: 400 });
}
const data = getSection(key);
let data = getSection(key);
if (!data) {
return NextResponse.json({ error: "Section not found" }, { status: 404 });
// Auto-seed from fallback content if section doesn't exist yet
const fallback = (siteContent as unknown as Record<string, unknown>)[key];
if (fallback) {
setSection(key, fallback);
data = fallback;
} else {
return NextResponse.json({ error: "Section not found" }, { status: 404 });
}
}
return NextResponse.json(data);

View File

@@ -0,0 +1,30 @@
import { NextResponse } from "next/server";
import { addMcRegistration } from "@/lib/db";
export async function POST(request: Request) {
try {
const body = await request.json();
const { masterClassTitle, name, instagram, telegram } = body;
if (!masterClassTitle || typeof masterClassTitle !== "string") {
return NextResponse.json({ error: "masterClassTitle is required" }, { status: 400 });
}
if (!name || typeof name !== "string" || !name.trim()) {
return NextResponse.json({ error: "name is required" }, { status: 400 });
}
if (!instagram || typeof instagram !== "string" || !instagram.trim()) {
return NextResponse.json({ error: "Instagram аккаунт обязателен" }, { status: 400 });
}
const id = addMcRegistration(
masterClassTitle.trim(),
name.trim(),
instagram.trim(),
telegram && typeof telegram === "string" ? telegram.trim() : undefined
);
return NextResponse.json({ ok: true, id });
} catch {
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}