// Shared types, validators, and helpers for the registration workflow. // Single source of truth — used by EventRegistrationForm, manage page, and API router. // --------------------------------------------------------------------------- // Drink card pricing // --------------------------------------------------------------------------- export const DRINK_CARD_BASE = 5; // €5 for primary registrant export const DRINK_CARD_PER_GUEST = 2; // €2 per additional guest export const MAX_GUESTS = 9; /** Returns drink card value in euros for a given number of extra guests. */ export function calculateDrinkCard(guestCount: number): number { return DRINK_CARD_BASE + guestCount * DRINK_CARD_PER_GUEST; } /** Returns drink card value in cents for payment processing. */ export function calculateDrinkCardCents(guestCount: number): number { return calculateDrinkCard(guestCount) * 100; } // --------------------------------------------------------------------------- // Guest types // --------------------------------------------------------------------------- export interface GuestEntry { firstName: string; lastName: string; email: string; phone: string; } export interface GuestErrors { firstName?: string; lastName?: string; email?: string; phone?: string; } /** * Safely parses the JSON blob stored in the `guests` DB column. * Returns an empty array on any parse failure. */ export function parseGuests(raw: string | null | undefined): GuestEntry[] { if (!raw) return []; try { const parsed = JSON.parse(raw); if (!Array.isArray(parsed)) return []; return parsed.map((g) => ({ firstName: g.firstName ?? "", lastName: g.lastName ?? "", email: g.email ?? "", phone: g.phone ?? "", })); } catch { return []; } } // --------------------------------------------------------------------------- // Module-level validators (pure functions — no hook deps) // --------------------------------------------------------------------------- export function validateTextField( value: string, required: boolean, label: string, minLen = 2, ): string | undefined { if (required && !value.trim()) return `${label} is verplicht`; if (value.trim() && value.length < minLen) return `${label} moet minimaal ${minLen} tekens bevatten`; return undefined; } export function validateEmail(value: string): string | undefined { if (!value.trim()) return "E-mail is verplicht"; if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return "Voer een geldig e-mailadres in"; return undefined; } export function validatePhone(value: string): string | undefined { if (value && !/^[\d\s\-+()]{10,}$/.test(value.replace(/\s/g, ""))) return "Voer een geldig telefoonnummer in"; return undefined; } /** Validates all guests and returns errors array + overall validity flag. */ export function validateGuests(guests: GuestEntry[]): { errors: GuestErrors[]; valid: boolean; } { const errors: GuestErrors[] = guests.map((g) => ({ firstName: !g.firstName.trim() ? "Voornaam is verplicht" : undefined, lastName: !g.lastName.trim() ? "Achternaam is verplicht" : undefined, email: g.email.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(g.email.trim()) ? "Voer een geldig e-mailadres in" : undefined, phone: g.phone.trim() && !/^[\d\s\-+()]{10,}$/.test(g.phone.replace(/\s/g, "")) ? "Voer een geldig telefoonnummer in" : undefined, })); const valid = !errors.some((e) => Object.values(e).some(Boolean)); return { errors, valid }; } // --------------------------------------------------------------------------- // Shared CSS helpers // --------------------------------------------------------------------------- /** Input class string with error-state variant. */ export function inputCls(hasError: boolean): string { return `w-full border-b bg-transparent pb-2 text-lg text-white placeholder:text-white/40 focus:outline-none focus:ring-0 transition-colors ${hasError ? "border-red-400" : "border-white/30 focus:border-white"}`; }