@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,216 @@
1
+ /**
2
+ * EscrowHoldStarted — notifies a user that an account recovery request was
3
+ * started and gives them a way to cancel it if it wasn't them.
4
+ *
5
+ * Used by: lca-api recovery hold routes (templateId: 'escrow-hold-started')
6
+ */
7
+
8
+ import { Text, Section } from '@react-email/components';
9
+ import * as React from 'react';
10
+
11
+ import type { TenantBranding } from '../branding';
12
+ import { DEFAULT_BRANDING } from '../branding';
13
+ import { Layout } from '../components/Layout';
14
+ import { EmailButton } from '../components/EmailButton';
15
+ import { LinkFallback } from '../components/LinkFallback';
16
+ import type { NotificationLocale } from '../i18n';
17
+ import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
18
+
19
+ export interface EscrowHoldStartedProps {
20
+ branding: TenantBranding;
21
+ /** Human-readable timestamp the recovery request was made. */
22
+ requestedAt: string;
23
+ /** Human-readable timestamp access will be restored, unless cancelled. */
24
+ releaseAfter: string;
25
+ /** Link that cancels the pending recovery request. */
26
+ cancelUrl: string;
27
+ /** Optional human-readable hint about where the request came from (e.g. browser/OS). */
28
+ deviceHint?: string;
29
+ /** Recipient locale (BCP-47). Defaults to English. */
30
+ locale?: string;
31
+ }
32
+
33
+ type Strings = {
34
+ preview: string;
35
+ heading: string;
36
+ body: (requestedAt: string, releaseAfter: string) => React.ReactNode;
37
+ deviceHint: (hint: string) => React.ReactNode;
38
+ warning: string;
39
+ button: string;
40
+ sincerely: string;
41
+ subject: string;
42
+ };
43
+
44
+ const EN: Strings = {
45
+ preview: 'Account recovery started',
46
+ heading: 'Account Recovery Started',
47
+ body: (requestedAt, releaseAfter) => (
48
+ <>
49
+ Someone started recovering your account on <strong>{requestedAt}</strong>. Access will
50
+ be restored on <strong>{releaseAfter}</strong> unless you cancel.
51
+ </>
52
+ ),
53
+ deviceHint: hint => (
54
+ <>
55
+ The request came from <strong>{hint}</strong>.
56
+ </>
57
+ ),
58
+ warning: "If this wasn't you, cancel now.",
59
+ button: "This wasn't me \u2014 Cancel recovery",
60
+ sincerely: 'Sincerely,',
61
+ subject: 'Account recovery started',
62
+ };
63
+
64
+ const ES: Strings = {
65
+ preview: 'Se inició la recuperación de tu cuenta',
66
+ heading: 'Recuperación de Cuenta Iniciada',
67
+ body: (requestedAt, releaseAfter) => (
68
+ <>
69
+ Alguien inició la recuperación de tu cuenta el <strong>{requestedAt}</strong>. El acceso
70
+ se restablecerá el <strong>{releaseAfter}</strong> a menos que la canceles.
71
+ </>
72
+ ),
73
+ deviceHint: hint => (
74
+ <>
75
+ La solicitud provino de <strong>{hint}</strong>.
76
+ </>
77
+ ),
78
+ warning: 'Si no fuiste tú, cancélalo ahora.',
79
+ button: 'No fui yo \u2014 Cancelar recuperación',
80
+ sincerely: 'Atentamente,',
81
+ subject: 'Se inició la recuperación de tu cuenta',
82
+ };
83
+
84
+ const FR: Strings = {
85
+ preview: 'La récupération de votre compte a commencé',
86
+ heading: 'Récupération de Compte Commencée',
87
+ body: (requestedAt, releaseAfter) => (
88
+ <>
89
+ Quelqu\u2019un a commencé à récupérer votre compte le <strong>{requestedAt}</strong>.
90
+ L\u2019accès sera rétabli le <strong>{releaseAfter}</strong> sauf si vous annulez.
91
+ </>
92
+ ),
93
+ deviceHint: hint => (
94
+ <>
95
+ La demande provient de <strong>{hint}</strong>.
96
+ </>
97
+ ),
98
+ warning: 'Si ce n\u2019était pas vous, annulez dès maintenant.',
99
+ button: 'Ce n\u2019était pas moi \u2014 Annuler la récupération',
100
+ sincerely: 'Cordialement,',
101
+ subject: 'La récupération de votre compte a commencé',
102
+ };
103
+
104
+ const AR: Strings = {
105
+ preview: 'بدأت عملية استعادة حسابك',
106
+ heading: 'بدأت استعادة الحساب',
107
+ body: (requestedAt, releaseAfter) => (
108
+ <>
109
+ بدأ شخص ما باستعادة حسابك في <strong>{requestedAt}</strong>. ستتم استعادة الوصول في{' '}
110
+ <strong>{releaseAfter}</strong> ما لم تقم بالإلغاء.
111
+ </>
112
+ ),
113
+ deviceHint: hint => (
114
+ <>
115
+ جاء الطلب من <strong>{hint}</strong>.
116
+ </>
117
+ ),
118
+ warning: 'إذا لم يكن هذا أنت، ألغِ الطلب الآن.',
119
+ button: 'لم يكن أنا \u2014 إلغاء الاستعادة',
120
+ sincerely: 'مع التحيات،',
121
+ subject: 'بدأت عملية استعادة حسابك',
122
+ };
123
+
124
+ const STRINGS: Record<NotificationLocale, Strings> = { en: EN, es: ES, fr: FR, ar: AR };
125
+
126
+ export const EscrowHoldStarted: React.FC<EscrowHoldStartedProps> = ({
127
+ branding,
128
+ requestedAt,
129
+ releaseAfter,
130
+ cancelUrl,
131
+ deviceHint,
132
+ locale,
133
+ }) => {
134
+ const s = STRINGS[resolveCatalogLocale(locale)];
135
+
136
+ return (
137
+ <Layout branding={branding} locale={locale} preview={s.preview}>
138
+ <Text style={heading}>{s.heading}</Text>
139
+
140
+ <Text style={paragraph}>{s.body(requestedAt, releaseAfter)}</Text>
141
+
142
+ {deviceHint && <Text style={paragraph}>{s.deviceHint(deviceHint)}</Text>}
143
+
144
+ <Text style={warning}>{s.warning}</Text>
145
+
146
+ <Section style={buttonWrapper}>
147
+ <EmailButton href={cancelUrl} branding={branding}>
148
+ {s.button}
149
+ </EmailButton>
150
+ </Section>
151
+
152
+ <Text style={signOff}>
153
+ {s.sincerely}
154
+ <br />
155
+ {interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
156
+ brandName: branding.brandName,
157
+ })}
158
+ </Text>
159
+
160
+ <LinkFallback href={cancelUrl} locale={locale} />
161
+ </Layout>
162
+ );
163
+ };
164
+
165
+ export const getEscrowHoldStartedSubject = (_branding: TenantBranding, locale?: string): string =>
166
+ STRINGS[resolveCatalogLocale(locale)].subject;
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Styles
170
+ // ---------------------------------------------------------------------------
171
+
172
+ const heading: React.CSSProperties = {
173
+ fontSize: 24,
174
+ fontWeight: 600,
175
+ color: '#111827',
176
+ margin: '0 0 24px',
177
+ };
178
+
179
+ const paragraph: React.CSSProperties = {
180
+ fontSize: 16,
181
+ color: '#374151',
182
+ lineHeight: '24px',
183
+ margin: '0 0 24px',
184
+ };
185
+
186
+ const warning: React.CSSProperties = {
187
+ ...paragraph,
188
+ fontWeight: 600,
189
+ };
190
+
191
+ const buttonWrapper: React.CSSProperties = {
192
+ margin: '0 0 24px',
193
+ };
194
+
195
+ const signOff: React.CSSProperties = {
196
+ fontSize: 14,
197
+ color: '#374151',
198
+ lineHeight: '20px',
199
+ margin: '24px 0 0',
200
+ };
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // Preview
204
+ // ---------------------------------------------------------------------------
205
+
206
+ export default function Preview() {
207
+ return (
208
+ <EscrowHoldStarted
209
+ branding={DEFAULT_BRANDING}
210
+ requestedAt="September 25, 2026, 3:00 PM"
211
+ releaseAfter="October 2, 2026, 3:00 PM"
212
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
213
+ deviceHint="Chrome on Windows"
214
+ />
215
+ );
216
+ }
@@ -0,0 +1,233 @@
1
+ /**
2
+ * EscrowPinLocked — notifies a user that their recovery PIN has been disabled
3
+ * after too many incorrect attempts. The slower, waiting-period recovery path
4
+ * is unaffected and still available.
5
+ *
6
+ * Used by: lca-api recovery hold routes (templateId: 'escrow-pin-locked')
7
+ */
8
+
9
+ import { Text, Section } from '@react-email/components';
10
+ import * as React from 'react';
11
+
12
+ import type { TenantBranding } from '../branding';
13
+ import { DEFAULT_BRANDING } from '../branding';
14
+ import { Layout } from '../components/Layout';
15
+ import { EmailButton } from '../components/EmailButton';
16
+ import { LinkFallback } from '../components/LinkFallback';
17
+ import type { NotificationLocale } from '../i18n';
18
+ import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
19
+
20
+ export interface EscrowPinLockedProps {
21
+ branding: TenantBranding;
22
+ /** Human-readable timestamp the PIN was locked. */
23
+ lockedAt: string;
24
+ /** Human-readable timestamp the waiting-period recovery will complete, if one is pending. */
25
+ releaseAfter?: string;
26
+ /** Link that cancels a pending waiting-period recovery request, if one is pending. */
27
+ cancelUrl?: string;
28
+ /** Recipient locale (BCP-47). Defaults to English. */
29
+ locale?: string;
30
+ }
31
+
32
+ type Strings = {
33
+ preview: string;
34
+ heading: string;
35
+ body: (lockedAt: string) => React.ReactNode;
36
+ fallbackAvailable: (releaseAfter?: string) => React.ReactNode;
37
+ warning: string;
38
+ button: string;
39
+ sincerely: string;
40
+ subject: string;
41
+ };
42
+
43
+ const EN: Strings = {
44
+ preview: 'Recovery PIN locked',
45
+ heading: 'Recovery PIN Locked',
46
+ body: lockedAt => (
47
+ <>
48
+ Too many incorrect PIN attempts were made on <strong>{lockedAt}</strong>. The PIN is now
49
+ disabled.
50
+ </>
51
+ ),
52
+ fallbackAvailable: releaseAfter =>
53
+ releaseAfter ? (
54
+ <>
55
+ The waiting-period recovery is still available and will complete on{' '}
56
+ <strong>{releaseAfter}</strong> unless you cancel it.
57
+ </>
58
+ ) : (
59
+ <>The waiting-period recovery is still available.</>
60
+ ),
61
+ warning: "If this wasn't you, cancel now.",
62
+ button: "This wasn't me \u2014 Cancel recovery",
63
+ sincerely: 'Sincerely,',
64
+ subject: 'Recovery PIN locked',
65
+ };
66
+
67
+ const ES: Strings = {
68
+ preview: 'PIN de recuperación bloqueado',
69
+ heading: 'PIN de Recuperación Bloqueado',
70
+ body: lockedAt => (
71
+ <>
72
+ Se realizaron demasiados intentos incorrectos de PIN el <strong>{lockedAt}</strong>. El
73
+ PIN está ahora deshabilitado.
74
+ </>
75
+ ),
76
+ fallbackAvailable: releaseAfter =>
77
+ releaseAfter ? (
78
+ <>
79
+ La recuperación por periodo de espera sigue disponible y se completará el{' '}
80
+ <strong>{releaseAfter}</strong> a menos que la canceles.
81
+ </>
82
+ ) : (
83
+ <>La recuperación por periodo de espera sigue disponible.</>
84
+ ),
85
+ warning: 'Si no fuiste tú, cancélalo ahora.',
86
+ button: 'No fui yo \u2014 Cancelar recuperación',
87
+ sincerely: 'Atentamente,',
88
+ subject: 'PIN de recuperación bloqueado',
89
+ };
90
+
91
+ const FR: Strings = {
92
+ preview: 'Code PIN de récupération verrouillé',
93
+ heading: 'Code PIN de Récupération Verrouillé',
94
+ body: lockedAt => (
95
+ <>
96
+ Un trop grand nombre de tentatives de code PIN incorrectes ont eu lieu le{' '}
97
+ <strong>{lockedAt}</strong>. Le code PIN est désormais désactivé.
98
+ </>
99
+ ),
100
+ fallbackAvailable: releaseAfter =>
101
+ releaseAfter ? (
102
+ <>
103
+ La récupération par délai d\u2019attente reste disponible et se terminera le{' '}
104
+ <strong>{releaseAfter}</strong> sauf si vous l\u2019annulez.
105
+ </>
106
+ ) : (
107
+ <>La récupération par délai d\u2019attente reste disponible.</>
108
+ ),
109
+ warning: 'Si ce n\u2019était pas vous, annulez dès maintenant.',
110
+ button: 'Ce n\u2019était pas moi \u2014 Annuler la récupération',
111
+ sincerely: 'Cordialement,',
112
+ subject: 'Code PIN de récupération verrouillé',
113
+ };
114
+
115
+ const AR: Strings = {
116
+ preview: 'تم قفل رمز PIN الخاص بالاستعادة',
117
+ heading: 'تم قفل رمز PIN الخاص بالاستعادة',
118
+ body: lockedAt => (
119
+ <>
120
+ تم إجراء عدة محاولات خاطئة لإدخال رمز PIN في <strong>{lockedAt}</strong>. تم الآن تعطيل
121
+ رمز PIN.
122
+ </>
123
+ ),
124
+ fallbackAvailable: releaseAfter =>
125
+ releaseAfter ? (
126
+ <>
127
+ لا تزال الاستعادة بفترة الانتظار متاحة وستكتمل في <strong>{releaseAfter}</strong> ما
128
+ لم تقم بإلغائها.
129
+ </>
130
+ ) : (
131
+ <>لا تزال الاستعادة بفترة الانتظار متاحة.</>
132
+ ),
133
+ warning: 'إذا لم يكن هذا أنت، ألغِ الطلب الآن.',
134
+ button: 'لم يكن أنا \u2014 إلغاء الاستعادة',
135
+ sincerely: 'مع التحيات،',
136
+ subject: 'تم قفل رمز PIN الخاص بالاستعادة',
137
+ };
138
+
139
+ const STRINGS: Record<NotificationLocale, Strings> = { en: EN, es: ES, fr: FR, ar: AR };
140
+
141
+ export const EscrowPinLocked: React.FC<EscrowPinLockedProps> = ({
142
+ branding,
143
+ lockedAt,
144
+ releaseAfter,
145
+ cancelUrl,
146
+ locale,
147
+ }) => {
148
+ const s = STRINGS[resolveCatalogLocale(locale)];
149
+
150
+ return (
151
+ <Layout branding={branding} locale={locale} preview={s.preview}>
152
+ <Text style={heading}>{s.heading}</Text>
153
+
154
+ <Text style={paragraph}>{s.body(lockedAt)}</Text>
155
+
156
+ <Text style={paragraph}>{s.fallbackAvailable(releaseAfter)}</Text>
157
+
158
+ {cancelUrl && (
159
+ <>
160
+ <Text style={warning}>{s.warning}</Text>
161
+
162
+ <Section style={buttonWrapper}>
163
+ <EmailButton href={cancelUrl} branding={branding}>
164
+ {s.button}
165
+ </EmailButton>
166
+ </Section>
167
+ </>
168
+ )}
169
+
170
+ <Text style={signOff}>
171
+ {s.sincerely}
172
+ <br />
173
+ {interpolate(SHARED[resolveCatalogLocale(locale)].teamSignature, {
174
+ brandName: branding.brandName,
175
+ })}
176
+ </Text>
177
+
178
+ {cancelUrl && <LinkFallback href={cancelUrl} locale={locale} />}
179
+ </Layout>
180
+ );
181
+ };
182
+
183
+ export const getEscrowPinLockedSubject = (_branding: TenantBranding, locale?: string): string =>
184
+ STRINGS[resolveCatalogLocale(locale)].subject;
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // Styles
188
+ // ---------------------------------------------------------------------------
189
+
190
+ const heading: React.CSSProperties = {
191
+ fontSize: 24,
192
+ fontWeight: 600,
193
+ color: '#111827',
194
+ margin: '0 0 24px',
195
+ };
196
+
197
+ const paragraph: React.CSSProperties = {
198
+ fontSize: 16,
199
+ color: '#374151',
200
+ lineHeight: '24px',
201
+ margin: '0 0 24px',
202
+ };
203
+
204
+ const warning: React.CSSProperties = {
205
+ ...paragraph,
206
+ fontWeight: 600,
207
+ };
208
+
209
+ const buttonWrapper: React.CSSProperties = {
210
+ margin: '0 0 24px',
211
+ };
212
+
213
+ const signOff: React.CSSProperties = {
214
+ fontSize: 14,
215
+ color: '#374151',
216
+ lineHeight: '20px',
217
+ margin: '24px 0 0',
218
+ };
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // Preview
222
+ // ---------------------------------------------------------------------------
223
+
224
+ export default function Preview() {
225
+ return (
226
+ <EscrowPinLocked
227
+ branding={DEFAULT_BRANDING}
228
+ lockedAt="September 25, 2026, 3:00 PM"
229
+ releaseAfter="October 2, 2026, 3:00 PM"
230
+ cancelUrl="https://learncard.app/recovery/cancel?token=abc123"
231
+ />
232
+ );
233
+ }
@@ -45,3 +45,20 @@ export type { EmailVerificationProps } from './email-verification';
45
45
 
46
46
  export { CredentialUpdated, getCredentialUpdatedSubject } from './credential-updated';
47
47
  export type { CredentialUpdatedProps } from './credential-updated';
48
+ export { AccountSignInChanged, getAccountSignInChangedSubject } from './account-sign-in-changed';
49
+ export type { AccountSignInChangedProps } from './account-sign-in-changed';
50
+
51
+ export { EscrowHoldStarted, getEscrowHoldStartedSubject } from './escrow-hold-started';
52
+ export type { EscrowHoldStartedProps } from './escrow-hold-started';
53
+
54
+ export { EscrowHoldReminder, getEscrowHoldReminderSubject } from './escrow-hold-reminder';
55
+ export type { EscrowHoldReminderProps } from './escrow-hold-reminder';
56
+
57
+ export { EscrowHoldReleased, getEscrowHoldReleasedSubject } from './escrow-hold-released';
58
+ export type { EscrowHoldReleasedProps } from './escrow-hold-released';
59
+
60
+ export { EscrowHoldCancelled, getEscrowHoldCancelledSubject } from './escrow-hold-cancelled';
61
+ export type { EscrowHoldCancelledProps, EscrowHoldCancelledReason } from './escrow-hold-cancelled';
62
+
63
+ export { EscrowPinLocked, getEscrowPinLockedSubject } from './escrow-pin-locked';
64
+ export type { EscrowPinLockedProps } from './escrow-pin-locked';
@@ -17,6 +17,7 @@ import { resolveCatalogLocale, interpolate, SHARED } from '../i18n';
17
17
  export interface RecoveryKeyProps {
18
18
  branding: TenantBranding;
19
19
  recoveryKey: string;
20
+ confirmationCode: string;
20
21
  /** Recipient locale (BCP-47). Defaults to English. */
21
22
  locale?: string;
22
23
  }
@@ -29,6 +30,8 @@ const STRINGS: Record<
29
30
  greeting: string;
30
31
  body: (brandName: string) => string;
31
32
  codeLabel: string;
33
+ confirmationCodeLabel: string;
34
+ confirmationInstruction: string;
32
35
  recoverInstruction: () => React.ReactNode;
33
36
  notRequested: string;
34
37
  passport: string;
@@ -43,6 +46,9 @@ const STRINGS: Record<
43
46
  body: b =>
44
47
  `Keep this email safe. You can use the recovery key below to regain access to your ${b} account if you lose your device.`,
45
48
  codeLabel: 'Recovery Key \u2014 do NOT share this with anyone',
49
+ confirmationCodeLabel: 'Confirmation code',
50
+ confirmationInstruction:
51
+ 'Enter this code in the app to confirm that you received your recovery key.',
46
52
  recoverInstruction: () => (
47
53
  <>
48
54
  To recover your account, choose <strong>&ldquo;Recover via Email&rdquo;</strong> in
@@ -62,6 +68,9 @@ const STRINGS: Record<
62
68
  body: b =>
63
69
  `Guarda este correo en un lugar seguro. Puedes usar la clave de recuperación siguiente para recuperar el acceso a tu cuenta de ${b} si pierdes tu dispositivo.`,
64
70
  codeLabel: 'Clave de recuperación \u2014 NO la compartas con nadie',
71
+ confirmationCodeLabel: 'Código de confirmación',
72
+ confirmationInstruction:
73
+ 'Introduce este código en la aplicación para confirmar que recibiste tu clave de recuperación.',
65
74
  recoverInstruction: () => (
66
75
  <>
67
76
  Para recuperar tu cuenta, elige <strong>&ldquo;Recuperar por correo&rdquo;</strong>{' '}
@@ -81,6 +90,9 @@ const STRINGS: Record<
81
90
  body: b =>
82
91
  `Conservez cet e-mail en sécurité. Vous pouvez utiliser la clé de récupération ci-dessous pour retrouver l\u2019accès à votre compte ${b} si vous perdez votre appareil.`,
83
92
  codeLabel: 'Clé de récupération \u2014 NE la partagez avec PERSONNE',
93
+ confirmationCodeLabel: 'Code de confirmation',
94
+ confirmationInstruction:
95
+ 'Saisissez ce code dans l’application pour confirmer que vous avez reçu votre clé de récupération.',
84
96
  recoverInstruction: () => (
85
97
  <>
86
98
  Pour récupérer votre compte, choisissez{' '}
@@ -103,6 +115,9 @@ const STRINGS: Record<
103
115
  body: b =>
104
116
  `احتفظ بهذا البريد في مكان آمن. يمكنك استخدام مفتاح الاستعادة أدناه لاستعادة الوصول إلى حسابك في ${b} إذا فقدت جهازك.`,
105
117
  codeLabel: 'مفتاح الاستعادة \u2014 لا تشاركه مع أي شخص إطلاقًا',
118
+ confirmationCodeLabel: 'رمز التأكيد',
119
+ confirmationInstruction:
120
+ 'أدخل هذا الرمز في التطبيق لتأكيد استلام مفتاح الاستعادة الخاص بك.',
106
121
  recoverInstruction: () => (
107
122
  <>
108
123
  لاستعادة حسابك، اختر <strong>&laquo;الاستعادة عبر البريد&raquo;</strong> في التطبيق
@@ -117,7 +132,12 @@ const STRINGS: Record<
117
132
  },
118
133
  };
119
134
 
120
- export const RecoveryKey: React.FC<RecoveryKeyProps> = ({ branding, recoveryKey, locale }) => {
135
+ export const RecoveryKey: React.FC<RecoveryKeyProps> = ({
136
+ branding,
137
+ recoveryKey,
138
+ confirmationCode,
139
+ locale,
140
+ }) => {
121
141
  const s = STRINGS[resolveCatalogLocale(locale)];
122
142
 
123
143
  return (
@@ -130,6 +150,10 @@ export const RecoveryKey: React.FC<RecoveryKeyProps> = ({ branding, recoveryKey,
130
150
 
131
151
  <CodeBlock code={recoveryKey} label={s.codeLabel} variant="key" />
132
152
 
153
+ <CodeBlock code={confirmationCode} label={s.confirmationCodeLabel} variant="code" />
154
+
155
+ <Text style={paragraph}>{s.confirmationInstruction}</Text>
156
+
133
157
  <Text style={paragraph}>{s.recoverInstruction()}</Text>
134
158
 
135
159
  <Text style={muted}>{s.notRequested}</Text>
@@ -193,6 +217,7 @@ export default function Preview() {
193
217
  <RecoveryKey
194
218
  branding={DEFAULT_BRANDING}
195
219
  recoveryKey="mango-delta-fox-echo-bravo-seven-lima-niner"
220
+ confirmationCode="123456"
196
221
  />
197
222
  );
198
223
  }