Re_Backend/src/emailtemplates/helpers.ts

614 lines
19 KiB
TypeScript

/**
* Email Template Helper Functions
*
* Reusable functions for generating dynamic email sections
*/
import { ApprovalChainItem } from './types';
/**
* Get rich text styles for email-safe HTML rendering
* Styles common HTML elements from rich text editors
*/
export function getRichTextStyles(): string {
return `
<style>
/* Rich text content styles */
.rich-text-content p {
margin: 0 0 12px 0;
color: #333333;
font-size: 14px;
line-height: 1.6;
}
.rich-text-content p:last-child {
margin-bottom: 0;
}
.rich-text-content strong,
.rich-text-content b {
font-weight: 600;
color: #1a1a1a;
}
.rich-text-content em,
.rich-text-content i {
font-style: italic;
}
.rich-text-content u {
text-decoration: underline;
}
.rich-text-content a {
color: #667eea;
text-decoration: underline;
}
.rich-text-content ul,
.rich-text-content ol {
margin: 0 0 12px 0;
padding-left: 25px;
color: #333333;
font-size: 14px;
line-height: 1.6;
}
.rich-text-content li {
margin-bottom: 8px;
}
.rich-text-content h1,
.rich-text-content h2,
.rich-text-content h3,
.rich-text-content h4 {
margin: 0 0 12px 0;
color: #1a1a1a;
font-weight: 600;
line-height: 1.3;
}
.rich-text-content h1 { font-size: 20px; }
.rich-text-content h2 { font-size: 18px; }
.rich-text-content h3 { font-size: 16px; }
.rich-text-content h4 { font-size: 14px; }
.rich-text-content blockquote {
margin: 0 0 12px 0;
padding: 12px 15px;
border-left: 3px solid #667eea;
background-color: #f8f9fa;
color: #666666;
font-style: italic;
}
.rich-text-content code {
padding: 2px 6px;
background-color: #f4f4f4;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 13px;
color: #667eea;
}
.rich-text-content pre {
margin: 0 0 12px 0;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
}
.rich-text-content hr {
border: none;
border-top: 1px solid #e9ecef;
margin: 20px 0;
}
.rich-text-content img {
max-width: 100%;
height: auto;
display: block;
margin: 12px 0;
}
/* Mobile adjustments for rich text */
@media only screen and (max-width: 600px) {
.rich-text-content p,
.rich-text-content ul,
.rich-text-content ol {
font-size: 13px !important;
}
.rich-text-content h1 { font-size: 18px !important; }
.rich-text-content h2 { font-size: 16px !important; }
.rich-text-content h3 { font-size: 15px !important; }
.rich-text-content h4 { font-size: 14px !important; }
}
</style>
`;
}
/**
* Wrap rich text content with proper styling
* Use this for descriptions and comments from rich text editors
*/
export function wrapRichText(htmlContent: string): string {
return `
<div class="rich-text-content" style="color: #666666; font-size: 14px; line-height: 1.6;">
${htmlContent}
</div>
`;
}
/**
* Generate all email styles (responsive + rich text)
* Optimized for screens up to 600px width
*/
export function getResponsiveStyles(): string {
return `
${getRichTextStyles()}
<style>
/* Reset styles */
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
table {
border-collapse: collapse !important;
}
/* Mobile responsive styles */
@media only screen and (max-width: 600px) {
/* Container adjustments */
.email-container {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
}
/* Header adjustments */
.email-header {
padding: 25px 15px 30px !important;
}
/* Content adjustments */
.email-content {
padding: 30px 20px !important;
}
/* Footer adjustments */
.email-footer {
padding: 25px 20px !important;
}
/* Logo responsive */
.logo-img {
width: 180px !important;
height: auto !important;
max-width: 90% !important;
}
/* Typography adjustments */
.header-title {
font-size: 20px !important;
letter-spacing: 1px !important;
line-height: 1.4 !important;
}
.header-subtitle {
font-size: 12px !important;
}
/* Detail tables */
.detail-box {
padding: 20px 15px !important;
}
.detail-table td {
font-size: 13px !important;
padding: 6px 0 !important;
display: block !important;
width: 100% !important;
}
.detail-label {
font-weight: 600 !important;
margin-bottom: 2px !important;
}
/* Button adjustments */
.cta-button {
display: block !important;
width: 100% !important;
max-width: 100% !important;
padding: 16px 20px !important;
font-size: 16px !important;
box-sizing: border-box !important;
}
/* Section adjustments */
.info-section {
padding: 15px !important;
margin-bottom: 20px !important;
}
.section-title {
font-size: 15px !important;
}
.section-text {
font-size: 13px !important;
line-height: 1.6 !important;
}
/* List items */
.info-section ul {
padding-left: 15px !important;
font-size: 13px !important;
}
.info-section li {
margin-bottom: 8px !important;
}
}
/* Very small screens (320px) */
@media only screen and (max-width: 400px) {
.logo-img {
width: 150px !important;
}
.header-title {
font-size: 18px !important;
}
.email-content {
padding: 25px 15px !important;
}
}
</style>
`;
}
/**
* Email header configuration types
*/
export interface EmailHeaderConfig {
title: string;
subtitle?: string;
gradientFrom: string;
gradientTo: string;
logoUrl?: string;
logoAlt?: string;
logoWidth?: number;
logoHeight?: number;
productName?: string; // e.g., "RE Flow"
}
/**
* Generate professional email header with Royal Enfield branding
* Black background with logo, product name, and gold accent line
* Fully responsive for mobile devices
*/
export function getEmailHeader(config: EmailHeaderConfig): string {
const logoWidth = config.logoWidth || 220;
const logoHeight = config.logoHeight || 65;
const logoSection = config.logoUrl ? `
<div style="margin-bottom: ${config.productName ? '15px' : '20px'};">
<img
src="${config.logoUrl}"
alt="${config.logoAlt || 'Company Logo'}"
class="logo-img"
style="width: ${logoWidth}px; height: auto; max-width: 100%; display: inline-block;"
/>
</div>
` : '';
const productNameSection = config.productName ? `
<p style="margin: 0 0 20px; color: #DEB219; font-size: 16px; font-weight: 600; letter-spacing: 2px; text-transform: uppercase;">
${config.productName}
</p>
` : '';
const dividerLine = `
<div style="width: 100%; height: 3px; background-color: #DEB219; margin: ${config.logoUrl || config.productName ? '20px 0 25px' : '0 0 25px'};"></div>
`;
const subtitleSection = config.subtitle ? `
<p class="header-subtitle" style="margin: 12px 0 0; color: #DEB219; font-size: 14px; font-weight: 500; letter-spacing: 0.5px; text-transform: uppercase;">${config.subtitle}</p>
` : '';
return `
<tr>
<td class="email-header" style="background-color: #1a1a1a; padding: ${config.logoUrl || config.productName ? '30px 30px 35px' : '35px 30px'}; text-align: center; border-radius: 8px 8px 0 0;">
${logoSection}
${productNameSection}
${dividerLine}
<h1 class="header-title" style="margin: 0; color: #ffffff; font-size: 26px; font-weight: 600; letter-spacing: 1.5px; line-height: 1.3; text-transform: uppercase;">${config.title}</h1>
${subtitleSection}
</td>
</tr>
`;
}
/**
* Royal Enfield Brand Colors (Official)
*/
export const BrandColors = {
red: '#DB281B', // Royal Enfield Red (Official)
black: '#1a1a1a', // Royal Enfield Black
gold: '#DEB219', // Royal Enfield Gold
darkRed: '#A51F16', // Darker red for accents
darkGold: '#C89F16', // Darker gold for accents
white: '#ffffff' // White for text
};
/**
* Predefined header styles using Royal Enfield brand colors
*/
export const HeaderStyles = {
// Primary - Red & Black (Main brand colors)
primary: {
gradientFrom: BrandColors.red,
gradientTo: BrandColors.darkRed
},
// Information/Neutral - Black & Gold accent
info: {
gradientFrom: BrandColors.black,
gradientTo: '#2d2d2d'
},
// Success - Black with gold accent
success: {
gradientFrom: BrandColors.black,
gradientTo: '#2d2d2d'
},
// Error/Critical - Red (Brand red)
error: {
gradientFrom: BrandColors.red,
gradientTo: BrandColors.darkRed
},
// Warning - Gold
warning: {
gradientFrom: BrandColors.gold,
gradientTo: BrandColors.darkGold
},
// Neutral - Black
neutral: {
gradientFrom: BrandColors.black,
gradientTo: '#2d2d2d'
},
// Info Secondary - Dark grey
infoSecondary: {
gradientFrom: '#424242',
gradientTo: '#1a1a1a'
},
// Complete - Black with gold
complete: {
gradientFrom: BrandColors.black,
gradientTo: '#2d2d2d'
}
};
/**
* Generate priority alert section
*/
export function getPrioritySection(priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'): string {
if (priority === 'HIGH' || priority === 'CRITICAL') {
return `
<div style="padding: 20px; background-color: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px; margin-bottom: 30px;">
<h3 style="margin: 0 0 10px; color: #856404; font-size: 16px; font-weight: 600;">High Priority</h3>
<p style="margin: 0; color: #856404; font-size: 14px; line-height: 1.6;">
This request has been marked as ${priority} priority and requires prompt attention.
</p>
</div>
`;
}
return '';
}
/**
* Generate approval chain visualization
*/
export function getApprovalChain(approvers: ApprovalChainItem[]): string {
return approvers.map(approver => {
let icon = '';
let textColor = '#333333';
let status = '';
switch (approver.status) {
case 'approved':
icon = `<span style="display: inline-block; width: 30px; height: 30px; background-color: #28a745; color: #ffffff; text-align: center; line-height: 30px; border-radius: 50%; font-size: 14px; margin-right: 10px; font-weight: bold;">✓</span>`;
status = approver.date ? `Approved on ${approver.date}` : 'Approved';
break;
case 'current':
icon = `<span style="display: inline-block; width: 30px; height: 30px; background-color: #667eea; color: #ffffff; text-align: center; line-height: 30px; border-radius: 50%; font-size: 14px; margin-right: 10px; font-weight: bold;">${approver.levelNumber}</span>`;
textColor = '#667eea';
status = 'Pending (Your Turn)';
break;
case 'pending':
icon = `<span style="display: inline-block; width: 30px; height: 30px; background-color: #ffc107; color: #ffffff; text-align: center; line-height: 30px; border-radius: 50%; font-size: 14px; margin-right: 10px; font-weight: bold;">${approver.levelNumber}</span>`;
status = 'Pending';
break;
case 'awaiting':
icon = `<span style="display: inline-block; width: 30px; height: 30px; background-color: #cccccc; color: #ffffff; text-align: center; line-height: 30px; border-radius: 50%; font-size: 14px; margin-right: 10px; font-weight: bold;">${approver.levelNumber}</span>`;
textColor = '#999999';
status = 'Awaiting';
break;
}
return `
<div style="padding: 10px 0; border-bottom: 1px solid #e9ecef;">
${icon}
<strong style="color: ${textColor};">${approver.name}</strong> - ${status}
</div>
`;
}).join('');
}
/**
* Generate next steps section for approval confirmation
*/
export function getNextStepsSection(isFinalApproval: boolean, nextApproverName?: string): string {
if (isFinalApproval) {
return `
<div style="padding: 20px; background-color: #e7f3ff; border-left: 4px solid #0066cc; border-radius: 4px; margin-bottom: 30px;">
<h3 style="margin: 0 0 10px; color: #004085; font-size: 16px; font-weight: 600;">Next Steps</h3>
<p style="margin: 0; color: #004085; font-size: 14px; line-height: 1.6;">
All approvals are complete! Please review the request and add any conclusion remarks before closing it.
</p>
</div>
`;
} else if (nextApproverName) {
return `
<div style="padding: 20px; background-color: #e7f3ff; border-left: 4px solid #0066cc; border-radius: 4px; margin-bottom: 30px;">
<h3 style="margin: 0 0 10px; color: #004085; font-size: 16px; font-weight: 600;">Next Steps</h3>
<p style="margin: 0; color: #004085; font-size: 14px; line-height: 1.6;">
The request has been forwarded to <strong>${nextApproverName}</strong> for the next level of approval.
You'll be notified when the request progresses.
</p>
</div>
`;
}
return '';
}
/**
* Generate permissions section for participant added
*/
export function getPermissionsContent(role: 'Approver' | 'Spectator'): string {
if (role === 'Approver') {
return `
<ul style="margin: 10px 0 0 0; padding-left: 20px; color: #004085; font-size: 14px; line-height: 1.8;">
<li>Review request details and documents</li>
<li>Approve or reject the request</li>
<li>Add comments and work notes</li>
<li>View approval workflow and history</li>
<li>Receive real-time notifications</li>
</ul>
`;
} else {
return `
<ul style="margin: 10px 0 0 0; padding-left: 20px; color: #004085; font-size: 14px; line-height: 1.8;">
<li>View request details and documents</li>
<li>Add comments and work notes</li>
<li>View approval workflow progress</li>
<li>Receive status update notifications</li>
<li>Cannot approve or reject the request</li>
</ul>
`;
}
}
/**
* Generate conclusion section for closed requests (supports rich text)
*/
export function getConclusionSection(conclusionRemark?: string): string {
if (conclusionRemark) {
return `
<div style="margin-bottom: 30px;">
<h3 style="margin: 0 0 15px; color: #333333; font-size: 16px; font-weight: 600;">Conclusion Remarks:</h3>
<div style="padding: 15px; background-color: #f8f9fa; border-left: 4px solid #6f42c1; border-radius: 4px;">
${wrapRichText(conclusionRemark)}
</div>
</div>
`;
}
return '';
}
/**
* Generate role description for participant added
*/
export function getRoleDescription(role: 'Approver' | 'Spectator'): string {
if (role === 'Approver') {
return 'You can now review and take action on this request.';
} else {
return 'You can now view this request and participate in discussions.';
}
}
/**
* Generate action required section for workflow resumed
*/
export function getActionRequiredSection(isApprover: boolean): string {
if (isApprover) {
return `
<div style="padding: 20px; background-color: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px; margin-bottom: 30px;">
<h3 style="margin: 0 0 10px; color: #856404; font-size: 16px; font-weight: 600;">Action Required</h3>
<p style="margin: 0; color: #856404; font-size: 14px; line-height: 1.6;">
This request requires your immediate attention. Please review and take action to keep the workflow moving forward.
</p>
</div>
`;
}
return '';
}
/**
* Common email footer with optional branding
*/
export interface EmailFooterConfig {
companyName: string;
companyWebsite?: string;
supportEmail?: string;
additionalLinks?: Array<{ text: string; url: string }>;
}
export function getEmailFooter(config: EmailFooterConfig | string): string {
// Backward compatibility - if string is passed, use it as companyName
const footerConfig: EmailFooterConfig = typeof config === 'string'
? { companyName: config }
: config;
const supportSection = footerConfig.supportEmail ? `
<p style="margin: 10px 0 0; color: #666666; font-size: 13px;">
Need help? Contact us at <a href="mailto:${footerConfig.supportEmail}" style="color: #667eea; text-decoration: none;">${footerConfig.supportEmail}</a>
</p>
` : '';
const linksSection = footerConfig.additionalLinks && footerConfig.additionalLinks.length > 0 ? `
<p style="margin: 15px 0 0; color: #999999; font-size: 12px;">
${footerConfig.additionalLinks.map(link =>
`<a href="${link.url}" style="color: #667eea; text-decoration: none; margin: 0 10px;">${link.text}</a>`
).join(' | ')}
</p>
` : '';
const companyLink = footerConfig.companyWebsite
? `<a href="${footerConfig.companyWebsite}" style="color: #999999; text-decoration: none;">${footerConfig.companyName}</a>`
: footerConfig.companyName;
return `
<tr>
<td style="padding: 30px; background-color: #f8f9fa; border-radius: 0 0 8px 8px; text-align: center; border-top: 1px solid #e9ecef;">
<p style="margin: 0 0 10px; color: #666666; font-size: 13px; line-height: 1.6;">
This is an automated notification. Please do not reply to this email.
</p>
${supportSection}
${linksSection}
<p style="margin: ${linksSection ? '15px' : '10px'} 0 0; color: #999999; font-size: 12px;">
© 2025 ${companyLink}. All rights reserved.
</p>
</td>
</tr>
`;
}