ab-ecosystem-sso 0.1.17 → 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 +161 -3
- package/dist/ab-ecosystem-sso.cjs +26 -26
- package/dist/ab-ecosystem-sso.js +3740 -3435
- package/dist/index.d.ts +84 -3
- package/dist/style.css +1 -1
- package/package.json +1 -1
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.
|
|
@@ -97,6 +101,41 @@ import { Signup, ForgotPassword } from "ab-ecosystem-sso";
|
|
|
97
101
|
<ForgotPassword projectId="…" onBackToLogin={() => navigate("/login")} />
|
|
98
102
|
```
|
|
99
103
|
|
|
104
|
+
`Signup` loads the project's currencies from `GET /currency` and offers only those. Its country dropdown uses a bundled default list; pass **`countries`** (an array of `{ code, name }`) to replace it:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<Signup
|
|
108
|
+
projectId="…"
|
|
109
|
+
countries={[
|
|
110
|
+
{ code: "US", name: "United States" },
|
|
111
|
+
{ code: "IN", name: "India" },
|
|
112
|
+
]}
|
|
113
|
+
onSuccess={handleAuth}
|
|
114
|
+
/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Password reset.** `ForgotPassword` has two steps. With no token it shows the request-a-link form (emails a reset link). When the user returns via that link, extract the token from the URL and pass it as **`resetToken`** — the component then renders the reset step (new password + confirm + captcha) and POSTs to `/auth/reset-password`:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// e.g. reset link is /reset-password?token=abc123
|
|
121
|
+
const token = new URLSearchParams(location.search).get("token") ?? undefined;
|
|
122
|
+
|
|
123
|
+
<ForgotPassword
|
|
124
|
+
projectId="…"
|
|
125
|
+
resetToken={token}
|
|
126
|
+
onResetSuccess={() => navigate("/login")}
|
|
127
|
+
onBackToLogin={() => navigate("/login")}
|
|
128
|
+
/>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The new password must be at least 8 characters and match the confirmation. On success it shows a confirmation and calls `onResetSuccess`. Extracting the token is the host's responsibility (the link format is yours to define).
|
|
132
|
+
|
|
133
|
+
| `ForgotPassword` prop | Type | Notes |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| `resetToken` | `string` | Present ⇒ render the reset-password step instead of request-a-link. |
|
|
136
|
+
| `onResetSuccess` | `() => void` | Fires after the password is reset. |
|
|
137
|
+
| `onBackToLogin` | `() => void` | Back-to-login link (both steps). |
|
|
138
|
+
|
|
100
139
|
### `VerifyEmail`
|
|
101
140
|
|
|
102
141
|
The email-verification screen for a `/verify/:token` route — the link you email to new users. It shows a captcha (site key from the project's auth-config), then posts the captcha + token to `/auth/verify-email`.
|
|
@@ -142,8 +181,9 @@ On success it calls `onVerified` (send the user to login) and shows a **Continue
|
|
|
142
181
|
| `onSuccess` | `({ token, user }) => void` | On successful auth. |
|
|
143
182
|
| `onError` | `(error: Error) => void` | On failure. |
|
|
144
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). |
|
|
145
185
|
|
|
146
|
-
Per-screen extras: `Login` adds `onForgotPassword` / `onSignupClick`; `Signup` adds `onLoginClick`; `ForgotPassword` adds `onBackToLogin`; `VerifyEmail` adds `token` / `onVerified` / `onLoginClick` / `onSignupClick` (and has no `onSuccess`, since no session is created).
|
|
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).
|
|
147
187
|
|
|
148
188
|
---
|
|
149
189
|
|
|
@@ -236,8 +276,10 @@ const [open, setOpen] = useState(false);
|
|
|
236
276
|
| `token` | `string` | Bearer token. |
|
|
237
277
|
| `apiBaseUrl` | `string` | Optional API origin. |
|
|
238
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). |
|
|
239
280
|
| `syncUrlHash` | `boolean` | Mirror open state + active section in the URL hash. Default `true`. See [URL hash & refresh](#url-hash--refresh). |
|
|
240
281
|
| `title` | `string` | Modal heading. Default `"Settings"`. |
|
|
282
|
+
| `mode` | `"light" \| "dark"` | Colour scheme. Default `"light"`. See [Light & dark mode](#light--dark-mode). |
|
|
241
283
|
| `data` | `Partial<AccountSettingsData>` | Override sample data (non-API mode / previews). |
|
|
242
284
|
| `onLogout` | `() => void` | Sign-out action (closes the modal, then runs this). |
|
|
243
285
|
| `onSaveProfile` | `(profile) => void` | Fires alongside the profile save. |
|
|
@@ -247,6 +289,45 @@ const [open, setOpen] = useState(false);
|
|
|
247
289
|
|
|
248
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.
|
|
249
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
|
+
|
|
250
331
|
### URL hash & refresh
|
|
251
332
|
|
|
252
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}`.
|
|
@@ -294,7 +375,7 @@ onClick={() => logout({
|
|
|
294
375
|
await logoutApi("your-project-id", token, "https://your-api");
|
|
295
376
|
```
|
|
296
377
|
|
|
297
|
-
`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.
|
|
298
379
|
|
|
299
380
|
---
|
|
300
381
|
|
|
@@ -354,6 +435,7 @@ import { TwoFactorSetup } from "ab-ecosystem-sso";
|
|
|
354
435
|
projectId="your-project-id"
|
|
355
436
|
token={token}
|
|
356
437
|
apiBaseUrl="https://your-api"
|
|
438
|
+
mode="dark" // optional — "light" (default) or "dark"
|
|
357
439
|
onEnabled={() => …}
|
|
358
440
|
onDisabled={() => …}
|
|
359
441
|
onError={(err) => …}
|
|
@@ -372,6 +454,58 @@ import { TwoFactorSetup } from "ab-ecosystem-sso";
|
|
|
372
454
|
|
|
373
455
|
---
|
|
374
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
|
+
|
|
375
509
|
## Theming
|
|
376
510
|
|
|
377
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:
|
|
@@ -389,6 +523,28 @@ The UI reads `--aas-*` CSS custom properties, so you can retheme without touchin
|
|
|
389
523
|
}
|
|
390
524
|
```
|
|
391
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
|
+
|
|
392
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.
|
|
393
549
|
|
|
394
550
|
---
|
|
@@ -401,6 +557,8 @@ Beyond the components, these functions are exported for building custom UIs:
|
|
|
401
557
|
| --- | --- |
|
|
402
558
|
| `getAuthConfig(projectId, apiBaseUrl?)` | `GET /auth-config` → `AuthConfig` (captcha, branding, card layout; no token) |
|
|
403
559
|
| `verifyEmailApi(projectId, { token, captchaToken }, apiBaseUrl?)` | `POST /auth/verify-email` |
|
|
560
|
+
| `resetPasswordApi(projectId, { token, password, captchaToken }, apiBaseUrl?)` | `POST /auth/reset-password` |
|
|
561
|
+
| `getCurrenciesApi(projectId, apiBaseUrl?)` | `GET /currency` → `ProjectCurrency[]` |
|
|
404
562
|
| `getUser(projectId, token, apiBaseUrl?)` | `GET /me` (raw envelope) |
|
|
405
563
|
| `getUserProfile(projectId, token, apiBaseUrl?)` | `GET /me` → `MappedProfile` |
|
|
406
564
|
| `getUserWallet(projectId, token, apiBaseUrl?)` | `GET /me` → `UserWallets` |
|
|
@@ -419,7 +577,7 @@ Everything is fully typed. Notable exports:
|
|
|
419
577
|
|
|
420
578
|
```ts
|
|
421
579
|
import type {
|
|
422
|
-
LoginProps, SignupProps, ForgotPasswordProps, VerifyEmailProps, AuthClassNames,
|
|
580
|
+
LoginProps, SignupProps, ForgotPasswordProps, VerifyEmailProps, AuthClassNames, AuthMode,
|
|
423
581
|
AccountSettingsProps, AccountSettingsSection, AccountSettingsData, ProfileFormValues,
|
|
424
582
|
OpenAccountSettingsOptions, AccountSettingsHandle,
|
|
425
583
|
AuthConfig, CaptchaConfig, CardLayoutConfig,
|