@learncard/email-templates 1.2.1 → 1.2.2
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/i18n.d.ts +15 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +31 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/previews/account-secured-scenarios.d.ts +6 -0
- package/dist/previews/account-secured-scenarios.d.ts.map +1 -0
- package/dist/previews/account-secured-scenarios.js +7 -0
- package/dist/previews/account-secured-scenarios.js.map +1 -0
- package/dist/previews/escrow-pin-sign-in-scenarios.d.ts +6 -0
- package/dist/previews/escrow-pin-sign-in-scenarios.d.ts.map +1 -0
- package/dist/previews/escrow-pin-sign-in-scenarios.js +7 -0
- package/dist/previews/escrow-pin-sign-in-scenarios.js.map +1 -0
- package/dist/render.d.ts +11 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +27 -1
- package/dist/render.js.map +1 -1
- package/dist/templates/account-secured.d.ts +27 -0
- package/dist/templates/account-secured.d.ts.map +1 -0
- package/dist/templates/account-secured.js +118 -0
- package/dist/templates/account-secured.js.map +1 -0
- package/dist/templates/escrow-pin-sign-in.d.ts +24 -0
- package/dist/templates/escrow-pin-sign-in.d.ts.map +1 -0
- package/dist/templates/escrow-pin-sign-in.js +96 -0
- package/dist/templates/escrow-pin-sign-in.js.map +1 -0
- package/dist/templates/index.d.ts +21 -38
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +20 -14
- package/dist/templates/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/i18n.test.ts +32 -1
- package/src/__tests__/render.test.ts +115 -0
- package/src/i18n.ts +33 -0
- package/src/index.ts +2 -0
- package/src/previews/account-secured-scenarios.tsx +41 -0
- package/src/previews/escrow-pin-sign-in-scenarios.tsx +52 -0
- package/src/render.ts +51 -0
- package/src/templates/account-secured.tsx +236 -0
- package/src/templates/escrow-pin-sign-in.tsx +197 -0
- package/src/templates/index.ts +95 -35
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccountSecured — tells the account owner that "Secure my account" ran: every other
|
|
3
|
+
* device and login was signed out and the recovery methods have to be set up again.
|
|
4
|
+
*
|
|
5
|
+
* The recipient may be an owner who did NOT do this (someone with their login and
|
|
6
|
+
* PIN ran it first) and whose own login was unlinked. So the copy never relies on the
|
|
7
|
+
* in-app "Secure my account" link. It also does not offer the app's lost-login path
|
|
8
|
+
* as the way back: the revoke itself returned every recovery method to pending and
|
|
9
|
+
* removed automatic recovery, so that path usually has nothing left to use. It sends
|
|
10
|
+
* the owner to the tenant's support address, or, when the tenant has none, to the
|
|
11
|
+
* organization that gave them access (no address is made up).
|
|
12
|
+
*
|
|
13
|
+
* Used by: lca-api `POST /keys/revoke-other-devices` (templateId: 'account-secured')
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Text, Section } from '@react-email/components';
|
|
17
|
+
import * as React from 'react';
|
|
18
|
+
|
|
19
|
+
import type { TenantBranding } from '../branding';
|
|
20
|
+
import { DEFAULT_BRANDING } from '../branding';
|
|
21
|
+
import { Layout } from '../components/Layout';
|
|
22
|
+
import { EmailButton } from '../components/EmailButton';
|
|
23
|
+
import { LinkFallback } from '../components/LinkFallback';
|
|
24
|
+
import type { NotificationLocale } from '../i18n';
|
|
25
|
+
import {
|
|
26
|
+
resolveCatalogLocale,
|
|
27
|
+
interpolate,
|
|
28
|
+
SHARED,
|
|
29
|
+
formatNoticeTimestamp,
|
|
30
|
+
localeProp,
|
|
31
|
+
} from '../i18n';
|
|
32
|
+
|
|
33
|
+
export interface AccountSecuredProps {
|
|
34
|
+
branding: TenantBranding;
|
|
35
|
+
/** ISO timestamp of the revoke (formatted per locale), or display text. */
|
|
36
|
+
securedAt: string;
|
|
37
|
+
/** Recipient locale (BCP-47). Defaults to English. */
|
|
38
|
+
locale?: string | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Strings = {
|
|
42
|
+
preview: string;
|
|
43
|
+
heading: string;
|
|
44
|
+
body: (securedAt: string, brandName: string) => React.ReactNode;
|
|
45
|
+
ifYou: string;
|
|
46
|
+
notYouWarning: string;
|
|
47
|
+
resetWarning: string;
|
|
48
|
+
support: string;
|
|
49
|
+
supportFallback: string;
|
|
50
|
+
button: string;
|
|
51
|
+
sincerely: string;
|
|
52
|
+
subject: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const EN: Strings = {
|
|
56
|
+
preview: 'Other devices were signed out of your account',
|
|
57
|
+
heading: 'Your Account Was Secured',
|
|
58
|
+
body: (securedAt, brandName) => (
|
|
59
|
+
<>
|
|
60
|
+
On <strong>{securedAt}</strong>, someone signed in to your {brandName} account signed
|
|
61
|
+
out every other device and login. The account’s recovery methods now need to be set up
|
|
62
|
+
again.
|
|
63
|
+
</>
|
|
64
|
+
),
|
|
65
|
+
ifYou: 'If this was you, there is nothing else to do here. Finish setting up your recovery methods in the app.',
|
|
66
|
+
notYouWarning:
|
|
67
|
+
'If this wasn’t you, someone else may have access to your account, and your own sign-in may have been removed.',
|
|
68
|
+
resetWarning:
|
|
69
|
+
'Securing the account also reset its recovery methods, so your recovery phrase, backup, passkey or recovery PIN may no longer get you back in.',
|
|
70
|
+
support: 'Contact {supportEmail} right away for help getting back into your account.',
|
|
71
|
+
supportFallback:
|
|
72
|
+
'Contact the organization that gave you access to {brandName} right away for help getting back into your account.',
|
|
73
|
+
button: 'Open {brandName}',
|
|
74
|
+
sincerely: 'Sincerely,',
|
|
75
|
+
subject: 'Other devices were signed out of your account',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const ES: Strings = {
|
|
79
|
+
preview: 'Se cerró la sesión de otros dispositivos en tu cuenta',
|
|
80
|
+
heading: 'Tu Cuenta Fue Protegida',
|
|
81
|
+
body: (securedAt, brandName) => (
|
|
82
|
+
<>
|
|
83
|
+
El <strong>{securedAt}</strong>, alguien con sesión iniciada en tu cuenta de {brandName}{' '}
|
|
84
|
+
cerró la sesión de todos los demás dispositivos e inicios de sesión. Ahora hay que
|
|
85
|
+
volver a configurar los métodos de recuperación de la cuenta.
|
|
86
|
+
</>
|
|
87
|
+
),
|
|
88
|
+
ifYou: 'Si fuiste tú, no tienes que hacer nada más aquí. Termina de configurar tus métodos de recuperación en la aplicación.',
|
|
89
|
+
notYouWarning:
|
|
90
|
+
'Si no fuiste tú, es posible que otra persona tenga acceso a tu cuenta y que se haya eliminado tu propio inicio de sesión.',
|
|
91
|
+
resetWarning:
|
|
92
|
+
'Al proteger la cuenta también se restablecieron sus métodos de recuperación, así que es posible que tu frase de recuperación, copia de seguridad, llave de acceso o PIN de recuperación ya no te permitan volver a entrar.',
|
|
93
|
+
support:
|
|
94
|
+
'Escribe cuanto antes a {supportEmail} para que te ayuden a recuperar el acceso a tu cuenta.',
|
|
95
|
+
supportFallback:
|
|
96
|
+
'Comunícate cuanto antes con la organización que te dio acceso a {brandName} para que te ayuden a recuperar el acceso a tu cuenta.',
|
|
97
|
+
button: 'Abrir {brandName}',
|
|
98
|
+
sincerely: 'Atentamente,',
|
|
99
|
+
subject: 'Se cerró la sesión de otros dispositivos en tu cuenta',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const FR: Strings = {
|
|
103
|
+
preview: 'Les autres appareils ont été déconnectés de votre compte',
|
|
104
|
+
heading: 'Votre Compte a Été Sécurisé',
|
|
105
|
+
body: (securedAt, brandName) => (
|
|
106
|
+
<>
|
|
107
|
+
Le <strong>{securedAt}</strong>, une personne connectée à votre compte {brandName} a
|
|
108
|
+
déconnecté tous les autres appareils et connexions. Les méthodes de récupération du
|
|
109
|
+
compte doivent maintenant être configurées à nouveau.
|
|
110
|
+
</>
|
|
111
|
+
),
|
|
112
|
+
ifYou: 'Si c’était vous, vous n’avez rien d’autre à faire ici. Terminez la configuration de vos méthodes de récupération dans l’application.',
|
|
113
|
+
notYouWarning:
|
|
114
|
+
'Si ce n’était pas vous, quelqu’un d’autre a peut-être accès à votre compte, et votre propre connexion a peut-être été supprimée.',
|
|
115
|
+
resetWarning:
|
|
116
|
+
'La sécurisation du compte a aussi réinitialisé ses méthodes de récupération : votre phrase de récupération, votre sauvegarde, votre clé d’accès ou votre code PIN de récupération ne vous permettront peut-être plus de vous reconnecter.',
|
|
117
|
+
support:
|
|
118
|
+
'Écrivez sans attendre à {supportEmail} pour obtenir de l’aide afin de récupérer l’accès à votre compte.',
|
|
119
|
+
supportFallback:
|
|
120
|
+
'Contactez sans attendre l’organisation qui vous a donné accès à {brandName} pour obtenir de l’aide afin de récupérer l’accès à votre compte.',
|
|
121
|
+
button: 'Ouvrir {brandName}',
|
|
122
|
+
sincerely: 'Cordialement,',
|
|
123
|
+
subject: 'Les autres appareils ont été déconnectés de votre compte',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const AR: Strings = {
|
|
127
|
+
preview: 'تم تسجيل خروج الأجهزة الأخرى من حسابك',
|
|
128
|
+
heading: 'تم تأمين حسابك',
|
|
129
|
+
body: (securedAt, brandName) => (
|
|
130
|
+
<>
|
|
131
|
+
في <strong>{securedAt}</strong>، قام شخص مسجّل الدخول إلى حسابك في {brandName} بتسجيل
|
|
132
|
+
خروج جميع الأجهزة وعمليات تسجيل الدخول الأخرى. يجب الآن إعداد طرق استعادة الحساب من
|
|
133
|
+
جديد.
|
|
134
|
+
</>
|
|
135
|
+
),
|
|
136
|
+
ifYou: 'إذا كنت أنت، فلا داعي لفعل أي شيء آخر هنا. أكمل إعداد طرق الاستعادة في التطبيق.',
|
|
137
|
+
notYouWarning:
|
|
138
|
+
'إذا لم تكن أنت، فقد يكون لدى شخص آخر حق الوصول إلى حسابك، وربما تمت إزالة تسجيل الدخول الخاص بك.',
|
|
139
|
+
resetWarning:
|
|
140
|
+
'أدى تأمين الحساب أيضًا إلى إعادة تعيين طرق استعادته، لذلك قد لا تتمكن بعد الآن من العودة إلى حسابك باستخدام عبارة الاستعادة أو النسخة الاحتياطية أو مفتاح المرور أو رمز PIN للاستعادة.',
|
|
141
|
+
support: 'راسل {supportEmail} فورًا للحصول على مساعدة في استعادة الوصول إلى حسابك.',
|
|
142
|
+
supportFallback:
|
|
143
|
+
'تواصل فورًا مع الجهة التي منحتك الوصول إلى {brandName} للحصول على مساعدة في استعادة الوصول إلى حسابك.',
|
|
144
|
+
button: 'فتح {brandName}',
|
|
145
|
+
sincerely: 'مع التحيات،',
|
|
146
|
+
subject: 'تم تسجيل خروج الأجهزة الأخرى من حسابك',
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const STRINGS: Record<NotificationLocale, Strings> = { en: EN, es: ES, fr: FR, ar: AR };
|
|
150
|
+
|
|
151
|
+
export const AccountSecured: React.FC<AccountSecuredProps> = ({ branding, securedAt, locale }) => {
|
|
152
|
+
const catalogLocale = resolveCatalogLocale(locale);
|
|
153
|
+
const s = STRINGS[catalogLocale];
|
|
154
|
+
const { brandName, supportEmail, appUrl } = branding;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<Layout branding={branding} preview={s.preview} {...localeProp(locale)}>
|
|
158
|
+
<Text style={heading}>{s.heading}</Text>
|
|
159
|
+
|
|
160
|
+
<Text style={paragraph}>
|
|
161
|
+
{s.body(formatNoticeTimestamp(securedAt, catalogLocale), brandName)}
|
|
162
|
+
</Text>
|
|
163
|
+
|
|
164
|
+
<Text style={paragraph}>{s.ifYou}</Text>
|
|
165
|
+
|
|
166
|
+
<Text style={warning}>{s.notYouWarning}</Text>
|
|
167
|
+
|
|
168
|
+
<Text style={paragraph}>{s.resetWarning}</Text>
|
|
169
|
+
|
|
170
|
+
<Text style={paragraph}>
|
|
171
|
+
{supportEmail
|
|
172
|
+
? interpolate(s.support, { supportEmail })
|
|
173
|
+
: interpolate(s.supportFallback, { brandName })}
|
|
174
|
+
</Text>
|
|
175
|
+
|
|
176
|
+
<Section style={buttonWrapper}>
|
|
177
|
+
<EmailButton href={appUrl} branding={branding}>
|
|
178
|
+
{interpolate(s.button, { brandName })}
|
|
179
|
+
</EmailButton>
|
|
180
|
+
</Section>
|
|
181
|
+
|
|
182
|
+
<Text style={signOff}>
|
|
183
|
+
{s.sincerely}
|
|
184
|
+
<br />
|
|
185
|
+
{interpolate(SHARED[catalogLocale].teamSignature, { brandName })}
|
|
186
|
+
</Text>
|
|
187
|
+
|
|
188
|
+
<LinkFallback href={appUrl} {...localeProp(locale)} />
|
|
189
|
+
</Layout>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export const getAccountSecuredSubject = (_branding: TenantBranding, locale?: string): string =>
|
|
194
|
+
STRINGS[resolveCatalogLocale(locale)].subject;
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Styles
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
const heading: React.CSSProperties = {
|
|
201
|
+
fontSize: 24,
|
|
202
|
+
fontWeight: 600,
|
|
203
|
+
color: '#111827',
|
|
204
|
+
margin: '0 0 24px',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const paragraph: React.CSSProperties = {
|
|
208
|
+
fontSize: 16,
|
|
209
|
+
color: '#374151',
|
|
210
|
+
lineHeight: '24px',
|
|
211
|
+
margin: '0 0 24px',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const warning: React.CSSProperties = {
|
|
215
|
+
...paragraph,
|
|
216
|
+
fontWeight: 600,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const buttonWrapper: React.CSSProperties = {
|
|
220
|
+
margin: '0 0 24px',
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const signOff: React.CSSProperties = {
|
|
224
|
+
fontSize: 14,
|
|
225
|
+
color: '#374151',
|
|
226
|
+
lineHeight: '20px',
|
|
227
|
+
margin: '24px 0 0',
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Preview
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
export default function Preview() {
|
|
235
|
+
return <AccountSecured branding={DEFAULT_BRANDING} securedAt="October 2, 2026, 3:00 PM" />;
|
|
236
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EscrowPinSignIn — tells the account owner that a new device just signed in
|
|
3
|
+
* with their recovery PIN, and links to the in-app "Secure my account" action
|
|
4
|
+
* (which stops other devices and logins from signing in again and turns off the old
|
|
5
|
+
* recovery methods).
|
|
6
|
+
*
|
|
7
|
+
* Used by: lca-api `/keys/escrow/complete` for PIN releases
|
|
8
|
+
* (templateId: 'escrow-pin-sign-in')
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Text, Section } from '@react-email/components';
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
|
|
14
|
+
import type { TenantBranding } from '../branding';
|
|
15
|
+
import { DEFAULT_BRANDING } from '../branding';
|
|
16
|
+
import { Layout } from '../components/Layout';
|
|
17
|
+
import { EmailButton } from '../components/EmailButton';
|
|
18
|
+
import { LinkFallback } from '../components/LinkFallback';
|
|
19
|
+
import type { NotificationLocale } from '../i18n';
|
|
20
|
+
import {
|
|
21
|
+
resolveCatalogLocale,
|
|
22
|
+
interpolate,
|
|
23
|
+
SHARED,
|
|
24
|
+
formatNoticeTimestamp,
|
|
25
|
+
localeProp,
|
|
26
|
+
} from '../i18n';
|
|
27
|
+
|
|
28
|
+
export interface EscrowPinSignInProps {
|
|
29
|
+
branding: TenantBranding;
|
|
30
|
+
/** ISO timestamp of the PIN sign-in (formatted per locale), or display text. */
|
|
31
|
+
signedInAt: string;
|
|
32
|
+
/** In-app link to the "Secure my account" action. */
|
|
33
|
+
secureUrl: string;
|
|
34
|
+
/** Recipient locale (BCP-47). Defaults to English. */
|
|
35
|
+
locale?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type Strings = {
|
|
39
|
+
preview: string;
|
|
40
|
+
heading: string;
|
|
41
|
+
body: (signedInAt: string) => React.ReactNode;
|
|
42
|
+
warning: string;
|
|
43
|
+
button: string;
|
|
44
|
+
sincerely: string;
|
|
45
|
+
subject: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const EN: Strings = {
|
|
49
|
+
preview: 'New device signed in with your PIN',
|
|
50
|
+
heading: 'New Device Signed In With Your PIN',
|
|
51
|
+
body: signedInAt => (
|
|
52
|
+
<>
|
|
53
|
+
A new device signed in to your account with your recovery PIN on{' '}
|
|
54
|
+
<strong>{signedInAt}</strong>.
|
|
55
|
+
</>
|
|
56
|
+
),
|
|
57
|
+
warning: 'Not you? Secure your account.',
|
|
58
|
+
button: 'Secure my account',
|
|
59
|
+
sincerely: 'Sincerely,',
|
|
60
|
+
subject: 'New device signed in with your PIN',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const ES: Strings = {
|
|
64
|
+
preview: 'Un dispositivo nuevo inició sesión con tu PIN',
|
|
65
|
+
heading: 'Un Dispositivo Nuevo Inició Sesión con tu PIN',
|
|
66
|
+
body: signedInAt => (
|
|
67
|
+
<>
|
|
68
|
+
Un dispositivo nuevo inició sesión en tu cuenta con tu PIN de recuperación el{' '}
|
|
69
|
+
<strong>{signedInAt}</strong>.
|
|
70
|
+
</>
|
|
71
|
+
),
|
|
72
|
+
warning: '¿No fuiste tú? Protege tu cuenta.',
|
|
73
|
+
button: 'Proteger mi cuenta',
|
|
74
|
+
sincerely: 'Atentamente,',
|
|
75
|
+
subject: 'Un dispositivo nuevo inició sesión con tu PIN',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const FR: Strings = {
|
|
79
|
+
preview: 'Un nouvel appareil s’est connecté avec votre code PIN',
|
|
80
|
+
heading: 'Un Nouvel Appareil S’est Connecté avec Votre Code PIN',
|
|
81
|
+
body: signedInAt => (
|
|
82
|
+
<>
|
|
83
|
+
Un nouvel appareil s’est connecté à votre compte avec votre code PIN de récupération le{' '}
|
|
84
|
+
<strong>{signedInAt}</strong>.
|
|
85
|
+
</>
|
|
86
|
+
),
|
|
87
|
+
warning: 'Ce n’était pas vous ? Sécurisez votre compte.',
|
|
88
|
+
button: 'Sécuriser mon compte',
|
|
89
|
+
sincerely: 'Cordialement,',
|
|
90
|
+
subject: 'Un nouvel appareil s’est connecté avec votre code PIN',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const AR: Strings = {
|
|
94
|
+
preview: 'تم تسجيل الدخول من جهاز جديد باستخدام رمز PIN الخاص بك',
|
|
95
|
+
heading: 'تم تسجيل الدخول من جهاز جديد باستخدام رمز PIN الخاص بك',
|
|
96
|
+
body: signedInAt => (
|
|
97
|
+
<>
|
|
98
|
+
سجّل جهاز جديد الدخول إلى حسابك باستخدام رمز PIN الخاص بالاستعادة في{' '}
|
|
99
|
+
<strong>{signedInAt}</strong>.
|
|
100
|
+
</>
|
|
101
|
+
),
|
|
102
|
+
warning: 'لم يكن أنت؟ أمّن حسابك.',
|
|
103
|
+
button: 'تأمين حسابي',
|
|
104
|
+
sincerely: 'مع التحيات،',
|
|
105
|
+
subject: 'تم تسجيل الدخول من جهاز جديد باستخدام رمز PIN الخاص بك',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const STRINGS: Record<NotificationLocale, Strings> = { en: EN, es: ES, fr: FR, ar: AR };
|
|
109
|
+
|
|
110
|
+
export const EscrowPinSignIn: React.FC<EscrowPinSignInProps> = ({
|
|
111
|
+
branding,
|
|
112
|
+
signedInAt,
|
|
113
|
+
secureUrl,
|
|
114
|
+
locale,
|
|
115
|
+
}) => {
|
|
116
|
+
const catalogLocale = resolveCatalogLocale(locale);
|
|
117
|
+
const s = STRINGS[catalogLocale];
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Layout branding={branding} preview={s.preview} {...localeProp(locale)}>
|
|
121
|
+
<Text style={heading}>{s.heading}</Text>
|
|
122
|
+
|
|
123
|
+
<Text style={paragraph}>
|
|
124
|
+
{s.body(formatNoticeTimestamp(signedInAt, catalogLocale))}
|
|
125
|
+
</Text>
|
|
126
|
+
|
|
127
|
+
<Text style={warning}>{s.warning}</Text>
|
|
128
|
+
|
|
129
|
+
<Section style={buttonWrapper}>
|
|
130
|
+
<EmailButton href={secureUrl} branding={branding}>
|
|
131
|
+
{s.button}
|
|
132
|
+
</EmailButton>
|
|
133
|
+
</Section>
|
|
134
|
+
|
|
135
|
+
<Text style={signOff}>
|
|
136
|
+
{s.sincerely}
|
|
137
|
+
<br />
|
|
138
|
+
{interpolate(SHARED[catalogLocale].teamSignature, {
|
|
139
|
+
brandName: branding.brandName,
|
|
140
|
+
})}
|
|
141
|
+
</Text>
|
|
142
|
+
|
|
143
|
+
<LinkFallback href={secureUrl} {...localeProp(locale)} />
|
|
144
|
+
</Layout>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const getEscrowPinSignInSubject = (_branding: TenantBranding, locale?: string): string =>
|
|
149
|
+
STRINGS[resolveCatalogLocale(locale)].subject;
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Styles
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
const heading: React.CSSProperties = {
|
|
156
|
+
fontSize: 24,
|
|
157
|
+
fontWeight: 600,
|
|
158
|
+
color: '#111827',
|
|
159
|
+
margin: '0 0 24px',
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const paragraph: React.CSSProperties = {
|
|
163
|
+
fontSize: 16,
|
|
164
|
+
color: '#374151',
|
|
165
|
+
lineHeight: '24px',
|
|
166
|
+
margin: '0 0 24px',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const warning: React.CSSProperties = {
|
|
170
|
+
...paragraph,
|
|
171
|
+
fontWeight: 600,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const buttonWrapper: React.CSSProperties = {
|
|
175
|
+
margin: '0 0 24px',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const signOff: React.CSSProperties = {
|
|
179
|
+
fontSize: 14,
|
|
180
|
+
color: '#374151',
|
|
181
|
+
lineHeight: '20px',
|
|
182
|
+
margin: '24px 0 0',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Preview
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
export default function Preview() {
|
|
190
|
+
return (
|
|
191
|
+
<EscrowPinSignIn
|
|
192
|
+
branding={DEFAULT_BRANDING}
|
|
193
|
+
signedInAt="October 2, 2026, 3:00 PM"
|
|
194
|
+
secureUrl="https://learncard.app/account/secure"
|
|
195
|
+
/>
|
|
196
|
+
);
|
|
197
|
+
}
|
package/src/templates/index.ts
CHANGED
|
@@ -1,64 +1,124 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
// One export declaration per template module. Projects that resolve this package
|
|
2
|
+
// through its `development` condition (source) typecheck without `jsx`, and TypeScript
|
|
3
|
+
// reports TS6142 once per declaration that imports a .tsx module from this .ts file;
|
|
4
|
+
// a separate `export type` line for the same module would add a second report.
|
|
3
5
|
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
+
export {
|
|
7
|
+
VerificationCode,
|
|
8
|
+
getVerificationCodeSubject,
|
|
9
|
+
type VerificationCodeProps,
|
|
10
|
+
type VerificationCodeVariant,
|
|
11
|
+
} from './verification-code';
|
|
6
12
|
|
|
7
|
-
export {
|
|
8
|
-
export type { GuardianApprovalProps } from './guardian-approval';
|
|
13
|
+
export { InboxClaim, getInboxClaimSubject, type InboxClaimProps } from './inbox-claim';
|
|
9
14
|
|
|
10
|
-
export {
|
|
11
|
-
|
|
15
|
+
export {
|
|
16
|
+
GuardianApproval,
|
|
17
|
+
getGuardianApprovalSubject,
|
|
18
|
+
type GuardianApprovalProps,
|
|
19
|
+
} from './guardian-approval';
|
|
12
20
|
|
|
13
|
-
export {
|
|
14
|
-
|
|
21
|
+
export {
|
|
22
|
+
AccountApproved,
|
|
23
|
+
getAccountApprovedSubject,
|
|
24
|
+
type AccountApprovedProps,
|
|
25
|
+
} from './account-approved';
|
|
15
26
|
|
|
16
|
-
export {
|
|
17
|
-
|
|
27
|
+
export { RecoveryKey, getRecoveryKeySubject, type RecoveryKeyProps } from './recovery-key';
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
EndorsementRequest,
|
|
31
|
+
getEndorsementRequestSubject,
|
|
32
|
+
type EndorsementRequestProps,
|
|
33
|
+
} from './endorsement-request';
|
|
18
34
|
|
|
19
35
|
export {
|
|
20
36
|
CredentialAwaitingGuardian,
|
|
21
37
|
getCredentialAwaitingGuardianSubject,
|
|
38
|
+
type CredentialAwaitingGuardianProps,
|
|
22
39
|
} from './credential-awaiting-guardian';
|
|
23
|
-
export type { CredentialAwaitingGuardianProps } from './credential-awaiting-guardian';
|
|
24
40
|
|
|
25
|
-
export {
|
|
26
|
-
|
|
41
|
+
export {
|
|
42
|
+
GuardianApprovedClaim,
|
|
43
|
+
getGuardianApprovedClaimSubject,
|
|
44
|
+
type GuardianApprovedClaimProps,
|
|
45
|
+
} from './guardian-approved-claim';
|
|
27
46
|
|
|
28
47
|
export {
|
|
29
48
|
GuardianCredentialApproval,
|
|
30
49
|
getGuardianCredentialApprovalSubject,
|
|
50
|
+
type GuardianCredentialApprovalProps,
|
|
31
51
|
} from './guardian-credential-approval';
|
|
32
|
-
export type { GuardianCredentialApprovalProps } from './guardian-credential-approval';
|
|
33
52
|
|
|
34
|
-
export {
|
|
35
|
-
|
|
53
|
+
export {
|
|
54
|
+
GuardianEmailOtp,
|
|
55
|
+
getGuardianEmailOtpSubject,
|
|
56
|
+
type GuardianEmailOtpProps,
|
|
57
|
+
} from './guardian-email-otp';
|
|
36
58
|
|
|
37
59
|
export {
|
|
38
60
|
GuardianRejectedCredential,
|
|
39
61
|
getGuardianRejectedCredentialSubject,
|
|
62
|
+
type GuardianRejectedCredentialProps,
|
|
40
63
|
} from './guardian-rejected-credential';
|
|
41
|
-
export type { GuardianRejectedCredentialProps } from './guardian-rejected-credential';
|
|
42
64
|
|
|
43
|
-
export {
|
|
44
|
-
|
|
65
|
+
export {
|
|
66
|
+
EmailVerification,
|
|
67
|
+
getEmailVerificationSubject,
|
|
68
|
+
type EmailVerificationProps,
|
|
69
|
+
} from './email-verification';
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
CredentialUpdated,
|
|
73
|
+
getCredentialUpdatedSubject,
|
|
74
|
+
type CredentialUpdatedProps,
|
|
75
|
+
} from './credential-updated';
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
AccountSignInChanged,
|
|
79
|
+
getAccountSignInChangedSubject,
|
|
80
|
+
type AccountSignInChangedProps,
|
|
81
|
+
} from './account-sign-in-changed';
|
|
45
82
|
|
|
46
|
-
export {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
83
|
+
export {
|
|
84
|
+
EscrowHoldStarted,
|
|
85
|
+
getEscrowHoldStartedSubject,
|
|
86
|
+
type EscrowHoldStartedProps,
|
|
87
|
+
} from './escrow-hold-started';
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
EscrowHoldReminder,
|
|
91
|
+
getEscrowHoldReminderSubject,
|
|
92
|
+
type EscrowHoldReminderProps,
|
|
93
|
+
} from './escrow-hold-reminder';
|
|
50
94
|
|
|
51
|
-
export {
|
|
52
|
-
|
|
95
|
+
export {
|
|
96
|
+
EscrowHoldReleased,
|
|
97
|
+
getEscrowHoldReleasedSubject,
|
|
98
|
+
type EscrowHoldReleasedProps,
|
|
99
|
+
} from './escrow-hold-released';
|
|
53
100
|
|
|
54
|
-
export {
|
|
55
|
-
|
|
101
|
+
export {
|
|
102
|
+
EscrowHoldCancelled,
|
|
103
|
+
getEscrowHoldCancelledSubject,
|
|
104
|
+
type EscrowHoldCancelledProps,
|
|
105
|
+
type EscrowHoldCancelledReason,
|
|
106
|
+
} from './escrow-hold-cancelled';
|
|
56
107
|
|
|
57
|
-
export {
|
|
58
|
-
|
|
108
|
+
export {
|
|
109
|
+
EscrowPinLocked,
|
|
110
|
+
getEscrowPinLockedSubject,
|
|
111
|
+
type EscrowPinLockedProps,
|
|
112
|
+
} from './escrow-pin-locked';
|
|
59
113
|
|
|
60
|
-
export {
|
|
61
|
-
|
|
114
|
+
export {
|
|
115
|
+
EscrowPinSignIn,
|
|
116
|
+
getEscrowPinSignInSubject,
|
|
117
|
+
type EscrowPinSignInProps,
|
|
118
|
+
} from './escrow-pin-sign-in';
|
|
62
119
|
|
|
63
|
-
export {
|
|
64
|
-
|
|
120
|
+
export {
|
|
121
|
+
AccountSecured,
|
|
122
|
+
getAccountSecuredSubject,
|
|
123
|
+
type AccountSecuredProps,
|
|
124
|
+
} from './account-secured';
|