@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 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.55",
28
- "@dbx-tools/shared-email": "0.6.55",
29
- "@dbx-tools/shared-email-template": "0.6.55",
30
- "@dbx-tools/ui-appkit": "0.6.55",
31
- "@dbx-tools/ui-branding": "0.6.55",
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.55",
40
+ "version": "0.6.57",
41
41
  "type": "module",
42
42
  "exports": {
43
43
  "./react": "./src/react/index.ts",
@@ -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 (!email.trim() || busy) return;
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: email.trim(),
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@company.com"
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 { EmailCard } from "@dbx-tools/shared-email-template";
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 subject={email.subject || "Message"} body={email.body || ""} headers={headers} />
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
  };