From aa859a1e4489031fc3aa289c8d76063a331eec3f Mon Sep 17 00:00:00 2001
From: Yannick Le Duc
Date: Wed, 27 Aug 2025 12:21:09 +0200
Subject: [PATCH] =?UTF-8?q?Ajout=20param=C3=A8tre=20message=20bas=20de=20p?=
=?UTF-8?q?age=20personnalisable?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../campaigns/[id]/propositions/page.tsx | 3 +-
src/app/admin/page.tsx | 3 +-
src/app/admin/settings/page.tsx | 70 +++++-
src/app/campaigns/[id]/propose/page.tsx | 119 ++++++----
.../[id]/vote/[participantId]/page.tsx | 16 +-
src/app/globals.css | 32 ++-
src/app/page.tsx | 7 +-
src/components/AddPropositionModal.tsx | 2 +-
src/components/DeleteCampaignModal.tsx | 3 +-
src/components/EditPropositionModal.tsx | 2 +-
src/components/Footer.tsx | 100 ++++++++
src/components/MarkdownEditor.tsx | 159 +++++++------
src/lib/markdown.ts | 224 ++++++++++++++----
src/lib/services.ts | 8 +
src/lib/utils.ts | 39 +++
15 files changed, 580 insertions(+), 207 deletions(-)
create mode 100644 src/components/Footer.tsx
diff --git a/src/app/admin/campaigns/[id]/propositions/page.tsx b/src/app/admin/campaigns/[id]/propositions/page.tsx
index a1d5187..b50bb90 100644
--- a/src/app/admin/campaigns/[id]/propositions/page.tsx
+++ b/src/app/admin/campaigns/[id]/propositions/page.tsx
@@ -15,6 +15,7 @@ import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import Navigation from '@/components/Navigation';
import AuthGuard from '@/components/AuthGuard';
import { FileText, Calendar, Mail, Upload } from 'lucide-react';
+import { MarkdownContent } from '@/components/MarkdownContent';
export const dynamic = 'force-dynamic';
@@ -196,7 +197,7 @@ function CampaignPropositionsPageContent() {
{proposition.title}
- {proposition.description}
+
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 361d89d..fab97fd 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -15,6 +15,7 @@ import { Badge } from '@/components/ui/badge';
import AuthGuard from '@/components/AuthGuard';
import { FolderOpen, Users, FileText, Plus, BarChart3, Settings, Check, Copy } from 'lucide-react';
import StatusSwitch from '@/components/StatusSwitch';
+import { MarkdownContent } from '@/components/MarkdownContent';
export const dynamic = 'force-dynamic';
@@ -255,7 +256,7 @@ function AdminPageContent() {
- {campaign.description}
+
{/* Status Switch */}
diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx
index 8094ed3..6cfed4d 100644
--- a/src/app/admin/settings/page.tsx
+++ b/src/app/admin/settings/page.tsx
@@ -9,7 +9,7 @@ import { Label } from '@/components/ui/label';
import Navigation from '@/components/Navigation';
import AuthGuard from '@/components/AuthGuard';
import SmtpSettingsForm from '@/components/SmtpSettingsForm';
-import { Settings, Monitor, Save, CheckCircle, Mail } from 'lucide-react';
+import { Settings, Monitor, Save, CheckCircle, Mail, FileText } from 'lucide-react';
export const dynamic = 'force-dynamic';
@@ -19,6 +19,8 @@ function SettingsPageContent() {
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(false);
const [randomizePropositions, setRandomizePropositions] = useState(false);
+ const [proposePageMessage, setProposePageMessage] = useState('');
+ const [footerMessage, setFooterMessage] = useState('');
useEffect(() => {
loadSettings();
@@ -33,6 +35,14 @@ function SettingsPageContent() {
// Charger la valeur du paramètre d'ordre aléatoire
const randomizeValue = await settingsService.getBooleanValue('randomize_propositions', false);
setRandomizePropositions(randomizeValue);
+
+ // Charger le message de la page de dépôt de propositions
+ const messageValue = await settingsService.getStringValue('propose_page_message', 'Partagez votre vision et proposez des projets qui feront la différence dans votre collectif. Votre voix compte pour façonner l\'avenir de votre communauté.');
+ setProposePageMessage(messageValue);
+
+ // Charger le message du bas de page
+ const footerValue = await settingsService.getStringValue('footer_message', 'Développé avec ❤️ pour faciliter la démocratie participative - [Logiciel libre et open source](GITURL) et transparent pour tous');
+ setFooterMessage(footerValue);
} catch (error) {
console.error('Erreur lors du chargement des paramètres:', error);
} finally {
@@ -48,6 +58,8 @@ function SettingsPageContent() {
try {
setSaving(true);
await settingsService.setBooleanValue('randomize_propositions', randomizePropositions);
+ await settingsService.setStringValue('propose_page_message', proposePageMessage);
+ await settingsService.setStringValue('footer_message', footerMessage);
setSaved(true);
setTimeout(() => setSaved(false), 2000);
} catch (error) {
@@ -148,6 +160,62 @@ function SettingsPageContent() {
+ {/* Textes Category */}
+
+
+
+
+
+
+
+ Textes
+
+ Personnalisez les textes affichés dans l'application
+
+
+
+
+
+ {/* Propose Page Message Setting */}
+
+
+
+
+ Ce texte apparaît sous le titre de la campagne pour inviter les utilisateurs à déposer des propositions.
+
+
+
+
+ {/* Footer Message Setting */}
+
+
+
+
+ Ce texte apparaît en bas des pages publiques. Vous pouvez utiliser [texte du lien](GITURL) pour insérer un lien vers le repository Git.
+
+
+
+
+
+
{/* Email Category */}
{
setSaved(true);
diff --git a/src/app/campaigns/[id]/propose/page.tsx b/src/app/campaigns/[id]/propose/page.tsx
index a006afd..b9930c2 100644
--- a/src/app/campaigns/[id]/propose/page.tsx
+++ b/src/app/campaigns/[id]/propose/page.tsx
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
import { useParams } from 'next/navigation';
import Link from 'next/link';
import { Campaign } from '@/types';
-import { campaignService, propositionService } from '@/lib/services';
+import { campaignService, propositionService, settingsService } from '@/lib/services';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@ -13,6 +13,7 @@ import { ArrowLeft, FileText, User, Mail, CheckCircle, AlertCircle } from 'lucid
import { MarkdownContent } from '@/components/MarkdownContent';
import { MarkdownEditor } from '@/components/MarkdownEditor';
import { PROJECT_CONFIG } from '@/lib/project.config';
+import Footer from '@/components/Footer';
export const dynamic = 'force-dynamic';
@@ -25,13 +26,16 @@ export default function PublicProposePage() {
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState(false);
+ const [startTime] = useState(Date.now()); // Validation temporelle
+ const [proposePageMessage, setProposePageMessage] = useState('');
const [formData, setFormData] = useState({
title: '',
description: '',
author_first_name: '',
author_last_name: '',
- author_email: ''
+ author_email: '',
+ website: '' // Honeypot field
});
useEffect(() => {
@@ -43,8 +47,12 @@ export default function PublicProposePage() {
const loadCampaign = async () => {
try {
setLoading(true);
- const campaigns = await campaignService.getAll();
- const campaignData = campaigns.find(c => c.id === campaignId);
+ const [campaigns, messageValue] = await Promise.all([
+ campaignService.getAll(),
+ settingsService.getStringValue('propose_page_message', 'Partagez votre vision et proposez des projets qui feront la différence dans votre collectif. Votre voix compte pour façonner l\'avenir de votre communauté.')
+ ]);
+
+ const campaignData = campaigns.find((c: Campaign) => c.id === campaignId);
if (!campaignData) {
setError('Campagne non trouvée');
@@ -57,6 +65,7 @@ export default function PublicProposePage() {
}
setCampaign(campaignData);
+ setProposePageMessage(messageValue);
} catch (error) {
console.error('Erreur lors du chargement de la campagne:', error);
setError('Erreur lors du chargement de la campagne');
@@ -70,6 +79,21 @@ export default function PublicProposePage() {
setSubmitting(true);
setError('');
+ // Validation temporelle - détecte les soumissions trop rapides
+ const timeSpent = Date.now() - startTime;
+ if (timeSpent < 5000) { // Moins de 5 secondes
+ setError('Veuillez prendre le temps de bien rédiger votre proposition');
+ setSubmitting(false);
+ return;
+ }
+
+ // Validation honeypot - détecte les bots
+ if (formData.website) {
+ setError('Soumission invalide');
+ setSubmitting(false);
+ return;
+ }
+
try {
await propositionService.create({
campaign_id: campaignId,
@@ -86,7 +110,8 @@ export default function PublicProposePage() {
description: '',
author_first_name: '',
author_last_name: '',
- author_email: ''
+ author_email: '',
+ website: ''
});
} catch (err: any) {
const errorMessage = err?.message || err?.details || 'Erreur lors de la soumission de la proposition';
@@ -172,44 +197,28 @@ export default function PublicProposePage() {
{/* Header */}
-
-
-
Déposer une proposition
-
- Campagne : {campaign?.title}
-
-
+
+
+ {campaign?.title}
+
+
+ {proposePageMessage}
+
- {/* Campaign Info */}
-
-
-
-
- Informations sur la campagne
-
-
-
-
-
-
+ {/* Campaign Description */}
+
+
+
{/* Form */}
-
-
- Votre proposition
-
- Remplissez le formulaire ci-dessous pour soumettre votre proposition.
-
+
+
+ Votre proposition
@@ -220,6 +229,25 @@ export default function PublicProposePage() {
)}
+ {/* Honeypot field - caché pour détecter les bots */}
+
+
);
diff --git a/src/app/campaigns/[id]/vote/[participantId]/page.tsx b/src/app/campaigns/[id]/vote/[participantId]/page.tsx
index 90ad768..a87c9f2 100644
--- a/src/app/campaigns/[id]/vote/[participantId]/page.tsx
+++ b/src/app/campaigns/[id]/vote/[participantId]/page.tsx
@@ -7,6 +7,7 @@ import { Campaign, Proposition, Participant, Vote, PropositionWithVote } from '@
import { campaignService, participantService, propositionService, voteService, settingsService } from '@/lib/services';
import { MarkdownContent } from '@/components/MarkdownContent';
import { PROJECT_CONFIG } from '@/lib/project.config';
+import Footer from '@/components/Footer';
// Force dynamic rendering to avoid SSR issues with Supabase
export const dynamic = 'force-dynamic';
@@ -539,20 +540,7 @@ export default function PublicVotePage() {
)}
{/* Footer discret */}
-
-
- Développé avec ❤️ pour faciliter la démocratie participative -{' '}
-
- Logiciel libre
- {' '}
- et transparent pour tous
-
-
+
{/* Barre fixe en bas */}
diff --git a/src/app/globals.css b/src/app/globals.css
index 7519226..20cc568 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -255,24 +255,24 @@
.prose h1 {
font-size: 1.5rem;
font-weight: 700;
- margin-bottom: 1rem;
- margin-top: 1.5rem;
+ margin-bottom: 0;
+ margin-top: 0.75rem;
color: hsl(222.2 84% 4.9%);
}
.prose h2 {
font-size: 1.25rem;
font-weight: 600;
- margin-bottom: 0.75rem;
- margin-top: 1.25rem;
+ margin-bottom: 0;
+ margin-top: 0.5rem;
color: hsl(222.2 84% 4.9%);
}
.prose h3 {
font-size: 1.125rem;
font-weight: 500;
- margin-bottom: 0.5rem;
- margin-top: 1rem;
+ margin-bottom: 0;
+ margin-top: 0.375rem;
color: hsl(222.2 84% 4.9%);
}
@@ -280,22 +280,22 @@
.vote-page .prose h1 {
font-size: 1.125rem;
font-weight: 600;
- margin-bottom: 0.75rem;
- margin-top: 1rem;
+ margin-bottom: 0;
+ margin-top: 0.5rem;
}
.vote-page .prose h2 {
font-size: 1rem;
font-weight: 600;
- margin-bottom: 0.5rem;
- margin-top: 0.75rem;
+ margin-bottom: 0;
+ margin-top: 0.375rem;
}
.vote-page .prose h3 {
font-size: 0.875rem;
font-weight: 500;
- margin-bottom: 0.375rem;
- margin-top: 0.5rem;
+ margin-bottom: 0;
+ margin-top: 0.25rem;
}
.prose p {
@@ -334,7 +334,7 @@
}
.prose li {
- margin-bottom: 0.25rem;
+ margin-bottom: 0.25rem !important;
}
.prose a {
@@ -445,3 +445,9 @@
border-color: hsl(210 40% 98%);
color: hsl(210 40% 98%);
}
+
+/* Styles simples pour réduire l'espacement des listes */
+.prose ul li,
+.prose ol li {
+ margin-bottom: 0.25rem !important;
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 0ca8c91..7dd8166 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { PROJECT_CONFIG } from '@/lib/project.config';
+import Footer from '@/components/Footer';
export default function HomePage() {
return (
@@ -154,11 +155,7 @@ export default function HomePage() {
{/* Footer */}
-
-
- Développé avec ❤️ pour faciliter la démocratie participative
-
-
+
);
diff --git a/src/components/AddPropositionModal.tsx b/src/components/AddPropositionModal.tsx
index 132e8e8..2812621 100644
--- a/src/components/AddPropositionModal.tsx
+++ b/src/components/AddPropositionModal.tsx
@@ -78,7 +78,7 @@ export default function AddPropositionModal({ isOpen, onClose, onSuccess, campai
return (
- Description : {campaign.description}
+ Description :
diff --git a/src/components/EditPropositionModal.tsx b/src/components/EditPropositionModal.tsx
index ebb0b36..3c70a55 100644
--- a/src/components/EditPropositionModal.tsx
+++ b/src/components/EditPropositionModal.tsx
@@ -75,7 +75,7 @@ export default function EditPropositionModal({ isOpen, onClose, onSuccess, propo
return (