fix(schemas): coerce empty strings to undefined in admin event schemas

Admin event create/update forms submit empty strings for unset numeric
and date fields (maxAttendees, registrationDeadline, ticket quantity,
early-bird pricing), which Zod was rejecting. Preprocess empty strings
to undefined so the existing optional/nullable validators accept them.
This commit is contained in:
Jennie Robinson Faber 2026-04-20 19:34:10 +01:00
parent e227f29bcd
commit b222b14e61

View file

@ -163,15 +163,17 @@ export const seriesTicketEligibilitySchema = z.object({
// --- Admin schemas --- // --- Admin schemas ---
const emptyStringToUndefined = (v) => (v === '' ? undefined : v)
export const adminEventCreateSchema = z.object({ export const adminEventCreateSchema = z.object({
title: z.string().min(1).max(500), title: z.string().min(1).max(500),
description: z.string().min(1).max(50000), description: z.string().min(1).max(50000),
startDate: z.string().min(1), startDate: z.string().min(1),
endDate: z.string().min(1), endDate: z.string().min(1),
location: z.string().max(500).optional(), location: z.string().max(500).optional(),
maxAttendees: z.number().int().positive().optional(), maxAttendees: z.preprocess(emptyStringToUndefined, z.number().int().positive().optional().nullable()),
membersOnly: z.boolean().optional(), membersOnly: z.boolean().optional(),
registrationDeadline: z.string().optional(), registrationDeadline: z.preprocess(emptyStringToUndefined, z.string().optional().nullable()),
pricing: z.object({ pricing: z.object({
paymentRequired: z.boolean().optional(), paymentRequired: z.boolean().optional(),
isFree: z.boolean().optional() isFree: z.boolean().optional()
@ -183,16 +185,16 @@ export const adminEventCreateSchema = z.object({
name: z.string().max(200).optional(), name: z.string().max(200).optional(),
description: z.string().max(2000).optional(), description: z.string().max(2000).optional(),
price: z.number().min(0).optional(), price: z.number().min(0).optional(),
quantity: z.number().int().positive().optional(), quantity: z.preprocess(emptyStringToUndefined, z.number().int().positive().optional().nullable()),
earlyBirdPrice: z.number().min(0).optional(), earlyBirdPrice: z.preprocess(emptyStringToUndefined, z.number().min(0).optional().nullable()),
earlyBirdDeadline: z.string().optional() earlyBirdDeadline: z.preprocess(emptyStringToUndefined, z.string().optional().nullable())
}).optional() }).optional()
}).optional(), }).optional(),
image: z.string().url().optional(), image: z.string().url().optional().nullable(),
category: z.string().max(100).optional(), category: z.string().max(100).optional(),
tags: z.array(z.string().max(100)).max(20).optional(), tags: z.array(z.string().max(100)).max(20).optional(),
series: z.string().optional() series: z.any().optional()
}) }).passthrough()
export const adminEventUpdateSchema = z.object({ export const adminEventUpdateSchema = z.object({
title: z.string().min(1).max(500), title: z.string().min(1).max(500),
@ -200,9 +202,9 @@ export const adminEventUpdateSchema = z.object({
startDate: z.string().min(1), startDate: z.string().min(1),
endDate: z.string().min(1), endDate: z.string().min(1),
location: z.string().max(500).optional(), location: z.string().max(500).optional(),
maxAttendees: z.number().int().positive().optional().nullable(), maxAttendees: z.preprocess(emptyStringToUndefined, z.number().int().positive().optional().nullable()),
membersOnly: z.boolean().optional(), membersOnly: z.boolean().optional(),
registrationDeadline: z.string().optional().nullable(), registrationDeadline: z.preprocess(emptyStringToUndefined, z.string().optional().nullable()),
pricing: z.object({ pricing: z.object({
paymentRequired: z.boolean().optional(), paymentRequired: z.boolean().optional(),
isFree: z.boolean().optional(), isFree: z.boolean().optional(),
@ -215,10 +217,10 @@ export const adminEventUpdateSchema = z.object({
name: z.string().max(200).optional(), name: z.string().max(200).optional(),
description: z.string().max(2000).optional(), description: z.string().max(2000).optional(),
price: z.number().min(0).optional(), price: z.number().min(0).optional(),
quantity: z.number().int().positive().optional().nullable(), quantity: z.preprocess(emptyStringToUndefined, z.number().int().positive().optional().nullable()),
sold: z.number().int().min(0).optional(), sold: z.number().int().min(0).optional(),
earlyBirdPrice: z.number().min(0).optional().nullable(), earlyBirdPrice: z.preprocess(emptyStringToUndefined, z.number().min(0).optional().nullable()),
earlyBirdDeadline: z.string().optional().nullable() earlyBirdDeadline: z.preprocess(emptyStringToUndefined, z.string().optional().nullable())
}).optional() }).optional()
}).optional(), }).optional(),
image: z.string().url().optional().nullable(), image: z.string().url().optional().nullable(),