@buildbase/sdk 0.0.19 → 0.0.21
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 +521 -2
- package/dist/index.d.ts +565 -14
- package/dist/index.esm.js +5 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/saas-os.css +1 -1
- package/dist/types/api/types.d.ts +63 -0
- package/dist/types/components/features/index.d.ts +16 -4
- package/dist/types/components/quota/index.d.ts +121 -0
- package/dist/types/components/subscription/index.d.ts +12 -3
- package/dist/types/components/user/auth.d.ts +8 -2
- package/dist/types/components/user/role.d.ts +8 -2
- package/dist/types/contexts/QuotaUsageContext/QuotaUsageContext.d.ts +22 -0
- package/dist/types/contexts/QuotaUsageContext/index.d.ts +2 -0
- package/dist/types/contexts/QuotaUsageContext/quotaUsageInvalidation.d.ts +19 -0
- package/dist/types/contexts/QuotaUsageContext/types.d.ts +14 -0
- package/dist/types/contexts/SubscriptionContext/types.d.ts +2 -0
- package/dist/types/index.d.ts +7 -2
- package/dist/types/providers/workspace/api.d.ts +28 -1
- package/dist/types/providers/workspace/subscription-hooks.d.ts +179 -1
- package/dist/types/providers/workspace/ui/SettingsDialog.d.ts +1 -1
- package/dist/types/providers/workspace/ui/SettingsUsage.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,9 @@ A React SDK for [BuildBase](https://www.buildbase.app/) that provides essential
|
|
|
14
14
|
- [User Management](#-user-management)
|
|
15
15
|
- [Workspace Management](#-complete-workspace-management)
|
|
16
16
|
- [Public Pricing (No Login)](#-public-pricing-no-login)
|
|
17
|
+
- [Multi-Currency & Pricing Utilities](#-multi-currency--pricing-utilities)
|
|
18
|
+
- [Quota Usage Tracking](#-quota-usage-tracking)
|
|
19
|
+
- [Quota Gates](#-quota-gates)
|
|
17
20
|
- [Beta Form Component](#-beta-form-component)
|
|
18
21
|
- [Event System](#-event-system)
|
|
19
22
|
- [Error Handling](#️-error-handling)
|
|
@@ -32,6 +35,7 @@ A React SDK for [BuildBase](https://www.buildbase.app/) that provides essential
|
|
|
32
35
|
- **👥 Role-Based Access Control** - User roles and workspace-specific permissions
|
|
33
36
|
- **🎯 Feature Flags** - Workspace-level and user-level feature toggles
|
|
34
37
|
- **📋 Subscription Gates** - Show or hide UI based on current workspace subscription (plan)
|
|
38
|
+
- **📊 Quota Usage Tracking** - Record and monitor metered usage (API calls, storage, etc.) with real-time status
|
|
35
39
|
- **👤 User Management** - User attributes and feature flags management
|
|
36
40
|
- **📝 Beta Form** - Pre-built signup/waitlist form component
|
|
37
41
|
- **📡 Event System** - Subscribe to user and workspace events
|
|
@@ -569,6 +573,485 @@ function PublicPricingPage() {
|
|
|
569
573
|
|
|
570
574
|
**Backend requirement**: `GET /api/v1/public/{orgId}/plans/{groupSlug}` must be implemented and allow unauthenticated access.
|
|
571
575
|
|
|
576
|
+
## 💱 Multi-Currency & Pricing Utilities
|
|
577
|
+
|
|
578
|
+
Plans support **pricing variants** (multi-currency). Use these utilities for display and lookup.
|
|
579
|
+
|
|
580
|
+
### Currency utilities
|
|
581
|
+
|
|
582
|
+
| Export | Purpose |
|
|
583
|
+
|--------|--------|
|
|
584
|
+
| `CURRENCY_DISPLAY` | Map of currency code → symbol (e.g. `usd` → `$`) |
|
|
585
|
+
| `CURRENCY_FLAG` | Map of currency code → flag emoji |
|
|
586
|
+
| `PLAN_CURRENCY_CODES` | Allowed billing currency codes (for dropdowns/validation) |
|
|
587
|
+
| `PLAN_CURRENCY_OPTIONS` | Options array for plan currency selects |
|
|
588
|
+
| `getCurrencySymbol(currency)` | Symbol for a Stripe currency code |
|
|
589
|
+
| `getCurrencyFlag(currency)` | Flag emoji for a currency code |
|
|
590
|
+
| `formatCents(cents, currency)` | Format cents as localized price string |
|
|
591
|
+
| `formatOverageRate(cents, currency)` | Format overage rate for display |
|
|
592
|
+
| `formatOverageRateWithLabel(...)` | Overage rate with optional unit label |
|
|
593
|
+
| `formatQuotaIncludedOverage(...)` | "X included, then $Y / unit" style text |
|
|
594
|
+
| `getQuotaUnitLabelFromName(name)` | Human-readable unit label from quota name |
|
|
595
|
+
|
|
596
|
+
### Pricing variant utilities
|
|
597
|
+
|
|
598
|
+
| Export | Purpose |
|
|
599
|
+
|--------|--------|
|
|
600
|
+
| `getPricingVariant(planVersion, currency)` | Get variant for a currency, or `null` |
|
|
601
|
+
| `getBasePriceCents(planVersion, currency, interval)` | Base price in cents for currency/interval |
|
|
602
|
+
| `getStripePriceIdForInterval(planVersion, currency, interval)` | Stripe price ID for checkout |
|
|
603
|
+
| `getQuotaOverageCents(planVersion, currency, quotaSlug, interval)` | Overage cents for a quota |
|
|
604
|
+
| `getQuotaDisplayWithVariant(planVersion, currency, quotaSlug, interval)` | Display value with overage for a variant |
|
|
605
|
+
| `getAvailableCurrenciesFromPlans(plans)` | Unique currency codes across plan versions |
|
|
606
|
+
| `getDisplayCurrency(planVersion, currency)` | Display currency (variant exists ? currency : plan.currency) |
|
|
607
|
+
| `getBillingIntervalAndCurrencyFromPriceId(planVersions, priceId)` | Resolve price ID to interval + currency |
|
|
608
|
+
|
|
609
|
+
Types: `IPricingVariant`, `PlanVersionWithPricingVariants`, `QuotaDisplayWithOverage`.
|
|
610
|
+
|
|
611
|
+
### Quota utilities
|
|
612
|
+
|
|
613
|
+
| Export | Purpose |
|
|
614
|
+
|--------|--------|
|
|
615
|
+
| `getQuotaDisplayValue(quotaByInterval, interval?)` | Normalize `IQuotaByInterval` to `{ included, overage?, unitSize? }` |
|
|
616
|
+
| `formatQuotaWithPrice(value, unitName, options?)` | Format as "X included, then $Y.YY / unit" |
|
|
617
|
+
|
|
618
|
+
Types: `QuotaDisplayValue`, `FormatQuotaWithPriceOptions`. Plan/subscription types use `IQuotaByInterval` and `IQuotaIntervalValue` for per-interval quotas and overages.
|
|
619
|
+
|
|
620
|
+
```tsx
|
|
621
|
+
import {
|
|
622
|
+
getCurrencySymbol,
|
|
623
|
+
formatCents,
|
|
624
|
+
getPricingVariant,
|
|
625
|
+
getBasePriceCents,
|
|
626
|
+
getQuotaDisplayValue,
|
|
627
|
+
formatQuotaWithPrice,
|
|
628
|
+
} from '@buildbase/sdk';
|
|
629
|
+
|
|
630
|
+
// Display price for a plan version in a currency
|
|
631
|
+
const variant = getPricingVariant(planVersion, 'usd');
|
|
632
|
+
const cents = getBasePriceCents(planVersion, 'usd', 'monthly');
|
|
633
|
+
if (cents != null) {
|
|
634
|
+
console.log(getCurrencySymbol('usd') + (cents / 100).toFixed(2));
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Quota display with overage
|
|
638
|
+
const display = getQuotaDisplayValue(planVersion.quotas?.videos, 'monthly');
|
|
639
|
+
const text = formatQuotaWithPrice(display, 'video', { currency: 'usd' });
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
## 📊 Quota Usage Tracking
|
|
643
|
+
|
|
644
|
+
Track and monitor metered usage for subscription quotas (e.g., API calls, emails, storage). Usage can be recorded from both the **client-side** (React app) and **server-side** (your backend).
|
|
645
|
+
|
|
646
|
+
### When to use which?
|
|
647
|
+
|
|
648
|
+
| Scenario | Where to record | Why |
|
|
649
|
+
|----------|----------------|-----|
|
|
650
|
+
| User clicks "Send Email" button | Client-side (SDK hook) | User-initiated, immediate UI feedback needed |
|
|
651
|
+
| API request hits your backend | Server-side (REST API) | Backend controls the resource, more secure |
|
|
652
|
+
| Background job processes data | Server-side (REST API) | No browser context available |
|
|
653
|
+
| File upload completes | Either | Depends on where validation happens |
|
|
654
|
+
|
|
655
|
+
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.
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
### Client-Side (React SDK)
|
|
660
|
+
|
|
661
|
+
Use the SDK hooks inside your React app. Quota gate components (see [Quota Gates](#-quota-gates)) automatically refresh after recording.
|
|
662
|
+
|
|
663
|
+
#### Record Usage
|
|
664
|
+
|
|
665
|
+
```tsx
|
|
666
|
+
import { useRecordUsage, useSaaSWorkspaces } from '@buildbase/sdk';
|
|
667
|
+
|
|
668
|
+
function SendEmailButton() {
|
|
669
|
+
const { currentWorkspace } = useSaaSWorkspaces();
|
|
670
|
+
const { recordUsage, loading, error } = useRecordUsage(currentWorkspace?._id);
|
|
671
|
+
|
|
672
|
+
const handleSend = async () => {
|
|
673
|
+
try {
|
|
674
|
+
const result = await recordUsage({
|
|
675
|
+
quotaSlug: 'emails',
|
|
676
|
+
quantity: 1,
|
|
677
|
+
source: 'web-app', // optional: track where usage came from
|
|
678
|
+
idempotencyKey: 'email-abc', // optional: prevent duplicate recordings
|
|
679
|
+
});
|
|
680
|
+
console.log(`Used: ${result.consumed}/${result.included}, Available: ${result.available}`);
|
|
681
|
+
if (result.overage > 0) {
|
|
682
|
+
console.warn(`Overage: ${result.overage} units`);
|
|
683
|
+
}
|
|
684
|
+
} catch (err) {
|
|
685
|
+
console.error('Failed to record usage:', err);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
return <button onClick={handleSend} disabled={loading}>Send Email</button>;
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
#### Check Single Quota Status
|
|
694
|
+
|
|
695
|
+
```tsx
|
|
696
|
+
import { useQuotaUsageStatus, useSaaSWorkspaces } from '@buildbase/sdk';
|
|
697
|
+
|
|
698
|
+
function QuotaStatusBar({ quotaSlug }: { quotaSlug: string }) {
|
|
699
|
+
const { currentWorkspace } = useSaaSWorkspaces();
|
|
700
|
+
const { status, loading, refetch } = useQuotaUsageStatus(currentWorkspace?._id, quotaSlug);
|
|
701
|
+
|
|
702
|
+
if (loading) return <Spinner />;
|
|
703
|
+
if (!status) return null;
|
|
704
|
+
|
|
705
|
+
const usagePercent = Math.round((status.consumed / status.included) * 100);
|
|
706
|
+
|
|
707
|
+
return (
|
|
708
|
+
<div>
|
|
709
|
+
<p>{quotaSlug}: {status.consumed} / {status.included} ({usagePercent}%)</p>
|
|
710
|
+
<p>Available: {status.available}</p>
|
|
711
|
+
{status.hasOverage && <p>Overage: {status.overage} units</p>}
|
|
712
|
+
<button onClick={refetch}>Refresh</button>
|
|
713
|
+
</div>
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
#### Check All Quotas
|
|
719
|
+
|
|
720
|
+
```tsx
|
|
721
|
+
import { useAllQuotaUsage, useSaaSWorkspaces } from '@buildbase/sdk';
|
|
722
|
+
|
|
723
|
+
function QuotaDashboard() {
|
|
724
|
+
const { currentWorkspace } = useSaaSWorkspaces();
|
|
725
|
+
const { quotas, loading, refetch } = useAllQuotaUsage(currentWorkspace?._id);
|
|
726
|
+
|
|
727
|
+
if (loading) return <Spinner />;
|
|
728
|
+
if (!quotas) return <p>No quota data available</p>;
|
|
729
|
+
|
|
730
|
+
return (
|
|
731
|
+
<div>
|
|
732
|
+
{Object.entries(quotas).map(([slug, usage]) => (
|
|
733
|
+
<div key={slug}>
|
|
734
|
+
<strong>{slug}</strong>: {usage.consumed} / {usage.included}
|
|
735
|
+
{usage.hasOverage && <span> (overage: {usage.overage})</span>}
|
|
736
|
+
</div>
|
|
737
|
+
))}
|
|
738
|
+
</div>
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
#### Usage Logs
|
|
744
|
+
|
|
745
|
+
```tsx
|
|
746
|
+
import { useUsageLogs, useSaaSWorkspaces } from '@buildbase/sdk';
|
|
747
|
+
|
|
748
|
+
function UsageLogsTable() {
|
|
749
|
+
const { currentWorkspace } = useSaaSWorkspaces();
|
|
750
|
+
const {
|
|
751
|
+
logs, totalDocs, totalPages, page, hasNextPage, loading, refetch,
|
|
752
|
+
} = useUsageLogs(
|
|
753
|
+
currentWorkspace?._id,
|
|
754
|
+
'api_calls', // optional: filter by quota slug
|
|
755
|
+
{ limit: 20, page: 1 } // optional: pagination and filters
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
if (loading) return <Spinner />;
|
|
759
|
+
|
|
760
|
+
return (
|
|
761
|
+
<div>
|
|
762
|
+
<table>
|
|
763
|
+
<thead>
|
|
764
|
+
<tr><th>Quota</th><th>Quantity</th><th>Source</th><th>Date</th></tr>
|
|
765
|
+
</thead>
|
|
766
|
+
<tbody>
|
|
767
|
+
{logs.map(log => (
|
|
768
|
+
<tr key={log._id}>
|
|
769
|
+
<td>{log.quotaSlug}</td>
|
|
770
|
+
<td>{log.quantity}</td>
|
|
771
|
+
<td>{log.source ?? '-'}</td>
|
|
772
|
+
<td>{new Date(log.createdAt).toLocaleString()}</td>
|
|
773
|
+
</tr>
|
|
774
|
+
))}
|
|
775
|
+
</tbody>
|
|
776
|
+
</table>
|
|
777
|
+
<p>Page {page} of {totalPages} ({totalDocs} total)</p>
|
|
778
|
+
</div>
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
**`useUsageLogs` parameters:**
|
|
784
|
+
|
|
785
|
+
| Parameter | Type | Required | Description |
|
|
786
|
+
|-----------|------|----------|-------------|
|
|
787
|
+
| `workspaceId` | `string \| null \| undefined` | Yes | Workspace ID (null/undefined disables fetching) |
|
|
788
|
+
| `quotaSlug` | `string` | No | Filter logs by quota slug |
|
|
789
|
+
| `options.from` | `string` | No | ISO date string — filter logs from this date |
|
|
790
|
+
| `options.to` | `string` | No | ISO date string — filter logs until this date |
|
|
791
|
+
| `options.source` | `string` | No | Filter logs by source |
|
|
792
|
+
| `options.page` | `number` | No | Page number (default: 1) |
|
|
793
|
+
| `options.limit` | `number` | No | Results per page (default: 20) |
|
|
794
|
+
|
|
795
|
+
#### Client-Side Hooks Summary
|
|
796
|
+
|
|
797
|
+
| Hook | Purpose |
|
|
798
|
+
|------|---------|
|
|
799
|
+
| `useRecordUsage(workspaceId)` | Record quota consumption (mutation) |
|
|
800
|
+
| `useQuotaUsageStatus(workspaceId, quotaSlug)` | Get single quota status (auto-fetches) |
|
|
801
|
+
| `useAllQuotaUsage(workspaceId)` | Get all quotas status (auto-fetches) |
|
|
802
|
+
| `useUsageLogs(workspaceId, quotaSlug?, options?)` | Get paginated usage history (auto-fetches) |
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
### Server-Side (REST API)
|
|
807
|
+
|
|
808
|
+
For backend services, background jobs, or API routes — call the BuildBase API directly. This is the recommended approach when usage happens on your server (e.g., processing an API request, running a cron job, handling webhooks).
|
|
809
|
+
|
|
810
|
+
#### Step 1: Get a Session ID
|
|
811
|
+
|
|
812
|
+
Exchange your org API token for a session ID. You can do this once and reuse the session for multiple requests (default expiry: 30 days).
|
|
813
|
+
|
|
814
|
+
```ts
|
|
815
|
+
// Do this once at startup or cache the result
|
|
816
|
+
const TOKEN = 'your-org-id:your-api-secret'; // from BuildBase dashboard
|
|
817
|
+
|
|
818
|
+
async function getSessionId(): Promise<string> {
|
|
819
|
+
const response = await fetch('https://your-server.buildbase.app/api/v1/public/token/exchange', {
|
|
820
|
+
method: 'POST',
|
|
821
|
+
headers: { 'Content-Type': 'application/json' },
|
|
822
|
+
body: JSON.stringify({
|
|
823
|
+
token: TOKEN,
|
|
824
|
+
expiresIn: 2592000, // 30 days (optional, default is 30 days)
|
|
825
|
+
}),
|
|
826
|
+
});
|
|
827
|
+
const data = await response.json();
|
|
828
|
+
return data.sessionId;
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
#### Step 2: Record Usage
|
|
833
|
+
|
|
834
|
+
```ts
|
|
835
|
+
const SESSION_ID = await getSessionId();
|
|
836
|
+
const BASE_URL = 'https://your-server.buildbase.app/api/v1/public';
|
|
837
|
+
|
|
838
|
+
async function recordUsage(workspaceId: string, quotaSlug: string, quantity: number) {
|
|
839
|
+
const response = await fetch(`${BASE_URL}/workspaces/${workspaceId}/subscription/usage`, {
|
|
840
|
+
method: 'POST',
|
|
841
|
+
headers: {
|
|
842
|
+
'Content-Type': 'application/json',
|
|
843
|
+
'x-session-id': SESSION_ID,
|
|
844
|
+
},
|
|
845
|
+
body: JSON.stringify({
|
|
846
|
+
quotaSlug,
|
|
847
|
+
quantity,
|
|
848
|
+
source: 'backend', // optional: helps distinguish from client-side usage
|
|
849
|
+
metadata: {}, // optional: attach custom data
|
|
850
|
+
idempotencyKey: undefined, // optional: prevent duplicate recordings
|
|
851
|
+
}),
|
|
852
|
+
});
|
|
853
|
+
return response.json();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Example: Record usage in an Express route handler
|
|
857
|
+
app.post('/api/generate-report', async (req, res) => {
|
|
858
|
+
const { workspaceId } = req.user; // your auth
|
|
859
|
+
|
|
860
|
+
// Record quota usage BEFORE or AFTER doing the work
|
|
861
|
+
const usage = await recordUsage(workspaceId, 'reports', 1);
|
|
862
|
+
|
|
863
|
+
if (usage.available <= 0 && !usage.hasOverage) {
|
|
864
|
+
return res.status(429).json({ error: 'Report quota exceeded' });
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// ... generate the report ...
|
|
868
|
+
res.json({ success: true, quotaRemaining: usage.available });
|
|
869
|
+
});
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
#### Step 3: Check Usage Status (Optional)
|
|
873
|
+
|
|
874
|
+
```ts
|
|
875
|
+
// Get status for a single quota
|
|
876
|
+
async function getQuotaStatus(workspaceId: string, quotaSlug: string) {
|
|
877
|
+
const response = await fetch(
|
|
878
|
+
`${BASE_URL}/workspaces/${workspaceId}/subscription/usage/status?quotaSlug=${quotaSlug}`,
|
|
879
|
+
{ headers: { 'x-session-id': SESSION_ID } }
|
|
880
|
+
);
|
|
881
|
+
return response.json();
|
|
882
|
+
// Returns: { quotaSlug, consumed, included, available, overage, hasOverage }
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Get status for ALL quotas
|
|
886
|
+
async function getAllQuotaStatus(workspaceId: string) {
|
|
887
|
+
const response = await fetch(
|
|
888
|
+
`${BASE_URL}/workspaces/${workspaceId}/subscription/usage/all`,
|
|
889
|
+
{ headers: { 'x-session-id': SESSION_ID } }
|
|
890
|
+
);
|
|
891
|
+
return response.json();
|
|
892
|
+
// Returns: { quotas: { [slug]: { consumed, included, available, overage, hasOverage } } }
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Example: Check before allowing an action
|
|
896
|
+
app.post('/api/send-email', async (req, res) => {
|
|
897
|
+
const status = await getQuotaStatus(req.user.workspaceId, 'emails');
|
|
898
|
+
|
|
899
|
+
if (status.available <= 0) {
|
|
900
|
+
return res.status(429).json({
|
|
901
|
+
error: 'Email quota exceeded',
|
|
902
|
+
consumed: status.consumed,
|
|
903
|
+
included: status.included,
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
await recordUsage(req.user.workspaceId, 'emails', 1);
|
|
908
|
+
// ... send the email ...
|
|
909
|
+
});
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
#### Server-Side API Reference
|
|
913
|
+
|
|
914
|
+
| Endpoint | Method | Description |
|
|
915
|
+
|----------|--------|-------------|
|
|
916
|
+
| `/api/v1/public/token/exchange` | POST | Exchange API token for session ID |
|
|
917
|
+
| `/api/v1/public/workspaces/:id/subscription/usage` | POST | Record quota usage |
|
|
918
|
+
| `/api/v1/public/workspaces/:id/subscription/usage/status?quotaSlug=X` | GET | Get single quota status |
|
|
919
|
+
| `/api/v1/public/workspaces/:id/subscription/usage/all` | GET | Get all quotas status |
|
|
920
|
+
| `/api/v1/public/workspaces/:id/subscription/usage/logs` | GET | Get paginated usage logs |
|
|
921
|
+
|
|
922
|
+
All endpoints (except `/token/exchange`) require the `x-session-id` header.
|
|
923
|
+
|
|
924
|
+
**Record usage request body:**
|
|
925
|
+
|
|
926
|
+
| Field | Type | Required | Description |
|
|
927
|
+
|-------|------|----------|-------------|
|
|
928
|
+
| `quotaSlug` | `string` | Yes | Quota identifier (e.g. `'api_calls'`, `'emails'`, `'storage'`) |
|
|
929
|
+
| `quantity` | `number` | Yes | Units to consume (minimum 1) |
|
|
930
|
+
| `metadata` | `object` | No | Custom metadata to attach to the usage record |
|
|
931
|
+
| `source` | `string` | No | Source identifier (e.g. `'backend'`, `'worker'`, `'cron'`) |
|
|
932
|
+
| `idempotencyKey` | `string` | No | Unique key for deduplication |
|
|
933
|
+
|
|
934
|
+
**Record usage response:**
|
|
935
|
+
|
|
936
|
+
| Field | Type | Description |
|
|
937
|
+
|-------|------|-------------|
|
|
938
|
+
| `used` | `number` | Quantity recorded in this request |
|
|
939
|
+
| `consumed` | `number` | Total usage in the current billing period |
|
|
940
|
+
| `included` | `number` | Total units included in the plan |
|
|
941
|
+
| `available` | `number` | Remaining units before overage |
|
|
942
|
+
| `overage` | `number` | Units used beyond the included amount |
|
|
943
|
+
| `billedAsync` | `boolean` | Whether overage billing was queued to Stripe |
|
|
944
|
+
|
|
945
|
+
## 🚦 Quota Gates
|
|
946
|
+
|
|
947
|
+
Control UI visibility based on quota usage status. Quota data is loaded once per workspace via `QuotaUsageContextProvider` (included in `SaaSOSProvider` by default) and refetched automatically after recording usage.
|
|
948
|
+
|
|
949
|
+
### Gate Components
|
|
950
|
+
|
|
951
|
+
```tsx
|
|
952
|
+
import {
|
|
953
|
+
WhenQuotaAvailable,
|
|
954
|
+
WhenQuotaExhausted,
|
|
955
|
+
WhenQuotaOverage,
|
|
956
|
+
WhenQuotaThreshold,
|
|
957
|
+
} from '@buildbase/sdk';
|
|
958
|
+
|
|
959
|
+
function Dashboard() {
|
|
960
|
+
return (
|
|
961
|
+
<div>
|
|
962
|
+
{/* Show action button only when quota has remaining units */}
|
|
963
|
+
<WhenQuotaAvailable slug="api_calls">
|
|
964
|
+
<MakeApiCallButton />
|
|
965
|
+
</WhenQuotaAvailable>
|
|
966
|
+
|
|
967
|
+
{/* Show upgrade prompt when quota is fully consumed */}
|
|
968
|
+
<WhenQuotaExhausted slug="api_calls">
|
|
969
|
+
<UpgradePrompt message="You've used all your API calls this month." />
|
|
970
|
+
</WhenQuotaExhausted>
|
|
971
|
+
|
|
972
|
+
{/* Show warning when usage exceeds included amount (overage billing active) */}
|
|
973
|
+
<WhenQuotaOverage slug="api_calls">
|
|
974
|
+
<OverageBillingWarning />
|
|
975
|
+
</WhenQuotaOverage>
|
|
976
|
+
|
|
977
|
+
{/* Show warning when usage reaches 80% of included amount */}
|
|
978
|
+
<WhenQuotaThreshold slug="api_calls" threshold={80}>
|
|
979
|
+
<p>Warning: You've used over 80% of your API calls.</p>
|
|
980
|
+
</WhenQuotaThreshold>
|
|
981
|
+
</div>
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### With Loading and Fallback
|
|
987
|
+
|
|
988
|
+
All quota gate components support optional `loadingComponent` and `fallbackComponent` props:
|
|
989
|
+
|
|
990
|
+
```tsx
|
|
991
|
+
<WhenQuotaAvailable
|
|
992
|
+
slug="emails"
|
|
993
|
+
loadingComponent={<Skeleton className="h-10" />}
|
|
994
|
+
fallbackComponent={<p>Email quota exhausted. <a href="/upgrade">Upgrade now</a></p>}
|
|
995
|
+
>
|
|
996
|
+
<SendEmailButton />
|
|
997
|
+
</WhenQuotaAvailable>
|
|
998
|
+
|
|
999
|
+
<WhenQuotaThreshold
|
|
1000
|
+
slug="storage"
|
|
1001
|
+
threshold={90}
|
|
1002
|
+
loadingComponent={<Spinner />}
|
|
1003
|
+
fallbackComponent={null}
|
|
1004
|
+
>
|
|
1005
|
+
<StorageWarningBanner />
|
|
1006
|
+
</WhenQuotaThreshold>
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### Quota Gate Components Reference
|
|
1010
|
+
|
|
1011
|
+
| Component | Renders children when | Props |
|
|
1012
|
+
|-----------|----------------------|-------|
|
|
1013
|
+
| `WhenQuotaAvailable` | Quota has remaining units (`available > 0`) | `slug`, `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1014
|
+
| `WhenQuotaExhausted` | Quota is fully consumed (`available <= 0`) | `slug`, `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1015
|
+
| `WhenQuotaOverage` | Usage exceeds included amount (`hasOverage`) | `slug`, `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1016
|
+
| `WhenQuotaThreshold` | Usage percentage >= threshold | `slug`, `threshold` (0-100), `children`, `loadingComponent?`, `fallbackComponent?` |
|
|
1017
|
+
|
|
1018
|
+
All gates must be used inside `QuotaUsageContextProvider` (included in `SaaSOSProvider`). By default they return `null` while loading or when the condition is not met.
|
|
1019
|
+
|
|
1020
|
+
### useQuotaUsageContext
|
|
1021
|
+
|
|
1022
|
+
Use the hook when you need raw quota data or a manual refetch (e.g. after a bulk operation):
|
|
1023
|
+
|
|
1024
|
+
```tsx
|
|
1025
|
+
import { useQuotaUsageContext } from '@buildbase/sdk';
|
|
1026
|
+
|
|
1027
|
+
function QuotaDebug() {
|
|
1028
|
+
const { quotas, loading, refetch } = useQuotaUsageContext();
|
|
1029
|
+
|
|
1030
|
+
if (loading) return <Spinner />;
|
|
1031
|
+
if (!quotas) return <p>No quota data</p>;
|
|
1032
|
+
|
|
1033
|
+
return (
|
|
1034
|
+
<div>
|
|
1035
|
+
{Object.entries(quotas).map(([slug, usage]) => (
|
|
1036
|
+
<p key={slug}>{slug}: {usage.consumed}/{usage.included}</p>
|
|
1037
|
+
))}
|
|
1038
|
+
<button onClick={() => refetch()}>Refresh</button>
|
|
1039
|
+
</div>
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
| Property | Type | Description |
|
|
1045
|
+
|----------|------|-------------|
|
|
1046
|
+
| `quotas` | `Record<string, IQuotaUsageStatus> \| null` | Current quota usage data keyed by slug |
|
|
1047
|
+
| `loading` | `boolean` | True while quota data is being fetched |
|
|
1048
|
+
| `refetch` | `() => Promise<void>` | Manually refetch all quota usage |
|
|
1049
|
+
|
|
1050
|
+
**When quota usage refetches:**
|
|
1051
|
+
- When the current workspace changes (automatic).
|
|
1052
|
+
- When usage is recorded via `useRecordUsage` — refetch is triggered automatically.
|
|
1053
|
+
- When you call `refetch()` manually.
|
|
1054
|
+
|
|
572
1055
|
## 📝 Beta Form Component
|
|
573
1056
|
|
|
574
1057
|
Use the pre-built `BetaForm` component for signup/waitlist forms:
|
|
@@ -667,9 +1150,17 @@ All SDK API clients extend a shared base class and are exported from the package
|
|
|
667
1150
|
| `BaseApi` | Abstract base (URL, auth, `fetchJson`/`fetchResponse`) – extend for custom APIs |
|
|
668
1151
|
| `IBaseApiConfig` | Config type: `serverUrl`, `version`, optional `orgId` |
|
|
669
1152
|
| `UserApi` | User attributes and features |
|
|
670
|
-
| `WorkspaceApi` | Workspaces, subscription, invoices, users
|
|
1153
|
+
| `WorkspaceApi` | Workspaces, subscription, invoices, quota usage, users |
|
|
671
1154
|
| `SettingsApi` | Organization settings |
|
|
672
1155
|
|
|
1156
|
+
### Currency, pricing variant & quota utilities
|
|
1157
|
+
|
|
1158
|
+
| Category | Exports |
|
|
1159
|
+
| -------- | ------- |
|
|
1160
|
+
| **Currency** | `CURRENCY_DISPLAY`, `CURRENCY_FLAG`, `PLAN_CURRENCY_CODES`, `PLAN_CURRENCY_OPTIONS`, `getCurrencySymbol`, `getCurrencyFlag`, `formatCents`, `formatOverageRate`, `formatOverageRateWithLabel`, `formatQuotaIncludedOverage`, `getQuotaUnitLabelFromName` |
|
|
1161
|
+
| **Pricing variants** | `getPricingVariant`, `getBasePriceCents`, `getStripePriceIdForInterval`, `getQuotaOverageCents`, `getQuotaDisplayWithVariant`, `getAvailableCurrenciesFromPlans`, `getDisplayCurrency`, `getBillingIntervalAndCurrencyFromPriceId`; types: `IPricingVariant`, `PlanVersionWithPricingVariants`, `QuotaDisplayWithOverage` |
|
|
1162
|
+
| **Quota** | `getQuotaDisplayValue`, `formatQuotaWithPrice`; types: `QuotaDisplayValue`, `FormatQuotaWithPriceOptions`. Plan types use `IQuotaByInterval`, `IQuotaIntervalValue` for per-interval quotas. |
|
|
1163
|
+
|
|
673
1164
|
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.):
|
|
674
1165
|
|
|
675
1166
|
```tsx
|
|
@@ -697,7 +1188,9 @@ Prefer these SDK hooks for state and operations instead of `useAppSelector`:
|
|
|
697
1188
|
| `useUserAttributes()` | User attributes and update/refresh |
|
|
698
1189
|
| `useUserFeatures()` | User feature flags |
|
|
699
1190
|
| `useSubscriptionContext()` | Subscription for current workspace (response, loading, refetch); use inside SubscriptionContextProvider |
|
|
700
|
-
| Subscription hooks | `usePublicPlans`, `useSubscription`, `usePlanGroup`,
|
|
1191
|
+
| Subscription hooks | `usePublicPlans`, `useSubscription`, `usePlanGroup`, `useCreateCheckoutSession`, `useUpdateSubscription`, `useCancelSubscription`, `useResumeSubscription`, `useInvoices`, `useInvoice` |
|
|
1192
|
+
| `useQuotaUsageContext()` | Quota usage for current workspace (quotas, loading, refetch); use inside QuotaUsageContextProvider |
|
|
1193
|
+
| Quota usage hooks | `useRecordUsage`, `useQuotaUsageStatus`, `useAllQuotaUsage`, `useUsageLogs` |
|
|
701
1194
|
|
|
702
1195
|
Using hooks keeps your code stable if internal state shape changes and avoids direct Redux/context coupling.
|
|
703
1196
|
|
|
@@ -889,6 +1382,32 @@ function BillingPage() {
|
|
|
889
1382
|
|
|
890
1383
|
SubscriptionContextProvider is included in SaaSOSProvider by default, so no extra wrapper is needed.
|
|
891
1384
|
|
|
1385
|
+
### Pattern 4c: Quota-Gated UI
|
|
1386
|
+
|
|
1387
|
+
```tsx
|
|
1388
|
+
import { WhenQuotaAvailable, WhenQuotaExhausted, WhenQuotaThreshold } from '@buildbase/sdk';
|
|
1389
|
+
|
|
1390
|
+
function FeatureWithQuota() {
|
|
1391
|
+
return (
|
|
1392
|
+
<div>
|
|
1393
|
+
<WhenQuotaThreshold slug="api_calls" threshold={80}>
|
|
1394
|
+
<WarningBanner message="You're running low on API calls" />
|
|
1395
|
+
</WhenQuotaThreshold>
|
|
1396
|
+
|
|
1397
|
+
<WhenQuotaAvailable slug="api_calls" fallbackComponent={<UpgradePrompt />}>
|
|
1398
|
+
<ApiCallButton />
|
|
1399
|
+
</WhenQuotaAvailable>
|
|
1400
|
+
|
|
1401
|
+
<WhenQuotaExhausted slug="api_calls">
|
|
1402
|
+
<p>No API calls remaining. <a href="/billing">Upgrade your plan</a></p>
|
|
1403
|
+
</WhenQuotaExhausted>
|
|
1404
|
+
</div>
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
```
|
|
1408
|
+
|
|
1409
|
+
QuotaUsageContextProvider is included in SaaSOSProvider by default, so no extra wrapper is needed. Quota data auto-refreshes after `useRecordUsage` calls.
|
|
1410
|
+
|
|
892
1411
|
### Pattern 5: Handling Workspace Changes
|
|
893
1412
|
|
|
894
1413
|
```tsx
|