614 lines
19 KiB
TypeScript
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>
|
|
`;
|
|
}
|
|
|