diff --git a/website/app/api/contact/route.ts b/website/app/api/contact/route.ts index a6cfd73b..a0dce936 100644 --- a/website/app/api/contact/route.ts +++ b/website/app/api/contact/route.ts @@ -27,7 +27,6 @@ export async function POST(req: NextRequest) { // Convert ContactFormData (camelCase) to GoogleScriptPayload (snake_case) const gsPayload: GoogleScriptPayload = { nom: formData.name, - situation: formData.userType, email: formData.email, message: formData.message, ville: formData.city, diff --git a/website/components/core/rf-button.tsx b/website/components/core/rf-button.tsx index 04e8ebb5..c4ad6160 100644 --- a/website/components/core/rf-button.tsx +++ b/website/components/core/rf-button.tsx @@ -1,18 +1,21 @@ import Link from "next/link"; import clsx from "clsx"; import { ReactNode } from "react"; +import { badgeVariants } from "../ui/badge"; interface RFButtonBaseProps { title: string; onClick?: () => void; - variant?: "primary" | "secondary"; + variant?: "primary" | "secondary" | "tertiary" | "quaternary"; icon?: ReactNode; iconPosition?: "left" | "right"; + className?: string; } interface RFLinkProps extends RFButtonBaseProps { as?: "link"; path: string; + target?: string; } interface RFButtonElement extends RFButtonBaseProps { @@ -24,7 +27,7 @@ interface RFButtonElement extends RFButtonBaseProps { type RFButtonProps = RFLinkProps | RFButtonElement; const baseClasses = clsx( - "inline-flex items-center gap-[8px] px-5 py-2 w-fit text-base font-bold border rounded-none transition-all duration-150", + "inline-flex items-center gap-[8px] px-5 py-2 w-fit text-base font-bold rounded-none transition-all duration-150", "hover:shadow-none hover:translate-x-1 hover:translate-y-1", ); @@ -33,6 +36,10 @@ const variantClasses = { "bg-rf-green-dark text-rf-lime border-rf-lime shadow-[4px_4px_0px_var(--color-rf-lime)]", secondary: "bg-rf-lime text-rf-green-dark border-rf-lime shadow-[4px_4px_0px_var(--color-rf-green-dark)]", + tertiary: + "bg-rf-lime text-rf-green-dark border-rf-lime shadow-[4px_4px_0px_var(--color-rf-green-light)]", + quaternary: + "bg-rf-green-dark text-rf-lime border-rf-lime shadow-[4px_4px_0px_var(--color-rf-green-light)]", }; function renderContent( @@ -56,10 +63,10 @@ export function RFButton(props: RFButtonProps) { variant = "primary", icon, iconPosition = "left", - as = "link", + className: extraClassName, } = props; - const className = clsx(baseClasses, variantClasses[variant]); + const className = clsx(baseClasses, variantClasses[variant], extraClassName); if (props.as === "button") { return ( @@ -74,7 +81,7 @@ export function RFButton(props: RFButtonProps) { } return ( - + {renderContent(icon, title, iconPosition)} ); diff --git a/website/components/home/citizen-modal.tsx b/website/components/home/citizen-modal.tsx new file mode 100644 index 00000000..c364acd0 --- /dev/null +++ b/website/components/home/citizen-modal.tsx @@ -0,0 +1,190 @@ +"use client"; + +import { useState } from "react"; +import { ArrowRight, Mail, Instagram, Copy, Check, X } from "lucide-react"; +import Image from "next/image"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { RFButton } from "@/components/core/rf-button"; +import SectionTitle from "./section-title"; + +const MAIL_TEMPLATE = `Madame la Maire / Monsieur le Maire, + +En consultant le site « Assurer ma ville », j'ai découvert que notre commune figure parmi les territoires exposés à des difficultés croissantes en matière d'assurance face aux risques climatiques. + +Selon les données présentées sur la plateforme, notre commune obtient un score de vulnérabilité de [SCORE]. Ce score s'appuie notamment sur l'exposition aux catastrophes naturelles, l'évolution des conditions d'assurance des collectivités et leur situation financière. + +C'est pourquoi je souhaitais porter cette initiative à votre connaissance et vous inviter à consulter les informations disponibles sur le site : assurermaville.fr. + +Cette situation m'interpelle en tant qu'habitant(e). Partout en France, de plus en plus de collectivités font face à des augmentations importantes de primes, à des franchises plus élevées, à des résiliations de contrats ou à des appels d'offres qui ne reçoivent aucune réponse. Ces difficultés peuvent avoir des conséquences directes sur les finances locales et sur la capacité des communes à protéger leurs équipements et services publics. + +Reclaim Finance appelle le gouvernement à mettre en place des solutions d'assurance justes et abordables pour les collectivités territoriales. Pour que ces solutions répondent aux réalités du terrain, il est essentiel que les maires et les élus locaux puissent faire entendre leur expérience et leurs besoins. + +J'espère que notre commune pourra s'intéresser à cette démarche et, le cas échéant, contribuer aux réflexions visant à garantir un accès durable à l'assurance pour l'ensemble des collectivités. + +Je vous remercie pour votre attention et vous prie d'agréer, Madame la Maire / Monsieur le Maire, l'expression de mes salutations respectueuses. + +[Nom] +[Commune] +`; + +export function CitizenModal() { + const [open, setOpen] = useState(false); + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(MAIL_TEMPLATE).catch(() => {}); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const handleEmail = () => { + const subject = encodeURIComponent("Notre commune est vulnérable"); + const body = encodeURIComponent(MAIL_TEMPLATE); + window.location.href = `mailto:?subject=${subject}&body=${body}`; + }; + + return ( + <> + } + iconPosition="right" + onClick={() => setOpen(true)} + /> + + + + {/* Header with topographic background */} +
+ + + Je suis citoyen.ne + {/* Title — mirrors SectionTitle style but centered */} + + +

+ Aidez-nous en alertant sur la situation de votre commune et en + partageant l'initiative à vos élu·e·s pour protéger + l'assurabilité des communes françaises +

+
+ + {/* Action cards */} +
+ {/* Email card */} +
+
+ +
+ +
+

+ 1. Contacter ma + mairie +

+

+ Aidez-nous en partageant la situation de votre commune +

+
+ + + + } + iconPosition="left" + onClick={handleEmail} + className="w-full justify-center" + /> +
+ + {/* Instagram card */} +
+
+ +
+ +
+

+ 2. Tagger votre + mairie en commentaires +

+

+ Aidez-nous en partageant la situation de votre commune +

+
+ + } + iconPosition="left" + className="mt-auto w-full justify-center" + /> +
+
+ + {/* L'affaire du siècle section */} + {/* TODO: replace logo-assurer-ma-ville.svg with the actual L'affaire du siècle logo once added to /public */} +
+ L'affaire du siècle +

+ Vous vous trouvez dans une commune{" "} + particulièrement exposée ? +
+ Vérifiez si vous êtes vous-même sinistré-e climatique et + interpellez l'Etat pour qu'il assure votre protection +

+ + Disponible le 9 juillet 2026 + +
+
+
+ + ); +} diff --git a/website/components/home/contact-section.tsx b/website/components/home/contact-section.tsx index d3b7783b..3193fc87 100644 --- a/website/components/home/contact-section.tsx +++ b/website/components/home/contact-section.tsx @@ -1,245 +1,120 @@ -"use client"; - -import { useState } from "react"; -import { ArrowRight, Check } from "lucide-react"; -import { toast } from "sonner"; -import { Toaster } from "@/components/ui/toaster"; -import { cn } from "@/lib/utils"; -import type { ContactFormData } from "@/lib/types/contact"; - -type FormErrors = Partial>; - -const ContactSection = () => { - const [formData, setFormData] = useState({ - name: "", - userType: "citoyen", - city: "", - insuranceQuestion: undefined, - email: "", - message: "", - }); - - const [errors, setErrors] = useState({}); - const [loading, setLoading] = useState(false); - const [submitted, setSubmitted] = useState(false); - - const isMayor = formData.userType === "maire_ou_elu"; - - const handleChange = (field: keyof ContactFormData, value: string) => { - setFormData((prev) => ({ ...prev, [field]: value })); - }; - - const handleSubmit = async () => { - const newErrors: FormErrors = { - name: !formData.name.trim(), - userType: !formData.userType, - city: !formData.city.trim(), - email: !formData.email.trim(), - message: !formData.message.trim(), - }; - if (isMayor) { - newErrors.insuranceQuestion = !formData.insuranceQuestion; - } - setErrors(newErrors); - if (Object.values(newErrors).some(Boolean)) { - toast.error("Veuillez remplir tous les champs obligatoires."); - return; - } - - setLoading(true); - try { - const res = await fetch("/api/contact", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(formData), - }); - - if (res.ok) { - setSubmitted(true); - toast.success( - "Message envoyé à Reclaim Finance ! Merci pour votre engagement.", - ); - } else { - const errorText = await res.text(); - console.error("Erreur lors de l'envoi du formulaire", errorText); - toast.error("Erreur lors de l'envoi du message. Veuillez réessayer."); - } - } catch (err) { - console.error("Erreur réseau:", err); - toast.error( - "Erreur réseau. Veuillez vérifier votre connexion et réessayer.", - ); - } finally { - setLoading(false); - } - }; - - const inputClass = (error: boolean | undefined) => - cn( - "w-full bg-transparent border-b pb-3 pt-1 outline-none text-lg transition-colors", - "placeholder:transition-colors", - error - ? "border-red-500 text-red-500 placeholder:text-red-500" - : "border-rf-green-light text-rf-green-dark placeholder:text-black", - ); +import SectionTitle from "./section-title"; +import { CitizenModal } from "./citizen-modal"; +import { MayorModal } from "./mayor-modal"; +const ContactSection: React.FC = () => { return ( - <> -
- {/* ── Titre ── */} -
-
- - Contactez-nous - -
-

+
+ {/* Header with background pattern */} +
+
+

+ Contactez-nous +

+

pour agir dès maintenant -

-

- Vous êtes citoyen.ne ou élu.e, contactez-nous dès maintenant pour en - savoir plus +

+
+

+ Vous êtes citoyen ou élu, contactez-nous dès maintenant pour en savoir + plus +

+ + + {/* Content section */} +
+ + +
+

+ Car aujourd'hui, maires, élus et citoyens sont déjà confrontés + à la dégradation des conditions d'assurance de leur commune + (augmentation des tarifs, des franchises, résiliations unilatérales, + appels d'offre sans réponse, etc.). +

+

+ Reclaim Finance appelle le gouvernement à mettre en + place des{" "} + solutions d'assurance justes et abordables{" "} + pour les collectivités territoriales en France. Pour défendre ces + solutions auprès du gouvernement et des assureurs français,{" "} + nous avons besoin de vous ! +

+

+ + Citoyens, maires et élus, chacun peut avoir un impact, alors + rejoignez l'initiative pour en savoir plus et agir. +

- {/* ── Formulaire ── */} -
- handleChange("name", e.target.value)} - className={inputClass(errors.name)} - /> - - handleChange("city", e.target.value)} - className={inputClass(errors.city)} - /> - - {/* ── Sélecteur Vous êtes: ── */} -
- -
- - -
+ {/* Cards */} +
+ {/* Citoyen card */} +
+ +

+ Aidez-nous en partageant la situation de votre commune, et en + alertant vos élus de notre initiative pour protéger + l'assurabilité des communes françaises. +

+
- {/* ── Sélecteur conditionnel pour maire/élu ── */} - {isMayor && ( -
- -
- - -
-
- )} - - handleChange("email", e.target.value)} - className={inputClass(errors.email)} - /> -