@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 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.56",
28
- "@dbx-tools/shared-email": "0.6.56",
29
- "@dbx-tools/shared-email-template": "0.6.56",
30
- "@dbx-tools/ui-appkit": "0.6.56",
31
- "@dbx-tools/ui-branding": "0.6.56",
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.56",
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,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@company.com"
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 { 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
  };