diff --git a/frontend/app/api/contact/route.ts b/frontend/app/api/contact/route.ts
index 99b12a3..2c73d08 100644
--- a/frontend/app/api/contact/route.ts
+++ b/frontend/app/api/contact/route.ts
@@ -24,3 +24,92 @@ export async function GET() {
return NextResponse.json({ error: "Unexpected error" }, { status: 500 });
}
}
+
+export async function POST(req: Request) {
+ try {
+ const RESEND_API_KEY = process.env.RESEND_API_KEY;
+ const RESEND_FROM = process.env.RESEND_FROM || "no-reply@resend.dev";
+ const CONTACT_TO = process.env.CONTACT_TO || process.env.CONTACT_EMAIL; // fallback name
+
+ if (!RESEND_API_KEY) {
+ return NextResponse.json({ error: "RESEND_API_KEY not configured" }, { status: 500 });
+ }
+ if (!CONTACT_TO) {
+ return NextResponse.json({ error: "CONTACT_TO (destination email) not configured" }, { status: 500 });
+ }
+
+ const payload = await req.json().catch(() => null);
+ if (!payload || typeof payload !== "object") {
+ return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
+ }
+
+ const name = String(payload.name || "").trim();
+ const email = String(payload.email || "").trim();
+ const subject = String(payload.subject || "").trim() || "Nouveau message via le site";
+ const message = String(payload.message || "").trim();
+
+ if (!name || !email || !message) {
+ return NextResponse.json({ error: "Champs requis manquants: name, email, message" }, { status: 400 });
+ }
+
+ // Basic email format check (very permissive)
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
+ return NextResponse.json({ error: "Email invalide" }, { status: 400 });
+ }
+
+ const html = `
+
+
Nom: ${escapeHtml(name)}
+
Email: ${escapeHtml(email)}
+
Sujet: ${escapeHtml(subject)}
+
Message:
${escapeHtml(message).replace(/\n/g, '
')}
+
+ `;
+
+ const resendRes = await fetch("https://api.resend.com/emails", {
+ method: "POST",
+ headers: {
+ "Authorization": `Bearer ${RESEND_API_KEY}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ from: RESEND_FROM,
+ to: [CONTACT_TO],
+ reply_to: [email],
+ subject: subject,
+ html,
+ text: `Nom: ${name}\nEmail: ${email}\nSujet: ${subject}\n\n${message}`,
+ }),
+ });
+
+ const text = await resendRes.text();
+
+ if (!resendRes.ok) {
+ try {
+ const err = JSON.parse(text);
+ return NextResponse.json(err, { status: resendRes.status });
+ } catch {
+ return new NextResponse(text || "Failed to send message via Resend", { status: resendRes.status });
+ }
+ }
+
+ try {
+ const json = JSON.parse(text || "{}");
+ return NextResponse.json(json, { status: 200 });
+ } catch {
+ return NextResponse.json({ ok: true }, { status: 200 });
+ }
+ } catch (error) {
+ return NextResponse.json({ error: "Unexpected error" }, { status: 500 });
+ }
+}
+
+// Small helper to prevent HTML injection in email body
+function escapeHtml(input: string): string {
+ return input
+ .replaceAll('&', '&')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>')
+ .replaceAll('"', '"')
+ .replaceAll("'", ''');
+}
diff --git a/frontend/app/components/Contact.tsx b/frontend/app/components/Contact.tsx
index bb6295c..5356921 100644
--- a/frontend/app/components/Contact.tsx
+++ b/frontend/app/components/Contact.tsx
@@ -64,40 +64,105 @@ export default function ContactComponent() {
{data?.email ? (
-
+
) : (
Une erreur est survenue. Vous pouvez tout de même me contacter directement par email : Me joindre par email
)}
);
-}
\ No newline at end of file
+}
+
+function ContactForm() {
+ const [name, setName] = useState("");
+ const [email, setEmail] = useState("");
+ const [subject, setSubject] = useState("");
+ const [message, setMessage] = useState("");
+ const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
+ const [error, setError] = useState(null);
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ setError(null);
+
+ if (!name || !email || !message) {
+ setError("Veuillez remplir les champs requis.");
+ return;
+ }
+
+ setStatus("loading");
+ try {
+ const res = await fetch("/api/contact", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ name, email, subject, message }),
+ });
+
+ if (!res.ok) {
+ const txt = await res.text();
+ throw new Error(txt || "Erreur lors de l'envoi du message");
+ }
+
+ setStatus("success");
+ setName("");
+ setEmail("");
+ setSubject("");
+ setMessage("");
+ } catch (err: any) {
+ setStatus("error");
+ setError(err?.message || "Une erreur est survenue.");
+ }
+ }
+
+ return (
+
+ );
+}
diff --git a/frontend/app/components/Footer.tsx b/frontend/app/components/Footer.tsx
index 2faee9c..96cc72f 100644
--- a/frontend/app/components/Footer.tsx
+++ b/frontend/app/components/Footer.tsx
@@ -1,21 +1,8 @@
-"use client";
-
-import { useEffect, useState } from "react";
-
export default function Footer() {
- const [isTop, setIsTop] = useState(true);
-
- useEffect(() => {
- const onScroll = () => setIsTop(window.scrollY <= 0);
- onScroll();
- window.addEventListener("scroll", onScroll, { passive: true });
- return () => window.removeEventListener("scroll", onScroll);
- }, []);
-
return (
-