@crediball/react 0.4.0 → 0.6.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/dist/CrediballProvider.d.ts +29 -7
- package/dist/CrediballProvider.js +93 -18
- package/dist/PaywallModal.js +1 -1
- package/package.json +2 -2
|
@@ -27,16 +27,32 @@ export interface CrediballProviderProps {
|
|
|
27
27
|
*/
|
|
28
28
|
amounts?: number[];
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* Optional override for the custom-amount top-up. If omitted, the paywall
|
|
31
|
+
* starts a Crediball-hosted Stripe Checkout itself (using publishableKey +
|
|
32
|
+
* userId) and redirects — no backend wiring needed, just connect payouts in the
|
|
33
|
+
* dashboard. Provide this only to run your own top-up/checkout flow instead.
|
|
33
34
|
*/
|
|
34
35
|
onTopup?: (amount: number) => void | Promise<void>;
|
|
35
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Optional override for one-time packages. If omitted, the paywall starts a
|
|
38
|
+
* Crediball-hosted Stripe Checkout itself and redirects (connect payouts in the
|
|
39
|
+
* dashboard). Provide this only to run your own flow (e.g. `meter.topup({ userId,
|
|
40
|
+
* packageId })` server-side).
|
|
41
|
+
*/
|
|
36
42
|
onSelectPackage?: (pkg: PackageOption) => void | Promise<void>;
|
|
37
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Optional override for subscription plans. If omitted, the paywall starts a
|
|
45
|
+
* recurring Stripe Checkout itself (connect payouts in the dashboard) and Stripe
|
|
46
|
+
* grants the plan's credits each period. Provide this only to run your own flow
|
|
47
|
+
* (e.g. `meter.subscribe({ userId, planId })` server-side).
|
|
48
|
+
*/
|
|
38
49
|
onSelectPlan?: (plan: SubscriptionPlanOption) => void | Promise<void>;
|
|
39
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Called when the user cancels their subscription. Cancellation is server-side
|
|
52
|
+
* only (it needs your secret key), so the paywall shows a Cancel button on the
|
|
53
|
+
* active-subscription banner ONLY when this is provided — wire it to
|
|
54
|
+
* `meter.cancelSubscription({ userId, subscriptionId })` on your backend.
|
|
55
|
+
*/
|
|
40
56
|
onCancelSubscription?: (subscriptionId: string) => void | Promise<void>;
|
|
41
57
|
/**
|
|
42
58
|
* Auto-detect insufficient-credit responses from any fetch() call and open the
|
|
@@ -47,9 +63,15 @@ export interface CrediballProviderProps {
|
|
|
47
63
|
* ndjson (streaming/SSE routes where the error arrives as a stream chunk).
|
|
48
64
|
*/
|
|
49
65
|
watchFetch?: boolean;
|
|
50
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Show a free-form amount field in the paywall. Defaults to your dashboard's
|
|
68
|
+
* custom-topup setting (with its min/max) when packages are loaded — set this
|
|
69
|
+
* explicitly only to override (e.g. `false` to force-hide it).
|
|
70
|
+
*/
|
|
51
71
|
allowCustom?: boolean;
|
|
72
|
+
/** Override the custom-amount minimum (defaults to the dashboard's configured minimum). */
|
|
52
73
|
customMin?: number;
|
|
74
|
+
/** Override the custom-amount maximum (defaults to the dashboard's configured maximum). */
|
|
53
75
|
customMax?: number;
|
|
54
76
|
/** Currency symbol shown in the paywall (default "€"). Match your app's billing currency. */
|
|
55
77
|
currencySymbol?: string;
|
|
@@ -38,7 +38,7 @@ const getEmptySnapshot = () => EMPTY_SNAPSHOT;
|
|
|
38
38
|
* SSE/streaming routes (text/event-stream, ndjson) are also handled automatically —
|
|
39
39
|
* the watcher scans each chunk for credit errors, so no manual wiring is needed.
|
|
40
40
|
*/
|
|
41
|
-
export function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts = [5, 10, 20], onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch = true, allowCustom
|
|
41
|
+
export function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts = [5, 10, 20], onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch = true, allowCustom, customMin, customMax, currencySymbol = "€", title, description, accentColor, }) {
|
|
42
42
|
const [open, setOpen] = useState(false);
|
|
43
43
|
const showPaywall = useCallback(() => setOpen(true), []);
|
|
44
44
|
const hidePaywall = useCallback(() => setOpen(false), []);
|
|
@@ -92,40 +92,115 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
92
92
|
topUp,
|
|
93
93
|
refresh,
|
|
94
94
|
}), [showPaywall, hidePaywall, open, snapshot, costOf, topUp, refresh]);
|
|
95
|
+
// The page to return to after Stripe checkout — the one the user is on now.
|
|
96
|
+
function currentReturnPath() {
|
|
97
|
+
if (typeof window === "undefined")
|
|
98
|
+
return undefined;
|
|
99
|
+
return window.location.pathname + window.location.search;
|
|
100
|
+
}
|
|
101
|
+
// Drive a Crediball-hosted Stripe Checkout with just the publishable key — used
|
|
102
|
+
// when the host didn't wire its own onSelectPackage/onTopup handler, so the
|
|
103
|
+
// paywall's buttons work out of the box (the developer only has to connect
|
|
104
|
+
// payouts in the dashboard). Redirects the browser to Stripe on success.
|
|
105
|
+
async function startBuiltInCheckout(input) {
|
|
106
|
+
if (!client)
|
|
107
|
+
return;
|
|
108
|
+
const { url } = await client.createCheckout({ ...input, returnPath: currentReturnPath() });
|
|
109
|
+
if (typeof window !== "undefined")
|
|
110
|
+
window.location.assign(url);
|
|
111
|
+
}
|
|
95
112
|
async function handleAdd(amount) {
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
// A host-supplied top-up flow always wins.
|
|
114
|
+
if (onTopup) {
|
|
115
|
+
try {
|
|
116
|
+
await topUp(amount);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
setOpen(false);
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
98
122
|
}
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
// Otherwise start Stripe ourselves (custom amount). Leave the modal open on
|
|
124
|
+
// failure so the error is visible instead of the modal silently closing.
|
|
125
|
+
if (client) {
|
|
126
|
+
try {
|
|
127
|
+
await startBuiltInCheckout({ eurAmount: amount });
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.error("[crediball] checkout failed:", err);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
101
133
|
}
|
|
134
|
+
// Data-less mode with no handler: nothing to charge against.
|
|
135
|
+
setOpen(false);
|
|
102
136
|
}
|
|
103
137
|
async function handleSelectPackage(pkg) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
138
|
+
if (onSelectPackage) {
|
|
139
|
+
try {
|
|
140
|
+
await onSelectPackage(pkg);
|
|
141
|
+
notifyBalanceChanged();
|
|
142
|
+
await client?.refresh();
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
setOpen(false);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
108
148
|
}
|
|
109
|
-
|
|
110
|
-
|
|
149
|
+
if (client) {
|
|
150
|
+
try {
|
|
151
|
+
await startBuiltInCheckout({ packageId: pkg.id });
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.error("[crediball] checkout failed:", err);
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
111
157
|
}
|
|
158
|
+
setOpen(false);
|
|
112
159
|
}
|
|
113
160
|
async function handleSelectPlan(plan) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
161
|
+
// Host-supplied subscription flow wins.
|
|
162
|
+
if (onSelectPlan) {
|
|
163
|
+
try {
|
|
164
|
+
await onSelectPlan(plan);
|
|
165
|
+
notifyBalanceChanged();
|
|
166
|
+
await client?.refresh();
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
setOpen(false);
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
118
172
|
}
|
|
119
|
-
|
|
120
|
-
|
|
173
|
+
// Otherwise start the recurring Stripe Checkout ourselves. Leave the modal
|
|
174
|
+
// open on failure so the error is visible.
|
|
175
|
+
if (client) {
|
|
176
|
+
try {
|
|
177
|
+
const { url } = await client.createSubscriptionCheckout({
|
|
178
|
+
planId: plan.id,
|
|
179
|
+
returnPath: currentReturnPath(),
|
|
180
|
+
});
|
|
181
|
+
if (typeof window !== "undefined")
|
|
182
|
+
window.location.assign(url);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
console.error("[crediball] subscription checkout failed:", err);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
121
188
|
}
|
|
189
|
+
setOpen(false);
|
|
122
190
|
}
|
|
123
191
|
async function handleCancelSubscription(subscriptionId) {
|
|
124
192
|
await onCancelSubscription?.(subscriptionId);
|
|
125
193
|
await client?.refresh();
|
|
126
194
|
}
|
|
127
195
|
const pkgs = snapshot.packages;
|
|
128
|
-
|
|
196
|
+
// Auto-derive the custom-amount field from the dashboard's custom-topup config,
|
|
197
|
+
// so a developer who enabled it doesn't also have to pass allowCustom/min/max.
|
|
198
|
+
// Explicit props still win (allowCustom={false} force-hides it).
|
|
199
|
+
const customCfg = pkgs?.customTopup;
|
|
200
|
+
const effectiveAllowCustom = allowCustom ?? customCfg?.enabled ?? false;
|
|
201
|
+
const effectiveCustomMin = customMin ?? customCfg?.minEur;
|
|
202
|
+
const effectiveCustomMax = customMax ?? customCfg?.maxEur ?? undefined;
|
|
203
|
+
return (_jsxs(CrediballContext.Provider, { value: ctx, children: [children, _jsx(PaywallModal, { open: open, packages: pkgs?.packages, onSelectPackage: handleSelectPackage, subscriptionPlans: pkgs?.subscriptionPlans, onSelectPlan: handleSelectPlan, activeSubscription: pkgs?.activeSubscription, onCancelSubscription: handleCancelSubscription, amounts: amounts, amountPrefix: `Add ${currencySymbol}`, onAdd: handleAdd, onClose: hidePaywall, allowCustom: effectiveAllowCustom, customMin: effectiveCustomMin, customMax: effectiveCustomMax, currencySymbol: currencySymbol, balance: snapshot.balance ?? undefined, balanceRate: pkgs?.customTopup.enabled ? pkgs.customTopup.rate : undefined, currency: pkgs?.currency, usage: snapshot.usage?.usedCredits ?? undefined, title: title, description: description, accentColor: accentColor })] }));
|
|
129
204
|
}
|
|
130
205
|
/**
|
|
131
206
|
* Access the paywall + live data from anywhere inside <CrediballProvider>.
|
package/dist/PaywallModal.js
CHANGED
|
@@ -130,7 +130,7 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
130
130
|
borderRadius: theme.radiusCard,
|
|
131
131
|
background: "#f5f5f7",
|
|
132
132
|
border: `1px solid ${theme.hairline}`,
|
|
133
|
-
}, children: [_jsx("div", { style: { fontSize: 13, fontWeight: 600, color: theme.ink }, children: cancelDone ? "Subscription canceled" : `${activeSubscription.planName} · ${activeSubscription.planPeriod}` }), !cancelDone && (_jsx("button", { onClick: handleCancelSubscription, disabled: cancelPending, style: {
|
|
133
|
+
}, children: [_jsx("div", { style: { fontSize: 13, fontWeight: 600, color: theme.ink }, children: cancelDone ? "Subscription canceled" : `${activeSubscription.planName} · ${activeSubscription.planPeriod}` }), onCancelSubscription && !cancelDone && (_jsx("button", { onClick: handleCancelSubscription, disabled: cancelPending, style: {
|
|
134
134
|
marginTop: 8,
|
|
135
135
|
appearance: "none",
|
|
136
136
|
border: "none",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediball/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Drop-in React components for showing Crediball credits inside your AI app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Filippo Rezzadore <filipporezzadore@gmail.com>",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"react": ">=18"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@crediball/core": "^0.
|
|
54
|
+
"@crediball/core": "^0.2.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/react": "^18.3.11",
|