118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
// 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"}`;
|
|
}
|