"use client"; import { FormEvent, useEffect, useMemo, useState } from "react"; import { useSearchParams } from "next/navigation"; import Link from "next/link"; import { Link as LinkIcon, Mail, MessageCircle, Send, ShieldCheck } from "lucide-react"; import { contactPayloadSchema, normalizeProjectParam, PROJECT_LABELS, projectValues, REQUEST_TYPE_LABELS, requestTypeValues, type ContactPayload, } from "@/lib/contact"; import { footerSites, PROJECT_DESCRIPTIONS } from "@/lib/sites"; type FormState = ContactPayload; type StatusState = { type: "success" | "error"; message: string } | null; type TouchedState = { [K in keyof FormState]?: boolean; }; const initialState: FormState = { name: "", email: "", project: "other", requestType: "question", message: "", honeypot: "", sourceUrl: "", }; const faqItems = [ { question: "Tu reponds en combien de temps ?", answer: "En general sous 24h ouvrables. Pour les urgences techniques, je priorise les bugs critiques.", }, { question: "Les projets sont-ils gratuits ?", answer: "La plupart ont un acces gratuit. Selon les besoins, certaines fonctionnalites peuvent etre premium ou auto-hebergees.", }, { question: "Puis-je proposer une idee ?", answer: "Oui. Les suggestions sont les bienvenues, surtout si elles incluent le contexte, le besoin et un exemple concret.", }, ]; function inputClass(hasError: boolean) { return [ "w-full rounded-2xl border bg-white/90 px-4 py-3 text-sm text-slate-900 shadow-sm outline-none transition focus:ring-2 dark:bg-slate-950/60 dark:text-slate-100", hasError ? "border-rose-300 focus:border-rose-400 focus:ring-rose-200/70 dark:border-rose-500/40 dark:focus:ring-rose-500/30" : "border-slate-200 focus:border-cyan-300 focus:ring-cyan-200/70 dark:border-slate-800 dark:focus:border-cyan-500 dark:focus:ring-cyan-500/30", ].join(" "); } export default function ContactPageClient() { const searchParams = useSearchParams(); const [form, setForm] = useState(initialState); const [touched, setTouched] = useState({}); const [status, setStatus] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [hasSubmitted, setHasSubmitted] = useState(false); useEffect(() => { const projectFromQuery = normalizeProjectParam(searchParams.get("project")); if (!projectFromQuery) { return; } setForm((current) => ({ ...current, project: projectFromQuery })); }, [searchParams]); useEffect(() => { if (typeof window === "undefined") { return; } setForm((current) => ({ ...current, sourceUrl: `${window.location.origin}${window.location.pathname}${window.location.search}`, })); }, []); const validation = useMemo(() => contactPayloadSchema.safeParse(form), [form]); const fieldErrors = useMemo(() => { if (validation.success) { return {} as Record; } const flattened = validation.error.flatten().fieldErrors; return { name: flattened.name?.[0], email: flattened.email?.[0], project: flattened.project?.[0], requestType: flattened.requestType?.[0], message: flattened.message?.[0], honeypot: flattened.honeypot?.[0], sourceUrl: flattened.sourceUrl?.[0], } satisfies Record; }, [validation]); const isFormValid = validation.success; const selectedProjectLabel = PROJECT_LABELS[form.project]; const updateField = (name: keyof FormState, value: string) => { setForm((current) => ({ ...current, [name]: value })); setStatus(null); }; const markTouched = (name: keyof FormState) => { setTouched((current) => ({ ...current, [name]: true })); }; async function handleSubmit(event: FormEvent) { event.preventDefault(); setHasSubmitted(true); if (!isFormValid) { setStatus({ type: "error", message: "Le formulaire contient des erreurs. Corrige-les puis renvoie ton message.", }); return; } setIsSubmitting(true); setStatus(null); try { const response = await fetch("/api/contact", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(form), }); const payload = (await response.json().catch(() => null)) as | { message?: string } | null; if (!response.ok) { throw new Error(payload?.message ?? "Une erreur est survenue lors de l'envoi."); } setStatus({ type: "success", message: "Message envoye ! Je te repondrai rapidement par email.", }); setForm((current) => ({ ...initialState, project: current.project })); setTouched({}); setHasSubmitted(false); } catch (error) { setStatus({ type: "error", message: error instanceof Error ? error.message : "Impossible d'envoyer le message pour le moment.", }); } finally { setIsSubmitting(false); } } return (
Reponse rapide ⚡

📬 Me contacter

Une question, un bug ou une idee ? Je reponds rapidement.

Temps de reponse moyen : < 24h

Vous contactez a propos de : {selectedProjectLabel}
{PROJECT_DESCRIPTIONS[form.project]}