@learncard/email-templates 1.1.0 → 1.2.1

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.
Files changed (81) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/previews/escrow-hold-cancelled-scenarios.d.ts +6 -0
  5. package/dist/previews/escrow-hold-cancelled-scenarios.d.ts.map +1 -0
  6. package/dist/previews/escrow-hold-cancelled-scenarios.js +7 -0
  7. package/dist/previews/escrow-hold-cancelled-scenarios.js.map +1 -0
  8. package/dist/previews/escrow-hold-released-scenarios.d.ts +6 -0
  9. package/dist/previews/escrow-hold-released-scenarios.d.ts.map +1 -0
  10. package/dist/previews/escrow-hold-released-scenarios.js +7 -0
  11. package/dist/previews/escrow-hold-released-scenarios.js.map +1 -0
  12. package/dist/previews/escrow-hold-reminder-scenarios.d.ts +6 -0
  13. package/dist/previews/escrow-hold-reminder-scenarios.d.ts.map +1 -0
  14. package/dist/previews/escrow-hold-reminder-scenarios.js +7 -0
  15. package/dist/previews/escrow-hold-reminder-scenarios.js.map +1 -0
  16. package/dist/previews/escrow-hold-started-scenarios.d.ts +6 -0
  17. package/dist/previews/escrow-hold-started-scenarios.d.ts.map +1 -0
  18. package/dist/previews/escrow-hold-started-scenarios.js +7 -0
  19. package/dist/previews/escrow-hold-started-scenarios.js.map +1 -0
  20. package/dist/previews/escrow-pin-locked-scenarios.d.ts +6 -0
  21. package/dist/previews/escrow-pin-locked-scenarios.d.ts.map +1 -0
  22. package/dist/previews/escrow-pin-locked-scenarios.js +7 -0
  23. package/dist/previews/escrow-pin-locked-scenarios.js.map +1 -0
  24. package/dist/previews/recovery-key-scenarios.d.ts.map +1 -1
  25. package/dist/previews/recovery-key-scenarios.js +1 -1
  26. package/dist/previews/recovery-key-scenarios.js.map +1 -1
  27. package/dist/render.d.ts +38 -0
  28. package/dist/render.d.ts.map +1 -1
  29. package/dist/render.js +48 -1
  30. package/dist/render.js.map +1 -1
  31. package/dist/templates/account-sign-in-changed.d.ts +11 -0
  32. package/dist/templates/account-sign-in-changed.d.ts.map +1 -0
  33. package/dist/templates/account-sign-in-changed.js +70 -0
  34. package/dist/templates/account-sign-in-changed.js.map +1 -0
  35. package/dist/templates/escrow-hold-cancelled.d.ts +23 -0
  36. package/dist/templates/escrow-hold-cancelled.d.ts.map +1 -0
  37. package/dist/templates/escrow-hold-cancelled.js +131 -0
  38. package/dist/templates/escrow-hold-cancelled.js.map +1 -0
  39. package/dist/templates/escrow-hold-released.d.ts +21 -0
  40. package/dist/templates/escrow-hold-released.d.ts.map +1 -0
  41. package/dist/templates/escrow-hold-released.js +92 -0
  42. package/dist/templates/escrow-hold-released.js.map +1 -0
  43. package/dist/templates/escrow-hold-reminder.d.ts +21 -0
  44. package/dist/templates/escrow-hold-reminder.d.ts.map +1 -0
  45. package/dist/templates/escrow-hold-reminder.js +92 -0
  46. package/dist/templates/escrow-hold-reminder.js.map +1 -0
  47. package/dist/templates/escrow-hold-started.d.ts +25 -0
  48. package/dist/templates/escrow-hold-started.d.ts.map +1 -0
  49. package/dist/templates/escrow-hold-started.js +96 -0
  50. package/dist/templates/escrow-hold-started.js.map +1 -0
  51. package/dist/templates/escrow-pin-locked.d.ts +24 -0
  52. package/dist/templates/escrow-pin-locked.d.ts.map +1 -0
  53. package/dist/templates/escrow-pin-locked.js +97 -0
  54. package/dist/templates/escrow-pin-locked.js.map +1 -0
  55. package/dist/templates/index.d.ts +12 -0
  56. package/dist/templates/index.d.ts.map +1 -1
  57. package/dist/templates/index.js +6 -0
  58. package/dist/templates/index.js.map +1 -1
  59. package/dist/templates/recovery-key.d.ts +1 -0
  60. package/dist/templates/recovery-key.d.ts.map +1 -1
  61. package/dist/templates/recovery-key.js +11 -3
  62. package/dist/templates/recovery-key.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/__tests__/i18n.test.ts +39 -2
  65. package/src/__tests__/render.test.ts +195 -0
  66. package/src/index.ts +7 -0
  67. package/src/previews/escrow-hold-cancelled-scenarios.tsx +79 -0
  68. package/src/previews/escrow-hold-released-scenarios.tsx +58 -0
  69. package/src/previews/escrow-hold-reminder-scenarios.tsx +52 -0
  70. package/src/previews/escrow-hold-started-scenarios.tsx +67 -0
  71. package/src/previews/escrow-pin-locked-scenarios.tsx +60 -0
  72. package/src/previews/recovery-key-scenarios.tsx +3 -0
  73. package/src/render.ts +128 -0
  74. package/src/templates/account-sign-in-changed.tsx +113 -0
  75. package/src/templates/escrow-hold-cancelled.tsx +212 -0
  76. package/src/templates/escrow-hold-released.tsx +184 -0
  77. package/src/templates/escrow-hold-reminder.tsx +184 -0
  78. package/src/templates/escrow-hold-started.tsx +216 -0
  79. package/src/templates/escrow-pin-locked.tsx +233 -0
  80. package/src/templates/index.ts +17 -0
  81. package/src/templates/recovery-key.tsx +26 -1
@@ -0,0 +1,79 @@
1
+ /**
2
+ * escrow-hold-cancelled — all preview scenarios stacked (one per reason).
3
+ */
4
+
5
+ import * as React from 'react';
6
+
7
+ import { EscrowHoldCancelled } from '../templates/escrow-hold-cancelled';
8
+ import { LEARNCARD_BRANDING, VETPASS_BRANDING, ScenarioDivider } from './_fixtures';
9
+
10
+ export default function EscrowHoldCancelledScenarios() {
11
+ return (
12
+ <>
13
+ <ScenarioDivider label="Reason: user" />
14
+ <EscrowHoldCancelled
15
+ branding={LEARNCARD_BRANDING}
16
+ cancelledAt="September 26, 2026, 9:15 AM"
17
+ reason="user"
18
+ />
19
+
20
+ <ScenarioDivider label="Reason: superseded" />
21
+ <EscrowHoldCancelled
22
+ branding={LEARNCARD_BRANDING}
23
+ cancelledAt="September 26, 2026, 9:15 AM"
24
+ reason="superseded"
25
+ />
26
+
27
+ <ScenarioDivider label="Reason: pin-locked" />
28
+ <EscrowHoldCancelled
29
+ branding={LEARNCARD_BRANDING}
30
+ cancelledAt="September 26, 2026, 9:15 AM"
31
+ reason="pin-locked"
32
+ />
33
+
34
+ <ScenarioDivider label="Reason: release-failed" />
35
+ <EscrowHoldCancelled
36
+ branding={LEARNCARD_BRANDING}
37
+ cancelledAt="September 26, 2026, 9:15 AM"
38
+ reason="release-failed"
39
+ />
40
+
41
+ <ScenarioDivider label="No reason (generic message)" />
42
+ <EscrowHoldCancelled
43
+ branding={LEARNCARD_BRANDING}
44
+ cancelledAt="September 26, 2026, 9:15 AM"
45
+ />
46
+
47
+ <ScenarioDivider label="Spanish" />
48
+ <EscrowHoldCancelled
49
+ branding={LEARNCARD_BRANDING}
50
+ cancelledAt="26 de septiembre de 2026, 9:15 a.m."
51
+ reason="user"
52
+ locale="es"
53
+ />
54
+
55
+ <ScenarioDivider label="French" />
56
+ <EscrowHoldCancelled
57
+ branding={LEARNCARD_BRANDING}
58
+ cancelledAt="26 septembre 2026, 9h15"
59
+ reason="user"
60
+ locale="fr"
61
+ />
62
+
63
+ <ScenarioDivider label="Arabic (RTL)" />
64
+ <EscrowHoldCancelled
65
+ branding={LEARNCARD_BRANDING}
66
+ cancelledAt="26 سبتمبر 2026، 9:15 ص"
67
+ reason="user"
68
+ locale="ar"
69
+ />
70
+
71
+ <ScenarioDivider label="VetPass tenant branding" />
72
+ <EscrowHoldCancelled
73
+ branding={VETPASS_BRANDING}
74
+ cancelledAt="September 26, 2026, 9:15 AM"
75
+ reason="user"
76
+ />
77
+ </>
78
+ );
79
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * escrow-hold-released — all preview scenarios stacked.
3
+ */
4
+
5
+ import * as React from 'react';
6
+
7
+ import { EscrowHoldReleased } from '../templates/escrow-hold-released';
8
+ import { LEARNCARD_BRANDING, VETPASS_BRANDING, ScenarioDivider } from './_fixtures';
9
+
10
+ export default function EscrowHoldReleasedScenarios() {
11
+ return (
12
+ <>
13
+ <ScenarioDivider label="With support link" />
14
+ <EscrowHoldReleased
15
+ branding={LEARNCARD_BRANDING}
16
+ completedAt="October 2, 2026, 3:00 PM"
17
+ supportUrl="https://learncard.app/support"
18
+ />
19
+
20
+ <ScenarioDivider label="No support link (footer contact only)" />
21
+ <EscrowHoldReleased
22
+ branding={LEARNCARD_BRANDING}
23
+ completedAt="October 2, 2026, 3:00 PM"
24
+ />
25
+
26
+ <ScenarioDivider label="Spanish" />
27
+ <EscrowHoldReleased
28
+ branding={LEARNCARD_BRANDING}
29
+ completedAt="2 de octubre de 2026, 3:00 p.m."
30
+ supportUrl="https://learncard.app/support"
31
+ locale="es"
32
+ />
33
+
34
+ <ScenarioDivider label="French" />
35
+ <EscrowHoldReleased
36
+ branding={LEARNCARD_BRANDING}
37
+ completedAt="2 octobre 2026, 15h00"
38
+ supportUrl="https://learncard.app/support"
39
+ locale="fr"
40
+ />
41
+
42
+ <ScenarioDivider label="Arabic (RTL)" />
43
+ <EscrowHoldReleased
44
+ branding={LEARNCARD_BRANDING}
45
+ completedAt="2 أكتوبر 2026، 3:00 م"
46
+ supportUrl="https://learncard.app/support"
47
+ locale="ar"
48
+ />
49
+
50
+ <ScenarioDivider label="VetPass tenant branding" />
51
+ <EscrowHoldReleased
52
+ branding={VETPASS_BRANDING}
53
+ completedAt="October 2, 2026, 3:00 PM"
54
+ supportUrl="https://vetpass.app/support"
55
+ />
56
+ </>
57
+ );
58
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * escrow-hold-reminder — all preview scenarios stacked.
3
+ */
4
+
5
+ import * as React from 'react';
6
+
7
+ import { EscrowHoldReminder } from '../templates/escrow-hold-reminder';
8
+ import { LEARNCARD_BRANDING, VETPASS_BRANDING, ScenarioDivider } from './_fixtures';
9
+
10
+ export default function EscrowHoldReminderScenarios() {
11
+ return (
12
+ <>
13
+ <ScenarioDivider label="Default — LearnCard branding" />
14
+ <EscrowHoldReminder
15
+ branding={LEARNCARD_BRANDING}
16
+ releaseAfter="October 2, 2026, 3:00 PM"
17
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
18
+ />
19
+
20
+ <ScenarioDivider label="Spanish" />
21
+ <EscrowHoldReminder
22
+ branding={LEARNCARD_BRANDING}
23
+ releaseAfter="2 de octubre de 2026, 3:00 p.m."
24
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
25
+ locale="es"
26
+ />
27
+
28
+ <ScenarioDivider label="French" />
29
+ <EscrowHoldReminder
30
+ branding={LEARNCARD_BRANDING}
31
+ releaseAfter="2 octobre 2026, 15h00"
32
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
33
+ locale="fr"
34
+ />
35
+
36
+ <ScenarioDivider label="Arabic (RTL)" />
37
+ <EscrowHoldReminder
38
+ branding={LEARNCARD_BRANDING}
39
+ releaseAfter="2 أكتوبر 2026، 3:00 م"
40
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
41
+ locale="ar"
42
+ />
43
+
44
+ <ScenarioDivider label="VetPass tenant branding" />
45
+ <EscrowHoldReminder
46
+ branding={VETPASS_BRANDING}
47
+ releaseAfter="October 2, 2026, 3:00 PM"
48
+ cancelUrl="https://vetpass.app/recovery/cancel?token=abc123"
49
+ />
50
+ </>
51
+ );
52
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * escrow-hold-started — all preview scenarios stacked.
3
+ */
4
+
5
+ import * as React from 'react';
6
+
7
+ import { EscrowHoldStarted } from '../templates/escrow-hold-started';
8
+ import { LEARNCARD_BRANDING, VETPASS_BRANDING, ScenarioDivider } from './_fixtures';
9
+
10
+ export default function EscrowHoldStartedScenarios() {
11
+ return (
12
+ <>
13
+ <ScenarioDivider label="Default — with device hint" />
14
+ <EscrowHoldStarted
15
+ branding={LEARNCARD_BRANDING}
16
+ requestedAt="September 25, 2026, 3:00 PM"
17
+ releaseAfter="October 2, 2026, 3:00 PM"
18
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
19
+ deviceHint="Chrome on Windows"
20
+ />
21
+
22
+ <ScenarioDivider label="No device hint" />
23
+ <EscrowHoldStarted
24
+ branding={LEARNCARD_BRANDING}
25
+ requestedAt="September 25, 2026, 3:00 PM"
26
+ releaseAfter="October 2, 2026, 3:00 PM"
27
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
28
+ />
29
+
30
+ <ScenarioDivider label="Spanish" />
31
+ <EscrowHoldStarted
32
+ branding={LEARNCARD_BRANDING}
33
+ requestedAt="25 de septiembre de 2026, 3:00 p.m."
34
+ releaseAfter="2 de octubre de 2026, 3:00 p.m."
35
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
36
+ locale="es"
37
+ />
38
+
39
+ <ScenarioDivider label="French" />
40
+ <EscrowHoldStarted
41
+ branding={LEARNCARD_BRANDING}
42
+ requestedAt="25 septembre 2026, 15h00"
43
+ releaseAfter="2 octobre 2026, 15h00"
44
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
45
+ locale="fr"
46
+ />
47
+
48
+ <ScenarioDivider label="Arabic (RTL)" />
49
+ <EscrowHoldStarted
50
+ branding={LEARNCARD_BRANDING}
51
+ requestedAt="25 سبتمبر 2026، 3:00 م"
52
+ releaseAfter="2 أكتوبر 2026، 3:00 م"
53
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
54
+ locale="ar"
55
+ />
56
+
57
+ <ScenarioDivider label="VetPass tenant branding" />
58
+ <EscrowHoldStarted
59
+ branding={VETPASS_BRANDING}
60
+ requestedAt="September 25, 2026, 3:00 PM"
61
+ releaseAfter="October 2, 2026, 3:00 PM"
62
+ cancelUrl="https://vetpass.app/recovery/cancel?token=abc123"
63
+ deviceHint="Safari on iPhone"
64
+ />
65
+ </>
66
+ );
67
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * escrow-pin-locked — all preview scenarios stacked.
3
+ */
4
+
5
+ import * as React from 'react';
6
+
7
+ import { EscrowPinLocked } from '../templates/escrow-pin-locked';
8
+ import { LEARNCARD_BRANDING, VETPASS_BRANDING, ScenarioDivider } from './_fixtures';
9
+
10
+ export default function EscrowPinLockedScenarios() {
11
+ return (
12
+ <>
13
+ <ScenarioDivider label="With pending waiting-period recovery (cancel link)" />
14
+ <EscrowPinLocked
15
+ branding={LEARNCARD_BRANDING}
16
+ lockedAt="September 25, 2026, 3:00 PM"
17
+ releaseAfter="October 2, 2026, 3:00 PM"
18
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
19
+ />
20
+
21
+ <ScenarioDivider label="No pending recovery" />
22
+ <EscrowPinLocked branding={LEARNCARD_BRANDING} lockedAt="September 25, 2026, 3:00 PM" />
23
+
24
+ <ScenarioDivider label="Spanish" />
25
+ <EscrowPinLocked
26
+ branding={LEARNCARD_BRANDING}
27
+ lockedAt="25 de septiembre de 2026, 3:00 p.m."
28
+ releaseAfter="2 de octubre de 2026, 3:00 p.m."
29
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
30
+ locale="es"
31
+ />
32
+
33
+ <ScenarioDivider label="French" />
34
+ <EscrowPinLocked
35
+ branding={LEARNCARD_BRANDING}
36
+ lockedAt="25 septembre 2026, 15h00"
37
+ releaseAfter="2 octobre 2026, 15h00"
38
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
39
+ locale="fr"
40
+ />
41
+
42
+ <ScenarioDivider label="Arabic (RTL)" />
43
+ <EscrowPinLocked
44
+ branding={LEARNCARD_BRANDING}
45
+ lockedAt="25 سبتمبر 2026، 3:00 م"
46
+ releaseAfter="2 أكتوبر 2026، 3:00 م"
47
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
48
+ locale="ar"
49
+ />
50
+
51
+ <ScenarioDivider label="VetPass tenant branding" />
52
+ <EscrowPinLocked
53
+ branding={VETPASS_BRANDING}
54
+ lockedAt="September 25, 2026, 3:00 PM"
55
+ releaseAfter="October 2, 2026, 3:00 PM"
56
+ cancelUrl="https://vetpass.app/recovery/cancel?token=abc123"
57
+ />
58
+ </>
59
+ );
60
+ }
@@ -14,18 +14,21 @@ export default function RecoveryKeyScenarios() {
14
14
  <RecoveryKey
15
15
  branding={LEARNCARD_BRANDING}
16
16
  recoveryKey="mango-delta-fox-echo-bravo-seven-lima-niner"
17
+ confirmationCode="123456"
17
18
  />
18
19
 
19
20
  <ScenarioDivider label="Long recovery key (wrapping test)" />
20
21
  <RecoveryKey
21
22
  branding={LEARNCARD_BRANDING}
22
23
  recoveryKey="alpha-bravo-charlie-delta-echo-foxtrot-golf-hotel-india-juliet-kilo-lima"
24
+ confirmationCode="654321"
23
25
  />
24
26
 
25
27
  <ScenarioDivider label="VetPass tenant branding" />
26
28
  <RecoveryKey
27
29
  branding={VETPASS_BRANDING}
28
30
  recoveryKey="sierra-tango-uniform-victor-whiskey-xray"
31
+ confirmationCode="987654"
29
32
  />
30
33
  </>
31
34
  );
package/src/render.ts CHANGED
@@ -41,6 +41,18 @@ import {
41
41
  getEmailVerificationSubject,
42
42
  CredentialUpdated,
43
43
  getCredentialUpdatedSubject,
44
+ AccountSignInChanged,
45
+ getAccountSignInChangedSubject,
46
+ EscrowHoldStarted,
47
+ getEscrowHoldStartedSubject,
48
+ EscrowHoldReminder,
49
+ getEscrowHoldReminderSubject,
50
+ EscrowHoldReleased,
51
+ getEscrowHoldReleasedSubject,
52
+ EscrowHoldCancelled,
53
+ getEscrowHoldCancelledSubject,
54
+ EscrowPinLocked,
55
+ getEscrowPinLockedSubject,
44
56
  } from './templates';
45
57
 
46
58
  import type {
@@ -56,6 +68,13 @@ import type {
56
68
  GuardianRejectedCredentialProps,
57
69
  EmailVerificationProps,
58
70
  CredentialUpdatedProps,
71
+ AccountSignInChangedProps,
72
+ EscrowHoldStartedProps,
73
+ EscrowHoldReminderProps,
74
+ EscrowHoldReleasedProps,
75
+ EscrowHoldCancelledProps,
76
+ EscrowHoldCancelledReason,
77
+ EscrowPinLockedProps,
59
78
  } from './templates';
60
79
 
61
80
  // ---------------------------------------------------------------------------
@@ -152,6 +171,25 @@ export interface TemplateDataMap {
152
171
 
153
172
  /** brain-service: managed credential refresh update notice (LC-2198) */
154
173
  'credential-updated': CredentialUpdatedData;
174
+ /** lca-api: lost-login identity rebind security notification */
175
+ 'account-sign-in-changed': AccountSignInChangedData;
176
+
177
+ // -- Recovery hold lifecycle (nitro-escrow-enclave) ----------------------
178
+
179
+ /** lca-api: recovery hold started — includes a cancel link */
180
+ 'escrow-hold-started': EscrowHoldStartedData;
181
+
182
+ /** lca-api: recovery hold reminder (T-24h) — includes a cancel link */
183
+ 'escrow-hold-reminder': EscrowHoldReminderData;
184
+
185
+ /** lca-api: recovery hold completed and access was restored */
186
+ 'escrow-hold-released': EscrowHoldReleasedData;
187
+
188
+ /** lca-api: recovery hold cancelled (user, superseded, PIN lock, or failure) */
189
+ 'escrow-hold-cancelled': EscrowHoldCancelledData;
190
+
191
+ /** lca-api: recovery PIN locked after too many failed attempts */
192
+ 'escrow-pin-locked': EscrowPinLockedData;
155
193
  }
156
194
 
157
195
  export type TemplateId = keyof TemplateDataMap;
@@ -190,6 +228,7 @@ export interface AccountApprovedData {
190
228
 
191
229
  export interface RecoveryKeyData {
192
230
  recoveryKey: string;
231
+ confirmationCode: string;
193
232
  }
194
233
 
195
234
  export interface EndorsementRequestData {
@@ -235,6 +274,36 @@ export interface CredentialUpdatedData {
235
274
  credential?: { name?: string };
236
275
  }
237
276
 
277
+ export type AccountSignInChangedData = Record<string, never>;
278
+
279
+ export interface EscrowHoldStartedData {
280
+ requestedAt: string;
281
+ releaseAfter: string;
282
+ cancelUrl: string;
283
+ deviceHint?: string;
284
+ }
285
+
286
+ export interface EscrowHoldReminderData {
287
+ releaseAfter: string;
288
+ cancelUrl: string;
289
+ }
290
+
291
+ export interface EscrowHoldReleasedData {
292
+ completedAt: string;
293
+ supportUrl?: string;
294
+ }
295
+
296
+ export interface EscrowHoldCancelledData {
297
+ cancelledAt: string;
298
+ reason?: EscrowHoldCancelledReason;
299
+ }
300
+
301
+ export interface EscrowPinLockedData {
302
+ lockedAt: string;
303
+ releaseAfter?: string;
304
+ cancelUrl?: string;
305
+ }
306
+
238
307
  // ---------------------------------------------------------------------------
239
308
  // renderEmail()
240
309
  // ---------------------------------------------------------------------------
@@ -442,6 +511,65 @@ function buildElement(
442
511
  };
443
512
  }
444
513
 
514
+ case 'account-sign-in-changed': {
515
+ const props: AccountSignInChangedProps = { branding, locale };
516
+
517
+ return {
518
+ element: React.createElement(AccountSignInChanged, props),
519
+ subject: getAccountSignInChangedSubject(branding, locale),
520
+ };
521
+ }
522
+
523
+ case 'escrow-hold-started': {
524
+ const d = data as EscrowHoldStartedData;
525
+ const props: EscrowHoldStartedProps = { branding, ...d, locale };
526
+
527
+ return {
528
+ element: React.createElement(EscrowHoldStarted, props),
529
+ subject: getEscrowHoldStartedSubject(branding, locale),
530
+ };
531
+ }
532
+
533
+ case 'escrow-hold-reminder': {
534
+ const d = data as EscrowHoldReminderData;
535
+ const props: EscrowHoldReminderProps = { branding, ...d, locale };
536
+
537
+ return {
538
+ element: React.createElement(EscrowHoldReminder, props),
539
+ subject: getEscrowHoldReminderSubject(branding, locale),
540
+ };
541
+ }
542
+
543
+ case 'escrow-hold-released': {
544
+ const d = data as EscrowHoldReleasedData;
545
+ const props: EscrowHoldReleasedProps = { branding, ...d, locale };
546
+
547
+ return {
548
+ element: React.createElement(EscrowHoldReleased, props),
549
+ subject: getEscrowHoldReleasedSubject(branding, locale),
550
+ };
551
+ }
552
+
553
+ case 'escrow-hold-cancelled': {
554
+ const d = data as EscrowHoldCancelledData;
555
+ const props: EscrowHoldCancelledProps = { branding, ...d, locale };
556
+
557
+ return {
558
+ element: React.createElement(EscrowHoldCancelled, props),
559
+ subject: getEscrowHoldCancelledSubject(branding, locale),
560
+ };
561
+ }
562
+
563
+ case 'escrow-pin-locked': {
564
+ const d = data as EscrowPinLockedData;
565
+ const props: EscrowPinLockedProps = { branding, ...d, locale };
566
+
567
+ return {
568
+ element: React.createElement(EscrowPinLocked, props),
569
+ subject: getEscrowPinLockedSubject(branding, locale),
570
+ };
571
+ }
572
+
445
573
  default: {
446
574
  const _exhaustive: never = templateId;
447
575
  throw new Error(`Unknown template: ${_exhaustive}`);
@@ -0,0 +1,113 @@
1
+ import { Text } from '@react-email/components';
2
+ import * as React from 'react';
3
+
4
+ import type { TenantBranding } from '../branding';
5
+ import { DEFAULT_BRANDING } from '../branding';
6
+ import { Layout } from '../components/Layout';
7
+ import type { NotificationLocale } from '../i18n';
8
+ import { interpolate, resolveCatalogLocale, SHARED } from '../i18n';
9
+
10
+ export interface AccountSignInChangedProps {
11
+ branding: TenantBranding;
12
+ locale?: string;
13
+ }
14
+
15
+ const STRINGS: Record<
16
+ NotificationLocale,
17
+ {
18
+ preview: string;
19
+ heading: string;
20
+ body: string;
21
+ warning: string;
22
+ subject: string;
23
+ sincerely: string;
24
+ }
25
+ > = {
26
+ en: {
27
+ preview: 'Your account sign-in was changed',
28
+ heading: 'Your Sign-In Was Changed',
29
+ body: 'Your {brandName} account was restored and connected to a new sign-in.',
30
+ warning: 'If this was not you, contact support immediately.',
31
+ subject: 'Your account sign-in was changed',
32
+ sincerely: 'Sincerely,',
33
+ },
34
+ es: {
35
+ preview: 'Se cambió el inicio de sesión de tu cuenta',
36
+ heading: 'Se cambió tu inicio de sesión',
37
+ body: 'Tu cuenta de {brandName} se restauró y se conectó a un nuevo inicio de sesión.',
38
+ warning: 'Si no fuiste tú, comunícate con soporte de inmediato.',
39
+ subject: 'Se cambió el inicio de sesión de tu cuenta',
40
+ sincerely: 'Atentamente,',
41
+ },
42
+ fr: {
43
+ preview: 'La connexion à votre compte a été modifiée',
44
+ heading: 'Votre connexion a été modifiée',
45
+ body: 'Votre compte {brandName} a été restauré et associé à une nouvelle connexion.',
46
+ warning:
47
+ 'Si vous n’êtes pas à l’origine de cette action, contactez immédiatement l’assistance.',
48
+ subject: 'La connexion à votre compte a été modifiée',
49
+ sincerely: 'Cordialement,',
50
+ },
51
+ ar: {
52
+ preview: 'تم تغيير تسجيل الدخول إلى حسابك',
53
+ heading: 'تم تغيير تسجيل الدخول',
54
+ body: 'تمت استعادة حسابك في {brandName} وربطه بتسجيل دخول جديد.',
55
+ warning: 'إذا لم تكن أنت من أجرى هذا التغيير، فتواصل مع الدعم فورًا.',
56
+ subject: 'تم تغيير تسجيل الدخول إلى حسابك',
57
+ sincerely: 'مع التحيات،',
58
+ },
59
+ };
60
+
61
+ export const AccountSignInChanged: React.FC<AccountSignInChangedProps> = ({ branding, locale }) => {
62
+ const strings = STRINGS[resolveCatalogLocale(locale)];
63
+
64
+ return (
65
+ <Layout branding={branding} locale={locale} preview={strings.preview}>
66
+ <Text style={heading}>{strings.heading}</Text>
67
+ <Text style={paragraph}>
68
+ {interpolate(strings.body, { brandName: branding.brandName })}
69
+ </Text>
70
+ <Text style={warning}>{strings.warning}</Text>
71
+ <Text style={signOff}>
72
+ {strings.sincerely}
73
+ <br />
74
+ {interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
75
+ brandName: branding.brandName,
76
+ })}
77
+ </Text>
78
+ </Layout>
79
+ );
80
+ };
81
+
82
+ /** Return the localized subject for an account sign-in change notification. */
83
+ export const getAccountSignInChangedSubject = (
84
+ _branding: TenantBranding,
85
+ locale?: string
86
+ ): string => STRINGS[resolveCatalogLocale(locale)].subject;
87
+
88
+ const heading: React.CSSProperties = {
89
+ color: '#111827',
90
+ fontSize: '24px',
91
+ fontWeight: 600,
92
+ lineHeight: '32px',
93
+ };
94
+
95
+ const paragraph: React.CSSProperties = {
96
+ color: '#374151',
97
+ fontSize: '16px',
98
+ lineHeight: '24px',
99
+ };
100
+
101
+ const warning: React.CSSProperties = {
102
+ ...paragraph,
103
+ fontWeight: 600,
104
+ };
105
+
106
+ const signOff: React.CSSProperties = {
107
+ ...paragraph,
108
+ marginTop: '24px',
109
+ };
110
+
111
+ export default function Preview(): React.ReactElement {
112
+ return <AccountSignInChanged branding={DEFAULT_BRANDING} />;
113
+ }