Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
90 lines
2.7 KiB
TypeScript
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 }
|
|
}
|