@buildbase/sdk 0.0.41 → 0.0.42
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 +369 -329
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react/index.d.ts +38 -38
- package/dist/react/index.js +6 -6
- package/dist/react/index.mjs +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,17 +70,17 @@ npm install @buildbase/sdk react@^19.0.0 react-dom@^19.0.0
|
|
|
70
70
|
|
|
71
71
|
```tsx
|
|
72
72
|
// app/layout.tsx (or your root layout)
|
|
73
|
-
import '@buildbase/sdk/css'
|
|
73
|
+
import '@buildbase/sdk/css';
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
### 2. Create Provider
|
|
77
77
|
|
|
78
78
|
```tsx
|
|
79
79
|
// components/provider.tsx
|
|
80
|
-
'use client'
|
|
80
|
+
'use client';
|
|
81
81
|
|
|
82
|
-
import { SaaSOSProvider } from '@buildbase/sdk/react'
|
|
83
|
-
import { ApiVersion } from '@buildbase/sdk'
|
|
82
|
+
import { SaaSOSProvider } from '@buildbase/sdk/react';
|
|
83
|
+
import { ApiVersion } from '@buildbase/sdk';
|
|
84
84
|
|
|
85
85
|
export default function SaaSProvider({ children }: { children: React.ReactNode }) {
|
|
86
86
|
return (
|
|
@@ -94,40 +94,40 @@ export default function SaaSProvider({ children }: { children: React.ReactNode }
|
|
|
94
94
|
callbacks: {
|
|
95
95
|
// Called on page refresh to restore session from httpOnly cookie
|
|
96
96
|
getSession: async () => {
|
|
97
|
-
const res = await fetch('/api/auth/session')
|
|
98
|
-
const data = await res.json()
|
|
99
|
-
return data.sessionId ?? null
|
|
97
|
+
const res = await fetch('/api/auth/session');
|
|
98
|
+
const data = await res.json();
|
|
99
|
+
return data.sessionId ?? null;
|
|
100
100
|
},
|
|
101
101
|
|
|
102
102
|
// Called after OAuth redirect to exchange code for sessionId
|
|
103
|
-
handleAuthentication: async
|
|
103
|
+
handleAuthentication: async code => {
|
|
104
104
|
const res = await fetch('/api/auth/verify', {
|
|
105
105
|
method: 'POST',
|
|
106
106
|
headers: { 'Content-Type': 'application/json' },
|
|
107
107
|
body: JSON.stringify({ code }),
|
|
108
|
-
})
|
|
109
|
-
const data = await res.json()
|
|
110
|
-
return { sessionId: data.sessionId }
|
|
108
|
+
});
|
|
109
|
+
const data = await res.json();
|
|
110
|
+
return { sessionId: data.sessionId };
|
|
111
111
|
},
|
|
112
112
|
|
|
113
113
|
// Called on sign out to clear the httpOnly cookie
|
|
114
114
|
onSignOut: async () => {
|
|
115
|
-
await fetch('/api/auth/signout', { method: 'POST' })
|
|
116
|
-
window.location.reload()
|
|
115
|
+
await fetch('/api/auth/signout', { method: 'POST' });
|
|
116
|
+
window.location.reload();
|
|
117
117
|
},
|
|
118
118
|
|
|
119
119
|
handleEvent: (eventType, data) => {
|
|
120
|
-
console.log('SDK Event:', eventType, data)
|
|
120
|
+
console.log('SDK Event:', eventType, data);
|
|
121
121
|
},
|
|
122
122
|
onWorkspaceChange: async ({ workspace, user, role }) => {
|
|
123
|
-
console.log('Switching to:', workspace.name, 'as', role)
|
|
123
|
+
console.log('Switching to:', workspace.name, 'as', role);
|
|
124
124
|
},
|
|
125
125
|
},
|
|
126
126
|
}}
|
|
127
127
|
>
|
|
128
128
|
{children}
|
|
129
129
|
</SaaSOSProvider>
|
|
130
|
-
)
|
|
130
|
+
);
|
|
131
131
|
}
|
|
132
132
|
```
|
|
133
133
|
|
|
@@ -141,8 +141,8 @@ The SDK uses the same session pattern as next-auth: the session token lives in a
|
|
|
141
141
|
|
|
142
142
|
```tsx
|
|
143
143
|
// app/layout.tsx
|
|
144
|
-
import SaaSProvider from '@/components/provider'
|
|
145
|
-
import '@buildbase/sdk/css'
|
|
144
|
+
import SaaSProvider from '@/components/provider';
|
|
145
|
+
import '@buildbase/sdk/css';
|
|
146
146
|
|
|
147
147
|
export default function RootLayout({ children }) {
|
|
148
148
|
return (
|
|
@@ -151,7 +151,7 @@ export default function RootLayout({ children }) {
|
|
|
151
151
|
<SaaSProvider>{children}</SaaSProvider>
|
|
152
152
|
</body>
|
|
153
153
|
</html>
|
|
154
|
-
)
|
|
154
|
+
);
|
|
155
155
|
}
|
|
156
156
|
```
|
|
157
157
|
|
|
@@ -253,13 +253,13 @@ const {
|
|
|
253
253
|
Open the workspace settings dialog to a specific section:
|
|
254
254
|
|
|
255
255
|
```tsx
|
|
256
|
-
openWorkspaceSettings('profile');
|
|
257
|
-
openWorkspaceSettings('general');
|
|
258
|
-
openWorkspaceSettings('users');
|
|
256
|
+
openWorkspaceSettings('profile'); // Account profile
|
|
257
|
+
openWorkspaceSettings('general'); // Workspace name, icon
|
|
258
|
+
openWorkspaceSettings('users'); // Workspace members
|
|
259
259
|
openWorkspaceSettings('subscription'); // Plan & Billing
|
|
260
|
-
openWorkspaceSettings('usage');
|
|
261
|
-
openWorkspaceSettings('features');
|
|
262
|
-
openWorkspaceSettings('danger');
|
|
260
|
+
openWorkspaceSettings('usage'); // Quota usage dashboard
|
|
261
|
+
openWorkspaceSettings('features'); // Feature toggles
|
|
262
|
+
openWorkspaceSettings('danger'); // Delete workspace (owner only)
|
|
263
263
|
```
|
|
264
264
|
|
|
265
265
|
### Authentication Components
|
|
@@ -324,11 +324,11 @@ createCheckoutSession({
|
|
|
324
324
|
});
|
|
325
325
|
```
|
|
326
326
|
|
|
327
|
-
| Field | Stripe mapping
|
|
328
|
-
| ---------------------- |
|
|
329
|
-
| `clientReferenceId` | `client_reference_id`
|
|
330
|
-
| `metadata` | `metadata` (checkout session)
|
|
331
|
-
| `subscriptionMetadata` | `subscription_data.metadata`
|
|
327
|
+
| Field | Stripe mapping | Use case |
|
|
328
|
+
| ---------------------- | ----------------------------- | -------------------------------------- |
|
|
329
|
+
| `clientReferenceId` | `client_reference_id` | Rewardful, FirstPromoter, etc. |
|
|
330
|
+
| `metadata` | `metadata` (checkout session) | Custom tracking, Endorsely |
|
|
331
|
+
| `subscriptionMetadata` | `subscription_data.metadata` | Data that persists on the subscription |
|
|
332
332
|
|
|
333
333
|
## 👥 Role-Based Access Control
|
|
334
334
|
|
|
@@ -423,7 +423,11 @@ Control UI visibility based on the current workspace’s subscription. Subscript
|
|
|
423
423
|
### Subscription Gate Components
|
|
424
424
|
|
|
425
425
|
```tsx
|
|
426
|
-
import {
|
|
426
|
+
import {
|
|
427
|
+
WhenSubscription,
|
|
428
|
+
WhenNoSubscription,
|
|
429
|
+
WhenSubscriptionToPlans,
|
|
430
|
+
} from '@buildbase/sdk/react';
|
|
427
431
|
|
|
428
432
|
function BillingExample() {
|
|
429
433
|
return (
|
|
@@ -538,10 +542,10 @@ function TrialExample() {
|
|
|
538
542
|
}
|
|
539
543
|
```
|
|
540
544
|
|
|
541
|
-
| Component | Renders when
|
|
542
|
-
| ----------------- |
|
|
543
|
-
| `WhenTrialing` | Subscription status is `trialing`
|
|
544
|
-
| `WhenNotTrialing` | Subscription status is NOT `trialing`
|
|
545
|
+
| Component | Renders when |
|
|
546
|
+
| ----------------- | --------------------------------------------------------------- |
|
|
547
|
+
| `WhenTrialing` | Subscription status is `trialing` |
|
|
548
|
+
| `WhenNotTrialing` | Subscription status is NOT `trialing` |
|
|
545
549
|
| `WhenTrialEnding` | Trialing AND trial ends within `daysThreshold` days (default 3) |
|
|
546
550
|
|
|
547
551
|
All trial gates support `loadingComponent` and `fallbackComponent` props.
|
|
@@ -567,13 +571,13 @@ function TrialInfo() {
|
|
|
567
571
|
}
|
|
568
572
|
```
|
|
569
573
|
|
|
570
|
-
| Property
|
|
571
|
-
|
|
|
572
|
-
| `isTrialing`
|
|
573
|
-
| `daysRemaining`
|
|
574
|
-
| `trialEndsAt`
|
|
575
|
-
| `trialStartedAt
|
|
576
|
-
| `isTrialEnding`
|
|
574
|
+
| Property | Type | Description |
|
|
575
|
+
| ---------------- | -------------- | ------------------------------------------------- |
|
|
576
|
+
| `isTrialing` | `boolean` | Whether subscription is in trial |
|
|
577
|
+
| `daysRemaining` | `number` | Days left in trial (0 if not trialing or expired) |
|
|
578
|
+
| `trialEndsAt` | `Date \| null` | Trial end date |
|
|
579
|
+
| `trialStartedAt` | `Date \| null` | Trial start date |
|
|
580
|
+
| `isTrialEnding` | `boolean` | True when 3 or fewer days remaining |
|
|
577
581
|
|
|
578
582
|
## 🔔 Push Notifications
|
|
579
583
|
|
|
@@ -582,7 +586,7 @@ Browser push notifications — built into the SDK. Users can enable/disable from
|
|
|
582
586
|
**Only setup required:** Create `public/push-sw.js` in your app:
|
|
583
587
|
|
|
584
588
|
```js
|
|
585
|
-
self.addEventListener('push', function(event) {
|
|
589
|
+
self.addEventListener('push', function (event) {
|
|
586
590
|
if (!event.data) return;
|
|
587
591
|
try {
|
|
588
592
|
var payload = event.data.json();
|
|
@@ -601,17 +605,23 @@ self.addEventListener('push', function(event) {
|
|
|
601
605
|
data: { url: payload.url, ...(payload.data || {}) },
|
|
602
606
|
};
|
|
603
607
|
event.waitUntil(self.registration.showNotification(payload.title || 'Notification', options));
|
|
604
|
-
} catch (e) {
|
|
608
|
+
} catch (e) {
|
|
609
|
+
console.error('[PushSW]', e);
|
|
610
|
+
}
|
|
605
611
|
});
|
|
606
612
|
|
|
607
|
-
self.addEventListener('notificationclick', function(event) {
|
|
613
|
+
self.addEventListener('notificationclick', function (event) {
|
|
608
614
|
event.notification.close();
|
|
609
615
|
var url = event.notification.data && event.notification.data.url;
|
|
610
616
|
if (url) {
|
|
611
|
-
event.waitUntil(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
617
|
+
event.waitUntil(
|
|
618
|
+
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (list) {
|
|
619
|
+
for (var i = 0; i < list.length; i++) {
|
|
620
|
+
if (list[i].url === url && 'focus' in list[i]) return list[i].focus();
|
|
621
|
+
}
|
|
622
|
+
if (clients.openWindow) return clients.openWindow(url);
|
|
623
|
+
})
|
|
624
|
+
);
|
|
615
625
|
}
|
|
616
626
|
});
|
|
617
627
|
```
|
|
@@ -629,16 +639,16 @@ Send email and push notifications to workspace members. The system has three lay
|
|
|
629
639
|
### Sending Notifications (Server-Side)
|
|
630
640
|
|
|
631
641
|
```ts
|
|
632
|
-
import { notification } from '@/lib/buildbase'
|
|
642
|
+
import { notification } from '@/lib/buildbase';
|
|
633
643
|
|
|
634
644
|
// Notify a specific user
|
|
635
645
|
await notification.send(workspaceId, 'comment_added', userId, {
|
|
636
|
-
title: 'New Comment',
|
|
646
|
+
title: 'New Comment', // Push title (falls back to event name)
|
|
637
647
|
message: 'Alice commented on your project', // Push body + email {{message}}
|
|
638
648
|
icon: 'https://example.com/comment-icon.png', // Custom push icon (falls back to org icon)
|
|
639
|
-
image: 'https://example.com/screenshot.jpg',
|
|
649
|
+
image: 'https://example.com/screenshot.jpg', // Large image in push notification body
|
|
640
650
|
url: 'https://app.example.com/projects/123#comments', // Opens on push click + {{url}} in email
|
|
641
|
-
})
|
|
651
|
+
});
|
|
642
652
|
|
|
643
653
|
// Notify all workspace members (omit userId)
|
|
644
654
|
await notification.send(workspaceId, 'new_release', undefined, {
|
|
@@ -646,7 +656,7 @@ await notification.send(workspaceId, 'new_release', undefined, {
|
|
|
646
656
|
message: 'Version 2.0 is now available with dark mode and API v2!',
|
|
647
657
|
image: 'https://example.com/release-banner.jpg',
|
|
648
658
|
url: 'https://app.example.com/changelog',
|
|
649
|
-
})
|
|
659
|
+
});
|
|
650
660
|
```
|
|
651
661
|
|
|
652
662
|
### Ad-hoc Notifications
|
|
@@ -661,7 +671,7 @@ await notification.send(workspaceId, 'deployment_success', userId, {
|
|
|
661
671
|
icon: 'https://example.com/deploy-icon.png',
|
|
662
672
|
url: '/deployments/latest',
|
|
663
673
|
channels: { push: true },
|
|
664
|
-
})
|
|
674
|
+
});
|
|
665
675
|
```
|
|
666
676
|
|
|
667
677
|
> **Note:** Ad-hoc events support push only. Email requires a registered event with a linked email template.
|
|
@@ -679,20 +689,20 @@ await notification.send(workspaceId, 'new_message', userId, {
|
|
|
679
689
|
{ action: 'reply', title: 'Reply', icon: 'https://example.com/reply.png' },
|
|
680
690
|
{ action: 'dismiss', title: 'Dismiss' },
|
|
681
691
|
],
|
|
682
|
-
tag: 'chat-alice',
|
|
683
|
-
renotify: true,
|
|
692
|
+
tag: 'chat-alice', // Replaces previous "chat-alice" notification instead of stacking
|
|
693
|
+
renotify: true, // Still vibrate/sound when replacing
|
|
684
694
|
channels: { push: true },
|
|
685
|
-
})
|
|
695
|
+
});
|
|
686
696
|
|
|
687
697
|
// Critical alert — stays visible until user interacts
|
|
688
698
|
await notification.send(workspaceId, 'payment_failed', userId, {
|
|
689
699
|
title: 'Payment Failed',
|
|
690
700
|
message: 'Your subscription will be suspended in 3 days',
|
|
691
701
|
badge: 'https://example.com/alert-badge.png',
|
|
692
|
-
requireInteraction: true,
|
|
693
|
-
urgency: 'high',
|
|
702
|
+
requireInteraction: true, // No auto-dismiss
|
|
703
|
+
urgency: 'high', // Prioritized delivery on mobile
|
|
694
704
|
channels: { push: true },
|
|
695
|
-
})
|
|
705
|
+
});
|
|
696
706
|
|
|
697
707
|
// Silent notification — no sound or vibration
|
|
698
708
|
await notification.send(workspaceId, 'sync_complete', userId, {
|
|
@@ -701,19 +711,19 @@ await notification.send(workspaceId, 'sync_complete', userId, {
|
|
|
701
711
|
silent: true,
|
|
702
712
|
urgency: 'low',
|
|
703
713
|
channels: { push: true },
|
|
704
|
-
})
|
|
714
|
+
});
|
|
705
715
|
```
|
|
706
716
|
|
|
707
|
-
| Option
|
|
708
|
-
|
|
709
|
-
| `badge`
|
|
710
|
-
| `tag`
|
|
711
|
-
| `actions`
|
|
712
|
-
| `silent`
|
|
713
|
-
| `requireInteraction` | `boolean`
|
|
714
|
-
| `renotify`
|
|
715
|
-
| `timestamp`
|
|
716
|
-
| `dir`
|
|
717
|
+
| Option | Type | Description |
|
|
718
|
+
| -------------------- | ------------------------------- | ---------------------------------------------------------------- |
|
|
719
|
+
| `badge` | `string` | Small monochrome icon for status bar (Android/ChromeOS) |
|
|
720
|
+
| `tag` | `string` | Replaces existing notification with same tag instead of stacking |
|
|
721
|
+
| `actions` | `Array<{action, title, icon?}>` | Up to 2 action buttons on the notification |
|
|
722
|
+
| `silent` | `boolean` | No sound or vibration |
|
|
723
|
+
| `requireInteraction` | `boolean` | Stays visible until user interacts |
|
|
724
|
+
| `renotify` | `boolean` | Sound/vibrate again when replacing via `tag` |
|
|
725
|
+
| `timestamp` | `number` | Custom timestamp (ms since epoch) shown on notification |
|
|
726
|
+
| `dir` | `'ltr' \| 'rtl' \| 'auto'` | Text direction for title/body |
|
|
717
727
|
|
|
718
728
|
### Delivery Options
|
|
719
729
|
|
|
@@ -724,26 +734,26 @@ Control how and when the push service delivers the notification:
|
|
|
724
734
|
await notification.send(workspaceId, 'daily_digest', undefined, {
|
|
725
735
|
title: 'Your Daily Digest',
|
|
726
736
|
message: '12 new updates in your workspace',
|
|
727
|
-
scheduledAt: '2026-04-16T09:00:00Z',
|
|
737
|
+
scheduledAt: '2026-04-16T09:00:00Z', // Deliver at 9am UTC tomorrow
|
|
728
738
|
channels: { push: true },
|
|
729
|
-
})
|
|
739
|
+
});
|
|
730
740
|
|
|
731
741
|
// Short-lived notification — discard if not delivered in 1 hour
|
|
732
742
|
await notification.send(workspaceId, 'flash_sale', undefined, {
|
|
733
743
|
title: 'Flash Sale — 50% off!',
|
|
734
744
|
message: 'Ends in 1 hour',
|
|
735
745
|
image: 'https://example.com/sale-banner.jpg',
|
|
736
|
-
ttl: 3600,
|
|
737
|
-
urgency: 'high',
|
|
746
|
+
ttl: 3600, // Expires after 1 hour (seconds)
|
|
747
|
+
urgency: 'high', // Deliver ASAP
|
|
738
748
|
channels: { push: true },
|
|
739
|
-
})
|
|
749
|
+
});
|
|
740
750
|
```
|
|
741
751
|
|
|
742
|
-
| Option
|
|
743
|
-
|
|
744
|
-
| `scheduledAt` | `string`
|
|
745
|
-
| `ttl`
|
|
746
|
-
| `urgency`
|
|
752
|
+
| Option | Type | Description |
|
|
753
|
+
| ------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
754
|
+
| `scheduledAt` | `string` | ISO 8601 date. Delays delivery until the specified time |
|
|
755
|
+
| `ttl` | `number` | Time-to-live in seconds. Push service discards if not delivered in time. Default: 86400 (24h) |
|
|
756
|
+
| `urgency` | `'very-low' \| 'low' \| 'normal' \| 'high'` | Delivery priority hint. Affects battery usage on mobile |
|
|
747
757
|
|
|
748
758
|
### Channel Control
|
|
749
759
|
|
|
@@ -754,19 +764,19 @@ By default, both email and push are sent (based on event config). Override per-s
|
|
|
754
764
|
await notification.send(workspaceId, 'typing_indicator', userId, {
|
|
755
765
|
message: 'Alice is typing...',
|
|
756
766
|
channels: { push: true },
|
|
757
|
-
})
|
|
767
|
+
});
|
|
758
768
|
|
|
759
769
|
// Only email — digest or report, no push
|
|
760
770
|
await notification.send(workspaceId, 'weekly_report', undefined, {
|
|
761
771
|
message: 'Your weekly activity report is ready',
|
|
762
772
|
channels: { email: true },
|
|
763
|
-
})
|
|
773
|
+
});
|
|
764
774
|
|
|
765
775
|
// Both channels explicitly
|
|
766
776
|
await notification.send(workspaceId, 'comment_added', userId, {
|
|
767
777
|
message: 'New comment on your project',
|
|
768
778
|
channels: { email: true, push: true },
|
|
769
|
-
})
|
|
779
|
+
});
|
|
770
780
|
```
|
|
771
781
|
|
|
772
782
|
> **Note:** Even with `channels` override, the 4-layer gate still applies. If the admin disabled push globally, `channels: { push: true }` won't send push.
|
|
@@ -777,21 +787,21 @@ Both email and push support merge tags with `{{tag}}` syntax:
|
|
|
777
787
|
|
|
778
788
|
```ts
|
|
779
789
|
await notification.send(workspaceId, 'export_ready', userId, {
|
|
780
|
-
title: '{{workspaceName}} — Export Ready',
|
|
790
|
+
title: '{{workspaceName}} — Export Ready', // → "Acme Corp — Export Ready"
|
|
781
791
|
message: 'Hi {{name}}, your export is ready', // → "Hi Alice, your export is ready"
|
|
782
792
|
downloadUrl: 'https://example.com/exports/123',
|
|
783
793
|
fileName: 'report.csv',
|
|
784
|
-
})
|
|
794
|
+
});
|
|
785
795
|
```
|
|
786
796
|
|
|
787
|
-
| Tag
|
|
788
|
-
|
|
789
|
-
| `{{name}}`
|
|
790
|
-
| `{{email}}`
|
|
791
|
-
| `{{workspaceName}}` | Workspace name
|
|
792
|
-
| `{{message}}`
|
|
793
|
-
| `{{url}}`
|
|
794
|
-
| `{{anyKey}}`
|
|
797
|
+
| Tag | Resolves to | Available in |
|
|
798
|
+
| ------------------- | ------------------------ | ---------------------------------- |
|
|
799
|
+
| `{{name}}` | Recipient's name | Email + Push |
|
|
800
|
+
| `{{email}}` | Recipient's email | Email + Push |
|
|
801
|
+
| `{{workspaceName}}` | Workspace name | Email + Push |
|
|
802
|
+
| `{{message}}` | The `message` field | Email template |
|
|
803
|
+
| `{{url}}` | The `url` field | Email template + Push click target |
|
|
804
|
+
| `{{anyKey}}` | Value from `data` object | Email + Push |
|
|
795
805
|
|
|
796
806
|
### Response
|
|
797
807
|
|
|
@@ -829,7 +839,7 @@ An email template is auto-created for each custom event. Edit it in **Email Temp
|
|
|
829
839
|
The workspace settings panel shows notification preferences automatically — **only for events where the developer enabled "User Control"**. System notifications are never shown to end users.
|
|
830
840
|
|
|
831
841
|
```tsx
|
|
832
|
-
import { SaaSOSProvider } from '@buildbase/sdk/react'
|
|
842
|
+
import { SaaSOSProvider } from '@buildbase/sdk/react';
|
|
833
843
|
|
|
834
844
|
// The Notifications tab in workspace settings shows:
|
|
835
845
|
// - Browser push toggle (subscribe/unsubscribe)
|
|
@@ -839,34 +849,34 @@ import { SaaSOSProvider } from '@buildbase/sdk/react'
|
|
|
839
849
|
### Types
|
|
840
850
|
|
|
841
851
|
```ts
|
|
842
|
-
import type { NotificationData, NotificationResult, NotificationEvent } from '@buildbase/sdk'
|
|
852
|
+
import type { NotificationData, NotificationResult, NotificationEvent } from '@buildbase/sdk';
|
|
843
853
|
|
|
844
854
|
interface NotificationData {
|
|
845
|
-
title?: string;
|
|
846
|
-
message?: string;
|
|
847
|
-
icon?: string;
|
|
848
|
-
image?: string;
|
|
849
|
-
badge?: string;
|
|
850
|
-
url?: string;
|
|
851
|
-
tag?: string;
|
|
852
|
-
actions?: Array<{action
|
|
853
|
-
silent?: boolean;
|
|
854
|
-
requireInteraction?: boolean;
|
|
855
|
-
renotify?: boolean;
|
|
856
|
-
timestamp?: number;
|
|
857
|
-
dir?: 'ltr' | 'rtl' | 'auto';
|
|
858
|
-
ttl?: number;
|
|
859
|
-
urgency?: 'very-low'|'low'|'normal'|'high'; // Delivery priority
|
|
860
|
-
scheduledAt?: string;
|
|
855
|
+
title?: string; // Push title (falls back to event name)
|
|
856
|
+
message?: string; // Push body + email {{message}}
|
|
857
|
+
icon?: string; // Custom push icon URL (falls back to org icon)
|
|
858
|
+
image?: string; // Large image in push notification body
|
|
859
|
+
badge?: string; // Small monochrome status bar icon
|
|
860
|
+
url?: string; // Opens on push click + {{url}} in email
|
|
861
|
+
tag?: string; // Replace instead of stack notifications
|
|
862
|
+
actions?: Array<{ action; title; icon? }>; // Action buttons (max 2)
|
|
863
|
+
silent?: boolean; // No sound/vibration
|
|
864
|
+
requireInteraction?: boolean; // No auto-dismiss
|
|
865
|
+
renotify?: boolean; // Re-alert on tag replace
|
|
866
|
+
timestamp?: number; // Custom timestamp (ms)
|
|
867
|
+
dir?: 'ltr' | 'rtl' | 'auto'; // Text direction
|
|
868
|
+
ttl?: number; // Time-to-live (seconds)
|
|
869
|
+
urgency?: 'very-low' | 'low' | 'normal' | 'high'; // Delivery priority
|
|
870
|
+
scheduledAt?: string; // ISO 8601 delayed delivery
|
|
861
871
|
channels?: { email?: boolean; push?: boolean }; // Override which channels to use
|
|
862
|
-
[key: string]: any;
|
|
872
|
+
[key: string]: any; // Custom merge tags for email + push
|
|
863
873
|
}
|
|
864
874
|
|
|
865
875
|
interface NotificationResult {
|
|
866
876
|
sent: boolean;
|
|
867
877
|
channels: { email: boolean; push: boolean };
|
|
868
878
|
notifiedCount?: number;
|
|
869
|
-
reason?: string;
|
|
879
|
+
reason?: string; // Only when sent=false
|
|
870
880
|
}
|
|
871
881
|
|
|
872
882
|
interface NotificationEvent {
|
|
@@ -885,23 +895,21 @@ The SDK supports 8 locales with ICU MessageFormat for plurals, selects, and numb
|
|
|
885
895
|
### Setup
|
|
886
896
|
|
|
887
897
|
```tsx
|
|
888
|
-
<SaaSOSProvider locale="hi">
|
|
889
|
-
{/* All SDK UI renders in Hindi */}
|
|
890
|
-
</SaaSOSProvider>
|
|
898
|
+
<SaaSOSProvider locale="hi">{/* All SDK UI renders in Hindi */}</SaaSOSProvider>
|
|
891
899
|
```
|
|
892
900
|
|
|
893
901
|
### Supported Locales
|
|
894
902
|
|
|
895
|
-
| Code | Language | Numerals
|
|
896
|
-
|
|
897
|
-
| `en` | English
|
|
898
|
-
| `es` | Spanish
|
|
899
|
-
| `fr` | French
|
|
900
|
-
| `de` | German
|
|
901
|
-
| `ja` | Japanese | 1,234.56
|
|
902
|
-
| `zh` | Chinese
|
|
903
|
-
| `hi` | Hindi
|
|
904
|
-
| `ar` | Arabic
|
|
903
|
+
| Code | Language | Numerals | Direction |
|
|
904
|
+
| ---- | -------- | ------------------------- | --------- |
|
|
905
|
+
| `en` | English | 1,234.56 | LTR |
|
|
906
|
+
| `es` | Spanish | 1.234,56 | LTR |
|
|
907
|
+
| `fr` | French | 1 234,56 | LTR |
|
|
908
|
+
| `de` | German | 1.234,56 | LTR |
|
|
909
|
+
| `ja` | Japanese | 1,234.56 | LTR |
|
|
910
|
+
| `zh` | Chinese | 1,234.56 | LTR |
|
|
911
|
+
| `hi` | Hindi | Devanagari (e.g. 1,234) | LTR |
|
|
912
|
+
| `ar` | Arabic | Arabic-Indic (e.g. 1,234) | RTL |
|
|
905
913
|
|
|
906
914
|
### useTranslation Hook
|
|
907
915
|
|
|
@@ -913,10 +921,10 @@ function MyComponent() {
|
|
|
913
921
|
|
|
914
922
|
return (
|
|
915
923
|
<div dir={dir}>
|
|
916
|
-
<p>{t('subscription.currentPlan')}</p>
|
|
917
|
-
<p>{t('users.memberCount', { count: 5 })}</p>
|
|
918
|
-
<p>{fmtNum(1234)}</p>
|
|
919
|
-
<p>{fmtCents(1999, 'usd')}</p>
|
|
924
|
+
<p>{t('subscription.currentPlan')}</p> {/* Type-safe key lookup */}
|
|
925
|
+
<p>{t('users.memberCount', { count: 5 })}</p> {/* ICU plural: "5 members" */}
|
|
926
|
+
<p>{fmtNum(1234)}</p> {/* Locale-aware: "1,234" or "1,234" */}
|
|
927
|
+
<p>{fmtCents(1999, 'usd')}</p> {/* "$19.99" or "19.99 US$" */}
|
|
920
928
|
</div>
|
|
921
929
|
);
|
|
922
930
|
}
|
|
@@ -958,13 +966,13 @@ For full SaaS platforms (Slack, GitHub, Discord):
|
|
|
958
966
|
|
|
959
967
|
Platform mode supports granular overrides from the admin dashboard:
|
|
960
968
|
|
|
961
|
-
| Setting
|
|
962
|
-
|
|
963
|
-
| Can Create Workspace
|
|
964
|
-
| Can Invite Members
|
|
965
|
-
| Show Workspace Switcher
|
|
966
|
-
| Max Workspaces Per User
|
|
967
|
-
| Auto-Create First Workspace | On / Off
|
|
969
|
+
| Setting | Options | Default |
|
|
970
|
+
| --------------------------- | -------------------------------- | -------- |
|
|
971
|
+
| Can Create Workspace | Everyone / Owner Only / Disabled | Everyone |
|
|
972
|
+
| Can Invite Members | Everyone / Admin Only / Disabled | Everyone |
|
|
973
|
+
| Show Workspace Switcher | On / Off | On |
|
|
974
|
+
| Max Workspaces Per User | 0 (unlimited) or a number | 0 |
|
|
975
|
+
| Auto-Create First Workspace | On / Off | On |
|
|
968
976
|
|
|
969
977
|
These let you achieve team-like, managed, or enterprise-like behavior without a separate mode.
|
|
970
978
|
|
|
@@ -1113,16 +1121,17 @@ function PublicPricingPage() {
|
|
|
1113
1121
|
```
|
|
1114
1122
|
|
|
1115
1123
|
`selectPlan()` handles everything automatically:
|
|
1124
|
+
|
|
1116
1125
|
- **Authenticated** → opens the "Choose Your Plan" dialog
|
|
1117
1126
|
- **Not authenticated** → saves a redirect URL, triggers sign-in, and after login the user lands on the dashboard with the plan picker dialog open
|
|
1118
1127
|
|
|
1119
|
-
| Prop | Type | Description
|
|
1120
|
-
| ----------------- | ------------------------------ |
|
|
1121
|
-
| `slug` | `string` | Plan group slug (e.g. 'main-pricing', 'enterprise')
|
|
1122
|
-
| `children` | `(details) => ReactNode` | Render prop receiving plan details (see below)
|
|
1128
|
+
| Prop | Type | Description |
|
|
1129
|
+
| ----------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
|
|
1130
|
+
| `slug` | `string` | Plan group slug (e.g. 'main-pricing', 'enterprise') |
|
|
1131
|
+
| `children` | `(details) => ReactNode` | Render prop receiving plan details (see below) |
|
|
1123
1132
|
| `redirectBaseUrl` | `string` | Base URL for post-login redirects (e.g. `"https://app.com/dashboard"`). Enables `selectPlan()` for unauthenticated users. |
|
|
1124
|
-
| `loadingFallback` | `ReactNode` | Custom loading UI (defaults to skeleton)
|
|
1125
|
-
| `errorFallback` | `(error: string) => ReactNode` | Custom error UI
|
|
1133
|
+
| `loadingFallback` | `ReactNode` | Custom loading UI (defaults to skeleton) |
|
|
1134
|
+
| `errorFallback` | `(error: string) => ReactNode` | Custom error UI |
|
|
1126
1135
|
|
|
1127
1136
|
**Render prop details**: `{ loading, error, items, plans, notes, refetch, selectPlan }`
|
|
1128
1137
|
|
|
@@ -1138,41 +1147,41 @@ Plans support **pricing variants** (multi-currency). Use these utilities for dis
|
|
|
1138
1147
|
|
|
1139
1148
|
### Currency utilities
|
|
1140
1149
|
|
|
1141
|
-
| Export
|
|
1142
|
-
|
|
1143
|
-
| `CURRENCY_DISPLAY`
|
|
1144
|
-
| `CURRENCY_FLAG`
|
|
1145
|
-
| `PLAN_CURRENCY_CODES`
|
|
1146
|
-
| `PLAN_CURRENCY_OPTIONS`
|
|
1147
|
-
| `getCurrencySymbol(currency)`
|
|
1148
|
-
| `getCurrencyFlag(currency)`
|
|
1149
|
-
| `formatCents(cents, currency)`
|
|
1150
|
-
| `formatOverageRate(cents, currency)` | Format overage rate for display
|
|
1151
|
-
| `formatOverageRateWithLabel(...)`
|
|
1152
|
-
| `formatQuotaIncludedOverage(...)`
|
|
1153
|
-
| `getQuotaUnitLabelFromName(name)`
|
|
1150
|
+
| Export | Purpose |
|
|
1151
|
+
| ------------------------------------ | --------------------------------------------------------- |
|
|
1152
|
+
| `CURRENCY_DISPLAY` | Map of currency code → symbol (e.g. `usd` → `$`) |
|
|
1153
|
+
| `CURRENCY_FLAG` | Map of currency code → flag emoji |
|
|
1154
|
+
| `PLAN_CURRENCY_CODES` | Allowed billing currency codes (for dropdowns/validation) |
|
|
1155
|
+
| `PLAN_CURRENCY_OPTIONS` | Options array for plan currency selects |
|
|
1156
|
+
| `getCurrencySymbol(currency)` | Symbol for a Stripe currency code |
|
|
1157
|
+
| `getCurrencyFlag(currency)` | Flag emoji for a currency code |
|
|
1158
|
+
| `formatCents(cents, currency)` | Format cents as localized price string |
|
|
1159
|
+
| `formatOverageRate(cents, currency)` | Format overage rate for display |
|
|
1160
|
+
| `formatOverageRateWithLabel(...)` | Overage rate with optional unit label |
|
|
1161
|
+
| `formatQuotaIncludedOverage(...)` | "X included, then $Y / unit" style text |
|
|
1162
|
+
| `getQuotaUnitLabelFromName(name)` | Human-readable unit label from quota name |
|
|
1154
1163
|
|
|
1155
1164
|
### Pricing variant utilities
|
|
1156
1165
|
|
|
1157
|
-
| Export
|
|
1158
|
-
|
|
1159
|
-
| `getPricingVariant(planVersion, currency)`
|
|
1160
|
-
| `getBasePriceCents(planVersion, currency, interval)`
|
|
1161
|
-
| `getStripePriceIdForInterval(planVersion, currency, interval)`
|
|
1162
|
-
| `getQuotaOverageCents(planVersion, currency, quotaSlug, interval)`
|
|
1163
|
-
| `getQuotaDisplayWithVariant(planVersion, currency, quotaSlug, interval)` | Display value with overage for a variant
|
|
1164
|
-
| `getAvailableCurrenciesFromPlans(plans)`
|
|
1165
|
-
| `getDisplayCurrency(planVersion, currency)`
|
|
1166
|
-
| `getBillingIntervalAndCurrencyFromPriceId(planVersions, priceId)`
|
|
1166
|
+
| Export | Purpose |
|
|
1167
|
+
| ------------------------------------------------------------------------ | ------------------------------------------------------------ |
|
|
1168
|
+
| `getPricingVariant(planVersion, currency)` | Get variant for a currency, or `null` |
|
|
1169
|
+
| `getBasePriceCents(planVersion, currency, interval)` | Base price in cents for currency/interval |
|
|
1170
|
+
| `getStripePriceIdForInterval(planVersion, currency, interval)` | Stripe price ID for checkout |
|
|
1171
|
+
| `getQuotaOverageCents(planVersion, currency, quotaSlug, interval)` | Overage cents for a quota |
|
|
1172
|
+
| `getQuotaDisplayWithVariant(planVersion, currency, quotaSlug, interval)` | Display value with overage for a variant |
|
|
1173
|
+
| `getAvailableCurrenciesFromPlans(plans)` | Unique currency codes across plan versions |
|
|
1174
|
+
| `getDisplayCurrency(planVersion, currency)` | Display currency (variant exists ? currency : plan.currency) |
|
|
1175
|
+
| `getBillingIntervalAndCurrencyFromPriceId(planVersions, priceId)` | Resolve price ID to interval + currency |
|
|
1167
1176
|
|
|
1168
1177
|
Types: `IPricingVariant`, `PlanVersionWithPricingVariants`, `QuotaDisplayWithOverage`.
|
|
1169
1178
|
|
|
1170
1179
|
### Quota utilities
|
|
1171
1180
|
|
|
1172
|
-
| Export
|
|
1173
|
-
|
|
1181
|
+
| Export | Purpose |
|
|
1182
|
+
| -------------------------------------------------- | ------------------------------------------------------------------- |
|
|
1174
1183
|
| `getQuotaDisplayValue(quotaByInterval, interval?)` | Normalize `IQuotaByInterval` to `{ included, overage?, unitSize? }` |
|
|
1175
|
-
| `formatQuotaWithPrice(value, unitName, options?)`
|
|
1184
|
+
| `formatQuotaWithPrice(value, unitName, options?)` | Format as "X included, then $Y.YY / unit" |
|
|
1176
1185
|
|
|
1177
1186
|
Types: `QuotaDisplayValue`, `FormatQuotaWithPriceOptions`. Plan/subscription types use `IQuotaByInterval` and `IQuotaIntervalValue` for per-interval quotas and overages.
|
|
1178
1187
|
|
|
@@ -1204,12 +1213,12 @@ Track and monitor metered usage for subscription quotas (e.g., API calls, emails
|
|
|
1204
1213
|
|
|
1205
1214
|
### When to use which?
|
|
1206
1215
|
|
|
1207
|
-
| Scenario
|
|
1208
|
-
|
|
1216
|
+
| Scenario | Where to record | Why |
|
|
1217
|
+
| ------------------------------- | ---------------------- | -------------------------------------------- |
|
|
1209
1218
|
| User clicks "Send Email" button | Client-side (SDK hook) | User-initiated, immediate UI feedback needed |
|
|
1210
|
-
| API request hits your backend
|
|
1211
|
-
| Background job processes data
|
|
1212
|
-
| File upload completes
|
|
1219
|
+
| API request hits your backend | Server-side (REST API) | Backend controls the resource, more secure |
|
|
1220
|
+
| Background job processes data | Server-side (REST API) | No browser context available |
|
|
1221
|
+
| File upload completes | Either | Depends on where validation happens |
|
|
1213
1222
|
|
|
1214
1223
|
As a general rule: **record usage where the resource is consumed**. If your backend processes the work, record from the backend. If it's a client-side action, record from the client.
|
|
1215
1224
|
|
|
@@ -1233,8 +1242,8 @@ function SendEmailButton() {
|
|
|
1233
1242
|
const result = await recordUsage({
|
|
1234
1243
|
quotaSlug: 'emails',
|
|
1235
1244
|
quantity: 1,
|
|
1236
|
-
source: 'web-app',
|
|
1237
|
-
idempotencyKey: 'email-abc',
|
|
1245
|
+
source: 'web-app', // optional: track where usage came from
|
|
1246
|
+
idempotencyKey: 'email-abc', // optional: prevent duplicate recordings
|
|
1238
1247
|
});
|
|
1239
1248
|
console.log(`Used: ${result.consumed}/${result.included}, Available: ${result.available}`);
|
|
1240
1249
|
if (result.overage > 0) {
|
|
@@ -1245,7 +1254,11 @@ function SendEmailButton() {
|
|
|
1245
1254
|
}
|
|
1246
1255
|
};
|
|
1247
1256
|
|
|
1248
|
-
return
|
|
1257
|
+
return (
|
|
1258
|
+
<button onClick={handleSend} disabled={loading}>
|
|
1259
|
+
Send Email
|
|
1260
|
+
</button>
|
|
1261
|
+
);
|
|
1249
1262
|
}
|
|
1250
1263
|
```
|
|
1251
1264
|
|
|
@@ -1265,7 +1278,9 @@ function QuotaStatusBar({ quotaSlug }: { quotaSlug: string }) {
|
|
|
1265
1278
|
|
|
1266
1279
|
return (
|
|
1267
1280
|
<div>
|
|
1268
|
-
<p>
|
|
1281
|
+
<p>
|
|
1282
|
+
{quotaSlug}: {status.consumed} / {status.included} ({usagePercent}%)
|
|
1283
|
+
</p>
|
|
1269
1284
|
<p>Available: {status.available}</p>
|
|
1270
1285
|
{status.hasOverage && <p>Overage: {status.overage} units</p>}
|
|
1271
1286
|
<button onClick={refetch}>Refresh</button>
|
|
@@ -1306,12 +1321,10 @@ import { useUsageLogs, useSaaSWorkspaces } from '@buildbase/sdk/react';
|
|
|
1306
1321
|
|
|
1307
1322
|
function UsageLogsTable() {
|
|
1308
1323
|
const { currentWorkspace } = useSaaSWorkspaces();
|
|
1309
|
-
const {
|
|
1310
|
-
logs, totalDocs, totalPages, page, hasNextPage, loading, refetch,
|
|
1311
|
-
} = useUsageLogs(
|
|
1324
|
+
const { logs, totalDocs, totalPages, page, hasNextPage, loading, refetch } = useUsageLogs(
|
|
1312
1325
|
currentWorkspace?._id,
|
|
1313
|
-
'api_calls',
|
|
1314
|
-
{ limit: 20, page: 1 }
|
|
1326
|
+
'api_calls', // optional: filter by quota slug
|
|
1327
|
+
{ limit: 20, page: 1 } // optional: pagination and filters
|
|
1315
1328
|
);
|
|
1316
1329
|
|
|
1317
1330
|
if (loading) return <Spinner />;
|
|
@@ -1320,7 +1333,12 @@ function UsageLogsTable() {
|
|
|
1320
1333
|
<div>
|
|
1321
1334
|
<table>
|
|
1322
1335
|
<thead>
|
|
1323
|
-
<tr
|
|
1336
|
+
<tr>
|
|
1337
|
+
<th>Quota</th>
|
|
1338
|
+
<th>Quantity</th>
|
|
1339
|
+
<th>Source</th>
|
|
1340
|
+
<th>Date</th>
|
|
1341
|
+
</tr>
|
|
1324
1342
|
</thead>
|
|
1325
1343
|
<tbody>
|
|
1326
1344
|
{logs.map(log => (
|
|
@@ -1333,7 +1351,9 @@ function UsageLogsTable() {
|
|
|
1333
1351
|
))}
|
|
1334
1352
|
</tbody>
|
|
1335
1353
|
</table>
|
|
1336
|
-
<p>
|
|
1354
|
+
<p>
|
|
1355
|
+
Page {page} of {totalPages} ({totalDocs} total)
|
|
1356
|
+
</p>
|
|
1337
1357
|
</div>
|
|
1338
1358
|
);
|
|
1339
1359
|
}
|
|
@@ -1341,23 +1361,23 @@ function UsageLogsTable() {
|
|
|
1341
1361
|
|
|
1342
1362
|
**`useUsageLogs` parameters:**
|
|
1343
1363
|
|
|
1344
|
-
| Parameter
|
|
1345
|
-
|
|
1346
|
-
| `workspaceId`
|
|
1347
|
-
| `quotaSlug`
|
|
1348
|
-
| `options.from`
|
|
1349
|
-
| `options.to`
|
|
1350
|
-
| `options.source` | `string`
|
|
1351
|
-
| `options.page`
|
|
1352
|
-
| `options.limit`
|
|
1364
|
+
| Parameter | Type | Required | Description |
|
|
1365
|
+
| ---------------- | ----------------------------- | -------- | ----------------------------------------------- |
|
|
1366
|
+
| `workspaceId` | `string \| null \| undefined` | Yes | Workspace ID (null/undefined disables fetching) |
|
|
1367
|
+
| `quotaSlug` | `string` | No | Filter logs by quota slug |
|
|
1368
|
+
| `options.from` | `string` | No | ISO date string — filter logs from this date |
|
|
1369
|
+
| `options.to` | `string` | No | ISO date string — filter logs until this date |
|
|
1370
|
+
| `options.source` | `string` | No | Filter logs by source |
|
|
1371
|
+
| `options.page` | `number` | No | Page number (default: 1) |
|
|
1372
|
+
| `options.limit` | `number` | No | Results per page (default: 20) |
|
|
1353
1373
|
|
|
1354
1374
|
#### Client-Side Hooks Summary
|
|
1355
1375
|
|
|
1356
|
-
| Hook
|
|
1357
|
-
|
|
1358
|
-
| `useRecordUsage(workspaceId)`
|
|
1359
|
-
| `useQuotaUsageStatus(workspaceId, quotaSlug)`
|
|
1360
|
-
| `useAllQuotaUsage(workspaceId)`
|
|
1376
|
+
| Hook | Purpose |
|
|
1377
|
+
| ------------------------------------------------- | ------------------------------------------ |
|
|
1378
|
+
| `useRecordUsage(workspaceId)` | Record quota consumption (mutation) |
|
|
1379
|
+
| `useQuotaUsageStatus(workspaceId, quotaSlug)` | Get single quota status (auto-fetches) |
|
|
1380
|
+
| `useAllQuotaUsage(workspaceId)` | Get all quotas status (auto-fetches) |
|
|
1361
1381
|
| `useUsageLogs(workspaceId, quotaSlug?, options?)` | Get paginated usage history (auto-fetches) |
|
|
1362
1382
|
|
|
1363
1383
|
---
|
|
@@ -1404,8 +1424,8 @@ async function recordUsage(workspaceId: string, quotaSlug: string, quantity: num
|
|
|
1404
1424
|
body: JSON.stringify({
|
|
1405
1425
|
quotaSlug,
|
|
1406
1426
|
quantity,
|
|
1407
|
-
source: 'backend',
|
|
1408
|
-
metadata: {},
|
|
1427
|
+
source: 'backend', // optional: helps distinguish from client-side usage
|
|
1428
|
+
metadata: {}, // optional: attach custom data
|
|
1409
1429
|
idempotencyKey: undefined, // optional: prevent duplicate recordings
|
|
1410
1430
|
}),
|
|
1411
1431
|
});
|
|
@@ -1443,10 +1463,9 @@ async function getQuotaStatus(workspaceId: string, quotaSlug: string) {
|
|
|
1443
1463
|
|
|
1444
1464
|
// Get status for ALL quotas
|
|
1445
1465
|
async function getAllQuotaStatus(workspaceId: string) {
|
|
1446
|
-
const response = await fetch(
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
);
|
|
1466
|
+
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/subscription/usage/all`, {
|
|
1467
|
+
headers: { 'x-session-id': SESSION_ID },
|
|
1468
|
+
});
|
|
1450
1469
|
return response.json();
|
|
1451
1470
|
// Returns: { quotas: { [slug]: { consumed, included, available, overage, hasOverage } } }
|
|
1452
1471
|
}
|
|
@@ -1483,26 +1502,23 @@ await bb.usage.recordBatch(workspaceId, {
|
|
|
1483
1502
|
{ quotaSlug: 'images', quantity: 500, source: 'batch-export-job' },
|
|
1484
1503
|
{ quotaSlug: 'videos', quantity: 10, source: 'batch-export-job' },
|
|
1485
1504
|
{ quotaSlug: 'images', quantity: 200, metadata: { jobId: 'abc123' } },
|
|
1486
|
-
]
|
|
1505
|
+
],
|
|
1487
1506
|
});
|
|
1488
1507
|
// Returns: { success, total: 3, succeeded: 3, failed: 0, results: [...] }
|
|
1489
1508
|
```
|
|
1490
1509
|
|
|
1491
1510
|
```ts
|
|
1492
1511
|
// Using REST API directly
|
|
1493
|
-
const response = await fetch(
|
|
1494
|
-
|
|
1495
|
-
{
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
}),
|
|
1504
|
-
}
|
|
1505
|
-
);
|
|
1512
|
+
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/subscription/usage/batch`, {
|
|
1513
|
+
method: 'POST',
|
|
1514
|
+
headers: { 'x-session-id': SESSION_ID, 'Content-Type': 'application/json' },
|
|
1515
|
+
body: JSON.stringify({
|
|
1516
|
+
items: [
|
|
1517
|
+
{ quotaSlug: 'images', quantity: 500, source: 'batch-export' },
|
|
1518
|
+
{ quotaSlug: 'videos', quantity: 10 },
|
|
1519
|
+
],
|
|
1520
|
+
}),
|
|
1521
|
+
});
|
|
1506
1522
|
```
|
|
1507
1523
|
|
|
1508
1524
|
- Max **100 items** per request
|
|
@@ -1512,35 +1528,35 @@ const response = await fetch(
|
|
|
1512
1528
|
|
|
1513
1529
|
#### Server-Side API Reference
|
|
1514
1530
|
|
|
1515
|
-
| Endpoint
|
|
1516
|
-
|
|
1517
|
-
| `/api/v1/public/token/exchange`
|
|
1518
|
-
| `/api/v1/public/workspaces/:id/subscription/usage`
|
|
1519
|
-
| `/api/v1/public/workspaces/:id/subscription/usage/status?quotaSlug=X` | GET
|
|
1520
|
-
| `/api/v1/public/workspaces/:id/subscription/usage/all`
|
|
1521
|
-
| `/api/v1/public/workspaces/:id/subscription/usage/logs`
|
|
1531
|
+
| Endpoint | Method | Description |
|
|
1532
|
+
| --------------------------------------------------------------------- | ------ | --------------------------------- |
|
|
1533
|
+
| `/api/v1/public/token/exchange` | POST | Exchange API token for session ID |
|
|
1534
|
+
| `/api/v1/public/workspaces/:id/subscription/usage` | POST | Record quota usage |
|
|
1535
|
+
| `/api/v1/public/workspaces/:id/subscription/usage/status?quotaSlug=X` | GET | Get single quota status |
|
|
1536
|
+
| `/api/v1/public/workspaces/:id/subscription/usage/all` | GET | Get all quotas status |
|
|
1537
|
+
| `/api/v1/public/workspaces/:id/subscription/usage/logs` | GET | Get paginated usage logs |
|
|
1522
1538
|
|
|
1523
1539
|
All endpoints (except `/token/exchange`) require the `x-session-id` header.
|
|
1524
1540
|
|
|
1525
1541
|
**Record usage request body:**
|
|
1526
1542
|
|
|
1527
|
-
| Field
|
|
1528
|
-
|
|
1529
|
-
| `quotaSlug`
|
|
1530
|
-
| `quantity`
|
|
1531
|
-
| `metadata`
|
|
1532
|
-
| `source`
|
|
1533
|
-
| `idempotencyKey` | `string` | No
|
|
1543
|
+
| Field | Type | Required | Description |
|
|
1544
|
+
| ---------------- | -------- | -------- | -------------------------------------------------------------- |
|
|
1545
|
+
| `quotaSlug` | `string` | Yes | Quota identifier (e.g. `'api_calls'`, `'emails'`, `'storage'`) |
|
|
1546
|
+
| `quantity` | `number` | Yes | Units to consume (minimum 1) |
|
|
1547
|
+
| `metadata` | `object` | No | Custom metadata to attach to the usage record |
|
|
1548
|
+
| `source` | `string` | No | Source identifier (e.g. `'backend'`, `'worker'`, `'cron'`) |
|
|
1549
|
+
| `idempotencyKey` | `string` | No | Unique key for deduplication |
|
|
1534
1550
|
|
|
1535
1551
|
**Record usage response:**
|
|
1536
1552
|
|
|
1537
|
-
| Field
|
|
1538
|
-
|
|
1539
|
-
| `used`
|
|
1540
|
-
| `consumed`
|
|
1541
|
-
| `included`
|
|
1542
|
-
| `available`
|
|
1543
|
-
| `overage`
|
|
1553
|
+
| Field | Type | Description |
|
|
1554
|
+
| ------------- | --------- | -------------------------------------------- |
|
|
1555
|
+
| `used` | `number` | Quantity recorded in this request |
|
|
1556
|
+
| `consumed` | `number` | Total usage in the current billing period |
|
|
1557
|
+
| `included` | `number` | Total units included in the plan |
|
|
1558
|
+
| `available` | `number` | Remaining units before overage |
|
|
1559
|
+
| `overage` | `number` | Units used beyond the included amount |
|
|
1544
1560
|
| `billedAsync` | `boolean` | Whether overage billing was queued to Stripe |
|
|
1545
1561
|
|
|
1546
1562
|
## 🚦 Quota Gates
|
|
@@ -1609,12 +1625,12 @@ All quota gate components support optional `loadingComponent` and `fallbackCompo
|
|
|
1609
1625
|
|
|
1610
1626
|
### Quota Gate Components Reference
|
|
1611
1627
|
|
|
1612
|
-
| Component
|
|
1613
|
-
|
|
1614
|
-
| `WhenQuotaAvailable` | Quota has remaining units (`available > 0`)
|
|
1615
|
-
| `WhenQuotaExhausted` | Quota is fully consumed (`available <= 0`)
|
|
1616
|
-
| `WhenQuotaOverage`
|
|
1617
|
-
| `WhenQuotaThreshold` | Usage percentage >= threshold
|
|
1628
|
+
| Component | Renders children when | Props |
|
|
1629
|
+
| -------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
1630
|
+
| `WhenQuotaAvailable` | Quota has remaining units (`available > 0`) | `slug`, `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1631
|
+
| `WhenQuotaExhausted` | Quota is fully consumed (`available <= 0`) | `slug`, `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1632
|
+
| `WhenQuotaOverage` | Usage exceeds included amount (`hasOverage`) | `slug`, `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1633
|
+
| `WhenQuotaThreshold` | Usage percentage >= threshold | `slug`, `threshold` (0-100), `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1618
1634
|
|
|
1619
1635
|
All gates must be used inside `QuotaUsageContextProvider` (included in `SaaSOSProvider`). By default they return `null` while loading or when the condition is not met.
|
|
1620
1636
|
|
|
@@ -1634,7 +1650,9 @@ function QuotaDebug() {
|
|
|
1634
1650
|
return (
|
|
1635
1651
|
<div>
|
|
1636
1652
|
{Object.entries(quotas).map(([slug, usage]) => (
|
|
1637
|
-
<p key={slug}>
|
|
1653
|
+
<p key={slug}>
|
|
1654
|
+
{slug}: {usage.consumed}/{usage.included}
|
|
1655
|
+
</p>
|
|
1638
1656
|
))}
|
|
1639
1657
|
<button onClick={() => refetch()}>Refresh</button>
|
|
1640
1658
|
</div>
|
|
@@ -1642,13 +1660,14 @@ function QuotaDebug() {
|
|
|
1642
1660
|
}
|
|
1643
1661
|
```
|
|
1644
1662
|
|
|
1645
|
-
| Property
|
|
1646
|
-
|
|
1647
|
-
| `quotas`
|
|
1648
|
-
| `loading` | `boolean`
|
|
1649
|
-
| `refetch` | `() => Promise<void>`
|
|
1663
|
+
| Property | Type | Description |
|
|
1664
|
+
| --------- | ------------------------------------------- | -------------------------------------- |
|
|
1665
|
+
| `quotas` | `Record<string, IQuotaUsageStatus> \| null` | Current quota usage data keyed by slug |
|
|
1666
|
+
| `loading` | `boolean` | True while quota data is being fetched |
|
|
1667
|
+
| `refetch` | `() => Promise<void>` | Manually refetch all quota usage |
|
|
1650
1668
|
|
|
1651
1669
|
**When quota usage refetches:**
|
|
1670
|
+
|
|
1652
1671
|
- When the current workspace changes (automatic).
|
|
1653
1672
|
- When usage is recorded via `useRecordUsage` — refetch is triggered automatically.
|
|
1654
1673
|
- When you call `refetch()` manually.
|
|
@@ -1751,25 +1770,25 @@ All SDK API clients extend a shared base class and are exported from the package
|
|
|
1751
1770
|
| `BaseApi` | Abstract base (URL, auth, `fetchJson`/`fetchResponse`) – extend for custom APIs |
|
|
1752
1771
|
| `IBaseApiConfig` | Config type: `serverUrl`, `version`, optional `orgId` |
|
|
1753
1772
|
| `UserApi` | User attributes and features |
|
|
1754
|
-
| `WorkspaceApi` | Workspaces, subscription, invoices, quota usage, users
|
|
1773
|
+
| `WorkspaceApi` | Workspaces, subscription, invoices, quota usage, users |
|
|
1755
1774
|
| `SettingsApi` | Organization settings |
|
|
1756
1775
|
|
|
1757
1776
|
### Components
|
|
1758
1777
|
|
|
1759
|
-
| Component
|
|
1760
|
-
|
|
|
1761
|
-
| `SubscriptionContextProvider`
|
|
1762
|
-
| `WhenSubscription`, `WhenNoSubscription`, `WhenSubscriptionToPlans` | Subscription gate components
|
|
1763
|
-
| `WhenTrialing`, `WhenNotTrialing`, `WhenTrialEnding`
|
|
1764
|
-
| `WhenQuotaAvailable`, `WhenQuotaExhausted`, `WhenQuotaOverage`
|
|
1778
|
+
| Component | Purpose |
|
|
1779
|
+
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
1780
|
+
| `SubscriptionContextProvider` | Provides subscription data to children (included in SaaSOSProvider) |
|
|
1781
|
+
| `WhenSubscription`, `WhenNoSubscription`, `WhenSubscriptionToPlans` | Subscription gate components |
|
|
1782
|
+
| `WhenTrialing`, `WhenNotTrialing`, `WhenTrialEnding` | Trial gate components |
|
|
1783
|
+
| `WhenQuotaAvailable`, `WhenQuotaExhausted`, `WhenQuotaOverage` | Quota gate components |
|
|
1765
1784
|
|
|
1766
1785
|
### Currency, pricing variant & quota utilities
|
|
1767
1786
|
|
|
1768
|
-
| Category
|
|
1769
|
-
|
|
|
1770
|
-
| **Currency**
|
|
1787
|
+
| Category | Exports |
|
|
1788
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1789
|
+
| **Currency** | `CURRENCY_DISPLAY`, `CURRENCY_FLAG`, `PLAN_CURRENCY_CODES`, `PLAN_CURRENCY_OPTIONS`, `getCurrencySymbol`, `getCurrencyFlag`, `formatCents`, `formatOverageRate`, `formatOverageRateWithLabel`, `formatQuotaIncludedOverage`, `getQuotaUnitLabelFromName` |
|
|
1771
1790
|
| **Pricing variants** | `getPricingVariant`, `getBasePriceCents`, `getStripePriceIdForInterval`, `getQuotaOverageCents`, `getQuotaDisplayWithVariant`, `getAvailableCurrenciesFromPlans`, `getDisplayCurrency`, `getBillingIntervalAndCurrencyFromPriceId`; types: `IPricingVariant`, `PlanVersionWithPricingVariants`, `QuotaDisplayWithOverage` |
|
|
1772
|
-
| **Quota**
|
|
1791
|
+
| **Quota** | `getQuotaDisplayValue`, `formatQuotaWithPrice`; types: `QuotaDisplayValue`, `FormatQuotaWithPriceOptions`. Plan types use `IQuotaByInterval`, `IQuotaIntervalValue` for per-interval quotas. |
|
|
1773
1792
|
|
|
1774
1793
|
Get OS config from `useSaaSOs()` and instantiate API classes when you need low-level access; otherwise prefer the high-level hooks (`useSaaSWorkspaces`, `useUserAttributes`, `useSaaSSettings`, etc.):
|
|
1775
1794
|
|
|
@@ -1790,21 +1809,21 @@ const workspaceApi = new WorkspaceApi({
|
|
|
1790
1809
|
|
|
1791
1810
|
Prefer these SDK hooks for state and operations instead of `useAppSelector`:
|
|
1792
1811
|
|
|
1793
|
-
| Hook | Purpose
|
|
1794
|
-
| -------------------------- |
|
|
1795
|
-
| `useSaaSAuth()` | Auth state (user, session, status), signIn(returnUrl?), signOut, openWorkspaceSettings
|
|
1796
|
-
| `useSaaSWorkspaces()` | Workspaces, currentWorkspace, loading, switching/switchingToId, CRUD and switch actions
|
|
1797
|
-
| `useSaaSOs()` | OS config (serverUrl, version, orgId, auth, settings) when you need the full config object
|
|
1798
|
-
| `useSaaSSettings()` | Organization settings and getSettings (prefer this when you only need settings)
|
|
1799
|
-
| `useUserAttributes()` | User attributes and update/refresh
|
|
1800
|
-
| `useUserFeatures()` | User feature flags
|
|
1801
|
-
| `useSubscriptionContext()` | Subscription for current workspace (response, loading, refetch); use inside SubscriptionContextProvider
|
|
1802
|
-
| `useTrialStatus()` | Trial state: `isTrialing`, `daysRemaining`, `trialEndsAt`, `isTrialEnding`
|
|
1803
|
-
| `usePushNotifications()` | Push notification state and actions: `isSubscribed`, `subscribe()`, `unsubscribe()`
|
|
1812
|
+
| Hook | Purpose |
|
|
1813
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1814
|
+
| `useSaaSAuth()` | Auth state (user, session, status), signIn(returnUrl?), signOut, openWorkspaceSettings |
|
|
1815
|
+
| `useSaaSWorkspaces()` | Workspaces, currentWorkspace, loading, switching/switchingToId, CRUD and switch actions |
|
|
1816
|
+
| `useSaaSOs()` | OS config (serverUrl, version, orgId, auth, settings) when you need the full config object |
|
|
1817
|
+
| `useSaaSSettings()` | Organization settings and getSettings (prefer this when you only need settings) |
|
|
1818
|
+
| `useUserAttributes()` | User attributes and update/refresh |
|
|
1819
|
+
| `useUserFeatures()` | User feature flags |
|
|
1820
|
+
| `useSubscriptionContext()` | Subscription for current workspace (response, loading, refetch); use inside SubscriptionContextProvider |
|
|
1821
|
+
| `useTrialStatus()` | Trial state: `isTrialing`, `daysRemaining`, `trialEndsAt`, `isTrialEnding` |
|
|
1822
|
+
| `usePushNotifications()` | Push notification state and actions: `isSubscribed`, `subscribe()`, `unsubscribe()` |
|
|
1804
1823
|
| Subscription hooks | `usePublicPlans`, `useSubscription`, `useSubscriptionManagement`, `usePlanGroup`, `usePlanGroupVersions`, `usePublicPlanGroupVersion`, `useCreateCheckoutSession`, `useUpdateSubscription`, `useCancelSubscription`, `useResumeSubscription`, `useInvoices`, `useInvoice` |
|
|
1805
|
-
| `useQuotaUsageContext()` | Quota usage for current workspace (quotas, loading, refetch); use inside QuotaUsageContextProvider
|
|
1806
|
-
| Quota usage hooks | `useRecordUsage`, `useQuotaUsageStatus`, `useAllQuotaUsage`, `useUsageLogs`
|
|
1807
|
-
| Invalidation helpers | `invalidateSubscription()`, `invalidateQuotaUsage()` — trigger context refetch after server-side mutations
|
|
1824
|
+
| `useQuotaUsageContext()` | Quota usage for current workspace (quotas, loading, refetch); use inside QuotaUsageContextProvider |
|
|
1825
|
+
| Quota usage hooks | `useRecordUsage`, `useQuotaUsageStatus`, `useAllQuotaUsage`, `useUsageLogs` |
|
|
1826
|
+
| Invalidation helpers | `invalidateSubscription()`, `invalidateQuotaUsage()` — trigger context refetch after server-side mutations |
|
|
1808
1827
|
|
|
1809
1828
|
Using hooks keeps your code stable if internal state shape changes and avoids direct Redux/context coupling.
|
|
1810
1829
|
|
|
@@ -1862,6 +1881,7 @@ interface OnWorkspaceChangeParams {
|
|
|
1862
1881
|
```
|
|
1863
1882
|
|
|
1864
1883
|
**Session flow** (same pattern as next-auth):
|
|
1884
|
+
|
|
1865
1885
|
- Session token is stored in an **httpOnly cookie** (set by your server, not readable by JS)
|
|
1866
1886
|
- On page refresh, the SDK calls `getSession()` once to restore the session
|
|
1867
1887
|
- Session data (user info) lives **in-memory only** (React context) — no localStorage
|
|
@@ -1874,16 +1894,16 @@ interface OnWorkspaceChangeParams {
|
|
|
1874
1894
|
|
|
1875
1895
|
### BetaForm Props
|
|
1876
1896
|
|
|
1877
|
-
| Prop
|
|
1878
|
-
|
|
|
1879
|
-
| `onSuccess`
|
|
1880
|
-
| `onError`
|
|
1881
|
-
| `className`
|
|
1882
|
-
| `fieldClassName`
|
|
1883
|
-
| `autoFocus`
|
|
1884
|
-
| `showSuccessMessage`
|
|
1885
|
-
| `hideLogo`
|
|
1886
|
-
| `hideTitles`
|
|
1897
|
+
| Prop | Type | Default | Description |
|
|
1898
|
+
| -------------------- | ------------------------- | -------------------------------- | --------------------------------------- |
|
|
1899
|
+
| `onSuccess` | `() => void` | - | Callback when form submits successfully |
|
|
1900
|
+
| `onError` | `(error: string) => void` | - | Callback when form submission fails |
|
|
1901
|
+
| `className` | `string` | `'w-full'` | CSS class for form container |
|
|
1902
|
+
| `fieldClassName` | `string` | `'flex flex-col gap-1.5 w-full'` | CSS class for form fields |
|
|
1903
|
+
| `autoFocus` | `boolean` | `true` | Auto-focus name field |
|
|
1904
|
+
| `showSuccessMessage` | `boolean` | `true` | Show success message after submit |
|
|
1905
|
+
| `hideLogo` | `boolean` | `false` | Hide logo |
|
|
1906
|
+
| `hideTitles` | `boolean` | `false` | Hide titles |
|
|
1887
1907
|
|
|
1888
1908
|
> **Note:** The form language is inherited from the `SaaSOSProvider` locale setting. Supported locales: en, es, fr, de, ja, zh, hi, ar.
|
|
1889
1909
|
|
|
@@ -1983,7 +2003,11 @@ function Dashboard() {
|
|
|
1983
2003
|
### Pattern 4b: Subscription-Gated UI
|
|
1984
2004
|
|
|
1985
2005
|
```tsx
|
|
1986
|
-
import {
|
|
2006
|
+
import {
|
|
2007
|
+
WhenSubscription,
|
|
2008
|
+
WhenNoSubscription,
|
|
2009
|
+
WhenSubscriptionToPlans,
|
|
2010
|
+
} from '@buildbase/sdk/react';
|
|
1987
2011
|
|
|
1988
2012
|
function BillingPage() {
|
|
1989
2013
|
return (
|
|
@@ -2023,7 +2047,9 @@ function FeatureWithQuota() {
|
|
|
2023
2047
|
</WhenQuotaAvailable>
|
|
2024
2048
|
|
|
2025
2049
|
<WhenQuotaExhausted slug="api_calls">
|
|
2026
|
-
<p>
|
|
2050
|
+
<p>
|
|
2051
|
+
No API calls remaining. <a href="/billing">Upgrade your plan</a>
|
|
2052
|
+
</p>
|
|
2027
2053
|
</WhenQuotaExhausted>
|
|
2028
2054
|
</div>
|
|
2029
2055
|
);
|
|
@@ -2385,31 +2411,45 @@ Configure once, use everywhere. Same pattern as Auth.js.
|
|
|
2385
2411
|
|
|
2386
2412
|
```ts
|
|
2387
2413
|
// lib/buildbase.ts
|
|
2388
|
-
import BuildBase from '@buildbase/sdk'
|
|
2389
|
-
import { cookies } from 'next/headers'
|
|
2390
|
-
|
|
2391
|
-
export const {
|
|
2414
|
+
import BuildBase from '@buildbase/sdk';
|
|
2415
|
+
import { cookies } from 'next/headers';
|
|
2416
|
+
|
|
2417
|
+
export const {
|
|
2418
|
+
auth,
|
|
2419
|
+
workspace,
|
|
2420
|
+
subscription,
|
|
2421
|
+
usage,
|
|
2422
|
+
plans,
|
|
2423
|
+
invoices,
|
|
2424
|
+
users,
|
|
2425
|
+
features,
|
|
2426
|
+
settings,
|
|
2427
|
+
notification,
|
|
2428
|
+
permissions,
|
|
2429
|
+
withSession,
|
|
2430
|
+
client,
|
|
2431
|
+
} = BuildBase({
|
|
2392
2432
|
serverUrl: process.env.BUILDBASE_URL!,
|
|
2393
2433
|
orgId: process.env.BUILDBASE_ORG_ID!,
|
|
2394
2434
|
getSessionId: async () => {
|
|
2395
|
-
const c = await cookies()
|
|
2396
|
-
return c.get('bb-session-id')?.value ?? null
|
|
2435
|
+
const c = await cookies();
|
|
2436
|
+
return c.get('bb-session-id')?.value ?? null;
|
|
2397
2437
|
},
|
|
2398
|
-
})
|
|
2438
|
+
});
|
|
2399
2439
|
```
|
|
2400
2440
|
|
|
2401
2441
|
Use in API routes — session is resolved automatically:
|
|
2402
2442
|
|
|
2403
2443
|
```ts
|
|
2404
2444
|
// app/api/workspace/route.ts
|
|
2405
|
-
import { auth, workspace, subscription } from '@/lib/buildbase'
|
|
2445
|
+
import { auth, workspace, subscription } from '@/lib/buildbase';
|
|
2406
2446
|
|
|
2407
2447
|
export async function GET() {
|
|
2408
|
-
const session = await auth()
|
|
2409
|
-
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
2448
|
+
const session = await auth();
|
|
2449
|
+
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
2410
2450
|
|
|
2411
|
-
const workspaces = await workspace.list()
|
|
2412
|
-
return Response.json({ workspaces })
|
|
2451
|
+
const workspaces = await workspace.list();
|
|
2452
|
+
return Response.json({ workspaces });
|
|
2413
2453
|
}
|
|
2414
2454
|
```
|
|
2415
2455
|
|
|
@@ -2419,31 +2459,31 @@ No `getSessionId` callback — use `withSession()` per-request instead.
|
|
|
2419
2459
|
|
|
2420
2460
|
```ts
|
|
2421
2461
|
// buildbase.ts
|
|
2422
|
-
import BuildBase from '@buildbase/sdk'
|
|
2462
|
+
import BuildBase from '@buildbase/sdk';
|
|
2423
2463
|
|
|
2424
2464
|
const bb = BuildBase({
|
|
2425
2465
|
serverUrl: process.env.BUILDBASE_URL!,
|
|
2426
2466
|
orgId: process.env.BUILDBASE_ORG_ID!,
|
|
2427
|
-
})
|
|
2467
|
+
});
|
|
2428
2468
|
|
|
2429
|
-
export const { withSession, plans } = bb
|
|
2469
|
+
export const { withSession, plans } = bb;
|
|
2430
2470
|
```
|
|
2431
2471
|
|
|
2432
2472
|
```ts
|
|
2433
2473
|
// routes
|
|
2434
2474
|
app.get('/workspaces', async (req, res) => {
|
|
2435
|
-
const { workspace } = withSession(req.headers['x-session-id'])
|
|
2436
|
-
res.json(await workspace.list())
|
|
2437
|
-
})
|
|
2475
|
+
const { workspace } = withSession(req.headers['x-session-id']);
|
|
2476
|
+
res.json(await workspace.list());
|
|
2477
|
+
});
|
|
2438
2478
|
|
|
2439
2479
|
app.post('/usage', async (req, res) => {
|
|
2440
|
-
const { usage } = withSession(req.headers['x-session-id'])
|
|
2480
|
+
const { usage } = withSession(req.headers['x-session-id']);
|
|
2441
2481
|
const result = await usage.record(req.body.workspaceId, {
|
|
2442
2482
|
quotaSlug: 'api-calls',
|
|
2443
2483
|
quantity: 1,
|
|
2444
|
-
})
|
|
2445
|
-
res.json(result)
|
|
2446
|
-
})
|
|
2484
|
+
});
|
|
2485
|
+
res.json(result);
|
|
2486
|
+
});
|
|
2447
2487
|
```
|
|
2448
2488
|
|
|
2449
2489
|
### Background Jobs / Webhooks
|
|
@@ -2451,35 +2491,35 @@ app.post('/usage', async (req, res) => {
|
|
|
2451
2491
|
For service accounts (no user session), use `withSession()` with a service token:
|
|
2452
2492
|
|
|
2453
2493
|
```ts
|
|
2454
|
-
import { withSession } from '@/lib/buildbase'
|
|
2494
|
+
import { withSession } from '@/lib/buildbase';
|
|
2455
2495
|
|
|
2456
|
-
const bb = withSession(process.env.SERVICE_SESSION_ID!)
|
|
2496
|
+
const bb = withSession(process.env.SERVICE_SESSION_ID!);
|
|
2457
2497
|
|
|
2458
2498
|
// Record usage from a webhook
|
|
2459
2499
|
await bb.usage.record(workspaceId, {
|
|
2460
2500
|
quotaSlug: 'uploads',
|
|
2461
2501
|
quantity: 1,
|
|
2462
2502
|
source: 'webhook:file.processed',
|
|
2463
|
-
})
|
|
2503
|
+
});
|
|
2464
2504
|
|
|
2465
2505
|
// Check subscription
|
|
2466
|
-
const sub = await bb.subscription.get(workspaceId)
|
|
2506
|
+
const sub = await bb.subscription.get(workspaceId);
|
|
2467
2507
|
```
|
|
2468
2508
|
|
|
2469
2509
|
### Server-Side Actions Reference
|
|
2470
2510
|
|
|
2471
|
-
| Module
|
|
2472
|
-
|
|
2473
|
-
| `workspace`
|
|
2474
|
-
| `users`
|
|
2475
|
-
| `subscription` | `get`, `checkout`, `update`, `cancel`, `resume`, `getBillingPortalUrl`
|
|
2476
|
-
| `plans`
|
|
2477
|
-
| `invoices`
|
|
2478
|
-
| `usage`
|
|
2479
|
-
| `settings`
|
|
2480
|
-
| `features`
|
|
2481
|
-
| `notification` | `send(workspaceId, event, userId?, data?)`
|
|
2482
|
-
| `permissions`
|
|
2511
|
+
| Module | Methods |
|
|
2512
|
+
| -------------- | ------------------------------------------------------------------------ |
|
|
2513
|
+
| `workspace` | `list`, `get`, `create`, `update`, `delete` |
|
|
2514
|
+
| `users` | `list`, `invite`, `remove`, `updateRole`, `getProfile`, `updateProfile` |
|
|
2515
|
+
| `subscription` | `get`, `checkout`, `update`, `cancel`, `resume`, `getBillingPortalUrl` |
|
|
2516
|
+
| `plans` | `getGroup`, `getVersions`, `getPublic`, `getVersion` |
|
|
2517
|
+
| `invoices` | `list`, `get` |
|
|
2518
|
+
| `usage` | `record`, `recordBatch`, `getQuota`, `getAll`, `getLogs` |
|
|
2519
|
+
| `settings` | `get` |
|
|
2520
|
+
| `features` | `list`, `update` |
|
|
2521
|
+
| `notification` | `send(workspaceId, event, userId?, data?)` |
|
|
2522
|
+
| `permissions` | `check(workspaceId, userId, permission)`, `resolve(workspaceId, userId)` |
|
|
2483
2523
|
|
|
2484
2524
|
### BuildBase Config Options
|
|
2485
2525
|
|