@saas-support/react 0.7.6 → 0.8.1

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
@@ -1,6 +1,6 @@
1
1
  # @saas-support/react
2
2
 
3
- Unified embeddable SDK for [SaaS Support](https://saas-support.com) — drop-in auth, billing, and reporting components for your React app. Zero external dependencies. Shadow DOM style isolation. Full theming support.
3
+ Embeddable auth SDK for [SaaS Support](https://saas-support.com) — drop-in sign-in, user menu, and settings components. Shadow DOM style isolation. Full theming support.
4
4
 
5
5
  ```bash
6
6
  npm install @saas-support/react
@@ -9,12 +9,12 @@ npm install @saas-support/react
9
9
  ## Quick Start
10
10
 
11
11
  ```tsx
12
- import { SaaSProvider, SignIn, PricingTable } from '@saas-support/react/react'
12
+ import { SaaSProvider, SignIn, UserButton } from '@saas-support/react/react'
13
13
 
14
14
  function App() {
15
15
  return (
16
- <SaaSProvider publishableKey="pub_live_..." apiKey="sk_live_...">
17
- <SignIn />
16
+ <SaaSProvider publishableKey="pub_live_..." baseUrl="https://api.example.com/v1">
17
+ <UserButton />
18
18
  </SaaSProvider>
19
19
  )
20
20
  }
@@ -24,7 +24,7 @@ function App() {
24
24
 
25
25
  | Import | Use |
26
26
  |--------|-----|
27
- | `@saas-support/react` | Vanilla JS clients, types, no React dependency |
27
+ | `@saas-support/react` | Vanilla JS client and types, no React dependency |
28
28
  | `@saas-support/react/react` | React components, hooks, provider |
29
29
 
30
30
  ---
@@ -37,8 +37,7 @@ Wrap your app in `<SaaSProvider>` to initialize the SDK:
37
37
  import { SaaSProvider } from '@saas-support/react/react'
38
38
 
39
39
  <SaaSProvider
40
- publishableKey="pub_live_..." // Auth (end-user facing)
41
- apiKey="sk_live_..." // Billing & Reports (server-side or trusted client)
40
+ publishableKey="pub_live_..."
42
41
  baseUrl="https://api.saas-support.com/v1"
43
42
  appearance={{ baseTheme: 'dark' }}
44
43
  >
@@ -49,7 +48,7 @@ import { SaaSProvider } from '@saas-support/react/react'
49
48
  | Prop | Type | Required | Description |
50
49
  |------|------|----------|-------------|
51
50
  | `publishableKey` | `string` | No* | Publishable key for auth operations |
52
- | `apiKey` | `string` | No* | API key for billing/report operations |
51
+ | `apiKey` | `string` | No* | API key for server-side operations |
53
52
  | `baseUrl` | `string` | No | API base URL override |
54
53
  | `appearance` | `Appearance` | No | Global theme configuration |
55
54
 
@@ -57,479 +56,205 @@ import { SaaSProvider } from '@saas-support/react/react'
57
56
 
58
57
  ---
59
58
 
60
- ## Authentication
61
-
62
- ### Components
59
+ ## Components
63
60
 
64
- #### `<SignIn />`
61
+ ### `<SignIn />`
65
62
 
66
- Pre-built sign-in form with email/password, OAuth buttons, and MFA support.
63
+ Combined sign-in and sign-up form with OAuth support, MFA, and a built-in mode toggle.
67
64
 
68
65
  ```tsx
69
66
  import { SignIn } from '@saas-support/react/react'
70
67
 
71
68
  <SignIn
72
- signUpUrl="/register"
69
+ initialMode="signIn"
73
70
  afterSignInUrl="/dashboard"
74
- onSignUp={() => navigate('/register')}
75
- />
76
- ```
77
-
78
- | Prop | Type | Default | Description |
79
- |------|------|---------|-------------|
80
- | `signUpUrl` | `string` | — | URL for the sign-up link |
81
- | `afterSignInUrl` | `string` | — | Redirect URL after sign-in |
82
- | `onSignUp` | `() => void` | — | Callback when sign-up link is clicked |
83
- | `appearance` | `Appearance` | — | Theme overrides |
84
-
85
- #### `<SignUp />`
86
-
87
- Registration form with email, password, confirm password, and OAuth.
88
-
89
- ```tsx
90
- import { SignUp } from '@saas-support/react/react'
91
-
92
- <SignUp
93
- signInUrl="/login"
94
71
  afterSignUpUrl="/onboarding"
95
- onSignIn={() => navigate('/login')}
96
72
  />
97
73
  ```
98
74
 
99
75
  | Prop | Type | Default | Description |
100
76
  |------|------|---------|-------------|
101
- | `signInUrl` | `string` | | URL for the sign-in link |
77
+ | `initialMode` | `'signIn' \| 'signUp'` | `'signIn'` | Starting mode |
78
+ | `afterSignInUrl` | `string` | — | Redirect URL after sign-in |
102
79
  | `afterSignUpUrl` | `string` | — | Redirect URL after sign-up |
103
- | `onSignIn` | `() => void` | — | Callback when sign-in link is clicked |
104
- | `appearance` | `Appearance` | — | Theme overrides |
105
-
106
- #### `<UserButton />`
107
-
108
- Avatar dropdown showing the signed-in user with a sign-out option.
109
-
110
- ```tsx
111
- import { UserButton } from '@saas-support/react/react'
112
-
113
- <UserButton afterSignOutUrl="/login" />
114
- ```
115
-
116
- | Prop | Type | Default | Description |
117
- |------|------|---------|-------------|
118
- | `afterSignOutUrl` | `string` | — | Redirect URL after sign-out |
119
- | `appearance` | `Appearance` | — | Theme overrides |
120
-
121
- #### `<UserProfile />`
122
-
123
- Read-only profile card showing email, provider, and verification status.
124
-
125
- ```tsx
126
- import { UserProfile } from '@saas-support/react/react'
127
-
128
- <UserProfile />
129
- ```
130
-
131
- #### `<OrgSwitcher />`
132
-
133
- Dropdown to switch between the user's organizations.
134
-
135
- ```tsx
136
- import { OrgSwitcher } from '@saas-support/react/react'
137
-
138
- <OrgSwitcher onOrgChange={(org) => console.log('Switched to', org.name)} />
139
- ```
140
-
141
- | Prop | Type | Default | Description |
142
- |------|------|---------|-------------|
143
- | `onOrgChange` | `(org: Org) => void` | — | Callback when organization changes |
144
80
  | `appearance` | `Appearance` | — | Theme overrides |
145
81
 
146
- ### Hooks
147
-
148
- ```tsx
149
- import { useAuth, useUser, useSignIn, useSignUp, useOrg } from '@saas-support/react/react'
150
- ```
151
-
152
- #### `useAuth()`
153
-
154
- ```tsx
155
- const { isLoaded, isSignedIn, user, signOut, getToken } = useAuth()
156
- ```
157
-
158
- #### `useUser()`
82
+ Features:
83
+ - Email/password sign-in and sign-up
84
+ - OAuth (Google, GitHub) when enabled in project settings
85
+ - MFA verification (6-digit code)
86
+ - Built-in toggle between sign-in and sign-up modes
87
+ - Password validation against project settings
159
88
 
160
- ```tsx
161
- const { user, isLoaded } = useUser()
162
- ```
89
+ ### `<UserButton />`
163
90
 
164
- #### `useSignIn()`
91
+ Avatar button that opens a dropdown with org switcher, settings, and sign-out.
165
92
 
166
93
  ```tsx
167
- const { signIn, signInWithOAuth, submitMfaCode, isLoading, error, setError } = useSignIn()
168
-
169
- await signIn('user@example.com', 'password')
170
- await signInWithOAuth('google')
171
- await submitMfaCode(mfaToken, '123456')
172
- ```
173
-
174
- #### `useSignUp()`
175
-
176
- ```tsx
177
- const { signUp, isLoading, error, setError } = useSignUp()
178
-
179
- await signUp('user@example.com', 'password')
180
- ```
181
-
182
- #### `useOrg()`
183
-
184
- ```tsx
185
- const { orgs, selectedOrg, members, isLoading, selectOrg, createOrg, refresh } = useOrg()
186
-
187
- await selectOrg('org_123')
188
- await createOrg('My Team', 'my-team')
189
- ```
190
-
191
- ---
192
-
193
- ## Billing
194
-
195
- ### Components
196
-
197
- #### `<PricingTable />`
198
-
199
- Responsive grid of pricing cards with features, trial badges, and selection.
200
-
201
- ```tsx
202
- import { PricingTable } from '@saas-support/react/react'
203
-
204
- <PricingTable
205
- plans={plans}
206
- currentPlanId="plan_123"
207
- onSelectPlan={(planId) => handleSubscribe(planId)}
208
- interval="month"
209
- />
210
- ```
211
-
212
- | Prop | Type | Default | Description |
213
- |------|------|---------|-------------|
214
- | `plans` | `Plan[]` | **required** | List of available plans |
215
- | `currentPlanId` | `string` | — | Highlight current plan |
216
- | `onSelectPlan` | `(planId: string) => void` | **required** | Callback on plan selection |
217
- | `interval` | `'month' \| 'year'` | — | Filter plans by billing interval |
218
- | `appearance` | `Appearance` | — | Theme overrides |
219
-
220
- #### `<SubscriptionStatus />`
221
-
222
- Displays customer's current subscription with status badge and actions.
223
-
224
- ```tsx
225
- import { SubscriptionStatus } from '@saas-support/react/react'
94
+ import { UserButton } from '@saas-support/react/react'
226
95
 
227
- <SubscriptionStatus
228
- customerId="cus_123"
229
- onChangePlan={() => showPlanModal()}
230
- onCancel={() => confirmCancel()}
96
+ <UserButton
97
+ showOrgSwitcher={true}
98
+ afterSignOutUrl="/login"
99
+ onOrgChange={(org) => console.log('Switched to', org.name)}
100
+ onOrgSettingsClick={(org) => navigate(`/org/${org.slug}/settings`)}
231
101
  />
232
102
  ```
233
103
 
234
104
  | Prop | Type | Default | Description |
235
105
  |------|------|---------|-------------|
236
- | `customerId` | `string` | **required** | Customer ID |
237
- | `portalToken` | `string` | — | Portal token for customer self-service |
238
- | `onChangePlan` | `() => void` | | Change plan callback |
239
- | `onCancel` | `() => void` | — | Cancel subscription callback |
240
- | `appearance` | `Appearance` | — | Theme overrides |
241
-
242
- #### `<InvoiceHistory />`
243
-
244
- Table of invoices with date, amount, status, and PDF download links.
245
-
246
- ```tsx
247
- import { InvoiceHistory } from '@saas-support/react/react'
248
-
249
- <InvoiceHistory customerId="cus_123" />
250
- ```
251
-
252
- | Prop | Type | Default | Description |
253
- |------|------|---------|-------------|
254
- | `customerId` | `string` | **required** | Customer ID |
255
- | `portalToken` | `string` | — | Portal token for self-service |
106
+ | `afterSignOutUrl` | `string` | | Redirect URL after sign-out |
107
+ | `afterDeleteAccountUrl` | `string` | — | Redirect URL after account deletion |
108
+ | `showOrgSwitcher` | `boolean` | `true` | Show org list in dropdown |
109
+ | `onOrgChange` | `(org: Org) => void` | — | Called when user switches org |
110
+ | `onOrgSettingsClick` | `(org: Org) => void` | — | Called when "Org settings" is clicked |
256
111
  | `appearance` | `Appearance` | — | Theme overrides |
257
112
 
258
- #### `<UsageDisplay />`
113
+ Features:
114
+ - Avatar with initials fallback
115
+ - Invite notification badge
116
+ - Inline org creation
117
+ - Full settings panel (profile, organization, people, invites, billing)
259
118
 
260
- Usage metrics with progress bars. Bars turn red when exceeding 90% of limits.
119
+ ### `<SettingsPanel />`
261
120
 
262
- ```tsx
263
- import { UsageDisplay } from '@saas-support/react/react'
264
-
265
- <UsageDisplay
266
- customerId="cus_123"
267
- limits={{ api_calls: 10000, storage_gb: 50 }}
268
- />
269
- ```
270
-
271
- | Prop | Type | Default | Description |
272
- |------|------|---------|-------------|
273
- | `customerId` | `string` | **required** | Customer ID |
274
- | `limits` | `Record<string, number>` | — | Usage limits for progress bars |
275
- | `portalToken` | `string` | — | Portal token for self-service |
276
- | `appearance` | `Appearance` | — | Theme overrides |
277
-
278
- #### `<PaymentPortal />`
279
-
280
- Tabbed interface combining subscription status, invoice history, and usage display.
121
+ Full-page settings overlay with tabs for profile, organization, people, invites, and billing. Typically opened by `<UserButton>` but can be used standalone.
281
122
 
282
123
  ```tsx
283
- import { PaymentPortal } from '@saas-support/react/react'
124
+ import { SettingsPanel } from '@saas-support/react/react'
284
125
 
285
- <PaymentPortal
286
- customerId="cus_123"
287
- portalToken="pt_..."
288
- limits={{ api_calls: 10000 }}
289
- onChangePlan={() => showPlanModal()}
126
+ <SettingsPanel
127
+ onClose={() => setShowSettings(false)}
128
+ defaultTab="profile"
129
+ afterDeleteAccountUrl="/login"
130
+ onOrgDeleted={refreshOrgs}
131
+ onOrgUpdated={refreshOrgs}
290
132
  />
291
133
  ```
292
134
 
293
135
  | Prop | Type | Default | Description |
294
136
  |------|------|---------|-------------|
295
- | `customerId` | `string` | **required** | Customer ID |
296
- | `portalToken` | `string` | | Portal token for self-service |
297
- | `limits` | `Record<string, number>` | — | Usage limits |
298
- | `onChangePlan` | `() => void` | — | Change plan callback |
299
- | `onCancel` | `() => void` | — | Cancel subscription callback |
300
- | `appearance` | `Appearance` | — | Theme overrides |
301
-
302
- #### `<CouponInput />`
303
-
304
- Input field to apply a coupon code.
305
-
306
- ```tsx
307
- import { CouponInput } from '@saas-support/react/react'
308
-
309
- <CouponInput
310
- customerId="cus_123"
311
- onApplied={(result) => console.log(result.discountType, result.amount)}
312
- />
313
- ```
314
-
315
- | Prop | Type | Default | Description |
316
- |------|------|---------|-------------|
317
- | `customerId` | `string` | **required** | Customer ID |
318
- | `portalToken` | `string` | — | Portal token for self-service |
319
- | `onApplied` | `(result: ApplyCouponResult) => void` | — | Callback after successful apply |
320
- | `appearance` | `Appearance` | — | Theme overrides |
321
-
322
- ### Hooks
323
-
324
- ```tsx
325
- import { useBilling, useSubscription, useInvoices, useUsage } from '@saas-support/react/react'
326
- ```
327
-
328
- #### `useBilling()`
329
-
330
- ```tsx
331
- const { billing } = useBilling()
332
-
333
- await billing.createCustomer({ email: 'user@example.com', name: 'Jane' })
334
- await billing.subscribe('cus_123', 'plan_456')
335
- ```
336
-
337
- #### `useSubscription(customerId, portalToken?)`
338
-
339
- ```tsx
340
- const { customer, isLoading, error, refresh } = useSubscription('cus_123')
341
- ```
342
-
343
- #### `useInvoices(customerId, portalToken?)`
344
-
345
- ```tsx
346
- const { invoices, isLoading, error, refresh } = useInvoices('cus_123')
347
- ```
348
-
349
- #### `useUsage(customerId, portalToken?)`
350
-
351
- ```tsx
352
- const { usage, isLoading, error, refresh } = useUsage('cus_123')
353
- ```
137
+ | `onClose` | `() => void` | **required** | Close callback |
138
+ | `defaultTab` | `SettingsTab` | `'profile'` | Initial active tab |
139
+ | `afterDeleteAccountUrl` | `string` | — | Redirect after account deletion |
140
+ | `onOrgDeleted` | `() => void` | — | Callback when org is deleted |
141
+ | `onOrgUpdated` | `() => void` | — | Callback when org is updated |
354
142
 
355
143
  ---
356
144
 
357
- ## Reports
358
-
359
- ### Components
145
+ ## Hooks
360
146
 
361
- #### `<QueryInput />`
147
+ ### `useAuth()`
362
148
 
363
- Textarea for natural language or SQL queries with a mode toggle and Run button.
149
+ Primary auth state hook.
364
150
 
365
151
  ```tsx
366
- import { QueryInput } from '@saas-support/react/react'
367
-
368
- <QueryInput
369
- mode="both"
370
- onResult={(result) => console.log(result.rows)}
371
- placeholder="Ask a question about your data..."
372
- />
152
+ const { isLoaded, isSignedIn, user, signOut, getToken, refreshUser } = useAuth()
373
153
  ```
374
154
 
375
- | Prop | Type | Default | Description |
376
- |------|------|---------|-------------|
377
- | `mode` | `'nl' \| 'sql' \| 'both'` | `'both'` | Input mode |
378
- | `onResult` | `(result: QueryResult) => void` | | Callback with query results |
379
- | `placeholder` | `string` | | Input placeholder |
380
- | `appearance` | `Appearance` | | Theme overrides |
155
+ | Return | Type | Description |
156
+ |--------|------|-------------|
157
+ | `isLoaded` | `boolean` | SDK has finished loading |
158
+ | `isSignedIn` | `boolean` | User is authenticated |
159
+ | `user` | `User \| null` | Current user object |
160
+ | `signOut` | `() => Promise<void>` | Sign out |
161
+ | `getToken` | `() => string` | Get current access token |
162
+ | `refreshUser` | `() => Promise<User \| null>` | Refresh user data from server |
381
163
 
382
- #### `<DataTable />`
164
+ ### `useUser()`
383
165
 
384
- Sortable data table from query results.
166
+ Lightweight user-only hook (no methods).
385
167
 
386
168
  ```tsx
387
- import { DataTable } from '@saas-support/react/react'
388
-
389
- <DataTable
390
- columns={result.columns}
391
- rows={result.rows}
392
- sortable
393
- maxRows={100}
394
- />
169
+ const { user, isLoaded } = useUser()
395
170
  ```
396
171
 
397
- | Prop | Type | Default | Description |
398
- |------|------|---------|-------------|
399
- | `columns` | `string[]` | **required** | Column names |
400
- | `rows` | `Record<string, unknown>[]` | **required** | Data rows |
401
- | `sortable` | `boolean` | `true` | Enable column sorting |
402
- | `maxRows` | `number` | — | Max rows to display |
403
- | `appearance` | `Appearance` | — | Theme overrides |
404
-
405
- #### `<Chart />`
172
+ ### `useSignIn()`
406
173
 
407
- Pure SVG chart with zero dependencies. Supports bar, line, and pie types.
174
+ Programmatic sign-in.
408
175
 
409
176
  ```tsx
410
- import { Chart } from '@saas-support/react/react'
411
-
412
- <Chart
413
- type="bar"
414
- data={{ labels: ['Jan', 'Feb', 'Mar'], values: [100, 200, 150] }}
415
- title="Monthly Revenue"
416
- width={500}
417
- height={300}
418
- />
419
- ```
420
-
421
- | Prop | Type | Default | Description |
422
- |------|------|---------|-------------|
423
- | `type` | `'bar' \| 'line' \| 'pie'` | **required** | Chart type |
424
- | `data` | `{ labels: string[]; values: number[] }` | **required** | Chart data |
425
- | `title` | `string` | — | Chart title |
426
- | `width` | `number` | `400` | SVG width |
427
- | `height` | `number` | `300` | SVG height |
428
- | `appearance` | `Appearance` | — | Theme overrides |
429
-
430
- #### `<DashboardView />`
431
-
432
- Multi-widget dashboard grid with auto-refresh.
177
+ const { signIn, signInWithOAuth, submitMfaCode, isLoading, error, setError } = useSignIn()
433
178
 
434
- ```tsx
435
- import { DashboardView } from '@saas-support/react/react'
179
+ const result = await signIn(email, password)
180
+ if (result && isMfaRequired(result)) {
181
+ await submitMfaCode(result.mfaToken, code)
182
+ }
436
183
 
437
- <DashboardView
438
- dashboardId="dash_123"
439
- refreshInterval={60}
440
- />
184
+ await signInWithOAuth('google') // or 'github'
441
185
  ```
442
186
 
443
- | Prop | Type | Default | Description |
444
- |------|------|---------|-------------|
445
- | `dashboardId` | `string` | **required** | Dashboard ID |
446
- | `embedToken` | `string` | — | Embed token for standalone use |
447
- | `baseUrl` | `string` | — | API base URL override |
448
- | `refreshInterval` | `number` | — | Auto-refresh interval in seconds |
449
- | `appearance` | `Appearance` | — | Theme overrides |
187
+ ### `useSignUp()`
450
188
 
451
- #### `<SavedQueryList />`
452
-
453
- Browseable list of saved queries with run buttons.
189
+ Programmatic sign-up.
454
190
 
455
191
  ```tsx
456
- import { SavedQueryList } from '@saas-support/react/react'
457
-
458
- <SavedQueryList
459
- onSelectQuery={(query) => setActiveQuery(query)}
460
- onRunQuery={(result) => setTableData(result)}
461
- />
192
+ const { signUp, isLoading, error, setError } = useSignUp()
193
+ await signUp(email, password)
462
194
  ```
463
195
 
464
- | Prop | Type | Default | Description |
465
- |------|------|---------|-------------|
466
- | `onSelectQuery` | `(query: SavedQuery) => void` | — | Callback on query selection |
467
- | `onRunQuery` | `(result: QueryResult) => void` | — | Callback with execution result |
468
- | `appearance` | `Appearance` | — | Theme overrides |
196
+ ### `useOrg()`
469
197
 
470
- #### `<ReportEmbed />`
471
-
472
- Standalone embeddable dashboard viewer. **Does not require `<SaaSProvider>`** — creates its own transport with the embed token.
198
+ Organization management.
473
199
 
474
200
  ```tsx
475
- import { ReportEmbed } from '@saas-support/react/react'
476
-
477
- // Can be used anywhere, no provider needed
478
- <ReportEmbed
479
- embedToken="emb_..."
480
- dashboardId="dash_123"
481
- refreshInterval={30}
482
- />
201
+ const {
202
+ orgs, // Org[]
203
+ selectedOrg, // Org | null
204
+ members, // Member[]
205
+ invites, // PendingInvite[]
206
+ selectOrg, // (orgId: string) => Promise<void>
207
+ createOrg, // (name: string, slug: string) => Promise<Org | null>
208
+ updateOrg, // (orgId: string, params) => Promise<Org | null>
209
+ deleteOrg, // (orgId: string) => Promise<boolean>
210
+ sendInvite, // (orgId, email, role) => Promise<Invite | null>
211
+ revokeInvite, // (orgId, inviteId) => Promise<boolean>
212
+ updateMemberRole, // (orgId, userId, role) => Promise<boolean>
213
+ removeMember, // (orgId, userId) => Promise<boolean>
214
+ refresh,
215
+ isLoading,
216
+ error,
217
+ } = useOrg()
483
218
  ```
484
219
 
485
- | Prop | Type | Default | Description |
486
- |------|------|---------|-------------|
487
- | `embedToken` | `string` | **required** | Embed token |
488
- | `dashboardId` | `string` | **required** | Dashboard ID |
489
- | `baseUrl` | `string` | `https://api.saas-support.com/v1` | API base URL |
490
- | `refreshInterval` | `number` | — | Auto-refresh interval in seconds |
491
- | `appearance` | `Appearance` | — | Theme overrides |
492
-
493
- ### Hooks
494
-
495
- ```tsx
496
- import { useReport, useQuery, useSavedQueries, useDashboard, useEmbedDashboard } from '@saas-support/react/react'
497
- ```
220
+ ### `useProfile()`
498
221
 
499
- #### `useReport()`
222
+ Profile management with avatar upload.
500
223
 
501
224
  ```tsx
502
- const { report } = useReport()
225
+ const { user, updateProfile, uploadAvatar, changePassword, isLoading, error, success } = useProfile()
503
226
 
504
- await report.executeQuery({ naturalLanguage: 'Show top customers by revenue' })
227
+ await updateProfile({ name: 'New Name' })
228
+ await uploadAvatar(imageBlob)
229
+ await changePassword(currentPassword, newPassword)
505
230
  ```
506
231
 
507
- #### `useQuery()`
232
+ ### `useInvites()`
233
+
234
+ Pending invite notifications for the current user.
508
235
 
509
236
  ```tsx
510
- const { result, execute, isLoading, error } = useQuery()
237
+ const { invites, accept, decline, refresh, isLoading, error } = useInvites()
511
238
 
512
- const data = await execute({ sql: 'SELECT * FROM orders LIMIT 10' })
239
+ await accept(inviteId) // Accept org invitation
240
+ await decline(inviteId) // Decline org invitation
513
241
  ```
514
242
 
515
- #### `useSavedQueries(params?)`
516
-
517
- ```tsx
518
- const { queries, meta, isLoading, refresh } = useSavedQueries({ page: 1, perPage: 20 })
519
- ```
243
+ ### `useDeleteAccount()`
520
244
 
521
- #### `useDashboard(dashboardId)`
245
+ Account deletion.
522
246
 
523
247
  ```tsx
524
- const { dashboard, isLoading, error, refresh } = useDashboard('dash_123')
248
+ const { deleteAccount, isLoading, error } = useDeleteAccount()
249
+ await deleteAccount()
525
250
  ```
526
251
 
527
- #### `useEmbedDashboard(embedToken, dashboardId, baseUrl?)`
252
+ ### `useSaaSContext()`
528
253
 
529
- Standalone hook for embedded dashboards. Creates its own transport — no `<SaaSProvider>` needed.
254
+ Low-level context access.
530
255
 
531
256
  ```tsx
532
- const { dashboard, reportClient, isLoading, refresh } = useEmbedDashboard('emb_...', 'dash_123')
257
+ const { client, user, isLoaded, appearance, settings } = useSaaSContext()
533
258
  ```
534
259
 
535
260
  ---
@@ -541,26 +266,12 @@ Use the client directly without React:
541
266
  ```ts
542
267
  import { SaaSSupport } from '@saas-support/react'
543
268
 
544
- const saas = new SaaSSupport({
545
- publishableKey: 'pub_live_...',
546
- apiKey: 'sk_live_...',
547
- })
269
+ const saas = new SaaSSupport({ publishableKey: 'pub_live_...' })
548
270
 
549
- // Auth
550
271
  await saas.load()
551
272
  const result = await saas.auth.signIn('user@example.com', 'password')
552
- const user = saas.auth.getUserSync()
273
+ const user = await saas.auth.getUser()
553
274
 
554
- // Billing
555
- const customer = await saas.billing.createCustomer({ email: 'user@example.com' })
556
- await saas.billing.subscribe(customer.id, 'plan_123')
557
-
558
- // Reports
559
- const data = await saas.report.executeQuery({
560
- naturalLanguage: 'Show revenue by month',
561
- })
562
-
563
- // Cleanup
564
275
  saas.destroy()
565
276
  ```
566
277
 
@@ -573,67 +284,34 @@ saas.destroy()
573
284
  | `signOut()` | `Promise<void>` |
574
285
  | `signInWithOAuth(provider)` | `Promise<SignInResult>` |
575
286
  | `submitMfaCode(mfaToken, code)` | `Promise<SignInResult>` |
576
- | `sendMagicLink(email, redirectUrl)` | `Promise<void>` |
577
- | `verifyMagicLink(token)` | `Promise<SignInResult>` |
578
- | `sendPasswordReset(email, redirectUrl)` | `Promise<void>` |
579
- | `resetPassword(token, newPassword)` | `Promise<void>` |
580
- | `setupMfa()` | `Promise<MfaSetupResult>` |
581
- | `verifyMfa(code)` | `Promise<MfaVerifyResult>` |
582
- | `disableMfa(code)` | `Promise<void>` |
583
287
  | `getToken()` | `Promise<string \| null>` |
584
288
  | `getUser()` | `Promise<User \| null>` |
585
- | `getUserSync()` | `User \| null` |
586
- | `updateProfile(metadata)` | `Promise<User>` |
289
+ | `refreshUser()` | `Promise<User \| null>` |
290
+ | `updateProfile(params)` | `Promise<User>` |
291
+ | `uploadAvatar(imageBlob)` | `Promise<{ url }>` |
292
+ | `changePassword(current, new)` | `Promise<void>` |
293
+ | `deleteAccount()` | `Promise<void>` |
294
+ | `getSettings()` | `Promise<ProjectSettings>` |
587
295
  | `listOrgs()` | `Promise<Org[]>` |
588
- | `createOrg(name, slug)` | `Promise<Org>` |
589
296
  | `getOrg(orgId)` | `Promise<Org>` |
297
+ | `createOrg(name, slug)` | `Promise<Org>` |
590
298
  | `updateOrg(orgId, params)` | `Promise<Org>` |
591
299
  | `deleteOrg(orgId)` | `Promise<void>` |
592
300
  | `listMembers(orgId)` | `Promise<Member[]>` |
593
- | `sendInvite(orgId, email, role)` | `Promise<Invite>` |
594
301
  | `updateMemberRole(orgId, userId, role)` | `Promise<void>` |
595
302
  | `removeMember(orgId, userId)` | `Promise<void>` |
596
- | `acceptInvite(token)` | `Promise<{ orgId, role }>` |
597
-
598
- ### BillingClient Methods
599
-
600
- | Method | Returns |
601
- |--------|---------|
602
- | `createCustomer(params)` | `Promise<Customer>` |
603
- | `getCustomer(customerId)` | `Promise<Customer>` |
604
- | `updateCustomer(customerId, params)` | `Promise<Customer>` |
605
- | `subscribe(customerId, planId)` | `Promise<Subscription>` |
606
- | `changePlan(customerId, planId)` | `Promise<Subscription>` |
607
- | `cancelSubscription(customerId)` | `Promise<{ canceledAtPeriodEnd }>` |
608
- | `getInvoices(customerId)` | `Promise<Invoice[]>` |
609
- | `ingestUsageEvent(params)` | `Promise<{ id, ingested }>` |
610
- | `getCurrentUsage(customerId)` | `Promise<UsageSummary[]>` |
611
- | `createPortalToken(customerId, expiresIn?)` | `Promise<PortalTokenResult>` |
612
- | `applyCoupon(customerId, code)` | `Promise<ApplyCouponResult>` |
613
-
614
- ### ReportClient Methods
615
-
616
- | Method | Returns |
617
- |--------|---------|
618
- | `executeQuery(params)` | `Promise<QueryResult>` |
619
- | `listQueries(params?)` | `Promise<OffsetPage<SavedQuery>>` |
620
- | `saveQuery(params)` | `Promise<SavedQuery>` |
621
- | `updateQuery(queryId, params)` | `Promise<SavedQuery>` |
622
- | `deleteQuery(queryId)` | `Promise<void>` |
623
- | `listDashboards(params?)` | `Promise<OffsetPage<Dashboard>>` |
624
- | `createDashboard(params)` | `Promise<Dashboard>` |
625
- | `getDashboard(dashboardId)` | `Promise<Dashboard>` |
626
- | `updateDashboard(dashboardId, params)` | `Promise<Dashboard>` |
627
- | `deleteDashboard(dashboardId)` | `Promise<void>` |
628
- | `createEmbedToken(params)` | `Promise<EmbedTokenResult>` |
629
- | `listEmbedTokens()` | `Promise<EmbedToken[]>` |
630
- | `revokeEmbedToken(tokenId)` | `Promise<void>` |
303
+ | `sendInvite(orgId, email, role)` | `Promise<Invite>` |
304
+ | `listInvites(orgId)` | `Promise<PendingInvite[]>` |
305
+ | `revokeInvite(orgId, inviteId)` | `Promise<void>` |
306
+ | `listMyInvites()` | `Promise<MyPendingInvite[]>` |
307
+ | `acceptInviteById(inviteId)` | `Promise<{ orgId, role }>` |
308
+ | `declineInvite(inviteId)` | `Promise<void>` |
631
309
 
632
310
  ---
633
311
 
634
312
  ## Theming
635
313
 
636
- All components support theming via the `appearance` prop (per-component) or the `<SaaSProvider>` `appearance` prop (global).
314
+ All components render inside Shadow DOM for style isolation. Customize via the `appearance` prop:
637
315
 
638
316
  ```tsx
639
317
  <SaaSProvider
@@ -644,8 +322,6 @@ All components support theming via the `appearance` prop (per-component) or the
644
322
  colorPrimary: '#8b5cf6',
645
323
  colorBackground: '#0f172a',
646
324
  colorText: '#f1f5f9',
647
- colorSuccess: '#22c55e',
648
- colorWarning: '#f59e0b',
649
325
  fontFamily: '"Inter", sans-serif',
650
326
  borderRadius: '12px',
651
327
  },
@@ -653,12 +329,13 @@ All components support theming via the `appearance` prop (per-component) or the
653
329
  card: { boxShadow: '0 4px 24px rgba(0,0,0,0.3)' },
654
330
  submitButton: { fontWeight: 700 },
655
331
  },
332
+ fontUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap',
656
333
  }}
657
334
  >
658
- <App />
659
- </SaaSProvider>
660
335
  ```
661
336
 
337
+ Set `fontUrl: null` to disable CDN font loading.
338
+
662
339
  ### Theme Variables
663
340
 
664
341
  | Variable | Light Default | Dark Default |
@@ -670,30 +347,11 @@ All components support theming via the `appearance` prop (per-component) or the
670
347
  | `colorInputBorder` | `#e2e8f0` | `#3a3a4e` |
671
348
  | `colorError` | `#ef4444` | `#f87171` |
672
349
  | `colorSuccess` | `#22c55e` | `#4ade80` |
673
- | `colorWarning` | `#f59e0b` | `#fbbf24` |
674
350
  | `fontFamily` | `-apple-system, ...` | `-apple-system, ...` |
675
351
  | `borderRadius` | `8px` | `8px` |
676
352
 
677
- ### Element Overrides
678
-
679
- Apply `React.CSSProperties` to specific elements:
680
-
681
- `card`, `headerTitle`, `formField`, `submitButton`, `socialButton`, `footerLink`, `divider`, `errorMessage`, `pricingCard`, `badge`, `table`, `tableHeader`, `tableRow`, `progressBar`, `chartContainer`, `queryInput`, `dashboardGrid`
682
-
683
353
  ---
684
354
 
685
- ## Portal Tokens
686
-
687
- Billing components accept an optional `portalToken` prop for customer self-service scenarios. This lets end-users view their own subscription, invoices, and usage without exposing your API key:
688
-
689
- ```tsx
690
- // Server-side: generate a portal token
691
- const { portalToken } = await billing.createPortalToken('cus_123', 3600)
692
-
693
- // Client-side: pass it to billing components
694
- <PaymentPortal customerId="cus_123" portalToken={portalToken} />
695
- ```
696
-
697
355
  ## Error Handling
698
356
 
699
357
  All errors are thrown as `SaaSError` instances:
@@ -708,24 +366,15 @@ try {
708
366
  console.log(err.code) // 401
709
367
  console.log(err.domain) // 'auth'
710
368
  console.log(err.isUnauthorized) // true
711
- console.log(err.isNotFound) // false
712
- console.log(err.isRateLimited) // false
713
369
  }
714
370
  }
715
371
  ```
716
372
 
717
- ## Style Isolation
718
-
719
- All components render inside Shadow DOM to prevent style conflicts with your application. The SDK generates its own CSS from the resolved theme and injects it into each shadow root.
720
-
721
- ## Tree Shaking
373
+ ## Token Storage
722
374
 
723
- The SDK is fully tree-shakeable. Importing only auth components won't bundle billing or report code:
724
-
725
- ```ts
726
- // Only auth code is included in your bundle
727
- import { SignIn, useAuth } from '@saas-support/react/react'
728
- ```
375
+ - **Access token**: stored in memory (XSS-safe)
376
+ - **Refresh token**: stored in localStorage as `ss_rt_<first12chars>`
377
+ - Token refresh is automatic and transparent
729
378
 
730
379
  ## License
731
380