import { NextResponse } from "next/server"; import { getPayload } from "payload"; import config from "@payload-config"; import { getAvailableSlots } from "@/lib/booking/slots"; export const dynamic = "force-dynamic"; type Body = { offerSlug?: unknown; start?: unknown; appointmentType?: unknown; message?: unknown; }; export async function POST(request: Request) { let body: Body; try { body = await request.json(); } catch { return NextResponse.json({ error: "Ungültige Anfrage." }, { status: 400 }); } const offerSlug = typeof body.offerSlug === "string" ? body.offerSlug : ""; const startISO = typeof body.start === "string" ? body.start : ""; const appointmentType = body.appointmentType === "online" ? "online" : "onsite"; const message = typeof body.message === "string" ? body.message.trim().slice(0, 2000) : ""; if (!offerSlug || !startISO) { return NextResponse.json({ error: "Angebot und Termin sind erforderlich." }, { status: 400 }); } try { const payload = await getPayload({ config }); const { user } = await payload.auth({ headers: request.headers }); if (!user || user.collection !== "customers") { return NextResponse.json({ error: "Bitte melde dich an, um einen Termin anzufragen." }, { status: 401 }); } const { docs } = await payload.find({ collection: "offers", where: { slug: { equals: offerSlug } }, limit: 1, overrideAccess: false, }); const offer = docs[0]; if (!offer || !offer.bookable || !offer.durationMinutes) { return NextResponse.json({ error: "Dieses Angebot ist nicht buchbar." }, { status: 404 }); } const start = new Date(startISO); if (Number.isNaN(start.getTime()) || start < new Date()) { return NextResponse.json({ error: "Bitte wähle einen gültigen, zukünftigen Termin." }, { status: 400 }); } const end = new Date(start.getTime() + offer.durationMinutes * 60_000); // Re-validate the slot is actually free right now (defense against a // stale slot list) — the hard, race-safe check still runs again when // Anna confirms (see collections/BookingRequests.ts). const dayStart = new Date(start); dayStart.setHours(0, 0, 0, 0); const dayEnd = new Date(start); dayEnd.setHours(23, 59, 59, 999); const slotsByDay = await getAvailableSlots(payload, { durationMinutes: offer.durationMinutes, from: dayStart, to: dayEnd, }); const key = dayStart.toISOString().slice(0, 10); const isStillFree = (slotsByDay.get(key) ?? []).some((s) => s.start.getTime() === start.getTime()); if (!isStillFree) { return NextResponse.json({ error: "Dieser Termin ist leider nicht mehr verfügbar." }, { status: 409 }); } await payload.create({ collection: "booking-requests", data: { user: user.id, offer: offer.id, date: start.toISOString(), startTime: start.toISOString(), endTime: end.toISOString(), appointmentType, status: "pending", userMessage: message || undefined, }, }); return NextResponse.json({ ok: true }); } catch (error) { console.error("booking request failed", error); return NextResponse.json({ error: "Die Anfrage konnte nicht gesendet werden. Bitte versuche es später erneut." }, { status: 500 }); } }