@dbx-tools/ui-email 0.6.56 → 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 +12 -5
- 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,13 +165,13 @@ export function AuthGate({ children, title, description }: AuthGateProps): React
|
|
|
158
165
|
</p>
|
|
159
166
|
|
|
160
167
|
{phase === "email" ? (
|
|
161
|
-
<form key="email" onSubmit={requestCode} className="space-y-3">
|
|
168
|
+
<form key="email" noValidate onSubmit={requestCode} className="space-y-3">
|
|
162
169
|
<Input
|
|
163
170
|
type="email"
|
|
164
171
|
name="email"
|
|
165
172
|
autoComplete="email"
|
|
166
173
|
aria-label="Email address"
|
|
167
|
-
placeholder="you@
|
|
174
|
+
placeholder="you@example.com"
|
|
168
175
|
value={email}
|
|
169
176
|
onChange={(e) => setEmail(e.target.value)}
|
|
170
177
|
required
|
|
@@ -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
|
};
|