feat:accessibility, new routes, cookie consent, and UI improvements
- Add contact, privacy, and terms pages - Add CookieConsent component with accept/decline and localStorage - Add self-hosted DM Sans font with @font-face definitions - Improve registration form with field validation, blur handlers, and performer toggle - Redesign Info section with 'Ongedesemd Brood' hero and FAQ layout - Remove scroll-snap behavior from all sections - Add reduced motion support and selection color theming - Add SVG favicon and SEO meta tags in root layout - Improve accessibility: aria attributes, semantic HTML, focus styles - Add link-hover underline animation utility
This commit is contained in:
@@ -1,82 +1,223 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { orpc } from "@/utils/orpc";
|
||||
|
||||
interface FormErrors {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
artForm?: string;
|
||||
experience?: string;
|
||||
}
|
||||
|
||||
export default function EventRegistrationForm() {
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
wantsToPerform: false,
|
||||
artForm: "",
|
||||
experience: "",
|
||||
extraQuestions: "",
|
||||
});
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
const [touched, setTouched] = useState<Record<string, boolean>>({});
|
||||
|
||||
const validateField = useCallback(
|
||||
(
|
||||
name: string,
|
||||
value: string,
|
||||
wantsToPerform?: boolean,
|
||||
): string | undefined => {
|
||||
switch (name) {
|
||||
case "firstName":
|
||||
if (!value.trim()) return "Voornaam is verplicht";
|
||||
if (value.length < 2)
|
||||
return "Voornaam moet minimaal 2 tekens bevatten";
|
||||
break;
|
||||
case "lastName":
|
||||
if (!value.trim()) return "Achternaam is verplicht";
|
||||
if (value.length < 2)
|
||||
return "Achternaam moet minimaal 2 tekens bevatten";
|
||||
break;
|
||||
case "email":
|
||||
if (!value.trim()) return "E-mail is verplicht";
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
||||
return "Voer een geldig e-mailadres in";
|
||||
}
|
||||
break;
|
||||
case "phone":
|
||||
if (value && !/^[\d\s\-+()]{10,}$/.test(value.replace(/\s/g, ""))) {
|
||||
return "Voer een geldig telefoonnummer in";
|
||||
}
|
||||
break;
|
||||
case "artForm":
|
||||
if (wantsToPerform && !value.trim()) return "Kunstvorm is verplicht";
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const submitMutation = useMutation({
|
||||
...orpc.submitRegistration.mutationOptions(),
|
||||
onSuccess: () => {
|
||||
toast.success("Registratie succesvol!");
|
||||
toast.success(
|
||||
"Registratie succesvol! We nemen binnenkort contact met je op.",
|
||||
);
|
||||
setFormData({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
wantsToPerform: false,
|
||||
artForm: "",
|
||||
experience: "",
|
||||
extraQuestions: "",
|
||||
});
|
||||
setErrors({});
|
||||
setTouched({});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Er is iets misgegaan: ${error.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
submitMutation.mutate({
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
email: formData.email,
|
||||
phone: formData.phone || undefined,
|
||||
artForm: formData.artForm,
|
||||
experience: formData.experience || undefined,
|
||||
});
|
||||
};
|
||||
const handleChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value, type } = e.target;
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
const newValue = type === "checkbox" ? checked : value;
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[e.target.name]: e.target.value,
|
||||
}));
|
||||
setFormData((prev) => {
|
||||
const updated = { ...prev, [name]: newValue };
|
||||
// Clear performer fields when unchecking wantsToPerform
|
||||
if (name === "wantsToPerform" && !checked) {
|
||||
updated.artForm = "";
|
||||
updated.experience = "";
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
|
||||
if (type !== "checkbox" && touched[name]) {
|
||||
const error = validateField(
|
||||
name,
|
||||
value,
|
||||
name === "artForm" ? formData.wantsToPerform : undefined,
|
||||
);
|
||||
setErrors((prev) => ({ ...prev, [name]: error }));
|
||||
}
|
||||
},
|
||||
[touched, validateField, formData.wantsToPerform],
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setTouched((prev) => ({ ...prev, [name]: true }));
|
||||
const error = validateField(
|
||||
name,
|
||||
value,
|
||||
name === "artForm" ? formData.wantsToPerform : undefined,
|
||||
);
|
||||
setErrors((prev) => ({ ...prev, [name]: error }));
|
||||
},
|
||||
[validateField, formData.wantsToPerform],
|
||||
);
|
||||
|
||||
const validateForm = useCallback((): boolean => {
|
||||
const newErrors: FormErrors = {};
|
||||
newErrors.firstName = validateField("firstName", formData.firstName);
|
||||
newErrors.lastName = validateField("lastName", formData.lastName);
|
||||
newErrors.email = validateField("email", formData.email);
|
||||
newErrors.phone = validateField("phone", formData.phone);
|
||||
newErrors.artForm = validateField(
|
||||
"artForm",
|
||||
formData.artForm,
|
||||
formData.wantsToPerform,
|
||||
);
|
||||
|
||||
setErrors(newErrors);
|
||||
setTouched({
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
email: true,
|
||||
phone: true,
|
||||
artForm: true,
|
||||
experience: true,
|
||||
});
|
||||
|
||||
return !Object.values(newErrors).some(Boolean);
|
||||
}, [formData, validateField]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!validateForm()) {
|
||||
toast.error("Controleer je invoer");
|
||||
return;
|
||||
}
|
||||
submitMutation.mutate({
|
||||
firstName: formData.firstName.trim(),
|
||||
lastName: formData.lastName.trim(),
|
||||
email: formData.email.trim(),
|
||||
phone: formData.phone.trim() || undefined,
|
||||
wantsToPerform: formData.wantsToPerform,
|
||||
artForm: formData.wantsToPerform
|
||||
? formData.artForm.trim() || undefined
|
||||
: undefined,
|
||||
experience: formData.wantsToPerform
|
||||
? formData.experience.trim() || undefined
|
||||
: undefined,
|
||||
extraQuestions: formData.extraQuestions.trim() || undefined,
|
||||
});
|
||||
},
|
||||
[formData, submitMutation, validateForm],
|
||||
);
|
||||
|
||||
const getInputClasses = (fieldName: keyof FormErrors) => {
|
||||
const baseClasses =
|
||||
"border-b bg-transparent pb-2 text-lg text-white placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-[#214e51] transition-all";
|
||||
const errorClasses =
|
||||
touched[fieldName] && errors[fieldName]
|
||||
? "border-red-400"
|
||||
: "border-white/30 focus:border-white";
|
||||
return `${baseClasses} ${errorClasses}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
id="registration"
|
||||
className="snap-section relative z-30 min-h-screen w-full bg-[#214e51]/96 px-12 py-16"
|
||||
className="relative z-30 flex w-full items-center justify-center bg-[#214e51]/96 px-6 py-16 md:px-12"
|
||||
>
|
||||
<div className="mx-auto flex h-full max-w-6xl flex-col">
|
||||
<h2 className="mb-2 font-['Intro',sans-serif] text-4xl text-white">
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col">
|
||||
<h2 className="mb-2 font-['Intro',sans-serif] text-3xl text-white md:text-4xl">
|
||||
Schrijf je nu in!
|
||||
</h2>
|
||||
<p className="mb-12 max-w-3xl font-['Intro',sans-serif] text-white/80 text-xl">
|
||||
Doet dit jouw creatieve geest borellen? Vul nog even dit formulier in
|
||||
<p className="mb-12 max-w-3xl text-lg text-white/80 md:text-xl">
|
||||
Doet dit jouw creatieve geest borrelen? Vul nog even dit formulier in
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
|
||||
<div className="flex flex-col gap-8">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col"
|
||||
noValidate
|
||||
>
|
||||
<div className="flex flex-col gap-6 md:gap-8">
|
||||
{/* Row 1: First Name + Last Name */}
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="firstName"
|
||||
className="font-['Intro',sans-serif] text-2xl text-white"
|
||||
className="text-white text-xl md:text-2xl"
|
||||
>
|
||||
Voornaam
|
||||
Voornaam <span className="text-red-300">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -84,18 +225,33 @@ export default function EventRegistrationForm() {
|
||||
name="firstName"
|
||||
value={formData.firstName}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
placeholder="Jouw voornaam"
|
||||
required
|
||||
className="border-white/30 border-b bg-transparent pb-2 font-['Intro',sans-serif] text-lg text-white placeholder:text-white/40 focus:border-white focus:outline-none"
|
||||
autoComplete="given-name"
|
||||
aria-required="true"
|
||||
aria-invalid={touched.firstName && !!errors.firstName}
|
||||
aria-describedby={
|
||||
errors.firstName ? "firstName-error" : undefined
|
||||
}
|
||||
className={getInputClasses("firstName")}
|
||||
/>
|
||||
{touched.firstName && errors.firstName && (
|
||||
<span
|
||||
id="firstName-error"
|
||||
className="text-red-300 text-sm"
|
||||
role="alert"
|
||||
>
|
||||
{errors.firstName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="lastName"
|
||||
className="font-['Intro',sans-serif] text-2xl text-white"
|
||||
className="text-white text-xl md:text-2xl"
|
||||
>
|
||||
Achternaam
|
||||
Achternaam <span className="text-red-300">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -103,21 +259,36 @@ export default function EventRegistrationForm() {
|
||||
name="lastName"
|
||||
value={formData.lastName}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
placeholder="Jouw achternaam"
|
||||
required
|
||||
className="border-white/30 border-b bg-transparent pb-2 font-['Intro',sans-serif] text-lg text-white placeholder:text-white/40 focus:border-white focus:outline-none"
|
||||
autoComplete="family-name"
|
||||
aria-required="true"
|
||||
aria-invalid={touched.lastName && !!errors.lastName}
|
||||
aria-describedby={
|
||||
errors.lastName ? "lastName-error" : undefined
|
||||
}
|
||||
className={getInputClasses("lastName")}
|
||||
/>
|
||||
{touched.lastName && errors.lastName && (
|
||||
<span
|
||||
id="lastName-error"
|
||||
className="text-red-300 text-sm"
|
||||
role="alert"
|
||||
>
|
||||
{errors.lastName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 2: Email + Phone */}
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="font-['Intro',sans-serif] text-2xl text-white"
|
||||
className="text-white text-xl md:text-2xl"
|
||||
>
|
||||
E-mail
|
||||
E-mail <span className="text-red-300">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
@@ -125,16 +296,30 @@ export default function EventRegistrationForm() {
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
placeholder="jouw@email.nl"
|
||||
required
|
||||
className="border-white/30 border-b bg-transparent pb-2 font-['Intro',sans-serif] text-lg text-white placeholder:text-white/40 focus:border-white focus:outline-none"
|
||||
onBlur={handleBlur}
|
||||
placeholder="jouw@email.be"
|
||||
autoComplete="email"
|
||||
inputMode="email"
|
||||
aria-required="true"
|
||||
aria-invalid={touched.email && !!errors.email}
|
||||
aria-describedby={errors.email ? "email-error" : undefined}
|
||||
className={getInputClasses("email")}
|
||||
/>
|
||||
{touched.email && errors.email && (
|
||||
<span
|
||||
id="email-error"
|
||||
className="text-red-300 text-sm"
|
||||
role="alert"
|
||||
>
|
||||
{errors.email}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="font-['Intro',sans-serif] text-2xl text-white"
|
||||
className="text-white text-xl md:text-2xl"
|
||||
>
|
||||
Telefoon
|
||||
</label>
|
||||
@@ -144,51 +329,158 @@ export default function EventRegistrationForm() {
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
placeholder="06-12345678"
|
||||
className="border-white/30 border-b bg-transparent pb-2 font-['Intro',sans-serif] text-lg text-white placeholder:text-white/40 focus:border-white focus:outline-none"
|
||||
autoComplete="tel"
|
||||
inputMode="tel"
|
||||
aria-invalid={touched.phone && !!errors.phone}
|
||||
aria-describedby={errors.phone ? "phone-error" : undefined}
|
||||
className={getInputClasses("phone")}
|
||||
/>
|
||||
{touched.phone && errors.phone && (
|
||||
<span
|
||||
id="phone-error"
|
||||
className="text-red-300 text-sm"
|
||||
role="alert"
|
||||
>
|
||||
{errors.phone}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 3: Art Form (full width) */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="artForm"
|
||||
className="font-['Intro',sans-serif] text-2xl text-white"
|
||||
>
|
||||
Kunstvorm
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="artForm"
|
||||
name="artForm"
|
||||
value={formData.artForm}
|
||||
onChange={handleChange}
|
||||
placeholder="Muziek, Theater, Dans, etc."
|
||||
required
|
||||
className="border-white/30 border-b bg-transparent pb-2 font-['Intro',sans-serif] text-lg text-white placeholder:text-white/40 focus:border-white focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Row 4: Experience (full width) */}
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="experience"
|
||||
className="font-['Intro',sans-serif] text-2xl text-white"
|
||||
>
|
||||
Ervaring
|
||||
</label>
|
||||
{/* Row 3: Wants to perform checkbox */}
|
||||
<label
|
||||
htmlFor="wantsToPerform"
|
||||
className="flex cursor-pointer items-center gap-4"
|
||||
>
|
||||
<div className="relative flex shrink-0 self-center">
|
||||
<input
|
||||
type="text"
|
||||
id="experience"
|
||||
name="experience"
|
||||
value={formData.experience}
|
||||
type="checkbox"
|
||||
id="wantsToPerform"
|
||||
name="wantsToPerform"
|
||||
checked={formData.wantsToPerform}
|
||||
onChange={handleChange}
|
||||
placeholder="Beginner / Gevorderd / Professional"
|
||||
className="border-white/30 border-b bg-transparent pb-2 font-['Intro',sans-serif] text-lg text-white placeholder:text-white/40 focus:border-white focus:outline-none"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<div className="h-6 w-6 border-2 border-white/50 bg-transparent transition-colors peer-checked:border-white peer-checked:bg-white peer-focus:ring-2 peer-focus:ring-white/50 peer-focus:ring-offset-2 peer-focus:ring-offset-[#214e51]" />
|
||||
<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-white text-xl md:text-2xl">
|
||||
Ik wil optreden
|
||||
</span>
|
||||
<span className="text-sm text-white/60">
|
||||
Vink aan als je wilt optreden. Laat dit leeg als je alleen
|
||||
wilt komen kijken.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{/* Performer fields: shown only when wantsToPerform is checked */}
|
||||
{formData.wantsToPerform && (
|
||||
<div className="flex flex-col gap-6 border border-white/20 p-6 md:gap-8">
|
||||
{/* Art Form */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="artForm"
|
||||
className="text-white text-xl md:text-2xl"
|
||||
>
|
||||
Kunstvorm <span className="text-red-300">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="artForm"
|
||||
name="artForm"
|
||||
value={formData.artForm}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
placeholder="Muziek, Theater, Dans, etc."
|
||||
autoComplete="off"
|
||||
list="artFormSuggestions"
|
||||
aria-required="true"
|
||||
aria-invalid={touched.artForm && !!errors.artForm}
|
||||
aria-describedby={
|
||||
errors.artForm ? "artForm-error" : undefined
|
||||
}
|
||||
className={getInputClasses("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
|
||||
id="artForm-error"
|
||||
className="text-red-300 text-sm"
|
||||
role="alert"
|
||||
>
|
||||
{errors.artForm}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Experience */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="experience"
|
||||
className="text-white text-xl md:text-2xl"
|
||||
>
|
||||
Ervaring
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="experience"
|
||||
name="experience"
|
||||
value={formData.experience}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
placeholder="Beginner / Gevorderd / Professional"
|
||||
autoComplete="off"
|
||||
list="experienceSuggestions"
|
||||
className={getInputClasses("experience")}
|
||||
/>
|
||||
<datalist id="experienceSuggestions">
|
||||
<option value="Beginner" />
|
||||
<option value="Gevorderd" />
|
||||
<option value="Professional" />
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Extra questions / remarks */}
|
||||
<div className="flex flex-col gap-2 border border-white/20 p-6">
|
||||
<label
|
||||
htmlFor="extraQuestions"
|
||||
className="text-white text-xl md:text-2xl"
|
||||
>
|
||||
Vragen of opmerkingen
|
||||
</label>
|
||||
<textarea
|
||||
id="extraQuestions"
|
||||
name="extraQuestions"
|
||||
value={formData.extraQuestions}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
placeholder="Heb je nog vragen of iets dat we moeten weten?"
|
||||
rows={4}
|
||||
autoComplete="off"
|
||||
className="resize-none bg-transparent text-lg text-white transition-all placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-[#214e51]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -197,17 +489,45 @@ export default function EventRegistrationForm() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitMutation.isPending}
|
||||
className="bg-white px-12 py-4 font-['Intro',sans-serif] text-2xl text-[#214e51] transition-all hover:scale-105 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
aria-busy={submitMutation.isPending}
|
||||
className="bg-white px-12 py-4 font-['Intro',sans-serif] text-[#214e51] text-xl transition-all hover:scale-105 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-[#214e51] disabled:cursor-not-allowed disabled:opacity-50 md:text-2xl"
|
||||
>
|
||||
{submitMutation.isPending ? "Bezig..." : "Bevestigen"}
|
||||
{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>
|
||||
|
||||
<p className="text-center font-['Intro',sans-serif] text-sm text-white/60">
|
||||
Nu al een act / idee van wat jij graag zou willen brengen,{" "}
|
||||
<span className="cursor-pointer underline hover:text-white">
|
||||
klik HIER
|
||||
</span>
|
||||
</p>
|
||||
<a
|
||||
href="/contact"
|
||||
className="link-hover block text-center text-sm text-white/60 transition-colors hover:text-white"
|
||||
>
|
||||
Nog vragen? Neem contact op
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user