From 99b7c904a6d867b30738afceebb8c179af3e4ba1 Mon Sep 17 00:00:00 2001 From: Adrien Mulattieri Date: Wed, 24 Jun 2026 10:11:49 +0200 Subject: [PATCH 01/12] implement ui --- data/utils/build_pmtiles.py | 2 +- website/components/core/rf-button.tsx | 10 +- .../components/home/contact-section-v2.tsx | 133 ++++++++++ website/components/home/contact-section.tsx | 246 ------------------ website/components/home/home.tsx | 4 +- website/public/contact-background.svg | 9 + 6 files changed, 152 insertions(+), 252 deletions(-) create mode 100644 website/components/home/contact-section-v2.tsx delete mode 100644 website/components/home/contact-section.tsx create mode 100644 website/public/contact-background.svg diff --git a/data/utils/build_pmtiles.py b/data/utils/build_pmtiles.py index c5833424..601e8807 100644 --- a/data/utils/build_pmtiles.py +++ b/data/utils/build_pmtiles.py @@ -22,7 +22,7 @@ REPO_ROOT = SCRIPT_DIR.parent.parent WEBSITE_DIR = REPO_ROOT / "website" -DUCKDB_PATH = REPO_ROOT / "data" / "exploration" / "website.duckdb" +DUCKDB_PATH = REPO_ROOT / "data" / "exploration" / "dev.duckdb" CACHE_DIR = REPO_ROOT / ".cache" GEOJSON_PATH = CACHE_DIR / "communes.geojsonl" PMTILES_PATH = WEBSITE_DIR / "public/pmtiles/communes.pmtiles" diff --git a/website/components/core/rf-button.tsx b/website/components/core/rf-button.tsx index 04e8ebb5..570232f6 100644 --- a/website/components/core/rf-button.tsx +++ b/website/components/core/rf-button.tsx @@ -1,11 +1,12 @@ 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"; } @@ -24,7 +25,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 +34,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,7 +61,6 @@ export function RFButton(props: RFButtonProps) { variant = "primary", icon, iconPosition = "left", - as = "link", } = props; const className = clsx(baseClasses, variantClasses[variant]); diff --git a/website/components/home/contact-section-v2.tsx b/website/components/home/contact-section-v2.tsx new file mode 100644 index 00000000..12013798 --- /dev/null +++ b/website/components/home/contact-section-v2.tsx @@ -0,0 +1,133 @@ +import SectionTitle from "./section-title"; +import { RFButton } from "@/components/core/rf-button"; +import { ArrowRight } from "lucide-react"; + +const ContactSectionV2: React.FC = () => { + return ( +
+ {/* Header with background pattern */} +
+
+

+ Contactez-nous +

+

+ pour agir dès maintenant +

+
+

+ 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. + +

+
+ + {/* 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. +

+ } + iconPosition="right" + /> +
+ + {/* Maire / Élu card */} +
+ +

+ Construisons des solutions justes et abordables ensemble, afin de + protéger l'assurabilité des communes françaises. +

+ } + iconPosition="right" + /> +
+
+
+
+ ); +}; + +export default ContactSectionV2; diff --git a/website/components/home/contact-section.tsx b/website/components/home/contact-section.tsx deleted file mode 100644 index d3b7783b..00000000 --- a/website/components/home/contact-section.tsx +++ /dev/null @@ -1,246 +0,0 @@ -"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", - ); - - return ( - <> -
- {/* ── Titre ── */} -
-
- - Contactez-nous - -
-

- pour agir dès maintenant -

-

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

-
- - {/* ── Formulaire ── */} -
- handleChange("name", e.target.value)} - className={inputClass(errors.name)} - /> - - handleChange("city", e.target.value)} - className={inputClass(errors.city)} - /> - - {/* ── Sélecteur Vous êtes: ── */} -
- -
- - -
-
- - {/* ── Sélecteur conditionnel pour maire/élu ── */} - {isMayor && ( -
- -
- - -
-
- )} - - handleChange("email", e.target.value)} - className={inputClass(errors.email)} - /> -