initial booking
This commit is contained in:
101
src/app/[locale]/admin/pickup-slots/actions.ts
Normal file
101
src/app/[locale]/admin/pickup-slots/actions.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
'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<typeof slotSchema>;
|
||||
|
||||
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');
|
||||
}
|
||||
Reference in New Issue
Block a user