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