@learncard/email-templates 1.0.1 → 1.0.3

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 (77) hide show
  1. package/README.md +43 -43
  2. package/dist/previews/account-approved-scenarios.d.ts +2 -1
  3. package/dist/previews/account-approved-scenarios.d.ts.map +1 -1
  4. package/dist/previews/endorsement-request-scenarios.d.ts +2 -1
  5. package/dist/previews/endorsement-request-scenarios.d.ts.map +1 -1
  6. package/dist/previews/guardian-approval-scenarios.d.ts +2 -1
  7. package/dist/previews/guardian-approval-scenarios.d.ts.map +1 -1
  8. package/dist/previews/guardian-credential-scenarios.d.ts +2 -1
  9. package/dist/previews/guardian-credential-scenarios.d.ts.map +1 -1
  10. package/dist/previews/inbox-claim-scenarios.d.ts +2 -1
  11. package/dist/previews/inbox-claim-scenarios.d.ts.map +1 -1
  12. package/dist/previews/recovery-key-scenarios.d.ts +2 -1
  13. package/dist/previews/recovery-key-scenarios.d.ts.map +1 -1
  14. package/dist/previews/verification-code-scenarios.d.ts +2 -1
  15. package/dist/previews/verification-code-scenarios.d.ts.map +1 -1
  16. package/dist/templates/account-approved.d.ts +1 -1
  17. package/dist/templates/account-approved.d.ts.map +1 -1
  18. package/dist/templates/credential-awaiting-guardian.d.ts +1 -1
  19. package/dist/templates/credential-awaiting-guardian.d.ts.map +1 -1
  20. package/dist/templates/email-verification.d.ts +1 -1
  21. package/dist/templates/email-verification.d.ts.map +1 -1
  22. package/dist/templates/endorsement-request.d.ts +1 -1
  23. package/dist/templates/endorsement-request.d.ts.map +1 -1
  24. package/dist/templates/guardian-approval.d.ts +1 -1
  25. package/dist/templates/guardian-approval.d.ts.map +1 -1
  26. package/dist/templates/guardian-approved-claim.d.ts +1 -1
  27. package/dist/templates/guardian-approved-claim.d.ts.map +1 -1
  28. package/dist/templates/guardian-credential-approval.d.ts +1 -1
  29. package/dist/templates/guardian-credential-approval.d.ts.map +1 -1
  30. package/dist/templates/guardian-email-otp.d.ts +1 -1
  31. package/dist/templates/guardian-email-otp.d.ts.map +1 -1
  32. package/dist/templates/guardian-rejected-credential.d.ts +1 -1
  33. package/dist/templates/guardian-rejected-credential.d.ts.map +1 -1
  34. package/dist/templates/inbox-claim.d.ts +1 -1
  35. package/dist/templates/inbox-claim.d.ts.map +1 -1
  36. package/dist/templates/recovery-key.d.ts +1 -1
  37. package/dist/templates/recovery-key.d.ts.map +1 -1
  38. package/dist/templates/verification-code.d.ts +1 -1
  39. package/dist/templates/verification-code.d.ts.map +1 -1
  40. package/dist/templates/verification-code.js +9 -9
  41. package/dist/templates/verification-code.js.map +1 -1
  42. package/package.json +49 -39
  43. package/src/__tests__/render.test.ts +432 -0
  44. package/src/__tests__/sms.test.ts +95 -0
  45. package/src/__tests__/tenant-registry.test.ts +257 -0
  46. package/src/branding.ts +61 -0
  47. package/src/components/CodeBlock.tsx +61 -0
  48. package/src/components/EmailButton.tsx +36 -0
  49. package/src/components/IssuerLogo.tsx +41 -0
  50. package/src/components/Layout.tsx +192 -0
  51. package/src/components/LinkFallback.tsx +50 -0
  52. package/src/index.ts +50 -0
  53. package/src/previews/_fixtures.tsx +51 -0
  54. package/src/previews/account-approved-scenarios.tsx +31 -0
  55. package/src/previews/endorsement-request-scenarios.tsx +50 -0
  56. package/src/previews/guardian-approval-scenarios.tsx +39 -0
  57. package/src/previews/guardian-credential-scenarios.tsx +88 -0
  58. package/src/previews/inbox-claim-scenarios.tsx +66 -0
  59. package/src/previews/recovery-key-scenarios.tsx +32 -0
  60. package/src/previews/verification-code-scenarios.tsx +60 -0
  61. package/src/render.ts +409 -0
  62. package/src/sms.ts +68 -0
  63. package/src/templates/account-approved.tsx +105 -0
  64. package/src/templates/credential-awaiting-guardian.tsx +137 -0
  65. package/src/templates/email-verification.tsx +110 -0
  66. package/src/templates/endorsement-request.tsx +157 -0
  67. package/src/templates/guardian-approval.tsx +122 -0
  68. package/src/templates/guardian-approved-claim.tsx +114 -0
  69. package/src/templates/guardian-credential-approval.tsx +127 -0
  70. package/src/templates/guardian-email-otp.tsx +101 -0
  71. package/src/templates/guardian-rejected-credential.tsx +103 -0
  72. package/src/templates/inbox-claim.tsx +141 -0
  73. package/src/templates/index.ts +35 -0
  74. package/src/templates/recovery-key.tsx +102 -0
  75. package/src/templates/verification-code.tsx +164 -0
  76. package/src/tenant-registry.ts +204 -0
  77. package/LICENSE +0 -21
@@ -0,0 +1,127 @@
1
+ /**
2
+ * GuardianCredentialApproval — email sent to a guardian asking them to
3
+ * approve or decline a specific credential issued to their ward.
4
+ *
5
+ * Used by: brain-service (templateAlias: 'guardian-credential-approval')
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 { IssuerLogo } from '../components/IssuerLogo';
16
+ import { LinkFallback } from '../components/LinkFallback';
17
+
18
+ export interface GuardianCredentialApprovalProps {
19
+ branding: TenantBranding;
20
+ approvalUrl: string;
21
+ approvalToken?: string;
22
+ issuer?: {
23
+ name?: string;
24
+ logoUrl?: string;
25
+ };
26
+ credential?: {
27
+ name?: string;
28
+ };
29
+ recipient?: {
30
+ email?: string;
31
+ };
32
+ }
33
+
34
+ export const GuardianCredentialApproval: React.FC<GuardianCredentialApprovalProps> = ({
35
+ branding,
36
+ approvalUrl,
37
+ issuer,
38
+ credential,
39
+ recipient,
40
+ }) => {
41
+ const issuerName = issuer?.name ?? 'An issuer';
42
+ const credentialName = credential?.name ?? 'a credential';
43
+ const recipientEmail = recipient?.email ?? 'a student';
44
+
45
+ return (
46
+ <Layout branding={branding} preview={`A credential needs your approval for ${recipientEmail}`} showHeaderLogo={false}>
47
+ <IssuerLogo logoUrl={issuer?.logoUrl} alt={issuerName ? `${issuerName} logo` : undefined} />
48
+
49
+ <Text style={heading}>A credential needs your approval</Text>
50
+
51
+ <Text style={paragraph}>Hello,</Text>
52
+
53
+ <Text style={paragraph}>
54
+ <strong>{issuerName}</strong> has issued the credential{' '}
55
+ <strong>&ldquo;{credentialName}&rdquo;</strong> to{' '}
56
+ <strong>{recipientEmail}</strong>.
57
+ </Text>
58
+
59
+ <Text style={paragraph}>
60
+ As their guardian, your approval is required before they can claim it.
61
+ Please review the credential and approve or decline below.
62
+ </Text>
63
+
64
+ <Section style={buttonWrapper}>
65
+ <EmailButton href={approvalUrl} branding={branding}>
66
+ Review &amp; Approve &rarr;
67
+ </EmailButton>
68
+ </Section>
69
+
70
+ <Text style={signOff}>
71
+ Thank you,<br />The {branding.brandName} Team
72
+ </Text>
73
+
74
+ <LinkFallback href={approvalUrl} />
75
+ </Layout>
76
+ );
77
+ };
78
+
79
+ export const getGuardianCredentialApprovalSubject = (
80
+ _branding: TenantBranding,
81
+ ): string => 'A credential needs your approval';
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Styles
85
+ // ---------------------------------------------------------------------------
86
+
87
+ const heading: React.CSSProperties = {
88
+ fontSize: 24,
89
+ fontWeight: 600,
90
+ color: '#111827',
91
+ margin: '0 0 24px',
92
+ };
93
+
94
+ const paragraph: React.CSSProperties = {
95
+ fontSize: 16,
96
+ color: '#374151',
97
+ lineHeight: '24px',
98
+ margin: '0 0 24px',
99
+ };
100
+
101
+ const buttonWrapper: React.CSSProperties = {
102
+ margin: '0 0 24px',
103
+ };
104
+
105
+ const signOff: React.CSSProperties = {
106
+ fontSize: 14,
107
+ color: '#374151',
108
+ lineHeight: '20px',
109
+ margin: '24px 0 0',
110
+ };
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Preview
114
+ // ---------------------------------------------------------------------------
115
+
116
+ export default function Preview() {
117
+ return (
118
+ <GuardianCredentialApproval
119
+ branding={DEFAULT_BRANDING}
120
+ approvalUrl="https://learncard.app/guardian/approve?token=abc123"
121
+ approvalToken="abc123"
122
+ issuer={{ name: 'Springfield School District' }}
123
+ credential={{ name: 'Perfect Attendance Award' }}
124
+ recipient={{ email: 'student@example.com' }}
125
+ />
126
+ );
127
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * GuardianEmailOtp — verification code email sent to a guardian when they
3
+ * open an approval link and need to confirm their identity via OTP.
4
+ *
5
+ * Used by: brain-service (templateAlias: 'guardian-email-otp')
6
+ */
7
+
8
+ import { Text } 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 { CodeBlock } from '../components/CodeBlock';
15
+
16
+ export interface GuardianEmailOtpProps {
17
+ branding: TenantBranding;
18
+ verificationCode: string;
19
+ }
20
+
21
+ export const GuardianEmailOtp: React.FC<GuardianEmailOtpProps> = ({
22
+ branding,
23
+ verificationCode,
24
+ }) => {
25
+ return (
26
+ <Layout branding={branding} preview={`Your verification code: ${verificationCode}`}>
27
+ <Text style={heading}>Your verification code</Text>
28
+
29
+ <Text style={paragraph}>Hi there,</Text>
30
+
31
+ <Text style={paragraph}>
32
+ Here is your verification code to confirm your identity as guardian and
33
+ complete your review:
34
+ </Text>
35
+
36
+ <CodeBlock code={verificationCode} label="Verification code" />
37
+
38
+ <Text style={muted}>
39
+ This code expires in <strong style={{ color: '#374151' }}>1 hour</strong>.
40
+ Do not share it with anyone.
41
+ </Text>
42
+
43
+ <Text style={muted}>
44
+ If you did not request this code, you can safely ignore this email.
45
+ </Text>
46
+
47
+ <Text style={signOff}>
48
+ Sincerely,<br />The {branding.brandName} Team
49
+ </Text>
50
+ </Layout>
51
+ );
52
+ };
53
+
54
+ export const getGuardianEmailOtpSubject = (
55
+ _branding: TenantBranding,
56
+ ): string => 'Your Verification Code';
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Styles
60
+ // ---------------------------------------------------------------------------
61
+
62
+ const heading: React.CSSProperties = {
63
+ fontSize: 24,
64
+ fontWeight: 600,
65
+ color: '#111827',
66
+ margin: '0 0 24px',
67
+ };
68
+
69
+ const paragraph: React.CSSProperties = {
70
+ fontSize: 16,
71
+ color: '#374151',
72
+ lineHeight: '24px',
73
+ margin: '0 0 24px',
74
+ };
75
+
76
+ const muted: React.CSSProperties = {
77
+ fontSize: 14,
78
+ color: '#6b7280',
79
+ lineHeight: '20px',
80
+ margin: '0 0 24px',
81
+ };
82
+
83
+ const signOff: React.CSSProperties = {
84
+ fontSize: 14,
85
+ color: '#374151',
86
+ lineHeight: '20px',
87
+ margin: '24px 0 0',
88
+ };
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Preview
92
+ // ---------------------------------------------------------------------------
93
+
94
+ export default function Preview() {
95
+ return (
96
+ <GuardianEmailOtp
97
+ branding={DEFAULT_BRANDING}
98
+ verificationCode="847293"
99
+ />
100
+ );
101
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * GuardianRejectedCredential — email sent to a student when their guardian
3
+ * has declined to approve a credential.
4
+ *
5
+ * Used by: brain-service (templateAlias: 'guardian-rejected-credential')
6
+ */
7
+
8
+ import { Text } 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
+
15
+ export interface GuardianRejectedCredentialProps {
16
+ branding: TenantBranding;
17
+ issuer?: {
18
+ name?: string;
19
+ };
20
+ credential?: {
21
+ name?: string;
22
+ };
23
+ recipient?: {
24
+ email?: string;
25
+ };
26
+ }
27
+
28
+ export const GuardianRejectedCredential: React.FC<GuardianRejectedCredentialProps> = ({
29
+ branding,
30
+ issuer,
31
+ credential,
32
+ }) => {
33
+ const issuerName = issuer?.name ?? 'An issuer';
34
+ const credentialName = credential?.name;
35
+
36
+ return (
37
+ <Layout branding={branding} preview="Your guardian has declined a credential">
38
+ <Text style={heading}>Credential not approved</Text>
39
+
40
+ <Text style={paragraph}>Hi there,</Text>
41
+
42
+ <Text style={paragraph}>
43
+ Your guardian has reviewed and <strong>declined</strong> the credential{' '}
44
+ {credentialName ? <><strong>&ldquo;{credentialName}&rdquo;</strong> </> : ''}
45
+ issued by <strong>{issuerName}</strong>.
46
+ </Text>
47
+
48
+ <Text style={paragraph}>
49
+ The credential will not be added to your account. If you think this was
50
+ a mistake, please reach out to your guardian or contact{' '}
51
+ <strong>{issuerName}</strong> directly.
52
+ </Text>
53
+
54
+ <Text style={signOff}>
55
+ Sincerely,<br />The {branding.brandName} Team
56
+ </Text>
57
+ </Layout>
58
+ );
59
+ };
60
+
61
+ export const getGuardianRejectedCredentialSubject = (
62
+ _branding: TenantBranding,
63
+ ): string => 'Credential Not Approved';
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Styles
67
+ // ---------------------------------------------------------------------------
68
+
69
+ const heading: React.CSSProperties = {
70
+ fontSize: 24,
71
+ fontWeight: 600,
72
+ color: '#111827',
73
+ margin: '0 0 24px',
74
+ };
75
+
76
+ const paragraph: React.CSSProperties = {
77
+ fontSize: 16,
78
+ color: '#374151',
79
+ lineHeight: '24px',
80
+ margin: '0 0 24px',
81
+ };
82
+
83
+ const signOff: React.CSSProperties = {
84
+ fontSize: 14,
85
+ color: '#374151',
86
+ lineHeight: '20px',
87
+ margin: '24px 0 0',
88
+ };
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Preview
92
+ // ---------------------------------------------------------------------------
93
+
94
+ export default function Preview() {
95
+ return (
96
+ <GuardianRejectedCredential
97
+ branding={DEFAULT_BRANDING}
98
+ issuer={{ name: 'Springfield School District' }}
99
+ credential={{ name: 'Perfect Attendance Award' }}
100
+ recipient={{ email: 'student@example.com' }}
101
+ />
102
+ );
103
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * InboxClaim — email notifying a recipient they have a credential to claim.
3
+ *
4
+ * Used by: brain-service inbox.helpers.ts (templateId: 'universal-inbox-claim')
5
+ */
6
+
7
+ import { Text, Section } from '@react-email/components';
8
+ import * as React from 'react';
9
+
10
+ import type { TenantBranding } from '../branding';
11
+ import { DEFAULT_BRANDING } from '../branding';
12
+ import { Layout } from '../components/Layout';
13
+ import { EmailButton } from '../components/EmailButton';
14
+ import { IssuerLogo } from '../components/IssuerLogo';
15
+ import { LinkFallback } from '../components/LinkFallback';
16
+
17
+ export interface InboxClaimProps {
18
+ branding: TenantBranding;
19
+ claimUrl: string;
20
+ recipient?: {
21
+ name?: string;
22
+ email?: string;
23
+ phone?: string;
24
+ };
25
+ issuer?: {
26
+ name?: string;
27
+ logoUrl?: string;
28
+ };
29
+ credential?: {
30
+ name?: string;
31
+ type?: string;
32
+ };
33
+ }
34
+
35
+ export const InboxClaim: React.FC<InboxClaimProps> = ({
36
+ branding,
37
+ claimUrl,
38
+ recipient,
39
+ issuer,
40
+ credential,
41
+ }) => {
42
+ const credentialName = credential?.name;
43
+ const credentialType = credential?.type ?? 'record';
44
+ const greeting = recipient?.name ? `Hello ${recipient.name},` : 'Hello,';
45
+ const issuerName = issuer?.name;
46
+ const issuerLogo = issuer?.logoUrl;
47
+ const showHeaderLogo = !issuerLogo;
48
+
49
+ return (
50
+ <Layout branding={branding} preview={`Your digital ${credentialType}${issuerName ? ` from ${issuerName}` : ''} is ready`} showHeaderLogo={showHeaderLogo}>
51
+ <IssuerLogo logoUrl={issuerLogo} alt={issuerName ? `${issuerName} logo` : undefined} />
52
+
53
+ <Text style={heading}>Your digital {credentialType} is ready</Text>
54
+
55
+ <Text style={paragraph}>
56
+ {greeting}
57
+ </Text>
58
+
59
+ <Text style={paragraph}>
60
+ {issuerName
61
+ ? <><strong>{issuerName}</strong> has sent you</>
62
+ : <>You&apos;ve received</>}{' '}
63
+ a secure, digital version of your{' '}
64
+ <strong>{credentialName ? `\u201C${credentialName}\u201D` : 'achievement'}</strong>.
65
+ </Text>
66
+
67
+ <Text style={paragraph}>
68
+ {branding.brandName} is your private, digital passport for learning and work.
69
+ It lets you securely collect and share your verified skills and achievements
70
+ online. By claiming this item, you are saving a verifiable, official {credentialType} to
71
+ your personal passport&mdash;one that only you control.
72
+ </Text>
73
+
74
+ <Section style={buttonWrapper}>
75
+ <EmailButton href={claimUrl} branding={branding}>
76
+ Claim Your Record &rarr;
77
+ </EmailButton>
78
+ </Section>
79
+
80
+ <Text style={signOff}>
81
+ Sincerely,<br />The {branding.brandName} Team
82
+ </Text>
83
+
84
+ <LinkFallback href={claimUrl} />
85
+ </Layout>
86
+ );
87
+ };
88
+
89
+ export const getInboxClaimSubject = (
90
+ _branding: TenantBranding,
91
+ props: InboxClaimProps,
92
+ ): string => {
93
+ const issuerPart = props.issuer?.name ? `from ${props.issuer.name} ` : '';
94
+
95
+ return `Your digital record ${issuerPart}is ready`;
96
+ };
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Styles
100
+ // ---------------------------------------------------------------------------
101
+
102
+ const heading: React.CSSProperties = {
103
+ fontSize: 24,
104
+ fontWeight: 600,
105
+ color: '#111827',
106
+ margin: '0 0 24px',
107
+ };
108
+
109
+ const paragraph: React.CSSProperties = {
110
+ fontSize: 16,
111
+ color: '#374151',
112
+ lineHeight: '24px',
113
+ margin: '0 0 24px',
114
+ };
115
+
116
+ const buttonWrapper: React.CSSProperties = {
117
+ margin: '0 0 24px',
118
+ };
119
+
120
+ const signOff: React.CSSProperties = {
121
+ fontSize: 14,
122
+ color: '#374151',
123
+ lineHeight: '20px',
124
+ margin: '24px 0 0',
125
+ };
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Preview
129
+ // ---------------------------------------------------------------------------
130
+
131
+ export default function Preview() {
132
+ return (
133
+ <InboxClaim
134
+ branding={DEFAULT_BRANDING}
135
+ claimUrl="https://learncard.app/claim/abc123"
136
+ recipient={{ name: 'Jane Doe' }}
137
+ issuer={{ name: 'Acme University', logoUrl: 'https://cdn.filestackcontent.com/J6suaVcQ467W9o1k48Kj' }}
138
+ credential={{ name: 'Bachelor of Science', type: 'Achievement' }}
139
+ />
140
+ );
141
+ }
@@ -0,0 +1,35 @@
1
+ export { VerificationCode, getVerificationCodeSubject } from './verification-code';
2
+ export type { VerificationCodeProps, VerificationCodeVariant } from './verification-code';
3
+
4
+ export { InboxClaim, getInboxClaimSubject } from './inbox-claim';
5
+ export type { InboxClaimProps } from './inbox-claim';
6
+
7
+ export { GuardianApproval, getGuardianApprovalSubject } from './guardian-approval';
8
+ export type { GuardianApprovalProps } from './guardian-approval';
9
+
10
+ export { AccountApproved, getAccountApprovedSubject } from './account-approved';
11
+ export type { AccountApprovedProps } from './account-approved';
12
+
13
+ export { RecoveryKey, getRecoveryKeySubject } from './recovery-key';
14
+ export type { RecoveryKeyProps } from './recovery-key';
15
+
16
+ export { EndorsementRequest, getEndorsementRequestSubject } from './endorsement-request';
17
+ export type { EndorsementRequestProps } from './endorsement-request';
18
+
19
+ export { CredentialAwaitingGuardian, getCredentialAwaitingGuardianSubject } from './credential-awaiting-guardian';
20
+ export type { CredentialAwaitingGuardianProps } from './credential-awaiting-guardian';
21
+
22
+ export { GuardianApprovedClaim, getGuardianApprovedClaimSubject } from './guardian-approved-claim';
23
+ export type { GuardianApprovedClaimProps } from './guardian-approved-claim';
24
+
25
+ export { GuardianCredentialApproval, getGuardianCredentialApprovalSubject } from './guardian-credential-approval';
26
+ export type { GuardianCredentialApprovalProps } from './guardian-credential-approval';
27
+
28
+ export { GuardianEmailOtp, getGuardianEmailOtpSubject } from './guardian-email-otp';
29
+ export type { GuardianEmailOtpProps } from './guardian-email-otp';
30
+
31
+ export { GuardianRejectedCredential, getGuardianRejectedCredentialSubject } from './guardian-rejected-credential';
32
+ export type { GuardianRejectedCredentialProps } from './guardian-rejected-credential';
33
+
34
+ export { EmailVerification, getEmailVerificationSubject } from './email-verification';
35
+ export type { EmailVerificationProps } from './email-verification';
@@ -0,0 +1,102 @@
1
+ /**
2
+ * RecoveryKey — email containing a user's account recovery key.
3
+ *
4
+ * Used by: lca-api keys.ts (templateAlias: RECOVERY_KEY_TEMPLATE_ALIAS)
5
+ */
6
+
7
+ import { Text } from '@react-email/components';
8
+ import * as React from 'react';
9
+
10
+ import type { TenantBranding } from '../branding';
11
+ import { DEFAULT_BRANDING } from '../branding';
12
+ import { Layout } from '../components/Layout';
13
+ import { CodeBlock } from '../components/CodeBlock';
14
+
15
+ export interface RecoveryKeyProps {
16
+ branding: TenantBranding;
17
+ recoveryKey: string;
18
+ }
19
+
20
+ export const RecoveryKey: React.FC<RecoveryKeyProps> = ({
21
+ branding,
22
+ recoveryKey,
23
+ }) => (
24
+ <Layout branding={branding} preview={`Your ${branding.brandName} recovery key — keep this safe`}>
25
+ <Text style={heading}>Your {branding.brandName} Recovery Key</Text>
26
+
27
+ <Text style={paragraph}>Hello,</Text>
28
+
29
+ <Text style={paragraph}>
30
+ Keep this email safe. You can use the recovery key below to regain access
31
+ to your {branding.brandName} account if you lose your device.
32
+ </Text>
33
+
34
+ <CodeBlock code={recoveryKey} label="Recovery Key — do NOT share this with anyone" variant="key" />
35
+
36
+ <Text style={paragraph}>
37
+ To recover your account, choose <strong>&ldquo;Recover via Email&rdquo;</strong> in
38
+ the app and paste the recovery key above when prompted.
39
+ </Text>
40
+
41
+ <Text style={muted}>
42
+ If you did not request this, you can safely ignore this email.
43
+ </Text>
44
+
45
+ <Text style={paragraph}>
46
+ {branding.brandName} is your private, digital passport for learning and work.
47
+ It lets you securely collect and share your verified skills and achievements online.
48
+ </Text>
49
+
50
+ <Text style={signOff}>
51
+ Sincerely,<br />The {branding.brandName} Team
52
+ </Text>
53
+ </Layout>
54
+ );
55
+
56
+ export const getRecoveryKeySubject = (branding: TenantBranding): string =>
57
+ `Your ${branding.brandName} recovery key`;
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Styles
61
+ // ---------------------------------------------------------------------------
62
+
63
+ const heading: React.CSSProperties = {
64
+ fontSize: 24,
65
+ fontWeight: 600,
66
+ color: '#111827',
67
+ margin: '0 0 24px',
68
+ };
69
+
70
+ const paragraph: React.CSSProperties = {
71
+ fontSize: 16,
72
+ color: '#374151',
73
+ lineHeight: '24px',
74
+ margin: '0 0 24px',
75
+ };
76
+
77
+ const muted: React.CSSProperties = {
78
+ fontSize: 14,
79
+ color: '#6b7280',
80
+ lineHeight: '20px',
81
+ margin: '0 0 24px',
82
+ };
83
+
84
+ const signOff: React.CSSProperties = {
85
+ fontSize: 14,
86
+ color: '#374151',
87
+ lineHeight: '20px',
88
+ margin: '24px 0 0',
89
+ };
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Preview
93
+ // ---------------------------------------------------------------------------
94
+
95
+ export default function Preview() {
96
+ return (
97
+ <RecoveryKey
98
+ branding={DEFAULT_BRANDING}
99
+ recoveryKey="mango-delta-fox-echo-bravo-seven-lima-niner"
100
+ />
101
+ );
102
+ }