diff --git a/src/app/admin/campaigns/[id]/send-emails/page.tsx b/src/app/admin/campaigns/[id]/send-emails/page.tsx new file mode 100644 index 0000000..56b75ca --- /dev/null +++ b/src/app/admin/campaigns/[id]/send-emails/page.tsx @@ -0,0 +1,465 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { Campaign, Participant } from '@/types'; +import { campaignService, participantService, settingsService } from '@/lib/services'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Textarea } from '@/components/ui/textarea'; +import { Label } from '@/components/ui/label'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Progress } from '@/components/ui/progress'; +import { Badge } from '@/components/ui/badge'; +import { ArrowLeft, Mail, Send, CheckCircle, XCircle, Clock, Users } from 'lucide-react'; +import AuthGuard from '@/components/AuthGuard'; +import Footer from '@/components/Footer'; + +export const dynamic = 'force-dynamic'; + +interface EmailProgress { + participant: Participant; + status: 'pending' | 'sending' | 'sent' | 'error'; + error?: string; +} + +function SendEmailsPageContent() { + const params = useParams(); + const router = useRouter(); + const campaignId = params.id as string; + + const [campaign, setCampaign] = useState(null); + const [participants, setParticipants] = useState([]); + const [loading, setLoading] = useState(true); + const [sending, setSending] = useState(false); + const [emailProgress, setEmailProgress] = useState([]); + const [defaultSubject, setDefaultSubject] = useState(''); + const [defaultMessage, setDefaultMessage] = useState(''); + const [smtpConfigured, setSmtpConfigured] = useState(false); + + useEffect(() => { + loadData(); + }, [campaignId]); + + const loadData = async () => { + try { + setLoading(true); + const [campaignData, participantsData, smtpSettings] = await Promise.all([ + campaignService.getById(campaignId), + participantService.getByCampaign(campaignId), + settingsService.getSmtpSettings() + ]); + + setCampaign(campaignData); + setParticipants(participantsData); + setSmtpConfigured(!!(smtpSettings.host && smtpSettings.username && smtpSettings.password)); + + // Initialiser le message par défaut + if (campaignData) { + setDefaultSubject(`Votez pour la campagne "${campaignData.title}"`); + setDefaultMessage(`Bonjour, + +Vous êtes invité(e) à participer au vote pour la campagne "${campaignData.title}". + +${campaignData.description} + +Pour voter, cliquez sur le lien suivant : +[LIEN_DE_VOTE] + +Vous disposez d'un budget de ${campaignData.budget_per_user}€ à répartir entre les propositions selon vos préférences. + +Merci de votre participation ! + +Cordialement,`); + } + + // Initialiser le progrès des emails + setEmailProgress(participantsData.map(participant => ({ + participant, + status: 'pending' as const + }))); + } catch (error) { + console.error('Erreur lors du chargement des données:', error); + } finally { + setLoading(false); + } + }; + + const handleSendAllEmails = async () => { + if (!campaign || !defaultSubject.trim() || !defaultMessage.trim()) { + return; + } + + setSending(true); + + for (let i = 0; i < participants.length; i++) { + const participant = participants[i]; + + // Mettre à jour le statut à "sending" + setEmailProgress(prev => prev.map(p => + p.participant.id === participant.id + ? { ...p, status: 'sending' as const } + : p + )); + + try { + // Générer le lien de vote + const voteUrl = participant.short_id + ? `${window.location.origin}/v/${participant.short_id}` + : `${window.location.origin}/v/EN_ATTENTE`; + + // Remplacer le placeholder dans le message + const personalizedMessage = defaultMessage.replace('[LIEN_DE_VOTE]', voteUrl); + + // Récupérer les paramètres SMTP + const smtpSettings = await settingsService.getSmtpSettings(); + + if (!smtpSettings.host || !smtpSettings.username || !smtpSettings.password) { + throw new Error('Configuration SMTP manquante'); + } + + // Envoyer l'email via l'API + const response = await fetch('/api/send-participant-email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + smtpSettings, + toEmail: participant.email, + toName: `${participant.first_name} ${participant.last_name}`, + subject: defaultSubject.trim(), + message: personalizedMessage.trim(), + campaignTitle: campaign.title, + voteUrl + }), + }); + + const result = await response.json(); + + if (result.success) { + // Mettre à jour le statut à "sent" + setEmailProgress(prev => prev.map(p => + p.participant.id === participant.id + ? { ...p, status: 'sent' as const } + : p + )); + } else { + throw new Error(result.error || 'Erreur lors de l\'envoi'); + } + } catch (error) { + // Mettre à jour le statut à "error" + setEmailProgress(prev => prev.map(p => + p.participant.id === participant.id + ? { ...p, status: 'error' as const, error: error instanceof Error ? error.message : 'Erreur inconnue' } + : p + )); + } + + // Attendre 1 seconde avant l'email suivant + if (i < participants.length - 1) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + setSending(false); + }; + + const getStatusIcon = (status: EmailProgress['status']) => { + switch (status) { + case 'pending': + return ; + case 'sending': + return ; + case 'sent': + return ; + case 'error': + return ; + } + }; + + const getStatusBadge = (status: EmailProgress['status']) => { + switch (status) { + case 'pending': + return En attente; + case 'sending': + return En cours; + case 'sent': + return Envoyé; + case 'error': + return Erreur; + } + }; + + const sentCount = emailProgress.filter(p => p.status === 'sent').length; + const errorCount = emailProgress.filter(p => p.status === 'error').length; + const progressPercentage = participants.length > 0 ? (sentCount / participants.length) * 100 : 0; + + if (loading) { + return ( +
+
+
+
+
+

Chargement...

+
+
+
+
+ ); + } + + if (!campaign) { + return ( +
+
+
+

Campagne non trouvée

+ +
+
+
+ ); + } + + if (campaign.status !== 'voting') { + return ( +
+
+
+

+ Cette fonctionnalité n'est disponible que pour les campagnes en mode vote +

+

+ La campagne "{campaign.title}" est actuellement en mode "{campaign.status}". +

+ +
+
+
+ ); + } + + if (!smtpConfigured) { + return ( +
+
+
+
+

+ Configuration SMTP requise +

+

+ Vous devez configurer les paramètres SMTP avant de pouvoir envoyer des emails. +

+
+ + + + + + + Veuillez configurer les paramètres SMTP dans les paramètres de l'application avant de pouvoir envoyer des emails aux participants. + + + +
+ + +
+
+
+
+
+
+ ); + } + + return ( +
+
+
+ {/* Header */} +
+
+

+ Envoyer des emails aux participants +

+

+ Campagne : {campaign.title} +

+
+ +
+ + {/* Statistiques */} +
+ + +
+ +
+

Participants

+

{participants.length}

+
+
+
+
+ + + +
+ +
+

Envoyés

+

{sentCount}

+
+
+
+
+ + + +
+ +
+

Erreurs

+

{errorCount}

+
+
+
+
+
+ + {/* Configuration de l'email */} + + + Configuration de l'email + + Personnalisez le message qui sera envoyé à tous les participants. Utilisez [LIEN_DE_VOTE] pour insérer automatiquement le lien de vote de chaque participant. + + + +
+ + setDefaultSubject(e.target.value)} + className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100" + placeholder="Sujet de l'email" + /> +
+ +
+ +