diff --git a/src/app/admin/campaigns/[id]/propositions/page.tsx b/src/app/admin/campaigns/[id]/propositions/page.tsx
index edc9fe8..67f8975 100644
--- a/src/app/admin/campaigns/[id]/propositions/page.tsx
+++ b/src/app/admin/campaigns/[id]/propositions/page.tsx
@@ -170,9 +170,17 @@ export default function CampaignPropositionsPage() {
{proposition.description}
-
- Créée le {new Date(proposition.created_at).toLocaleDateString('fr-FR')}
-
+
+
+ Auteur : {proposition.author_first_name} {proposition.author_last_name}
+
+
+ Email : {proposition.author_email}
+
+
+ Créée le : {new Date(proposition.created_at).toLocaleDateString('fr-FR')}
+
+
diff --git a/src/app/campaigns/[id]/propose/page.tsx b/src/app/campaigns/[id]/propose/page.tsx
new file mode 100644
index 0000000..b77432d
--- /dev/null
+++ b/src/app/campaigns/[id]/propose/page.tsx
@@ -0,0 +1,331 @@
+'use client';
+
+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';
+
+// Force dynamic rendering to avoid SSR issues with Supabase
+export const dynamic = 'force-dynamic';
+
+export default function PublicProposePage() {
+ const params = useParams();
+ const campaignId = params.id as string;
+
+ const [campaign, setCampaign] = useState
(null);
+ const [loading, setLoading] = useState(true);
+ const [submitting, setSubmitting] = useState(false);
+ const [error, setError] = useState('');
+ const [success, setSuccess] = useState(false);
+
+ const [formData, setFormData] = useState({
+ title: '',
+ description: '',
+ author_first_name: '',
+ author_last_name: '',
+ author_email: ''
+ });
+
+ useEffect(() => {
+ if (campaignId) {
+ loadCampaign();
+ }
+ }, [campaignId]);
+
+ const loadCampaign = async () => {
+ try {
+ setLoading(true);
+ const campaigns = await campaignService.getAll();
+ const campaignData = campaigns.find(c => c.id === campaignId);
+
+ if (!campaignData) {
+ setError('Campagne non trouvée');
+ return;
+ }
+
+ if (campaignData.status !== 'deposit') {
+ setError('Cette campagne n\'accepte plus de propositions');
+ return;
+ }
+
+ setCampaign(campaignData);
+ } catch (error) {
+ console.error('Erreur lors du chargement de la campagne:', error);
+ setError('Erreur lors du chargement de la campagne');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setSubmitting(true);
+ setError('');
+
+ try {
+ // Créer la proposition avec les informations de l'auteur
+ await propositionService.create({
+ campaign_id: campaignId,
+ title: formData.title,
+ description: formData.description,
+ author_first_name: formData.author_first_name,
+ author_last_name: formData.author_last_name,
+ author_email: formData.author_email
+ });
+
+ setSuccess(true);
+ setFormData({
+ title: '',
+ description: '',
+ author_first_name: '',
+ author_last_name: '',
+ author_email: ''
+ });
+ } catch (err: any) {
+ const errorMessage = err?.message || err?.details || 'Erreur lors de la soumission de la proposition';
+ setError(`Erreur lors de la soumission de la proposition: ${errorMessage}`);
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: value
+ }));
+ };
+
+ if (loading) {
+ return (
+
+
+
+
Chargement de la campagne...
+
+
+ );
+ }
+
+ if (error && !campaign) {
+ return (
+
+
+
+
+
Erreur
+
{error}
+
+ Retour à l'accueil
+
+
+
+
+ );
+ }
+
+ if (success) {
+ return (
+
+
+
+
+
Proposition soumise !
+
+ Votre proposition a été soumise avec succès. Merci pour votre participation !
+
+
+ setSuccess(false)}
+ className="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700"
+ >
+ Soumettre une autre proposition
+
+
+ Retour à l'accueil
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+ Retour à l'accueil
+
+
Déposer une proposition
+
+ Campagne : {campaign?.title}
+
+
+
+
+
+ {/* Campaign Info */}
+
+
Informations sur la campagne
+
+
+
Description
+
{campaign?.description}
+
+
+
Budget par participant
+
{campaign?.budget_per_user}€
+
+
+
Paliers de dépenses
+
{campaign?.spending_tiers}
+
+
+
Statut
+
+ Dépôt de propositions
+
+
+
+
+
+ {/* Form */}
+
+
+
Votre proposition
+
+ Remplissez le formulaire ci-dessous pour soumettre votre proposition.
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/AddPropositionModal.tsx b/src/components/AddPropositionModal.tsx
index 1dbe36f..37e8a72 100644
--- a/src/components/AddPropositionModal.tsx
+++ b/src/components/AddPropositionModal.tsx
@@ -22,7 +22,10 @@ export default function AddPropositionModal({
}: AddPropositionModalProps) {
const [formData, setFormData] = useState({
title: '',
- description: ''
+ description: '',
+ author_first_name: '',
+ author_last_name: '',
+ author_email: ''
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
@@ -36,13 +39,19 @@ export default function AddPropositionModal({
await propositionService.create({
campaign_id: campaignId,
title: formData.title,
- description: formData.description
+ description: formData.description,
+ author_first_name: formData.author_first_name,
+ author_last_name: formData.author_last_name,
+ author_email: formData.author_email
});
onSuccess();
// Reset form
setFormData({
title: '',
- description: ''
+ description: '',
+ author_first_name: '',
+ author_last_name: '',
+ author_email: ''
});
} catch (err) {
setError('Erreur lors de l\'ajout de la proposition');
@@ -123,6 +132,60 @@ export default function AddPropositionModal({
/>
+
+
Informations de l'auteur
+
+
+
+
+
+
+
+
+
+
+
Informations de l'auteur
+
+
+
+
+
+
+
+
+