682 lines
21 KiB
TypeScript
682 lines
21 KiB
TypeScript
import { useMutation } from "@tanstack/react-query";
|
|
import { useEffect, useState } from "react";
|
|
import { toast } from "sonner";
|
|
import { authClient } from "@/lib/auth-client";
|
|
import {
|
|
inputCls,
|
|
validateBirthdate,
|
|
validateEmail,
|
|
validatePhone,
|
|
validatePostcode,
|
|
validateTextField,
|
|
} from "@/lib/registration";
|
|
import { orpc } from "@/utils/orpc";
|
|
import { GiftSelector } from "./GiftSelector";
|
|
|
|
interface PerformerErrors {
|
|
firstName?: string;
|
|
lastName?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
birthdate?: string;
|
|
postcode?: string;
|
|
artForm?: string;
|
|
isOver16?: string;
|
|
}
|
|
|
|
interface Props {
|
|
onBack: () => void;
|
|
onSuccess: (token: string, email: string, name: string) => void;
|
|
}
|
|
|
|
export function PerformerForm({ onBack, onSuccess }: Props) {
|
|
const { data: session } = authClient.useSession();
|
|
const sessionEmail = session?.user?.email ?? "";
|
|
|
|
const [data, setData] = useState({
|
|
firstName: "",
|
|
lastName: "",
|
|
email: "",
|
|
phone: "",
|
|
birthdateDay: "",
|
|
birthdateMonth: "",
|
|
birthdateYear: "",
|
|
postcode: "",
|
|
artForm: "",
|
|
experience: "",
|
|
isOver16: false,
|
|
extraQuestions: "",
|
|
});
|
|
const [giftAmount, setGiftAmount] = useState(0);
|
|
const [errors, setErrors] = useState<PerformerErrors>({});
|
|
const [touched, setTouched] = useState<Record<string, boolean>>({});
|
|
|
|
useEffect(() => {
|
|
if (sessionEmail) {
|
|
setData((prev) => ({ ...prev, email: sessionEmail }));
|
|
}
|
|
}, [sessionEmail]);
|
|
|
|
const submitMutation = useMutation({
|
|
...orpc.submitRegistration.mutationOptions(),
|
|
onSuccess: (result) => {
|
|
if (result.managementToken)
|
|
onSuccess(
|
|
result.managementToken,
|
|
data.email.trim(),
|
|
`${data.firstName.trim()} ${data.lastName.trim()}`.trim(),
|
|
);
|
|
},
|
|
onError: (error) => {
|
|
toast.error(`Er is iets misgegaan: ${error.message}`);
|
|
},
|
|
});
|
|
|
|
function getBirthdate(): string {
|
|
const { birthdateYear, birthdateMonth, birthdateDay } = data;
|
|
if (!birthdateYear || !birthdateMonth || !birthdateDay) return "";
|
|
return `${birthdateYear}-${birthdateMonth.padStart(2, "0")}-${birthdateDay.padStart(2, "0")}`;
|
|
}
|
|
|
|
function validate(): boolean {
|
|
const birthdate = getBirthdate();
|
|
const errs: PerformerErrors = {
|
|
firstName: validateTextField(data.firstName, true, "Voornaam"),
|
|
lastName: validateTextField(data.lastName, true, "Achternaam"),
|
|
email: validateEmail(data.email),
|
|
phone: validatePhone(data.phone),
|
|
birthdate: validateBirthdate(birthdate),
|
|
postcode: validatePostcode(data.postcode),
|
|
artForm: !data.artForm.trim() ? "Kunstvorm is verplicht" : undefined,
|
|
isOver16: !data.isOver16
|
|
? "Je moet 16 jaar of ouder zijn om op te treden"
|
|
: undefined,
|
|
};
|
|
setErrors(errs);
|
|
setTouched({
|
|
firstName: true,
|
|
lastName: true,
|
|
email: true,
|
|
phone: true,
|
|
birthdate: true,
|
|
postcode: true,
|
|
artForm: true,
|
|
isOver16: true,
|
|
});
|
|
return !Object.values(errs).some(Boolean);
|
|
}
|
|
|
|
function handleChange(
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
) {
|
|
const { name, value, type } = e.target;
|
|
const newValue =
|
|
type === "checkbox" ? (e.target as HTMLInputElement).checked : value;
|
|
setData((prev) => ({ ...prev, [name]: newValue }));
|
|
|
|
if (type !== "checkbox" && touched[name]) {
|
|
const fieldError: Record<string, string | undefined> = {
|
|
firstName: validateTextField(
|
|
name === "firstName" ? value : data.firstName,
|
|
true,
|
|
"Voornaam",
|
|
),
|
|
lastName: validateTextField(
|
|
name === "lastName" ? value : data.lastName,
|
|
true,
|
|
"Achternaam",
|
|
),
|
|
email: validateEmail(name === "email" ? value : data.email),
|
|
phone: validatePhone(name === "phone" ? value : data.phone),
|
|
artForm:
|
|
name === "artForm" && !value.trim()
|
|
? "Kunstvorm is verplicht"
|
|
: undefined,
|
|
};
|
|
setErrors((prev) => ({ ...prev, [name]: fieldError[name] }));
|
|
}
|
|
}
|
|
|
|
function handleBlur(
|
|
e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
) {
|
|
const { name, value } = e.target;
|
|
setTouched((prev) => ({ ...prev, [name]: true }));
|
|
const errMap: Record<string, string | undefined> = {
|
|
firstName: validateTextField(value, true, "Voornaam"),
|
|
lastName: validateTextField(value, true, "Achternaam"),
|
|
email: validateEmail(value),
|
|
phone: validatePhone(value),
|
|
artForm: !value.trim() ? "Kunstvorm is verplicht" : undefined,
|
|
};
|
|
setErrors((prev) => ({ ...prev, [name]: errMap[name] }));
|
|
}
|
|
|
|
function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
if (!validate()) {
|
|
toast.error("Controleer je invoer");
|
|
return;
|
|
}
|
|
submitMutation.mutate({
|
|
firstName: data.firstName.trim(),
|
|
lastName: data.lastName.trim(),
|
|
email: data.email.trim(),
|
|
phone: data.phone.trim() || undefined,
|
|
birthdate: getBirthdate(),
|
|
postcode: data.postcode.trim(),
|
|
registrationType: "performer",
|
|
artForm: data.artForm.trim() || undefined,
|
|
experience: data.experience.trim() || undefined,
|
|
isOver16: data.isOver16,
|
|
extraQuestions: data.extraQuestions.trim() || undefined,
|
|
giftAmount,
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
{/* Back + type header */}
|
|
<div className="mb-8 flex items-center gap-4">
|
|
<button
|
|
type="button"
|
|
onClick={onBack}
|
|
className="flex items-center gap-2 text-white/60 transition-colors hover:text-white"
|
|
>
|
|
<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="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
|
|
/>
|
|
</svg>
|
|
Terug
|
|
</button>
|
|
<div className="h-px flex-1 bg-white/10" />
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-amber-400/20 text-amber-300">
|
|
<svg
|
|
className="h-4 w-4"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
strokeWidth={1.5}
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<span className="font-['Intro',sans-serif] text-amber-300">
|
|
Ik wil optreden
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="flex flex-col gap-6" noValidate>
|
|
{/* Name row */}
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
<div className="flex flex-col gap-2">
|
|
<label htmlFor="p-firstName" className="text-white text-xl">
|
|
Voornaam <span className="text-red-300">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="p-firstName"
|
|
name="firstName"
|
|
value={data.firstName}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
placeholder="Jouw voornaam"
|
|
autoComplete="given-name"
|
|
aria-required="true"
|
|
aria-invalid={touched.firstName && !!errors.firstName}
|
|
className={inputCls(!!touched.firstName && !!errors.firstName)}
|
|
/>
|
|
{touched.firstName && errors.firstName && (
|
|
<span className="text-red-300 text-sm" role="alert">
|
|
{errors.firstName}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<label htmlFor="p-lastName" className="text-white text-xl">
|
|
Achternaam <span className="text-red-300">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="p-lastName"
|
|
name="lastName"
|
|
value={data.lastName}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
placeholder="Jouw achternaam"
|
|
autoComplete="family-name"
|
|
aria-required="true"
|
|
aria-invalid={touched.lastName && !!errors.lastName}
|
|
className={inputCls(!!touched.lastName && !!errors.lastName)}
|
|
/>
|
|
{touched.lastName && errors.lastName && (
|
|
<span className="text-red-300 text-sm" role="alert">
|
|
{errors.lastName}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Contact row */}
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
<div className="flex flex-col gap-2">
|
|
<label htmlFor="p-email" className="text-white text-xl">
|
|
E-mail <span className="text-red-300">*</span>
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id="p-email"
|
|
name="email"
|
|
value={data.email}
|
|
onChange={sessionEmail ? undefined : handleChange}
|
|
onBlur={sessionEmail ? undefined : handleBlur}
|
|
readOnly={!!sessionEmail}
|
|
placeholder="jouw@email.be"
|
|
autoComplete="email"
|
|
inputMode="email"
|
|
aria-required="true"
|
|
aria-invalid={touched.email && !!errors.email}
|
|
className={`${inputCls(!!touched.email && !!errors.email)}${sessionEmail ? "cursor-not-allowed opacity-75" : ""}`}
|
|
/>
|
|
{touched.email && errors.email && (
|
|
<span className="text-red-300 text-sm" role="alert">
|
|
{errors.email}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<label htmlFor="p-phone" className="text-white text-xl">
|
|
Telefoon
|
|
</label>
|
|
<input
|
|
type="tel"
|
|
id="p-phone"
|
|
name="phone"
|
|
value={data.phone}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
placeholder="06-12345678"
|
|
autoComplete="tel"
|
|
inputMode="tel"
|
|
aria-invalid={touched.phone && !!errors.phone}
|
|
className={inputCls(!!touched.phone && !!errors.phone)}
|
|
/>
|
|
{touched.phone && errors.phone && (
|
|
<span className="text-red-300 text-sm" role="alert">
|
|
{errors.phone}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Birthdate + Postcode row */}
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
<div className="flex flex-col gap-2">
|
|
<p className="text-white text-xl">
|
|
Geboortedatum <span className="text-red-300">*</span>
|
|
</p>
|
|
<fieldset className="m-0 flex gap-2 border-0 p-0">
|
|
<legend className="sr-only">Geboortedatum</legend>
|
|
<div className="flex flex-1 flex-col gap-1">
|
|
<label htmlFor="p-birthdateDay" className="sr-only">
|
|
Dag
|
|
</label>
|
|
<select
|
|
id="p-birthdateDay"
|
|
name="birthdateDay"
|
|
value={data.birthdateDay}
|
|
onChange={(e) =>
|
|
setData((prev) => ({
|
|
...prev,
|
|
birthdateDay: e.target.value,
|
|
}))
|
|
}
|
|
aria-label="Dag"
|
|
className={`border-b bg-transparent pb-2 text-lg text-white transition-colors focus:outline-none ${touched.birthdate && errors.birthdate ? "border-red-400" : "border-white/30 focus:border-white"}`}
|
|
>
|
|
<option value="" className="bg-[#214e51]">
|
|
DD
|
|
</option>
|
|
{Array.from({ length: 31 }, (_, i) => i + 1).map((d) => (
|
|
<option key={d} value={String(d)} className="bg-[#214e51]">
|
|
{String(d).padStart(2, "0")}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="flex flex-1 flex-col gap-1">
|
|
<label htmlFor="p-birthdateMonth" className="sr-only">
|
|
Maand
|
|
</label>
|
|
<select
|
|
id="p-birthdateMonth"
|
|
name="birthdateMonth"
|
|
value={data.birthdateMonth}
|
|
onChange={(e) =>
|
|
setData((prev) => ({
|
|
...prev,
|
|
birthdateMonth: e.target.value,
|
|
}))
|
|
}
|
|
aria-label="Maand"
|
|
className={`border-b bg-transparent pb-2 text-lg text-white transition-colors focus:outline-none ${touched.birthdate && errors.birthdate ? "border-red-400" : "border-white/30 focus:border-white"}`}
|
|
>
|
|
<option value="" className="bg-[#214e51]">
|
|
MM
|
|
</option>
|
|
{[
|
|
"Jan",
|
|
"Feb",
|
|
"Mrt",
|
|
"Apr",
|
|
"Mei",
|
|
"Jun",
|
|
"Jul",
|
|
"Aug",
|
|
"Sep",
|
|
"Okt",
|
|
"Nov",
|
|
"Dec",
|
|
].map((m, i) => (
|
|
<option
|
|
key={m}
|
|
value={String(i + 1)}
|
|
className="bg-[#214e51]"
|
|
>
|
|
{m}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="flex flex-1 flex-col gap-1">
|
|
<label htmlFor="p-birthdateYear" className="sr-only">
|
|
Jaar
|
|
</label>
|
|
<select
|
|
id="p-birthdateYear"
|
|
name="birthdateYear"
|
|
value={data.birthdateYear}
|
|
onChange={(e) =>
|
|
setData((prev) => ({
|
|
...prev,
|
|
birthdateYear: e.target.value,
|
|
}))
|
|
}
|
|
aria-label="Jaar"
|
|
className={`border-b bg-transparent pb-2 text-lg text-white transition-colors focus:outline-none ${touched.birthdate && errors.birthdate ? "border-red-400" : "border-white/30 focus:border-white"}`}
|
|
>
|
|
<option value="" className="bg-[#214e51]">
|
|
JJJJ
|
|
</option>
|
|
{Array.from(
|
|
{ length: 100 },
|
|
(_, i) => new Date().getFullYear() - i,
|
|
).map((y) => (
|
|
<option key={y} value={String(y)} className="bg-[#214e51]">
|
|
{y}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</fieldset>
|
|
{touched.birthdate && errors.birthdate && (
|
|
<span className="text-red-300 text-sm" role="alert">
|
|
{errors.birthdate}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<label htmlFor="p-postcode" className="text-white text-xl">
|
|
Postcode <span className="text-red-300">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="p-postcode"
|
|
name="postcode"
|
|
value={data.postcode}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
placeholder="bv. 9000"
|
|
autoComplete="postal-code"
|
|
inputMode="numeric"
|
|
aria-required="true"
|
|
aria-invalid={touched.postcode && !!errors.postcode}
|
|
className={inputCls(!!touched.postcode && !!errors.postcode)}
|
|
/>
|
|
{touched.postcode && errors.postcode && (
|
|
<span className="text-red-300 text-sm" role="alert">
|
|
{errors.postcode}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Performer-specific fields */}
|
|
<div className="border border-amber-400/20 bg-amber-400/5 p-6">
|
|
<p className="mb-5 text-amber-300/80 text-sm uppercase tracking-wider">
|
|
Optreden details
|
|
</p>
|
|
<div className="flex flex-col gap-6">
|
|
<div className="flex flex-col gap-2">
|
|
<label htmlFor="p-artForm" className="text-white text-xl">
|
|
Kunstvorm <span className="text-red-300">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="p-artForm"
|
|
name="artForm"
|
|
value={data.artForm}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
placeholder="Muziek, Theater, Dans, etc."
|
|
autoComplete="off"
|
|
list="artFormSuggestions"
|
|
aria-required="true"
|
|
aria-invalid={touched.artForm && !!errors.artForm}
|
|
className={inputCls(!!touched.artForm && !!errors.artForm)}
|
|
/>
|
|
<datalist id="artFormSuggestions">
|
|
<option value="Muziek" />
|
|
<option value="Theater" />
|
|
<option value="Dans" />
|
|
<option value="Beeldende Kunst" />
|
|
<option value="Woordkunst" />
|
|
<option value="Comedy" />
|
|
</datalist>
|
|
{touched.artForm && errors.artForm && (
|
|
<span className="text-red-300 text-sm" role="alert">
|
|
{errors.artForm}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<label htmlFor="p-experience" className="text-white text-xl">
|
|
Ervaring
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="p-experience"
|
|
name="experience"
|
|
value={data.experience}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
placeholder="Beginner / Gevorderd / Professional"
|
|
autoComplete="off"
|
|
list="experienceSuggestions"
|
|
className={inputCls(false)}
|
|
/>
|
|
<datalist id="experienceSuggestions">
|
|
<option value="Beginner" />
|
|
<option value="Gevorderd" />
|
|
<option value="Professional" />
|
|
</datalist>
|
|
</div>
|
|
|
|
{/* Age confirmation */}
|
|
<div>
|
|
<label
|
|
htmlFor="p-isOver16"
|
|
className="flex cursor-pointer items-start gap-4"
|
|
>
|
|
<div className="relative mt-0.5 flex shrink-0">
|
|
<input
|
|
type="checkbox"
|
|
id="p-isOver16"
|
|
name="isOver16"
|
|
checked={data.isOver16}
|
|
onChange={handleChange}
|
|
className="peer sr-only"
|
|
/>
|
|
<div
|
|
className={`h-6 w-6 border-2 bg-transparent transition-colors peer-checked:bg-amber-400 peer-focus:ring-2 peer-focus:ring-amber-400/50 peer-focus:ring-offset-2 peer-focus:ring-offset-[#214e51] ${touched.isOver16 && errors.isOver16 ? "border-red-400" : "border-white/50 peer-checked:border-amber-400"}`}
|
|
/>
|
|
<svg
|
|
className="pointer-events-none absolute top-0 left-0 h-6 w-6 scale-0 text-[#214e51] transition-transform peer-checked:scale-100"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="3"
|
|
aria-hidden="true"
|
|
>
|
|
<polyline points="20 6 9 17 4 12" />
|
|
</svg>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-lg text-white">
|
|
Ik ben 16 jaar of ouder{" "}
|
|
<span className="text-red-300">*</span>
|
|
</span>
|
|
<span className="text-sm text-white/60">
|
|
Je moet minimaal 16 jaar oud zijn om op te treden.
|
|
</span>
|
|
</div>
|
|
</label>
|
|
{touched.isOver16 && errors.isOver16 && (
|
|
<span className="mt-2 block text-red-300 text-sm" role="alert">
|
|
{errors.isOver16}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Extra questions */}
|
|
<div className="flex flex-col gap-2 border border-white/10 p-6">
|
|
<label htmlFor="p-extraQuestions" className="text-white text-xl">
|
|
Vragen of opmerkingen
|
|
</label>
|
|
<textarea
|
|
id="p-extraQuestions"
|
|
name="extraQuestions"
|
|
value={data.extraQuestions}
|
|
onChange={handleChange}
|
|
onBlur={handleBlur}
|
|
placeholder="Heb je nog vragen of iets dat we moeten weten?"
|
|
rows={3}
|
|
autoComplete="off"
|
|
className="resize-none bg-transparent text-lg text-white transition-all placeholder:text-white/40 focus:outline-none"
|
|
/>
|
|
</div>
|
|
|
|
{/* Gift selector */}
|
|
<div className="border border-white/10 p-6">
|
|
<h3 className="mb-4 font-['Intro',sans-serif] text-white text-xl">
|
|
Vrijwillige Gift
|
|
</h3>
|
|
<GiftSelector value={giftAmount} onChange={setGiftAmount} />
|
|
</div>
|
|
|
|
{/* Under review notice */}
|
|
<div className="flex items-start gap-3 rounded-lg border border-amber-400/30 bg-amber-400/10 p-4">
|
|
<svg
|
|
className="mt-0.5 h-5 w-5 shrink-0 text-amber-300"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
strokeWidth={1.5}
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
|
|
/>
|
|
</svg>
|
|
<div>
|
|
<p className="font-semibold text-amber-300 text-sm">
|
|
Jouw inschrijving staat onder voorbehoud
|
|
</p>
|
|
<p className="mt-1 text-sm text-white/70">
|
|
Na het indienen wordt je aanvraag beoordeeld. Je ontvangt een
|
|
bevestiging of je effectief uitgenodigd wordt om op te treden.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col items-center gap-4 pt-4">
|
|
<button
|
|
type="submit"
|
|
disabled={submitMutation.isPending}
|
|
aria-busy={submitMutation.isPending}
|
|
className="bg-amber-400 px-12 py-4 font-['Intro',sans-serif] text-[#1a3d40] text-xl transition-all hover:scale-105 hover:bg-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-400/50 focus:ring-offset-2 focus:ring-offset-[#214e51] disabled:cursor-not-allowed disabled:opacity-50 md:text-2xl"
|
|
>
|
|
{submitMutation.isPending ? (
|
|
<span className="flex items-center gap-2">
|
|
<svg
|
|
className="h-5 w-5 animate-spin"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
aria-label="Laden"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
Bezig...
|
|
</span>
|
|
) : (
|
|
"Bevestigen"
|
|
)}
|
|
</button>
|
|
<a
|
|
href="/contact"
|
|
className="text-sm text-white/60 transition-colors hover:text-white"
|
|
>
|
|
Nog vragen? Neem contact op
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|