ab-ecosystem-sso 0.1.18 → 0.1.19

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
@@ -4,6 +4,7 @@ Drop-in React auth & account UI for the partner-ecosystem **Auth-as-a-Service**
4
4
 
5
5
  - ⚛️ React 18 / 19, shipped as ESM + CJS with full TypeScript types.
6
6
  - 🎨 Self-contained, theme-aware CSS that **won't leak into or be overridden by** your app.
7
+ - 🌗 Built-in **light and dark mode** — per component, via a single `mode` prop.
7
8
  - 🔌 Use it declaratively (`<AccountSettings />`) or imperatively (`openAccountSettings()`).
8
9
  - 🧩 Every network call also available as a plain function for custom UIs.
9
10
 
@@ -21,6 +22,7 @@ Drop-in React auth & account UI for the partner-ecosystem **Auth-as-a-Service**
21
22
  - [Fetching user data](#fetching-user-data) — `getUserProfile` · `getUserWallet` · `getUserCreditWallet` · `getUserSubscription`
22
23
  - [Two-factor setup](#two-factor-setup)
23
24
  - [Hooks](#hooks)
25
+ - [Light & dark mode](#light--dark-mode)
24
26
  - [Theming](#theming)
25
27
  - [API reference](#api-reference)
26
28
  - [TypeScript](#typescript)
@@ -84,6 +86,8 @@ function LoginPage() {
84
86
  }
85
87
  ```
86
88
 
89
+ The password field has a **show/hide (eye) toggle** — it always starts masked, and the same control appears on `Signup`, the `ForgotPassword` reset step, and the Account Settings change-password modal.
90
+
87
91
  `Login` first fetches the project's `auth-config` (captcha provider/site-key, branding, which links to show) and renders accordingly. If the account has **2FA** enabled it automatically shows a **segmented 6-digit OTP** step (with captcha when the project requires it), and only calls `onSuccess` after verification succeeds.
88
92
 
89
93
  **Magic-link sign-in** works automatically: point your magic link at `/login#<access_token>` (a bare `/login#access_token=<token>` is also accepted). On mount, `Login` reads the token from the URL hash, validates it with a `/me` call, and — if valid — strips it from the URL and fires `onSuccess({ token, user })`, exactly like a password sign-in. While it validates, a loading state shows; if the token is invalid or expired, it falls back to the normal sign-in form with a short notice (and calls `onError`). No extra wiring needed beyond routing the link to your login page.
@@ -177,6 +181,7 @@ On success it calls `onVerified` (send the user to login) and shows a **Continue
177
181
  | `onSuccess` | `({ token, user }) => void` | On successful auth. |
178
182
  | `onError` | `(error: Error) => void` | On failure. |
179
183
  | `classNames` | `AuthClassNames` | Per-element class overrides: `card`, `input`, `label`, `button`, `link`, `captcha`, `error`, … |
184
+ | `mode` | `"light" \| "dark"` | Colour scheme. Default `"light"`. See [Light & dark mode](#light--dark-mode). |
180
185
 
181
186
  Per-screen extras: `Login` adds `onForgotPassword` / `onSignupClick`; `Signup` adds `onLoginClick` / `countries`; `ForgotPassword` adds `onBackToLogin` / `resetToken` / `onResetSuccess`; `VerifyEmail` adds `token` / `onVerified` / `onLoginClick` / `onSignupClick` (and has no `onSuccess`, since no session is created).
182
187
 
@@ -271,8 +276,10 @@ const [open, setOpen] = useState(false);
271
276
  | `token` | `string` | Bearer token. |
272
277
  | `apiBaseUrl` | `string` | Optional API origin. |
273
278
  | `initialSection` | `"general" \| "account" \| "wallet" \| "plans" \| "credits"` | First section shown. Default `"general"`. |
279
+ | `customComponents` | `AccountSettingsCustomPanel[]` | Add your own panels to the modal. See [Custom panels](#custom-panels). |
274
280
  | `syncUrlHash` | `boolean` | Mirror open state + active section in the URL hash. Default `true`. See [URL hash & refresh](#url-hash--refresh). |
275
281
  | `title` | `string` | Modal heading. Default `"Settings"`. |
282
+ | `mode` | `"light" \| "dark"` | Colour scheme. Default `"light"`. See [Light & dark mode](#light--dark-mode). |
276
283
  | `data` | `Partial<AccountSettingsData>` | Override sample data (non-API mode / previews). |
277
284
  | `onLogout` | `() => void` | Sign-out action (closes the modal, then runs this). |
278
285
  | `onSaveProfile` | `(profile) => void` | Fires alongside the profile save. |
@@ -282,6 +289,45 @@ const [open, setOpen] = useState(false);
282
289
 
283
290
  > In live mode the modal performs the API calls itself (save profile, change password, subscribe, recharge, etc.); the callbacks are observation hooks, not required wiring.
284
291
 
292
+ ### Custom panels
293
+
294
+ Add your own screens to the modal via `customComponents` — an array of `AccountSettingsCustomPanel`. Each entry renders a **trigger** (in the sidebar, pinned to the sidebar bottom, or in the header next to the close button) and shows its `component` in the modal body when activated.
295
+
296
+ ```tsx
297
+ <AccountSettings
298
+ open={open}
299
+ onOpenChange={setOpen}
300
+ projectId="your-project-id"
301
+ token={token}
302
+ customComponents={[
303
+ {
304
+ id: "referrals",
305
+ location: "sidebar", // "sidebar" | "sidebar-bottom" | "header"
306
+ label: "Referrals",
307
+ icon: <GiftIcon />, // any node (an inline SVG works best)
308
+ component: <ReferralsPanel />, // rendered in the modal body
309
+ },
310
+ {
311
+ id: "help",
312
+ location: "header",
313
+ label: "Help",
314
+ icon: <HelpIcon />,
315
+ component: <HelpCenter />,
316
+ },
317
+ ]}
318
+ />
319
+ ```
320
+
321
+ | Field | Type | Notes |
322
+ | --- | --- | --- |
323
+ | `id` | `string` | **Required**, unique. Tracks which panel is active. |
324
+ | `location` | `"sidebar" \| "sidebar-bottom" \| "header"` | Where the trigger appears. Default `"sidebar"`. |
325
+ | `label` | `string` | Sidebar text; header button tooltip + `aria-label`. |
326
+ | `icon` | `ReactNode` | Optional trigger icon. |
327
+ | `component` | `ReactNode` | Rendered in the body when the panel is active. |
328
+
329
+ The same `customComponents` array works with the imperative `openAccountSettings({ customComponents: [...] })`. Panels are host-owned — they don't affect the URL hash, and activating one doesn't run the built-in section's API calls.
330
+
285
331
  ### URL hash & refresh
286
332
 
287
333
  While the modal is open it reflects the active section in the URL hash as `#settings-<section>` (e.g. `#settings-general`, `#settings-plans`), updating as the user switches tabs and clearing it on close. This means a **page refresh can re-open the modal on the same section**. It uses `history.replaceState`, so it never adds browser-history entries and never clobbers an unrelated `#anchor`. Opt out with `syncUrlHash={false}`.
@@ -329,7 +375,7 @@ onClick={() => logout({
329
375
  await logoutApi("your-project-id", token, "https://your-api");
330
376
  ```
331
377
 
332
- `logout` POSTs to `/auth/logout` and **always runs `onSuccess` once the request settles** — so the user is logged out client-side even if the server call fails (`onError` fires for observability). Inside a `SessionProvider`, `<LogoutButton />` with no props uses the provider's logout instead.
378
+ `<LogoutButton />` also takes `mode="dark"`. `logout` POSTs to `/auth/logout` and **always runs `onSuccess` once the request settles** — so the user is logged out client-side even if the server call fails (`onError` fires for observability). Inside a `SessionProvider`, `<LogoutButton />` with no props uses the provider's logout instead.
333
379
 
334
380
  ---
335
381
 
@@ -389,6 +435,7 @@ import { TwoFactorSetup } from "ab-ecosystem-sso";
389
435
  projectId="your-project-id"
390
436
  token={token}
391
437
  apiBaseUrl="https://your-api"
438
+ mode="dark" // optional — "light" (default) or "dark"
392
439
  onEnabled={() => …}
393
440
  onDisabled={() => …}
394
441
  onError={(err) => …}
@@ -407,6 +454,58 @@ import { TwoFactorSetup } from "ab-ecosystem-sso";
407
454
 
408
455
  ---
409
456
 
457
+ ## Light & dark mode
458
+
459
+ Every component renders in **light mode by default**. Pass `mode="dark"` to switch:
460
+
461
+ ```tsx
462
+ <Login projectId="…" mode="dark" onSuccess={handleAuth} />
463
+ <Signup projectId="…" mode="dark" onLoginClick={() => navigate("/login")} />
464
+ <ForgotPassword projectId="…" mode="dark" />
465
+ <VerifyEmail projectId="…" mode="dark" />
466
+ <AccountSettings open={open} onOpenChange={setOpen} mode="dark" />
467
+ <TwoFactorSetup projectId="…" token={token} mode="dark" />
468
+ <LogoutButton mode="dark" />
469
+
470
+ openAccountSettings({ projectId, token, mode: "dark" });
471
+ ```
472
+
473
+ `mode` is **per component**, not global — each component root carries a
474
+ `data-aas-theme="light" | "dark"` attribute and the palette inherits from there, so a dark
475
+ login card and a light settings modal can coexist on the same page.
476
+
477
+ Follow your app's own theme by passing the value you already track:
478
+
479
+ ```tsx
480
+ const { theme } = useTheme(); // your app's theme state
481
+ <Login projectId="…" mode={theme === "dark" ? "dark" : "light"} />
482
+ ```
483
+
484
+ To follow the OS setting instead:
485
+
486
+ ```tsx
487
+ import type { AuthMode } from "ab-ecosystem-sso";
488
+
489
+ const [mode, setMode] = useState<AuthMode>(
490
+ () => (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
491
+ );
492
+
493
+ useEffect(() => {
494
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
495
+ const onChange = (e: MediaQueryListEvent) => setMode(e.matches ? "dark" : "light");
496
+ mq.addEventListener("change", onChange);
497
+ return () => mq.removeEventListener("change", onChange);
498
+ }, []);
499
+
500
+ <Login projectId="…" mode={mode} />
501
+ ```
502
+
503
+ > The package deliberately does **not** read `prefers-color-scheme` itself — an auth screen
504
+ > that silently disagrees with the host app's theme is worse than one that follows it, and only
505
+ > your app knows which theme it's in.
506
+
507
+ ---
508
+
410
509
  ## Theming
411
510
 
412
511
  The UI reads `--aas-*` CSS custom properties, so you can retheme without touching the components. Override them on `:root` (or any ancestor) after importing the stylesheet:
@@ -424,6 +523,28 @@ The UI reads `--aas-*` CSS custom properties, so you can retheme without touchin
424
523
  }
425
524
  ```
426
525
 
526
+ Dark mode is the same variables redeclared on `[data-aas-theme='dark']`, so retheming dark is symmetrical — override there and light stays untouched:
527
+
528
+ ```css
529
+ [data-aas-theme="dark"] {
530
+ --aas-background: oklch(0.16 0.01 265);
531
+ --aas-card: oklch(0.21 0.01 265);
532
+ --aas-primary: oklch(0.72 0.17 265);
533
+ --aas-primary-foreground: oklch(0.16 0.01 265);
534
+ }
535
+ ```
536
+
537
+ Beyond the shadcn-style tokens above, a few extra ones exist so dark mode is a pure variable
538
+ swap — override them to fine-tune either palette:
539
+
540
+ | Token | Used for |
541
+ | --- | --- |
542
+ | `--aas-sidebar` | Account Settings sidebar panel. |
543
+ | `--aas-hover` / `--aas-hover-strong` | Nav/icon-button hover and active fills. |
544
+ | `--aas-overlay` / `--aas-overlay-strong` | The blur overlays on loading cards and settings sections. |
545
+ | `--aas-success` / `--aas-success-bg` | "Verified" badges and credit amounts. |
546
+ | `--aas-switch-track`, `--aas-switch-knob`, `--aas-switch-knob-on` | The settings toggle switch. |
547
+
427
548
  The styles are theme-aware and scoped to the components — they won't affect the rest of your app, and your app's global resets can't override them.
428
549
 
429
550
  ---
@@ -456,7 +577,7 @@ Everything is fully typed. Notable exports:
456
577
 
457
578
  ```ts
458
579
  import type {
459
- LoginProps, SignupProps, ForgotPasswordProps, VerifyEmailProps, AuthClassNames,
580
+ LoginProps, SignupProps, ForgotPasswordProps, VerifyEmailProps, AuthClassNames, AuthMode,
460
581
  AccountSettingsProps, AccountSettingsSection, AccountSettingsData, ProfileFormValues,
461
582
  OpenAccountSettingsOptions, AccountSettingsHandle,
462
583
  AuthConfig, CaptchaConfig, CardLayoutConfig,