@saas-support/react 0.2.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/README.md ADDED
@@ -0,0 +1,732 @@
1
+ # @saas-support/react
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.
4
+
5
+ ```bash
6
+ npm install @saas-support/react
7
+ ```
8
+
9
+ ## Quick Start
10
+
11
+ ```tsx
12
+ import { SaaSProvider, SignIn, PricingTable } from '@saas-support/react/react'
13
+
14
+ function App() {
15
+ return (
16
+ <SaaSProvider publishableKey="pub_live_..." apiKey="sk_live_...">
17
+ <SignIn />
18
+ </SaaSProvider>
19
+ )
20
+ }
21
+ ```
22
+
23
+ ## Entry Points
24
+
25
+ | Import | Use |
26
+ |--------|-----|
27
+ | `@saas-support/react` | Vanilla JS clients, types, no React dependency |
28
+ | `@saas-support/react/react` | React components, hooks, provider |
29
+
30
+ ---
31
+
32
+ ## Provider
33
+
34
+ Wrap your app in `<SaaSProvider>` to initialize the SDK:
35
+
36
+ ```tsx
37
+ import { SaaSProvider } from '@saas-support/react/react'
38
+
39
+ <SaaSProvider
40
+ publishableKey="pub_live_..." // Auth (end-user facing)
41
+ apiKey="sk_live_..." // Billing & Reports (server-side or trusted client)
42
+ baseUrl="https://api.saas-support.com/v1"
43
+ appearance={{ baseTheme: 'dark' }}
44
+ >
45
+ <App />
46
+ </SaaSProvider>
47
+ ```
48
+
49
+ | Prop | Type | Required | Description |
50
+ |------|------|----------|-------------|
51
+ | `publishableKey` | `string` | No* | Publishable key for auth operations |
52
+ | `apiKey` | `string` | No* | API key for billing/report operations |
53
+ | `baseUrl` | `string` | No | API base URL override |
54
+ | `appearance` | `Appearance` | No | Global theme configuration |
55
+
56
+ \* At least one of `publishableKey` or `apiKey` is required.
57
+
58
+ ---
59
+
60
+ ## Authentication
61
+
62
+ ### Components
63
+
64
+ #### `<SignIn />`
65
+
66
+ Pre-built sign-in form with email/password, OAuth buttons, and MFA support.
67
+
68
+ ```tsx
69
+ import { SignIn } from '@saas-support/react/react'
70
+
71
+ <SignIn
72
+ signUpUrl="/register"
73
+ 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
+ afterSignUpUrl="/onboarding"
95
+ onSignIn={() => navigate('/login')}
96
+ />
97
+ ```
98
+
99
+ | Prop | Type | Default | Description |
100
+ |------|------|---------|-------------|
101
+ | `signInUrl` | `string` | — | URL for the sign-in link |
102
+ | `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
+ | `appearance` | `Appearance` | — | Theme overrides |
145
+
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()`
159
+
160
+ ```tsx
161
+ const { user, isLoaded } = useUser()
162
+ ```
163
+
164
+ #### `useSignIn()`
165
+
166
+ ```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'
226
+
227
+ <SubscriptionStatus
228
+ customerId="cus_123"
229
+ onChangePlan={() => showPlanModal()}
230
+ onCancel={() => confirmCancel()}
231
+ />
232
+ ```
233
+
234
+ | Prop | Type | Default | Description |
235
+ |------|------|---------|-------------|
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 |
256
+ | `appearance` | `Appearance` | — | Theme overrides |
257
+
258
+ #### `<UsageDisplay />`
259
+
260
+ Usage metrics with progress bars. Bars turn red when exceeding 90% of limits.
261
+
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.
281
+
282
+ ```tsx
283
+ import { PaymentPortal } from '@saas-support/react/react'
284
+
285
+ <PaymentPortal
286
+ customerId="cus_123"
287
+ portalToken="pt_..."
288
+ limits={{ api_calls: 10000 }}
289
+ onChangePlan={() => showPlanModal()}
290
+ />
291
+ ```
292
+
293
+ | Prop | Type | Default | Description |
294
+ |------|------|---------|-------------|
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
+ ```
354
+
355
+ ---
356
+
357
+ ## Reports
358
+
359
+ ### Components
360
+
361
+ #### `<QueryInput />`
362
+
363
+ Textarea for natural language or SQL queries with a mode toggle and Run button.
364
+
365
+ ```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
+ />
373
+ ```
374
+
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 |
381
+
382
+ #### `<DataTable />`
383
+
384
+ Sortable data table from query results.
385
+
386
+ ```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
+ />
395
+ ```
396
+
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 />`
406
+
407
+ Pure SVG chart with zero dependencies. Supports bar, line, and pie types.
408
+
409
+ ```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.
433
+
434
+ ```tsx
435
+ import { DashboardView } from '@saas-support/react/react'
436
+
437
+ <DashboardView
438
+ dashboardId="dash_123"
439
+ refreshInterval={60}
440
+ />
441
+ ```
442
+
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 |
450
+
451
+ #### `<SavedQueryList />`
452
+
453
+ Browseable list of saved queries with run buttons.
454
+
455
+ ```tsx
456
+ import { SavedQueryList } from '@saas-support/react/react'
457
+
458
+ <SavedQueryList
459
+ onSelectQuery={(query) => setActiveQuery(query)}
460
+ onRunQuery={(result) => setTableData(result)}
461
+ />
462
+ ```
463
+
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 |
469
+
470
+ #### `<ReportEmbed />`
471
+
472
+ Standalone embeddable dashboard viewer. **Does not require `<SaaSProvider>`** — creates its own transport with the embed token.
473
+
474
+ ```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
+ />
483
+ ```
484
+
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
+ ```
498
+
499
+ #### `useReport()`
500
+
501
+ ```tsx
502
+ const { report } = useReport()
503
+
504
+ await report.executeQuery({ naturalLanguage: 'Show top customers by revenue' })
505
+ ```
506
+
507
+ #### `useQuery()`
508
+
509
+ ```tsx
510
+ const { result, execute, isLoading, error } = useQuery()
511
+
512
+ const data = await execute({ sql: 'SELECT * FROM orders LIMIT 10' })
513
+ ```
514
+
515
+ #### `useSavedQueries(params?)`
516
+
517
+ ```tsx
518
+ const { queries, meta, isLoading, refresh } = useSavedQueries({ page: 1, perPage: 20 })
519
+ ```
520
+
521
+ #### `useDashboard(dashboardId)`
522
+
523
+ ```tsx
524
+ const { dashboard, isLoading, error, refresh } = useDashboard('dash_123')
525
+ ```
526
+
527
+ #### `useEmbedDashboard(embedToken, dashboardId, baseUrl?)`
528
+
529
+ Standalone hook for embedded dashboards. Creates its own transport — no `<SaaSProvider>` needed.
530
+
531
+ ```tsx
532
+ const { dashboard, reportClient, isLoading, refresh } = useEmbedDashboard('emb_...', 'dash_123')
533
+ ```
534
+
535
+ ---
536
+
537
+ ## Vanilla JS (No React)
538
+
539
+ Use the client directly without React:
540
+
541
+ ```ts
542
+ import { SaaSSupport } from '@saas-support/react'
543
+
544
+ const saas = new SaaSSupport({
545
+ publishableKey: 'pub_live_...',
546
+ apiKey: 'sk_live_...',
547
+ })
548
+
549
+ // Auth
550
+ await saas.load()
551
+ const result = await saas.auth.signIn('user@example.com', 'password')
552
+ const user = saas.auth.getUserSync()
553
+
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
+ saas.destroy()
565
+ ```
566
+
567
+ ### AuthClient Methods
568
+
569
+ | Method | Returns |
570
+ |--------|---------|
571
+ | `signIn(email, password)` | `Promise<AuthResult>` |
572
+ | `signUp(email, password)` | `Promise<SignUpResult>` |
573
+ | `signOut()` | `Promise<void>` |
574
+ | `signInWithOAuth(provider)` | `Promise<SignInResult>` |
575
+ | `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
+ | `getToken()` | `Promise<string \| null>` |
584
+ | `getUser()` | `Promise<User \| null>` |
585
+ | `getUserSync()` | `User \| null` |
586
+ | `updateProfile(metadata)` | `Promise<User>` |
587
+ | `listOrgs()` | `Promise<Org[]>` |
588
+ | `createOrg(name, slug)` | `Promise<Org>` |
589
+ | `getOrg(orgId)` | `Promise<Org>` |
590
+ | `updateOrg(orgId, params)` | `Promise<Org>` |
591
+ | `deleteOrg(orgId)` | `Promise<void>` |
592
+ | `listMembers(orgId)` | `Promise<Member[]>` |
593
+ | `sendInvite(orgId, email, role)` | `Promise<Invite>` |
594
+ | `updateMemberRole(orgId, userId, role)` | `Promise<void>` |
595
+ | `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>` |
631
+
632
+ ---
633
+
634
+ ## Theming
635
+
636
+ All components support theming via the `appearance` prop (per-component) or the `<SaaSProvider>` `appearance` prop (global).
637
+
638
+ ```tsx
639
+ <SaaSProvider
640
+ publishableKey="pub_live_..."
641
+ appearance={{
642
+ baseTheme: 'dark',
643
+ variables: {
644
+ colorPrimary: '#8b5cf6',
645
+ colorBackground: '#0f172a',
646
+ colorText: '#f1f5f9',
647
+ colorSuccess: '#22c55e',
648
+ colorWarning: '#f59e0b',
649
+ fontFamily: '"Inter", sans-serif',
650
+ borderRadius: '12px',
651
+ },
652
+ elements: {
653
+ card: { boxShadow: '0 4px 24px rgba(0,0,0,0.3)' },
654
+ submitButton: { fontWeight: 700 },
655
+ },
656
+ }}
657
+ >
658
+ <App />
659
+ </SaaSProvider>
660
+ ```
661
+
662
+ ### Theme Variables
663
+
664
+ | Variable | Light Default | Dark Default |
665
+ |----------|--------------|--------------|
666
+ | `colorPrimary` | `#6366f1` | `#818cf8` |
667
+ | `colorBackground` | `#ffffff` | `#1e1e2e` |
668
+ | `colorText` | `#1a1a2e` | `#e2e8f0` |
669
+ | `colorInputBackground` | `#f8f9fa` | `#2a2a3e` |
670
+ | `colorInputBorder` | `#e2e8f0` | `#3a3a4e` |
671
+ | `colorError` | `#ef4444` | `#f87171` |
672
+ | `colorSuccess` | `#22c55e` | `#4ade80` |
673
+ | `colorWarning` | `#f59e0b` | `#fbbf24` |
674
+ | `fontFamily` | `-apple-system, ...` | `-apple-system, ...` |
675
+ | `borderRadius` | `8px` | `8px` |
676
+
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
+ ---
684
+
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
+ ## Error Handling
698
+
699
+ All errors are thrown as `SaaSError` instances:
700
+
701
+ ```ts
702
+ import { SaaSError } from '@saas-support/react'
703
+
704
+ try {
705
+ await saas.auth.signIn(email, password)
706
+ } catch (err) {
707
+ if (err instanceof SaaSError) {
708
+ console.log(err.code) // 401
709
+ console.log(err.domain) // 'auth'
710
+ console.log(err.isUnauthorized) // true
711
+ console.log(err.isNotFound) // false
712
+ console.log(err.isRateLimited) // false
713
+ }
714
+ }
715
+ ```
716
+
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
722
+
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
+ ```
729
+
730
+ ## License
731
+
732
+ MIT