admin/validation/consumerEventWizardStep2.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

90 lines
2.7 KiB
TypeScript

import { z } from 'zod'
import { texts } from '@/texts'
const v = texts.validation.eventWizard
function toDateTime(dateIso: string, minutes: number): Date | null {
const date = new Date(dateIso)
if (!Number.isFinite(date.getTime()) || !Number.isFinite(minutes)) return null
date.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0)
return date
}
export interface ConsumerEventWizardStep2Values {
startDate: string
startTime: number
endDate: string
endTime: number
address: string
addressVisibility: 'public' | 'attendees_only'
generalArea?: string
}
export type ConsumerEventWizardStep2FieldErrors = Partial<Record<keyof ConsumerEventWizardStep2Values, string>>
export const ConsumerEventWizardStep2Validation = z
.object({
startDate: z.string().min(1, v.startDateRequired),
startTime: z.number({ error: v.startTimeRequired }),
endDate: z.string().min(1, v.endDateRequired),
endTime: z.number({ error: v.endTimeRequired }),
address: z.string().trim().min(1, v.addressRequired),
addressVisibility: z.enum(['public', 'attendees_only']),
generalArea: z.string().trim().optional().or(z.literal('')),
})
.superRefine((values, ctx) => {
const startsAt = toDateTime(values.startDate, values.startTime)
const endsAt = toDateTime(values.endDate, values.endTime)
if (!startsAt || !endsAt) {
ctx.addIssue({ code: 'custom', message: v.startEndRequired, path: ['startDate'] })
return
}
if (endsAt <= startsAt) {
ctx.addIssue({ code: 'custom', message: v.endAfterStart, path: ['endDate'] })
}
if (values.addressVisibility === 'attendees_only' && !values.generalArea?.trim()) {
ctx.addIssue({ code: 'custom', message: v.generalAreaRequired, path: ['generalArea'] })
}
})
export function parseConsumerEventWizardStep2(
payload: ConsumerEventWizardStep2Values
): { success: true; data: ConsumerEventWizardStep2Values } | { success: false; fieldErrors: ConsumerEventWizardStep2FieldErrors } {
const result = ConsumerEventWizardStep2Validation.safeParse(payload)
if (result.success) {
return {
success: true,
data: {
startDate: result.data.startDate,
startTime: result.data.startTime,
endDate: result.data.endDate,
endTime: result.data.endTime,
address: result.data.address,
addressVisibility: result.data.addressVisibility,
generalArea: result.data.generalArea,
},
}
}
const fieldErrors: ConsumerEventWizardStep2FieldErrors = {}
for (const issue of result.error.issues) {
const key = issue.path[0]
if (typeof key === 'string' && !(key in fieldErrors)) {
fieldErrors[key as keyof ConsumerEventWizardStep2Values] = issue.message
}
}
return { success: false, fieldErrors }
}