@saas-support/react 0.7.5 → 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 +139 -490
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +23 -231
- package/dist/index.js +204 -288
- package/dist/react.cjs +386 -348
- package/dist/react.d.ts +41 -438
- package/dist/react.js +1133 -2075
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @saas-support/react
|
|
2
2
|
|
|
3
|
-
|
|
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,
|
|
12
|
+
import { SaaSProvider, SignIn, UserButton } from '@saas-support/react/react'
|
|
13
13
|
|
|
14
14
|
function App() {
|
|
15
15
|
return (
|
|
16
|
-
<SaaSProvider publishableKey="pub_live_..."
|
|
17
|
-
<
|
|
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
|
|
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_..."
|
|
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
|
|
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
|
-
##
|
|
61
|
-
|
|
62
|
-
### Components
|
|
59
|
+
## Components
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
### `<SignIn />`
|
|
65
62
|
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
161
|
-
const { user, isLoaded } = useUser()
|
|
162
|
-
```
|
|
89
|
+
### `<UserButton />`
|
|
163
90
|
|
|
164
|
-
|
|
91
|
+
Avatar button that opens a dropdown with org switcher, settings, and sign-out.
|
|
165
92
|
|
|
166
93
|
```tsx
|
|
167
|
-
|
|
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
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
| `
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
240
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
119
|
+
### `<SettingsPanel />`
|
|
261
120
|
|
|
262
|
-
|
|
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 {
|
|
124
|
+
import { SettingsPanel } from '@saas-support/react/react'
|
|
284
125
|
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
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
|
-
##
|
|
358
|
-
|
|
359
|
-
### Components
|
|
145
|
+
## Hooks
|
|
360
146
|
|
|
361
|
-
|
|
147
|
+
### `useAuth()`
|
|
362
148
|
|
|
363
|
-
|
|
149
|
+
Primary auth state hook.
|
|
364
150
|
|
|
365
151
|
```tsx
|
|
366
|
-
|
|
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
|
-
|
|
|
376
|
-
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
380
|
-
| `
|
|
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
|
-
|
|
164
|
+
### `useUser()`
|
|
383
165
|
|
|
384
|
-
|
|
166
|
+
Lightweight user-only hook (no methods).
|
|
385
167
|
|
|
386
168
|
```tsx
|
|
387
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
+
Programmatic sign-in.
|
|
408
175
|
|
|
409
176
|
```tsx
|
|
410
|
-
|
|
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
|
-
|
|
435
|
-
|
|
179
|
+
const result = await signIn(email, password)
|
|
180
|
+
if (result && isMfaRequired(result)) {
|
|
181
|
+
await submitMfaCode(result.mfaToken, code)
|
|
182
|
+
}
|
|
436
183
|
|
|
437
|
-
|
|
438
|
-
dashboardId="dash_123"
|
|
439
|
-
refreshInterval={60}
|
|
440
|
-
/>
|
|
184
|
+
await signInWithOAuth('google') // or 'github'
|
|
441
185
|
```
|
|
442
186
|
|
|
443
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
Browseable list of saved queries with run buttons.
|
|
189
|
+
Programmatic sign-up.
|
|
454
190
|
|
|
455
191
|
```tsx
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
+
Profile management with avatar upload.
|
|
500
223
|
|
|
501
224
|
```tsx
|
|
502
|
-
const {
|
|
225
|
+
const { user, updateProfile, uploadAvatar, changePassword, isLoading, error, success } = useProfile()
|
|
503
226
|
|
|
504
|
-
await
|
|
227
|
+
await updateProfile({ name: 'New Name' })
|
|
228
|
+
await uploadAvatar(imageBlob)
|
|
229
|
+
await changePassword(currentPassword, newPassword)
|
|
505
230
|
```
|
|
506
231
|
|
|
507
|
-
|
|
232
|
+
### `useInvites()`
|
|
233
|
+
|
|
234
|
+
Pending invite notifications for the current user.
|
|
508
235
|
|
|
509
236
|
```tsx
|
|
510
|
-
const {
|
|
237
|
+
const { invites, accept, decline, refresh, isLoading, error } = useInvites()
|
|
511
238
|
|
|
512
|
-
|
|
239
|
+
await accept(inviteId) // Accept org invitation
|
|
240
|
+
await decline(inviteId) // Decline org invitation
|
|
513
241
|
```
|
|
514
242
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
```tsx
|
|
518
|
-
const { queries, meta, isLoading, refresh } = useSavedQueries({ page: 1, perPage: 20 })
|
|
519
|
-
```
|
|
243
|
+
### `useDeleteAccount()`
|
|
520
244
|
|
|
521
|
-
|
|
245
|
+
Account deletion.
|
|
522
246
|
|
|
523
247
|
```tsx
|
|
524
|
-
const {
|
|
248
|
+
const { deleteAccount, isLoading, error } = useDeleteAccount()
|
|
249
|
+
await deleteAccount()
|
|
525
250
|
```
|
|
526
251
|
|
|
527
|
-
|
|
252
|
+
### `useSaaSContext()`
|
|
528
253
|
|
|
529
|
-
|
|
254
|
+
Low-level context access.
|
|
530
255
|
|
|
531
256
|
```tsx
|
|
532
|
-
const {
|
|
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.
|
|
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
|
-
| `
|
|
586
|
-
| `updateProfile(
|
|
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
|
-
| `
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
|