@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,137 @@
1
+ /**
2
+ * CredentialAwaitingGuardian — email sent to a student/recipient when their
3
+ * credential is pending guardian approval.
4
+ *
5
+ * Used by: brain-service (templateAlias: 'credential-awaiting-guardian')
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 { IssuerLogo } from '../components/IssuerLogo';
15
+
16
+ export interface CredentialAwaitingGuardianProps {
17
+ branding: TenantBranding;
18
+ issuer?: {
19
+ name?: string;
20
+ logoUrl?: string;
21
+ };
22
+ credential?: {
23
+ name?: string;
24
+ };
25
+ recipient?: {
26
+ email?: string;
27
+ };
28
+ }
29
+
30
+ export const CredentialAwaitingGuardian: React.FC<CredentialAwaitingGuardianProps> = ({
31
+ branding,
32
+ issuer,
33
+ credential,
34
+ }) => {
35
+ const issuerName = issuer?.name ?? 'An issuer';
36
+ const credentialName = credential?.name ?? 'a credential';
37
+
38
+ return (
39
+ <Layout branding={branding} preview={`Your credential "${credentialName}" is pending guardian approval`} showHeaderLogo={false}>
40
+ <IssuerLogo logoUrl={issuer?.logoUrl} alt={issuerName ? `${issuerName} logo` : undefined} />
41
+
42
+ <Text style={heading}>Pending guardian approval</Text>
43
+
44
+ <Text style={paragraph}>Hi there,</Text>
45
+
46
+ <Text style={paragraph}>
47
+ <strong>{issuerName}</strong> has sent you the credential{' '}
48
+ <strong>&ldquo;{credentialName}&rdquo;</strong>.
49
+ </Text>
50
+
51
+ <Text style={paragraph}>
52
+ Before you can claim it, your guardian needs to review and approve it first.
53
+ We&apos;ve already sent them a notification — once they approve, you&apos;ll receive
54
+ a follow-up email and the credential will appear in your {branding.brandName} account.
55
+ </Text>
56
+
57
+ <Section style={statusBadge}>
58
+ <Text style={statusText}>Awaiting Guardian Approval</Text>
59
+ </Section>
60
+
61
+ <Text style={muted}>
62
+ No action is needed from you right now.
63
+ </Text>
64
+
65
+ <Text style={signOff}>
66
+ Sincerely,<br />The {branding.brandName} Team
67
+ </Text>
68
+ </Layout>
69
+ );
70
+ };
71
+
72
+ export const getCredentialAwaitingGuardianSubject = (
73
+ _branding: TenantBranding,
74
+ ): string => 'Your Credential Is Pending Guardian Approval';
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Styles
78
+ // ---------------------------------------------------------------------------
79
+
80
+ const heading: React.CSSProperties = {
81
+ fontSize: 24,
82
+ fontWeight: 600,
83
+ color: '#111827',
84
+ margin: '0 0 24px',
85
+ };
86
+
87
+ const paragraph: React.CSSProperties = {
88
+ fontSize: 16,
89
+ color: '#374151',
90
+ lineHeight: '24px',
91
+ margin: '0 0 24px',
92
+ };
93
+
94
+ const statusBadge: React.CSSProperties = {
95
+ backgroundColor: '#f5f3ff',
96
+ border: '1px solid #ddd6fe',
97
+ borderRadius: 8,
98
+ padding: '16px 24px',
99
+ margin: '20px 0',
100
+ textAlign: 'center' as const,
101
+ };
102
+
103
+ const statusText: React.CSSProperties = {
104
+ fontSize: 14,
105
+ fontWeight: 600,
106
+ color: '#6366f1',
107
+ margin: 0,
108
+ };
109
+
110
+ const muted: React.CSSProperties = {
111
+ fontSize: 14,
112
+ color: '#6b7280',
113
+ lineHeight: '20px',
114
+ margin: '0 0 24px',
115
+ };
116
+
117
+ const signOff: React.CSSProperties = {
118
+ fontSize: 14,
119
+ color: '#374151',
120
+ lineHeight: '20px',
121
+ margin: '24px 0 0',
122
+ };
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Preview
126
+ // ---------------------------------------------------------------------------
127
+
128
+ export default function Preview() {
129
+ return (
130
+ <CredentialAwaitingGuardian
131
+ branding={DEFAULT_BRANDING}
132
+ issuer={{ name: 'Springfield School District' }}
133
+ credential={{ name: 'Perfect Attendance Award' }}
134
+ recipient={{ email: 'student@example.com' }}
135
+ />
136
+ );
137
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * EmailVerification — link-based email verification (click a button to verify).
3
+ *
4
+ * Used by: brain-service contact-methods.ts (templateId: 'contact-method-verification')
5
+ *
6
+ * This is distinct from VerificationCode (which displays a 6-digit code).
7
+ * Here the user receives a UUID token embedded in a CTA link.
8
+ */
9
+
10
+ import { Text, Section } from '@react-email/components';
11
+ import * as React from 'react';
12
+
13
+ import type { TenantBranding } from '../branding';
14
+ import { DEFAULT_BRANDING } from '../branding';
15
+ import { Layout } from '../components/Layout';
16
+ import { EmailButton } from '../components/EmailButton';
17
+ import { LinkFallback } from '../components/LinkFallback';
18
+
19
+ export interface EmailVerificationProps {
20
+ branding: TenantBranding;
21
+ verificationToken: string;
22
+ recipient?: {
23
+ name?: string;
24
+ };
25
+ }
26
+
27
+ export const EmailVerification: React.FC<EmailVerificationProps> = ({
28
+ branding,
29
+ verificationToken,
30
+ recipient,
31
+ }) => {
32
+ const greeting = recipient?.name ? `Hello ${recipient.name},` : 'Hello,';
33
+ const verifyUrl = `${branding.appUrl}/verify-email?token=${verificationToken}`;
34
+
35
+ return (
36
+ <Layout branding={branding} preview="Verify your email address">
37
+ <Text style={heading}>Verify Your Email</Text>
38
+
39
+ <Text style={paragraph}>{greeting}</Text>
40
+
41
+ <Text style={paragraph}>
42
+ Please click the button below to verify your email address.
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
+ <Section style={buttonWrapper}>
51
+ <EmailButton href={verifyUrl} branding={branding}>
52
+ Verify Your Email &rarr;
53
+ </EmailButton>
54
+ </Section>
55
+
56
+ <Text style={signOff}>
57
+ Sincerely,<br />The {branding.brandName} Team
58
+ </Text>
59
+
60
+ <LinkFallback href={verifyUrl} />
61
+ </Layout>
62
+ );
63
+ };
64
+
65
+ export const getEmailVerificationSubject = (
66
+ _branding: TenantBranding,
67
+ ): string => 'Verify Your Email';
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Styles
71
+ // ---------------------------------------------------------------------------
72
+
73
+ const heading: React.CSSProperties = {
74
+ fontSize: 24,
75
+ fontWeight: 600,
76
+ color: '#111827',
77
+ margin: '0 0 24px',
78
+ };
79
+
80
+ const paragraph: React.CSSProperties = {
81
+ fontSize: 16,
82
+ color: '#374151',
83
+ lineHeight: '24px',
84
+ margin: '0 0 24px',
85
+ };
86
+
87
+ const buttonWrapper: React.CSSProperties = {
88
+ margin: '0 0 24px',
89
+ };
90
+
91
+ const signOff: React.CSSProperties = {
92
+ fontSize: 14,
93
+ color: '#374151',
94
+ lineHeight: '20px',
95
+ margin: '24px 0 0',
96
+ };
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Preview
100
+ // ---------------------------------------------------------------------------
101
+
102
+ export default function Preview() {
103
+ return (
104
+ <EmailVerification
105
+ branding={DEFAULT_BRANDING}
106
+ verificationToken="f47ac10b-58cc-4372-a567-0e02b2c3d479"
107
+ recipient={{ name: 'Jane Doe' }}
108
+ />
109
+ );
110
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * EndorsementRequest — email asking someone to endorse a credential.
3
+ *
4
+ * Used by: lca-api credentials.ts (templateAlias: ENDORSEMENT_REQUEST_TEMPLATE_ALIAS)
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 EndorsementRequestProps {
18
+ branding: TenantBranding;
19
+ shareLink: string;
20
+ recipient?: {
21
+ name?: string;
22
+ email?: string;
23
+ };
24
+ issuer?: {
25
+ name?: string;
26
+ logoUrl?: string;
27
+ };
28
+ credential?: {
29
+ name?: string;
30
+ type?: string;
31
+ };
32
+ message?: string;
33
+ }
34
+
35
+ export const EndorsementRequest: React.FC<EndorsementRequestProps> = ({
36
+ branding,
37
+ shareLink,
38
+ recipient,
39
+ issuer,
40
+ credential,
41
+ message,
42
+ }) => {
43
+ const greeting = recipient?.name ? `Hello ${recipient.name},` : 'Hello,';
44
+ const issuerName = issuer?.name ?? 'Someone';
45
+ const credentialName = credential?.name;
46
+
47
+ return (
48
+ <Layout branding={branding} preview={`${issuerName} is requesting your endorsement`} showHeaderLogo={false}>
49
+ <IssuerLogo logoUrl={issuer?.logoUrl} alt={issuerName ? `${issuerName} logo` : undefined} />
50
+
51
+ <Text style={paragraph}>{greeting}</Text>
52
+
53
+ <Text style={paragraph}>
54
+ <strong>{issuerName}</strong> has requested an endorsement from you
55
+ {credentialName ? <> for <strong>{credentialName}</strong></> : ''}.
56
+ </Text>
57
+
58
+ {message && (
59
+ <Section style={messageBox}>
60
+ <Text style={messageText}>{message}</Text>
61
+
62
+ {issuer?.name && (
63
+ <Text style={messageSender}>— {issuer.name}</Text>
64
+ )}
65
+ </Section>
66
+ )}
67
+
68
+ <Text style={paragraph}>
69
+ {branding.brandName} is your private, digital passport for learning and work.
70
+ It lets you securely collect and share your verified skills and achievements online.
71
+ </Text>
72
+
73
+ <Section style={buttonWrapper}>
74
+ <EmailButton href={shareLink} branding={branding}>
75
+ Endorse &rarr;
76
+ </EmailButton>
77
+ </Section>
78
+
79
+ <Text style={signOff}>
80
+ Sincerely,<br />The {branding.brandName} Team
81
+ </Text>
82
+
83
+ <LinkFallback href={shareLink} />
84
+ </Layout>
85
+ );
86
+ };
87
+
88
+ export const getEndorsementRequestSubject = (
89
+ _branding: TenantBranding,
90
+ props: EndorsementRequestProps,
91
+ ): string => {
92
+ const issuerName = props.issuer?.name ?? 'Someone';
93
+
94
+ return `${issuerName} is requesting your endorsement`;
95
+ };
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Styles
99
+ // ---------------------------------------------------------------------------
100
+
101
+ const paragraph: React.CSSProperties = {
102
+ fontSize: 16,
103
+ color: '#374151',
104
+ lineHeight: '24px',
105
+ margin: '0 0 24px',
106
+ };
107
+
108
+ const messageBox: React.CSSProperties = {
109
+ backgroundColor: '#f6f6f9',
110
+ borderRadius: 8,
111
+ padding: '16px 20px',
112
+ margin: '16px 0',
113
+ borderLeft: '3px solid #C5C8D3',
114
+ };
115
+
116
+ const messageText: React.CSSProperties = {
117
+ fontSize: 15,
118
+ color: '#111827',
119
+ lineHeight: '22px',
120
+ margin: 0,
121
+ textAlign: 'left' as const,
122
+ };
123
+
124
+ const messageSender: React.CSSProperties = {
125
+ fontSize: 14,
126
+ fontWeight: 600,
127
+ color: '#111827',
128
+ margin: '12px 0 0',
129
+ };
130
+
131
+ const buttonWrapper: React.CSSProperties = {
132
+ margin: '0 0 24px',
133
+ };
134
+
135
+ const signOff: React.CSSProperties = {
136
+ fontSize: 14,
137
+ color: '#374151',
138
+ lineHeight: '20px',
139
+ margin: '24px 0 0',
140
+ };
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // Preview
144
+ // ---------------------------------------------------------------------------
145
+
146
+ export default function Preview() {
147
+ return (
148
+ <EndorsementRequest
149
+ branding={DEFAULT_BRANDING}
150
+ shareLink="https://learncard.app/share/xyz789"
151
+ recipient={{ name: 'Dr. Emily Chen' }}
152
+ issuer={{ name: 'John Doe' }}
153
+ credential={{ name: 'Professional Teaching Certificate' }}
154
+ message="I would greatly appreciate your endorsement of my teaching credential. We worked together at Springfield Academy for 3 years."
155
+ />
156
+ );
157
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * GuardianApproval — email sent to a guardian to approve a minor's account.
3
+ *
4
+ * Used by: brain-service inbox.ts (templateId: 'guardian-approval')
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 { LinkFallback } from '../components/LinkFallback';
15
+
16
+ export interface GuardianApprovalProps {
17
+ branding: TenantBranding;
18
+ approvalUrl: string;
19
+ approvalToken: string;
20
+ requester?: {
21
+ displayName?: string;
22
+ profileId?: string;
23
+ };
24
+ guardian?: {
25
+ email?: string;
26
+ };
27
+ }
28
+
29
+ export const GuardianApproval: React.FC<GuardianApprovalProps> = ({
30
+ branding,
31
+ approvalUrl,
32
+ requester,
33
+ }) => {
34
+ const requesterName = requester?.displayName ?? 'Someone';
35
+
36
+ return (
37
+ <Layout branding={branding} preview={`${requesterName} needs your approval to join ${branding.brandName}`}>
38
+ <Text style={heading}>Account Approval Request</Text>
39
+
40
+ <Text style={paragraph}>Hello,</Text>
41
+
42
+ <Text style={paragraph}>
43
+ <strong>{requesterName}</strong> has requested approval to use their {branding.brandName} account.
44
+ </Text>
45
+
46
+ <Text style={paragraph}>
47
+ {branding.brandName} is a private, digital passport for learning and work.
48
+ It lets users securely collect and share their verified skills and achievements online.
49
+ </Text>
50
+
51
+ <Text style={paragraph}>
52
+ Please click the button below to approve this account request.
53
+ </Text>
54
+
55
+ <Section style={buttonWrapper}>
56
+ <EmailButton href={approvalUrl} branding={branding}>
57
+ Approve Request &rarr;
58
+ </EmailButton>
59
+ </Section>
60
+
61
+ <Text style={signOff}>
62
+ Thank you,<br />The {branding.brandName} Team
63
+ </Text>
64
+
65
+ <LinkFallback href={approvalUrl} />
66
+ </Layout>
67
+ );
68
+ };
69
+
70
+ export const getGuardianApprovalSubject = (
71
+ branding: TenantBranding,
72
+ props: GuardianApprovalProps,
73
+ ): string => {
74
+ const name = props.requester?.displayName ?? 'Someone';
75
+
76
+ return `${name} needs your approval to join ${branding.brandName}`;
77
+ };
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Styles
81
+ // ---------------------------------------------------------------------------
82
+
83
+ const heading: React.CSSProperties = {
84
+ fontSize: 24,
85
+ fontWeight: 600,
86
+ color: '#111827',
87
+ margin: '0 0 24px',
88
+ };
89
+
90
+ const paragraph: React.CSSProperties = {
91
+ fontSize: 16,
92
+ color: '#374151',
93
+ lineHeight: '24px',
94
+ margin: '0 0 24px',
95
+ };
96
+
97
+ const buttonWrapper: React.CSSProperties = {
98
+ margin: '0 0 24px',
99
+ };
100
+
101
+ const signOff: React.CSSProperties = {
102
+ fontSize: 14,
103
+ color: '#374151',
104
+ lineHeight: '20px',
105
+ margin: '24px 0 0',
106
+ };
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Preview
110
+ // ---------------------------------------------------------------------------
111
+
112
+ export default function Preview() {
113
+ return (
114
+ <GuardianApproval
115
+ branding={DEFAULT_BRANDING}
116
+ approvalUrl="https://learncard.app/approve?token=abc123"
117
+ approvalToken="abc123"
118
+ requester={{ displayName: 'Alex Smith' }}
119
+ guardian={{ email: 'parent@example.com' }}
120
+ />
121
+ );
122
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * GuardianApprovedClaim — email sent to a student when their guardian has
3
+ * approved a credential.
4
+ *
5
+ * Used by: brain-service (templateAlias: 'guardian-approved-claim')
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
+
17
+ export interface GuardianApprovedClaimProps {
18
+ branding: TenantBranding;
19
+ issuer?: {
20
+ name?: string;
21
+ logoUrl?: string;
22
+ };
23
+ credential?: {
24
+ name?: string;
25
+ };
26
+ }
27
+
28
+ export const GuardianApprovedClaim: React.FC<GuardianApprovedClaimProps> = ({
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 credential has been approved by your guardian!" showHeaderLogo={false}>
38
+ <IssuerLogo logoUrl={issuer?.logoUrl} alt={issuerName ? `${issuerName} logo` : undefined} />
39
+
40
+ <Text style={heading}>Your credential has been approved!</Text>
41
+
42
+ <Text style={paragraph}>Great news!</Text>
43
+
44
+ <Text style={paragraph}>
45
+ Your guardian has approved the credential{' '}
46
+ {credentialName ? <><strong>&ldquo;{credentialName}&rdquo;</strong> </> : ''}
47
+ issued by <strong>{issuerName}</strong>. It is now available in
48
+ your {branding.brandName} account.
49
+ </Text>
50
+
51
+ <Text style={paragraph}>
52
+ Open the app to see it — your credential should appear automatically.
53
+ If you don&apos;t see it right away, try refreshing.
54
+ </Text>
55
+
56
+ <Section style={buttonWrapper}>
57
+ <EmailButton href={branding.appUrl} branding={branding}>
58
+ Open {branding.brandName} &rarr;
59
+ </EmailButton>
60
+ </Section>
61
+
62
+ <Text style={signOff}>
63
+ Sincerely,<br />The {branding.brandName} Team
64
+ </Text>
65
+ </Layout>
66
+ );
67
+ };
68
+
69
+ export const getGuardianApprovedClaimSubject = (
70
+ _branding: TenantBranding,
71
+ ): string => 'Your Credential Has Been Approved!';
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Styles
75
+ // ---------------------------------------------------------------------------
76
+
77
+ const heading: React.CSSProperties = {
78
+ fontSize: 24,
79
+ fontWeight: 600,
80
+ color: '#111827',
81
+ margin: '0 0 24px',
82
+ };
83
+
84
+ const paragraph: React.CSSProperties = {
85
+ fontSize: 16,
86
+ color: '#374151',
87
+ lineHeight: '24px',
88
+ margin: '0 0 24px',
89
+ };
90
+
91
+ const buttonWrapper: React.CSSProperties = {
92
+ margin: '0 0 24px',
93
+ };
94
+
95
+ const signOff: React.CSSProperties = {
96
+ fontSize: 14,
97
+ color: '#374151',
98
+ lineHeight: '20px',
99
+ margin: '24px 0 0',
100
+ };
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Preview
104
+ // ---------------------------------------------------------------------------
105
+
106
+ export default function Preview() {
107
+ return (
108
+ <GuardianApprovedClaim
109
+ branding={DEFAULT_BRANDING}
110
+ issuer={{ name: 'Springfield School District' }}
111
+ credential={{ name: 'Perfect Attendance Award' }}
112
+ />
113
+ );
114
+ }