class EmailTemplateData { final String templateName; final String subject; final String htmlBody; final int? templateId; final List placeholders; const EmailTemplateData({ this.templateName = 'Enrollment Reminder Mail', required this.subject, required this.htmlBody, this.templateId, this.placeholders = const [], }); static EmailTemplateData fromApiResponse( Map response, { required String defaultSubject, required String defaultHtmlBody, String defaultTemplateName = 'Enrollment Reminder Mail', }) { final data = response['data']; Map source; Map? parentData; if (data is Map) { parentData = Map.from(data); final nested = parentData['email_template'] ?? parentData['template']; source = nested is Map ? Map.from(nested) : parentData; } else { source = Map.from(response); } final templateName = _readString(source, const [ 'template_name', 'name', 'mail_template_name', ]); final subject = _readString(source, const [ 'email_subject', 'subject', 'mail_subject', ]); final htmlBody = _readString(source, const [ 'email_body', 'email_html', 'html_body', 'body', 'template_html', 'mail_body', ]); final idRaw = source['id'] ?? source['template_id']; final templateId = idRaw == null ? null : int.tryParse(idRaw.toString()); final placeholders = parsePlaceholders( source['place_holders'] ?? source['placeholders'] ?? parentData?['place_holders'] ?? parentData?['placeholders'], ); return EmailTemplateData( templateName: templateName.isNotEmpty ? templateName : defaultTemplateName, subject: subject.isNotEmpty ? subject : defaultSubject, htmlBody: htmlBody.isNotEmpty ? htmlBody : defaultHtmlBody, templateId: templateId, placeholders: placeholders.isNotEmpty ? placeholders : defaultPlaceholders(), ); } static List defaultPlaceholders() { return parsePlaceholders(kDefaultEnrollmentReminderPlaceholderKeys); } static List parsePlaceholders(dynamic raw) { if (raw is! List || raw.isEmpty) { return const []; } final placeholders = []; for (final item in raw) { final key = item?.toString().trim(); if (key == null || key.isEmpty) continue; placeholders.add( EmailTemplatePlaceholder( formatPlaceholderLabel(key), '{{$key}}', ), ); } return placeholders; } static String formatPlaceholderLabel(String key) { return key .split('_') .where((part) => part.isNotEmpty) .map( (part) => '${part[0].toUpperCase()}${part.substring(1).toLowerCase()}', ) .join(' '); } static String _readString(Map map, List keys) { for (final key in keys) { final value = map[key]; if (value != null && value.toString().trim().isNotEmpty) { return value.toString(); } } return ''; } static String wrapPlainTextAsHtml(String text) { final escaped = text .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('\n', '
'); return '''

$escaped

'''; } } class EmailTemplatePlaceholder { final String label; final String token; const EmailTemplatePlaceholder(this.label, this.token); } const kDefaultEnrollmentReminderPlaceholderKeys = [ 'member_name', 'nhance_logo', 'client_logo', 'policy_name', 'policy_no', 'emp_code', 'app_link', 'client_name', 'enrollment_open_date', 'enrollment_close_date', ]; const kDefaultEnrollmentReminderHtmlBody = r''' Enrollment Reminder
Company Logo

Enrollment Reminder

Dear {{member_name}},

This is a reminder to complete your insurance enrollment for the policy below. Please finish enrollment before the deadline to avoid missing your benefits.

Policy Number: {{policy_no}}
Enrollment Open Date: {{enrollment_open_date}}
Enrollment Close Date: {{enrollment_close_date}}
Complete Enrollment

If you have already completed your enrollment, please ignore this email.

Regards,
{{client_name}}

This is an automated reminder email. Please do not reply directly.
Nhance
'''; const kEmailContentBlocks = <({String label, String snippet})>[ ( label: 'Columns', snippet: '
Column 1Column 2
', ), ( label: 'Button', snippet: '

Click Here

', ), ( label: 'Divider', snippet: '
', ), ( label: 'Heading', snippet: '

Heading

', ), ( label: 'Paragraph', snippet: '

Paragraph text

', ), ( label: 'Image', snippet: '

Logo

', ), ( label: 'Social', snippet: '

FacebookLinkedIn

', ), ( label: 'Menu', snippet: '

Home|Support

', ), ( label: 'HTML', snippet: '', ), ]; /// Column layout blocks (Unlayer Blocks > Blank tab). class EmailColumnLayout { final List ratios; final String snippet; const EmailColumnLayout({ required this.ratios, required this.snippet, }); } const kEmailColumnLayouts = [ EmailColumnLayout( ratios: [100], snippet: '
 
', ), EmailColumnLayout( ratios: [50, 50], snippet: '
  
', ), EmailColumnLayout( ratios: [33, 33, 33], snippet: '
   
', ), EmailColumnLayout( ratios: [25, 75], snippet: '
  
', ), EmailColumnLayout( ratios: [75, 25], snippet: '
  
', ), EmailColumnLayout( ratios: [25, 25, 25, 25], snippet: '
    
', ), ]; class EmailBodyStyle { final String backgroundColor; final String contentWidth; final String fontFamily; final String textColor; final String linkColor; final String contentAlignment; final String fontWeight; const EmailBodyStyle({ this.backgroundColor = '#ffffff', this.contentWidth = '500px', this.fontFamily = 'Arial, Helvetica, sans-serif', this.textColor = '#000000', this.linkColor = '#009195', this.contentAlignment = 'left', this.fontWeight = '400', }); int get contentWidthPx { final n = int.tryParse(contentWidth.replaceAll('px', '').trim()); return n ?? 500; } EmailBodyStyle copyWith({ String? backgroundColor, String? contentWidth, String? fontFamily, String? textColor, String? linkColor, String? contentAlignment, String? fontWeight, }) { return EmailBodyStyle( backgroundColor: backgroundColor ?? this.backgroundColor, contentWidth: contentWidth ?? this.contentWidth, fontFamily: fontFamily ?? this.fontFamily, textColor: textColor ?? this.textColor, linkColor: linkColor ?? this.linkColor, contentAlignment: contentAlignment ?? this.contentAlignment, fontWeight: fontWeight ?? this.fontWeight, ); } static EmailBodyStyle parseFromHtml(String html) { final bgMatch = RegExp( r'background(?:-color)?:\s*([^;"]+)', caseSensitive: false, ).firstMatch(html); final widthMatch = RegExp( r'max-width:\s*(\d+)px', caseSensitive: false, ).firstMatch(html); final fontMatch = RegExp( r'font-family:\s*([^;"]+)', caseSensitive: false, ).firstMatch(html); final colorMatch = RegExp( r'(?', bodyStart); if (openEnd == -1) return html; final bodyTag = html.substring(bodyStart, openEnd + 1); final hasStyle = bodyTag.toLowerCase().contains('style='); String newBodyTag; if (hasStyle) { newBodyTag = bodyTag.replaceAll( RegExp(r'style="[^"]*"', caseSensitive: false), 'style="$bodyStyle"', ); } else { newBodyTag = bodyTag.replaceFirst('>', ' style="$bodyStyle">'); } var result = html.replaceRange(bodyStart, openEnd + 1, newBodyTag); final linkStyle = ''; if (!result.toLowerCase().contains('