@dbx-tools/ui-email 0.6.55 → 0.6.57
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/README.md +9 -1
- package/package.json +6 -6
- package/src/react/auth-gate.tsx +16 -6
- package/src/react/email-approval-card.tsx +16 -3
package/README.md
CHANGED
|
@@ -66,7 +66,10 @@ import { EmailPreview } from "@dbx-tools/ui-email/react";
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
Use `EmailPreview` when a page needs the same full branded card without action
|
|
69
|
-
buttons, such as a review queue, audit log, or test harness.
|
|
69
|
+
buttons, such as a review queue, audit log, or test harness. It projects the
|
|
70
|
+
nearest `BrandProvider` context into email-safe colors and typography, so a live
|
|
71
|
+
site brand picker updates the preview too. Pass the optional `brand` prop when a
|
|
72
|
+
specific message needs an independent campaign or partner identity.
|
|
70
73
|
|
|
71
74
|
## Provide A Compose View
|
|
72
75
|
|
|
@@ -134,6 +137,11 @@ It holds no token: the session lives in an HttpOnly cookie the browser sends
|
|
|
134
137
|
automatically. `title` and `description` override the default copy, which
|
|
135
138
|
otherwise names the app from the brand context.
|
|
136
139
|
|
|
140
|
+
Before requesting a code, the email field requires exactly one address parsed
|
|
141
|
+
and validated by `@dbx-tools/shared-core`'s `net.parseEmails` + `net.isEmail`.
|
|
142
|
+
Malformed or multi-address input stays in the browser and never reaches the auth
|
|
143
|
+
endpoint.
|
|
144
|
+
|
|
137
145
|
The code field carries `autocomplete="one-time-code"`, which is what lets iOS,
|
|
138
146
|
Android, and Safari offer the code straight from the notification. That only pays
|
|
139
147
|
off while the email keeps the conventional `Your verification code is: / <code>`
|
package/package.json
CHANGED
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"typescript": "^5.9.3"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@dbx-tools/shared-core": "0.6.
|
|
28
|
-
"@dbx-tools/shared-email": "0.6.
|
|
29
|
-
"@dbx-tools/shared-email-template": "0.6.
|
|
30
|
-
"@dbx-tools/ui-appkit": "0.6.
|
|
31
|
-
"@dbx-tools/ui-branding": "0.6.
|
|
27
|
+
"@dbx-tools/shared-core": "0.6.57",
|
|
28
|
+
"@dbx-tools/shared-email": "0.6.57",
|
|
29
|
+
"@dbx-tools/shared-email-template": "0.6.57",
|
|
30
|
+
"@dbx-tools/ui-appkit": "0.6.57",
|
|
31
|
+
"@dbx-tools/ui-branding": "0.6.57",
|
|
32
32
|
"lucide-react": "^0.554.0",
|
|
33
33
|
"react": "^19.2.4",
|
|
34
34
|
"react-dom": "^19.2.4"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
|
-
"version": "0.6.
|
|
40
|
+
"version": "0.6.57",
|
|
41
41
|
"type": "module",
|
|
42
42
|
"exports": {
|
|
43
43
|
"./react": "./src/react/index.ts",
|
package/src/react/auth-gate.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { string } from "@dbx-tools/shared-core";
|
|
1
|
+
import { net, string } from "@dbx-tools/shared-core";
|
|
2
2
|
import type { AuthStatus } from "@dbx-tools/shared-email";
|
|
3
3
|
import { Button, Input } from "@dbx-tools/ui-appkit/react";
|
|
4
4
|
import { BrandIcon, useBrand } from "@dbx-tools/ui-branding/react";
|
|
@@ -86,12 +86,19 @@ export function AuthGate({ children, title, description }: AuthGateProps): React
|
|
|
86
86
|
const requestCode = useCallback(
|
|
87
87
|
async (e: FormEvent) => {
|
|
88
88
|
e.preventDefault();
|
|
89
|
-
if (
|
|
89
|
+
if (busy) return;
|
|
90
|
+
const addresses = net.parseEmails(email);
|
|
91
|
+
const normalizedEmail = addresses[0];
|
|
92
|
+
if (addresses.length !== 1 || !normalizedEmail || !net.isEmail(normalizedEmail)) {
|
|
93
|
+
setNotice("Enter a valid email address.");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
90
96
|
setBusy(true);
|
|
91
97
|
setNotice(null);
|
|
98
|
+
setEmail(normalizedEmail);
|
|
92
99
|
try {
|
|
93
100
|
const result = await postJson<{ ok: true; retryAfter?: number }>(`${AUTH_BASE}/request`, {
|
|
94
|
-
email:
|
|
101
|
+
email: normalizedEmail,
|
|
95
102
|
});
|
|
96
103
|
// Anti-enumeration: always advance to the code step. Surface only a
|
|
97
104
|
// rate-limit cooldown, which leaks no allow-list state.
|
|
@@ -158,12 +165,13 @@ export function AuthGate({ children, title, description }: AuthGateProps): React
|
|
|
158
165
|
</p>
|
|
159
166
|
|
|
160
167
|
{phase === "email" ? (
|
|
161
|
-
<form onSubmit={requestCode} className="space-y-3">
|
|
168
|
+
<form key="email" noValidate onSubmit={requestCode} className="space-y-3">
|
|
162
169
|
<Input
|
|
163
170
|
type="email"
|
|
171
|
+
name="email"
|
|
164
172
|
autoComplete="email"
|
|
165
173
|
aria-label="Email address"
|
|
166
|
-
placeholder="you@
|
|
174
|
+
placeholder="you@example.com"
|
|
167
175
|
value={email}
|
|
168
176
|
onChange={(e) => setEmail(e.target.value)}
|
|
169
177
|
required
|
|
@@ -173,7 +181,7 @@ export function AuthGate({ children, title, description }: AuthGateProps): React
|
|
|
173
181
|
</Button>
|
|
174
182
|
</form>
|
|
175
183
|
) : (
|
|
176
|
-
<form onSubmit={verifyCode} className="space-y-3">
|
|
184
|
+
<form key="code" onSubmit={verifyCode} className="space-y-3">
|
|
177
185
|
{/*
|
|
178
186
|
`autoComplete="one-time-code"` is what lets iOS/Android/Safari offer
|
|
179
187
|
the code straight from the notification, and it only pays off when
|
|
@@ -182,6 +190,8 @@ export function AuthGate({ children, title, description }: AuthGateProps): React
|
|
|
182
190
|
number pad without rejecting a paste.
|
|
183
191
|
*/}
|
|
184
192
|
<Input
|
|
193
|
+
type="text"
|
|
194
|
+
name="code"
|
|
185
195
|
inputMode="numeric"
|
|
186
196
|
autoComplete="one-time-code"
|
|
187
197
|
aria-label="Verification code"
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EmailCard,
|
|
3
|
+
emailBrandFromContext,
|
|
4
|
+
type EmailBrand,
|
|
5
|
+
} from "@dbx-tools/shared-email-template";
|
|
2
6
|
import { Button } from "@dbx-tools/ui-appkit/react";
|
|
7
|
+
import { useBrand } from "@dbx-tools/ui-branding/react";
|
|
3
8
|
import { CheckIcon, MailIcon, XIcon } from "lucide-react";
|
|
4
9
|
import { attachmentNames, joinAddresses, type EmailDraft } from "./fields.ts";
|
|
5
10
|
|
|
@@ -16,6 +21,8 @@ export type { EmailDraft } from "./fields.ts";
|
|
|
16
21
|
/** Props for {@link EmailPreview}. */
|
|
17
22
|
export interface EmailPreviewProps {
|
|
18
23
|
email: EmailDraft;
|
|
24
|
+
/** Optional email-only brand. Defaults to the active `BrandProvider` context. */
|
|
25
|
+
brand?: EmailBrand;
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
/**
|
|
@@ -24,7 +31,8 @@ export interface EmailPreviewProps {
|
|
|
24
31
|
* rendered through the same React Email body used for delivery. Fields that
|
|
25
32
|
* are empty are omitted.
|
|
26
33
|
*/
|
|
27
|
-
export const EmailPreview = ({ email }: EmailPreviewProps) => {
|
|
34
|
+
export const EmailPreview = ({ email, brand }: EmailPreviewProps) => {
|
|
35
|
+
const { context } = useBrand();
|
|
28
36
|
const to = joinAddresses(email.to);
|
|
29
37
|
const cc = joinAddresses(email.cc);
|
|
30
38
|
const bcc = joinAddresses(email.bcc);
|
|
@@ -36,7 +44,12 @@ export const EmailPreview = ({ email }: EmailPreviewProps) => {
|
|
|
36
44
|
if (attachments) headers.push(["Files", attachments]);
|
|
37
45
|
return (
|
|
38
46
|
<div className="overflow-x-auto rounded-2xl bg-muted/20 p-2">
|
|
39
|
-
<EmailCard
|
|
47
|
+
<EmailCard
|
|
48
|
+
subject={email.subject || "Message"}
|
|
49
|
+
body={email.body || ""}
|
|
50
|
+
headers={headers}
|
|
51
|
+
brand={brand ?? emailBrandFromContext(context)}
|
|
52
|
+
/>
|
|
40
53
|
</div>
|
|
41
54
|
);
|
|
42
55
|
};
|