@consilioweb/payload-support 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +303 -85
- package/dist/index.js +303 -85
- package/dist/styles/TicketDetail.module.scss +3 -1
- package/package.json +1 -1
- package/src/collections/KnowledgeBase.ts +29 -0
- package/src/collections/SupportClients.ts +115 -0
- package/src/collections/TicketMessages.ts +21 -3
- package/src/collections/Tickets.ts +22 -3
- package/src/collections/TimeEntries.ts +12 -0
- package/src/hooks/ticketStatusEmail.ts +38 -27
- package/src/styles/TicketDetail.module.scss +3 -1
- package/src/utils/emailTemplate.ts +153 -61
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* ConsilioWEB Support — email template system v2
|
|
3
|
+
*
|
|
4
|
+
* Design system: warm-neutral background, ink + teal accents.
|
|
5
|
+
* - ink #1d2b4d (primary text / headers)
|
|
6
|
+
* - teal #17807c (accent / links / reply family)
|
|
7
|
+
* - amber #c2410c (alert family — "action requise")
|
|
8
|
+
* - paper #faf8f5 (outer canvas) card #ffffff
|
|
9
|
+
* - sand #f4f1ec (quote / panel bg) line #e7e2d9 (hairlines)
|
|
10
|
+
*
|
|
11
|
+
* Goals: fewer, clearer emails. Each client email carries a visual "family"
|
|
12
|
+
* (eyebrow + accent) so the inbox reads at a glance, plus an explicit preheader.
|
|
13
|
+
*
|
|
14
|
+
* Public API is FROZEN — names & signatures of emailWrapper, emailButton,
|
|
15
|
+
* emailParagraph, emailQuote, emailRichContent, emailTrackingPixel,
|
|
16
|
+
* emailInfoRow, escapeHtml, createEmailTemplateFactory must not change.
|
|
5
17
|
*/
|
|
6
18
|
|
|
19
|
+
// ─── Design tokens ───────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const COLORS = {
|
|
22
|
+
ink: '#1d2b4d',
|
|
23
|
+
inkSoft: '#3a486a',
|
|
24
|
+
teal: '#17807c',
|
|
25
|
+
tealDark: '#0f5d5a',
|
|
26
|
+
amber: '#c2410c',
|
|
27
|
+
paper: '#faf8f5',
|
|
28
|
+
card: '#ffffff',
|
|
29
|
+
sand: '#f4f1ec',
|
|
30
|
+
line: '#e7e2d9',
|
|
31
|
+
text: '#2a3142',
|
|
32
|
+
muted: '#6b7280',
|
|
33
|
+
faint: '#9aa1ad',
|
|
34
|
+
} as const
|
|
35
|
+
|
|
7
36
|
// ─── Configuration ───────────────────────────────────────────────────
|
|
8
37
|
|
|
9
38
|
export interface EmailTemplateConfig {
|
|
@@ -18,19 +47,22 @@ export interface EmailTemplateConfig {
|
|
|
18
47
|
location?: string
|
|
19
48
|
/** Short brand initials shown in header badge (e.g. "CW", "AB") */
|
|
20
49
|
brandInitials?: string
|
|
50
|
+
/** Absolute URL of the client notification preferences page (footer link) */
|
|
51
|
+
preferencesUrl?: string
|
|
21
52
|
}
|
|
22
53
|
|
|
23
54
|
const DEFAULT_CONFIG: Required<EmailTemplateConfig> = {
|
|
24
55
|
brandName: 'Support',
|
|
25
|
-
brandColor:
|
|
26
|
-
secondaryColor:
|
|
27
|
-
accentColor:
|
|
56
|
+
brandColor: COLORS.teal,
|
|
57
|
+
secondaryColor: COLORS.ink,
|
|
58
|
+
accentColor: COLORS.amber,
|
|
28
59
|
logoUrl: '',
|
|
29
60
|
supportEmail: process.env.SUPPORT_EMAIL || '',
|
|
30
61
|
websiteUrl: process.env.NEXT_PUBLIC_SERVER_URL || '',
|
|
31
62
|
phone: '',
|
|
32
63
|
location: '',
|
|
33
64
|
brandInitials: '',
|
|
65
|
+
preferencesUrl: '',
|
|
34
66
|
}
|
|
35
67
|
|
|
36
68
|
function resolveConfig(config?: EmailTemplateConfig): Required<EmailTemplateConfig> {
|
|
@@ -117,34 +149,43 @@ export function emailRichContent(html: string, config?: EmailTemplateConfig): st
|
|
|
117
149
|
result = convertFencedCodeBlocks(result)
|
|
118
150
|
|
|
119
151
|
// Apply inline styles to elements
|
|
120
|
-
result = styleTag(result, 'blockquote', `border-left:
|
|
152
|
+
result = styleTag(result, 'blockquote', `border-left: 3px solid ${COLORS.teal}; margin: 16px 0; padding: 12px 20px; background: ${COLORS.sand}; border-radius: 0 8px 8px 0; color: ${COLORS.inkSoft};`)
|
|
121
153
|
result = styleTag(result, 'img', 'max-width: 100%; height: auto; border-radius: 8px; margin: 12px 0; display: block;')
|
|
122
|
-
result = styleTag(result, 'a', `color: ${
|
|
154
|
+
result = styleTag(result, 'a', `color: ${COLORS.teal}; text-decoration: underline; font-weight: 600;`)
|
|
123
155
|
result = styleTag(result, 'ul', 'margin: 8px 0; padding-left: 24px;')
|
|
124
156
|
result = styleTag(result, 'ol', 'margin: 8px 0; padding-left: 24px;')
|
|
125
|
-
result = styleTag(result, 'li',
|
|
126
|
-
result = styleTag(result, 'p',
|
|
157
|
+
result = styleTag(result, 'li', `margin: 4px 0; line-height: 1.6; color: ${COLORS.text};`)
|
|
158
|
+
result = styleTag(result, 'p', `margin: 0 0 12px 0; line-height: 1.75; font-size: 15px; color: ${COLORS.text};`)
|
|
127
159
|
|
|
128
|
-
// Strip dangerous content
|
|
160
|
+
// Strip dangerous content — keep this in sync with any rich-text source.
|
|
129
161
|
result = result
|
|
162
|
+
// <script>…</script> and <style>…</style> (including unclosed/nested-ish)
|
|
130
163
|
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
131
164
|
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
|
|
165
|
+
// Embedded frames / plugins — never valid in email, common XSS vector
|
|
166
|
+
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
|
167
|
+
.replace(/<iframe\b[^>]*\/?>/gi, '')
|
|
168
|
+
.replace(/<embed\b[^>]*\/?>/gi, '')
|
|
169
|
+
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
|
|
170
|
+
// Inline event handlers (onclick, onload, …)
|
|
132
171
|
.replace(/\bon\w+\s*=\s*"[^"]*"/gi, '')
|
|
133
172
|
.replace(/\bon\w+\s*=\s*'[^']*'/gi, '')
|
|
173
|
+
.replace(/\bon\w+\s*=\s*[^\s>]+/gi, '')
|
|
174
|
+
// javascript: URIs
|
|
134
175
|
.replace(/javascript:/gi, '')
|
|
135
176
|
|
|
136
|
-
return `<div style="font-size: 15px; line-height: 1.75; color:
|
|
177
|
+
return `<div style="font-size: 15px; line-height: 1.75; color: ${COLORS.text};">${result}</div>`
|
|
137
178
|
}
|
|
138
179
|
|
|
139
180
|
// ─── Configurable email components ───────────────────────────────────
|
|
140
181
|
|
|
141
182
|
type ButtonColor = 'primary' | 'secondary' | 'dark'
|
|
142
183
|
|
|
143
|
-
function getButtonColors(
|
|
184
|
+
function getButtonColors(_config: Required<EmailTemplateConfig>): Record<ButtonColor, { bg: string; text: string; border: string }> {
|
|
144
185
|
return {
|
|
145
|
-
primary: { bg:
|
|
146
|
-
secondary: { bg:
|
|
147
|
-
dark: { bg:
|
|
186
|
+
primary: { bg: COLORS.teal, text: '#ffffff', border: COLORS.teal },
|
|
187
|
+
secondary: { bg: COLORS.sand, text: COLORS.ink, border: COLORS.line },
|
|
188
|
+
dark: { bg: COLORS.ink, text: '#ffffff', border: COLORS.ink },
|
|
148
189
|
}
|
|
149
190
|
}
|
|
150
191
|
|
|
@@ -157,7 +198,7 @@ export function emailButton(text: string, url: string, color: ButtonColor = 'pri
|
|
|
157
198
|
const bc = colors[color]
|
|
158
199
|
return `
|
|
159
200
|
<div style="text-align: center; margin: 32px 0;">
|
|
160
|
-
<a href="${url}" style="display: inline-block; padding:
|
|
201
|
+
<a href="${url}" style="display: inline-block; padding: 14px 36px; background: ${bc.bg}; color: ${bc.text}; font-weight: 700; font-size: 15px; text-decoration: none; border-radius: 10px; border: 1px solid ${bc.border}; letter-spacing: 0.01em;">
|
|
161
202
|
${text}
|
|
162
203
|
</a>
|
|
163
204
|
</div>
|
|
@@ -181,8 +222,9 @@ function renderCodeBlockHtml(lang: string, code: string): string {
|
|
|
181
222
|
* Quote block for message previews — supports fenced code blocks.
|
|
182
223
|
*/
|
|
183
224
|
export function emailQuote(content: string, borderColor?: string, config?: EmailTemplateConfig): string {
|
|
184
|
-
|
|
185
|
-
const color = borderColor ||
|
|
225
|
+
resolveConfig(config)
|
|
226
|
+
const color = borderColor || COLORS.teal
|
|
227
|
+
const panel = `margin: 24px 0; padding: 18px 22px; background: ${COLORS.sand}; border-left: 3px solid ${color}; border-radius: 0 8px 8px 0;`
|
|
186
228
|
|
|
187
229
|
// If content has fenced code blocks, render them as styled blocks
|
|
188
230
|
if (content && content.includes('```')) {
|
|
@@ -194,7 +236,7 @@ export function emailQuote(content: string, borderColor?: string, config?: Email
|
|
|
194
236
|
// Text before the code block
|
|
195
237
|
const textBefore = content.slice(lastIndex, match.index).trim()
|
|
196
238
|
if (textBefore) {
|
|
197
|
-
parts.push(`<p style="margin: 0 0 12px 0; font-size: 15px; line-height: 1.75; color:
|
|
239
|
+
parts.push(`<p style="margin: 0 0 12px 0; font-size: 15px; line-height: 1.75; color: ${COLORS.inkSoft}; white-space: pre-wrap;">${escapeHtml(textBefore)}</p>`)
|
|
198
240
|
}
|
|
199
241
|
parts.push(renderCodeBlockHtml(match[1] || '', match[2] || ''))
|
|
200
242
|
lastIndex = match.index + match[0].length
|
|
@@ -202,18 +244,18 @@ export function emailQuote(content: string, borderColor?: string, config?: Email
|
|
|
202
244
|
// Remaining text after the last code block
|
|
203
245
|
const textAfter = content.slice(lastIndex).trim()
|
|
204
246
|
if (textAfter) {
|
|
205
|
-
parts.push(`<p style="margin: 12px 0 0 0; font-size: 15px; line-height: 1.75; color:
|
|
247
|
+
parts.push(`<p style="margin: 12px 0 0 0; font-size: 15px; line-height: 1.75; color: ${COLORS.inkSoft}; white-space: pre-wrap;">${escapeHtml(textAfter)}</p>`)
|
|
206
248
|
}
|
|
207
249
|
return `
|
|
208
|
-
<div style="
|
|
250
|
+
<div style="${panel}">
|
|
209
251
|
${parts.join('')}
|
|
210
252
|
</div>
|
|
211
253
|
`
|
|
212
254
|
}
|
|
213
255
|
|
|
214
256
|
return `
|
|
215
|
-
<div style="
|
|
216
|
-
<p style="margin: 0; font-size: 15px; line-height: 1.75; color:
|
|
257
|
+
<div style="${panel}">
|
|
258
|
+
<p style="margin: 0; font-size: 15px; line-height: 1.75; color: ${COLORS.inkSoft}; white-space: pre-wrap;">${escapeHtml(content)}</p>
|
|
217
259
|
</div>
|
|
218
260
|
`
|
|
219
261
|
}
|
|
@@ -224,8 +266,8 @@ export function emailQuote(content: string, borderColor?: string, config?: Email
|
|
|
224
266
|
export function emailInfoRow(label: string, value: string): string {
|
|
225
267
|
return `
|
|
226
268
|
<tr>
|
|
227
|
-
<td style="padding: 8px 0; font-
|
|
228
|
-
<td style="padding: 8px 0; font-size: 15px; color:
|
|
269
|
+
<td style="padding: 8px 0; font-weight: 700; color: ${COLORS.muted}; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em; font-size: 11px;">${escapeHtml(label)}</td>
|
|
270
|
+
<td style="padding: 8px 0; font-size: 15px; color: ${COLORS.text}; line-height: 1.5;">${value}</td>
|
|
229
271
|
</tr>
|
|
230
272
|
`
|
|
231
273
|
}
|
|
@@ -234,7 +276,28 @@ export function emailInfoRow(label: string, value: string): string {
|
|
|
234
276
|
* Paragraph helper with professional styling
|
|
235
277
|
*/
|
|
236
278
|
export function emailParagraph(text: string): string {
|
|
237
|
-
return `<p style="margin: 0 0 18px 0; font-size: 15px; line-height: 1.75; color:
|
|
279
|
+
return `<p style="margin: 0 0 18px 0; font-size: 15px; line-height: 1.75; color: ${COLORS.text};">${text}</p>`
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─── Email families (eyebrow + accent) ───────────────────────────────
|
|
283
|
+
|
|
284
|
+
export type EmailKind = 'reply' | 'system' | 'alert'
|
|
285
|
+
|
|
286
|
+
interface KindStyle {
|
|
287
|
+
eyebrow: string
|
|
288
|
+
accent: string
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getKindStyle(kind: EmailKind): KindStyle {
|
|
292
|
+
switch (kind) {
|
|
293
|
+
case 'reply':
|
|
294
|
+
return { eyebrow: 'Réponse de votre équipe', accent: COLORS.teal }
|
|
295
|
+
case 'alert':
|
|
296
|
+
return { eyebrow: 'Action requise', accent: COLORS.amber }
|
|
297
|
+
case 'system':
|
|
298
|
+
default:
|
|
299
|
+
return { eyebrow: 'Notification automatique', accent: COLORS.inkSoft }
|
|
300
|
+
}
|
|
238
301
|
}
|
|
239
302
|
|
|
240
303
|
/**
|
|
@@ -245,41 +308,49 @@ function emailFooter(config: Required<EmailTemplateConfig>): string {
|
|
|
245
308
|
? `<a href="${config.websiteUrl}">
|
|
246
309
|
<img src="${config.logoUrl}" alt="${escapeHtml(config.brandName)}" width="100" height="47" style="display: block; border: 0;" />
|
|
247
310
|
</a>`
|
|
248
|
-
: `<a href="${config.websiteUrl}" style="font-size:
|
|
311
|
+
: `<a href="${config.websiteUrl}" style="font-size: 16px; font-weight: 800; color: ${COLORS.ink}; text-decoration: none; letter-spacing: -0.01em;">${escapeHtml(config.brandName)}</a>`
|
|
249
312
|
|
|
250
313
|
const contactParts: string[] = []
|
|
251
314
|
if (config.supportEmail) {
|
|
252
|
-
contactParts.push(`<a href="mailto:${config.supportEmail}" style="color:
|
|
315
|
+
contactParts.push(`<a href="mailto:${config.supportEmail}" style="color: ${COLORS.muted}; text-decoration: none;">${config.supportEmail}</a>`)
|
|
253
316
|
}
|
|
254
317
|
if (config.phone) {
|
|
255
|
-
contactParts.push(`<a href="tel:${config.phone.replace(/\s/g, '')}" style="color:
|
|
318
|
+
contactParts.push(`<a href="tel:${config.phone.replace(/\s/g, '')}" style="color: ${COLORS.muted}; text-decoration: none;">${config.phone}</a>`)
|
|
256
319
|
}
|
|
257
320
|
|
|
258
321
|
const locationParts: string[] = []
|
|
259
322
|
if (config.websiteUrl) {
|
|
260
323
|
const displayUrl = config.websiteUrl.replace(/^https?:\/\//, '')
|
|
261
|
-
locationParts.push(`<a href="${config.websiteUrl}" style="color: ${
|
|
324
|
+
locationParts.push(`<a href="${config.websiteUrl}" style="color: ${COLORS.teal}; text-decoration: none; font-weight: 600;">${displayUrl}</a>`)
|
|
262
325
|
}
|
|
263
326
|
if (config.location) {
|
|
264
327
|
locationParts.push(config.location)
|
|
265
328
|
}
|
|
266
329
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
330
|
+
// "Gérer mes notifications" link — points at the client preferences page
|
|
331
|
+
// when available, otherwise falls back to a mailto so the link is never dead.
|
|
332
|
+
const manageUrl = config.preferencesUrl
|
|
333
|
+
|| (config.websiteUrl ? `${config.websiteUrl}/support/profile` : '')
|
|
334
|
+
|| (config.supportEmail ? `mailto:${config.supportEmail}?subject=Notifications` : '')
|
|
335
|
+
const preferencesLinks: string[] = []
|
|
336
|
+
if (manageUrl) {
|
|
337
|
+
preferencesLinks.push(`<a href="${manageUrl}" style="color: ${COLORS.muted}; text-decoration: underline;">Gérer mes notifications</a>`)
|
|
338
|
+
}
|
|
339
|
+
if (config.supportEmail) {
|
|
340
|
+
preferencesLinks.push(`<a href="mailto:${config.supportEmail}?subject=Unsubscribe" style="color: ${COLORS.faint}; text-decoration: underline;">Se désinscrire</a>`)
|
|
341
|
+
}
|
|
342
|
+
const preferencesHtml = preferencesLinks.length > 0
|
|
343
|
+
? `<p style="margin: 10px 0 0 0; font-size: 11px; color: ${COLORS.faint}; line-height: 1.5;">${preferencesLinks.join(' · ')}</p>`
|
|
271
344
|
: ''
|
|
272
345
|
|
|
273
346
|
return `
|
|
274
347
|
<!-- Spacer -->
|
|
275
|
-
<div style="height:
|
|
348
|
+
<div style="height: 28px;"></div>
|
|
276
349
|
|
|
277
|
-
<!--
|
|
278
|
-
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom:
|
|
350
|
+
<!-- Hairline separator -->
|
|
351
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 22px;">
|
|
279
352
|
<tr>
|
|
280
|
-
<td
|
|
281
|
-
<td width="25%" height="3" bgcolor="${config.secondaryColor}" style="font-size:1px;line-height:1px;"> </td>
|
|
282
|
-
<td width="25%" height="3" bgcolor="${config.accentColor}" style="font-size:1px;line-height:1px;"> </td>
|
|
353
|
+
<td height="1" bgcolor="${COLORS.line}" style="font-size:1px;line-height:1px;"> </td>
|
|
283
354
|
</tr>
|
|
284
355
|
</table>
|
|
285
356
|
|
|
@@ -290,10 +361,10 @@ function emailFooter(config: Required<EmailTemplateConfig>): string {
|
|
|
290
361
|
${logoHtml}
|
|
291
362
|
</td>
|
|
292
363
|
<td valign="top">
|
|
293
|
-
<p style="margin: 0; font-size:
|
|
294
|
-
${contactParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 13px; color:
|
|
295
|
-
${locationParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 12px; color:
|
|
296
|
-
${
|
|
364
|
+
<p style="margin: 0; font-size: 13px; font-weight: 700; color: ${COLORS.ink}; letter-spacing: 0.01em;">${escapeHtml(config.brandName)}</p>
|
|
365
|
+
${contactParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 13px; color: ${COLORS.muted}; line-height: 1.5;">${contactParts.join(' · ')}</p>` : ''}
|
|
366
|
+
${locationParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 12px; color: ${COLORS.faint}; line-height: 1.4;">${locationParts.join(' · ')}</p>` : ''}
|
|
367
|
+
${preferencesHtml}
|
|
297
368
|
</td>
|
|
298
369
|
</tr>
|
|
299
370
|
</table>
|
|
@@ -301,10 +372,21 @@ function emailFooter(config: Required<EmailTemplateConfig>): string {
|
|
|
301
372
|
}
|
|
302
373
|
|
|
303
374
|
interface EmailOptions {
|
|
304
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* Header background color variant.
|
|
377
|
+
* @deprecated Prefer `kind` for client emails — it drives the visual family
|
|
378
|
+
* (eyebrow + accent). `headerColor` is kept for backward compatibility.
|
|
379
|
+
*/
|
|
305
380
|
headerColor?: 'primary' | 'secondary'
|
|
306
|
-
/** Preheader text (hidden preview in
|
|
381
|
+
/** Preheader text (hidden preview shown in the inbox list) */
|
|
307
382
|
preheader?: string
|
|
383
|
+
/**
|
|
384
|
+
* Visual family of the email:
|
|
385
|
+
* - 'reply' → "Réponse de votre équipe" (teal) — human answer
|
|
386
|
+
* - 'system' → "Notification automatique" (ink) — status / confirmation
|
|
387
|
+
* - 'alert' → "Action requise" (amber) — reminder / needs client
|
|
388
|
+
*/
|
|
389
|
+
kind?: EmailKind
|
|
308
390
|
}
|
|
309
391
|
|
|
310
392
|
/**
|
|
@@ -312,13 +394,18 @@ interface EmailOptions {
|
|
|
312
394
|
*/
|
|
313
395
|
export function emailWrapper(title: string, body: string, options: EmailOptions = {}, config?: EmailTemplateConfig): string {
|
|
314
396
|
const c = resolveConfig(config)
|
|
315
|
-
const {
|
|
316
|
-
const
|
|
397
|
+
const { preheader, kind } = options
|
|
398
|
+
const kindStyle = kind ? getKindStyle(kind) : null
|
|
399
|
+
const accent = kindStyle?.accent || COLORS.teal
|
|
400
|
+
|
|
401
|
+
const eyebrowHtml = kindStyle
|
|
402
|
+
? `<p style="margin: 0 0 6px 0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: ${kindStyle.accent};">${escapeHtml(kindStyle.eyebrow)}</p>`
|
|
403
|
+
: ''
|
|
317
404
|
|
|
318
405
|
const badgeHtml = c.brandInitials
|
|
319
406
|
? `<td width="48" align="right" valign="middle">
|
|
320
|
-
<div style="width:
|
|
321
|
-
<span style="color: #fff; font-weight:
|
|
407
|
+
<div style="width: 38px; height: 38px; border-radius: 10px; background: ${COLORS.ink}; display: inline-block; text-align: center; line-height: 38px;">
|
|
408
|
+
<span style="color: #fff; font-weight: 800; font-size: 14px; letter-spacing: 0.04em;">${escapeHtml(c.brandInitials)}</span>
|
|
322
409
|
</div>
|
|
323
410
|
</td>`
|
|
324
411
|
: ''
|
|
@@ -335,23 +422,29 @@ export function emailWrapper(title: string, body: string, options: EmailOptions
|
|
|
335
422
|
</style>
|
|
336
423
|
<![endif]-->
|
|
337
424
|
</head>
|
|
338
|
-
<body style="margin: 0; padding: 0; background:
|
|
339
|
-
${preheader ? `<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">${escapeHtml(preheader)}
|
|
425
|
+
<body style="margin: 0; padding: 0; background: ${COLORS.paper}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;">
|
|
426
|
+
${preheader ? `<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all; font-size: 1px; line-height: 1px; color: ${COLORS.paper};">${escapeHtml(preheader)} ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>` : ''}
|
|
340
427
|
|
|
341
428
|
<!-- Outer wrapper -->
|
|
342
|
-
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background:
|
|
429
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background: ${COLORS.paper}; padding: 40px 16px;">
|
|
343
430
|
<tr>
|
|
344
431
|
<td align="center">
|
|
345
|
-
<!-- Main container —
|
|
346
|
-
<table cellpadding="0" cellspacing="0" border="0" width="
|
|
432
|
+
<!-- Main container — 640px -->
|
|
433
|
+
<table cellpadding="0" cellspacing="0" border="0" width="640" style="max-width: 640px; width: 100%;">
|
|
434
|
+
|
|
435
|
+
<!-- Accent rule -->
|
|
436
|
+
<tr>
|
|
437
|
+
<td style="height: 4px; background: ${accent}; border-radius: 12px 12px 0 0; font-size: 1px; line-height: 1px;"> </td>
|
|
438
|
+
</tr>
|
|
347
439
|
|
|
348
440
|
<!-- Header -->
|
|
349
441
|
<tr>
|
|
350
|
-
<td style="background: ${
|
|
442
|
+
<td style="background: ${COLORS.card}; padding: 30px 40px 22px 40px; border: 1px solid ${COLORS.line}; border-top: none; border-bottom: none;">
|
|
351
443
|
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
|
352
444
|
<tr>
|
|
353
|
-
<td>
|
|
354
|
-
|
|
445
|
+
<td valign="middle">
|
|
446
|
+
${eyebrowHtml}
|
|
447
|
+
<h1 style="margin: 0; color: ${COLORS.ink}; font-size: 21px; font-weight: 800; line-height: 1.3; letter-spacing: -0.01em;">
|
|
355
448
|
${escapeHtml(title)}
|
|
356
449
|
</h1>
|
|
357
450
|
</td>
|
|
@@ -363,7 +456,7 @@ export function emailWrapper(title: string, body: string, options: EmailOptions
|
|
|
363
456
|
|
|
364
457
|
<!-- Body -->
|
|
365
458
|
<tr>
|
|
366
|
-
<td style="background:
|
|
459
|
+
<td style="background: ${COLORS.card}; padding: 8px 40px 40px 40px; border: 1px solid ${COLORS.line}; border-top: none; border-radius: 0 0 12px 12px;">
|
|
367
460
|
${body}
|
|
368
461
|
${emailFooter(c)}
|
|
369
462
|
</td>
|
|
@@ -397,7 +490,6 @@ export interface EmailTemplateFactory {
|
|
|
397
490
|
* @example
|
|
398
491
|
* const email = createEmailTemplateFactory({
|
|
399
492
|
* brandName: 'MyBrand',
|
|
400
|
-
* brandColor: '#FF5733',
|
|
401
493
|
* supportEmail: 'help@mybrand.com',
|
|
402
494
|
* websiteUrl: 'https://mybrand.com',
|
|
403
495
|
* logoUrl: 'https://mybrand.com/logo.png',
|