diff --git a/.env.example b/.env.example
index b99c222..d2be06e 100644
--- a/.env.example
+++ b/.env.example
@@ -1,22 +1,46 @@
+# La Voix du Peuple — Variables d'environnement
+# Copiez ce fichier en .env et remplissez les valeurs.
+# Sécurisez le fichier : chmod 600 .env
+
# ─── Base de données PostgreSQL ──────────────────────────────────────────────
# Format : postgresql://utilisateur:motdepasse@hote:port/nomdb
DATABASE_URL=postgresql://voixdupeuple:CHANGEME@localhost:5432/voixdupeuple
-# ─── Clé API OpenAI ──────────────────────────────────────────────────────────
-# Obtenez votre clé sur https://platform.openai.com/api-keys
-OPENAI_API_KEY=sk-...
+# ─── IA — Mistral (recommandé — souveraineté européenne) ─────────────────────
+# Obtenez votre clé sur https://console.mistral.ai
+MISTRAL_API_KEY=sk-...
-# ─── (Optionnel) Proxy OpenAI compatible ─────────────────────────────────────
-# Décommentez si vous utilisez un proxy (Ollama avec OpenAI compat, Azure, etc.)
-# OPENAI_BASE_URL=https://votre-proxy.example.com/v1
+# ─── IA — Alternative OpenAI-compatible (si Mistral non disponible) ──────────
+# OPENAI_API_KEY=sk-...
+# OPENAI_BASE_URL=https://votre-proxy.example.com/v1 # Optionnel : proxy/Ollama
-# ─── Modèles IA (optionnel — valeurs par défaut recommandées) ────────────────
-# OPENAI_FILTER_MODEL=gpt-4o-mini # Modèle de filtrage (rapide, économique)
-# OPENAI_SYNTHESIS_MODEL=gpt-4o # Modèle de synthèse (haute qualité)
+# ─── Modèles IA (optionnel — valeurs par défaut ci-dessous) ──────────────────
+# FILTER_MODEL=mistral-small-latest # Modèle de filtrage (rapide, économique)
+# SYNTHESIS_MODEL=mistral-large-latest # Modèle de synthèse (haute qualité)
+
+# ─── Sécurité Flask ───────────────────────────────────────────────────────────
+# Générez avec : python3 -c "import secrets; print(secrets.token_hex(32))"
+SECRET_KEY=CHANGEZ_CE_SECRET_AVEC_UNE_VALEUR_ALEATOIRE_LONGUE
+
+# ─── Panel d'administration ───────────────────────────────────────────────────
+# Mot de passe pour accéder à /admin
+ADMIN_SECRET=CHANGEZ_CE_MOT_DE_PASSE_ADMIN
# ─── Flask ────────────────────────────────────────────────────────────────────
FLASK_ENV=production
-PORT=8000
+PORT=8080
-# ─── Session (générez avec: python3 -c "import secrets; print(secrets.token_hex(32))") ───
-SESSION_SECRET=CHANGEZ_CE_SECRET_AVEC_UNE_VALEUR_ALEATOIRE_LONGUE
+# ─── Anti-abus (optionnel — valeurs par défaut raisonnables) ─────────────────
+# REDIS_URL=redis://localhost:6379/0 # Rate limiting persistant (recommandé en prod)
+# RATE_LIMIT_CONTRIBUTIONS=5 per minute;3 per hour # Limite de soumissions
+# CONTRIBUTION_COOLDOWN_SECONDS=3600 # Délai entre deux soumissions (même session)
+# FLOOD_THRESHOLD=10 # Alerte flood : nb soumissions / 5 min / IP
+
+# ─── hCaptcha (optionnel — recommandé en production) ─────────────────────────
+# Créez un compte sur https://www.hcaptcha.com (RGPD-friendly)
+# HCAPTCHA_SECRET_KEY=votre-cle-secrete-hcaptcha
+# VITE_HCAPTCHA_SITE_KEY=votre-cle-de-site-hcaptcha # Nécessite rebuild frontend
+
+# ─── Frontend ─────────────────────────────────────────────────────────────────
+# URL publique du site (utilisée par le QR code et les exports)
+VITE_APP_URL=https://votredomaine.fr
diff --git a/artifacts/voix-du-peuple/src/App.tsx b/artifacts/voix-du-peuple/src/App.tsx
index d39afdb..cdb0452 100644
--- a/artifacts/voix-du-peuple/src/App.tsx
+++ b/artifacts/voix-du-peuple/src/App.tsx
@@ -14,6 +14,7 @@ import LegalNotice from "@/pages/legal-notice";
import PrivacyPolicy from "@/pages/privacy-policy";
import ContributionsBrutes from "@/pages/contributions-brutes";
import ConsultationPage from "@/pages/consultation";
+import ConsultationsList from "@/pages/consultations-list";
import { AccessibilityProvider } from "@/hooks/use-accessibility";
import { AccessibilityPanel } from "@/components/accessibility-panel";
import { setVisitorId } from "@workspace/api-client-react";
@@ -75,6 +76,9 @@ function Footer() {
Contributions brutes
+
+ Consultations
+
EUPL-1.2 · billisdead
@@ -105,6 +109,7 @@ function Router() {
+
diff --git a/artifacts/voix-du-peuple/src/pages/consultations-list.tsx b/artifacts/voix-du-peuple/src/pages/consultations-list.tsx
new file mode 100644
index 0000000..b05ac78
--- /dev/null
+++ b/artifacts/voix-du-peuple/src/pages/consultations-list.tsx
@@ -0,0 +1,147 @@
+// Copyright (C) 2026 billisdead — Licence EUPL-1.2
+// Page d'index des consultations publiques actives
+import { useState, useEffect } from "react";
+import { Link } from "wouter";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Clock, Users, Building2, ArrowRight, Loader2, Lock, CalendarDays,
+} from "lucide-react";
+import { format, formatDistanceToNow } from "date-fns";
+import { fr } from "date-fns/locale";
+
+const API_BASE = import.meta.env.VITE_API_URL ?? "";
+
+type Consultation = {
+ id: number;
+ slug: string;
+ title: string;
+ subject: string;
+ organizerName: string | null;
+ organizerLogoUrl: string | null;
+ startsAt: string | null;
+ endsAt: string | null;
+ closedAt: string | null;
+ isOpen: boolean;
+ stats: { total: number; accepted: number };
+};
+
+export default function ConsultationsList() {
+ const [consultations, setConsultations] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [showClosed, setShowClosed] = useState(false);
+
+ useEffect(() => {
+ setLoading(true);
+ fetch(`${API_BASE}/api/consultations?include_closed=${showClosed}`)
+ .then((r) => r.json())
+ .then(setConsultations)
+ .catch(() => setConsultations([]))
+ .finally(() => setLoading(false));
+ }, [showClosed]);
+
+ return (
+
+
+
Consultations citoyennes
+
+ Chaque consultation est un espace thématique ciblé, ouvert par un organisateur
+ (collectivité, association, collectif). Contribuez aux consultations ouvertes,
+ consultez les synthèses des consultations clôturées.
+