@learncard/email-templates 1.0.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.
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/branding.d.ts +36 -0
- package/dist/branding.d.ts.map +1 -0
- package/dist/branding.js +28 -0
- package/dist/branding.js.map +1 -0
- package/dist/components/CodeBlock.d.ts +16 -0
- package/dist/components/CodeBlock.d.ts.map +1 -0
- package/dist/components/CodeBlock.js +42 -0
- package/dist/components/CodeBlock.js.map +1 -0
- package/dist/components/EmailButton.d.ts +15 -0
- package/dist/components/EmailButton.d.ts.map +1 -0
- package/dist/components/EmailButton.js +20 -0
- package/dist/components/EmailButton.js.map +1 -0
- package/dist/components/IssuerLogo.d.ts +14 -0
- package/dist/components/IssuerLogo.d.ts.map +1 -0
- package/dist/components/IssuerLogo.js +23 -0
- package/dist/components/IssuerLogo.js.map +1 -0
- package/dist/components/Layout.d.ts +19 -0
- package/dist/components/Layout.d.ts.map +1 -0
- package/dist/components/Layout.js +95 -0
- package/dist/components/Layout.js.map +1 -0
- package/dist/components/LinkFallback.d.ts +12 -0
- package/dist/components/LinkFallback.d.ts.map +1 -0
- package/dist/components/LinkFallback.js +29 -0
- package/dist/components/LinkFallback.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/previews/_fixtures.d.ts +12 -0
- package/dist/previews/_fixtures.d.ts.map +1 -0
- package/dist/previews/_fixtures.js +34 -0
- package/dist/previews/_fixtures.js.map +1 -0
- package/dist/previews/account-approved-scenarios.d.ts +5 -0
- package/dist/previews/account-approved-scenarios.d.ts.map +1 -0
- package/dist/previews/account-approved-scenarios.js +7 -0
- package/dist/previews/account-approved-scenarios.js.map +1 -0
- package/dist/previews/endorsement-request-scenarios.d.ts +5 -0
- package/dist/previews/endorsement-request-scenarios.d.ts.map +1 -0
- package/dist/previews/endorsement-request-scenarios.js +7 -0
- package/dist/previews/endorsement-request-scenarios.js.map +1 -0
- package/dist/previews/guardian-approval-scenarios.d.ts +5 -0
- package/dist/previews/guardian-approval-scenarios.d.ts.map +1 -0
- package/dist/previews/guardian-approval-scenarios.js +7 -0
- package/dist/previews/guardian-approval-scenarios.js.map +1 -0
- package/dist/previews/guardian-credential-scenarios.d.ts +5 -0
- package/dist/previews/guardian-credential-scenarios.d.ts.map +1 -0
- package/dist/previews/guardian-credential-scenarios.js +11 -0
- package/dist/previews/guardian-credential-scenarios.js.map +1 -0
- package/dist/previews/inbox-claim-scenarios.d.ts +8 -0
- package/dist/previews/inbox-claim-scenarios.d.ts.map +1 -0
- package/dist/previews/inbox-claim-scenarios.js +7 -0
- package/dist/previews/inbox-claim-scenarios.js.map +1 -0
- package/dist/previews/recovery-key-scenarios.d.ts +5 -0
- package/dist/previews/recovery-key-scenarios.d.ts.map +1 -0
- package/dist/previews/recovery-key-scenarios.js +7 -0
- package/dist/previews/recovery-key-scenarios.js.map +1 -0
- package/dist/previews/verification-code-scenarios.d.ts +5 -0
- package/dist/previews/verification-code-scenarios.d.ts.map +1 -0
- package/dist/previews/verification-code-scenarios.js +7 -0
- package/dist/previews/verification-code-scenarios.js.map +1 -0
- package/dist/render.d.ts +185 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +153 -0
- package/dist/render.js.map +1 -0
- package/dist/sms.d.ts +34 -0
- package/dist/sms.d.ts.map +1 -0
- package/dist/sms.js +34 -0
- package/dist/sms.js.map +1 -0
- package/dist/templates/account-approved.d.ts +17 -0
- package/dist/templates/account-approved.d.ts.map +1 -0
- package/dist/templates/account-approved.js +46 -0
- package/dist/templates/account-approved.js.map +1 -0
- package/dist/templates/credential-awaiting-guardian.d.ts +25 -0
- package/dist/templates/credential-awaiting-guardian.d.ts.map +1 -0
- package/dist/templates/credential-awaiting-guardian.js +65 -0
- package/dist/templates/credential-awaiting-guardian.js.map +1 -0
- package/dist/templates/email-verification.d.ts +21 -0
- package/dist/templates/email-verification.d.ts.map +1 -0
- package/dist/templates/email-verification.js +51 -0
- package/dist/templates/email-verification.js.map +1 -0
- package/dist/templates/endorsement-request.d.ts +28 -0
- package/dist/templates/endorsement-request.d.ts.map +1 -0
- package/dist/templates/endorsement-request.js +67 -0
- package/dist/templates/endorsement-request.js.map +1 -0
- package/dist/templates/guardian-approval.d.ts +23 -0
- package/dist/templates/guardian-approval.d.ts.map +1 -0
- package/dist/templates/guardian-approval.js +50 -0
- package/dist/templates/guardian-approval.js.map +1 -0
- package/dist/templates/guardian-approved-claim.d.ts +22 -0
- package/dist/templates/guardian-approved-claim.d.ts.map +1 -0
- package/dist/templates/guardian-approved-claim.js +49 -0
- package/dist/templates/guardian-approved-claim.js.map +1 -0
- package/dist/templates/guardian-credential-approval.d.ts +27 -0
- package/dist/templates/guardian-credential-approval.d.ts.map +1 -0
- package/dist/templates/guardian-credential-approval.js +51 -0
- package/dist/templates/guardian-credential-approval.js.map +1 -0
- package/dist/templates/guardian-email-otp.d.ts +16 -0
- package/dist/templates/guardian-email-otp.d.ts.map +1 -0
- package/dist/templates/guardian-email-otp.js +49 -0
- package/dist/templates/guardian-email-otp.js.map +1 -0
- package/dist/templates/guardian-rejected-credential.d.ts +24 -0
- package/dist/templates/guardian-rejected-credential.d.ts.map +1 -0
- package/dist/templates/guardian-rejected-credential.js +44 -0
- package/dist/templates/guardian-rejected-credential.js.map +1 -0
- package/dist/templates/inbox-claim.d.ts +28 -0
- package/dist/templates/inbox-claim.d.ts.map +1 -0
- package/dist/templates/inbox-claim.js +58 -0
- package/dist/templates/inbox-claim.js.map +1 -0
- package/dist/templates/index.d.ts +25 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +13 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/recovery-key.d.ts +15 -0
- package/dist/templates/recovery-key.d.ts.map +1 -0
- package/dist/templates/recovery-key.js +46 -0
- package/dist/templates/recovery-key.js.map +1 -0
- package/dist/templates/verification-code.d.ts +24 -0
- package/dist/templates/verification-code.d.ts.map +1 -0
- package/dist/templates/verification-code.js +87 -0
- package/dist/templates/verification-code.js.map +1 -0
- package/dist/tenant-registry.d.ts +48 -0
- package/dist/tenant-registry.d.ts.map +1 -0
- package/dist/tenant-registry.js +146 -0
- package/dist/tenant-registry.js.map +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Learning Economy Foundation <sdk@learningeconomy.io>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# @learncard/email-templates
|
|
2
|
+
|
|
3
|
+
Git-managed, tenant-branded email and SMS templates for LearnCard services.
|
|
4
|
+
|
|
5
|
+
Built with [react-email](https://react.email/) so templates are composable React components with full type safety, local preview, and PR-based review.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
branding.ts — TenantBranding type, defaults, resolveBranding()
|
|
12
|
+
render.ts — renderEmail(templateId, branding, data) → { html, text, subject }
|
|
13
|
+
sms.ts — renderSms(templateId, branding, data) → string
|
|
14
|
+
tenant-registry.ts — server-side tenant resolution from request headers
|
|
15
|
+
index.ts — barrel export
|
|
16
|
+
|
|
17
|
+
components/
|
|
18
|
+
Layout.tsx — shared email wrapper (header, footer, branding)
|
|
19
|
+
EmailButton.tsx — branded CTA button
|
|
20
|
+
CodeBlock.tsx — verification code / recovery key display
|
|
21
|
+
IssuerLogo.tsx — issuer logo with fallback
|
|
22
|
+
LinkFallback.tsx — "copy and paste this link" fallback text
|
|
23
|
+
|
|
24
|
+
templates/
|
|
25
|
+
verification-code.tsx — 3 variants: login, recovery-email, embed-verification
|
|
26
|
+
email-verification.tsx — link-based email verification (CTA button)
|
|
27
|
+
inbox-claim.tsx — credential claim notification
|
|
28
|
+
endorsement-request.tsx — endorsement request
|
|
29
|
+
guardian-approval.tsx — minor account approval request
|
|
30
|
+
account-approved.tsx — account approved confirmation
|
|
31
|
+
recovery-key.tsx — recovery key delivery
|
|
32
|
+
guardian-credential-approval.tsx — guardian approve/decline a credential
|
|
33
|
+
credential-awaiting-guardian.tsx — student: credential pending guardian
|
|
34
|
+
guardian-approved-claim.tsx — student: guardian approved credential
|
|
35
|
+
guardian-rejected-credential.tsx — student: guardian rejected credential
|
|
36
|
+
guardian-email-otp.tsx — guardian OTP verification
|
|
37
|
+
|
|
38
|
+
previews/
|
|
39
|
+
_fixtures.tsx — shared branding presets and scenario divider
|
|
40
|
+
inbox-claim-scenarios.tsx
|
|
41
|
+
verification-code-scenarios.tsx
|
|
42
|
+
endorsement-request-scenarios.tsx
|
|
43
|
+
guardian-approval-scenarios.tsx
|
|
44
|
+
guardian-credential-scenarios.tsx
|
|
45
|
+
account-approved-scenarios.tsx
|
|
46
|
+
recovery-key-scenarios.tsx
|
|
47
|
+
|
|
48
|
+
__tests__/
|
|
49
|
+
render.test.ts — smoke, content, branding, subject, and edge-case tests
|
|
50
|
+
sms.test.ts — SMS rendering tests
|
|
51
|
+
tenant-registry.test.ts — tenant resolution unit tests
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { renderEmail, renderSms, resolveBranding } from '@learncard/email-templates';
|
|
58
|
+
|
|
59
|
+
// Render a branded email
|
|
60
|
+
const { html, text, subject } = await renderEmail('inbox-claim', tenantBranding, {
|
|
61
|
+
claimUrl: 'https://vetpass.app/claim/abc123',
|
|
62
|
+
credential: { name: 'Service Badge' },
|
|
63
|
+
issuer: { name: 'VA Medical Center' },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Render a branded SMS
|
|
67
|
+
const smsBody = renderSms('inbox-claim', tenantBranding, {
|
|
68
|
+
claimUrl: 'https://vetpass.app/claim/abc123',
|
|
69
|
+
credential: { name: 'Service Badge', type: 'Achievement' },
|
|
70
|
+
issuer: { name: 'VA Medical Center' },
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Template IDs
|
|
75
|
+
|
|
76
|
+
| Template ID | Service | Purpose |
|
|
77
|
+
| --------------------------------- | ------------- | ------------------------------------ |
|
|
78
|
+
| `login-verification-code` | lca-api | Login OTP code |
|
|
79
|
+
| `recovery-email-code` | lca-api | Recovery email verification |
|
|
80
|
+
| `embed-email-verification` | brain-service | Embed SDK email verification |
|
|
81
|
+
| `contact-method-verification` | brain-service | Link-based email verification |
|
|
82
|
+
| `inbox-claim` | brain-service | Credential claim notification |
|
|
83
|
+
| `endorsement-request` | lca-api | Endorsement request |
|
|
84
|
+
| `guardian-approval` | brain-service | Guardian approval for minor accounts |
|
|
85
|
+
| `account-approved` | brain-service | Account approved notification |
|
|
86
|
+
| `recovery-key` | lca-api | Recovery key delivery |
|
|
87
|
+
| `credential-awaiting-guardian` | brain-service | Student: credential pending guardian |
|
|
88
|
+
| `guardian-credential-approval` | brain-service | Guardian: approve/decline credential |
|
|
89
|
+
| `guardian-approved-claim` | brain-service | Student: guardian approved credential|
|
|
90
|
+
| `guardian-rejected-credential` | brain-service | Student: guardian rejected credential|
|
|
91
|
+
| `guardian-email-otp` | brain-service | Guardian OTP verification |
|
|
92
|
+
|
|
93
|
+
Legacy Postmark aliases (`universal-inbox-claim`, `contact-method-verification-1`, `account-approved-email`, `recovery-key-backup`, `recovery-email-verification`, `universal-inbox-claim-1`) are also supported and map to the corresponding template above.
|
|
94
|
+
|
|
95
|
+
## Tenant Branding
|
|
96
|
+
|
|
97
|
+
Every field in `TenantBranding` is optional when calling `renderEmail` / `renderSms` — missing fields fall back to LearnCard defaults.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface TenantBranding {
|
|
101
|
+
brandName: string; // e.g. "VetPass"
|
|
102
|
+
logoUrl: string; // hosted logo image URL
|
|
103
|
+
logoAlt: string; // logo alt text
|
|
104
|
+
primaryColor: string; // hex, e.g. "#1B5E20"
|
|
105
|
+
primaryTextColor: string; // hex, e.g. "#ffffff"
|
|
106
|
+
supportEmail: string; // footer support email
|
|
107
|
+
websiteUrl: string; // footer website link
|
|
108
|
+
appUrl: string; // base app URL for CTA links
|
|
109
|
+
fromDomain: string; // email from domain
|
|
110
|
+
copyrightHolder: string; // footer copyright name
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Branding values are sourced from the tenant config's `email` section (see `tenantEmailConfigSchema` in `packages/learn-card-base/src/config/tenantConfigSchema.ts`).
|
|
115
|
+
|
|
116
|
+
## Tenant Resolution
|
|
117
|
+
|
|
118
|
+
`tenant-registry.ts` provides server-side tenant resolution from HTTP request headers:
|
|
119
|
+
|
|
120
|
+
1. **`X-Tenant-Id` header** — explicit tenant (native apps, informed clients)
|
|
121
|
+
2. **`Origin` / `Referer` header** — hostname mapping (web apps)
|
|
122
|
+
3. **`DEFAULT_TENANT_ID` env var** — per-tenant deploys, cron jobs
|
|
123
|
+
4. **Fallback** — `'learncard'`
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { resolveTenantFromRequest } from '@learncard/email-templates';
|
|
127
|
+
|
|
128
|
+
const tenant = resolveTenantFromRequest(req.headers);
|
|
129
|
+
// { id: 'vetpass', emailBranding: { brandName: 'VetPass', ... }, resolvedVia: 'origin' }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Integration
|
|
133
|
+
|
|
134
|
+
Both `brain-service` and `lca-api` PostmarkAdapters:
|
|
135
|
+
|
|
136
|
+
1. Check if the template ID maps to a known local template
|
|
137
|
+
2. If yes → render locally with tenant branding, send as raw HTML via Postmark
|
|
138
|
+
3. If rendering fails → fall back to Postmark's template engine (legacy path)
|
|
139
|
+
|
|
140
|
+
This makes migration gradual and backward-compatible. Call sites that don't pass `branding` get LearnCard defaults.
|
|
141
|
+
|
|
142
|
+
## Adding a New Template
|
|
143
|
+
|
|
144
|
+
1. Create `src/templates/my-template.tsx` — export the component + a `getMyTemplateSubject()` function
|
|
145
|
+
2. Define the data interface in `src/render.ts` and add it to `TemplateDataMap`
|
|
146
|
+
3. Add a case to `buildElement()` in `src/render.ts`
|
|
147
|
+
4. Export from `src/templates/index.ts` and `src/index.ts`
|
|
148
|
+
5. Map the old template ID to the new one in the service's PostmarkAdapter (`LOCAL_TEMPLATE_MAP`)
|
|
149
|
+
6. Add test fixtures and assertions to `src/__tests__/render.test.ts`
|
|
150
|
+
7. Run `pnpm --filter @learncard/email-templates test` and `typecheck` to verify
|
|
151
|
+
|
|
152
|
+
## Previewing Templates
|
|
153
|
+
|
|
154
|
+
The package includes a built-in dev server powered by [react-email](https://react.email/):
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pnpm --filter @learncard/email-templates dev
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This starts a hot-reloading UI at **http://localhost:3333** where you can:
|
|
161
|
+
|
|
162
|
+
- Browse all templates in a sidebar
|
|
163
|
+
- See a live preview with sample data
|
|
164
|
+
- Toggle between HTML, plain text, and source views
|
|
165
|
+
- Copy the rendered HTML for testing in email clients
|
|
166
|
+
|
|
167
|
+
Each template file has a `default` export with realistic sample data used for previews. Multi-scenario previews in `src/previews/` show templates with different branding and data combinations.
|
|
168
|
+
|
|
169
|
+
## Testing
|
|
170
|
+
|
|
171
|
+
Tests use [Vitest](https://vitest.dev/) and cover:
|
|
172
|
+
|
|
173
|
+
- **Smoke tests** — every template ID renders without throwing (default, custom, and empty branding)
|
|
174
|
+
- **Content assertions** — dynamic values (URLs, codes, names) appear in rendered HTML
|
|
175
|
+
- **Branding tests** — custom branding flows through to output (colors, brand name, URLs)
|
|
176
|
+
- **Subject line tests** — correct subjects, alias parity
|
|
177
|
+
- **Plain text tests** — no HTML tags in plain text output
|
|
178
|
+
- **Edge cases** — minimal data, missing optional fields
|
|
179
|
+
- **SMS tests** — `renderSms` output strings with branding
|
|
180
|
+
- **Tenant resolution** — header priority, origin mapping, env fallback, registration helpers
|
|
181
|
+
|
|
182
|
+
## Scripts
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
pnpm --filter @learncard/email-templates dev # Preview templates in browser
|
|
186
|
+
pnpm --filter @learncard/email-templates typecheck # Type check
|
|
187
|
+
pnpm --filter @learncard/email-templates test # Run tests (vitest run)
|
|
188
|
+
pnpm --filter @learncard/email-templates test:watch # Run tests in watch mode
|
|
189
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant branding for email and SMS templates.
|
|
3
|
+
*
|
|
4
|
+
* Values are typically sourced from the tenant config's `email` section.
|
|
5
|
+
* Every field has a sensible LearnCard default so templates always render,
|
|
6
|
+
* even if the caller provides a partial (or empty) branding object.
|
|
7
|
+
*/
|
|
8
|
+
export interface TenantBranding {
|
|
9
|
+
/** Display name shown in email headers, subject lines, and SMS. */
|
|
10
|
+
brandName: string;
|
|
11
|
+
/** Full URL to a hosted logo image (PNG/SVG, ideally 200×50 or similar). */
|
|
12
|
+
logoUrl: string;
|
|
13
|
+
/** Alt text for the logo image. */
|
|
14
|
+
logoAlt: string;
|
|
15
|
+
/** Primary brand color (hex). Used for CTA buttons and accents. */
|
|
16
|
+
primaryColor: string;
|
|
17
|
+
/** Text color on primary buttons. */
|
|
18
|
+
primaryTextColor: string;
|
|
19
|
+
/** Support / reply-to email address shown in footers. */
|
|
20
|
+
supportEmail: string;
|
|
21
|
+
/** Marketing website URL shown in footers. */
|
|
22
|
+
websiteUrl: string;
|
|
23
|
+
/** Base app URL for constructing links (e.g. "https://learncard.app"). */
|
|
24
|
+
appUrl: string;
|
|
25
|
+
/** Email domain for the From address (e.g. "learncard.com"). */
|
|
26
|
+
fromDomain: string;
|
|
27
|
+
/** Copyright holder name for the footer. */
|
|
28
|
+
copyrightHolder: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_BRANDING: TenantBranding;
|
|
31
|
+
/**
|
|
32
|
+
* Merge partial branding with defaults.
|
|
33
|
+
* Any missing field falls back to the LearnCard default.
|
|
34
|
+
*/
|
|
35
|
+
export declare const resolveBranding: (partial?: Partial<TenantBranding>) => TenantBranding;
|
|
36
|
+
//# sourceMappingURL=branding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../src/branding.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC3B,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAElB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAEhB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAEhB,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IAErB,qCAAqC;IACrC,gBAAgB,EAAE,MAAM,CAAC;IAEzB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IAErB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IAEnB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IAEf,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IAEnB,4CAA4C;IAC5C,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,gBAAgB,EAAE,cAW9B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,aAAc,OAAO,CAAC,cAAc,CAAC,KAAG,cAGlE,CAAC"}
|
package/dist/branding.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant branding for email and SMS templates.
|
|
3
|
+
*
|
|
4
|
+
* Values are typically sourced from the tenant config's `email` section.
|
|
5
|
+
* Every field has a sensible LearnCard default so templates always render,
|
|
6
|
+
* even if the caller provides a partial (or empty) branding object.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_BRANDING = {
|
|
9
|
+
brandName: 'LearnCard',
|
|
10
|
+
logoUrl: 'https://learncard.app/assets/icon/icon.png',
|
|
11
|
+
logoAlt: 'LearnCard',
|
|
12
|
+
primaryColor: '#6366f1',
|
|
13
|
+
primaryTextColor: '#ffffff',
|
|
14
|
+
supportEmail: 'support@learningeconomy.io',
|
|
15
|
+
websiteUrl: 'https://www.learncard.com',
|
|
16
|
+
appUrl: 'https://learncard.app',
|
|
17
|
+
fromDomain: 'learncard.com',
|
|
18
|
+
copyrightHolder: 'Learning Economy Foundation',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Merge partial branding with defaults.
|
|
22
|
+
* Any missing field falls back to the LearnCard default.
|
|
23
|
+
*/
|
|
24
|
+
export const resolveBranding = (partial) => ({
|
|
25
|
+
...DEFAULT_BRANDING,
|
|
26
|
+
...partial,
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=branding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branding.js","sourceRoot":"","sources":["../src/branding.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkCH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC5C,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,4CAA4C;IACrD,OAAO,EAAE,WAAW;IACpB,YAAY,EAAE,SAAS;IACvB,gBAAgB,EAAE,SAAS;IAC3B,YAAY,EAAE,4BAA4B;IAC1C,UAAU,EAAE,2BAA2B;IACvC,MAAM,EAAE,uBAAuB;IAC/B,UAAU,EAAE,eAAe;IAC3B,eAAe,EAAE,6BAA6B;CACjD,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAiC,EAAkB,EAAE,CAAC,CAAC;IACnF,GAAG,gBAAgB;IACnB,GAAG,OAAO;CACb,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeBlock — displays a verification code or recovery key prominently.
|
|
3
|
+
*
|
|
4
|
+
* Used by verification-code and recovery-key templates.
|
|
5
|
+
*/
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
interface CodeBlockProps {
|
|
8
|
+
code: string;
|
|
9
|
+
/** Optional label above the code (e.g. "Your verification code"). */
|
|
10
|
+
label?: string;
|
|
11
|
+
/** 'code' (default) for short 6-digit codes, 'key' for long recovery keys. */
|
|
12
|
+
variant?: 'code' | 'key';
|
|
13
|
+
}
|
|
14
|
+
export declare const CodeBlock: React.FC<CodeBlockProps>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=CodeBlock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodeBlock.d.ts","sourceRoot":"","sources":["../../src/components/CodeBlock.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,UAAU,cAAc;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAM9C,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* CodeBlock — displays a verification code or recovery key prominently.
|
|
4
|
+
*
|
|
5
|
+
* Used by verification-code and recovery-key templates.
|
|
6
|
+
*/
|
|
7
|
+
import { Section, Text } from '@react-email/components';
|
|
8
|
+
export const CodeBlock = ({ code, label, variant = 'code' }) => (_jsxs(Section, { style: wrapper, children: [label && _jsx(Text, { style: labelStyle, children: label }), _jsx(Text, { style: variant === 'key' ? keyStyle : codeStyle, children: code })] }));
|
|
9
|
+
const wrapper = {
|
|
10
|
+
backgroundColor: '#f9fafb',
|
|
11
|
+
border: '1px solid #e5e7eb',
|
|
12
|
+
borderRadius: 8,
|
|
13
|
+
padding: '16px 24px',
|
|
14
|
+
margin: '0 0 24px',
|
|
15
|
+
textAlign: 'center',
|
|
16
|
+
};
|
|
17
|
+
const labelStyle = {
|
|
18
|
+
fontSize: 12,
|
|
19
|
+
fontWeight: 600,
|
|
20
|
+
color: '#6b7280',
|
|
21
|
+
margin: '0 0 8px',
|
|
22
|
+
textTransform: 'uppercase',
|
|
23
|
+
letterSpacing: '0.05em',
|
|
24
|
+
};
|
|
25
|
+
const codeStyle = {
|
|
26
|
+
fontSize: 32,
|
|
27
|
+
fontWeight: 700,
|
|
28
|
+
color: '#111827',
|
|
29
|
+
letterSpacing: 6,
|
|
30
|
+
margin: 0,
|
|
31
|
+
fontFamily: "'Courier New', Courier, monospace",
|
|
32
|
+
};
|
|
33
|
+
const keyStyle = {
|
|
34
|
+
fontSize: 13,
|
|
35
|
+
fontWeight: 400,
|
|
36
|
+
color: '#111827',
|
|
37
|
+
lineHeight: '20px',
|
|
38
|
+
margin: 0,
|
|
39
|
+
fontFamily: "'Courier New', Courier, monospace",
|
|
40
|
+
wordBreak: 'break-all',
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=CodeBlock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodeBlock.js","sourceRoot":"","sources":["../../src/components/CodeBlock.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAWxD,MAAM,CAAC,MAAM,SAAS,GAA6B,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,EAAE,EAAE,CAAC,CACtF,MAAC,OAAO,IAAC,KAAK,EAAE,OAAO,aAClB,KAAK,IAAI,KAAC,IAAI,IAAC,KAAK,EAAE,UAAU,YAAG,KAAK,GAAQ,EAEjD,KAAC,IAAI,IAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAAG,IAAI,GAAQ,IAC9D,CACb,CAAC;AAEF,MAAM,OAAO,GAAwB;IACjC,eAAe,EAAE,SAAS;IAC1B,MAAM,EAAE,mBAAmB;IAC3B,YAAY,EAAE,CAAC;IACf,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,UAAU;IAClB,SAAS,EAAE,QAAiB;CAC/B,CAAC;AAEF,MAAM,UAAU,GAAwB;IACpC,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,GAAG;IACf,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,SAAS;IACjB,aAAa,EAAE,WAAoB;IACnC,aAAa,EAAE,QAAQ;CAC1B,CAAC;AAEF,MAAM,SAAS,GAAwB;IACnC,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,GAAG;IACf,KAAK,EAAE,SAAS;IAChB,aAAa,EAAE,CAAC;IAChB,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,mCAAmC;CAClD,CAAC;AAEF,MAAM,QAAQ,GAAwB;IAClC,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,GAAG;IACf,KAAK,EAAE,SAAS;IAChB,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,mCAAmC;IAC/C,SAAS,EAAE,WAAoB;CAClC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmailButton — branded CTA button for emails.
|
|
3
|
+
*
|
|
4
|
+
* Renders as an anchor tag with inline styles for maximum email client compatibility.
|
|
5
|
+
*/
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import type { TenantBranding } from '../branding';
|
|
8
|
+
interface EmailButtonProps {
|
|
9
|
+
href: string;
|
|
10
|
+
branding: TenantBranding;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export declare const EmailButton: React.FC<EmailButtonProps>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=EmailButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailButton.d.ts","sourceRoot":"","sources":["../../src/components/EmailButton.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,gBAAgB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkBlD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* EmailButton — branded CTA button for emails.
|
|
4
|
+
*
|
|
5
|
+
* Renders as an anchor tag with inline styles for maximum email client compatibility.
|
|
6
|
+
*/
|
|
7
|
+
import { Button } from '@react-email/components';
|
|
8
|
+
export const EmailButton = ({ href, branding, children }) => (_jsx(Button, { href: href, style: {
|
|
9
|
+
backgroundColor: branding.primaryColor,
|
|
10
|
+
color: branding.primaryTextColor,
|
|
11
|
+
borderRadius: 4,
|
|
12
|
+
fontSize: 16,
|
|
13
|
+
fontWeight: 600,
|
|
14
|
+
textDecoration: 'none',
|
|
15
|
+
textAlign: 'center',
|
|
16
|
+
display: 'inline-block',
|
|
17
|
+
padding: '16px 24px',
|
|
18
|
+
lineHeight: '1',
|
|
19
|
+
}, children: children }));
|
|
20
|
+
//# sourceMappingURL=EmailButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailButton.js","sourceRoot":"","sources":["../../src/components/EmailButton.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAWjD,MAAM,CAAC,MAAM,WAAW,GAA+B,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CACrF,KAAC,MAAM,IACH,IAAI,EAAE,IAAI,EACV,KAAK,EAAE;QACH,eAAe,EAAE,QAAQ,CAAC,YAAY;QACtC,KAAK,EAAE,QAAQ,CAAC,gBAAgB;QAChC,YAAY,EAAE,CAAC;QACf,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,GAAG;QACf,cAAc,EAAE,MAAM;QACtB,SAAS,EAAE,QAAiB;QAC5B,OAAO,EAAE,cAAc;QACvB,OAAO,EAAE,WAAW;QACpB,UAAU,EAAE,GAAG;KAClB,YAEA,QAAQ,GACJ,CACZ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IssuerLogo — circular avatar for the credential issuer.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the 80×80 circular logo used in the original Postmark templates.
|
|
5
|
+
* Only renders when `logoUrl` is provided; otherwise renders nothing.
|
|
6
|
+
*/
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
interface IssuerLogoProps {
|
|
9
|
+
logoUrl?: string;
|
|
10
|
+
alt?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const IssuerLogo: React.FC<IssuerLogoProps>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=IssuerLogo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IssuerLogo.d.ts","sourceRoot":"","sources":["../../src/components/IssuerLogo.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,UAAU,eAAe;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAchD,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* IssuerLogo — circular avatar for the credential issuer.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the 80×80 circular logo used in the original Postmark templates.
|
|
6
|
+
* Only renders when `logoUrl` is provided; otherwise renders nothing.
|
|
7
|
+
*/
|
|
8
|
+
import { Img, Section } from '@react-email/components';
|
|
9
|
+
export const IssuerLogo = ({ logoUrl, alt }) => {
|
|
10
|
+
if (!logoUrl)
|
|
11
|
+
return null;
|
|
12
|
+
return (_jsx(Section, { style: wrapper, children: _jsx(Img, { src: logoUrl, alt: alt ?? 'Issuer logo', width: 80, height: 80, style: avatar }) }));
|
|
13
|
+
};
|
|
14
|
+
const wrapper = {
|
|
15
|
+
textAlign: 'center',
|
|
16
|
+
margin: '0 0 24px',
|
|
17
|
+
};
|
|
18
|
+
const avatar = {
|
|
19
|
+
borderRadius: '50%',
|
|
20
|
+
display: 'inline-block',
|
|
21
|
+
objectFit: 'cover',
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=IssuerLogo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IssuerLogo.js","sourceRoot":"","sources":["../../src/components/IssuerLogo.tsx"],"names":[],"mappings":";AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAQvD,MAAM,CAAC,MAAM,UAAU,GAA8B,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;IACtE,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,OAAO,CACH,KAAC,OAAO,IAAC,KAAK,EAAE,OAAO,YACnB,KAAC,GAAG,IACA,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,IAAI,aAAa,EACzB,KAAK,EAAE,EAAE,EACT,MAAM,EAAE,EAAE,EACV,KAAK,EAAE,MAAM,GACf,GACI,CACb,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,OAAO,GAAwB;IACjC,SAAS,EAAE,QAAiB;IAC5B,MAAM,EAAE,UAAU;CACrB,CAAC;AAEF,MAAM,MAAM,GAAwB;IAChC,YAAY,EAAE,KAAK;IACnB,OAAO,EAAE,cAAc;IACvB,SAAS,EAAE,OAAO;CACrB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TenantLayout — shared email wrapper with branded header + footer.
|
|
3
|
+
*
|
|
4
|
+
* Every email template wraps its content in this layout so branding
|
|
5
|
+
* is consistent across all tenant emails.
|
|
6
|
+
*/
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import type { TenantBranding } from '../branding';
|
|
9
|
+
interface LayoutProps {
|
|
10
|
+
branding: TenantBranding;
|
|
11
|
+
preview: string;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/** Show the brand icon at the top of the card. Defaults to true.
|
|
14
|
+
* Set to false when the template already has an IssuerLogo. */
|
|
15
|
+
showHeaderLogo?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const Layout: React.FC<LayoutProps>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=Layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../src/components/Layout.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,WAAW;IACjB,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;oEACgE;IAChE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA2DxC,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* TenantLayout — shared email wrapper with branded header + footer.
|
|
4
|
+
*
|
|
5
|
+
* Every email template wraps its content in this layout so branding
|
|
6
|
+
* is consistent across all tenant emails.
|
|
7
|
+
*/
|
|
8
|
+
import { Html, Head, Body, Container, Section, Img, Text, Link, Preview, } from '@react-email/components';
|
|
9
|
+
export const Layout = ({ branding, preview, children, showHeaderLogo = true }) => (_jsxs(Html, { lang: "en", children: [_jsx(Head, {}), _jsx(Preview, { children: preview }), _jsxs(Body, { style: body, children: [_jsx("div", { style: topSpacer, children: "\u00A0" }), _jsx(Container, { style: wordmarkContainer, children: _jsx(Text, { style: wordmark, children: _jsx(Link, { href: branding.websiteUrl, style: wordmarkLink, children: branding.brandName.toUpperCase() }) }) }), _jsxs(Container, { style: container, children: [showHeaderLogo && (_jsx(Section, { style: headerLogo, children: _jsx(Img, { src: branding.logoUrl, alt: branding.logoAlt, width: 80, height: 80, style: logoImg }) })), _jsx(Section, { style: showHeaderLogo ? contentAfterLogo : content, children: children })] }), _jsxs(Container, { style: footerContainer, children: [_jsxs(Text, { style: footerDisclaimer, children: ["You received this email because someone requested to add this email to their ", branding.brandName, " account. Please ignore this email if this was not you."] }), _jsxs(Text, { style: footerCopyright, children: [branding.copyrightHolder, " \u00A9 ", new Date().getFullYear()] }), _jsx(Text, { style: footerLinks, children: _jsx(Link, { href: `mailto:${branding.supportEmail}`, style: footerLink, children: "Contact Support" }) })] })] })] }));
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Styles (inline for email compatibility)
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
const body = {
|
|
14
|
+
backgroundColor: '#f3f4f6',
|
|
15
|
+
fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif",
|
|
16
|
+
margin: 0,
|
|
17
|
+
padding: 0,
|
|
18
|
+
};
|
|
19
|
+
const topSpacer = {
|
|
20
|
+
height: 56,
|
|
21
|
+
lineHeight: '56px',
|
|
22
|
+
fontSize: 1,
|
|
23
|
+
};
|
|
24
|
+
const wordmarkContainer = {
|
|
25
|
+
maxWidth: 560,
|
|
26
|
+
margin: '0 auto',
|
|
27
|
+
padding: '0 40px',
|
|
28
|
+
textAlign: 'center',
|
|
29
|
+
};
|
|
30
|
+
const wordmark = {
|
|
31
|
+
fontSize: 13,
|
|
32
|
+
fontWeight: 600,
|
|
33
|
+
letterSpacing: 4,
|
|
34
|
+
color: '#52597A',
|
|
35
|
+
margin: '0 0 32px',
|
|
36
|
+
padding: '8px 0 0',
|
|
37
|
+
};
|
|
38
|
+
const wordmarkLink = {
|
|
39
|
+
color: '#52597A',
|
|
40
|
+
textDecoration: 'none',
|
|
41
|
+
};
|
|
42
|
+
const container = {
|
|
43
|
+
backgroundColor: '#ffffff',
|
|
44
|
+
borderRadius: 4,
|
|
45
|
+
margin: '0 auto',
|
|
46
|
+
maxWidth: 600,
|
|
47
|
+
padding: 0,
|
|
48
|
+
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
|
49
|
+
};
|
|
50
|
+
const headerLogo = {
|
|
51
|
+
padding: '48px 48px 0',
|
|
52
|
+
textAlign: 'center',
|
|
53
|
+
};
|
|
54
|
+
const logoImg = {
|
|
55
|
+
borderRadius: 10,
|
|
56
|
+
display: 'inline-block',
|
|
57
|
+
};
|
|
58
|
+
const content = {
|
|
59
|
+
padding: '48px 48px 48px',
|
|
60
|
+
};
|
|
61
|
+
const contentAfterLogo = {
|
|
62
|
+
padding: '24px 48px 48px',
|
|
63
|
+
};
|
|
64
|
+
const footerContainer = {
|
|
65
|
+
maxWidth: 560,
|
|
66
|
+
margin: '0 auto',
|
|
67
|
+
padding: '32px 40px 40px',
|
|
68
|
+
textAlign: 'center',
|
|
69
|
+
};
|
|
70
|
+
const footerDisclaimer = {
|
|
71
|
+
fontSize: 12,
|
|
72
|
+
color: '#6b7280',
|
|
73
|
+
lineHeight: '18px',
|
|
74
|
+
margin: '0 0 16px',
|
|
75
|
+
textAlign: 'center',
|
|
76
|
+
};
|
|
77
|
+
const footerCopyright = {
|
|
78
|
+
fontSize: 12,
|
|
79
|
+
color: '#6b7280',
|
|
80
|
+
lineHeight: '18px',
|
|
81
|
+
margin: '0 0 8px',
|
|
82
|
+
textAlign: 'center',
|
|
83
|
+
};
|
|
84
|
+
const footerLinks = {
|
|
85
|
+
fontSize: 12,
|
|
86
|
+
color: '#6b7280',
|
|
87
|
+
lineHeight: '18px',
|
|
88
|
+
margin: 0,
|
|
89
|
+
textAlign: 'center',
|
|
90
|
+
};
|
|
91
|
+
const footerLink = {
|
|
92
|
+
color: '#6b7280',
|
|
93
|
+
textDecoration: 'underline',
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=Layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Layout.js","sourceRoot":"","sources":["../../src/components/Layout.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkFallback — "copy and paste this link" fallback below CTA buttons.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the fallback link pattern used in the original Postmark templates.
|
|
5
|
+
*/
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
interface LinkFallbackProps {
|
|
8
|
+
href: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const LinkFallback: React.FC<LinkFallbackProps>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=LinkFallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkFallback.d.ts","sourceRoot":"","sources":["../../src/components/LinkFallback.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,UAAU,iBAAiB;IACvB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAYpD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* LinkFallback — "copy and paste this link" fallback below CTA buttons.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the fallback link pattern used in the original Postmark templates.
|
|
6
|
+
*/
|
|
7
|
+
import { Text, Link, Hr } from '@react-email/components';
|
|
8
|
+
export const LinkFallback = ({ href }) => (_jsxs(_Fragment, { children: [_jsx(Hr, { style: divider }), _jsx(Text, { style: hint, children: "If you have trouble with the button above, copy and paste this link into your browser:" }), _jsx(Text, { style: linkText, children: _jsx(Link, { href: href, style: link, children: href }) })] }));
|
|
9
|
+
const divider = {
|
|
10
|
+
borderColor: '#e5e7eb',
|
|
11
|
+
margin: '24px 0 16px',
|
|
12
|
+
};
|
|
13
|
+
const hint = {
|
|
14
|
+
fontSize: 12,
|
|
15
|
+
color: '#6b7280',
|
|
16
|
+
lineHeight: '18px',
|
|
17
|
+
margin: '0 0 4px',
|
|
18
|
+
};
|
|
19
|
+
const linkText = {
|
|
20
|
+
fontSize: 12,
|
|
21
|
+
lineHeight: '18px',
|
|
22
|
+
margin: '0 0 4px',
|
|
23
|
+
wordBreak: 'break-all',
|
|
24
|
+
};
|
|
25
|
+
const link = {
|
|
26
|
+
color: '#6366f1',
|
|
27
|
+
textDecoration: 'none',
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=LinkFallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkFallback.js","sourceRoot":"","sources":["../../src/components/LinkFallback.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,yBAAyB,CAAC;AAOzD,MAAM,CAAC,MAAM,YAAY,GAAgC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CACnE,8BACI,KAAC,EAAE,IAAC,KAAK,EAAE,OAAO,GAAI,EAEtB,KAAC,IAAI,IAAC,KAAK,EAAE,IAAI,uGAEV,EAEP,KAAC,IAAI,IAAC,KAAK,EAAE,QAAQ,YACjB,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,YAAG,IAAI,GAAQ,GACzC,IACR,CACN,CAAC;AAEF,MAAM,OAAO,GAAwB;IACjC,WAAW,EAAE,SAAS;IACtB,MAAM,EAAE,aAAa;CACxB,CAAC;AAEF,MAAM,IAAI,GAAwB;IAC9B,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,SAAS;IAChB,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,SAAS;CACpB,CAAC;AAEF,MAAM,QAAQ,GAAwB;IAClC,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,WAAoB;CAClC,CAAC;AAEF,MAAM,IAAI,GAAwB;IAC9B,KAAK,EAAE,SAAS;IAChB,cAAc,EAAE,MAAM;CACzB,CAAC"}
|