@crediball/react 0.7.0 → 0.9.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 +37 -2
- package/dist/CrediballProvider.js +82 -71
- package/dist/CreditIndicator.js +4 -2
- package/dist/CreditsBadge.js +6 -3
- package/dist/InlineValue.d.ts +11 -0
- package/dist/InlineValue.js +10 -0
- package/dist/ReferralCard.d.ts +30 -0
- package/dist/ReferralCard.js +70 -0
- package/dist/format.d.ts +1 -4
- package/dist/format.js +1 -13
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/useReferral.d.ts +42 -0
- package/dist/useReferral.js +92 -0
- package/dist/variables/CrediballBalance.js +2 -1
- package/dist/variables/CrediballLowCreditWarning.js +9 -4
- package/dist/variables/CrediballTopUpButton.js +2 -2
- package/dist/variables/CrediballUsage.js +2 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { type PackageOption, type SubscriptionPlanOption } from "./PaywallModal";
|
|
3
|
-
import { type UsagePeriod } from "@crediball/core";
|
|
3
|
+
import { type UsagePeriod, type PackagesPayload, type CrediballUiContent } from "@crediball/core";
|
|
4
4
|
export interface CrediballProviderProps {
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
/**
|
|
@@ -84,7 +84,36 @@ export interface CrediballProviderProps {
|
|
|
84
84
|
description?: string;
|
|
85
85
|
/** Override the accent color (defaults to Action Blue). */
|
|
86
86
|
accentColor?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Apply the theme (colors, radii, font) the developer published on the
|
|
89
|
+
* Crediball dashboard's UI-patterns page — served alongside packages — as
|
|
90
|
+
* `--crediball-*` CSS variables, so preview edits reflect here with no code
|
|
91
|
+
* change. On by default; the published theme takes precedence over any
|
|
92
|
+
* `--crediball-*` vars you set in your own CSS. Set false to opt out and
|
|
93
|
+
* theme entirely from your own stylesheet.
|
|
94
|
+
*/
|
|
95
|
+
applyRemoteTheme?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Automatic referral handling. On by default: any visit with `?ref=CODE` in
|
|
98
|
+
* the URL is captured (stored locally + click counted), and once a `userId`
|
|
99
|
+
* is present the stored code is attached to that user as a pending referral —
|
|
100
|
+
* so with referrals enabled in the dashboard there is zero extra wiring.
|
|
101
|
+
* Rewards are only ever granted server-side when the conversion condition is
|
|
102
|
+
* met. Set false to manage capture/completion yourself via @crediball/core.
|
|
103
|
+
*/
|
|
104
|
+
captureReferrals?: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Internal: the provider's raw connection config, for hooks (useReferral) that
|
|
108
|
+
* talk to the API directly instead of reading the polled snapshot.
|
|
109
|
+
*/
|
|
110
|
+
interface CrediballConfigValue {
|
|
111
|
+
publishableKey?: string;
|
|
112
|
+
userId?: string;
|
|
113
|
+
apiUrl?: string;
|
|
87
114
|
}
|
|
115
|
+
/** Internal hook: the provider's publishableKey/userId/apiUrl. Throws outside a provider. */
|
|
116
|
+
export declare function useCrediballConfig(): CrediballConfigValue;
|
|
88
117
|
interface CrediballContextValue {
|
|
89
118
|
/**
|
|
90
119
|
* Open the paywall modal. Always safe to call — never guard with "already shown"
|
|
@@ -116,6 +145,12 @@ interface CrediballContextValue {
|
|
|
116
145
|
topUp: (amount: number) => Promise<void>;
|
|
117
146
|
/** Refetch balance, usage, costs, and packages immediately. No-op without publishableKey/userId. */
|
|
118
147
|
refresh: () => Promise<void>;
|
|
148
|
+
/** The live packages payload (top-up options, plans, custom-topup, currency, ui). null until loaded. */
|
|
149
|
+
packages: PackagesPayload | null;
|
|
150
|
+
/** Currency symbol derived from the dashboard's configured currency (e.g. "€", "$"). */
|
|
151
|
+
currencySymbol: string;
|
|
152
|
+
/** Developer-published default copy from the dashboard, used as a label fallback. null when unconfigured. */
|
|
153
|
+
content: CrediballUiContent | null;
|
|
119
154
|
}
|
|
120
155
|
/**
|
|
121
156
|
* Pattern B, automated. Wrap your app once:
|
|
@@ -137,7 +172,7 @@ interface CrediballContextValue {
|
|
|
137
172
|
* SSE/streaming routes (text/event-stream, ndjson) are also handled automatically —
|
|
138
173
|
* the watcher scans each chunk for credit errors, so no manual wiring is needed.
|
|
139
174
|
*/
|
|
140
|
-
export declare function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts, onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch, allowCustom, customMin, customMax, currencySymbol: currencySymbolProp, title, description, accentColor, }: CrediballProviderProps): import("react").JSX.Element;
|
|
175
|
+
export declare function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts, onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch, allowCustom, customMin, customMax, currencySymbol: currencySymbolProp, title, description, accentColor, applyRemoteTheme, captureReferrals, }: CrediballProviderProps): import("react").JSX.Element;
|
|
141
176
|
/**
|
|
142
177
|
* Access the paywall + live data from anywhere inside <CrediballProvider>.
|
|
143
178
|
*
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
4
4
|
import { PaywallModal } from "./PaywallModal";
|
|
5
5
|
import { subscribeFetchWatcher } from "./fetchWatcher";
|
|
6
6
|
import { subscribeInsufficientCredits } from "./creditEvent";
|
|
7
|
-
import { getCrediballClient, notifyBalanceChanged } from "@crediball/core";
|
|
7
|
+
import { getCrediballClient, notifyBalanceChanged, loadRemoteFont, themeToCssVars, captureReferral, completeStoredReferral, } from "@crediball/core";
|
|
8
|
+
const CrediballConfigContext = createContext(null);
|
|
9
|
+
/** Internal hook: the provider's publishableKey/userId/apiUrl. Throws outside a provider. */
|
|
10
|
+
export function useCrediballConfig() {
|
|
11
|
+
const ctx = useContext(CrediballConfigContext);
|
|
12
|
+
if (!ctx) {
|
|
13
|
+
throw new Error("This hook must be used inside <CrediballProvider>.");
|
|
14
|
+
}
|
|
15
|
+
return ctx;
|
|
16
|
+
}
|
|
8
17
|
const CrediballContext = createContext(null);
|
|
9
18
|
const EMPTY_SNAPSHOT = {
|
|
10
19
|
balance: null,
|
|
@@ -54,8 +63,25 @@ function symbolForCurrency(iso) {
|
|
|
54
63
|
* SSE/streaming routes (text/event-stream, ndjson) are also handled automatically —
|
|
55
64
|
* the watcher scans each chunk for credit errors, so no manual wiring is needed.
|
|
56
65
|
*/
|
|
57
|
-
export function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts = [5, 10, 20], onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch = true, allowCustom, customMin, customMax, currencySymbol: currencySymbolProp, title, description, accentColor, }) {
|
|
66
|
+
export function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts = [5, 10, 20], onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch = true, allowCustom, customMin, customMax, currencySymbol: currencySymbolProp, title, description, accentColor, applyRemoteTheme = true, captureReferrals = true, }) {
|
|
58
67
|
const [open, setOpen] = useState(false);
|
|
68
|
+
// Referral auto-capture: pick up a `?ref=CODE` visit into localStorage (and
|
|
69
|
+
// count the click) as soon as the provider mounts — the visitor usually has
|
|
70
|
+
// no account yet at this point.
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!captureReferrals || !publishableKey)
|
|
73
|
+
return;
|
|
74
|
+
captureReferral({ publishableKey, apiUrl });
|
|
75
|
+
}, [captureReferrals, publishableKey, apiUrl]);
|
|
76
|
+
// Referral auto-complete: the moment a userId is known (signup finished, or
|
|
77
|
+
// a signed-in user returns), attach any stored code to them as a pending
|
|
78
|
+
// referral. Best-effort — a failure leaves the code stored for a retry on
|
|
79
|
+
// the next mount.
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!captureReferrals || !publishableKey || !userId)
|
|
82
|
+
return;
|
|
83
|
+
void completeStoredReferral({ publishableKey, userId, apiUrl }).catch(() => { });
|
|
84
|
+
}, [captureReferrals, publishableKey, userId, apiUrl]);
|
|
59
85
|
const showPaywall = useCallback(() => setOpen(true), []);
|
|
60
86
|
const hidePaywall = useCallback(() => setOpen(false), []);
|
|
61
87
|
// Keep a ref so the fetch-watcher closure always calls the latest version.
|
|
@@ -84,6 +110,20 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
84
110
|
return () => client.stop();
|
|
85
111
|
}, [client]);
|
|
86
112
|
const snapshot = useSyncExternalStore(client ? client.subscribe : noopSubscribe, client ? client.getSnapshot : getEmptySnapshot, client ? client.getSnapshot : getEmptySnapshot);
|
|
113
|
+
const pkgs = snapshot.packages;
|
|
114
|
+
const uiTheme = pkgs?.ui?.theme ?? null;
|
|
115
|
+
const uiContent = pkgs?.ui?.content ?? null;
|
|
116
|
+
// Follow the dashboard's configured currency once packages load, unless the
|
|
117
|
+
// host pins an explicit symbol. Falls back to "€" only until the first fetch
|
|
118
|
+
// resolves, so switching currencies on the Packages page never requires a
|
|
119
|
+
// matching code change/redeploy here.
|
|
120
|
+
const currencySymbol = currencySymbolProp ?? (pkgs?.currency ? symbolForCurrency(pkgs.currency) : "€");
|
|
121
|
+
// Load the published theme's webfont, if any, so --crediball-font renders.
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!applyRemoteTheme || !uiTheme?.fontUrl)
|
|
124
|
+
return;
|
|
125
|
+
loadRemoteFont(uiTheme.fontUrl);
|
|
126
|
+
}, [applyRemoteTheme, uiTheme?.fontUrl]);
|
|
87
127
|
const costOf = useCallback((event) => client?.costOf(event), [client]);
|
|
88
128
|
const refresh = useCallback(async () => {
|
|
89
129
|
await client?.refresh();
|
|
@@ -107,7 +147,10 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
107
147
|
costOf,
|
|
108
148
|
topUp,
|
|
109
149
|
refresh,
|
|
110
|
-
|
|
150
|
+
packages: pkgs,
|
|
151
|
+
currencySymbol,
|
|
152
|
+
content: uiContent,
|
|
153
|
+
}), [showPaywall, hidePaywall, open, snapshot, costOf, topUp, refresh, pkgs, currencySymbol, uiContent]);
|
|
111
154
|
// The page to return to after Stripe checkout — the one the user is on now.
|
|
112
155
|
function currentReturnPath() {
|
|
113
156
|
if (typeof window === "undefined")
|
|
@@ -125,35 +168,14 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
125
168
|
if (typeof window !== "undefined")
|
|
126
169
|
window.location.assign(url);
|
|
127
170
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
171
|
+
// Shared shape for handleAdd/handleSelectPackage/handleSelectPlan: a host-supplied
|
|
172
|
+
// handler always wins (and refreshes balance/usage on completion); otherwise fall
|
|
173
|
+
// back to the built-in Stripe checkout when a data client is available, leaving the
|
|
174
|
+
// modal open on failure so the error stays visible; otherwise just close the modal.
|
|
175
|
+
async function runPaywallAction(hostHandler, arg, builtIn, errorLabel) {
|
|
176
|
+
if (hostHandler) {
|
|
131
177
|
try {
|
|
132
|
-
await
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
setOpen(false);
|
|
136
|
-
}
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
// Otherwise start Stripe ourselves (custom amount). Leave the modal open on
|
|
140
|
-
// failure so the error is visible instead of the modal silently closing.
|
|
141
|
-
if (client) {
|
|
142
|
-
try {
|
|
143
|
-
await startBuiltInCheckout({ eurAmount: amount });
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
console.error("[crediball] checkout failed:", err);
|
|
147
|
-
}
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
// Data-less mode with no handler: nothing to charge against.
|
|
151
|
-
setOpen(false);
|
|
152
|
-
}
|
|
153
|
-
async function handleSelectPackage(pkg) {
|
|
154
|
-
if (onSelectPackage) {
|
|
155
|
-
try {
|
|
156
|
-
await onSelectPackage(pkg);
|
|
178
|
+
await hostHandler(arg);
|
|
157
179
|
notifyBalanceChanged();
|
|
158
180
|
await client?.refresh();
|
|
159
181
|
}
|
|
@@ -164,56 +186,38 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
164
186
|
}
|
|
165
187
|
if (client) {
|
|
166
188
|
try {
|
|
167
|
-
await
|
|
189
|
+
await builtIn();
|
|
168
190
|
}
|
|
169
191
|
catch (err) {
|
|
170
|
-
console.error(
|
|
192
|
+
console.error(`[crediball] ${errorLabel} failed:`, err);
|
|
171
193
|
}
|
|
172
194
|
return;
|
|
173
195
|
}
|
|
196
|
+
// Data-less mode with no handler: nothing to charge against.
|
|
174
197
|
setOpen(false);
|
|
175
198
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const { url } = await client.createSubscriptionCheckout({
|
|
194
|
-
planId: plan.id,
|
|
195
|
-
returnPath: currentReturnPath(),
|
|
196
|
-
});
|
|
197
|
-
if (typeof window !== "undefined")
|
|
198
|
-
window.location.assign(url);
|
|
199
|
-
}
|
|
200
|
-
catch (err) {
|
|
201
|
-
console.error("[crediball] subscription checkout failed:", err);
|
|
202
|
-
}
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
setOpen(false);
|
|
199
|
+
function handleAdd(amount) {
|
|
200
|
+
return runPaywallAction(onTopup, amount, () => startBuiltInCheckout({ eurAmount: amount }), "checkout");
|
|
201
|
+
}
|
|
202
|
+
function handleSelectPackage(pkg) {
|
|
203
|
+
return runPaywallAction(onSelectPackage, pkg, () => startBuiltInCheckout({ packageId: pkg.id }), "checkout");
|
|
204
|
+
}
|
|
205
|
+
function handleSelectPlan(plan) {
|
|
206
|
+
return runPaywallAction(onSelectPlan, plan, async () => {
|
|
207
|
+
if (!client)
|
|
208
|
+
return;
|
|
209
|
+
const { url } = await client.createSubscriptionCheckout({
|
|
210
|
+
planId: plan.id,
|
|
211
|
+
returnPath: currentReturnPath(),
|
|
212
|
+
});
|
|
213
|
+
if (typeof window !== "undefined")
|
|
214
|
+
window.location.assign(url);
|
|
215
|
+
}, "subscription checkout");
|
|
206
216
|
}
|
|
207
217
|
async function handleCancelSubscription(subscriptionId) {
|
|
208
218
|
await onCancelSubscription?.(subscriptionId);
|
|
209
219
|
await client?.refresh();
|
|
210
220
|
}
|
|
211
|
-
const pkgs = snapshot.packages;
|
|
212
|
-
// Follow the dashboard's configured currency once packages load, unless the
|
|
213
|
-
// host pins an explicit symbol. Falls back to "€" only until the first fetch
|
|
214
|
-
// resolves, so switching currencies on the Packages page never requires a
|
|
215
|
-
// matching code change/redeploy here.
|
|
216
|
-
const currencySymbol = currencySymbolProp ?? (pkgs?.currency ? symbolForCurrency(pkgs.currency) : "€");
|
|
217
221
|
// Auto-derive the custom-amount field from the dashboard's custom-topup config,
|
|
218
222
|
// so a developer who enabled it doesn't also have to pass allowCustom/min/max.
|
|
219
223
|
// Explicit props still win (allowCustom={false} force-hides it).
|
|
@@ -221,7 +225,14 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
221
225
|
const effectiveAllowCustom = allowCustom ?? customCfg?.enabled ?? false;
|
|
222
226
|
const effectiveCustomMin = customMin ?? customCfg?.minEur;
|
|
223
227
|
const effectiveCustomMax = customMax ?? customCfg?.maxEur ?? undefined;
|
|
224
|
-
|
|
228
|
+
// Published theme → --crediball-* CSS vars, applied on a display:contents
|
|
229
|
+
// wrapper so it themes every descendant (and the paywall) without adding a
|
|
230
|
+
// layout box. Host props/CSS are overridden by design (dashboard wins).
|
|
231
|
+
const themeVars = applyRemoteTheme ? themeToCssVars(uiTheme) : {};
|
|
232
|
+
const hasThemeVars = Object.keys(themeVars).length > 0;
|
|
233
|
+
const inner = (_jsxs(_Fragment, { children: [children, _jsx(PaywallModal, { open: open, packages: pkgs?.packages, onSelectPackage: handleSelectPackage, subscriptionPlans: pkgs?.subscriptionPlans, onSelectPlan: handleSelectPlan, activeSubscription: pkgs?.activeSubscription, onCancelSubscription: onCancelSubscription ? handleCancelSubscription : undefined, 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 ?? uiContent?.paywallTitle, description: description ?? uiContent?.paywallDescription, addButtonText: uiContent?.paywallButtonText, accentColor: accentColor })] }));
|
|
234
|
+
const configCtx = useMemo(() => ({ publishableKey, userId, apiUrl }), [publishableKey, userId, apiUrl]);
|
|
235
|
+
return (_jsx(CrediballContext.Provider, { value: ctx, children: _jsx(CrediballConfigContext.Provider, { value: configCtx, children: hasThemeVars ? (_jsx("div", { style: { display: "contents", ...themeVars }, children: inner })) : (inner) }) }));
|
|
225
236
|
}
|
|
226
237
|
/**
|
|
227
238
|
* Access the paywall + live data from anywhere inside <CrediballProvider>.
|
package/dist/CreditIndicator.js
CHANGED
|
@@ -7,9 +7,11 @@ import { useCrediballOptional } from "./CrediballProvider";
|
|
|
7
7
|
* Pattern C — Subtle indicator. A quiet balance chip for a host app's top bar;
|
|
8
8
|
* clickable to open a top-up flow. Minimal and non-intrusive.
|
|
9
9
|
*/
|
|
10
|
-
export function CreditIndicator({ balanceLabel, label
|
|
10
|
+
export function CreditIndicator({ balanceLabel, label, onClick, accentColor, style, }) {
|
|
11
11
|
const ctx = useCrediballOptional();
|
|
12
12
|
const resolvedLabel = balanceLabel ?? (ctx?.balance != null ? formatCredits(ctx.balance) : "—");
|
|
13
|
+
// Prefer the host prop, then the dashboard-published default, then the built-in.
|
|
14
|
+
const resolvedText = label ?? ctx?.content?.indicatorLabel ?? "Credits:";
|
|
13
15
|
const resolvedOnClick = onClick ?? ctx?.openPaywall;
|
|
14
16
|
return (_jsxs("button", { onClick: resolvedOnClick, style: {
|
|
15
17
|
display: "inline-flex",
|
|
@@ -26,5 +28,5 @@ export function CreditIndicator({ balanceLabel, label = "Credits:", onClick, acc
|
|
|
26
28
|
padding: "4px 12px",
|
|
27
29
|
...accentStyle(accentColor),
|
|
28
30
|
...style,
|
|
29
|
-
}, children: [_jsx("span", { style: { color: theme.inkMuted }, children:
|
|
31
|
+
}, children: [_jsx("span", { style: { color: theme.inkMuted }, children: resolvedText }), _jsx("span", { style: { fontWeight: 600 }, children: resolvedLabel })] }));
|
|
30
32
|
}
|
package/dist/CreditsBadge.js
CHANGED
|
@@ -9,9 +9,12 @@ import { useCrediballOptional } from "./CrediballProvider";
|
|
|
9
9
|
* as the primary figure and an approximate money value beneath it. No Crediball
|
|
10
10
|
* branding.
|
|
11
11
|
*/
|
|
12
|
-
export function CreditsBadge({ credits, rate, currency = "EUR", label
|
|
12
|
+
export function CreditsBadge({ credits, rate, currency = "EUR", label, onAddCredits, addLabel, accentColor, style, }) {
|
|
13
13
|
const ctx = useCrediballOptional();
|
|
14
14
|
const resolvedCredits = credits ?? ctx?.balance ?? 0;
|
|
15
|
+
// Prefer the host prop, then the dashboard-published default, then the built-in.
|
|
16
|
+
const resolvedLabel = label ?? ctx?.content?.badgeLabel ?? "Credit balance";
|
|
17
|
+
const resolvedAddLabel = addLabel ?? ctx?.content?.badgeAddLabel ?? "Add credits";
|
|
15
18
|
const showMoney = typeof rate === "number" && rate > 0;
|
|
16
19
|
return (_jsxs("div", { style: {
|
|
17
20
|
display: "flex",
|
|
@@ -25,7 +28,7 @@ export function CreditsBadge({ credits, rate, currency = "EUR", label = "Credit
|
|
|
25
28
|
fontFamily: theme.font,
|
|
26
29
|
...accentStyle(accentColor),
|
|
27
30
|
...style,
|
|
28
|
-
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, color: theme.inkMuted }, children:
|
|
31
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, color: theme.inkMuted }, children: resolvedLabel }), _jsxs("div", { style: { fontSize: 28, fontWeight: 600, color: theme.ink, letterSpacing: "-0.01em" }, children: [formatCredits(resolvedCredits), _jsx("span", { style: { fontSize: 16, fontWeight: 400, color: theme.inkMuted, marginLeft: 6 }, children: "credits" })] }), showMoney ? (_jsxs("div", { style: { fontSize: 13, color: theme.inkMuted, marginTop: 2 }, children: ["\u2248 ", formatMoney(resolvedCredits / rate, currency)] })) : null] }), _jsx("button", { onClick: onAddCredits ?? ctx?.openPaywall, style: {
|
|
29
32
|
appearance: "none",
|
|
30
33
|
border: "none",
|
|
31
34
|
cursor: "pointer",
|
|
@@ -35,5 +38,5 @@ export function CreditsBadge({ credits, rate, currency = "EUR", label = "Credit
|
|
|
35
38
|
fontSize: 14,
|
|
36
39
|
padding: "8px 16px",
|
|
37
40
|
borderRadius: theme.radiusPill,
|
|
38
|
-
}, children:
|
|
41
|
+
}, children: resolvedAddLabel })] }));
|
|
39
42
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Shared rendering shell for the Tier-2 "variable" components (CrediballBalance,
|
|
4
|
+
* CrediballUsage, CrediballLowCreditWarning): an inline <span> that inherits the
|
|
5
|
+
* surrounding font/color so it reads naturally wherever it's placed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function InlineValue({ className, style, children, }: {
|
|
8
|
+
className?: string;
|
|
9
|
+
style?: CSSProperties;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Shared rendering shell for the Tier-2 "variable" components (CrediballBalance,
|
|
5
|
+
* CrediballUsage, CrediballLowCreditWarning): an inline <span> that inherits the
|
|
6
|
+
* surrounding font/color so it reads naturally wherever it's placed.
|
|
7
|
+
*/
|
|
8
|
+
export function InlineValue({ className, style, children, }) {
|
|
9
|
+
return (_jsx("span", { className: className, style: { font: "inherit", color: "currentColor", ...style }, children: children }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { type CrediballThemeOverrides } from "./theme";
|
|
3
|
+
export interface ReferralCardProps extends CrediballThemeOverrides {
|
|
4
|
+
/** Heading. Default "Invite friends". */
|
|
5
|
+
title?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Subtitle. Defaults to a reward blurb built from the dashboard config
|
|
8
|
+
* (e.g. "Earn 100 credits for every friend who joins.").
|
|
9
|
+
*/
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Copy-button label. Default "Copy". */
|
|
12
|
+
copyLabel?: string;
|
|
13
|
+
/** Share-button label. Default "Share". */
|
|
14
|
+
shareLabel?: string;
|
|
15
|
+
/** Hide the clicks / converted counters row. */
|
|
16
|
+
hideStats?: boolean;
|
|
17
|
+
style?: CSSProperties;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Drop-in invite card built on useReferral(). Place anywhere inside
|
|
21
|
+
* <CrediballProvider publishableKey userId>:
|
|
22
|
+
*
|
|
23
|
+
* <ReferralCard />
|
|
24
|
+
*
|
|
25
|
+
* Shows the user's invite link with Copy/Share actions and their referral
|
|
26
|
+
* stats. Lazily creates the code on first render, renders nothing while
|
|
27
|
+
* loading or when the referral program is disabled in the dashboard, and is
|
|
28
|
+
* themed by the same `--crediball-*` variables as every other component.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ReferralCard({ title, description, copyLabel, shareLabel, hideStats, accentColor, style, }: ReferralCardProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { theme, accentStyle } from "./theme";
|
|
4
|
+
import { formatCredits } from "./format";
|
|
5
|
+
import { useReferral } from "./useReferral";
|
|
6
|
+
/**
|
|
7
|
+
* Drop-in invite card built on useReferral(). Place anywhere inside
|
|
8
|
+
* <CrediballProvider publishableKey userId>:
|
|
9
|
+
*
|
|
10
|
+
* <ReferralCard />
|
|
11
|
+
*
|
|
12
|
+
* Shows the user's invite link with Copy/Share actions and their referral
|
|
13
|
+
* stats. Lazily creates the code on first render, renders nothing while
|
|
14
|
+
* loading or when the referral program is disabled in the dashboard, and is
|
|
15
|
+
* themed by the same `--crediball-*` variables as every other component.
|
|
16
|
+
*/
|
|
17
|
+
export function ReferralCard({ title = "Invite friends", description, copyLabel = "Copy", shareLabel = "Share", hideStats = false, accentColor, style, }) {
|
|
18
|
+
const { enabled, link, code, clicks, rewards, rewardCredits, referredRewardCredits, loading, copy, copied, share, } = useReferral();
|
|
19
|
+
if (loading || !enabled || !code)
|
|
20
|
+
return null;
|
|
21
|
+
const resolvedDescription = description ??
|
|
22
|
+
(rewardCredits > 0
|
|
23
|
+
? referredRewardCredits > 0
|
|
24
|
+
? `Earn ${formatCredits(rewardCredits)} credits for every friend who joins — they get ${formatCredits(referredRewardCredits)} credits too.`
|
|
25
|
+
: `Earn ${formatCredits(rewardCredits)} credits for every friend who joins.`
|
|
26
|
+
: undefined);
|
|
27
|
+
const canShare = typeof navigator !== "undefined" && typeof navigator.share === "function";
|
|
28
|
+
const buttonBase = {
|
|
29
|
+
appearance: "none",
|
|
30
|
+
cursor: "pointer",
|
|
31
|
+
fontFamily: theme.font,
|
|
32
|
+
fontSize: 14,
|
|
33
|
+
padding: "8px 16px",
|
|
34
|
+
borderRadius: theme.radiusPill,
|
|
35
|
+
whiteSpace: "nowrap",
|
|
36
|
+
};
|
|
37
|
+
return (_jsxs("div", { style: {
|
|
38
|
+
display: "flex",
|
|
39
|
+
flexDirection: "column",
|
|
40
|
+
gap: 14,
|
|
41
|
+
padding: 24,
|
|
42
|
+
border: `1px solid ${theme.hairline}`,
|
|
43
|
+
borderRadius: theme.radiusCard,
|
|
44
|
+
background: theme.canvas,
|
|
45
|
+
fontFamily: theme.font,
|
|
46
|
+
...accentStyle(accentColor),
|
|
47
|
+
...style,
|
|
48
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, fontWeight: 600, color: theme.ink }, children: title }), resolvedDescription ? (_jsx("div", { style: { fontSize: 13, color: theme.inkMuted, marginTop: 2 }, children: resolvedDescription })) : null] }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: [_jsx("div", { style: {
|
|
49
|
+
flex: "1 1 180px",
|
|
50
|
+
minWidth: 0,
|
|
51
|
+
overflow: "hidden",
|
|
52
|
+
textOverflow: "ellipsis",
|
|
53
|
+
whiteSpace: "nowrap",
|
|
54
|
+
fontSize: 14,
|
|
55
|
+
color: theme.ink,
|
|
56
|
+
border: `1px solid ${theme.hairline}`,
|
|
57
|
+
borderRadius: theme.radiusPill,
|
|
58
|
+
padding: "8px 14px",
|
|
59
|
+
}, title: link ?? code, children: link ?? code }), _jsx("button", { onClick: () => void copy(), style: {
|
|
60
|
+
...buttonBase,
|
|
61
|
+
border: `1px solid ${theme.hairline}`,
|
|
62
|
+
background: "transparent",
|
|
63
|
+
color: theme.ink,
|
|
64
|
+
}, children: copied ? "Copied!" : copyLabel }), canShare ? (_jsx("button", { onClick: () => void share(), style: {
|
|
65
|
+
...buttonBase,
|
|
66
|
+
border: "none",
|
|
67
|
+
background: theme.accent,
|
|
68
|
+
color: theme.onAccent,
|
|
69
|
+
}, children: shareLabel })) : null] }), hideStats ? null : (_jsxs("div", { style: { display: "flex", gap: 16, fontSize: 13, color: theme.inkMuted }, children: [_jsxs("span", { children: [_jsx("strong", { style: { color: theme.ink, fontWeight: 600 }, children: clicks }), " clicks"] }), _jsxs("span", { children: [_jsx("strong", { style: { color: theme.ink, fontWeight: 600 }, children: rewards.converted }), " ", "joined"] }), rewards.creditsEarned > 0 ? (_jsxs("span", { children: [_jsx("strong", { style: { color: theme.ink, fontWeight: 600 }, children: formatCredits(rewards.creditsEarned) }), " ", "credits earned"] })) : null] }))] }));
|
|
70
|
+
}
|
package/dist/format.d.ts
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function formatCredits(credits: number): string;
|
|
3
|
-
/** Formats a monetary amount in the given ISO 4217 currency, falling back to a plain number + code for unknown currencies. */
|
|
4
|
-
export declare function formatMoney(amount: number, currency: string): string;
|
|
1
|
+
export { formatCredits, formatMoney } from "@crediball/core";
|
package/dist/format.js
CHANGED
|
@@ -1,13 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export function formatCredits(credits) {
|
|
3
|
-
return new Intl.NumberFormat(undefined).format(Math.round(credits));
|
|
4
|
-
}
|
|
5
|
-
/** Formats a monetary amount in the given ISO 4217 currency, falling back to a plain number + code for unknown currencies. */
|
|
6
|
-
export function formatMoney(amount, currency) {
|
|
7
|
-
try {
|
|
8
|
-
return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(amount);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return `${amount.toFixed(2)} ${currency}`;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
1
|
+
export { formatCredits, formatMoney } from "@crediball/core";
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { PaywallModal, type PaywallModalProps, type PackageOption, type Subscrip
|
|
|
3
3
|
export { CreditIndicator, type CreditIndicatorProps } from "./CreditIndicator";
|
|
4
4
|
export { CrediballProvider, useCrediball, useCrediballOptional, type CrediballProviderProps, } from "./CrediballProvider";
|
|
5
5
|
export { usePaywall, isInsufficientCreditsError, type UsePaywallProps, type UsePaywallResult, } from "./usePaywall";
|
|
6
|
+
export { useReferral, type UseReferralResult } from "./useReferral";
|
|
7
|
+
export { ReferralCard, type ReferralCardProps } from "./ReferralCard";
|
|
6
8
|
export { tokens, theme, cssVars, accentStyle, type CrediballThemeOverrides } from "./theme";
|
|
7
9
|
export { notifyInsufficientCredits } from "./creditEvent";
|
|
8
10
|
export { CrediballBalance, type CrediballBalanceProps } from "./variables/CrediballBalance";
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export { PaywallModal, } from "./PaywallModal";
|
|
|
5
5
|
export { CreditIndicator } from "./CreditIndicator";
|
|
6
6
|
export { CrediballProvider, useCrediball, useCrediballOptional, } from "./CrediballProvider";
|
|
7
7
|
export { usePaywall, isInsufficientCreditsError, } from "./usePaywall";
|
|
8
|
+
export { useReferral } from "./useReferral";
|
|
9
|
+
export { ReferralCard } from "./ReferralCard";
|
|
8
10
|
export { tokens, theme, cssVars, accentStyle } from "./theme";
|
|
9
11
|
export { notifyInsufficientCredits } from "./creditEvent";
|
|
10
12
|
// Tier 2 — "variables" you place anywhere inside <CrediballProvider>.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface UseReferralResult {
|
|
2
|
+
/** False while loading, or when the referral program is off in the dashboard. */
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
/** The user's referral code (lazily created server-side on first read). */
|
|
5
|
+
code: string | null;
|
|
6
|
+
/** Full invite link: the dashboard's link template, or `origin/?ref=CODE` when unset. */
|
|
7
|
+
link: string | null;
|
|
8
|
+
/** How many times the invite link was opened. */
|
|
9
|
+
clicks: number;
|
|
10
|
+
/** Referral outcomes: pending / converted counts and credits earned so far. */
|
|
11
|
+
rewards: {
|
|
12
|
+
pending: number;
|
|
13
|
+
converted: number;
|
|
14
|
+
creditsEarned: number;
|
|
15
|
+
};
|
|
16
|
+
/** Credits the referrer earns per converted referral (dashboard config). */
|
|
17
|
+
rewardCredits: number;
|
|
18
|
+
/** Credits the invited friend gets on conversion (dashboard config, 0 = off). */
|
|
19
|
+
referredRewardCredits: number;
|
|
20
|
+
loading: boolean;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
/** Copy the invite link (falls back to the code) to the clipboard. */
|
|
23
|
+
copy: () => Promise<void>;
|
|
24
|
+
/** True for 2s after a successful copy — for "Copied!" feedback. */
|
|
25
|
+
copied: boolean;
|
|
26
|
+
/** Open the native share sheet with the invite link; falls back to copy. */
|
|
27
|
+
share: () => Promise<void>;
|
|
28
|
+
/** Refetch code/stats. */
|
|
29
|
+
refresh: () => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Headless referral hook — render any invite UI on top of it:
|
|
33
|
+
*
|
|
34
|
+
* const { link, copy, share, rewards, loading } = useReferral();
|
|
35
|
+
*
|
|
36
|
+
* Must be used inside <CrediballProvider publishableKey userId>. The referral
|
|
37
|
+
* code is created lazily by the server on the first call and stays stable.
|
|
38
|
+
* When the dashboard's referral program is disabled, `enabled` is false and
|
|
39
|
+
* everything else stays empty — render nothing in that case (as
|
|
40
|
+
* <ReferralCard/> does).
|
|
41
|
+
*/
|
|
42
|
+
export declare function useReferral(): UseReferralResult;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { fetchReferral } from "@crediball/core";
|
|
4
|
+
import { useCrediballConfig } from "./CrediballProvider";
|
|
5
|
+
const EMPTY_REWARDS = { pending: 0, converted: 0, creditsEarned: 0 };
|
|
6
|
+
/**
|
|
7
|
+
* Headless referral hook — render any invite UI on top of it:
|
|
8
|
+
*
|
|
9
|
+
* const { link, copy, share, rewards, loading } = useReferral();
|
|
10
|
+
*
|
|
11
|
+
* Must be used inside <CrediballProvider publishableKey userId>. The referral
|
|
12
|
+
* code is created lazily by the server on the first call and stays stable.
|
|
13
|
+
* When the dashboard's referral program is disabled, `enabled` is false and
|
|
14
|
+
* everything else stays empty — render nothing in that case (as
|
|
15
|
+
* <ReferralCard/> does).
|
|
16
|
+
*/
|
|
17
|
+
export function useReferral() {
|
|
18
|
+
const { publishableKey, userId, apiUrl } = useCrediballConfig();
|
|
19
|
+
const [data, setData] = useState(null);
|
|
20
|
+
const [loading, setLoading] = useState(true);
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
const [copied, setCopied] = useState(false);
|
|
23
|
+
const copiedTimer = useRef(null);
|
|
24
|
+
const refresh = useCallback(async () => {
|
|
25
|
+
if (!publishableKey || !userId) {
|
|
26
|
+
setLoading(false);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const next = await fetchReferral({ publishableKey, userId, apiUrl });
|
|
31
|
+
setData(next);
|
|
32
|
+
setError(null);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
setLoading(false);
|
|
39
|
+
}
|
|
40
|
+
}, [publishableKey, userId, apiUrl]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
setLoading(true);
|
|
43
|
+
void refresh();
|
|
44
|
+
}, [refresh]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return () => {
|
|
47
|
+
if (copiedTimer.current)
|
|
48
|
+
clearTimeout(copiedTimer.current);
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
const link = data?.link ?? null;
|
|
52
|
+
const code = data?.code ?? null;
|
|
53
|
+
const copy = useCallback(async () => {
|
|
54
|
+
const text = link ?? code;
|
|
55
|
+
if (!text || typeof navigator === "undefined" || !navigator.clipboard)
|
|
56
|
+
return;
|
|
57
|
+
await navigator.clipboard.writeText(text);
|
|
58
|
+
setCopied(true);
|
|
59
|
+
if (copiedTimer.current)
|
|
60
|
+
clearTimeout(copiedTimer.current);
|
|
61
|
+
copiedTimer.current = setTimeout(() => setCopied(false), 2000);
|
|
62
|
+
}, [link, code]);
|
|
63
|
+
const share = useCallback(async () => {
|
|
64
|
+
const url = link ?? undefined;
|
|
65
|
+
const nav = typeof navigator === "undefined" ? undefined : navigator;
|
|
66
|
+
if (url && nav?.share) {
|
|
67
|
+
try {
|
|
68
|
+
await nav.share({ url });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// dismissed or unsupported payload — fall through to copy
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
await copy();
|
|
76
|
+
}, [link, copy]);
|
|
77
|
+
return {
|
|
78
|
+
enabled: data?.enabled ?? false,
|
|
79
|
+
code,
|
|
80
|
+
link,
|
|
81
|
+
clicks: data?.clicks ?? 0,
|
|
82
|
+
rewards: data?.rewards ?? EMPTY_REWARDS,
|
|
83
|
+
rewardCredits: data?.rewardCredits ?? 0,
|
|
84
|
+
referredRewardCredits: data?.referredRewardCredits ?? 0,
|
|
85
|
+
loading,
|
|
86
|
+
error,
|
|
87
|
+
copy,
|
|
88
|
+
copied,
|
|
89
|
+
share,
|
|
90
|
+
refresh,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useCrediball } from "../CrediballProvider";
|
|
4
4
|
import { formatCredits } from "../format";
|
|
5
|
+
import { InlineValue } from "../InlineValue";
|
|
5
6
|
/**
|
|
6
7
|
* Tier 2 — a "variable" you drop anywhere inside <CrediballProvider>. Renders
|
|
7
8
|
* as an inline <span> that inherits the surrounding font, size, and color
|
|
@@ -15,5 +16,5 @@ export function CrediballBalance({ className, style, format = formatCredits, chi
|
|
|
15
16
|
if (typeof children === "function") {
|
|
16
17
|
return _jsx(_Fragment, { children: children({ balance, loading }) });
|
|
17
18
|
}
|
|
18
|
-
return (_jsx(
|
|
19
|
+
return (_jsx(InlineValue, { className: className, style: style, children: children ?? (balance == null ? "—" : format(balance)) }));
|
|
19
20
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { isBalanceLow } from "@crediball/core";
|
|
3
4
|
import { useCrediball } from "../CrediballProvider";
|
|
5
|
+
import { InlineValue } from "../InlineValue";
|
|
4
6
|
/**
|
|
5
7
|
* Tier 2 — renders nothing until the user's balance is low, then shows a
|
|
6
8
|
* message inline. Place it next to a specific action ("this costs 20 credits")
|
|
@@ -8,13 +10,16 @@ import { useCrediball } from "../CrediballProvider";
|
|
|
8
10
|
*
|
|
9
11
|
* <CrediballLowCreditWarning threshold={20} message="Not enough for one more image." />
|
|
10
12
|
*/
|
|
11
|
-
export function CrediballLowCreditWarning({ className, style, threshold, message
|
|
12
|
-
const { balance, isLow: contextIsLow } = useCrediball();
|
|
13
|
-
|
|
13
|
+
export function CrediballLowCreditWarning({ className, style, threshold, message, children, }) {
|
|
14
|
+
const { balance, isLow: contextIsLow, content } = useCrediball();
|
|
15
|
+
// Prefer the host prop, then the dashboard-published default, then the provider's.
|
|
16
|
+
const effectiveThreshold = threshold ?? content?.lowCreditThreshold;
|
|
17
|
+
const isLow = isBalanceLow(balance, effectiveThreshold, contextIsLow);
|
|
14
18
|
if (!isLow)
|
|
15
19
|
return null;
|
|
16
20
|
if (typeof children === "function") {
|
|
17
21
|
return _jsx(_Fragment, { children: children({ balance, isLow }) });
|
|
18
22
|
}
|
|
19
|
-
|
|
23
|
+
const resolvedMessage = message ?? content?.lowCreditMessage ?? "Low credit balance — top up to keep going.";
|
|
24
|
+
return (_jsx(InlineValue, { className: className, style: style, children: children ?? resolvedMessage }));
|
|
20
25
|
}
|
|
@@ -11,7 +11,7 @@ import { theme, accentStyle } from "../theme";
|
|
|
11
11
|
* <CrediballTopUpButton>Get more credits</CrediballTopUpButton>
|
|
12
12
|
*/
|
|
13
13
|
export function CrediballTopUpButton({ className, style, children, onClick, accentColor }) {
|
|
14
|
-
const { openPaywall } = useCrediball();
|
|
14
|
+
const { openPaywall, content } = useCrediball();
|
|
15
15
|
return (_jsx("button", { type: "button", className: className, onClick: onClick ?? openPaywall, style: {
|
|
16
16
|
font: "inherit",
|
|
17
17
|
appearance: "none",
|
|
@@ -23,5 +23,5 @@ export function CrediballTopUpButton({ className, style, children, onClick, acce
|
|
|
23
23
|
padding: "0.5em 1.1em",
|
|
24
24
|
...accentStyle(accentColor),
|
|
25
25
|
...style,
|
|
26
|
-
}, children: children ?? "Add credits" }));
|
|
26
|
+
}, children: children ?? content?.topUpButtonText ?? "Add credits" }));
|
|
27
27
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useCrediball } from "../CrediballProvider";
|
|
4
4
|
import { formatCredits } from "../format";
|
|
5
|
+
import { InlineValue } from "../InlineValue";
|
|
5
6
|
/**
|
|
6
7
|
* Tier 2 — credits consumed in the user's current period (their subscription's
|
|
7
8
|
* billing cycle, or the calendar month for non-subscribers). Inline, inherits
|
|
@@ -15,5 +16,5 @@ export function CrediballUsage({ className, style, show = "used", format = forma
|
|
|
15
16
|
if (typeof children === "function") {
|
|
16
17
|
return _jsx(_Fragment, { children: children({ usage, value, loading }) });
|
|
17
18
|
}
|
|
18
|
-
return (_jsx(
|
|
19
|
+
return (_jsx(InlineValue, { className: className, style: style, children: children ?? (value == null ? "—" : format(value)) }));
|
|
19
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediball/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.4.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/react": "^18.3.11",
|