feat:simplify dx and improve payment functions

This commit is contained in:
2026-03-03 14:02:29 +01:00
parent b9e5c44588
commit 0a1d1db9ec
10 changed files with 2167 additions and 2224 deletions

View File

@@ -0,0 +1,117 @@
// 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"}`;
}