From 3b630bb68c4f76e29963b2689ed4aa3efea22080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=A9o=20GUILBERT?= Date: Tue, 2 Jun 2026 18:49:24 +0200 Subject: [PATCH] add contact form modal and email sending functionality --- app/[locale]/freelance/_client.tsx | 18 ++-- app/actions/sendContactMail.ts | 39 +++++++ components/ui/ContactFormModal.tsx | 160 +++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 app/actions/sendContactMail.ts create mode 100644 components/ui/ContactFormModal.tsx diff --git a/app/[locale]/freelance/_client.tsx b/app/[locale]/freelance/_client.tsx index c71cbe8..981dce0 100644 --- a/app/[locale]/freelance/_client.tsx +++ b/app/[locale]/freelance/_client.tsx @@ -1,8 +1,9 @@ 'use client'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useTranslations } from 'next-intl'; import { ProjectCard } from '@/components/ui/ProjectCard'; +import { ContactFormModal } from '@/components/ui/ContactFormModal'; import { Project } from '@/lib/projects/types'; type props = { @@ -13,6 +14,8 @@ type props = { export default function FreelanceClient({ projects, locale }: props) { const t = useTranslations('freelancePage'); + const [contactOpen, setContactOpen] = useState(false); + const heroRef = useRef(null); const belowFoldRef = useRef(null); @@ -102,6 +105,7 @@ export default function FreelanceClient({ projects, locale }: props) { return (
+ setContactOpen(false)} /> {/* ── Hero ── */}
- setContactOpen(true)} className="inline-flex items-center justify-center gap-2 font-sans text-[13px] tracking-wide text-brand-brown bg-brand-beige rounded-lg px-7 py-3.5 hover:bg-brand-beige/90 transition-colors duration-200 cursor-pointer" > {t('hero.cta1')} - +
{t('cta.note1')} {t('cta.note2')} diff --git a/app/actions/sendContactMail.ts b/app/actions/sendContactMail.ts new file mode 100644 index 0000000..d63de7d --- /dev/null +++ b/app/actions/sendContactMail.ts @@ -0,0 +1,39 @@ +'use server'; + +import { sendMail } from '@/lib/mail'; + +export type ContactFormState = { success: true } | { success: false; error: string } | null; + +export async function sendContactMail( + _: ContactFormState, + formData: FormData, +): Promise { + const name = (formData.get('name') as string)?.trim(); + const email = (formData.get('email') as string)?.trim(); + const message = (formData.get('message') as string)?.trim(); + + if (!name || !email || !message) { + return { success: false, error: 'Tous les champs sont requis.' }; + } + + const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRe.test(email)) { + return { success: false, error: 'Adresse email invalide.' }; + } + + try { + await sendMail({ + from: 'Formulaire de contact ', + subject: `Message de ${name}`, + html: ` +

Nom : ${name}

+

Email : ${email}

+
+

${message.replace(/\n/g, '
')}

+ `, + }); + return { success: true }; + } catch { + return { success: false, error: 'Une erreur est survenue. Réessaie plus tard.' }; + } +} diff --git a/components/ui/ContactFormModal.tsx b/components/ui/ContactFormModal.tsx new file mode 100644 index 0000000..d6b91a7 --- /dev/null +++ b/components/ui/ContactFormModal.tsx @@ -0,0 +1,160 @@ +'use client'; + +import { useEffect, useRef, useState, useActionState } from 'react'; +import { createPortal } from 'react-dom'; +import { sendContactMail, type ContactFormState } from '@/app/actions/sendContactMail'; + +type Props = { + isOpen: boolean; + onClose: () => void; +}; + +type FieldProps = { + label: string; + name: string; + type?: string; + required?: boolean; + textarea?: boolean; + inputRef?: React.RefObject; +}; + +function Field({ label, name, type = 'text', required, textarea, inputRef }: FieldProps) { + const base = + 'w-full font-sans text-sm text-brand-brown bg-white/60 border border-brand-brown/12 rounded-lg px-4 py-3 placeholder:text-brand-brown/25 focus:outline-none focus:border-brand-brown/35 focus:bg-white/80 transition-colors duration-150'; + + return ( +
+ + {textarea ? ( +