@nozle-js/react 0.5.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -304
- package/dist/index.cjs +400 -1909
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -295
- package/dist/index.d.ts +122 -295
- package/dist/index.js +400 -1899
- package/dist/index.js.map +1 -1
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,329 +1,59 @@
|
|
|
1
1
|
# @nozle-js/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Browser-safe React components for Nozle's public plan catalog and merchant-controlled checkout.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Credential boundary
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
- `pk_` is used only for `GET /api/v1/plans`.
|
|
8
|
+
- Secret keys stay on the merchant backend.
|
|
9
|
+
- The React package does not fetch customer billing, invoice, subscription, entitlement, or credit data.
|
|
10
|
+
- The React package never sends a customer ID.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
## Install
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
|
-
npm install react react-dom @stripe/stripe-js @stripe/react-stripe-js
|
|
15
|
+
npm install @nozle-js/react react react-dom @stripe/stripe-js @stripe/react-stripe-js
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
Wrap your app with `BillingProvider`:
|
|
18
|
+
## Merchant-backed checkout
|
|
20
19
|
|
|
21
20
|
```tsx
|
|
22
|
-
import { BillingProvider } from "@nozle-js/react";
|
|
21
|
+
import { BillingProvider, PricingTable } from "@nozle-js/react";
|
|
23
22
|
|
|
24
|
-
function
|
|
23
|
+
export function BillingPage({ csrfToken }: { csrfToken: string }) {
|
|
25
24
|
return (
|
|
26
25
|
<BillingProvider
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
publishableKey={import.meta.env.VITE_NOZLE_PUBLISHABLE_KEY}
|
|
27
|
+
createCheckout={async ({ planCode, returnUrl }) => {
|
|
28
|
+
const response = await fetch("/api/billing/checkout", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
credentials: "include",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
"X-CSRF-Token": csrfToken,
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({ planCode, returnUrl }),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!response.ok) throw new Error("Checkout failed");
|
|
39
|
+
return response.json();
|
|
40
|
+
}}
|
|
31
41
|
>
|
|
32
|
-
<
|
|
42
|
+
<PricingTable returnUrl={window.location.href} highlightPlan="pro" />
|
|
33
43
|
</BillingProvider>
|
|
34
44
|
);
|
|
35
45
|
}
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
## Hooks
|
|
41
|
-
|
|
42
|
-
### `useCan(feature)`
|
|
43
|
-
|
|
44
|
-
Check if a customer has access to a feature.
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
import { useCan } from "@nozle-js/react";
|
|
48
|
-
|
|
49
|
-
function MyFeature() {
|
|
50
|
-
const { allowed, isLoading, error } = useCan("advanced_analytics");
|
|
51
|
-
|
|
52
|
-
if (isLoading) return <p>Loading...</p>;
|
|
53
|
-
if (!allowed) return <p>Upgrade to access this feature.</p>;
|
|
54
|
-
|
|
55
|
-
return <div>Feature content here</div>;
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### `useUsage(metric)`
|
|
60
|
-
|
|
61
|
-
Get real-time usage data for a metric. Updates automatically via WebSocket.
|
|
62
|
-
|
|
63
|
-
```tsx
|
|
64
|
-
import { useUsage } from "@nozle-js/react";
|
|
65
|
-
|
|
66
|
-
function TokenCounter() {
|
|
67
|
-
const { data, isLoading } = useUsage("tokens_used");
|
|
68
|
-
|
|
69
|
-
if (isLoading || !data) return null;
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<p>
|
|
73
|
-
{data.used.toLocaleString()} / {data.limit.toLocaleString()} tokens used
|
|
74
|
-
</p>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### `usePlan()`
|
|
80
|
-
|
|
81
|
-
Get the customer's current plan and subscription status.
|
|
82
|
-
|
|
83
|
-
```tsx
|
|
84
|
-
import { usePlan } from "@nozle-js/react";
|
|
85
|
-
|
|
86
|
-
function CurrentPlan() {
|
|
87
|
-
const { data } = usePlan();
|
|
88
|
-
|
|
89
|
-
if (!data) return null;
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<p>
|
|
93
|
-
Plan: {data.plan_slug} ({data.subscription_status})
|
|
94
|
-
</p>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### `usePlans()`
|
|
100
|
-
|
|
101
|
-
Fetch all available plans.
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
import { usePlans } from "@nozle-js/react";
|
|
105
|
-
|
|
106
|
-
function PlanList() {
|
|
107
|
-
const { plans, isLoading } = usePlans();
|
|
108
|
-
|
|
109
|
-
if (isLoading) return <p>Loading plans...</p>;
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<ul>
|
|
113
|
-
{plans.map((plan) => (
|
|
114
|
-
<li key={plan.code}>
|
|
115
|
-
{plan.name} — ${(plan.amount_cents / 100).toFixed(2)}/{plan.interval}
|
|
116
|
-
</li>
|
|
117
|
-
))}
|
|
118
|
-
</ul>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### `useCheckout()`
|
|
124
|
-
|
|
125
|
-
Create a Stripe checkout session and render embedded checkout.
|
|
126
|
-
|
|
127
|
-
```tsx
|
|
128
|
-
import { useCheckout } from "@nozle-js/react";
|
|
129
|
-
|
|
130
|
-
function UpgradeButton({ planCode }: { planCode: string }) {
|
|
131
|
-
const { fetchClientSecret, isLoading } = useCheckout();
|
|
132
|
-
|
|
133
|
-
const handleUpgrade = async () => {
|
|
134
|
-
const clientSecret = await fetchClientSecret(planCode);
|
|
135
|
-
// Use clientSecret with Stripe EmbeddedCheckout
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<button onClick={handleUpgrade} disabled={isLoading}>
|
|
140
|
-
Upgrade
|
|
141
|
-
</button>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### `useSubscribe()`
|
|
147
|
-
|
|
148
|
-
Create a subscription after successful payment.
|
|
149
|
-
|
|
150
|
-
```tsx
|
|
151
|
-
import { useSubscribe } from "@nozle-js/react";
|
|
152
|
-
|
|
153
|
-
function AfterPayment({ planCode }: { planCode: string }) {
|
|
154
|
-
const { subscribe, isLoading } = useSubscribe();
|
|
48
|
+
The merchant endpoint authenticates the user, derives the Nozle customer from the authenticated user or team, validates the plan and HTTPS return URL, and calls Nozle with a restricted server-side `sk_`. Any browser-supplied customer identifier must be ignored or rejected.
|
|
155
49
|
|
|
156
|
-
|
|
157
|
-
await subscribe(planCode);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<button onClick={handleComplete} disabled={isLoading}>
|
|
162
|
-
Activate Subscription
|
|
163
|
-
</button>
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
```
|
|
50
|
+
`createCheckout` may return a hosted URL, an embedded Stripe client secret, `type: "completed"`, or `type: "scheduled"`. Paid plans activate only after Nozle processes verified Stripe success; a browser redirect is not proof of payment.
|
|
167
51
|
|
|
168
52
|
## Components
|
|
169
53
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
Renders plan cards in a responsive grid with monthly/annual toggle, current plan detection, and CSS variable theming. Auto-fetches plans from the API if none are provided.
|
|
175
|
-
|
|
176
|
-
```tsx
|
|
177
|
-
import { PricingTable } from "@nozle-js/react";
|
|
178
|
-
|
|
179
|
-
// Auto-fetch plans from API
|
|
180
|
-
<PricingTable
|
|
181
|
-
customerId="cust_123"
|
|
182
|
-
highlightPlan="pro"
|
|
183
|
-
onSelect={(plan) => handleUpgrade(plan)}
|
|
184
|
-
/>
|
|
185
|
-
|
|
186
|
-
// Or pass plans explicitly
|
|
187
|
-
<PricingTable
|
|
188
|
-
plans={[
|
|
189
|
-
{ code: "starter", name: "Starter", amount_cents: 2900, amount_currency: "USD", interval: "monthly" },
|
|
190
|
-
{ code: "pro", name: "Pro", amount_cents: 9900, amount_currency: "USD", interval: "monthly" },
|
|
191
|
-
]}
|
|
192
|
-
features={[
|
|
193
|
-
["10K API calls", "Email support"],
|
|
194
|
-
["100K API calls", "Priority support", "Analytics"],
|
|
195
|
-
]}
|
|
196
|
-
currentPlanCode="starter"
|
|
197
|
-
highlightPlan="pro"
|
|
198
|
-
onSelect={(plan) => console.log("Selected:", plan.code)}
|
|
199
|
-
showToggle={true}
|
|
200
|
-
enterpriseEmail="sales@example.com"
|
|
201
|
-
className="my-pricing"
|
|
202
|
-
/>
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
| Prop | Type | Default | Description |
|
|
206
|
-
|------|------|---------|-------------|
|
|
207
|
-
| `customerId` | `string` | -- | Customer ID for auto-detecting current plan |
|
|
208
|
-
| `currentPlanCode` | `string` | -- | Explicitly set current plan (overrides auto-detect) |
|
|
209
|
-
| `plans` | `PricingPlan[]` | -- | Plans to display (auto-fetched from API if omitted) |
|
|
210
|
-
| `features` | `string[][]` | -- | Feature lists per plan (index-matched to plans array) |
|
|
211
|
-
| `onSelect` | `(plan: PricingPlan) => void` | -- | Called when a plan's CTA is clicked |
|
|
212
|
-
| `highlightPlan` | `string` | -- | Plan code to highlight as "Most Popular" |
|
|
213
|
-
| `enterpriseEmail` | `string` | -- | Email for enterprise plan "Contact Sales" button |
|
|
214
|
-
| `showToggle` | `boolean` | `true` | Show monthly/annual toggle (only when annual plans exist) |
|
|
215
|
-
| `className` | `string` | -- | CSS class for the outer wrapper |
|
|
216
|
-
|
|
217
|
-
#### PricingPlan type
|
|
218
|
-
|
|
219
|
-
```ts
|
|
220
|
-
interface PricingPlan {
|
|
221
|
-
code: string;
|
|
222
|
-
name: string;
|
|
223
|
-
amount_cents: number;
|
|
224
|
-
amount_currency: string;
|
|
225
|
-
interval: string;
|
|
226
|
-
description?: string;
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
#### CSS Variable Theming
|
|
231
|
-
|
|
232
|
-
PricingTable uses CSS custom properties for full theming control. Set these on a parent element or `:root`:
|
|
233
|
-
|
|
234
|
-
```css
|
|
235
|
-
:root {
|
|
236
|
-
/* Component-specific overrides */
|
|
237
|
-
--nozle-pricing-bg: #ffffff;
|
|
238
|
-
--nozle-pricing-card-bg: #ffffff;
|
|
239
|
-
--nozle-pricing-highlight: #6366f1;
|
|
240
|
-
--nozle-pricing-border: #e5e7eb;
|
|
241
|
-
--nozle-pricing-radius: 12px;
|
|
242
|
-
|
|
243
|
-
/* Or use global Nozle variables (PricingTable falls back to these) */
|
|
244
|
-
--nozle-background: #ffffff;
|
|
245
|
-
--nozle-card: #ffffff;
|
|
246
|
-
--nozle-primary: #6366f1;
|
|
247
|
-
--nozle-border: #e5e7eb;
|
|
248
|
-
--nozle-radius: 12px;
|
|
249
|
-
--nozle-foreground: #111827;
|
|
250
|
-
--nozle-muted-foreground: #6b7280;
|
|
251
|
-
--nozle-muted: #f3f4f6;
|
|
252
|
-
--nozle-primary-foreground: #ffffff;
|
|
253
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
The component-specific variables (`--nozle-pricing-*`) take precedence over the global ones (`--nozle-*`), which fall back to sensible defaults.
|
|
257
|
-
|
|
258
|
-
### `UsageMeter`
|
|
259
|
-
|
|
260
|
-
Displays usage with bar, ring, or minimal variants.
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
import { UsageMeter } from "@nozle-js/react";
|
|
264
|
-
|
|
265
|
-
<UsageMeter metric="tokens_used" variant="bar" />
|
|
266
|
-
<UsageMeter metric="tokens_used" variant="ring" />
|
|
267
|
-
<UsageMeter metric="tokens_used" variant="minimal" />
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### `PlanBadge`
|
|
271
|
-
|
|
272
|
-
Shows the current plan with status indicator.
|
|
273
|
-
|
|
274
|
-
```tsx
|
|
275
|
-
import { PlanBadge } from "@nozle-js/react";
|
|
276
|
-
|
|
277
|
-
<PlanBadge variant="pill" />
|
|
278
|
-
<PlanBadge variant="text" />
|
|
279
|
-
<PlanBadge variant="icon" />
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### `UpgradePrompt`
|
|
283
|
-
|
|
284
|
-
Conditionally renders an upgrade prompt when a feature is gated.
|
|
285
|
-
|
|
286
|
-
```tsx
|
|
287
|
-
import { UpgradePrompt } from "@nozle-js/react";
|
|
288
|
-
|
|
289
|
-
<UpgradePrompt feature="advanced_analytics" variant="card" upgradeUrl="/upgrade" />
|
|
290
|
-
<UpgradePrompt feature="advanced_analytics" variant="banner" />
|
|
291
|
-
<UpgradePrompt feature="advanced_analytics" variant="inline" />
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## Tailwind CSS Preset
|
|
295
|
-
|
|
296
|
-
Optional Tailwind preset for styling the `data-nozle` attributes on components.
|
|
297
|
-
|
|
298
|
-
```ts
|
|
299
|
-
// tailwind.config.ts
|
|
300
|
-
import { nozlePreset } from "@nozle-js/react/styles/preset";
|
|
301
|
-
|
|
302
|
-
export default {
|
|
303
|
-
presets: [nozlePreset],
|
|
304
|
-
};
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
## TypeScript
|
|
308
|
-
|
|
309
|
-
All hooks and components are fully typed. Exported types:
|
|
310
|
-
|
|
311
|
-
```ts
|
|
312
|
-
import type {
|
|
313
|
-
BillingState,
|
|
314
|
-
UseCanResult,
|
|
315
|
-
UseUsageResult,
|
|
316
|
-
UsePlanResult,
|
|
317
|
-
UseCheckoutResult,
|
|
318
|
-
Plan,
|
|
319
|
-
PricingPlan,
|
|
320
|
-
PricingTableProps,
|
|
321
|
-
UsageMeterProps,
|
|
322
|
-
PlanBadgeProps,
|
|
323
|
-
UpgradePromptProps,
|
|
324
|
-
} from "@nozle-js/react";
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
## License
|
|
54
|
+
- `PricingTable` and `usePlans` read the public catalog with the publishable key.
|
|
55
|
+
- `CheckoutButton`, `UpgradeButton`, `UpgradeModal`, and the default `PricingTable` CTA call the merchant's `createCheckout` callback.
|
|
56
|
+
- `Checkout` renders Stripe hosted/embedded checkout results supplied by the merchant.
|
|
57
|
+
- Gates, usage displays, `PlanBadge`, and `PaymentMethodDisplay` are presentational and consume caller-supplied data.
|
|
328
58
|
|
|
329
|
-
|
|
59
|
+
Customer billing, invoices, cancellation, top-ups, subscriptions, entitlements, and credits belong behind authenticated merchant endpoints. Never put an `sk_`, master key, or internal credential in browser code.
|