- Add will-change to .hero-glow-orb (filter, transform) and .team-card-glitter::before (background-position) - Clear CSRF cookie on logout alongside auth cookie - Add max array length (100) validation on team reorder endpoint - Remove redundant isOpenDayClassBookedByPhone pre-check (DB UNIQUE constraint handles it) - Extract Schedule grid layout calculation into useMemo - Reduce HeroLogo sparkle animations on mobile (15 → 8 via hidden sm:block) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
61 lines
2.1 KiB
TypeScript
61 lines
2.1 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import {
|
|
addOpenDayBooking,
|
|
getPersonOpenDayBookings,
|
|
getOpenDayEvent,
|
|
} from "@/lib/db";
|
|
import { checkRateLimit, getClientIp } from "@/lib/rateLimit";
|
|
import { sanitizeName, sanitizePhone, sanitizeHandle } from "@/lib/validation";
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const ip = getClientIp(request);
|
|
if (!checkRateLimit(ip, 10, 60_000)) {
|
|
return NextResponse.json(
|
|
{ error: "Слишком много запросов. Попробуйте через минуту." },
|
|
{ status: 429 }
|
|
);
|
|
}
|
|
|
|
try {
|
|
const body = await request.json();
|
|
const { classId, eventId, name, phone, instagram, telegram } = body;
|
|
|
|
if (!classId || !eventId) {
|
|
return NextResponse.json({ error: "classId and eventId are required" }, { status: 400 });
|
|
}
|
|
|
|
const cleanName = sanitizeName(name);
|
|
if (!cleanName) {
|
|
return NextResponse.json({ error: "Имя обязательно" }, { status: 400 });
|
|
}
|
|
|
|
const cleanPhone = sanitizePhone(phone);
|
|
if (!cleanPhone) {
|
|
return NextResponse.json({ error: "Телефон обязателен" }, { status: 400 });
|
|
}
|
|
|
|
const id = addOpenDayBooking(classId, eventId, {
|
|
name: cleanName,
|
|
phone: cleanPhone,
|
|
instagram: sanitizeHandle(instagram),
|
|
telegram: sanitizeHandle(telegram),
|
|
});
|
|
|
|
// Return total bookings for this person (for discount calculation)
|
|
const totalBookings = getPersonOpenDayBookings(eventId, cleanPhone);
|
|
const event = getOpenDayEvent(eventId);
|
|
const pricePerClass = event && totalBookings >= event.discountThreshold
|
|
? event.discountPrice
|
|
: event?.pricePerClass ?? 30;
|
|
|
|
return NextResponse.json({ ok: true, id, totalBookings, pricePerClass });
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : "Internal error";
|
|
if (msg.includes("UNIQUE")) {
|
|
return NextResponse.json({ error: "Вы уже записаны на это занятие" }, { status: 409 });
|
|
}
|
|
console.error("[open-day-register] POST error:", e);
|
|
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
}
|
|
}
|