From 6d302e79983f73696f24ab1dfa71fd7f4f6345ca Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Tue, 26 May 2026 20:44:11 +0200 Subject: [PATCH] feat(crm): add event_reminder_* templates to dev email tester MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-event reminder feature shipped with 5 seeded templates (event_reminder_default + wedding/birthday/corporate/other) but the CRM → Development "Send any CRM email to me" picker only listed the quote/invoice/contract templates. Maintainer can now eyeball each reminder category's body without staging a real event. Backend: - Extend TEMPLATES_KEYS in adminDev.js with all 5 reminder keys. - Add event_date (today+2d), days_before (2), business_name (from business_profile.legal_name) to the common payload so the {{tokens}} in the reminder bodies resolve. Frontend: - Extend CrmEmailTemplateKey union. - Add TEMPLATE_LABEL_KEYS entries. - EN+DE i18n labels under crmDev.templates.label.event_reminder_*. No PDF attachment — reminders are body-only emails (matches the real flow). --- backend/src/routes/adminDev.js | 19 ++++++++++++ frontend/src/i18n/locales/de.json | 20 +++++++++++++ frontend/src/i18n/locales/en.json | 20 +++++++++++++ .../admin/clients/CrmDevelopmentPage.tsx | 30 +++++++++++++++++++ frontend/src/services/devTools.service.ts | 11 ++++++- 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/backend/src/routes/adminDev.js b/backend/src/routes/adminDev.js index b5732d16..69416406 100644 --- a/backend/src/routes/adminDev.js +++ b/backend/src/routes/adminDev.js @@ -98,6 +98,16 @@ const TEMPLATES_KEYS = [ 'contract_sent', 'contract_signed_admin_notification', 'contract_fully_signed', + // Pre-event reminder emails (migration 143). The runtime resolver + // picks `event_reminder_` with fallback to + // `event_reminder_default`. The dev tester exposes every seeded + // template so the maintainer can eyeball each category's body + // without staging a real event for each. + 'event_reminder_default', + 'event_reminder_wedding', + 'event_reminder_birthday', + 'event_reminder_corporate', + 'event_reminder_other', ]; router.get( @@ -355,11 +365,20 @@ async function buildPayloadFor(key, adminId, frontendUrl) { const today = new Date(); const dueDate = new Date(today.getTime() - 5 * 86400000); const validUntil = new Date(today.getTime() + 14 * 86400000); + // Pre-event reminder mock: pretend the event is 2 days out (the + // global default for `crm_event_reminders_days_before`). Renders + // {{event_date}} + {{days_before}} for the event_reminder_* + // templates. + const eventDate = new Date(today.getTime() + 2 * 86400000); + const businessProfile = await db('business_profile').first().catch(() => null); const common = { customer_name: 'Sample Customer', customer_email: 'sample.customer@example.test', event_name: 'Sample Event', + event_date: fakeShortDate(eventDate), + days_before: 2, + business_name: businessProfile?.legal_name || 'Sample Studio', invoice_number: 'R-DEV-0001', quote_number: 'Q-DEV-0001', total_amount: fakeMoney(total, 'CHF'), diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index ba587fc1..11dea9ad 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -3317,6 +3317,26 @@ "contract_fully_signed": { "title": "Vertrag beidseitig unterzeichnet", "description": "Beidseitiger Versand, sobald beide Unterschriften vorliegen. Gestempeltes Vertrags-PDF im Anhang." + }, + "event_reminder_default": { + "title": "Event-Erinnerung — Standard (Fallback)", + "description": "Vor-Event-Erinnerung, wenn keine kategoriespezifische Vorlage zutrifft." + }, + "event_reminder_wedding": { + "title": "Event-Erinnerung — Hochzeit", + "description": "Vor-Event-Erinnerung für Events mit event_type=wedding." + }, + "event_reminder_birthday": { + "title": "Event-Erinnerung — Geburtstag", + "description": "Vor-Event-Erinnerung für Events mit event_type=birthday." + }, + "event_reminder_corporate": { + "title": "Event-Erinnerung — Firma", + "description": "Vor-Event-Erinnerung für Events mit event_type=corporate." + }, + "event_reminder_other": { + "title": "Event-Erinnerung — Sonstiges", + "description": "Vor-Event-Erinnerung für Events mit event_type=other." } } } diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 2318b7fe..fce76b4b 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -3317,6 +3317,26 @@ "contract_fully_signed": { "title": "Contract fully signed (dual-party)", "description": "Dual-party send once both signatures are in. Stamped contract PDF attached." + }, + "event_reminder_default": { + "title": "Event reminder — default (catch-all)", + "description": "Pre-event nudge used when no per-category template matches." + }, + "event_reminder_wedding": { + "title": "Event reminder — wedding", + "description": "Pre-event nudge for events with event_type=wedding." + }, + "event_reminder_birthday": { + "title": "Event reminder — birthday", + "description": "Pre-event nudge for events with event_type=birthday." + }, + "event_reminder_corporate": { + "title": "Event reminder — corporate", + "description": "Pre-event nudge for events with event_type=corporate." + }, + "event_reminder_other": { + "title": "Event reminder — other", + "description": "Pre-event nudge for events with event_type=other." } } } diff --git a/frontend/src/pages/admin/clients/CrmDevelopmentPage.tsx b/frontend/src/pages/admin/clients/CrmDevelopmentPage.tsx index bfe47c8d..dcb6ee85 100644 --- a/frontend/src/pages/admin/clients/CrmDevelopmentPage.tsx +++ b/frontend/src/pages/admin/clients/CrmDevelopmentPage.tsx @@ -98,6 +98,36 @@ const TEMPLATE_LABEL_KEYS: Record< descKey: 'crmDev.templates.label.contract_fully_signed.description', descFallback: 'Dual-party send once both signatures are in. Stamped contract PDF attached.', }, + event_reminder_default: { + titleKey: 'crmDev.templates.label.event_reminder_default.title', + titleFallback: 'Event reminder — default (catch-all)', + descKey: 'crmDev.templates.label.event_reminder_default.description', + descFallback: 'Pre-event nudge used when no per-category template matches.', + }, + event_reminder_wedding: { + titleKey: 'crmDev.templates.label.event_reminder_wedding.title', + titleFallback: 'Event reminder — wedding', + descKey: 'crmDev.templates.label.event_reminder_wedding.description', + descFallback: 'Pre-event nudge for events with event_type=wedding.', + }, + event_reminder_birthday: { + titleKey: 'crmDev.templates.label.event_reminder_birthday.title', + titleFallback: 'Event reminder — birthday', + descKey: 'crmDev.templates.label.event_reminder_birthday.description', + descFallback: 'Pre-event nudge for events with event_type=birthday.', + }, + event_reminder_corporate: { + titleKey: 'crmDev.templates.label.event_reminder_corporate.title', + titleFallback: 'Event reminder — corporate', + descKey: 'crmDev.templates.label.event_reminder_corporate.description', + descFallback: 'Pre-event nudge for events with event_type=corporate.', + }, + event_reminder_other: { + titleKey: 'crmDev.templates.label.event_reminder_other.title', + titleFallback: 'Event reminder — other', + descKey: 'crmDev.templates.label.event_reminder_other.description', + descFallback: 'Pre-event nudge for events with event_type=other.', + }, }; export const CrmDevelopmentPage: React.FC = () => { diff --git a/frontend/src/services/devTools.service.ts b/frontend/src/services/devTools.service.ts index 726aec6d..8f9edb24 100644 --- a/frontend/src/services/devTools.service.ts +++ b/frontend/src/services/devTools.service.ts @@ -22,7 +22,16 @@ export type CrmEmailTemplateKey = // template_key strings. | 'contract_sent' | 'contract_signed_admin_notification' - | 'contract_fully_signed'; + | 'contract_fully_signed' + // Pre-event reminder emails (migration 143). One template per + // event_type with a catch-all default. Backend resolves + // `event_reminder_` then falls back to + // `event_reminder_default`. + | 'event_reminder_default' + | 'event_reminder_wedding' + | 'event_reminder_birthday' + | 'event_reminder_corporate' + | 'event_reminder_other'; export interface DevEmailTemplateStatus { key: CrmEmailTemplateKey;