diff --git a/artifacts/voix-du-peuple/package.json b/artifacts/voix-du-peuple/package.json
index 0c32cf4..ce136a3 100644
--- a/artifacts/voix-du-peuple/package.json
+++ b/artifacts/voix-du-peuple/package.json
@@ -73,5 +73,8 @@
"vite": "catalog:",
"wouter": "^3.3.5",
"zod": "catalog:"
+ },
+ "dependencies": {
+ "qrcode.react": "^4.2.0"
}
}
diff --git a/artifacts/voix-du-peuple/public/opengraph.jpg b/artifacts/voix-du-peuple/public/opengraph.jpg
index fbbfc7c..566f14f 100644
Binary files a/artifacts/voix-du-peuple/public/opengraph.jpg and b/artifacts/voix-du-peuple/public/opengraph.jpg differ
diff --git a/artifacts/voix-du-peuple/src/App.tsx b/artifacts/voix-du-peuple/src/App.tsx
index f0125e0..8234e83 100644
--- a/artifacts/voix-du-peuple/src/App.tsx
+++ b/artifacts/voix-du-peuple/src/App.tsx
@@ -6,6 +6,7 @@ import NotFound from "@/pages/not-found";
import Home from "@/pages/home";
import About from "@/pages/about";
import Transparence from "@/pages/transparence";
+import Flyer from "@/pages/flyer";
const queryClient = new QueryClient({
defaultOptions: {
@@ -27,6 +28,7 @@ function Navbar() {
Manifeste
À propos
Fonctionnement
+ Flyer QR
+
diff --git a/artifacts/voix-du-peuple/src/index.css b/artifacts/voix-du-peuple/src/index.css
index e8ef3d4..0b1f411 100644
--- a/artifacts/voix-du-peuple/src/index.css
+++ b/artifacts/voix-du-peuple/src/index.css
@@ -302,3 +302,26 @@
inset: -1px;
}
}
+
+/* ─── Impression / PDF ─────────────────────────────────────── */
+@media print {
+ /* Masquer tout sauf le flyer */
+ header,
+ nav,
+ .no-print {
+ display: none !important;
+ }
+
+ #flyer-print {
+ box-shadow: none !important;
+ border: none !important;
+ max-width: 100% !important;
+ width: 100% !important;
+ aspect-ratio: auto !important;
+ page-break-inside: avoid;
+ }
+
+ body {
+ background: white !important;
+ }
+}
diff --git a/artifacts/voix-du-peuple/src/pages/flyer.tsx b/artifacts/voix-du-peuple/src/pages/flyer.tsx
new file mode 100644
index 0000000..6804763
--- /dev/null
+++ b/artifacts/voix-du-peuple/src/pages/flyer.tsx
@@ -0,0 +1,124 @@
+import React, { useState } from "react";
+import { QRCodeSVG } from "qrcode.react";
+import { Printer, ExternalLink } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Link } from "wouter";
+
+// ══════════════════════════════════════════════════════════
+// URL encodée dans le QR code — modifiez cette ligne
+const DEFAULT_QR_URL = "https://lavoixdupeuple.fr";
+// ══════════════════════════════════════════════════════════
+
+export default function Flyer() {
+ const params = new URLSearchParams(window.location.search);
+ const [qrUrl, setQrUrl] = useState(params.get("url") || DEFAULT_QR_URL);
+ const [inputValue, setInputValue] = useState(qrUrl);
+
+ const applyUrl = () => {
+ const trimmed = inputValue.trim();
+ if (trimmed) setQrUrl(trimmed);
+ };
+
+ return (
+
+
+ {/* Barre de contrôle — masquée à l'impression */}
+
+
+ Destination du QR code
+
+
+ setInputValue(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && applyUrl()}
+ placeholder="https://lavoixdupeuple.fr"
+ className="font-mono text-sm"
+ />
+
+
+
+
+
+
+
+
+
+
+ {/* Flyer imprimable */}
+
+
+
+ {/* Tricolore décoratif */}
+
+
+
+
+
+
+ {/* Titre */}
+
+
+ La Voix du Peuple
+
+
+ Soumettez vos propositions citoyennes.
+ Elles sont synthétisées et transmises à vos élus.
+
+
+
+ {/* QR Code */}
+
+
+ {/* Appel à l'action */}
+
+
+ Scannez le QR code ou rendez-vous sur
+
+
+ {qrUrl.replace(/^https?:\/\//, "")}
+
+
+
+ {/* Pied — fondements */}
+
+
+ Plateforme modérée selon la Déclaration universelle des droits de l'homme (ONU, 1948),
+ le PIDCP et la CEDH. Aucune donnée personnelle collectée.
+
+
+
+
+
+ );
+}
diff --git a/artifacts/voix-du-peuple/src/pages/home.tsx b/artifacts/voix-du-peuple/src/pages/home.tsx
index 1e67f69..f05eb1b 100644
--- a/artifacts/voix-du-peuple/src/pages/home.tsx
+++ b/artifacts/voix-du-peuple/src/pages/home.tsx
@@ -27,7 +27,9 @@ import {
} from "@/components/ui/accordion";
import {
Loader2, PenTool, CheckCircle2, Info, AlertCircle, TrendingUp, Users, Scale,
+ Share2, Printer, Copy,
} from "lucide-react";
+import { useToast } from "@/hooks/use-toast";
const submitIdeaSchema = z.object({
content: z.string()
@@ -71,6 +73,7 @@ const VALEURS = [
export default function Home() {
const queryClient = useQueryClient();
+ const { toast } = useToast();
const [submitResult, setSubmitResult] = React.useState<{
success: boolean;
message: string;
@@ -84,6 +87,64 @@ export default function Home() {
query: { refetchInterval: 15000 },
});
+ const handleShare = () => {
+ if (!synthesis?.text) return;
+ const date = format(new Date(), "d MMMM yyyy 'à' HH:mm", { locale: fr });
+ const nb = synthesis.ideaCount ?? 0;
+ const shareText = `La Voix du Peuple — Synthèse citoyenne\nGénérée le ${date}\n\n${synthesis.text}\n\n(${nb} contribution${nb !== 1 ? "s" : ""} intégrée${nb !== 1 ? "s" : ""})\n\nhttps://lavoixdupeuple.fr`;
+ if (navigator.share) {
+ navigator.share({ title: "La Voix du Peuple — Synthèse", text: shareText });
+ } else {
+ navigator.clipboard.writeText(shareText).then(() => {
+ toast({ description: "Texte copié dans le presse-papier ✓" });
+ });
+ }
+ };
+
+ const handlePrint = () => {
+ if (!synthesis?.text) return;
+ const date = format(new Date(), "d MMMM yyyy 'à' HH:mm", { locale: fr });
+ const nb = synthesis.ideaCount ?? 0;
+ const safe = (s: string) => s.replace(/&/g, "&").replace(//g, ">");
+ const html = `
+
+
+
+
La Voix du Peuple — Synthèse
+
+
+
+
+
La Voix du Peuple — Synthèse citoyenne
+
Générée le ${safe(date)} · ${nb} contribution${nb !== 1 ? "s" : ""} intégrée${nb !== 1 ? "s" : ""}
+
${safe(synthesis.text)}
+
+
+`;
+ const w = window.open("", "_blank");
+ if (w) {
+ w.document.write(html);
+ w.document.close();
+ w.onload = () => w.print();
+ }
+ };
+
const form = useForm
({
resolver: zodResolver(submitIdeaSchema),
defaultValues: { content: "", author: "" },
@@ -306,8 +367,8 @@ export default function Home() {
/>
{/* En-tête fixe */}
-
-
+
+
Synthèse des contributions
@@ -315,12 +376,38 @@ export default function Home() {
Mise à jour à chaque nouvelle contribution
- {stats && (
-
- Contributions intégrées
- {stats.accepted}
-
- )}
+
+ {stats && (
+
+ Intégrées
+ {stats.accepted}
+
+ )}
+
+
+
{/* Texte défilable */}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 78f7498..6b8f567 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -403,6 +403,10 @@ importers:
version: 3.25.76
artifacts/voix-du-peuple:
+ dependencies:
+ qrcode.react:
+ specifier: ^4.2.0
+ version: 4.2.0(react@19.1.0)
devDependencies:
'@hookform/resolvers':
specifier: ^3.10.0
@@ -2701,6 +2705,11 @@ packages:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
+ qrcode.react@4.2.0:
+ resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
qs@6.15.0:
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
engines: {node: '>=0.6'}
@@ -5292,6 +5301,10 @@ snapshots:
punycode.js@2.3.1: {}
+ qrcode.react@4.2.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
qs@6.15.0:
dependencies:
side-channel: 1.1.0