@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 +732 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +491 -0
- package/dist/index.js +524 -0
- package/dist/react.cjs +593 -0
- package/dist/react.d.ts +783 -0
- package/dist/react.js +1908 -0
- package/package.json +52 -0
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
|