@agentaily/design-system 0.3.0 → 0.5.0

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/DESIGN.md CHANGED
@@ -98,7 +98,7 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
98
98
  | `components/code/` | **Terminal, FileTree, Snippet, StackTrace, TestResults, Artifact, WebPreview, Agent, Commit, EnvironmentVariables, PackageInfo, Sandbox, SchemaDisplay, JSXPreview** |
99
99
  | `components/voice/` | **AudioPlayer, MicSelector, VoiceSelector, SpeechInput, Transcription, Persona** |
100
100
  | `components/workflow/` | **Flow (Canvas), Canvas, Node, Edge, Connection, Controls, Panel, Toolbar** |
101
- | `components/utilities/` | **Image, OpenInChat, Icon (unified Lucide set), BrandMark** |
101
+ | `components/utilities/` | **Image, OpenInChat, Icon (unified Lucide set), BrandMark, RotatingTagline** |
102
102
  | `components/settings/` | **TestRow, HelpSteps, IntegrationSettings** — connection-card primitives + the DeepSeek/Feishu integration modal |
103
103
  | `components/auth/` | **AuthDialog (+ AuthDialog.useAuth), AccountControl, SignInPage** — sign-in/register modal + persisted session + top-bar account menu + full-page sign-in |
104
104
  | `components/review/` | **MarkupLayer** — point-at-an-element review overlay (`data-mk-label`) |
@@ -108,7 +108,7 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
108
108
  | `ui_kits/docs/` | Documentation site (interactive) |
109
109
  | `SKILL.md` | Agent-skill entry point |
110
110
 
111
- Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **145 component exports** across the primitive categories (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities) plus product-domain layers (**settings, auth, review**). Full-page frames — `AppShell` / `DesignerShell` / `DocsLayout` / `SettingsPage` (layout), `ConversationThread` (chat), `SignInPage` (auth) — are **live components, not copy-templates**: change one, every consuming project benefits on re-sync. Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
111
+ Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **146 component exports** across the primitive categories (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities) plus product-domain layers (**settings, auth, review**). Full-page frames — `AppShell` / `DesignerShell` / `DocsLayout` / `SettingsPage` (layout), `ConversationThread` (chat), `SignInPage` (auth) — are **live components, not copy-templates**: change one, every consuming project benefits on re-sync. Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
112
112
 
113
113
  **Forms are layered, not monolithic.** Presentational controls (Input/Select/Field…) own layout and never depend on a form engine. `Form` + `FormActions` add pure structure. `Form.useForm` is an **optional**, zero-dependency orchestration hook (values/errors/touched/validate/submit) exposed off the capitalized `Form` export — drop it for react-hook-form or TanStack and the controls still work. Errors surface only after blur or submit; spread `form.field(name)` onto any value control, or `form.field(name, {type:"checkbox"})` for boolean ones.
114
114
 
@@ -121,7 +121,7 @@ The system sits on **two orthogonal axes**. Don't conflate them: a *domain* is n
121
121
  | Layer | What | Depends on | Examples |
122
122
  |---|---|---|---|
123
123
  | **L0 Tokens** | colour / type / space / radius / shadow / motion variables | — | `tokens/*.css` |
124
- | **L1 Primitives** (atoms) | no business meaning, usable anywhere | tokens | `Button`, `Input`, `Badge`, `Icon`, `BrandMark` |
124
+ | **L1 Primitives** (atoms) | no business meaning, usable anywhere | tokens | `Button`, `Input`, `Badge`, `Icon`, `BrandMark`, `RotatingTagline` |
125
125
  | **L2 Composites** (molecules) | a few primitives combined; still cross-product | L1 | `Composer`, `SecretField`, `StatusPill` |
126
126
  | **L3 Patterns** (organisms) | self-contained interaction + state, configurable | L1–L2 | `AuthDialog`, `IntegrationSettings`, `MarkupLayer`, `ConversationThread` |
127
127
  | **L4 Page shells / frames** | full-page layout with slots | L1–L3 | `AppShell`, `DesignerShell`, `DocsLayout`, `SettingsPage`, `SignInPage` |
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # agentaily design system
2
2
 
3
- Agentaily(AI chatbot)设计系统:112 个 React 组件 + Storybook,单色亮色优先(暗色可切)。品牌一句话:**极客风格,简约,大气,科技感**。
3
+ Agentaily(AI chatbot)设计系统:113 个 React 组件 + Storybook,单色亮色优先(暗色可切)。品牌一句话:**极客风格,简约,大气,科技感**。
4
4
 
5
5
  📖 **在线 Storybook:** https://agentaily.github.io/design-system/
6
6
 
@@ -28,7 +28,7 @@ npm run build:lib # 产出 dist/:每组件一个 .js + index.d.ts + styles.css
28
28
 
29
29
  | 路径 | 内容 |
30
30
  | ----------------------------------------------- | ----------------------------------------- |
31
- | `dist/index.js` | ESM 入口,re-export 全部 112 个组件符号 |
31
+ | `dist/index.js` | ESM 入口,re-export 全部 113 个组件符号 |
32
32
  | `dist/components/**/*.js` | 每个组件独立模块(含运行时 CSS 注入) |
33
33
  | `dist/index.d.ts` + `dist/components/**/*.d.ts` | TypeScript 类型契约 |
34
34
  | `dist/styles.css` | 内联好的 tokens + 字体,消费方 import 一次 |
@@ -4,7 +4,9 @@
4
4
  * are shared; only fields, copy, and footer differ. The built-in footer link
5
5
  * flips mode and fires onModeChange (wire it to your router to swap the URL).
6
6
  * Light built-in validation (email format, password length, confirm match).
7
- * For a modal instead, use AuthDialog. Brand panel hides < 860px.
7
+ * Pass `error` to surface a backend error above the submit button and
8
+ * `submitting` to drive the async busy state. For a modal instead, use AuthDialog.
9
+ * Brand panel hides < 860px.
8
10
  */
9
11
  export interface SignInPageCopy {
10
12
  title?: string;
@@ -15,6 +17,21 @@ export interface SignInPageCopy {
15
17
  /** Footer link label that flips mode, e.g. "Create one" */
16
18
  switchCta?: string;
17
19
  }
20
+ /** Shared, non-mode-specific strings. All optional — merged over the English defaults. */
21
+ export interface SignInPageSharedCopy {
22
+ /** Field labels + chrome: { email, password, confirm, forgot, or, sso }. */
23
+ labels?: { email?: string; password?: string; confirm?: string; forgot?: string; or?: string; sso?: string };
24
+ /** Input placeholders: { email, password, passwordNew, confirm }. */
25
+ placeholders?: { email?: string; password?: string; passwordNew?: string; confirm?: string };
26
+ /** Validation messages: { emailRequired, emailInvalid, passwordRequired, passwordShort, confirmRequired, confirmMismatch }. */
27
+ errors?: {
28
+ emailRequired?: string; emailInvalid?: string;
29
+ passwordRequired?: string; passwordShort?: string;
30
+ confirmRequired?: string; confirmMismatch?: string;
31
+ };
32
+ /** Default signup terms line (string). Overridden by the `terms` prop if passed. */
33
+ terms?: React.ReactNode;
34
+ }
18
35
  export interface SignInPageProps {
19
36
  /** Controlled mode. Omit for uncontrolled (uses defaultMode). */
20
37
  mode?: "signin" | "signup";
@@ -24,8 +41,14 @@ export interface SignInPageProps {
24
41
  onModeChange?: (mode: "signin" | "signup") => void;
25
42
  /** Brand-panel lockup. @default <BrandMark wordmark /> (blinking cursor) */
26
43
  brand?: React.ReactNode;
27
- /** Per-mode copy overrides, merged over defaults: { signin?, signup? }. */
28
- copy?: { signin?: SignInPageCopy; signup?: SignInPageCopy };
44
+ /**
45
+ * All user-facing strings, deep-merged over the English defaults. Per-mode
46
+ * copy under `signin`/`signup`; shared labels, placeholders, errors, and the
47
+ * terms line at the top level. Pass a full zh-CN object to localize.
48
+ */
49
+ copy?: { signin?: SignInPageCopy; signup?: SignInPageCopy } & SignInPageSharedCopy;
50
+ /** Props forwarded to the brand-panel RotatingTagline (prefix, phrases, flowDuration…). @default { breakAfterPrefix: true } */
51
+ tagline?: Record<string, unknown>;
29
52
  email?: string;
30
53
  password?: string;
31
54
  onEmailChange?: (value: string) => void;
@@ -34,7 +57,7 @@ export interface SignInPageProps {
34
57
  onSubmit?: (values: { mode: "signin" | "signup"; email: string; password: string }) => void;
35
58
  /** Show + handle the "Forgot?" link (signin mode only). */
36
59
  onForgot?: () => void;
37
- /** @default "Continue with SSO" */
60
+ /** SSO button label. Overrides copy.labels.sso. @default "Continue with SSO" */
38
61
  ssoLabel?: string;
39
62
  /** Show + handle the SSO button (hidden when omitted). */
40
63
  onSSO?: () => void;
@@ -44,5 +67,20 @@ export interface SignInPageProps {
44
67
  footer?: React.ReactNode;
45
68
  /** Show the left brand panel. @default true */
46
69
  showBrandPanel?: boolean;
70
+ /**
71
+ * Server-side error to show in a danger banner directly above the submit
72
+ * button (e.g. 409 email taken, 401 bad credentials, 400 weak password).
73
+ * Omit/falsy = nothing shown. The component owns its CLIENT validation errors
74
+ * and clears them on input/mode change; this backend error is caller-owned —
75
+ * clear it yourself (typically inside onSubmit before the request, and on
76
+ * onModeChange / onEmailChange / onPasswordChange).
77
+ */
78
+ error?: React.ReactNode;
79
+ /**
80
+ * Submit-in-flight flag for an async onSubmit. When true the submit button is
81
+ * disabled and shows a spinner, and re-submits are blocked — prevents double
82
+ * submission. @default false
83
+ */
84
+ submitting?: boolean;
47
85
  }
48
86
  export declare function SignInPage(props: SignInPageProps): JSX.Element;
@@ -1,20 +1,24 @@
1
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import React, { useState } from "react";
3
- import { BrandMark } from "../utilities/BrandMark.js";
3
+ import { Input } from "../inputs/Input.js";
4
4
  import { Button } from "../buttons/Button.js";
5
5
  import { Icon } from "../utilities/Icon.js";
6
- import { Input } from "../inputs/Input.js";
6
+ import { BrandMark } from "../utilities/BrandMark.js";
7
+ import { RotatingTagline } from "../utilities/RotatingTagline.js";
8
+ import { Spinner } from "../feedback/Spinner.js";
7
9
  const AX_SIGNIN_CSS = `
8
10
  .ax-signin { display: grid; grid-template-columns: 1fr 1fr; height: 100%; background: var(--surface-page); }
9
11
  .ax-signin__brand { position: relative; border-right: 1px solid var(--border-default); background: var(--surface-panel);
10
12
  background-image: var(--dot-grid); background-size: 24px 24px; padding: 40px; display: flex; flex-direction: column; }
11
13
  .ax-signin__brandtop { display: flex; align-items: center; gap: 10px; }
12
- .ax-signin__mid { margin-top: auto; margin-bottom: auto; max-width: 22ch; }
13
- .ax-signin__quote { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }
14
+ .ax-signin__mid { margin-top: auto; margin-bottom: auto; }
15
+ .ax-signin__quote { font-family: var(--font-display); white-space: nowrap; font-size: clamp(40px, 4.4vw, var(--text-hero)); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }
14
16
  .ax-signin__by { margin: 16px 0 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--text-faint); }
15
17
  .ax-signin__form { display: flex; align-items: center; justify-content: center; padding: 40px; }
16
18
  .ax-signin__card { width: 100%; max-width: 360px; }
17
19
  .ax-signin__mbrand { display: none; }
20
+ .ax-signin__mtag { display: none; }
21
+ .ax-signin__mtag .ax-signin__quote { font-size: var(--text-3xl); }
18
22
  .ax-signin__h { margin: 0; font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); color: var(--text-body); }
19
23
  .ax-signin__sub { margin: 8px 0 28px; font-size: var(--text-sm); color: var(--text-muted); }
20
24
  .ax-signin__fields { display: flex; flex-direction: column; gap: 16px; }
@@ -22,6 +26,9 @@ const AX_SIGNIN_CSS = `
22
26
  .ax-signin__link { font-size: 12px; color: var(--text-muted); text-decoration: none; background: none; border: none; cursor: pointer; font-family: inherit; }
23
27
  .ax-signin__link:hover { color: var(--text-body); }
24
28
  .ax-signin__submit { margin-top: 22px; }
29
+ .ax-signin__error { display: flex; align-items: flex-start; gap: 9px; margin-top: 22px; padding: 10px 12px; border: 1px solid var(--danger); border-radius: var(--radius-2); background: var(--danger-dim); color: var(--danger); font-size: var(--text-sm); line-height: var(--leading-snug); }
30
+ .ax-signin__error svg { flex: none; margin-top: 1px; }
31
+ .ax-signin__submit .ax-spinner { border-top-color: var(--accent-fg); }
25
32
  .ax-signin__or { display: flex; align-items: center; gap: 12px; margin: 22px 0; }
26
33
  .ax-signin__or::before, .ax-signin__or::after { content: ""; flex: 1; height: 1px; background: var(--border-default); }
27
34
  .ax-signin__or span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; color: var(--text-faint); }
@@ -34,7 +41,7 @@ const AX_SIGNIN_CSS = `
34
41
  .ax-signin__footlink { color: var(--text-body); background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--text-sm);
35
42
  border-bottom: 1px solid var(--border-strong); padding: 0 0 1px; transition: border-color var(--dur-1) var(--ease-out); }
36
43
  .ax-signin__footlink:hover { border-color: var(--text-faint); }
37
- @media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__mbrand { display: flex; margin-bottom: 26px; } }
44
+ @media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__form { padding: 56px 40px; } .ax-signin__mbrand { display: flex; margin-bottom: 28px; } .ax-signin__mtag { display: block; margin-bottom: 36px; } .ax-signin__h { font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-medium); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-body); margin: 0 0 8px; } .ax-signin__sub { margin: 0 0 32px; color: var(--text-muted); } .ax-signin__fields { gap: 22px; } .ax-signin__submit { margin-top: 32px; } .ax-signin__or { margin: 32px 0; } .ax-signin__foot { margin-top: 28px; } }
38
45
  `;
39
46
  if (typeof document !== "undefined" && !document.getElementById("ax-signin-css")) {
40
47
  const s = document.createElement("style");
@@ -56,7 +63,30 @@ const DEFAULT_COPY = {
56
63
  submit: "Create account",
57
64
  switchText: "Already have an account?",
58
65
  switchCta: "Sign in"
59
- }
66
+ },
67
+ labels: {
68
+ email: "EMAIL",
69
+ password: "PASSWORD",
70
+ confirm: "CONFIRM PASSWORD",
71
+ forgot: "Forgot?",
72
+ or: "OR",
73
+ sso: "Continue with SSO"
74
+ },
75
+ placeholders: {
76
+ email: "you@example.com",
77
+ password: "••••••••",
78
+ passwordNew: "At least 8 characters",
79
+ confirm: "Re-enter your password"
80
+ },
81
+ errors: {
82
+ emailRequired: "Enter your email",
83
+ emailInvalid: "That email doesn’t look right",
84
+ passwordRequired: "Enter your password",
85
+ passwordShort: "Password must be at least 8 characters",
86
+ confirmRequired: "Re-enter your password",
87
+ confirmMismatch: "Passwords don’t match"
88
+ },
89
+ terms: "By creating an account you agree to our Terms and Privacy Policy."
60
90
  };
61
91
  function SignInPage({
62
92
  mode,
@@ -64,22 +94,30 @@ function SignInPage({
64
94
  onModeChange,
65
95
  brand,
66
96
  copy,
97
+ tagline,
67
98
  email,
68
99
  password,
69
100
  onEmailChange,
70
101
  onPasswordChange,
71
102
  onSubmit,
72
103
  onForgot,
73
- ssoLabel = "Continue with SSO",
104
+ ssoLabel,
74
105
  onSSO,
75
106
  terms,
76
107
  footer,
77
- showBrandPanel = true
108
+ showBrandPanel = true,
109
+ error,
110
+ submitting = false
78
111
  }) {
79
112
  const [modeI, setModeI] = useState(defaultMode);
80
113
  const m = mode !== void 0 ? mode : modeI;
81
114
  const isSignup = m === "signup";
82
115
  const c = { ...DEFAULT_COPY[m], ...copy && copy[m] };
116
+ const labels = { ...DEFAULT_COPY.labels, ...copy && copy.labels };
117
+ const ph = { ...DEFAULT_COPY.placeholders, ...copy && copy.placeholders };
118
+ const err = { ...DEFAULT_COPY.errors, ...copy && copy.errors };
119
+ const ssoText = ssoLabel !== void 0 ? ssoLabel : labels.sso;
120
+ const taglineProps = { breakAfterPrefix: true, ...tagline };
83
121
  const [emailI, setEmailI] = useState("");
84
122
  const [pwI, setPwI] = useState("");
85
123
  const [confirm, setConfirm] = useState("");
@@ -110,55 +148,46 @@ function SignInPage({
110
148
  const validate = () => {
111
149
  const e = {};
112
150
  const em = (emailV || "").trim();
113
- if (!em) e.email = "请输入邮箱";
114
- else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(em)) e.email = "邮箱格式不正确";
115
- if (!pwV) e.password = "请输入密码";
116
- else if (isSignup && pwV.length < 8) e.password = "密码至少 8 位";
151
+ if (!em) e.email = err.emailRequired;
152
+ else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(em)) e.email = err.emailInvalid;
153
+ if (!pwV) e.password = err.passwordRequired;
154
+ else if (isSignup && pwV.length < 8) e.password = err.passwordShort;
117
155
  if (isSignup) {
118
- if (!confirm) e.confirm = "请再次输入密码";
119
- else if (confirm !== pwV) e.confirm = "两次输入的密码不一致";
156
+ if (!confirm) e.confirm = err.confirmRequired;
157
+ else if (confirm !== pwV) e.confirm = err.confirmMismatch;
120
158
  }
121
159
  return e;
122
160
  };
123
161
  const submit = (ev) => {
124
162
  ev.preventDefault();
163
+ if (submitting) return;
125
164
  const e = validate();
126
165
  setErrs(e);
127
166
  if (Object.keys(e).some((k) => e[k])) return;
128
167
  onSubmit && onSubmit({ mode: m, email: (emailV || "").trim(), password: pwV });
129
168
  };
130
- const termsNode = terms !== void 0 ? terms : /* @__PURE__ */ jsxs(Fragment, { children: [
131
- "注册即代表你同意 ",
132
- /* @__PURE__ */ jsx("span", { className: "lk", children: "服务条款" }),
133
- " 与",
134
- " ",
135
- /* @__PURE__ */ jsx("span", { className: "lk", children: "隐私政策" }),
136
- "。"
137
- ] });
169
+ const termsNode = terms !== void 0 ? terms : c.terms !== void 0 ? c.terms : DEFAULT_COPY.terms;
138
170
  return /* @__PURE__ */ jsxs("div", { className: "ax-signin", children: [
139
171
  showBrandPanel ? /* @__PURE__ */ jsxs("aside", { className: "ax-signin__brand", children: [
140
- /* @__PURE__ */ jsx("div", { className: "ax-signin__brandtop", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 24, wordmark: true }) }),
172
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__brandtop", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 24, wordmark: true, blink: false }) }),
141
173
  /* @__PURE__ */ jsxs("div", { className: "ax-signin__mid", children: [
142
- /* @__PURE__ */ jsxs("p", { className: "ax-signin__quote", children: [
143
- "聊天,",
144
- /* @__PURE__ */ jsx("br", {}),
145
- "构建万物"
146
- ] }),
174
+ /* @__PURE__ */ jsx("p", { className: "ax-signin__quote", children: /* @__PURE__ */ jsx(RotatingTagline, { ...taglineProps }) }),
147
175
  /* @__PURE__ */ jsx("p", { className: "ax-signin__by", children: "— AGENTAILY" })
148
176
  ] })
149
177
  ] }) : null,
150
178
  /* @__PURE__ */ jsx("main", { className: "ax-signin__form", children: /* @__PURE__ */ jsxs("form", { className: "ax-signin__card", onSubmit: submit, noValidate: true, children: [
151
- /* @__PURE__ */ jsx("div", { className: "ax-signin__mbrand", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 22, wordmark: true }) }),
179
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__mbrand", children: brand || /* @__PURE__ */ jsx(BrandMark, { size: 22, wordmark: true, blink: false }) }),
180
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__mtag", children: /* @__PURE__ */ jsx("p", { className: "ax-signin__quote", children: /* @__PURE__ */ jsx(RotatingTagline, { ...taglineProps }) }) }),
152
181
  /* @__PURE__ */ jsx("h1", { className: "ax-signin__h", children: c.title }),
153
182
  /* @__PURE__ */ jsx("p", { className: "ax-signin__sub", children: c.subtitle }),
154
183
  /* @__PURE__ */ jsxs("div", { className: "ax-signin__fields", children: [
155
184
  /* @__PURE__ */ jsx(
156
185
  Input,
157
186
  {
158
- label: "EMAIL",
187
+ label: labels.email,
159
188
  type: "email",
160
189
  autoComplete: "email",
161
- placeholder: "you@example.com",
190
+ placeholder: ph.email,
162
191
  value: emailV,
163
192
  error: errs.email,
164
193
  onChange: (e) => setEmail(e.target.value)
@@ -166,15 +195,15 @@ function SignInPage({
166
195
  ),
167
196
  /* @__PURE__ */ jsxs("div", { children: [
168
197
  /* @__PURE__ */ jsxs("div", { className: "ax-signin__row", children: [
169
- /* @__PURE__ */ jsx("span", { className: "ax-label", children: "PASSWORD" }),
170
- !isSignup && onForgot ? /* @__PURE__ */ jsx("button", { type: "button", className: "ax-signin__link", onClick: onForgot, children: "Forgot?" }) : null
198
+ /* @__PURE__ */ jsx("span", { className: "ax-label", children: labels.password }),
199
+ !isSignup && onForgot ? /* @__PURE__ */ jsx("button", { type: "button", className: "ax-signin__link", onClick: onForgot, children: labels.forgot }) : null
171
200
  ] }),
172
201
  /* @__PURE__ */ jsx(
173
202
  Input,
174
203
  {
175
204
  type: "password",
176
205
  autoComplete: isSignup ? "new-password" : "current-password",
177
- placeholder: isSignup ? "至少 8 位" : "••••••••",
206
+ placeholder: isSignup ? ph.passwordNew : ph.password,
178
207
  value: pwV,
179
208
  error: errs.password,
180
209
  onChange: (e) => setPw(e.target.value)
@@ -184,23 +213,38 @@ function SignInPage({
184
213
  isSignup ? /* @__PURE__ */ jsx(
185
214
  Input,
186
215
  {
187
- label: "CONFIRM PASSWORD",
216
+ label: labels.confirm,
188
217
  type: "password",
189
218
  autoComplete: "new-password",
190
- placeholder: "再次输入密码",
219
+ placeholder: ph.confirm,
191
220
  value: confirm,
192
221
  error: errs.confirm,
193
222
  onChange: (e) => setConf(e.target.value)
194
223
  }
195
224
  ) : null
196
225
  ] }),
197
- /* @__PURE__ */ jsx("div", { className: "ax-signin__submit", children: /* @__PURE__ */ jsx(Button, { variant: "primary", full: true, type: "submit", icon: /* @__PURE__ */ jsx(Icon, { name: "arrow", size: 15 }), children: c.submit }) }),
226
+ error ? /* @__PURE__ */ jsxs("div", { className: "ax-signin__error", role: "alert", children: [
227
+ /* @__PURE__ */ jsx(Icon, { name: "warn", size: 15 }),
228
+ /* @__PURE__ */ jsx("span", { children: error })
229
+ ] }) : null,
230
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__submit", children: /* @__PURE__ */ jsx(
231
+ Button,
232
+ {
233
+ variant: "primary",
234
+ full: true,
235
+ type: "submit",
236
+ disabled: submitting,
237
+ "aria-busy": submitting || void 0,
238
+ icon: submitting ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : /* @__PURE__ */ jsx(Icon, { name: "arrow", size: 15 }),
239
+ children: c.submit
240
+ }
241
+ ) }),
198
242
  isSignup ? /* @__PURE__ */ jsx("p", { className: "ax-signin__terms", children: termsNode }) : null,
199
243
  onSSO ? /* @__PURE__ */ jsxs(React.Fragment, { children: [
200
- /* @__PURE__ */ jsx("div", { className: "ax-signin__or", children: /* @__PURE__ */ jsx("span", { children: "OR" }) }),
244
+ /* @__PURE__ */ jsx("div", { className: "ax-signin__or", children: /* @__PURE__ */ jsx("span", { children: labels.or }) }),
201
245
  /* @__PURE__ */ jsxs("button", { type: "button", className: "ax-signin__sso", onClick: onSSO, children: [
202
246
  /* @__PURE__ */ jsx(Icon, { name: "shield", size: 15 }),
203
- ssoLabel
247
+ ssoText
204
248
  ] })
205
249
  ] }) : null,
206
250
  footer !== void 0 ? footer ? /* @__PURE__ */ jsx("p", { className: "ax-signin__foot", children: footer }) : null : /* @__PURE__ */ jsxs("p", { className: "ax-signin__foot", children: [
@@ -1 +1 @@
1
- {"version":3,"file":"SignInPage.js","sources":["../../../src/components/auth/SignInPage.jsx"],"sourcesContent":["import React, { useState } from \"react\";\nimport { BrandMark } from \"../utilities/BrandMark.jsx\";\nimport { Button } from \"../buttons/Button.jsx\";\nimport { Icon } from \"../utilities/Icon.jsx\";\nimport { Input } from \"../inputs/Input.jsx\";\n\n// SignInPage — full-page auth: a split brand panel (dot-grid + quote) beside a\n// centered card. One component, two modes (signin / signup) — the brand panel,\n// SSO, and card chrome are shared; only the fields, copy, and footer differ.\n// The footer link flips mode (and fires onModeChange so a router can swap the\n// URL). The full-page sibling of AuthDialog (the modal); same login/register idea.\nconst AX_SIGNIN_CSS = `\n.ax-signin { display: grid; grid-template-columns: 1fr 1fr; height: 100%; background: var(--surface-page); }\n.ax-signin__brand { position: relative; border-right: 1px solid var(--border-default); background: var(--surface-panel);\n background-image: var(--dot-grid); background-size: 24px 24px; padding: 40px; display: flex; flex-direction: column; }\n.ax-signin__brandtop { display: flex; align-items: center; gap: 10px; }\n.ax-signin__mid { margin-top: auto; margin-bottom: auto; max-width: 22ch; }\n.ax-signin__quote { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }\n.ax-signin__by { margin: 16px 0 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--text-faint); }\n.ax-signin__form { display: flex; align-items: center; justify-content: center; padding: 40px; }\n.ax-signin__card { width: 100%; max-width: 360px; }\n.ax-signin__mbrand { display: none; }\n.ax-signin__h { margin: 0; font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); color: var(--text-body); }\n.ax-signin__sub { margin: 8px 0 28px; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__fields { display: flex; flex-direction: column; gap: 16px; }\n.ax-signin__row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; }\n.ax-signin__link { font-size: 12px; color: var(--text-muted); text-decoration: none; background: none; border: none; cursor: pointer; font-family: inherit; }\n.ax-signin__link:hover { color: var(--text-body); }\n.ax-signin__submit { margin-top: 22px; }\n.ax-signin__or { display: flex; align-items: center; gap: 12px; margin: 22px 0; }\n.ax-signin__or::before, .ax-signin__or::after { content: \"\"; flex: 1; height: 1px; background: var(--border-default); }\n.ax-signin__or span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; color: var(--text-faint); }\n.ax-signin__sso { display: flex; align-items: center; justify-content: center; gap: 9px; width: 100%; height: 40px; border: 1px solid var(--border-default);\n border-radius: var(--radius-2); background: var(--surface-card); color: var(--text-body); font-family: inherit; font-size: var(--text-sm); cursor: pointer; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__sso:hover { border-color: var(--border-strong); }\n.ax-signin__terms { margin: 16px 0 0; font-size: var(--text-xs); color: var(--text-faint); line-height: var(--leading-snug); text-align: center; }\n.ax-signin__terms a, .ax-signin__terms span.lk { color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }\n.ax-signin__foot { margin-top: 26px; text-align: center; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__footlink { color: var(--text-body); background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--text-sm);\n border-bottom: 1px solid var(--border-strong); padding: 0 0 1px; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__footlink:hover { border-color: var(--text-faint); }\n@media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__mbrand { display: flex; margin-bottom: 26px; } }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-signin-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-signin-css\";\n s.textContent = AX_SIGNIN_CSS;\n document.head.appendChild(s);\n}\n\nconst DEFAULT_COPY = {\n signin: {\n title: \"Sign in\",\n subtitle: \"Welcome back. Use your work email to continue.\",\n submit: \"Continue\",\n switchText: \"No account?\",\n switchCta: \"Create one\",\n },\n signup: {\n title: \"Create account\",\n subtitle: \"Start with your work email — it takes a minute.\",\n submit: \"Create account\",\n switchText: \"Already have an account?\",\n switchCta: \"Sign in\",\n },\n};\n\nexport function SignInPage({\n mode,\n defaultMode = \"signin\",\n onModeChange,\n brand,\n copy,\n email,\n password,\n onEmailChange,\n onPasswordChange,\n onSubmit,\n onForgot,\n ssoLabel = \"Continue with SSO\",\n onSSO,\n terms,\n footer,\n showBrandPanel = true,\n}) {\n const [modeI, setModeI] = useState(defaultMode);\n const m = mode !== undefined ? mode : modeI;\n const isSignup = m === \"signup\";\n const c = { ...DEFAULT_COPY[m], ...(copy && copy[m]) };\n\n const [emailI, setEmailI] = useState(\"\");\n const [pwI, setPwI] = useState(\"\");\n const [confirm, setConfirm] = useState(\"\");\n const [errs, setErrs] = useState({});\n const emailV = email !== undefined ? email : emailI;\n const pwV = password !== undefined ? password : pwI;\n const setEmail = (v) => {\n if (onEmailChange) onEmailChange(v);\n else setEmailI(v);\n if (errs.email) setErrs((e) => ({ ...e, email: undefined }));\n };\n const setPw = (v) => {\n if (onPasswordChange) onPasswordChange(v);\n else setPwI(v);\n if (errs.password) setErrs((e) => ({ ...e, password: undefined }));\n };\n const setConf = (v) => {\n setConfirm(v);\n if (errs.confirm) setErrs((e) => ({ ...e, confirm: undefined }));\n };\n\n const switchMode = () => {\n const next = isSignup ? \"signin\" : \"signup\";\n setErrs({});\n setConfirm(\"\");\n if (onModeChange) onModeChange(next);\n if (mode === undefined) setModeI(next);\n };\n\n const validate = () => {\n const e = {};\n const em = (emailV || \"\").trim();\n if (!em) e.email = \"请输入邮箱\";\n else if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(em)) e.email = \"邮箱格式不正确\";\n if (!pwV) e.password = \"请输入密码\";\n else if (isSignup && pwV.length < 8) e.password = \"密码至少 8 位\";\n if (isSignup) {\n if (!confirm) e.confirm = \"请再次输入密码\";\n else if (confirm !== pwV) e.confirm = \"两次输入的密码不一致\";\n }\n return e;\n };\n\n const submit = (ev) => {\n ev.preventDefault();\n const e = validate();\n setErrs(e);\n if (Object.keys(e).some((k) => e[k])) return;\n onSubmit && onSubmit({ mode: m, email: (emailV || \"\").trim(), password: pwV });\n };\n\n const termsNode =\n terms !== undefined ? (\n terms\n ) : (\n <>\n 注册即代表你同意 <span className=\"lk\">服务条款</span> 与{\" \"}\n <span className=\"lk\">隐私政策</span>。\n </>\n );\n\n return (\n <div className=\"ax-signin\">\n {showBrandPanel ? (\n <aside className=\"ax-signin__brand\">\n <div className=\"ax-signin__brandtop\">{brand || <BrandMark size={24} wordmark />}</div>\n <div className=\"ax-signin__mid\">\n <p className=\"ax-signin__quote\">\n 聊天,\n <br />\n 构建万物\n </p>\n <p className=\"ax-signin__by\">— AGENTAILY</p>\n </div>\n </aside>\n ) : null}\n\n <main className=\"ax-signin__form\">\n <form className=\"ax-signin__card\" onSubmit={submit} noValidate>\n <div className=\"ax-signin__mbrand\">{brand || <BrandMark size={22} wordmark />}</div>\n <h1 className=\"ax-signin__h\">{c.title}</h1>\n <p className=\"ax-signin__sub\">{c.subtitle}</p>\n\n <div className=\"ax-signin__fields\">\n <Input\n label=\"EMAIL\"\n type=\"email\"\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n value={emailV}\n error={errs.email}\n onChange={(e) => setEmail(e.target.value)}\n />\n <div>\n <div className=\"ax-signin__row\">\n <span className=\"ax-label\">PASSWORD</span>\n {!isSignup && onForgot ? (\n <button type=\"button\" className=\"ax-signin__link\" onClick={onForgot}>\n Forgot?\n </button>\n ) : null}\n </div>\n <Input\n type=\"password\"\n autoComplete={isSignup ? \"new-password\" : \"current-password\"}\n placeholder={isSignup ? \"至少 8 位\" : \"••••••••\"}\n value={pwV}\n error={errs.password}\n onChange={(e) => setPw(e.target.value)}\n />\n </div>\n {isSignup ? (\n <Input\n label=\"CONFIRM PASSWORD\"\n type=\"password\"\n autoComplete=\"new-password\"\n placeholder=\"再次输入密码\"\n value={confirm}\n error={errs.confirm}\n onChange={(e) => setConf(e.target.value)}\n />\n ) : null}\n </div>\n\n <div className=\"ax-signin__submit\">\n <Button variant=\"primary\" full type=\"submit\" icon={<Icon name=\"arrow\" size={15} />}>\n {c.submit}\n </Button>\n </div>\n\n {isSignup ? <p className=\"ax-signin__terms\">{termsNode}</p> : null}\n\n {onSSO ? (\n <React.Fragment>\n <div className=\"ax-signin__or\">\n <span>OR</span>\n </div>\n <button type=\"button\" className=\"ax-signin__sso\" onClick={onSSO}>\n <Icon name=\"shield\" size={15} />\n {ssoLabel}\n </button>\n </React.Fragment>\n ) : null}\n\n {footer !== undefined ? (\n footer ? (\n <p className=\"ax-signin__foot\">{footer}</p>\n ) : null\n ) : (\n <p className=\"ax-signin__foot\">\n {c.switchText}{\" \"}\n <button type=\"button\" className=\"ax-signin__footlink\" onClick={switchMode}>\n {c.switchCta}\n </button>\n </p>\n )}\n </form>\n </main>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SignInPage.js","sources":["../../../src/components/auth/SignInPage.jsx"],"sourcesContent":["import React, { useState } from \"react\";\nimport { Input } from \"../inputs/Input.jsx\";\nimport { Button } from \"../buttons/Button.jsx\";\nimport { Icon } from \"../utilities/Icon.jsx\";\nimport { BrandMark } from \"../utilities/BrandMark.jsx\";\nimport { RotatingTagline } from \"../utilities/RotatingTagline.jsx\";\nimport { Spinner } from \"../feedback/Spinner.jsx\";\n\n// SignInPage — full-page auth: a split brand panel (dot-grid + quote) beside a\n// centered card. One component, two modes (signin / signup) — the brand panel,\n// SSO, and card chrome are shared; only the fields, copy, and footer differ.\n// The footer link flips mode (and fires onModeChange so a router can swap the\n// URL). The full-page sibling of AuthDialog (the modal); same login/register idea.\nconst AX_SIGNIN_CSS = `\n.ax-signin { display: grid; grid-template-columns: 1fr 1fr; height: 100%; background: var(--surface-page); }\n.ax-signin__brand { position: relative; border-right: 1px solid var(--border-default); background: var(--surface-panel);\n background-image: var(--dot-grid); background-size: 24px 24px; padding: 40px; display: flex; flex-direction: column; }\n.ax-signin__brandtop { display: flex; align-items: center; gap: 10px; }\n.ax-signin__mid { margin-top: auto; margin-bottom: auto; }\n.ax-signin__quote { font-family: var(--font-display); white-space: nowrap; font-size: clamp(40px, 4.4vw, var(--text-hero)); font-weight: var(--weight-medium); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--text-body); margin: 0; }\n.ax-signin__by { margin: 16px 0 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; color: var(--text-faint); }\n.ax-signin__form { display: flex; align-items: center; justify-content: center; padding: 40px; }\n.ax-signin__card { width: 100%; max-width: 360px; }\n.ax-signin__mbrand { display: none; }\n.ax-signin__mtag { display: none; }\n.ax-signin__mtag .ax-signin__quote { font-size: var(--text-3xl); }\n.ax-signin__h { margin: 0; font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); color: var(--text-body); }\n.ax-signin__sub { margin: 8px 0 28px; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__fields { display: flex; flex-direction: column; gap: 16px; }\n.ax-signin__row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; }\n.ax-signin__link { font-size: 12px; color: var(--text-muted); text-decoration: none; background: none; border: none; cursor: pointer; font-family: inherit; }\n.ax-signin__link:hover { color: var(--text-body); }\n.ax-signin__submit { margin-top: 22px; }\n.ax-signin__error { display: flex; align-items: flex-start; gap: 9px; margin-top: 22px; padding: 10px 12px; border: 1px solid var(--danger); border-radius: var(--radius-2); background: var(--danger-dim); color: var(--danger); font-size: var(--text-sm); line-height: var(--leading-snug); }\n.ax-signin__error svg { flex: none; margin-top: 1px; }\n.ax-signin__submit .ax-spinner { border-top-color: var(--accent-fg); }\n.ax-signin__or { display: flex; align-items: center; gap: 12px; margin: 22px 0; }\n.ax-signin__or::before, .ax-signin__or::after { content: \"\"; flex: 1; height: 1px; background: var(--border-default); }\n.ax-signin__or span { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; color: var(--text-faint); }\n.ax-signin__sso { display: flex; align-items: center; justify-content: center; gap: 9px; width: 100%; height: 40px; border: 1px solid var(--border-default);\n border-radius: var(--radius-2); background: var(--surface-card); color: var(--text-body); font-family: inherit; font-size: var(--text-sm); cursor: pointer; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__sso:hover { border-color: var(--border-strong); }\n.ax-signin__terms { margin: 16px 0 0; font-size: var(--text-xs); color: var(--text-faint); line-height: var(--leading-snug); text-align: center; }\n.ax-signin__terms a, .ax-signin__terms span.lk { color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }\n.ax-signin__foot { margin-top: 26px; text-align: center; font-size: var(--text-sm); color: var(--text-muted); }\n.ax-signin__footlink { color: var(--text-body); background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--text-sm);\n border-bottom: 1px solid var(--border-strong); padding: 0 0 1px; transition: border-color var(--dur-1) var(--ease-out); }\n.ax-signin__footlink:hover { border-color: var(--text-faint); }\n@media (max-width: 860px) { .ax-signin { grid-template-columns: 1fr; } .ax-signin__brand { display: none; } .ax-signin__form { padding: 56px 40px; } .ax-signin__mbrand { display: flex; margin-bottom: 28px; } .ax-signin__mtag { display: block; margin-bottom: 36px; } .ax-signin__h { font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-medium); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-body); margin: 0 0 8px; } .ax-signin__sub { margin: 0 0 32px; color: var(--text-muted); } .ax-signin__fields { gap: 22px; } .ax-signin__submit { margin-top: 32px; } .ax-signin__or { margin: 32px 0; } .ax-signin__foot { margin-top: 28px; } }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-signin-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-signin-css\";\n s.textContent = AX_SIGNIN_CSS;\n document.head.appendChild(s);\n}\n\n// Every user-facing string lives here so nothing is hardcoded in the markup.\n// Defaults are a self-consistent English baseline; pass a `copy` prop (any\n// subset, deep-merged per group) to localize — e.g. a full zh-CN object.\nconst DEFAULT_COPY = {\n signin: {\n title: \"Sign in\",\n subtitle: \"Welcome back. Use your work email to continue.\",\n submit: \"Continue\",\n switchText: \"No account?\",\n switchCta: \"Create one\",\n },\n signup: {\n title: \"Create account\",\n subtitle: \"Start with your work email — it takes a minute.\",\n submit: \"Create account\",\n switchText: \"Already have an account?\",\n switchCta: \"Sign in\",\n },\n labels: {\n email: \"EMAIL\",\n password: \"PASSWORD\",\n confirm: \"CONFIRM PASSWORD\",\n forgot: \"Forgot?\",\n or: \"OR\",\n sso: \"Continue with SSO\",\n },\n placeholders: {\n email: \"you@example.com\",\n password: \"••••••••\",\n passwordNew: \"At least 8 characters\",\n confirm: \"Re-enter your password\",\n },\n errors: {\n emailRequired: \"Enter your email\",\n emailInvalid: \"That email doesn’t look right\",\n passwordRequired: \"Enter your password\",\n passwordShort: \"Password must be at least 8 characters\",\n confirmRequired: \"Re-enter your password\",\n confirmMismatch: \"Passwords don’t match\",\n },\n terms: \"By creating an account you agree to our Terms and Privacy Policy.\",\n};\n\nexport function SignInPage({\n mode,\n defaultMode = \"signin\",\n onModeChange,\n brand,\n copy,\n tagline,\n email,\n password,\n onEmailChange,\n onPasswordChange,\n onSubmit,\n onForgot,\n ssoLabel,\n onSSO,\n terms,\n footer,\n showBrandPanel = true,\n error,\n submitting = false,\n}) {\n const [modeI, setModeI] = useState(defaultMode);\n const m = mode !== undefined ? mode : modeI;\n const isSignup = m === \"signup\";\n const c = { ...DEFAULT_COPY[m], ...(copy && copy[m]) };\n const labels = { ...DEFAULT_COPY.labels, ...(copy && copy.labels) };\n const ph = { ...DEFAULT_COPY.placeholders, ...(copy && copy.placeholders) };\n const err = { ...DEFAULT_COPY.errors, ...(copy && copy.errors) };\n const ssoText = ssoLabel !== undefined ? ssoLabel : labels.sso;\n const taglineProps = { breakAfterPrefix: true, ...tagline };\n\n const [emailI, setEmailI] = useState(\"\");\n const [pwI, setPwI] = useState(\"\");\n const [confirm, setConfirm] = useState(\"\");\n const [errs, setErrs] = useState({});\n const emailV = email !== undefined ? email : emailI;\n const pwV = password !== undefined ? password : pwI;\n const setEmail = (v) => {\n if (onEmailChange) onEmailChange(v);\n else setEmailI(v);\n if (errs.email) setErrs((e) => ({ ...e, email: undefined }));\n };\n const setPw = (v) => {\n if (onPasswordChange) onPasswordChange(v);\n else setPwI(v);\n if (errs.password) setErrs((e) => ({ ...e, password: undefined }));\n };\n const setConf = (v) => {\n setConfirm(v);\n if (errs.confirm) setErrs((e) => ({ ...e, confirm: undefined }));\n };\n\n const switchMode = () => {\n const next = isSignup ? \"signin\" : \"signup\";\n setErrs({});\n setConfirm(\"\");\n if (onModeChange) onModeChange(next);\n if (mode === undefined) setModeI(next);\n };\n\n const validate = () => {\n const e = {};\n const em = (emailV || \"\").trim();\n if (!em) e.email = err.emailRequired;\n else if (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(em)) e.email = err.emailInvalid;\n if (!pwV) e.password = err.passwordRequired;\n else if (isSignup && pwV.length < 8) e.password = err.passwordShort;\n if (isSignup) {\n if (!confirm) e.confirm = err.confirmRequired;\n else if (confirm !== pwV) e.confirm = err.confirmMismatch;\n }\n return e;\n };\n\n const submit = (ev) => {\n ev.preventDefault();\n if (submitting) return;\n const e = validate();\n setErrs(e);\n if (Object.keys(e).some((k) => e[k])) return;\n onSubmit && onSubmit({ mode: m, email: (emailV || \"\").trim(), password: pwV });\n };\n\n const termsNode =\n terms !== undefined ? terms : c.terms !== undefined ? c.terms : DEFAULT_COPY.terms;\n\n return (\n <div className=\"ax-signin\">\n {showBrandPanel ? (\n <aside className=\"ax-signin__brand\">\n <div className=\"ax-signin__brandtop\">\n {brand || <BrandMark size={24} wordmark blink={false} />}\n </div>\n <div className=\"ax-signin__mid\">\n <p className=\"ax-signin__quote\">\n <RotatingTagline {...taglineProps} />\n </p>\n <p className=\"ax-signin__by\">— AGENTAILY</p>\n </div>\n </aside>\n ) : null}\n\n <main className=\"ax-signin__form\">\n <form className=\"ax-signin__card\" onSubmit={submit} noValidate>\n <div className=\"ax-signin__mbrand\">\n {brand || <BrandMark size={22} wordmark blink={false} />}\n </div>\n <div className=\"ax-signin__mtag\">\n <p className=\"ax-signin__quote\">\n <RotatingTagline {...taglineProps} />\n </p>\n </div>\n <h1 className=\"ax-signin__h\">{c.title}</h1>\n <p className=\"ax-signin__sub\">{c.subtitle}</p>\n\n <div className=\"ax-signin__fields\">\n <Input\n label={labels.email}\n type=\"email\"\n autoComplete=\"email\"\n placeholder={ph.email}\n value={emailV}\n error={errs.email}\n onChange={(e) => setEmail(e.target.value)}\n />\n <div>\n <div className=\"ax-signin__row\">\n <span className=\"ax-label\">{labels.password}</span>\n {!isSignup && onForgot ? (\n <button type=\"button\" className=\"ax-signin__link\" onClick={onForgot}>\n {labels.forgot}\n </button>\n ) : null}\n </div>\n <Input\n type=\"password\"\n autoComplete={isSignup ? \"new-password\" : \"current-password\"}\n placeholder={isSignup ? ph.passwordNew : ph.password}\n value={pwV}\n error={errs.password}\n onChange={(e) => setPw(e.target.value)}\n />\n </div>\n {isSignup ? (\n <Input\n label={labels.confirm}\n type=\"password\"\n autoComplete=\"new-password\"\n placeholder={ph.confirm}\n value={confirm}\n error={errs.confirm}\n onChange={(e) => setConf(e.target.value)}\n />\n ) : null}\n </div>\n\n {error ? (\n <div className=\"ax-signin__error\" role=\"alert\">\n <Icon name=\"warn\" size={15} />\n <span>{error}</span>\n </div>\n ) : null}\n\n <div className=\"ax-signin__submit\">\n <Button\n variant=\"primary\"\n full\n type=\"submit\"\n disabled={submitting}\n aria-busy={submitting || undefined}\n icon={submitting ? <Spinner size=\"sm\" /> : <Icon name=\"arrow\" size={15} />}\n >\n {c.submit}\n </Button>\n </div>\n\n {isSignup ? <p className=\"ax-signin__terms\">{termsNode}</p> : null}\n\n {onSSO ? (\n <React.Fragment>\n <div className=\"ax-signin__or\">\n <span>{labels.or}</span>\n </div>\n <button type=\"button\" className=\"ax-signin__sso\" onClick={onSSO}>\n <Icon name=\"shield\" size={15} />\n {ssoText}\n </button>\n </React.Fragment>\n ) : null}\n\n {footer !== undefined ? (\n footer ? (\n <p className=\"ax-signin__foot\">{footer}</p>\n ) : null\n ) : (\n <p className=\"ax-signin__foot\">\n {c.switchText}{\" \"}\n <button type=\"button\" className=\"ax-signin__footlink\" onClick={switchMode}>\n {c.switchCta}\n </button>\n </p>\n )}\n </form>\n </main>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,15 +1,84 @@
1
1
  /**
2
2
  * Fullscreen integration modal: connect a DeepSeek key (conversation engine) and
3
- * a Feishu Bitable (data sink). Self-persists to localStorage, reports a 0/2
4
- * readiness state, and gates Save until both connect. Composes SecretField,
5
- * StatusPill, TestRow, HelpSteps. The monthly usage-cap block is opt-in.
3
+ * a Feishu Bitable (data sink). Reports a 0/2 readiness state and gates Save until
4
+ * both connect. Composes SecretField, StatusPill, TestRow, HelpSteps. The monthly
5
+ * usage-cap block is opt-in.
6
+ *
7
+ * Two storage modes:
8
+ * - **Uncontrolled (default):** self-persists the whole config to localStorage —
9
+ * the legacy behavior, unchanged when none of the seam props below are passed.
10
+ * - **Controlled / backend-wired:** pass `value` + `onChange` to hold the config
11
+ * yourself (no localStorage writes), `onSave`/`onTest` to reach a real backend,
12
+ * `readiness` to reflect server-known connection state, and `masked` so stored
13
+ * secrets echo masked and are never re-submitted.
6
14
  */
15
+ export interface IntegrationConfig {
16
+ /** DeepSeek API key. Empty when masked + untouched (caller keeps the stored one). */
17
+ dsKey: string;
18
+ /** Conversation model id, e.g. "deepseek-chat" | "deepseek-reasoner". */
19
+ dsModel: string;
20
+ /** Monthly usage-cap toggle. */
21
+ capOn: boolean;
22
+ /** Monthly cap amount (digits-only string). */
23
+ cap: string;
24
+ /** DeepSeek connection status. */
25
+ dsStatus: "idle" | "testing" | "ok" | "error";
26
+ /** DeepSeek test result line. */
27
+ dsResult: string;
28
+ /** Feishu app id (public). */
29
+ appId: string;
30
+ /** Feishu app secret. Empty when masked + untouched. */
31
+ secret: string;
32
+ /** Feishu Bitable share URL (app_token + table auto-parsed). */
33
+ link: string;
34
+ /** Feishu connection status. */
35
+ fsStatus: "idle" | "testing" | "ok" | "error";
36
+ /** Feishu test result line. */
37
+ fsResult: string;
38
+ /** Whether the last config was saved. */
39
+ saved: boolean;
40
+ }
7
41
  export interface IntegrationSettingsProps {
8
42
  /** Close handler (Esc + the × button + overlay). */
9
43
  onClose?: () => void;
10
44
  /** Show the DeepSeek monthly usage-cap warning + toggle. @default true */
11
45
  showUsageCap?: boolean;
12
- /** localStorage key for the persisted config. @default "agentaily.integrations.v1" */
46
+ /** localStorage key for the persisted config (uncontrolled mode only). @default "agentaily.integrations.v1" */
13
47
  storageKey?: string;
48
+ /**
49
+ * Controlled config. When provided, this is the source of truth — the modal
50
+ * stops reading/writing localStorage and every edit flows out via `onChange`.
51
+ * Omit for the self-persisting default.
52
+ */
53
+ value?: IntegrationConfig;
54
+ /** Fires with the next full config on every edit/test (controlled mode). */
55
+ onChange?: (next: IntegrationConfig) => void;
56
+ /**
57
+ * Persist handler. Receives the current config (masked secrets stay empty —
58
+ * treat empty as "keep the stored one"). Return a Promise: Save is disabled and
59
+ * spins while it's pending, marks saved on resolve, and stays dirty on reject.
60
+ * When omitted, Save just flips the in-component saved state.
61
+ */
62
+ onSave?: (value: IntegrationConfig) => void | Promise<void>;
63
+ /**
64
+ * Connection probe. `which` is "deepseek" | "feishu"; return
65
+ * `Promise<{ ok, message }>` to drive that card's StatusPill + TestRow. When
66
+ * omitted, a built-in mock probe runs (validates key shape / link parsing).
67
+ */
68
+ onTest?: (which: "deepseek" | "feishu") => Promise<{ ok: boolean; message?: string }>;
69
+ /**
70
+ * External readiness override. When set, these flags (not the in-app test
71
+ * status) decide the 0/2 rail, Save gating, and the green pills — e.g. show
72
+ * "connected" for credentials already verified server-side. Live testing/error
73
+ * transitions still surface on the pill.
74
+ */
75
+ readiness?: { deepseek?: boolean; feishu?: boolean };
76
+ /**
77
+ * Masked-echo flags for already-stored secrets. When `deepseek`/`feishu` is
78
+ * true and the field is untouched, it renders a masked placeholder, keeps its
79
+ * value empty, and still counts as "present" for testing — so the mask is never
80
+ * sent back as a new value. Typing overrides the mask for that field.
81
+ */
82
+ masked?: { deepseek?: boolean; feishu?: boolean };
14
83
  }
15
84
  export declare function IntegrationSettings(props: IntegrationSettingsProps): JSX.Element;