"use client"; import { useEffect, useId, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { contactFormSchema, type ContactFormValues } from "@/lib/validation"; import { Button } from "@/components/ui/Button"; type Status = | { kind: "idle" } | { kind: "submitting" } | { kind: "success"; delivered: boolean } | { kind: "error"; message: string }; export function ContactForm({ subjects, consentLabel, successTitle, successMessage, }: { subjects: string[]; consentLabel: string; successTitle: string; successMessage: string; }) { const id = useId(); const startedAt = useRef(0); useEffect(() => { startedAt.current = Date.now(); }, []); const [status, setStatus] = useState({ kind: "idle" }); const { register, handleSubmit, reset, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(contactFormSchema), defaultValues: { name: "", email: "", organization: "", subject: "", message: "", consent: undefined, }, }); const submit = async (values: ContactFormValues) => { setStatus({ kind: "submitting" }); try { const form = document.getElementById(`${id}-form`) as HTMLFormElement | null; const honeypot = (form?.elements.namedItem("website") as HTMLInputElement | null)?.value ?? ""; const res = await fetch("/api/contact", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ ...values, website: honeypot, startedAt: startedAt.current }), }); const data = (await res.json().catch(() => ({}))) as { ok?: boolean; delivered?: boolean; error?: string; }; if (!res.ok || !data.ok) { setStatus({ kind: "error", message: res.status === 429 ? "Trop de tentatives. Veuillez réessayer dans quelques minutes." : (data.error ?? "L’envoi a échoué. Veuillez réessayer ou nous écrire directement."), }); return; } setStatus({ kind: "success", delivered: data.delivered !== false }); reset(); } catch { setStatus({ kind: "error", message: "Connexion impossible. Vérifiez votre réseau puis réessayez.", }); } }; if (status.kind === "success") { return (

{successTitle}

{successMessage}

{!status.delivered ? (

Note : le service de messagerie n’est pas encore configuré sur cet environnement ; le message a été reçu mais pas relayé par courriel.

) : null}
); } const field = (name: keyof ContactFormValues) => ({ "aria-invalid": errors[name] ? true : undefined, "aria-describedby": errors[name] ? `${id}-${name}-error` : undefined, }); const err = (name: keyof ContactFormValues) => errors[name] ? ( ) : null; const input = "mt-1.5 w-full rounded-md border border-pale-300 bg-white px-3.5 py-2.5 text-ink-900 placeholder:text-ink-300 focus:border-slate-500 aria-[invalid]:border-danger"; const label = "block text-sm font-medium text-ink-900"; return (
void handleSubmit(submit)(e)} noValidate className="flex flex-col gap-5" aria-describedby={status.kind === "error" ? `${id}-status` : undefined} >
{err("name")}
{err("email")}
{err("organization")}
{err("subject")}