Your backend decides what an end user may do. Your frontend shows them what they have: which plan they are on, what each plan grants, and how much of the allowance is left. Two components of @shiprev/react do that, PricingTable and UsageMeter. They display only; a ruling is always made in your backend.
This page covers version 0.1.0-beta.0, installed with npm install @shiprev/react@beta. It assumes the provider and the forwarding path are set up as described in The React components; both components read with the signed-in end user's session.
What the end user has: UsageMeter
UsageMeter reads the buyer organization's usage of the current period and shows, for each capability, what is used out of the allowance, with a bar.
import { UsageMeter } from '@shiprev/react';
export function Usage() {
return <UsageMeter capabilityKeys={['report_exports', 'seats']} showWalletBalance />;
}| Prop | What it does |
|---|---|
capabilityKeys | Shows only these capabilities, in this order. Without it, every capability of the organization is shown |
showWalletBalance | Also shows the organization's wallet balance. Off by default |
summary | A usage summary you already have; the component then does not read it |
The figures come from GET /public/v1/usage/summary, which answers from the buyer organization's entitlements at your application's region. They are the same figures your backend rules on, and they move when reported usage has been counted. When an allowance is not a positive number, only the amount used is shown, without a bar.
What the plans grant: PricingTable
PricingTable lists your published plans, add-ons excepted, with the price of their latest version and what each grants, one line per capability with its allowance. The organization's current plan is marked, and the end user can choose another one.
import { PricingTable } from '@shiprev/react';
export function Plans() {
return <PricingTable successUrl="/billing?done=1" cancelUrl="/billing" />;
}What choosing a plan does depends on the organization's subscription:
| The organization | Choosing a plan |
|---|---|
| Has no active subscription | Opens checkout for that plan version; checkout returns to successUrl or cancelUrl, the current page by default |
| Has an active subscription | Asks the end user to confirm, then changes the plan. The change takes effect at once, or opens a payment page when the region asks for one |
| Prop | What it does |
|---|---|
successUrl, cancelUrl | Where checkout returns |
onSelectPlan | Takes over the choice: the component calls it with the plan version and 'subscribe' or 'change', and sends no request itself |
onPlanChanged | Called with the new subscription after a change took effect |
onNavigate | Opens the checkout and payment pages; window.location.assign by default |
plans, subscription | Data you already have; the component then does not read it |
Once a change has taken effect, the region pushes the new entitlements to your backend, and the next ruling uses them; see Snapshot, mirror and offline.
When the allowance runs out
When your backend refuses a call with quota_exhausted, tell the frontend so, and show the plans there. With Overage policies, Prompt to upgrade names exactly this intent; the component does the same for any policy.
import { useState } from 'react';
import { PricingTable, UsageMeter } from '@shiprev/react';
export function ExportButton() {
const [limitReached, setLimitReached] = useState(false);
async function exportReport() {
const response = await fetch('/api/reports/export', { method: 'POST', headers: authHeaders() });
if (response.status === 403 && (await response.json()).reason === 'quota_exhausted') setLimitReached(true);
}
return limitReached ? (
<>
<UsageMeter capabilityKeys={['report_exports']} />
<PricingTable />
</>
) : (
<button onClick={exportReport}>Export</button>
);
}Your backend has to put the reason in its answer for this to work; Protect routes shows how.
To hide a feature the plan does not include rather than refuse it, let your backend tell the frontend which features are granted, from rulings with quantity: 0; see Check a capability without using it. Hiding is for display only: keep the ruling on the route that does the work.
Labels and errors
Both components take labels, to replace any of their English texts, className, added to their root element, and onError, called once for each failed request while the component shows its own error state. A signed-out end user sees a prompt to sign in.