From 515814e1d56d1c46d2219fd8983d10090a09be59 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 16 Jul 2025 17:17:48 +0200 Subject: [PATCH] feat: update English email templates to match German quality and content - Enhanced all English email templates with proper HTML styling - Added conditional content blocks (Handlebars {{#if}} syntax) - Added styled alert boxes for warnings and success messages - Added action buttons with proper styling - Added emoji icons for better visual communication - Added missing variables: support_email, support_phone, archive_date, photo_count - Matched professional tone and formatting from German templates - Improved visual hierarchy with background colors and borders - All templates now have equal or better quality scores --- backend/data/photo_sharing.db | Bin 188416 -> 208896 bytes ...pdate_english_templates_to_match_german.js | 307 ++++++++++++++++++ backend/scripts/verify-template-equality.js | 110 +++++++ 3 files changed, 417 insertions(+) create mode 100644 backend/migrations/028_update_english_templates_to_match_german.js create mode 100644 backend/scripts/verify-template-equality.js diff --git a/backend/data/photo_sharing.db b/backend/data/photo_sharing.db index 6e2ef798b3f3adaddbfbe0fc37fbeae75d02c6ec..865d5af538267a5c2557fb52abaccdbe37dc9194 100644 GIT binary patch delta 16411 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 delta 1060 zcmcJNe`r%z6vyv5H!nY4UheA4jDv*gEtqw=^Qi+WYuX0L;9H@`xMg8g{qxCDuPK;i zwr2D-o}NYzADcpJ|5t_Z~V!#VWw<3Ou&(l2A)U-Xj)MPUX~ zl*b<90`|o>2Y7QI^!jV{XgD4TYum$#jvcMxwu*pWX((076*0aV>6<)w5+v?-!8iI*VnZ-`HM{}X5VbH@2f9Lfwqk|L&`NlKYvZda#BeF`nW+rPtOyzmab%135# z;h$8M>-2`olb+a$OzRo8v zU_C77TZb^h)JqfM+#v2n1DtwEX~W@_$qlt3{cuyVPH5A35B?Y4?g7X&npo;e3s)JG ziH`^HN8ss&G+!9cP!9YSgIs?8I8^i5lkkG@S?Lz!M%63q>ew(}+Xtt4NTRvoJ&CRb sHaE6_-!{`~w)I*GyM4LnU;kGv%UpTupHNmnCKDKpnO(v>j=!o;0AgBc761SM diff --git a/backend/migrations/028_update_english_templates_to_match_german.js b/backend/migrations/028_update_english_templates_to_match_german.js new file mode 100644 index 0000000..432824d --- /dev/null +++ b/backend/migrations/028_update_english_templates_to_match_german.js @@ -0,0 +1,307 @@ +exports.up = async function(knex) { + // Update English templates to match the quality and content of German templates + + // 1. Gallery Created - Match German version with proper styling and conditionals + await knex('email_templates') + .where('template_key', 'gallery_created') + .update({ + subject_en: 'Your photo gallery is ready', + body_html_en: ` +

Hello {{host_name}},

+ +

Your photo gallery {{event_name}} for {{event_date}} has been successfully created and is now online!

+ +{{#if welcome_message}} +
+

Personal message from your photographer:

+

{{welcome_message}}

+
+{{/if}} + +
+

Your access data:

+ +
+ + + +
+

Important: Your gallery will be available until {{expiry_date}}. After this date, the photos will be archived and will only be available upon request.

+
+ +

We hope you enjoy your photos!

+ +

Best regards,
+Your Photo Sharing Team

`, + body_text_en: `Hello {{host_name}}, + +Your photo gallery "{{event_name}}" for {{event_date}} has been successfully created and is now online! + +{{#if welcome_message}} +Personal message from your photographer: +{{welcome_message}} +{{/if}} + +Your access data: +- Gallery link: {{gallery_link}} +- Password: {{gallery_password}} + +Important: Your gallery will be available until {{expiry_date}}. After this date, the photos will be archived and will only be available upon request. + +We hope you enjoy your photos! + +Best regards, +Your Photo Sharing Team` + }); + + // 2. Expiration Warning - Match German version with urgency and styling + await knex('email_templates') + .where('template_key', 'expiration_warning') + .update({ + subject_en: 'Your photo gallery expires soon', + body_html_en: ` +

Hello {{host_name}},

+ +

Your photo gallery {{event_name}} will expire in {{days_remaining}} days!

+ +
+

⚠️ Important Notice

+

After {{expiry_date}}, your gallery will no longer be accessible online. The photos will be archived and will only be available upon special request.

+
+ +

Don't miss out – download your photos now!

+ + + +
+

Quick reminder of your access data:

+ +
+ +

If you have any questions, please don't hesitate to contact us.

+ +

Best regards,
+Your Photo Sharing Team

`, + body_text_en: `Hello {{host_name}}, + +Your photo gallery "{{event_name}}" will expire in {{days_remaining}} days! + +⚠️ Important Notice +After {{expiry_date}}, your gallery will no longer be accessible online. The photos will be archived and will only be available upon special request. + +Don't miss out – download your photos now! + +Quick reminder of your access data: +- Gallery link: {{gallery_link}} +- Password: {{gallery_password}} + +If you have any questions, please don't hesitate to contact us. + +Best regards, +Your Photo Sharing Team` + }); + + // 3. Gallery Expired - Match German version with contact information + await knex('email_templates') + .where('template_key', 'gallery_expired') + .update({ + subject_en: 'Your photo gallery has expired', + body_html_en: ` +

Hello {{host_name}},

+ +

Your photo gallery {{event_name}} expired on {{expiry_date}} and is no longer accessible online.

+ +
+

Your photos are safely archived

+

Don't worry – your photos have been securely archived and are not lost. If you need access to your photos, please contact us:

+
    +
  • 📧 Email: {{support_email}}
  • + {{#if support_phone}} +
  • 📞 Phone: {{support_phone}}
  • + {{/if}} +
+
+ +

Please have the following information ready when contacting us:

+
    +
  • Event name: {{event_name}}
  • +
  • Event date: {{event_date}}
  • +
  • Expiry date: {{expiry_date}}
  • +
+ +

We'll be happy to help you access your archived photos.

+ +

Best regards,
+Your Photo Sharing Team

`, + body_text_en: `Hello {{host_name}}, + +Your photo gallery "{{event_name}}" expired on {{expiry_date}} and is no longer accessible online. + +Your photos are safely archived +Don't worry – your photos have been securely archived and are not lost. If you need access to your photos, please contact us: + +📧 Email: {{support_email}} +{{#if support_phone}}📞 Phone: {{support_phone}}{{/if}} + +Please have the following information ready when contacting us: +- Event name: {{event_name}} +- Event date: {{event_date}} +- Expiry date: {{expiry_date}} + +We'll be happy to help you access your archived photos. + +Best regards, +Your Photo Sharing Team` + }); + + // 4. Archive Complete - Match German version with success message and details + await knex('email_templates') + .where('template_key', 'archive_complete') + .update({ + subject_en: 'Your photo gallery has been successfully archived', + body_html_en: ` +

Hello {{host_name}},

+ +

Your photo gallery {{event_name}} has been successfully archived.

+ +
+

✅ Archive successfully created

+

Your photos are now safely stored in our archive.

+
+ +
+

Archive details:

+
    +
  • Event: {{event_name}}
  • +
  • Archive date: {{archive_date}}
  • +
  • Number of photos: {{photo_count}}
  • +
  • Archive size: {{archive_size}}
  • +
+
+ +

If you need access to your archived photos in the future, please contact us at:

+

+ 📧 {{support_email}}
+ {{#if support_phone}}📞 {{support_phone}}{{/if}} +

+ +

Thank you for using our photo sharing service!

+ +

Best regards,
+Your Photo Sharing Team

`, + body_text_en: `Hello {{host_name}}, + +Your photo gallery "{{event_name}}" has been successfully archived. + +✅ Archive successfully created +Your photos are now safely stored in our archive. + +Archive details: +- Event: {{event_name}} +- Archive date: {{archive_date}} +- Number of photos: {{photo_count}} +- Archive size: {{archive_size}} + +If you need access to your archived photos in the future, please contact us at: +📧 {{support_email}} +{{#if support_phone}}📞 {{support_phone}}{{/if}} + +Thank you for using our photo sharing service! + +Best regards, +Your Photo Sharing Team` + }); + + // 5. Test Email - Update to match German style + await knex('email_templates') + .where('template_key', 'test_email') + .update({ + subject_en: 'Test Email - Photo Sharing Platform', + body_html_en: ` +

Test Email

+ +

This is a test email from your photo sharing platform.

+ +
+

✅ Email configuration successful!

+

Your email settings have been configured correctly and emails can be sent.

+
+ +
+

Configuration details:

+
    +
  • Timestamp: {{timestamp}}
  • +
  • Sender: {{from_email}}
  • +
+
+ +

Best regards,
+Your Photo Sharing Team

`, + body_text_en: `Test Email + +This is a test email from your photo sharing platform. + +✅ Email configuration successful! +Your email settings have been configured correctly and emails can be sent. + +Configuration details: +- Timestamp: {{timestamp}} +- Sender: {{from_email}} + +Best regards, +Your Photo Sharing Team` + }); +}; + +exports.down = async function(knex) { + // Revert to previous simpler English templates + await knex('email_templates') + .where('template_key', 'gallery_created') + .update({ + subject_en: 'Your Photo Gallery is Ready', + body_html_en: '

Hello,

Your photo gallery "{{event_name}}" has been created.

Access Link: {{gallery_link}}

Password: {{gallery_password}}

The gallery will be available until {{expiry_date}}.

', + body_text_en: 'Your photo gallery "{{event_name}}" has been created. Access Link: {{gallery_link}} Password: {{gallery_password}} The gallery will be available until {{expiry_date}}.' + }); + + await knex('email_templates') + .where('template_key', 'expiration_warning') + .update({ + subject_en: 'Gallery Expires in {{days_remaining}} Days', + body_html_en: '

Reminder

Your photo gallery "{{event_name}}" will expire in {{days_remaining}} days.

Please download your photos before {{expiry_date}}.

Access Gallery

', + body_text_en: 'Your photo gallery "{{event_name}}" will expire in {{days_remaining}} days. Please download your photos before {{expiry_date}}. Access Gallery: {{gallery_link}}' + }); + + await knex('email_templates') + .where('template_key', 'gallery_expired') + .update({ + subject_en: 'Gallery Expired', + body_html_en: '

Gallery Expired

Your photo gallery "{{event_name}}" has expired and is no longer accessible.

If you need access to your photos, please contact support.

', + body_text_en: 'Your photo gallery "{{event_name}}" has expired and is no longer accessible. If you need access to your photos, please contact support.' + }); + + await knex('email_templates') + .where('template_key', 'archive_complete') + .update({ + subject_en: 'Gallery Archived', + body_html_en: '

Archive Complete

Your gallery "{{event_name}}" has been archived.

Archive size: {{archive_size}}

', + body_text_en: 'Your gallery "{{event_name}}" has been archived. Archive size: {{archive_size}}' + }); + + await knex('email_templates') + .where('template_key', 'test_email') + .update({ + subject_en: 'Test Email', + body_html_en: '

This is a test email sent at {{timestamp}}.

', + body_text_en: 'This is a test email sent at {{timestamp}}.' + }); +}; \ No newline at end of file diff --git a/backend/scripts/verify-template-equality.js b/backend/scripts/verify-template-equality.js new file mode 100644 index 0000000..c922797 --- /dev/null +++ b/backend/scripts/verify-template-equality.js @@ -0,0 +1,110 @@ +const { db } = require('../src/database/db'); + +async function verifyTemplateEquality() { + try { + console.log('Verifying template equality between German and English versions...\n'); + + const templates = await db('email_templates').select('*'); + + for (const template of templates) { + console.log(`\n=== ${template.template_key.toUpperCase()} ===`); + + // Check subject length similarity + const subjectEnLength = template.subject_en?.length || 0; + const subjectDeLength = template.subject_de?.length || 0; + console.log(`Subject length - EN: ${subjectEnLength}, DE: ${subjectDeLength}`); + + // Check HTML content features + const htmlEn = template.body_html_en || ''; + const htmlDe = template.body_html_de || ''; + + // Check for key features in both versions + const features = [ + { name: 'Handlebars conditionals', pattern: /{{#if/g }, + { name: 'Styled divs', pattern: /style="/g }, + { name: 'Background colors', pattern: /background-color:/g }, + { name: 'Buttons/CTAs', pattern: //g }, + { name: 'Icons/Emojis', pattern: /[📧📞✅⚠️]/g }, + { name: 'Lists', pattern: /
    /g } + ]; + + console.log('\nFeature comparison:'); + for (const feature of features) { + const enCount = (htmlEn.match(feature.pattern) || []).length; + const deCount = (htmlDe.match(feature.pattern) || []).length; + const status = enCount === deCount ? '✅' : '❌'; + console.log(`${status} ${feature.name}: EN=${enCount}, DE=${deCount}`); + } + + // Check text content length + const textEn = template.body_text_en || ''; + const textDe = template.body_text_de || ''; + console.log(`\nText content length - EN: ${textEn.length}, DE: ${textDe.length}`); + + // Check for specific variables usage + const variables = [ + 'host_name', 'event_name', 'event_date', 'gallery_link', + 'gallery_password', 'expiry_date', 'welcome_message', + 'days_remaining', 'support_email', 'support_phone', + 'archive_date', 'photo_count', 'archive_size' + ]; + + const missingInEn = []; + const missingInDe = []; + + for (const variable of variables) { + const varPattern = new RegExp(`{{${variable}}}`, 'g'); + const inEn = varPattern.test(htmlEn) || varPattern.test(textEn); + const inDe = varPattern.test(htmlDe) || varPattern.test(textDe); + + if (inDe && !inEn) missingInEn.push(variable); + if (inEn && !inDe) missingInDe.push(variable); + } + + if (missingInEn.length > 0) { + console.log(`\n⚠️ Variables in DE but missing in EN: ${missingInEn.join(', ')}`); + } + if (missingInDe.length > 0) { + console.log(`\n⚠️ Variables in EN but missing in DE: ${missingInDe.join(', ')}`); + } + + // Overall quality score + const enScore = [ + htmlEn.includes('style='), + htmlEn.includes('{{#if'), + htmlEn.includes('background-color'), + htmlEn.includes(''), + htmlEn.includes('margin:'), + htmlEn.includes('padding:') + ].filter(Boolean).length; + + const deScore = [ + htmlDe.includes('style='), + htmlDe.includes('{{#if'), + htmlDe.includes('background-color'), + htmlDe.includes(''), + htmlDe.includes('margin:'), + htmlDe.includes('padding:') + ].filter(Boolean).length; + + console.log(`\nQuality score (out of 6) - EN: ${enScore}, DE: ${deScore}`); + console.log(enScore === deScore ? '✅ Templates have equal quality!' : '❌ Quality mismatch'); + } + + console.log('\n\nSummary:'); + console.log('The English templates have been updated to match the German templates in:'); + console.log('- HTML styling and structure'); + console.log('- Conditional content blocks'); + console.log('- Visual elements (buttons, alerts, icons)'); + console.log('- Information completeness'); + console.log('- Professional formatting'); + + } catch (error) { + console.error('Error:', error); + } finally { + await db.destroy(); + } +} + +verifyTemplateEquality(); \ No newline at end of file