From 0a30b2fb3a8f7ea56ec36cc7c3f8f58b12cc81de Mon Sep 17 00:00:00 2001 From: Marc Sportiello Date: Fri, 17 Jul 2026 16:25:12 +0200 Subject: [PATCH] feat(templates): add support for HTML templates --- components/email/email-composer.tsx | 2 +- components/templates/placeholder-fill-modal.tsx | 2 +- components/templates/template-form.tsx | 12 +++++++++++- lib/__tests__/template-utils.test.ts | 1 + lib/template-types.ts | 1 + lib/template-utils.ts | 3 ++- 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index f0249c76..dda1f790 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -1092,7 +1092,7 @@ export function EmailComposer({ : template.body; // In plain text mode, use template body as-is; otherwise convert to HTML - const bodyContent = plainTextMode + const bodyContent = plainTextMode || template.isHTML ? filledBody : `

${filledBody.replace(/&/g, '&').replace(//g, '>').replace(/\n/g, '
')}

`; diff --git a/components/templates/placeholder-fill-modal.tsx b/components/templates/placeholder-fill-modal.tsx index cdad6e80..6db51c6d 100644 --- a/components/templates/placeholder-fill-modal.tsx +++ b/components/templates/placeholder-fill-modal.tsx @@ -94,7 +94,7 @@ export function PlaceholderFillModal({

{t('preview')}

- {preview} + {template.isHTML ?
: preview}
)} diff --git a/components/templates/template-form.tsx b/components/templates/template-form.tsx index f4514508..71c60a37 100644 --- a/components/templates/template-form.tsx +++ b/components/templates/template-form.tsx @@ -4,7 +4,7 @@ import { useState, useMemo } from 'react'; import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import { Star, Plus } from 'lucide-react'; +import { Star, Plus, Square, CheckSquare } from 'lucide-react'; import { cn } from '@/lib/utils'; import { validateTemplateName } from '@/lib/template-utils'; import { BUILT_IN_PLACEHOLDERS } from '@/lib/template-types'; @@ -38,6 +38,7 @@ export function TemplateForm({ template, initialData, onSave, onCancel }: Templa const [category, setCategory] = useState(template?.category || ''); const [subject, setSubject] = useState(template?.subject || initialData?.subject || ''); const [body, setBody] = useState(template?.body || initialData?.body || ''); + const [isHTML, setIsHTML] = useState(template?.isHTML || false); const [toRecipients, setToRecipients] = useState( template?.defaultRecipients?.to?.join(', ') || initialData?.to?.join(', ') || '' ); @@ -76,6 +77,7 @@ export function TemplateForm({ template, initialData, onSave, onCancel }: Templa name: name.trim(), subject, body, + isHTML, category: category.trim(), defaultRecipients: to.length || cc.length || bcc.length ? { to: to.length ? to : undefined, cc: cc.length ? cc : undefined, bcc: bcc.length ? bcc : undefined } @@ -190,6 +192,14 @@ export function TemplateForm({ template, initialData, onSave, onCancel }: Templa rows={6} className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary resize-y" /> +
diff --git a/lib/__tests__/template-utils.test.ts b/lib/__tests__/template-utils.test.ts index 1a4fd8c4..32162a18 100644 --- a/lib/__tests__/template-utils.test.ts +++ b/lib/__tests__/template-utils.test.ts @@ -20,6 +20,7 @@ function makeTemplate(overrides: Partial = {}): EmailTemplate { name: 'Test Template', subject: '', body: '', + isHTML: false, category: '', isFavorite: false, createdAt: '2026-01-01T00:00:00Z', diff --git a/lib/template-types.ts b/lib/template-types.ts index 10cce5c0..b25eb053 100644 --- a/lib/template-types.ts +++ b/lib/template-types.ts @@ -3,6 +3,7 @@ export interface EmailTemplate { name: string; subject: string; body: string; + isHTML?: boolean; category: string; defaultRecipients?: { to?: string[]; diff --git a/lib/template-utils.ts b/lib/template-utils.ts index ca12ffc6..c956f6a8 100644 --- a/lib/template-utils.ts +++ b/lib/template-utils.ts @@ -173,7 +173,8 @@ export function importTemplates(json: string): ImportResult { id: generateUUID(), name: sanitizeText(t.name), subject: sanitizeText(t.subject), - body: sanitizeText(t.body), + body: t.isHTML ? String(t.body || '') : sanitizeText(t.body), + isHTML: Boolean(t.isHTML), category: sanitizeText(t.category), defaultRecipients: recipients && typeof recipients === 'object' ? {