@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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -0
  3. package/dist/branding.d.ts +36 -0
  4. package/dist/branding.d.ts.map +1 -0
  5. package/dist/branding.js +28 -0
  6. package/dist/branding.js.map +1 -0
  7. package/dist/components/CodeBlock.d.ts +16 -0
  8. package/dist/components/CodeBlock.d.ts.map +1 -0
  9. package/dist/components/CodeBlock.js +42 -0
  10. package/dist/components/CodeBlock.js.map +1 -0
  11. package/dist/components/EmailButton.d.ts +15 -0
  12. package/dist/components/EmailButton.d.ts.map +1 -0
  13. package/dist/components/EmailButton.js +20 -0
  14. package/dist/components/EmailButton.js.map +1 -0
  15. package/dist/components/IssuerLogo.d.ts +14 -0
  16. package/dist/components/IssuerLogo.d.ts.map +1 -0
  17. package/dist/components/IssuerLogo.js +23 -0
  18. package/dist/components/IssuerLogo.js.map +1 -0
  19. package/dist/components/Layout.d.ts +19 -0
  20. package/dist/components/Layout.d.ts.map +1 -0
  21. package/dist/components/Layout.js +95 -0
  22. package/dist/components/Layout.js.map +1 -0
  23. package/dist/components/LinkFallback.d.ts +12 -0
  24. package/dist/components/LinkFallback.d.ts.map +1 -0
  25. package/dist/components/LinkFallback.js +29 -0
  26. package/dist/components/LinkFallback.js.map +1 -0
  27. package/dist/index.d.ts +24 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +28 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/previews/_fixtures.d.ts +12 -0
  32. package/dist/previews/_fixtures.d.ts.map +1 -0
  33. package/dist/previews/_fixtures.js +34 -0
  34. package/dist/previews/_fixtures.js.map +1 -0
  35. package/dist/previews/account-approved-scenarios.d.ts +5 -0
  36. package/dist/previews/account-approved-scenarios.d.ts.map +1 -0
  37. package/dist/previews/account-approved-scenarios.js +7 -0
  38. package/dist/previews/account-approved-scenarios.js.map +1 -0
  39. package/dist/previews/endorsement-request-scenarios.d.ts +5 -0
  40. package/dist/previews/endorsement-request-scenarios.d.ts.map +1 -0
  41. package/dist/previews/endorsement-request-scenarios.js +7 -0
  42. package/dist/previews/endorsement-request-scenarios.js.map +1 -0
  43. package/dist/previews/guardian-approval-scenarios.d.ts +5 -0
  44. package/dist/previews/guardian-approval-scenarios.d.ts.map +1 -0
  45. package/dist/previews/guardian-approval-scenarios.js +7 -0
  46. package/dist/previews/guardian-approval-scenarios.js.map +1 -0
  47. package/dist/previews/guardian-credential-scenarios.d.ts +5 -0
  48. package/dist/previews/guardian-credential-scenarios.d.ts.map +1 -0
  49. package/dist/previews/guardian-credential-scenarios.js +11 -0
  50. package/dist/previews/guardian-credential-scenarios.js.map +1 -0
  51. package/dist/previews/inbox-claim-scenarios.d.ts +8 -0
  52. package/dist/previews/inbox-claim-scenarios.d.ts.map +1 -0
  53. package/dist/previews/inbox-claim-scenarios.js +7 -0
  54. package/dist/previews/inbox-claim-scenarios.js.map +1 -0
  55. package/dist/previews/recovery-key-scenarios.d.ts +5 -0
  56. package/dist/previews/recovery-key-scenarios.d.ts.map +1 -0
  57. package/dist/previews/recovery-key-scenarios.js +7 -0
  58. package/dist/previews/recovery-key-scenarios.js.map +1 -0
  59. package/dist/previews/verification-code-scenarios.d.ts +5 -0
  60. package/dist/previews/verification-code-scenarios.d.ts.map +1 -0
  61. package/dist/previews/verification-code-scenarios.js +7 -0
  62. package/dist/previews/verification-code-scenarios.js.map +1 -0
  63. package/dist/render.d.ts +185 -0
  64. package/dist/render.d.ts.map +1 -0
  65. package/dist/render.js +153 -0
  66. package/dist/render.js.map +1 -0
  67. package/dist/sms.d.ts +34 -0
  68. package/dist/sms.d.ts.map +1 -0
  69. package/dist/sms.js +34 -0
  70. package/dist/sms.js.map +1 -0
  71. package/dist/templates/account-approved.d.ts +17 -0
  72. package/dist/templates/account-approved.d.ts.map +1 -0
  73. package/dist/templates/account-approved.js +46 -0
  74. package/dist/templates/account-approved.js.map +1 -0
  75. package/dist/templates/credential-awaiting-guardian.d.ts +25 -0
  76. package/dist/templates/credential-awaiting-guardian.d.ts.map +1 -0
  77. package/dist/templates/credential-awaiting-guardian.js +65 -0
  78. package/dist/templates/credential-awaiting-guardian.js.map +1 -0
  79. package/dist/templates/email-verification.d.ts +21 -0
  80. package/dist/templates/email-verification.d.ts.map +1 -0
  81. package/dist/templates/email-verification.js +51 -0
  82. package/dist/templates/email-verification.js.map +1 -0
  83. package/dist/templates/endorsement-request.d.ts +28 -0
  84. package/dist/templates/endorsement-request.d.ts.map +1 -0
  85. package/dist/templates/endorsement-request.js +67 -0
  86. package/dist/templates/endorsement-request.js.map +1 -0
  87. package/dist/templates/guardian-approval.d.ts +23 -0
  88. package/dist/templates/guardian-approval.d.ts.map +1 -0
  89. package/dist/templates/guardian-approval.js +50 -0
  90. package/dist/templates/guardian-approval.js.map +1 -0
  91. package/dist/templates/guardian-approved-claim.d.ts +22 -0
  92. package/dist/templates/guardian-approved-claim.d.ts.map +1 -0
  93. package/dist/templates/guardian-approved-claim.js +49 -0
  94. package/dist/templates/guardian-approved-claim.js.map +1 -0
  95. package/dist/templates/guardian-credential-approval.d.ts +27 -0
  96. package/dist/templates/guardian-credential-approval.d.ts.map +1 -0
  97. package/dist/templates/guardian-credential-approval.js +51 -0
  98. package/dist/templates/guardian-credential-approval.js.map +1 -0
  99. package/dist/templates/guardian-email-otp.d.ts +16 -0
  100. package/dist/templates/guardian-email-otp.d.ts.map +1 -0
  101. package/dist/templates/guardian-email-otp.js +49 -0
  102. package/dist/templates/guardian-email-otp.js.map +1 -0
  103. package/dist/templates/guardian-rejected-credential.d.ts +24 -0
  104. package/dist/templates/guardian-rejected-credential.d.ts.map +1 -0
  105. package/dist/templates/guardian-rejected-credential.js +44 -0
  106. package/dist/templates/guardian-rejected-credential.js.map +1 -0
  107. package/dist/templates/inbox-claim.d.ts +28 -0
  108. package/dist/templates/inbox-claim.d.ts.map +1 -0
  109. package/dist/templates/inbox-claim.js +58 -0
  110. package/dist/templates/inbox-claim.js.map +1 -0
  111. package/dist/templates/index.d.ts +25 -0
  112. package/dist/templates/index.d.ts.map +1 -0
  113. package/dist/templates/index.js +13 -0
  114. package/dist/templates/index.js.map +1 -0
  115. package/dist/templates/recovery-key.d.ts +15 -0
  116. package/dist/templates/recovery-key.d.ts.map +1 -0
  117. package/dist/templates/recovery-key.js +46 -0
  118. package/dist/templates/recovery-key.js.map +1 -0
  119. package/dist/templates/verification-code.d.ts +24 -0
  120. package/dist/templates/verification-code.d.ts.map +1 -0
  121. package/dist/templates/verification-code.js +87 -0
  122. package/dist/templates/verification-code.js.map +1 -0
  123. package/dist/tenant-registry.d.ts +48 -0
  124. package/dist/tenant-registry.d.ts.map +1 -0
  125. package/dist/tenant-registry.js +146 -0
  126. package/dist/tenant-registry.js.map +1 -0
  127. 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"}
@@ -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"}