xpeditis2.0/apps/backend/src/infrastructure/email/templates/email-templates.ts
David 890bc189ee
Some checks failed
CI/CD Pipeline - Xpeditis PreProd / Frontend - Build & Test (push) Failing after 5m31s
CI/CD Pipeline - Xpeditis PreProd / Frontend - Docker Build & Push (push) Has been skipped
CI/CD Pipeline - Xpeditis PreProd / Backend - Build & Test (push) Failing after 5m42s
CI/CD Pipeline - Xpeditis PreProd / Backend - Docker Build & Push (push) Has been skipped
CI/CD Pipeline - Xpeditis PreProd / Deploy to PreProd Server (push) Has been skipped
CI/CD Pipeline - Xpeditis PreProd / Run Smoke Tests (push) Has been skipped
fix v0.2
2025-11-12 18:00:33 +01:00

502 lines
17 KiB
TypeScript

/**
* Email Templates Service
*
* Renders email templates using MJML and Handlebars
*/
import { Injectable } from '@nestjs/common';
import mjml2html from 'mjml';
import Handlebars from 'handlebars';
@Injectable()
export class EmailTemplates {
/**
* Render booking confirmation email
*/
async renderBookingConfirmation(data: {
bookingNumber: string;
bookingDetails: any;
}): Promise<string> {
const mjmlTemplate = `
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" />
<mj-text font-size="14px" color="#333333" line-height="1.6" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f4">
<mj-section background-color="#ffffff" padding="20px">
<mj-column>
<mj-text font-size="24px" font-weight="bold" color="#0066cc">
Booking Confirmation
</mj-text>
<mj-divider border-color="#0066cc" />
<mj-text font-size="16px">
Your booking has been confirmed successfully!
</mj-text>
<mj-text>
<strong>Booking Number:</strong> {{bookingNumber}}
</mj-text>
<mj-text>
Thank you for using Xpeditis. Your booking confirmation is attached as a PDF.
</mj-text>
<mj-button background-color="#0066cc" href="{{dashboardUrl}}">
View in Dashboard
</mj-button>
</mj-column>
</mj-section>
<mj-section background-color="#f4f4f4" padding="10px">
<mj-column>
<mj-text font-size="12px" color="#666666" align="center">
© 2025 Xpeditis. All rights reserved.
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
const { html } = mjml2html(mjmlTemplate);
const template = Handlebars.compile(html);
return template(data);
}
/**
* Render verification email
*/
async renderVerificationEmail(data: { verifyUrl: string }): Promise<string> {
const mjmlTemplate = `
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f4">
<mj-section background-color="#ffffff" padding="20px">
<mj-column>
<mj-text font-size="24px" font-weight="bold" color="#0066cc">
Verify Your Email
</mj-text>
<mj-divider border-color="#0066cc" />
<mj-text>
Welcome to Xpeditis! Please verify your email address to get started.
</mj-text>
<mj-button background-color="#0066cc" href="{{verifyUrl}}">
Verify Email Address
</mj-button>
<mj-text font-size="12px" color="#666666">
If you didn't create an account, you can safely ignore this email.
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#f4f4f4" padding="10px">
<mj-column>
<mj-text font-size="12px" color="#666666" align="center">
© 2025 Xpeditis. All rights reserved.
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
const { html } = mjml2html(mjmlTemplate);
const template = Handlebars.compile(html);
return template(data);
}
/**
* Render password reset email
*/
async renderPasswordResetEmail(data: { resetUrl: string }): Promise<string> {
const mjmlTemplate = `
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f4">
<mj-section background-color="#ffffff" padding="20px">
<mj-column>
<mj-text font-size="24px" font-weight="bold" color="#0066cc">
Reset Your Password
</mj-text>
<mj-divider border-color="#0066cc" />
<mj-text>
You requested to reset your password. Click the button below to set a new password.
</mj-text>
<mj-button background-color="#0066cc" href="{{resetUrl}}">
Reset Password
</mj-button>
<mj-text font-size="12px" color="#666666">
This link will expire in 1 hour. If you didn't request this, please ignore this email.
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#f4f4f4" padding="10px">
<mj-column>
<mj-text font-size="12px" color="#666666" align="center">
© 2025 Xpeditis. All rights reserved.
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
const { html } = mjml2html(mjmlTemplate);
const template = Handlebars.compile(html);
return template(data);
}
/**
* Render welcome email
*/
async renderWelcomeEmail(data: { firstName: string; dashboardUrl: string }): Promise<string> {
const mjmlTemplate = `
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f4">
<mj-section background-color="#ffffff" padding="20px">
<mj-column>
<mj-text font-size="24px" font-weight="bold" color="#0066cc">
Welcome to Xpeditis, {{firstName}}!
</mj-text>
<mj-divider border-color="#0066cc" />
<mj-text>
We're excited to have you on board. Xpeditis helps you search and book maritime freight with ease.
</mj-text>
<mj-text>
<strong>Get started:</strong>
</mj-text>
<mj-text>
• Search for shipping rates<br/>
• Compare carriers and prices<br/>
• Book containers online<br/>
• Track your shipments
</mj-text>
<mj-button background-color="#0066cc" href="{{dashboardUrl}}">
Go to Dashboard
</mj-button>
</mj-column>
</mj-section>
<mj-section background-color="#f4f4f4" padding="10px">
<mj-column>
<mj-text font-size="12px" color="#666666" align="center">
© 2025 Xpeditis. All rights reserved.
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
const { html } = mjml2html(mjmlTemplate);
const template = Handlebars.compile(html);
return template(data);
}
/**
* Render user invitation email
*/
async renderUserInvitation(data: {
organizationName: string;
inviterName: string;
tempPassword: string;
loginUrl: string;
}): Promise<string> {
const mjmlTemplate = `
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f4">
<mj-section background-color="#ffffff" padding="20px">
<mj-column>
<mj-text font-size="24px" font-weight="bold" color="#0066cc">
You've Been Invited!
</mj-text>
<mj-divider border-color="#0066cc" />
<mj-text>
{{inviterName}} has invited you to join <strong>{{organizationName}}</strong> on Xpeditis.
</mj-text>
<mj-text>
<strong>Your temporary password:</strong> {{tempPassword}}
</mj-text>
<mj-text font-size="12px" color="#ff6600">
Please change your password after your first login.
</mj-text>
<mj-button background-color="#0066cc" href="{{loginUrl}}">
Login Now
</mj-button>
</mj-column>
</mj-section>
<mj-section background-color="#f4f4f4" padding="10px">
<mj-column>
<mj-text font-size="12px" color="#666666" align="center">
© 2025 Xpeditis. All rights reserved.
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
const { html } = mjml2html(mjmlTemplate);
const template = Handlebars.compile(html);
return template(data);
}
/**
* Render CSV booking request email
*/
async renderCsvBookingRequest(data: {
bookingId: string;
origin: string;
destination: string;
volumeCBM: number;
weightKG: number;
palletCount: number;
priceUSD: number;
priceEUR: number;
primaryCurrency: string;
transitDays: number;
containerType: string;
documents: Array<{
type: string;
fileName: string;
}>;
acceptUrl: string;
rejectUrl: string;
}): Promise<string> {
const mjmlTemplate = `
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" />
<mj-text font-size="14px" color="#333333" line-height="1.6" />
</mj-attributes>
<mj-style>
.info-row {
padding: 8px 0;
border-bottom: 1px solid #e0e0e0;
}
.info-label {
font-weight: bold;
color: #0066cc;
}
</mj-style>
</mj-head>
<mj-body background-color="#f4f4f4">
<!-- Header -->
<mj-section background-color="#0066cc" padding="20px">
<mj-column>
<mj-text font-size="28px" font-weight="bold" color="#ffffff" align="center">
Nouvelle demande de réservation
</mj-text>
<mj-text font-size="16px" color="#ffffff" align="center">
Xpeditis
</mj-text>
</mj-column>
</mj-section>
<!-- Introduction -->
<mj-section background-color="#ffffff" padding="20px">
<mj-column>
<mj-text font-size="16px">
Bonjour,
</mj-text>
<mj-text>
Vous avez reçu une nouvelle demande de réservation via Xpeditis. Veuillez examiner les détails ci-dessous et confirmer ou refuser cette demande.
</mj-text>
</mj-column>
</mj-section>
<!-- Booking Details -->
<mj-section background-color="#ffffff" padding="20px 20px 10px 20px">
<mj-column>
<mj-text font-size="20px" font-weight="bold" color="#0066cc">
Détails du transport
</mj-text>
<mj-divider border-color="#0066cc" border-width="2px" />
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px">
<mj-column width="40%">
<mj-text css-class="info-label">Route</mj-text>
</mj-column>
<mj-column width="60%">
<mj-text>{{origin}} → {{destination}}</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px">
<mj-column width="40%">
<mj-text css-class="info-label">Volume</mj-text>
</mj-column>
<mj-column width="60%">
<mj-text>{{volumeCBM}} CBM</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px">
<mj-column width="40%">
<mj-text css-class="info-label">Poids</mj-text>
</mj-column>
<mj-column width="60%">
<mj-text>{{weightKG}} kg</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px">
<mj-column width="40%">
<mj-text css-class="info-label">Palettes</mj-text>
</mj-column>
<mj-column width="60%">
<mj-text>{{palletCount}}</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px">
<mj-column width="40%">
<mj-text css-class="info-label">Type de conteneur</mj-text>
</mj-column>
<mj-column width="60%">
<mj-text>{{containerType}}</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px">
<mj-column width="40%">
<mj-text css-class="info-label">Transit</mj-text>
</mj-column>
<mj-column width="60%">
<mj-text>{{transitDays}} jours</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px 20px 20px">
<mj-column width="40%">
<mj-text css-class="info-label">Prix</mj-text>
</mj-column>
<mj-column width="60%">
<mj-text font-size="18px" font-weight="bold" color="#00aa00">
{{#if (eq primaryCurrency "EUR")}}
{{priceEUR}} EUR
{{else}}
{{priceUSD}} USD
{{/if}}
</mj-text>
<mj-text font-size="12px" color="#666666">
{{#if (eq primaryCurrency "EUR")}}
(≈ {{priceUSD}} USD)
{{else}}
(≈ {{priceEUR}} EUR)
{{/if}}
</mj-text>
</mj-column>
</mj-section>
<!-- Documents Section -->
<mj-section background-color="#f9f9f9" padding="20px">
<mj-column>
<mj-text font-size="18px" font-weight="bold" color="#0066cc">
📄 Documents fournis
</mj-text>
<mj-divider border-color="#0066cc" border-width="2px" />
{{#each documents}}
<mj-text padding="5px 0">
• <strong>{{this.type}}:</strong> {{this.fileName}}
</mj-text>
{{/each}}
</mj-column>
</mj-section>
<!-- Action Buttons -->
<mj-section background-color="#ffffff" padding="30px 20px">
<mj-column>
<mj-text font-size="16px" font-weight="bold" align="center">
Veuillez confirmer votre décision:
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px 20px 30px 20px">
<mj-column width="50%" padding="0px 10px">
<mj-button
background-color="#00aa00"
href="{{acceptUrl}}"
font-size="16px"
font-weight="bold"
border-radius="5px"
padding="15px 30px"
>
✓ Accepter la demande
</mj-button>
</mj-column>
<mj-column width="50%" padding="0px 10px">
<mj-button
background-color="#cc0000"
href="{{rejectUrl}}"
font-size="16px"
font-weight="bold"
border-radius="5px"
padding="15px 30px"
>
✗ Refuser la demande
</mj-button>
</mj-column>
</mj-section>
<!-- Important Notice -->
<mj-section background-color="#fff8e1" padding="20px">
<mj-column>
<mj-text font-size="14px" color="#f57c00" font-weight="bold">
⚠️ Important
</mj-text>
<mj-text font-size="13px" color="#666666">
Cette demande expire automatiquement dans <strong>7 jours</strong> si aucune action n'est prise. Merci de répondre dans les meilleurs délais.
</mj-text>
</mj-column>
</mj-section>
<!-- Footer -->
<mj-section background-color="#f4f4f4" padding="20px">
<mj-column>
<mj-text font-size="12px" color="#666666" align="center">
Référence de réservation: <strong>{{bookingId}}</strong>
</mj-text>
<mj-divider border-color="#cccccc" padding="10px 0" />
<mj-text font-size="12px" color="#666666" align="center">
© 2025 Xpeditis. Tous droits réservés.
</mj-text>
<mj-text font-size="11px" color="#999999" align="center">
Cet email a été envoyé automatiquement. Merci de ne pas y répondre directement.
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
// Register Handlebars helper for equality check
Handlebars.registerHelper('eq', function (a, b) {
return a === b;
});
const { html } = mjml2html(mjmlTemplate);
const template = Handlebars.compile(html);
return template(data);
}
}