From 9737caff99eb44e487695d5ae34898b657671271 Mon Sep 17 00:00:00 2001 From: toine Date: Sun, 5 Oct 2025 08:52:12 +0200 Subject: [PATCH] mise a jour du frontend --- frontend/app/api/contact/route.ts | 89 +++++++++++++++++++ frontend/app/components/Contact.tsx | 127 ++++++++++++++++++++------- frontend/app/components/Footer.tsx | 17 +--- frontend/app/components/Header.tsx | 6 +- frontend/app/components/Services.tsx | 2 +- frontend/app/components/Skills.tsx | 23 ++++- frontend/app/css/components.css | 60 ++++++++++++- frontend/app/css/globals.css | 4 +- frontend/app/projects/page.tsx | 64 ++++++++++++-- 9 files changed, 332 insertions(+), 60 deletions(-) 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 ? ( -
-
- - -
- -
- - -
- -
- - -
- -
- -