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,200 @@
import {
type GuestEntry,
type GuestErrors,
inputCls,
MAX_GUESTS,
} from "@/lib/registration";
interface Props {
guests: GuestEntry[];
errors: GuestErrors[];
onChange: (index: number, field: keyof GuestEntry, value: string) => void;
onAdd: () => void;
onRemove: (index: number) => void;
/** Optional suffix rendered after the guest count header (e.g. price warning) */
headerNote?: React.ReactNode;
}
export function GuestList({
guests,
errors,
onChange,
onAdd,
onRemove,
headerNote,
}: Props) {
return (
<div className="border border-teal-400/20 bg-teal-400/5 p-6">
<div className="mb-4 flex items-center justify-between">
<div>
<p className="text-sm text-teal-300/80 uppercase tracking-wider">
Medebezoekers{" "}
<span className="text-white/50 normal-case">
({guests.length}/{MAX_GUESTS})
</span>
</p>
{headerNote}
</div>
{guests.length < MAX_GUESTS && (
<button
type="button"
onClick={onAdd}
className="flex items-center gap-1.5 rounded border border-teal-400/40 bg-teal-400/10 px-3 py-1.5 text-sm text-teal-300 transition-colors hover:bg-teal-400/20"
>
<svg
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 4.5v15m7.5-7.5h-15"
/>
</svg>
{guests.length === 0 ? "Medebezoeker toevoegen" : "Toevoegen"}
</button>
)}
</div>
{guests.length === 0 && (
<p className="text-sm text-white/40">
Kom je met iemand mee? Voeg je medebezoekers toe. Elke extra persoon
kost 2 op de drinkkaart.
</p>
)}
<div className="flex flex-col gap-4">
{guests.map((guest, idx) => (
<div
key={`guest-${
// biome-ignore lint/suspicious/noArrayIndexKey: stable positional index
idx
}`}
className="border border-teal-400/20 p-4"
>
<div className="mb-3 flex items-center justify-between">
<span className="font-medium text-sm text-teal-300">
Medebezoeker {idx + 1}
</span>
<button
type="button"
onClick={() => onRemove(idx)}
className="flex items-center gap-1 text-red-400/70 text-sm transition-colors hover:text-red-300"
>
<svg
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
Verwijderen
</button>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="flex flex-col gap-2">
<label
htmlFor={`guest-${idx}-firstName`}
className="text-white/80"
>
Voornaam <span className="text-red-300">*</span>
</label>
<input
type="text"
id={`guest-${idx}-firstName`}
value={guest.firstName}
onChange={(e) => onChange(idx, "firstName", e.target.value)}
placeholder="Voornaam"
autoComplete="off"
className={inputCls(!!errors[idx]?.firstName)}
/>
{errors[idx]?.firstName && (
<span className="text-red-300 text-sm" role="alert">
{errors[idx].firstName}
</span>
)}
</div>
<div className="flex flex-col gap-2">
<label
htmlFor={`guest-${idx}-lastName`}
className="text-white/80"
>
Achternaam <span className="text-red-300">*</span>
</label>
<input
type="text"
id={`guest-${idx}-lastName`}
value={guest.lastName}
onChange={(e) => onChange(idx, "lastName", e.target.value)}
placeholder="Achternaam"
autoComplete="off"
className={inputCls(!!errors[idx]?.lastName)}
/>
{errors[idx]?.lastName && (
<span className="text-red-300 text-sm" role="alert">
{errors[idx].lastName}
</span>
)}
</div>
<div className="flex flex-col gap-2">
<label htmlFor={`guest-${idx}-email`} className="text-white/80">
E-mail
</label>
<input
type="email"
id={`guest-${idx}-email`}
value={guest.email}
onChange={(e) => onChange(idx, "email", e.target.value)}
placeholder="optioneel@email.be"
autoComplete="off"
inputMode="email"
className={inputCls(!!errors[idx]?.email)}
/>
{errors[idx]?.email && (
<span className="text-red-300 text-sm" role="alert">
{errors[idx].email}
</span>
)}
</div>
<div className="flex flex-col gap-2">
<label htmlFor={`guest-${idx}-phone`} className="text-white/80">
Telefoon
</label>
<input
type="tel"
id={`guest-${idx}-phone`}
value={guest.phone}
onChange={(e) => onChange(idx, "phone", e.target.value)}
placeholder="06-12345678"
autoComplete="off"
inputMode="tel"
className={inputCls(!!errors[idx]?.phone)}
/>
{errors[idx]?.phone && (
<span className="text-red-300 text-sm" role="alert">
{errors[idx].phone}
</span>
)}
</div>
</div>
</div>
))}
</div>
</div>
);
}