@beechcms/api 0.4.0-preview.12 → 0.4.0-preview.13
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/assets/dashboard/BeechLogo.svg +18 -18
- package/assets/dashboard/BeechLogoLIght.svg +48 -48
- package/assets/dashboard/assets/index-CewtCjom.css +1 -0
- package/assets/dashboard/assets/{index-CTSuGxlX.js → index-FQ6JhvRH.js} +99 -99
- package/assets/dashboard/beechLogoDark.svg +48 -48
- package/assets/dashboard/index.html +18 -18
- package/assets/dashboard/sol.svg +3 -3
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
- package/migrations/0000_v040_base.sql +213 -213
- package/package.json +2 -2
- package/src/auth/constants.ts +10 -10
- package/src/auth/login.ts +91 -91
- package/src/auth/refresh.ts +127 -127
- package/src/factory.ts +347 -325
- package/src/features/content/constants.ts +10 -10
- package/src/features/content/handlers/create.ts +153 -163
- package/src/features/content/handlers/delete.ts +76 -85
- package/src/features/content/handlers/facets.ts +45 -45
- package/src/features/content/handlers/get.ts +116 -116
- package/src/features/content/handlers/list.ts +88 -88
- package/src/features/content/handlers/update.ts +207 -216
- package/src/features/content/index.ts +20 -20
- package/src/features/draft/draft.handler.ts +272 -272
- package/src/features/draft/index.ts +1 -1
- package/src/features/email/email.provider.ts +38 -38
- package/src/features/email/email.service.ts +80 -80
- package/src/features/email/email.types.ts +98 -98
- package/src/features/email/index.ts +28 -28
- package/src/features/email/providers/resend.ts +63 -63
- package/src/features/email/templates/password-changed.ts +59 -59
- package/src/features/email/templates/password-reset.ts +64 -64
- package/src/features/email/templates/shell.ts +92 -93
- package/src/features/notifications/index.ts +1 -1
- package/src/features/notifications/notifications.handler.ts +130 -88
- package/src/features/password-reset/index.ts +15 -15
- package/src/features/password-reset/request.ts +106 -88
- package/src/features/password-reset/reset.ts +128 -110
- package/src/features/rotate-field/index.ts +1 -1
- package/src/features/rotate-field/rotate-field.handler.ts +132 -82
- package/src/features/rotate-field/rotate-field.schema.ts +13 -9
- package/src/features/schema/schema.handler.ts +16 -16
- package/src/features/settings/settings.handler.ts +414 -267
- package/src/features/setup/index.ts +96 -59
- package/src/features/stats/index.ts +1 -1
- package/src/features/stats/stats.handler.ts +458 -395
- package/src/index.ts +24 -24
- package/src/media-utils.ts +78 -78
- package/src/middleware/repository.middleware.ts +24 -18
- package/src/middleware/storage.middleware.ts +21 -0
- package/src/middleware.ts +67 -67
- package/src/public/access-policy.ts +23 -23
- package/src/public/api-key-middleware.ts +53 -53
- package/src/public/index.ts +12 -12
- package/src/public/problem-details.ts +48 -42
- package/src/public/public-add.ts +166 -166
- package/src/public/public-edit.ts +158 -158
- package/src/public/public-errors.ts +15 -15
- package/src/public/public-read.ts +216 -216
- package/src/public/public-routes.ts +84 -31
- package/src/public/query-builder.ts +152 -152
- package/src/public/rate-limit-middleware.ts +42 -42
- package/src/public/response-builder.ts +26 -26
- package/src/public/sanitize.ts +65 -65
- package/src/public/slug-utils.ts +14 -14
- package/src/search-utils.ts +192 -192
- package/src/search.ts +72 -72
- package/src/shared/activity-logger.ts +79 -79
- package/src/shared/apply-policies.ts +63 -63
- package/src/shared/base.repository.d1.ts +28 -28
- package/src/shared/content-utils.ts +82 -108
- package/src/shared/content.repository.d1.ts +382 -382
- package/src/shared/fts-sync.ts +4 -4
- package/src/shared/idempotency.repository.d1.ts +56 -45
- package/src/shared/media.repository.d1.ts +64 -0
- package/src/shared/notification-service.ts +56 -56
- package/src/shared/query-utils.ts +137 -137
- package/src/shared/storage/factory.ts +40 -0
- package/src/shared/storage/r2-binding-bucket.ts +81 -0
- package/src/shared/storage/s3-bucket.ts +163 -0
- package/src/shared/storage-utils.ts +36 -36
- package/src/shared/system-stats.repository.d1.ts +44 -0
- package/src/types.ts +46 -41
- package/src/upload.ts +179 -331
- package/src/widget.ts +349 -349
- package/assets/dashboard/assets/index-ye3325L9.css +0 -1
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import type { EmailLocale } from '../email.types'
|
|
2
|
-
import { buildEmailShell } from './shell'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* ───
|
|
8
|
-
*
|
|
9
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
-
*/
|
|
11
|
-
const COPY: Record<EmailLocale, {
|
|
12
|
-
subject: string
|
|
13
|
-
title: string
|
|
14
|
-
body: string
|
|
15
|
-
warning: string
|
|
16
|
-
footer: string
|
|
17
|
-
}> = {
|
|
18
|
-
en: {
|
|
19
|
-
subject: 'Your Beech CMS password has been changed',
|
|
20
|
-
title: 'Your password has been changed',
|
|
21
|
-
body: 'Your Beech CMS account password was successfully changed. If you made this change, no action is needed.',
|
|
22
|
-
warning:
|
|
23
|
-
'If you did not make this change, your account may be compromised. Contact your administrator immediately.',
|
|
24
|
-
footer: 'This is an automated security notification. Do not reply to this email.',
|
|
25
|
-
},
|
|
26
|
-
it: {
|
|
27
|
-
subject: 'La tua password Beech CMS è stata modificata',
|
|
28
|
-
title: 'La tua password è stata modificata',
|
|
29
|
-
body: 'La password del tuo account Beech CMS è stata modificata con successo. Se hai effettuato tu questa modifica, non devi fare nulla.',
|
|
30
|
-
warning:
|
|
31
|
-
'Se non hai effettuato tu questa modifica, il tuo account potrebbe essere compromesso. Contatta immediatamente il tuo amministratore.',
|
|
32
|
-
footer: 'Questa è una notifica di sicurezza automatica. Non rispondere a questa email.',
|
|
33
|
-
},
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param locale -
|
|
44
|
-
* @returns
|
|
45
|
-
*/
|
|
46
|
-
export function buildPasswordChangedEmail(
|
|
47
|
-
locale: EmailLocale,
|
|
48
|
-
): { subject: string; html: string } {
|
|
49
|
-
const c = COPY[locale]
|
|
50
|
-
return {
|
|
51
|
-
subject: c.subject,
|
|
52
|
-
html: buildEmailShell(locale, {
|
|
53
|
-
title: c.title,
|
|
54
|
-
body: c.body,
|
|
55
|
-
warning: c.warning,
|
|
56
|
-
footer: c.footer,
|
|
57
|
-
}),
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
import type { EmailLocale } from '../email.types'
|
|
2
|
+
import { buildEmailShell } from './shell'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Localized texts for the "password changed" security notification.
|
|
6
|
+
*
|
|
7
|
+
* ─── ADDING A NEW LANGUAGE ──────────────────────────────────────────────────
|
|
8
|
+
* See instructions in `templates/password-reset.ts`.
|
|
9
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
*/
|
|
11
|
+
const COPY: Record<EmailLocale, {
|
|
12
|
+
subject: string
|
|
13
|
+
title: string
|
|
14
|
+
body: string
|
|
15
|
+
warning: string
|
|
16
|
+
footer: string
|
|
17
|
+
}> = {
|
|
18
|
+
en: {
|
|
19
|
+
subject: 'Your Beech CMS password has been changed',
|
|
20
|
+
title: 'Your password has been changed',
|
|
21
|
+
body: 'Your Beech CMS account password was successfully changed. If you made this change, no action is needed.',
|
|
22
|
+
warning:
|
|
23
|
+
'If you did not make this change, your account may be compromised. Contact your administrator immediately.',
|
|
24
|
+
footer: 'This is an automated security notification. Do not reply to this email.',
|
|
25
|
+
},
|
|
26
|
+
it: {
|
|
27
|
+
subject: 'La tua password Beech CMS è stata modificata',
|
|
28
|
+
title: 'La tua password è stata modificata',
|
|
29
|
+
body: 'La password del tuo account Beech CMS è stata modificata con successo. Se hai effettuato tu questa modifica, non devi fare nulla.',
|
|
30
|
+
warning:
|
|
31
|
+
'Se non hai effettuato tu questa modifica, il tuo account potrebbe essere compromesso. Contatta immediatamente il tuo amministratore.',
|
|
32
|
+
footer: 'Questa è una notifica di sicurezza automatica. Non rispondere a questa email.',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Builds the "password changed" notification email.
|
|
38
|
+
*
|
|
39
|
+
* This email does not have a CTA button — it is a pure security notification.
|
|
40
|
+
* The `warning` block (red text) warns the user to take action if they
|
|
41
|
+
* were not the one who changed the password.
|
|
42
|
+
*
|
|
43
|
+
* @param locale - Language for the email subject and body.
|
|
44
|
+
* @returns Object with `subject` (string) and `html` (complete HTML document).
|
|
45
|
+
*/
|
|
46
|
+
export function buildPasswordChangedEmail(
|
|
47
|
+
locale: EmailLocale,
|
|
48
|
+
): { subject: string; html: string } {
|
|
49
|
+
const c = COPY[locale]
|
|
50
|
+
return {
|
|
51
|
+
subject: c.subject,
|
|
52
|
+
html: buildEmailShell(locale, {
|
|
53
|
+
title: c.title,
|
|
54
|
+
body: c.body,
|
|
55
|
+
warning: c.warning,
|
|
56
|
+
footer: c.footer,
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import type { EmailLocale } from '../email.types'
|
|
2
|
-
import { buildEmailShell } from './shell'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* ───
|
|
8
|
-
* 1.
|
|
9
|
-
* 2.
|
|
10
|
-
* TypeScript
|
|
11
|
-
* `Record<EmailLocale, …>`.
|
|
12
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
-
*/
|
|
14
|
-
const COPY: Record<EmailLocale, {
|
|
15
|
-
subject: string
|
|
16
|
-
title: string
|
|
17
|
-
body: string
|
|
18
|
-
ctaLabel: string
|
|
19
|
-
footer: string
|
|
20
|
-
}> = {
|
|
21
|
-
en: {
|
|
22
|
-
subject: 'Reset your Beech CMS password',
|
|
23
|
-
title: 'Reset your password',
|
|
24
|
-
body: 'You requested a password reset for your Beech CMS account. Click the button below to set a new password. This link expires in 30 minutes.',
|
|
25
|
-
ctaLabel: 'Reset password',
|
|
26
|
-
footer: "If you didn't request this, you can safely ignore this email.",
|
|
27
|
-
},
|
|
28
|
-
it: {
|
|
29
|
-
subject: 'Reimposta la tua password Beech CMS',
|
|
30
|
-
title: 'Reimposta la tua password',
|
|
31
|
-
body: 'Hai richiesto il reset della password per il tuo account Beech CMS. Clicca il pulsante qui sotto per impostare una nuova password. Questo link scade tra 30 minuti.',
|
|
32
|
-
ctaLabel: 'Reimposta password',
|
|
33
|
-
footer: 'Se non hai richiesto questo, puoi ignorare questa email in tutta sicurezza.',
|
|
34
|
-
},
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param resetUrl - URL
|
|
44
|
-
* `https://dashboard.beechcms.dev/reset-password?token=<uuid>`.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* @param locale -
|
|
48
|
-
* @returns
|
|
49
|
-
*/
|
|
50
|
-
export function buildPasswordResetEmail(
|
|
51
|
-
resetUrl: string,
|
|
52
|
-
locale: EmailLocale,
|
|
53
|
-
): { subject: string; html: string } {
|
|
54
|
-
const c = COPY[locale]
|
|
55
|
-
return {
|
|
56
|
-
subject: c.subject,
|
|
57
|
-
html: buildEmailShell(locale, {
|
|
58
|
-
title: c.title,
|
|
59
|
-
body: c.body,
|
|
60
|
-
cta: { label: c.ctaLabel, href: resetUrl },
|
|
61
|
-
footer: c.footer,
|
|
62
|
-
}),
|
|
63
|
-
}
|
|
64
|
-
}
|
|
1
|
+
import type { EmailLocale } from '../email.types'
|
|
2
|
+
import { buildEmailShell } from './shell'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Localized texts for the password reset email.
|
|
6
|
+
*
|
|
7
|
+
* ─── ADDING A NEW LANGUAGE ──────────────────────────────────────────────────
|
|
8
|
+
* 1. Add the ISO code in `SUPPORTED_EMAIL_LOCALES` (`email.types.ts`).
|
|
9
|
+
* 2. Add a corresponding key in this object with all fields.
|
|
10
|
+
* TypeScript immediately reports missing keys thanks to
|
|
11
|
+
* `Record<EmailLocale, …>`.
|
|
12
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
*/
|
|
14
|
+
const COPY: Record<EmailLocale, {
|
|
15
|
+
subject: string
|
|
16
|
+
title: string
|
|
17
|
+
body: string
|
|
18
|
+
ctaLabel: string
|
|
19
|
+
footer: string
|
|
20
|
+
}> = {
|
|
21
|
+
en: {
|
|
22
|
+
subject: 'Reset your Beech CMS password',
|
|
23
|
+
title: 'Reset your password',
|
|
24
|
+
body: 'You requested a password reset for your Beech CMS account. Click the button below to set a new password. This link expires in 30 minutes.',
|
|
25
|
+
ctaLabel: 'Reset password',
|
|
26
|
+
footer: "If you didn't request this, you can safely ignore this email.",
|
|
27
|
+
},
|
|
28
|
+
it: {
|
|
29
|
+
subject: 'Reimposta la tua password Beech CMS',
|
|
30
|
+
title: 'Reimposta la tua password',
|
|
31
|
+
body: 'Hai richiesto il reset della password per il tuo account Beech CMS. Clicca il pulsante qui sotto per impostare una nuova password. Questo link scade tra 30 minuti.',
|
|
32
|
+
ctaLabel: 'Reimposta password',
|
|
33
|
+
footer: 'Se non hai richiesto questo, puoi ignorare questa email in tutta sicurezza.',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Builds the email with the password reset link.
|
|
39
|
+
*
|
|
40
|
+
* Composes the localized content with the base layout (`buildEmailShell`)
|
|
41
|
+
* injecting the CTA button pointing to the reset URL.
|
|
42
|
+
*
|
|
43
|
+
* @param resetUrl - Complete URL with the token in plain text, e.g.
|
|
44
|
+
* `https://dashboard.beechcms.dev/reset-password?token=<uuid>`.
|
|
45
|
+
* It is directly embedded in the CTA button — do not sanitize
|
|
46
|
+
* further: the token is an internally generated UUID.
|
|
47
|
+
* @param locale - Language for the email subject and body.
|
|
48
|
+
* @returns Object with `subject` (string) and `html` (complete HTML document).
|
|
49
|
+
*/
|
|
50
|
+
export function buildPasswordResetEmail(
|
|
51
|
+
resetUrl: string,
|
|
52
|
+
locale: EmailLocale,
|
|
53
|
+
): { subject: string; html: string } {
|
|
54
|
+
const c = COPY[locale]
|
|
55
|
+
return {
|
|
56
|
+
subject: c.subject,
|
|
57
|
+
html: buildEmailShell(locale, {
|
|
58
|
+
title: c.title,
|
|
59
|
+
body: c.body,
|
|
60
|
+
cta: { label: c.ctaLabel, href: resetUrl },
|
|
61
|
+
footer: c.footer,
|
|
62
|
+
}),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,93 +1,92 @@
|
|
|
1
|
-
import type { EmailLocale } from '../email.types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
export interface EmailShellSlots {
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*/
|
|
12
|
-
title: string
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*/
|
|
20
|
-
body: string
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*/
|
|
26
|
-
cta?: { label: string; href: string }
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* ("
|
|
32
|
-
*/
|
|
33
|
-
warning?: string
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*/
|
|
39
|
-
footer: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* -
|
|
50
|
-
* -
|
|
51
|
-
* -
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @param
|
|
59
|
-
* @
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
<meta
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
<p style="margin:0
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
</
|
|
92
|
-
|
|
93
|
-
}
|
|
1
|
+
import type { EmailLocale } from '../email.types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Content slots that every template must provide to compose a complete email.
|
|
5
|
+
* Each slot corresponds to a visual block in the email card layout.
|
|
6
|
+
*/
|
|
7
|
+
export interface EmailShellSlots {
|
|
8
|
+
/**
|
|
9
|
+
* H2 heading shown at the top of the card. Keep it under ~50 characters
|
|
10
|
+
* to ensure good readability on mobile clients.
|
|
11
|
+
*/
|
|
12
|
+
title: string
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Main body text. Rendered as a paragraph.
|
|
16
|
+
* Safe inline HTML is allowed (e.g., `<strong>`, `<a href="...">`),
|
|
17
|
+
* but avoid block elements (`<p>`, `<div>`) that could break
|
|
18
|
+
* the layout structure in rigid email clients (Outlook, Gmail).
|
|
19
|
+
*/
|
|
20
|
+
body: string
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Optional call-to-action button, rendered as a dark link-button.
|
|
24
|
+
* Omit for notification-only emails that do not require user action.
|
|
25
|
+
*/
|
|
26
|
+
cta?: { label: string; href: string }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Optional warning paragraph. Rendered in red (#ef4444) to attract
|
|
30
|
+
* attention. Use it for security alerts
|
|
31
|
+
* ("if it wasn't you, act immediately").
|
|
32
|
+
*/
|
|
33
|
+
warning?: string
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Small gray text at the bottom of the card.
|
|
37
|
+
* Used for notes like "automated notification, do not reply".
|
|
38
|
+
*/
|
|
39
|
+
footer: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds the base HTML layout shared by all Beech CMS transactional emails.
|
|
44
|
+
*
|
|
45
|
+
* ─── SINGLE SOURCE OF TRUTH FOR BRANDING ─────────────────────────────────────
|
|
46
|
+
* Modifying this function changes the visual appearance of ALL outgoing emails
|
|
47
|
+
* simultaneously:
|
|
48
|
+
* - background and card color
|
|
49
|
+
* - border style and border-radius
|
|
50
|
+
* - typographic scale and spacing
|
|
51
|
+
* - CTA button style
|
|
52
|
+
*
|
|
53
|
+
* To change the text or structure of a specific email, instead modify
|
|
54
|
+
* the corresponding template file (`templates/password-reset.ts`, etc.).
|
|
55
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
*
|
|
57
|
+
* @param locale - Used for the `lang` attribute of the `<html>` tag.
|
|
58
|
+
* @param slots - Content blocks injected into the layout.
|
|
59
|
+
* @returns A complete and self-contained HTML document ready to be sent.
|
|
60
|
+
*/
|
|
61
|
+
export function buildEmailShell(locale: EmailLocale, slots: EmailShellSlots): string {
|
|
62
|
+
const ctaBlock = slots.cta
|
|
63
|
+
? `<a href="${slots.cta.href}"
|
|
64
|
+
style="display:inline-block;background:#111;color:#fff;padding:12px 24px;
|
|
65
|
+
border-radius:6px;text-decoration:none;font-size:15px;font-weight:500;
|
|
66
|
+
margin-bottom:24px">
|
|
67
|
+
${slots.cta.label}
|
|
68
|
+
</a>`
|
|
69
|
+
: ''
|
|
70
|
+
|
|
71
|
+
const warningBlock = slots.warning
|
|
72
|
+
? `<p style="margin:0 0 24px;color:#ef4444;font-size:15px;line-height:1.5;font-weight:500">
|
|
73
|
+
${slots.warning}
|
|
74
|
+
</p>`
|
|
75
|
+
: ''
|
|
76
|
+
|
|
77
|
+
return `<!DOCTYPE html>
|
|
78
|
+
<html lang="${locale}">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="UTF-8">
|
|
81
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
82
|
+
</head>
|
|
83
|
+
<body style="font-family:sans-serif;background:#f9f9f9;margin:0;padding:32px">
|
|
84
|
+
<div style="max-width:480px;margin:0 auto;background:#fff;border-radius:8px;
|
|
85
|
+
padding:32px;border:1px solid #e5e5e5">
|
|
86
|
+
<h2 style="margin:0 0 16px;font-size:20px;color:#111">${slots.title}</h2>
|
|
87
|
+
<p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.5">${slots.body}</p>
|
|
88
|
+
${ctaBlock}${warningBlock}<p style="margin:0;color:#999;font-size:13px">${slots.footer}</p>
|
|
89
|
+
</div>
|
|
90
|
+
</body>
|
|
91
|
+
</html>`
|
|
92
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { notificationsApp } from './notifications.handler'
|
|
1
|
+
export { notificationsApp } from './notifications.handler'
|