'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { z } from 'zod'; import { prisma } from '@/lib/prisma'; import { requireAdmin } from '@/lib/requireAdmin'; // datetime-local inputs return "YYYY-MM-DDTHH:mm" in the browser's local // timezone. We treat them as local time and convert via Date constructor // (which interprets the string in the server's timezone — for a single-host // deploy that's fine; document if you ever shard across timezones). const slotSchema = z .object({ labelSv: z.string().trim().min(1).max(200), labelEn: z.string().trim().min(1).max(200), startsAt: z.string().trim().min(1), endsAt: z.string().trim().min(1), capacity: z.coerce.number().int().min(0).max(10000), active: z.preprocess( (v) => v === 'on' || v === 'true' || v === true, z.boolean(), ), }) .refine((d) => new Date(d.startsAt) < new Date(d.endsAt), { path: ['endsAt'], message: 'invalidTime', }); type Parsed = z.infer; function toDbFields(d: Parsed) { return { labelSv: d.labelSv, labelEn: d.labelEn, startsAt: new Date(d.startsAt), endsAt: new Date(d.endsAt), capacity: d.capacity, active: d.active, }; } function fromFormData(fd: FormData): Parsed { return slotSchema.parse({ labelSv: fd.get('labelSv') ?? '', labelEn: fd.get('labelEn') ?? '', startsAt: fd.get('startsAt') ?? '', endsAt: fd.get('endsAt') ?? '', capacity: fd.get('capacity') ?? 0, active: fd.get('active') ?? false, }); } export async function createPickupSlot(formData: FormData) { await requireAdmin(); const data = fromFormData(formData); await prisma.pickupSlot.create({ data: toDbFields(data) }); revalidatePath('/admin/pickup-slots'); revalidatePath('/'); redirect('/admin/pickup-slots'); } export async function updatePickupSlot(formData: FormData) { await requireAdmin(); const id = String(formData.get('id') ?? ''); if (!id) return; const data = fromFormData(formData); await prisma.pickupSlot.update({ where: { id }, data: toDbFields(data) }); revalidatePath('/admin/pickup-slots'); revalidatePath(`/admin/pickup-slots/${id}`); revalidatePath('/'); redirect('/admin/pickup-slots'); } export async function togglePickupSlotActive(formData: FormData) { await requireAdmin(); const id = String(formData.get('id') ?? ''); if (!id) return; const cur = await prisma.pickupSlot.findUnique({ where: { id } }); if (!cur) return; await prisma.pickupSlot.update({ where: { id }, data: { active: !cur.active }, }); revalidatePath('/admin/pickup-slots'); revalidatePath('/'); } export async function deletePickupSlot(formData: FormData) { await requireAdmin(); const id = String(formData.get('id') ?? ''); if (!id) return; const inUse = await prisma.booking.count({ where: { pickupSlotId: id } }); if (inUse > 0) { redirect(`/admin/pickup-slots/${id}?error=in-use&count=${inUse}`); } await prisma.pickupSlot.delete({ where: { id } }); revalidatePath('/admin/pickup-slots'); revalidatePath('/'); redirect('/admin/pickup-slots'); }