first commit

This commit is contained in:
Michaël
2026-01-29 14:13:11 -03:00
commit 2302748c87
105 changed files with 93301 additions and 0 deletions

View File

@@ -0,0 +1,675 @@
// Email templates for Spanglish platform
// These are the default templates that get seeded into the database
export interface EmailVariable {
name: string;
description: string;
example: string;
}
export interface DefaultTemplate {
name: string;
slug: string;
subject: string;
subjectEs: string;
bodyHtml: string;
bodyHtmlEs: string;
bodyText: string;
bodyTextEs: string;
description: string;
variables: EmailVariable[];
isSystem: boolean;
}
// Common variables available in all templates
export const commonVariables: EmailVariable[] = [
{ name: 'siteName', description: 'Website name', example: 'Spanglish' },
{ name: 'siteUrl', description: 'Website URL', example: 'https://spanglish.com' },
{ name: 'currentYear', description: 'Current year', example: '2026' },
{ name: 'supportEmail', description: 'Support email address', example: 'hello@spanglish.com' },
];
// Booking-specific variables
export const bookingVariables: EmailVariable[] = [
{ name: 'attendeeName', description: 'Attendee full name', example: 'John Doe' },
{ name: 'attendeeEmail', description: 'Attendee email', example: 'john@example.com' },
{ name: 'ticketId', description: 'Unique ticket ID', example: 'TKT-ABC123' },
{ name: 'qrCode', description: 'QR code for check-in', example: 'data:image/png;base64,...' },
{ name: 'eventTitle', description: 'Event title', example: 'Spanglish Night - January Edition' },
{ name: 'eventDate', description: 'Event date formatted', example: 'January 28, 2026' },
{ name: 'eventTime', description: 'Event time', example: '7:00 PM' },
{ name: 'eventLocation', description: 'Event location', example: 'Casa Cultural, Asunción' },
{ name: 'eventLocationUrl', description: 'Google Maps link', example: 'https://maps.google.com/...' },
{ name: 'eventPrice', description: 'Event price with currency', example: '50,000 PYG' },
];
// Payment-specific variables
export const paymentVariables: EmailVariable[] = [
{ name: 'paymentAmount', description: 'Payment amount with currency', example: '50,000 PYG' },
{ name: 'paymentMethod', description: 'Payment method used', example: 'Lightning' },
{ name: 'paymentReference', description: 'Payment reference ID', example: 'PAY-XYZ789' },
{ name: 'paymentDate', description: 'Payment date', example: 'January 28, 2026' },
];
// Base HTML wrapper for all emails
export const baseEmailWrapper = `
<!DOCTYPE html>
<html lang="{{lang}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{subject}}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
}
.header {
background-color: #1a1a1a;
padding: 24px;
text-align: center;
}
.header h1 {
margin: 0;
color: #fff;
font-size: 24px;
}
.header h1 span {
color: #f4d03f;
}
.content {
padding: 32px 24px;
}
.content h2 {
color: #1a1a1a;
margin-top: 0;
}
.event-card {
background-color: #f9f9f9;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.event-card h3 {
margin-top: 0;
color: #1a1a1a;
}
.event-detail {
display: flex;
margin: 8px 0;
}
.event-detail strong {
min-width: 80px;
color: #666;
}
.ticket-box {
background-color: #f4d03f;
border-radius: 8px;
padding: 16px;
text-align: center;
margin: 20px 0;
}
.ticket-box p {
margin: 4px 0;
font-weight: 600;
color: #1a1a1a;
}
.ticket-id {
font-size: 20px;
font-family: monospace;
letter-spacing: 2px;
}
.btn {
display: inline-block;
background-color: #f4d03f;
color: #1a1a1a;
text-decoration: none;
padding: 12px 24px;
border-radius: 6px;
font-weight: 600;
margin: 16px 0;
}
.btn:hover {
background-color: #e6c230;
}
.footer {
background-color: #f5f5f5;
padding: 24px;
text-align: center;
font-size: 14px;
color: #666;
}
.footer a {
color: #333;
}
.qr-code {
text-align: center;
margin: 20px 0;
}
.qr-code img {
max-width: 150px;
height: auto;
}
.divider {
height: 1px;
background-color: #eee;
margin: 24px 0;
}
.note {
background-color: #fff9e6;
border-left: 4px solid #f4d03f;
padding: 12px 16px;
margin: 16px 0;
font-size: 14px;
}
@media (max-width: 600px) {
.content {
padding: 24px 16px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Span<span>glish</span></h1>
</div>
<div class="content">
{{content}}
</div>
<div class="footer">
<p>{{siteName}} - Language Exchange Community in Asunción</p>
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
<p>Questions? Contact us at <a href="mailto:{{supportEmail}}">{{supportEmail}}</a></p>
<p>&copy; {{currentYear}} {{siteName}}. All rights reserved.</p>
</div>
</div>
</body>
</html>
`;
// Default templates
export const defaultTemplates: DefaultTemplate[] = [
{
name: 'Booking Confirmation',
slug: 'booking-confirmation',
subject: 'Your Spanglish ticket is confirmed 🎉',
subjectEs: 'Tu entrada de Spanglish está confirmada 🎉',
bodyHtml: `
<h2>Your Booking is Confirmed!</h2>
<p>Hi {{attendeeName}},</p>
<p>Great news! Your spot for <strong>{{eventTitle}}</strong> has been confirmed. We can't wait to see you there!</p>
<div class="event-card">
<h3>📅 Event Details</h3>
<div class="event-detail"><strong>Event:</strong> {{eventTitle}}</div>
<div class="event-detail"><strong>Date:</strong> {{eventDate}}</div>
<div class="event-detail"><strong>Time:</strong> {{eventTime}}</div>
<div class="event-detail"><strong>Location:</strong> {{eventLocation}}</div>
{{#if eventLocationUrl}}
<p><a href="{{eventLocationUrl}}" class="btn">📍 View on Map</a></p>
{{/if}}
</div>
<div class="ticket-box">
<p>Your Ticket ID</p>
<p class="ticket-id">{{ticketId}}</p>
</div>
{{#if qrCode}}
<div class="qr-code">
<p><strong>Show this QR code at check-in:</strong></p>
<img src="{{qrCode}}" alt="Check-in QR Code" />
</div>
{{/if}}
<div class="note">
<strong>💡 Important:</strong> Please arrive 10-15 minutes early for check-in. Bring your ticket ID or show this email.
</div>
<p>See you at Spanglish!</p>
<p>The Spanglish Team</p>
`,
bodyHtmlEs: `
<h2>¡Tu Reserva está Confirmada!</h2>
<p>Hola {{attendeeName}},</p>
<p>¡Excelentes noticias! Tu lugar para <strong>{{eventTitle}}</strong> ha sido confirmado. ¡No podemos esperar a verte ahí!</p>
<div class="event-card">
<h3>📅 Detalles del Evento</h3>
<div class="event-detail"><strong>Evento:</strong> {{eventTitle}}</div>
<div class="event-detail"><strong>Fecha:</strong> {{eventDate}}</div>
<div class="event-detail"><strong>Hora:</strong> {{eventTime}}</div>
<div class="event-detail"><strong>Ubicación:</strong> {{eventLocation}}</div>
{{#if eventLocationUrl}}
<p><a href="{{eventLocationUrl}}" class="btn">📍 Ver en el Mapa</a></p>
{{/if}}
</div>
<div class="ticket-box">
<p>Tu ID de Ticket</p>
<p class="ticket-id">{{ticketId}}</p>
</div>
{{#if qrCode}}
<div class="qr-code">
<p><strong>Muestra este código QR en el check-in:</strong></p>
<img src="{{qrCode}}" alt="Código QR de Check-in" />
</div>
{{/if}}
<div class="note">
<strong>💡 Importante:</strong> Por favor llega 10-15 minutos antes para el check-in. Trae tu ID de ticket o muestra este email.
</div>
<p>¡Nos vemos en Spanglish!</p>
<p>El Equipo de Spanglish</p>
`,
bodyText: `Your Booking is Confirmed!
Hi {{attendeeName}},
Great news! Your spot for {{eventTitle}} has been confirmed.
Event Details:
- Event: {{eventTitle}}
- Date: {{eventDate}}
- Time: {{eventTime}}
- Location: {{eventLocation}}
Your Ticket ID: {{ticketId}}
Important: Please arrive 10-15 minutes early for check-in. Bring your ticket ID or show this email.
See you at Spanglish!
The Spanglish Team`,
bodyTextEs: `¡Tu Reserva está Confirmada!
Hola {{attendeeName}},
¡Excelentes noticias! Tu lugar para {{eventTitle}} ha sido confirmado.
Detalles del Evento:
- Evento: {{eventTitle}}
- Fecha: {{eventDate}}
- Hora: {{eventTime}}
- Ubicación: {{eventLocation}}
Tu ID de Ticket: {{ticketId}}
Importante: Por favor llega 10-15 minutos antes para el check-in. Trae tu ID de ticket o muestra este email.
¡Nos vemos en Spanglish!
El Equipo de Spanglish`,
description: 'Sent automatically when a booking is confirmed after payment',
variables: [...commonVariables, ...bookingVariables],
isSystem: true,
},
{
name: 'Payment Receipt',
slug: 'payment-receipt',
subject: 'Payment Receipt - Spanglish',
subjectEs: 'Recibo de Pago - Spanglish',
bodyHtml: `
<h2>Payment Received</h2>
<p>Hi {{attendeeName}},</p>
<p>Thank you for your payment! Here's your receipt for your records.</p>
<div class="event-card">
<h3>💳 Payment Details</h3>
<div class="event-detail"><strong>Amount:</strong> {{paymentAmount}}</div>
<div class="event-detail"><strong>Method:</strong> {{paymentMethod}}</div>
<div class="event-detail"><strong>Reference:</strong> {{paymentReference}}</div>
<div class="event-detail"><strong>Date:</strong> {{paymentDate}}</div>
</div>
<div class="divider"></div>
<div class="event-card">
<h3>📅 Event</h3>
<div class="event-detail"><strong>Event:</strong> {{eventTitle}}</div>
<div class="event-detail"><strong>Date:</strong> {{eventDate}}</div>
<div class="event-detail"><strong>Ticket ID:</strong> {{ticketId}}</div>
</div>
<p>Keep this email as your payment confirmation.</p>
<p>The Spanglish Team</p>
`,
bodyHtmlEs: `
<h2>Pago Recibido</h2>
<p>Hola {{attendeeName}},</p>
<p>¡Gracias por tu pago! Aquí está tu recibo para tus registros.</p>
<div class="event-card">
<h3>💳 Detalles del Pago</h3>
<div class="event-detail"><strong>Monto:</strong> {{paymentAmount}}</div>
<div class="event-detail"><strong>Método:</strong> {{paymentMethod}}</div>
<div class="event-detail"><strong>Referencia:</strong> {{paymentReference}}</div>
<div class="event-detail"><strong>Fecha:</strong> {{paymentDate}}</div>
</div>
<div class="divider"></div>
<div class="event-card">
<h3>📅 Evento</h3>
<div class="event-detail"><strong>Evento:</strong> {{eventTitle}}</div>
<div class="event-detail"><strong>Fecha:</strong> {{eventDate}}</div>
<div class="event-detail"><strong>ID de Ticket:</strong> {{ticketId}}</div>
</div>
<p>Guarda este email como tu confirmación de pago.</p>
<p>El Equipo de Spanglish</p>
`,
bodyText: `Payment Received
Hi {{attendeeName}},
Thank you for your payment! Here's your receipt:
Payment Details:
- Amount: {{paymentAmount}}
- Method: {{paymentMethod}}
- Reference: {{paymentReference}}
- Date: {{paymentDate}}
Event: {{eventTitle}}
Date: {{eventDate}}
Ticket ID: {{ticketId}}
Keep this email as your payment confirmation.
The Spanglish Team`,
bodyTextEs: `Pago Recibido
Hola {{attendeeName}},
¡Gracias por tu pago! Aquí está tu recibo:
Detalles del Pago:
- Monto: {{paymentAmount}}
- Método: {{paymentMethod}}
- Referencia: {{paymentReference}}
- Fecha: {{paymentDate}}
Evento: {{eventTitle}}
Fecha: {{eventDate}}
ID de Ticket: {{ticketId}}
Guarda este email como tu confirmación de pago.
El Equipo de Spanglish`,
description: 'Sent automatically after payment is processed',
variables: [...commonVariables, ...bookingVariables, ...paymentVariables],
isSystem: true,
},
{
name: 'Event Update',
slug: 'event-update',
subject: 'Important Update: {{eventTitle}}',
subjectEs: 'Actualización Importante: {{eventTitle}}',
bodyHtml: `
<h2>Important Event Update</h2>
<p>Hi {{attendeeName}},</p>
<p>We have an important update regarding <strong>{{eventTitle}}</strong>.</p>
<div class="event-card">
<h3>📢 Message</h3>
<p>{{customMessage}}</p>
</div>
<div class="event-card">
<h3>📅 Event Details</h3>
<div class="event-detail"><strong>Event:</strong> {{eventTitle}}</div>
<div class="event-detail"><strong>Date:</strong> {{eventDate}}</div>
<div class="event-detail"><strong>Time:</strong> {{eventTime}}</div>
<div class="event-detail"><strong>Location:</strong> {{eventLocation}}</div>
</div>
<p>If you have any questions, please don't hesitate to contact us.</p>
<p>The Spanglish Team</p>
`,
bodyHtmlEs: `
<h2>Actualización Importante del Evento</h2>
<p>Hola {{attendeeName}},</p>
<p>Tenemos una actualización importante sobre <strong>{{eventTitle}}</strong>.</p>
<div class="event-card">
<h3>📢 Mensaje</h3>
<p>{{customMessage}}</p>
</div>
<div class="event-card">
<h3>📅 Detalles del Evento</h3>
<div class="event-detail"><strong>Evento:</strong> {{eventTitle}}</div>
<div class="event-detail"><strong>Fecha:</strong> {{eventDate}}</div>
<div class="event-detail"><strong>Hora:</strong> {{eventTime}}</div>
<div class="event-detail"><strong>Ubicación:</strong> {{eventLocation}}</div>
</div>
<p>Si tienes alguna pregunta, no dudes en contactarnos.</p>
<p>El Equipo de Spanglish</p>
`,
bodyText: `Important Event Update
Hi {{attendeeName}},
We have an important update regarding {{eventTitle}}.
Message:
{{customMessage}}
Event Details:
- Event: {{eventTitle}}
- Date: {{eventDate}}
- Time: {{eventTime}}
- Location: {{eventLocation}}
If you have any questions, please don't hesitate to contact us.
The Spanglish Team`,
bodyTextEs: `Actualización Importante del Evento
Hola {{attendeeName}},
Tenemos una actualización importante sobre {{eventTitle}}.
Mensaje:
{{customMessage}}
Detalles del Evento:
- Evento: {{eventTitle}}
- Fecha: {{eventDate}}
- Hora: {{eventTime}}
- Ubicación: {{eventLocation}}
Si tienes alguna pregunta, no dudes en contactarnos.
El Equipo de Spanglish`,
description: 'Template for sending event updates to attendees (sent manually)',
variables: [
...commonVariables,
...bookingVariables,
{ name: 'customMessage', description: 'Custom message from admin', example: 'The venue has changed...' }
],
isSystem: true,
},
{
name: 'Post-Event Follow-Up',
slug: 'post-event-followup',
subject: 'Thanks for joining {{eventTitle}}! 🙏',
subjectEs: '¡Gracias por asistir a {{eventTitle}}! 🙏',
bodyHtml: `
<h2>Thank You for Joining Us!</h2>
<p>Hi {{attendeeName}},</p>
<p>Thank you so much for being part of <strong>{{eventTitle}}</strong>! We hope you had a great time practicing languages and meeting new people.</p>
<div class="event-card">
<h3>💬 Share Your Experience</h3>
<p>{{customMessage}}</p>
</div>
{{#if nextEventTitle}}
<div class="event-card">
<h3>📅 Next Event</h3>
<div class="event-detail"><strong>Event:</strong> {{nextEventTitle}}</div>
<div class="event-detail"><strong>Date:</strong> {{nextEventDate}}</div>
<p style="text-align: center; margin-top: 16px;">
<a href="{{nextEventUrl}}" class="btn">Reserve Your Spot</a>
</p>
</div>
{{/if}}
<p>Follow us on social media for updates and photos from the event!</p>
<p>See you at the next Spanglish!</p>
<p>The Spanglish Team</p>
`,
bodyHtmlEs: `
<h2>¡Gracias por Unirte!</h2>
<p>Hola {{attendeeName}},</p>
<p>¡Muchas gracias por ser parte de <strong>{{eventTitle}}</strong>! Esperamos que hayas pasado un gran momento practicando idiomas y conociendo gente nueva.</p>
<div class="event-card">
<h3>💬 Comparte tu Experiencia</h3>
<p>{{customMessage}}</p>
</div>
{{#if nextEventTitle}}
<div class="event-card">
<h3>📅 Próximo Evento</h3>
<div class="event-detail"><strong>Evento:</strong> {{nextEventTitle}}</div>
<div class="event-detail"><strong>Fecha:</strong> {{nextEventDate}}</div>
<p style="text-align: center; margin-top: 16px;">
<a href="{{nextEventUrl}}" class="btn">Reserva tu Lugar</a>
</p>
</div>
{{/if}}
<p>¡Síguenos en redes sociales para actualizaciones y fotos del evento!</p>
<p>¡Nos vemos en el próximo Spanglish!</p>
<p>El Equipo de Spanglish</p>
`,
bodyText: `Thank You for Joining Us!
Hi {{attendeeName}},
Thank you so much for being part of {{eventTitle}}! We hope you had a great time.
{{customMessage}}
Follow us on social media for updates and photos from the event!
See you at the next Spanglish!
The Spanglish Team`,
bodyTextEs: `¡Gracias por Unirte!
Hola {{attendeeName}},
¡Muchas gracias por ser parte de {{eventTitle}}! Esperamos que hayas pasado un gran momento.
{{customMessage}}
¡Síguenos en redes sociales para actualizaciones y fotos del evento!
¡Nos vemos en el próximo Spanglish!
El Equipo de Spanglish`,
description: 'Template for post-event follow-up emails (sent manually)',
variables: [
...commonVariables,
...bookingVariables,
{ name: 'customMessage', description: 'Custom message from admin', example: 'We would love to hear your feedback!' },
{ name: 'nextEventTitle', description: 'Next event title (optional)', example: 'Spanglish Night - February' },
{ name: 'nextEventDate', description: 'Next event date (optional)', example: 'February 25, 2026' },
{ name: 'nextEventUrl', description: 'Next event booking URL (optional)', example: 'https://spanglish.com/book/...' },
],
isSystem: true,
},
{
name: 'Custom Email',
slug: 'custom-email',
subject: '{{customSubject}}',
subjectEs: '{{customSubject}}',
bodyHtml: `
<h2>{{customTitle}}</h2>
<p>Hi {{attendeeName}},</p>
<div class="event-card">
{{customMessage}}
</div>
<p>The Spanglish Team</p>
`,
bodyHtmlEs: `
<h2>{{customTitle}}</h2>
<p>Hola {{attendeeName}},</p>
<div class="event-card">
{{customMessage}}
</div>
<p>El Equipo de Spanglish</p>
`,
bodyText: `{{customTitle}}
Hi {{attendeeName}},
{{customMessage}}
The Spanglish Team`,
bodyTextEs: `{{customTitle}}
Hola {{attendeeName}},
{{customMessage}}
El Equipo de Spanglish`,
description: 'Blank template for fully custom emails',
variables: [
...commonVariables,
{ name: 'attendeeName', description: 'Recipient name', example: 'John Doe' },
{ name: 'customSubject', description: 'Email subject', example: 'Special Announcement' },
{ name: 'customTitle', description: 'Email title/heading', example: 'Special Announcement' },
{ name: 'customMessage', description: 'Email body content (supports HTML)', example: '<p>Your message here...</p>' },
],
isSystem: true,
},
];
// Helper function to replace template variables
export function replaceTemplateVariables(template: string, variables: Record<string, any>): string {
let result = template;
// Handle conditional blocks {{#if variable}}...{{/if}}
const conditionalRegex = /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
result = result.replace(conditionalRegex, (match, varName, content) => {
return variables[varName] ? content : '';
});
// Replace simple variables {{variable}}
const variableRegex = /\{\{(\w+)\}\}/g;
result = result.replace(variableRegex, (match, varName) => {
return variables[varName] !== undefined ? String(variables[varName]) : match;
});
return result;
}
// Helper to wrap content in the base template
export function wrapInBaseTemplate(content: string, variables: Record<string, any>): string {
const wrappedContent = baseEmailWrapper.replace('{{content}}', content);
return replaceTemplateVariables(wrappedContent, variables);
}
// Get all available variables for a template by slug
export function getTemplateVariables(slug: string): EmailVariable[] {
const template = defaultTemplates.find(t => t.slug === slug);
return template?.variables || commonVariables;
}