- Create /api/contact → sends admin notification email on audit request - Create /api/devis → sends admin notification email on macon devis request - Contact.tsx: make inputs controlled, call /api/contact on submit - MaconClient.tsx DevisForm: add controlled state + submit handler calling /api/devis, add success/error states - /api/candidature: add admin notification email alongside candidate confirmation - /api/admin/candidatures/[id]/reject: fetch candidate info + send rejection email All routes read ADMIN_EMAIL env var for admin notifications (fallback to RESEND_FROM_EMAIL). https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
180 lines
8.1 KiB
TypeScript
180 lines
8.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Button from "@/components/ui/Button";
|
|
import ScrollReveal from "@/components/animations/ScrollReveal";
|
|
|
|
export default function Contact() {
|
|
const [submitted, setSubmitted] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [fields, setFields] = useState({
|
|
name: "",
|
|
phone: "",
|
|
metier: "",
|
|
ville: "",
|
|
});
|
|
|
|
const updateField = (key: keyof typeof fields, value: string) =>
|
|
setFields((prev) => ({ ...prev, [key]: value }));
|
|
|
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const res = await fetch("/api/contact", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(fields),
|
|
});
|
|
if (!res.ok) {
|
|
const data = await res.json();
|
|
throw new Error(data.error || "Erreur lors de l'envoi");
|
|
}
|
|
setSubmitted(true);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Erreur inattendue");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<section id="contact" className="py-16 md:py-24 bg-navy" aria-label="Contact">
|
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
|
{/* Left - Text */}
|
|
<ScrollReveal direction="left">
|
|
<div>
|
|
<span className="inline-block px-3 py-1.5 bg-white/10 rounded-full text-orange text-xs font-semibold mb-4">
|
|
Audit gratuit
|
|
</span>
|
|
<h2 className="text-2xl md:text-3xl lg:text-4xl font-bold text-white tracking-[-0.02em] mb-4">
|
|
Prêt à sécuriser votre carnet de commandes ?
|
|
</h2>
|
|
<p className="text-white/70 text-base leading-relaxed mb-6">
|
|
Je regarde votre situation actuelle sur internet (gratuitement)
|
|
et je vous dis honnêtement ce qu’on peut améliorer.
|
|
</p>
|
|
|
|
<ul className="space-y-3">
|
|
{[
|
|
"Audit de votre présence Google actuelle",
|
|
"Analyse de vos concurrents locaux",
|
|
"Plan d\u2019action concret et chiffré",
|
|
"Réponse sous 24h",
|
|
].map((item, i) => (
|
|
<li key={i} className="flex items-center gap-3">
|
|
<div className="w-5 h-5 bg-orange/20 rounded-full flex items-center justify-center shrink-0">
|
|
<svg className="w-3 h-3 text-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-white/80 text-sm">{item}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</ScrollReveal>
|
|
|
|
{/* Right - Form */}
|
|
<ScrollReveal direction="right">
|
|
<div className="bg-white rounded-2xl p-6 sm:p-8 shadow-xl">
|
|
{submitted ? (
|
|
<div className="text-center py-8">
|
|
<div className="w-16 h-16 bg-success/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<svg className="w-8 h-8 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-navy font-bold text-xl mb-2">Demande envoyée !</h3>
|
|
<p className="text-text-light text-sm">
|
|
Je vous recontacte sous 24h pour planifier votre audit.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<h3 className="text-navy font-bold text-xl mb-1">
|
|
Réserver mon audit gratuit
|
|
</h3>
|
|
<p className="text-text-light text-sm mb-6">
|
|
Remplissez le formulaire, je vous recontacte rapidement.
|
|
</p>
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div>
|
|
<label htmlFor="contact-name" className="block text-sm font-medium text-text mb-1.5">
|
|
Votre nom
|
|
</label>
|
|
<input
|
|
id="contact-name"
|
|
type="text"
|
|
required
|
|
placeholder="Marc Dupont"
|
|
value={fields.name}
|
|
onChange={(e) => updateField("name", e.target.value)}
|
|
className="w-full px-4 py-3 bg-bg border border-border rounded-xl text-text text-sm placeholder:text-text-muted focus:border-orange focus:ring-1 focus:ring-orange outline-none transition-colors"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="contact-phone" className="block text-sm font-medium text-text mb-1.5">
|
|
Téléphone
|
|
</label>
|
|
<input
|
|
id="contact-phone"
|
|
type="tel"
|
|
required
|
|
placeholder="06 12 34 56 78"
|
|
value={fields.phone}
|
|
onChange={(e) => updateField("phone", e.target.value)}
|
|
className="w-full px-4 py-3 bg-bg border border-border rounded-xl text-text text-sm placeholder:text-text-muted focus:border-orange focus:ring-1 focus:ring-orange outline-none transition-colors"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="contact-metier" className="block text-sm font-medium text-text mb-1.5">
|
|
Votre métier
|
|
</label>
|
|
<input
|
|
id="contact-metier"
|
|
type="text"
|
|
required
|
|
placeholder="Couvreur, Menuisier, Paysagiste..."
|
|
value={fields.metier}
|
|
onChange={(e) => updateField("metier", e.target.value)}
|
|
className="w-full px-4 py-3 bg-bg border border-border rounded-xl text-text text-sm placeholder:text-text-muted focus:border-orange focus:ring-1 focus:ring-orange outline-none transition-colors"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="contact-ville" className="block text-sm font-medium text-text mb-1.5">
|
|
Ville / Zone
|
|
</label>
|
|
<input
|
|
id="contact-ville"
|
|
type="text"
|
|
required
|
|
placeholder="Douai, Valenciennes, Orchies..."
|
|
value={fields.ville}
|
|
onChange={(e) => updateField("ville", e.target.value)}
|
|
className="w-full px-4 py-3 bg-bg border border-border rounded-xl text-text text-sm placeholder:text-text-muted focus:border-orange focus:ring-1 focus:ring-orange outline-none transition-colors"
|
|
/>
|
|
</div>
|
|
{error && (
|
|
<p className="text-red-600 text-sm">{error}</p>
|
|
)}
|
|
<Button type="submit" size="lg" className="w-full" loading={loading}>
|
|
RÉSERVER MON AUDIT GRATUIT
|
|
</Button>
|
|
<p className="text-text-muted text-xs text-center">
|
|
Gratuit · Sans engagement · Réponse sous 24h
|
|
</p>
|
|
</form>
|
|
</>
|
|
)}
|
|
</div>
|
|
</ScrollReveal>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|