meilleure gestion des templates d'email (allégés)

This commit is contained in:
Yannick Le Duc
2025-09-16 16:47:14 +02:00
parent b20c88b05d
commit 17deb72834
6 changed files with 227 additions and 19 deletions

View File

@@ -0,0 +1,120 @@
import { parseFooterMessage } from '../../lib/utils';
import { PROJECT_CONFIG } from '../../lib/project.config';
describe('Footer Email Integration', () => {
describe('parseFooterMessage', () => {
it('should parse footer message with GITURL link', () => {
const footerMessage = 'Développé avec ❤️ pour faciliter la démocratie participative - [Logiciel libre et open source](GITURL)';
const repositoryUrl = PROJECT_CONFIG.repository.url;
const result = parseFooterMessage(footerMessage, repositoryUrl);
expect(result.text).toBe('Développé avec ❤️ pour faciliter la démocratie participative - Logiciel libre et open source');
expect(result.links).toHaveLength(1);
expect(result.links[0]).toMatchObject({
text: 'Logiciel libre et open source',
url: repositoryUrl
});
expect(result.links[0].start).toBeGreaterThan(0);
expect(result.links[0].end).toBeGreaterThan(result.links[0].start);
});
it('should handle footer message without links', () => {
const footerMessage = 'Simple footer message without links';
const repositoryUrl = PROJECT_CONFIG.repository.url;
const result = parseFooterMessage(footerMessage, repositoryUrl);
expect(result.text).toBe('Simple footer message without links');
expect(result.links).toHaveLength(0);
});
it('should handle multiple links in footer message', () => {
const footerMessage = 'Check our [docs](GITURL) and [code](GITURL)';
const repositoryUrl = PROJECT_CONFIG.repository.url;
const result = parseFooterMessage(footerMessage, repositoryUrl);
expect(result.text).toBe('Check our docs and code');
expect(result.links).toHaveLength(2);
expect(result.links[0].text).toBe('docs');
expect(result.links[1].text).toBe('code');
expect(result.links[0].url).toBe(repositoryUrl);
expect(result.links[1].url).toBe(repositoryUrl);
});
it('should handle empty footer message', () => {
const footerMessage = '';
const repositoryUrl = PROJECT_CONFIG.repository.url;
const result = parseFooterMessage(footerMessage, repositoryUrl);
expect(result.text).toBe('');
expect(result.links).toHaveLength(0);
});
});
describe('Footer message integration in emails', () => {
it('should generate correct footer text for email HTML', () => {
const footerMessage = 'Développé avec ❤️ pour faciliter la démocratie participative - [Logiciel libre et open source](GITURL)';
const repositoryUrl = PROJECT_CONFIG.repository.url;
const { text: processedFooterText, links } = parseFooterMessage(footerMessage, repositoryUrl);
// Vérifier que le texte traité peut être utilisé dans du HTML
expect(processedFooterText).toBe('Développé avec ❤️ pour faciliter la démocratie participative - Logiciel libre et open source');
expect(processedFooterText).not.toContain('[Logiciel libre et open source](GITURL)');
expect(processedFooterText).toContain('Logiciel libre et open source');
// Vérifier que les liens sont disponibles pour générer le HTML
expect(links).toHaveLength(1);
expect(links[0].text).toBe('Logiciel libre et open source');
expect(links[0].url).toBe(repositoryUrl);
});
it('should generate HTML with clickable links', () => {
const footerMessage = 'Développé avec ❤️ pour faciliter la démocratie participative - [Logiciel libre et open source](GITURL)';
const repositoryUrl = PROJECT_CONFIG.repository.url;
const { text: processedFooterText, links } = parseFooterMessage(footerMessage, repositoryUrl);
// Simuler la génération du HTML avec les liens
let footerHtml = processedFooterText;
if (links.length > 0) {
links.forEach(link => {
const linkHtml = `<a href="${link.url}" style="color: #6b7280; text-decoration: underline;" target="_blank" rel="noopener noreferrer">${link.text}</a>`;
footerHtml = footerHtml.replace(link.text, linkHtml);
});
}
// Vérifier que le HTML contient les liens cliquables
expect(footerHtml).toContain('<a href="' + repositoryUrl + '"');
expect(footerHtml).toContain('target="_blank"');
expect(footerHtml).toContain('rel="noopener noreferrer"');
expect(footerHtml).toContain('Logiciel libre et open source');
expect(footerHtml).not.toContain('[Logiciel libre et open source](GITURL)');
});
it('should handle special characters in footer message', () => {
const footerMessage = 'Footer with special chars: @#$%^&*() and [link](GITURL)';
const repositoryUrl = PROJECT_CONFIG.repository.url;
const { text: processedFooterText } = parseFooterMessage(footerMessage, repositoryUrl);
expect(processedFooterText).toBe('Footer with special chars: @#$%^&*() and link');
expect(processedFooterText).toContain('@#$%^&*()');
});
it('should handle personalized message placeholders', () => {
const message = 'Bonjour [PRENOM], votre nom est [NOM].';
const firstName = 'Jean';
const lastName = 'Dupont';
const personalizedMessage = message
.replace(/\[PRENOM\]/g, firstName)
.replace(/\[NOM\]/g, lastName);
expect(personalizedMessage).toBe('Bonjour Jean, votre nom est Dupont.');
});
});
});

View File

@@ -57,7 +57,7 @@ function SendEmailsPageContent() {
// Initialiser le message par défaut
if (campaignData) {
setDefaultSubject(`Votez pour la campagne "${campaignData.title}"`);
setDefaultMessage(`Bonjour,
setDefaultMessage(`Bonjour [PRENOM],
Vous êtes invité(e) à participer au vote pour la campagne "${campaignData.title}".

View File

@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
import * as nodemailer from 'nodemailer';
import { SmtpSettings } from '@/types';
import { settingsService } from '@/lib/services';
import { parseFooterMessage } from '@/lib/utils';
import { PROJECT_CONFIG } from '@/lib/project.config';
export async function POST(request: NextRequest) {
try {
@@ -59,19 +62,44 @@ export async function POST(request: NextRequest) {
// Vérifier la connexion
await transporter.verify();
// Récupérer le message du footer depuis les paramètres
let footerMessage = '';
try {
footerMessage = await settingsService.getStringValue(
'footer_message',
'Développé avec ❤️ pour faciliter la démocratie participative - [Logiciel libre et open source](GITURL)'
);
} catch (error) {
console.warn('Erreur lors de la récupération du message du footer:', error);
footerMessage = 'Développé avec ❤️ pour faciliter la démocratie participative - [Logiciel libre et open source](GITURL)';
}
// Traiter le message du footer pour remplacer les liens
const { text: processedFooterText, links } = parseFooterMessage(footerMessage, PROJECT_CONFIG.repository.url);
// Générer le HTML du footer avec les liens cliquables
let footerHtml = processedFooterText;
if (links.length > 0) {
// Remplacer les liens par des balises <a> HTML
links.forEach(link => {
const linkHtml = `<a href="${link.url}" style="color: #6b7280; text-decoration: underline;" target="_blank" rel="noopener noreferrer">${link.text}</a>`;
footerHtml = footerHtml.replace(link.text, linkHtml);
});
}
// Traiter le message pour remplacer les placeholders [NOM] et [PRENOM]
const firstName = toName.split(' ')[0];
const lastName = toName.split(' ').slice(1).join(' ');
let personalizedMessage = message
.replace(/\[PRENOM\]/g, firstName)
.replace(/\[NOM\]/g, lastName);
// Créer le contenu HTML de l'email
const htmlContent = `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; line-height: 1.6;">
<div style="background-color: #2563eb; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0;">
<h1 style="margin: 0; font-size: 24px;">Mes Budgets Participatifs</h1>
</div>
<div style="background-color: #ffffff; padding: 30px; border: 1px solid #e5e7eb; border-top: none;">
<h2 style="color: #1f2937; margin-top: 0;">Bonjour ${toName},</h2>
<div style="background-color: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0;">
<h3 style="margin-top: 0; color: #374151;">Campagne : ${campaignTitle}</h3>
<p style="margin-bottom: 0; color: #6b7280;">${message.replace(/\n/g, '<br>')}</p>
<div style="background-color: #ffffff; padding: 30px; border: 1px solid #e5e7eb; border-radius: 8px;">
<div style="color: #374151; font-size: 16px; margin-bottom: 30px;">
${personalizedMessage.replace(/\n/g, '<br>')}
</div>
<div style="text-align: center; margin: 30px 0;">
@@ -102,6 +130,10 @@ export async function POST(request: NextRequest) {
Cet email a été envoyé automatiquement par Mes Budgets Participatifs.<br>
Si vous avez des questions, contactez l'administrateur de la campagne.
</p>
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 15px 0;">
<p style="color: #9ca3af; font-size: 11px; margin: 0;">
${footerHtml}
</p>
</div>
</div>
`;

View File

@@ -38,7 +38,7 @@ export default function SendParticipantEmailModal({
useEffect(() => {
if (isOpen && campaign && participant) {
setSubject(`Votez pour la campagne "${campaign.title}"`);
setMessage(`Bonjour ${participant.first_name},
setMessage(`Bonjour [PRENOM],
Vous êtes invité(e) à participer au vote pour la campagne "${campaign.title}".