diff --git a/src/app/campaigns/[id]/vote/[participantId]/page.tsx b/src/app/campaigns/[id]/vote/[participantId]/page.tsx
index e0b0343..3768f43 100644
--- a/src/app/campaigns/[id]/vote/[participantId]/page.tsx
+++ b/src/app/campaigns/[id]/vote/[participantId]/page.tsx
@@ -37,6 +37,31 @@ export default function PublicVotePage() {
}
}, [campaignId, participantId]);
+ // Écouter les changements de connectivité réseau
+ useEffect(() => {
+ const handleOnline = () => {
+ console.log('Connexion réseau rétablie');
+ setError('');
+ };
+
+ const handleOffline = () => {
+ console.log('Connexion réseau perdue');
+ setError('Connexion réseau perdue. Veuillez vérifier votre connexion internet.');
+ };
+
+ if (typeof window !== 'undefined') {
+ window.addEventListener('online', handleOnline);
+ window.addEventListener('offline', handleOffline);
+ }
+
+ return () => {
+ if (typeof window !== 'undefined') {
+ window.removeEventListener('online', handleOnline);
+ window.removeEventListener('offline', handleOffline);
+ }
+ };
+ }, []);
+
// Calculer le total voté à partir des votes locaux
useEffect(() => {
const total = Object.values(localVotes).reduce((sum, amount) => sum + amount, 0);
@@ -91,6 +116,13 @@ export default function PublicVotePage() {
const loadData = async () => {
try {
setLoading(true);
+ setError('');
+
+ // Vérifier la connectivité réseau
+ if (typeof window !== 'undefined' && !navigator.onLine) {
+ throw new Error('Pas de connexion internet. Veuillez vérifier votre connexion réseau.');
+ }
+
const [campaigns, participants, propositionsData] = await Promise.all([
campaignService.getAll(),
participantService.getByCampaign(campaignId),
@@ -147,7 +179,23 @@ export default function PublicVotePage() {
} catch (error) {
console.error('Erreur lors du chargement des données:', error);
- setError('Erreur lors du chargement des données');
+ let errorMessage = 'Erreur lors du chargement des données';
+
+ if (error instanceof Error) {
+ errorMessage = error.message;
+ } else if (typeof error === 'object' && error !== null) {
+ // Essayer d'extraire plus d'informations de l'erreur
+ const errorObj = error as any;
+ if (errorObj.message) {
+ errorMessage = errorObj.message;
+ } else if (errorObj.error) {
+ errorMessage = errorObj.error;
+ } else if (errorObj.details) {
+ errorMessage = errorObj.details;
+ }
+ }
+
+ setError(errorMessage);
} finally {
setLoading(false);
}
@@ -178,28 +226,39 @@ export default function PublicVotePage() {
setError('');
try {
- // Supprimer tous les votes existants pour ce participant
- const existingVotes = await voteService.getByParticipant(campaignId, participantId);
- for (const vote of existingVotes) {
- await voteService.delete(vote.id);
- }
+ // Préparer les votes à sauvegarder (seulement ceux avec amount > 0)
+ const votesToSave = Object.entries(localVotes)
+ .filter(([_, amount]) => amount > 0)
+ .map(([propositionId, amount]) => ({
+ proposition_id: propositionId,
+ amount
+ }));
- // Créer les nouveaux votes
- for (const [propositionId, amount] of Object.entries(localVotes)) {
- if (amount > 0) {
- await voteService.create({
- campaign_id: campaignId,
- participant_id: participantId,
- proposition_id: propositionId,
- amount
- });
- }
- }
+ // Utiliser la méthode atomique pour remplacer tous les votes
+ await voteService.replaceVotes(campaignId, participantId, votesToSave);
setSuccess(true);
} catch (error) {
console.error('Erreur lors de la validation:', error);
- setError('Erreur lors de la validation des votes');
+
+ // Améliorer l'affichage de l'erreur
+ let errorMessage = 'Erreur lors de la validation des votes';
+
+ if (error instanceof Error) {
+ errorMessage = error.message;
+ } else if (typeof error === 'object' && error !== null) {
+ // Essayer d'extraire plus d'informations de l'erreur
+ const errorObj = error as any;
+ if (errorObj.message) {
+ errorMessage = errorObj.message;
+ } else if (errorObj.error) {
+ errorMessage = errorObj.error;
+ } else if (errorObj.details) {
+ errorMessage = errorObj.details;
+ }
+ }
+
+ setError(errorMessage);
} finally {
setSaving(false);
}
diff --git a/src/lib/services.ts b/src/lib/services.ts
index 511f376..5b62b64 100644
--- a/src/lib/services.ts
+++ b/src/lib/services.ts
@@ -36,6 +36,36 @@ function generateShortIdClient(): string {
return `${result}${timestamp}`;
}
+// Fonction utilitaire pour gérer les erreurs Supabase
+function handleSupabaseError(error: any, operation: string): never {
+ console.error(`Erreur Supabase lors de ${operation}:`, error);
+
+ // Extraire les détails de l'erreur
+ let errorMessage = `Erreur lors de ${operation}`;
+
+ if (error?.message) {
+ errorMessage = error.message;
+ } else if (error?.error_description) {
+ errorMessage = error.error_description;
+ } else if (error?.details) {
+ errorMessage = error.details;
+ } else if (typeof error === 'string') {
+ errorMessage = error;
+ }
+
+ // Ajouter des informations de débogage
+ const debugInfo = {
+ operation,
+ error,
+ timestamp: new Date().toISOString(),
+ userAgent: typeof window !== 'undefined' ? window.navigator.userAgent : 'server'
+ };
+
+ console.error('Informations de débogage:', debugInfo);
+
+ throw new Error(errorMessage);
+}
+
// Services pour les campagnes
export const campaignService = {
async getAll(): Promise {
@@ -349,7 +379,7 @@ export const voteService = {
.eq('campaign_id', campaignId)
.eq('participant_id', participantId);
- if (error) throw error;
+ if (error) handleSupabaseError(error, 'récupération des votes par participant');
return data || [];
},
@@ -359,7 +389,7 @@ export const voteService = {
.select('*')
.eq('proposition_id', propositionId);
- if (error) throw error;
+ if (error) handleSupabaseError(error, 'récupération des votes par proposition');
return data || [];
},
@@ -371,7 +401,7 @@ export const voteService = {
.select()
.single();
- if (error) throw error;
+ if (error) handleSupabaseError(error, 'création de vote');
return data;
},
@@ -384,7 +414,7 @@ export const voteService = {
.select()
.single();
- if (error) throw error;
+ if (error) handleSupabaseError(error, 'mise à jour de vote');
return data;
},
@@ -395,7 +425,7 @@ export const voteService = {
.select()
.single();
- if (error) throw error;
+ if (error) handleSupabaseError(error, 'upsert de vote');
return data;
},
@@ -405,7 +435,7 @@ export const voteService = {
.delete()
.eq('id', id);
- if (error) throw error;
+ if (error) handleSupabaseError(error, 'suppression de vote');
},
async getByCampaign(campaignId: string): Promise {
@@ -414,7 +444,7 @@ export const voteService = {
.select('*')
.eq('campaign_id', campaignId);
- if (error) throw error;
+ if (error) handleSupabaseError(error, 'récupération des votes par campagne');
return data || [];
},
@@ -443,6 +473,22 @@ export const voteService = {
total_voted_amount: totalVotedAmount
};
});
+ },
+
+ // Méthode pour remplacer tous les votes d'un participant de manière atomique
+ async replaceVotes(
+ campaignId: string,
+ participantId: string,
+ votes: Array<{ proposition_id: string; amount: number }>
+ ): Promise {
+ // Utiliser une transaction pour garantir l'atomicité
+ const { error } = await supabase.rpc('replace_participant_votes', {
+ p_campaign_id: campaignId,
+ p_participant_id: participantId,
+ p_votes: votes
+ });
+
+ if (error) handleSupabaseError(error, 'remplacement des votes du participant');
}
};
diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts
index d7f60c6..b8f16c6 100644
--- a/src/lib/supabase.ts
+++ b/src/lib/supabase.ts
@@ -3,4 +3,20 @@ import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://placeholder.supabase.co';
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'placeholder-key';
-export const supabase = createClient(supabaseUrl, supabaseAnonKey);
+export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
+ auth: {
+ autoRefreshToken: true,
+ persistSession: true,
+ detectSessionInUrl: true
+ },
+ realtime: {
+ params: {
+ eventsPerSecond: 10
+ }
+ },
+ global: {
+ headers: {
+ 'X-Client-Info': 'mes-budgets-participatifs'
+ }
+ }
+});