Add sharing options and a printable flyer page to the website
Implement share and PDF export buttons on the home page, and create a new flyer page with a customizable QR code for printing and distribution. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a2b8df7d-660f-4020-961b-e37cb231d6a4 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/Z3YUti7 Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -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, "<").replace(/>/g, ">");
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>La Voix du Peuple — Synthèse</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: 'Inter', 'Segoe UI', sans-serif; max-width: 680px; margin: 48px auto; color: #1a1a2e; line-height: 1.75; padding: 0 24px; }
|
||||
.tricolor { display: flex; height: 4px; width: 80px; margin-bottom: 32px; border-radius: 2px; overflow: hidden; }
|
||||
.tricolor span:nth-child(1) { flex: 1; background: #002395; }
|
||||
.tricolor span:nth-child(2) { flex: 1; background: #EDEDED; }
|
||||
.tricolor span:nth-child(3) { flex: 1; background: #ED2939; }
|
||||
h1 { font-size: 1.35rem; font-weight: 700; letter-spacing: -0.01em; margin-bottom: 4px; color: #1a1a2e; }
|
||||
.meta { font-size: 0.72rem; color: #888; font-family: monospace; margin-bottom: 36px; }
|
||||
.text { font-size: 1rem; white-space: pre-line; color: #1a1a2e; }
|
||||
.footer { margin-top: 48px; padding-top: 16px; border-top: 1px solid #eee; font-size: 0.7rem; color: #bbb; font-family: monospace; display: flex; justify-content: space-between; }
|
||||
@media print { body { margin: 32px auto; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="tricolor"><span></span><span></span><span></span></div>
|
||||
<h1>La Voix du Peuple — Synthèse citoyenne</h1>
|
||||
<div class="meta">Générée le ${safe(date)} · ${nb} contribution${nb !== 1 ? "s" : ""} intégrée${nb !== 1 ? "s" : ""}</div>
|
||||
<div class="text">${safe(synthesis.text)}</div>
|
||||
<div class="footer">
|
||||
<span>lavoixdupeuple.fr</span>
|
||||
<span>Document généré automatiquement — modération selon DUDH / PIDCP / CEDH</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
const w = window.open("", "_blank");
|
||||
if (w) {
|
||||
w.document.write(html);
|
||||
w.document.close();
|
||||
w.onload = () => w.print();
|
||||
}
|
||||
};
|
||||
|
||||
const form = useForm<SubmitIdeaValues>({
|
||||
resolver: zodResolver(submitIdeaSchema),
|
||||
defaultValues: { content: "", author: "" },
|
||||
@@ -306,8 +367,8 @@ export default function Home() {
|
||||
/>
|
||||
|
||||
{/* En-tête fixe */}
|
||||
<div className="flex justify-between items-start px-6 md:px-10 py-5 border-b border-border/30 relative z-10 flex-shrink-0">
|
||||
<div>
|
||||
<div className="flex justify-between items-start px-6 md:px-10 py-5 border-b border-border/30 relative z-10 flex-shrink-0 gap-3">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-xs font-mono font-bold uppercase tracking-widest text-primary flex items-center gap-2">
|
||||
<Users className="h-3.5 w-3.5" /> Synthèse des contributions
|
||||
</h2>
|
||||
@@ -315,12 +376,38 @@ export default function Home() {
|
||||
Mise à jour à chaque nouvelle contribution
|
||||
</p>
|
||||
</div>
|
||||
{stats && (
|
||||
<div className="text-xs font-mono text-right" data-testid="text-stats">
|
||||
<span className="text-muted-foreground">Contributions intégrées </span>
|
||||
<span className="font-bold text-primary">{stats.accepted}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
{stats && (
|
||||
<div className="text-xs font-mono text-right mr-2" data-testid="text-stats">
|
||||
<span className="text-muted-foreground">Intégrées </span>
|
||||
<span className="font-bold text-primary">{stats.accepted}</span>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 px-2 text-xs gap-1.5 text-muted-foreground hover:text-foreground"
|
||||
onClick={handleShare}
|
||||
disabled={!synthesis?.text}
|
||||
title="Partager ou copier la synthèse"
|
||||
>
|
||||
{navigator.share ? (
|
||||
<><Share2 className="h-3.5 w-3.5" /> Partager</>
|
||||
) : (
|
||||
<><Copy className="h-3.5 w-3.5" /> Copier</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 px-2 text-xs gap-1.5 text-muted-foreground hover:text-foreground"
|
||||
onClick={handlePrint}
|
||||
disabled={!synthesis?.text}
|
||||
title="Exporter en PDF / imprimer"
|
||||
>
|
||||
<Printer className="h-3.5 w-3.5" /> PDF
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Texte défilable */}
|
||||
|
||||
Reference in New Issue
Block a user