@crediball/react 0.9.0 → 0.11.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 +13 -1
- package/dist/CrediballProvider.js +37 -3
- package/dist/PaywallModal.d.ts +41 -1
- package/dist/PaywallModal.js +108 -3
- package/dist/ReferralCard.d.ts +20 -1
- package/dist/ReferralCard.js +15 -2
- package/package.json +2 -2
|
@@ -54,6 +54,18 @@ export interface CrediballProviderProps {
|
|
|
54
54
|
* `meter.cancelSubscription({ userId, subscriptionId })` on your backend.
|
|
55
55
|
*/
|
|
56
56
|
onCancelSubscription?: (subscriptionId: string) => void | Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Optional override for the auto-topup opt-in. If omitted, the paywall starts a
|
|
59
|
+
* Crediball-hosted Stripe Checkout in setup mode itself (connect payouts in the
|
|
60
|
+
* dashboard) — no money changes hands there, it only saves a payment method.
|
|
61
|
+
* Provide this only to run your own flow (e.g.
|
|
62
|
+
* `meter.setupAutoTopupCheckout({ userId, packageId, thresholdCredits, ... })`
|
|
63
|
+
* server-side).
|
|
64
|
+
*/
|
|
65
|
+
onEnableAutoTopup?: (input: {
|
|
66
|
+
packageId: string;
|
|
67
|
+
thresholdCredits: number;
|
|
68
|
+
}) => void | Promise<void>;
|
|
57
69
|
/**
|
|
58
70
|
* Auto-detect insufficient-credit responses from any fetch() call and open the
|
|
59
71
|
* paywall automatically — no per-call wiring. Defaults to true. Set false to only
|
|
@@ -172,7 +184,7 @@ interface CrediballContextValue {
|
|
|
172
184
|
* SSE/streaming routes (text/event-stream, ndjson) are also handled automatically —
|
|
173
185
|
* the watcher scans each chunk for credit errors, so no manual wiring is needed.
|
|
174
186
|
*/
|
|
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;
|
|
187
|
+
export declare function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts, onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, onEnableAutoTopup, watchFetch, allowCustom, customMin, customMax, currencySymbol: currencySymbolProp, title, description, accentColor, applyRemoteTheme, captureReferrals, }: CrediballProviderProps): import("react").JSX.Element;
|
|
176
188
|
/**
|
|
177
189
|
* Access the paywall + live data from anywhere inside <CrediballProvider>.
|
|
178
190
|
*
|
|
@@ -4,7 +4,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, use
|
|
|
4
4
|
import { PaywallModal } from "./PaywallModal";
|
|
5
5
|
import { subscribeFetchWatcher } from "./fetchWatcher";
|
|
6
6
|
import { subscribeInsufficientCredits } from "./creditEvent";
|
|
7
|
-
import { getCrediballClient, notifyBalanceChanged, loadRemoteFont, themeToCssVars, captureReferral, completeStoredReferral, } from "@crediball/core";
|
|
7
|
+
import { getCrediballClient, notifyBalanceChanged, loadRemoteFont, themeToCssVars, captureReferral, completeStoredReferral, fetchReferral, } from "@crediball/core";
|
|
8
8
|
const CrediballConfigContext = createContext(null);
|
|
9
9
|
/** Internal hook: the provider's publishableKey/userId/apiUrl. Throws outside a provider. */
|
|
10
10
|
export function useCrediballConfig() {
|
|
@@ -63,7 +63,7 @@ function symbolForCurrency(iso) {
|
|
|
63
63
|
* SSE/streaming routes (text/event-stream, ndjson) are also handled automatically —
|
|
64
64
|
* the watcher scans each chunk for credit errors, so no manual wiring is needed.
|
|
65
65
|
*/
|
|
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, }) {
|
|
66
|
+
export function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts = [5, 10, 20], onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, onEnableAutoTopup, watchFetch = true, allowCustom, customMin, customMax, currencySymbol: currencySymbolProp, title, description, accentColor, applyRemoteTheme = true, captureReferrals = true, }) {
|
|
67
67
|
const [open, setOpen] = useState(false);
|
|
68
68
|
// Referral auto-capture: pick up a `?ref=CODE` visit into localStorage (and
|
|
69
69
|
// count the click) as soon as the provider mounts — the visitor usually has
|
|
@@ -82,6 +82,28 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
82
82
|
return;
|
|
83
83
|
void completeStoredReferral({ publishableKey, userId, apiUrl }).catch(() => { });
|
|
84
84
|
}, [captureReferrals, publishableKey, userId, apiUrl]);
|
|
85
|
+
// The user's own referral invite link + reward, for the paywall's
|
|
86
|
+
// "Refer a friend" row. Null while loading or when referrals are disabled.
|
|
87
|
+
const [referral, setReferral] = useState(null);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!publishableKey || !userId) {
|
|
90
|
+
setReferral(null);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let cancelled = false;
|
|
94
|
+
fetchReferral({ publishableKey, userId, apiUrl })
|
|
95
|
+
.then((data) => {
|
|
96
|
+
if (!cancelled)
|
|
97
|
+
setReferral(data.enabled ? data : null);
|
|
98
|
+
})
|
|
99
|
+
.catch(() => {
|
|
100
|
+
if (!cancelled)
|
|
101
|
+
setReferral(null);
|
|
102
|
+
});
|
|
103
|
+
return () => {
|
|
104
|
+
cancelled = true;
|
|
105
|
+
};
|
|
106
|
+
}, [publishableKey, userId, apiUrl]);
|
|
85
107
|
const showPaywall = useCallback(() => setOpen(true), []);
|
|
86
108
|
const hidePaywall = useCallback(() => setOpen(false), []);
|
|
87
109
|
// Keep a ref so the fetch-watcher closure always calls the latest version.
|
|
@@ -218,6 +240,18 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
218
240
|
await onCancelSubscription?.(subscriptionId);
|
|
219
241
|
await client?.refresh();
|
|
220
242
|
}
|
|
243
|
+
function handleEnableAutoTopup(input) {
|
|
244
|
+
return runPaywallAction(onEnableAutoTopup, input, async () => {
|
|
245
|
+
if (!client)
|
|
246
|
+
return;
|
|
247
|
+
const { url } = await client.createAutoTopupCheckout({
|
|
248
|
+
...input,
|
|
249
|
+
returnPath: currentReturnPath(),
|
|
250
|
+
});
|
|
251
|
+
if (typeof window !== "undefined")
|
|
252
|
+
window.location.assign(url);
|
|
253
|
+
}, "auto-topup setup");
|
|
254
|
+
}
|
|
221
255
|
// Auto-derive the custom-amount field from the dashboard's custom-topup config,
|
|
222
256
|
// so a developer who enabled it doesn't also have to pass allowCustom/min/max.
|
|
223
257
|
// Explicit props still win (allowCustom={false} force-hides it).
|
|
@@ -230,7 +264,7 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
230
264
|
// layout box. Host props/CSS are overridden by design (dashboard wins).
|
|
231
265
|
const themeVars = applyRemoteTheme ? themeToCssVars(uiTheme) : {};
|
|
232
266
|
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 })] }));
|
|
267
|
+
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, autoTopup: pkgs?.autoTopup, onEnableAutoTopup: handleEnableAutoTopup, 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, referralLink: referral?.link ?? undefined, referralReward: referral?.rewardCredits, referralLabel: uiContent?.referralLabel, accentColor: accentColor })] }));
|
|
234
268
|
const configCtx = useMemo(() => ({ publishableKey, userId, apiUrl }), [publishableKey, userId, apiUrl]);
|
|
235
269
|
return (_jsx(CrediballContext.Provider, { value: ctx, children: _jsx(CrediballConfigContext.Provider, { value: configCtx, children: hasThemeVars ? (_jsx("div", { style: { display: "contents", ...themeVars }, children: inner })) : (inner) }) }));
|
|
236
270
|
}
|
package/dist/PaywallModal.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ export interface ActiveSubscriptionInfo {
|
|
|
22
22
|
planPeriod: string;
|
|
23
23
|
currentPeriodEnd: string;
|
|
24
24
|
}
|
|
25
|
+
/** The end user's current auto-topup rule (returned by listPackages() with userId). */
|
|
26
|
+
export interface AutoTopupInfo {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
packageId: string;
|
|
29
|
+
thresholdCredits: number;
|
|
30
|
+
status: "pending" | "active" | "needs_auth" | "failed";
|
|
31
|
+
}
|
|
25
32
|
export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
26
33
|
open: boolean;
|
|
27
34
|
/**
|
|
@@ -58,6 +65,24 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
|
58
65
|
* Call `meter.cancelSubscription({ userId, subscriptionId })` in your backend.
|
|
59
66
|
*/
|
|
60
67
|
onCancelSubscription?: (subscriptionId: string) => void | Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* The end user's current auto-topup rule (from `meter.listPackages({ userId })`
|
|
70
|
+
* or `meter.getAutoTopupStatus(userId)`). When absent/null and `onEnableAutoTopup`
|
|
71
|
+
* is provided, an opt-in row is shown below the packages so the user can turn
|
|
72
|
+
* on "keep me topped up automatically".
|
|
73
|
+
*/
|
|
74
|
+
autoTopup?: AutoTopupInfo | null;
|
|
75
|
+
/**
|
|
76
|
+
* Called when the user opts into auto top-up (only shown when `packages` is
|
|
77
|
+
* non-empty). Call `meter.setupAutoTopupCheckout({ userId, packageId,
|
|
78
|
+
* thresholdCredits, successUrl, cancelUrl })` in your backend — it saves a
|
|
79
|
+
* payment method, no money changes hands in this step — then redirect the
|
|
80
|
+
* browser to the returned `url`.
|
|
81
|
+
*/
|
|
82
|
+
onEnableAutoTopup?: (input: {
|
|
83
|
+
packageId: string;
|
|
84
|
+
thresholdCredits: number;
|
|
85
|
+
}) => void;
|
|
61
86
|
/**
|
|
62
87
|
* Legacy: flat list of euro amounts. Prefer `packages` + `onSelectPackage`.
|
|
63
88
|
* When both are provided, `packages` takes precedence.
|
|
@@ -96,6 +121,21 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
|
96
121
|
usageLabel?: string;
|
|
97
122
|
/** Hide the "Powered by Crediball" footer line (default false). */
|
|
98
123
|
hideBranding?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* The user's referral invite link. When provided, a "Refer a friend" row with
|
|
126
|
+
* a copyable link is shown at the bottom of the modal — a way to earn credits
|
|
127
|
+
* without paying. Wired automatically by <CrediballProvider> when referrals are
|
|
128
|
+
* enabled in the dashboard; pass it yourself when using the modal standalone.
|
|
129
|
+
*/
|
|
130
|
+
referralLink?: string | null;
|
|
131
|
+
/** Credits earned per successful referral, shown in the refer-a-friend blurb. */
|
|
132
|
+
referralReward?: number;
|
|
133
|
+
/**
|
|
134
|
+
* Label above the referral link. `{credits}` is replaced with `referralReward`.
|
|
135
|
+
* Default "Refer a friend · earn {credits} credits". Shown as written (not
|
|
136
|
+
* uppercased), so lowercase/sentence case is preserved.
|
|
137
|
+
*/
|
|
138
|
+
referralLabel?: string;
|
|
99
139
|
style?: CSSProperties;
|
|
100
140
|
}
|
|
101
141
|
/**
|
|
@@ -103,4 +143,4 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
|
103
143
|
* continue. The host controls when it appears; shows a small "Powered by
|
|
104
144
|
* Crediball" line by default (set `hideBranding` to remove it).
|
|
105
145
|
*/
|
|
106
|
-
export declare function PaywallModal({ open, packages, onSelectPackage, subscriptionPlans, onSelectPlan, activeSubscription, onCancelSubscription, amounts, amountPrefix, title, description, onAdd, onClose, allowCustom, addButtonText, customMin, customMax, currencySymbol, balance, balanceRate, currency, usage, usageLabel, hideBranding, accentColor, style, }: PaywallModalProps): import("react").JSX.Element | null;
|
|
146
|
+
export declare function PaywallModal({ open, packages, onSelectPackage, subscriptionPlans, onSelectPlan, activeSubscription, onCancelSubscription, autoTopup, onEnableAutoTopup, amounts, amountPrefix, title, description, onAdd, onClose, allowCustom, addButtonText, customMin, customMax, currencySymbol, balance, balanceRate, currency, usage, usageLabel, hideBranding, referralLink, referralReward, referralLabel, accentColor, style, }: PaywallModalProps): import("react").JSX.Element | null;
|
package/dist/PaywallModal.js
CHANGED
|
@@ -19,7 +19,7 @@ function sectionLabelStyle(marginBottom) {
|
|
|
19
19
|
* continue. The host controls when it appears; shows a small "Powered by
|
|
20
20
|
* Crediball" line by default (set `hideBranding` to remove it).
|
|
21
21
|
*/
|
|
22
|
-
export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlans, onSelectPlan, activeSubscription, onCancelSubscription, amounts = [5, 10], amountPrefix = "Add €", title = "You need more credits to continue.", description = "Top up to keep using this feature.", onAdd, onClose, allowCustom = false, addButtonText = "Add", customMin = 1, customMax, currencySymbol = "€", balance, balanceRate, currency = "EUR", usage, usageLabel = "Usage", hideBranding = false, accentColor, style, }) {
|
|
22
|
+
export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlans, onSelectPlan, activeSubscription, onCancelSubscription, autoTopup, onEnableAutoTopup, amounts = [5, 10], amountPrefix = "Add €", title = "You need more credits to continue.", description = "Top up to keep using this feature.", onAdd, onClose, allowCustom = false, addButtonText = "Add", customMin = 1, customMax, currencySymbol = "€", balance, balanceRate, currency = "EUR", usage, usageLabel = "Usage", hideBranding = false, referralLink, referralReward, referralLabel = "Refer a friend · earn {credits} credits", accentColor, style, }) {
|
|
23
23
|
// Stack the top-up buttons one-per-line when the dialog itself is narrow.
|
|
24
24
|
// Measured off the dialog's own box (via ResizeObserver) rather than the
|
|
25
25
|
// window — in normal use the two are the same width, but this also makes
|
|
@@ -31,6 +31,10 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
31
31
|
const [customError, setCustomError] = useState(null);
|
|
32
32
|
const [cancelPending, setCancelPending] = useState(false);
|
|
33
33
|
const [cancelDone, setCancelDone] = useState(false);
|
|
34
|
+
const [referralCopied, setReferralCopied] = useState(false);
|
|
35
|
+
const [autoTopupOpen, setAutoTopupOpen] = useState(false);
|
|
36
|
+
const [autoTopupPackageId, setAutoTopupPackageId] = useState("");
|
|
37
|
+
const [autoTopupThreshold, setAutoTopupThreshold] = useState("");
|
|
34
38
|
useEffect(() => {
|
|
35
39
|
const el = overlayRef.current;
|
|
36
40
|
if (!el || typeof ResizeObserver === "undefined")
|
|
@@ -42,6 +46,15 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
42
46
|
observer.observe(el);
|
|
43
47
|
return () => observer.disconnect();
|
|
44
48
|
}, []);
|
|
49
|
+
// Default the auto-topup picker to the cheapest package + its own credit
|
|
50
|
+
// amount as the suggested threshold, once packages are available.
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!packages || packages.length === 0 || autoTopupPackageId)
|
|
53
|
+
return;
|
|
54
|
+
const cheapest = packages.reduce((min, p) => (p.price < min.price ? p : min), packages[0]);
|
|
55
|
+
setAutoTopupPackageId(cheapest.id);
|
|
56
|
+
setAutoTopupThreshold(String(cheapest.credits));
|
|
57
|
+
}, [packages, autoTopupPackageId]);
|
|
45
58
|
if (!open)
|
|
46
59
|
return null;
|
|
47
60
|
async function handleCancelSubscription() {
|
|
@@ -56,6 +69,13 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
56
69
|
setCancelPending(false);
|
|
57
70
|
}
|
|
58
71
|
}
|
|
72
|
+
async function copyReferral() {
|
|
73
|
+
if (!referralLink || typeof navigator === "undefined" || !navigator.clipboard)
|
|
74
|
+
return;
|
|
75
|
+
await navigator.clipboard.writeText(referralLink);
|
|
76
|
+
setReferralCopied(true);
|
|
77
|
+
setTimeout(() => setReferralCopied(false), 2000);
|
|
78
|
+
}
|
|
59
79
|
function submitCustom() {
|
|
60
80
|
const amt = Number(customValue);
|
|
61
81
|
if (!Number.isFinite(amt) || amt <= 0) {
|
|
@@ -73,6 +93,12 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
73
93
|
setCustomError(null);
|
|
74
94
|
onAdd?.(amt);
|
|
75
95
|
}
|
|
96
|
+
function submitAutoTopup() {
|
|
97
|
+
const threshold = Number(autoTopupThreshold);
|
|
98
|
+
if (!autoTopupPackageId || !Number.isFinite(threshold) || threshold < 0)
|
|
99
|
+
return;
|
|
100
|
+
onEnableAutoTopup?.({ packageId: autoTopupPackageId, thresholdCredits: threshold });
|
|
101
|
+
}
|
|
76
102
|
const buttonStyle = {
|
|
77
103
|
flex: 1,
|
|
78
104
|
appearance: "none",
|
|
@@ -87,6 +113,7 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
87
113
|
padding: "8px 16px",
|
|
88
114
|
borderRadius: theme.radiusPill,
|
|
89
115
|
};
|
|
116
|
+
const resolvedReferralLabel = referralLabel.replace("{credits}", formatCredits(referralReward ?? 0));
|
|
90
117
|
return (_jsx("div", { ref: overlayRef, role: "dialog", "aria-modal": "true", style: {
|
|
91
118
|
position: "fixed",
|
|
92
119
|
inset: 0,
|
|
@@ -162,7 +189,52 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
162
189
|
color: theme.ink,
|
|
163
190
|
background: theme.canvas,
|
|
164
191
|
outline: "none",
|
|
165
|
-
} }), _jsx("button", { onClick: submitCustom, style: { ...buttonStyle, flex: "none" }, children: addButtonText })] }), customError ? (_jsx("p", { style: { margin: "8px 0 0", fontSize: 13, color: theme.accent }, children: customError })) : null] })) : null,
|
|
192
|
+
} }), _jsx("button", { onClick: submitCustom, style: { ...buttonStyle, flex: "none" }, children: addButtonText })] }), customError ? (_jsx("p", { style: { margin: "8px 0 0", fontSize: 13, color: theme.accent }, children: customError })) : null] })) : null, onEnableAutoTopup && packages && packages.length > 0 && (_jsx("div", { style: { marginTop: 16 }, children: autoTopup?.enabled && autoTopup.status === "active" ? (_jsxs("p", { style: { margin: 0, fontSize: 12, color: theme.inkMuted }, children: ["Auto top-up is on \u2014 buying", " ", packages.find((p) => p.id === autoTopup.packageId)?.name ?? "your package", " whenever your balance drops below ", formatCredits(autoTopup.thresholdCredits), " credits."] })) : autoTopup?.enabled && (autoTopup.status === "needs_auth" || autoTopup.status === "failed") ? (_jsxs("div", { children: [_jsx("p", { style: { margin: 0, fontSize: 12, color: theme.accent }, children: autoTopup.status === "needs_auth"
|
|
193
|
+
? "Auto top-up needs you to re-confirm your payment method."
|
|
194
|
+
: "Auto top-up's last charge failed." }), _jsx("button", { onClick: () => onEnableAutoTopup({
|
|
195
|
+
packageId: autoTopup.packageId,
|
|
196
|
+
thresholdCredits: autoTopup.thresholdCredits,
|
|
197
|
+
}), style: {
|
|
198
|
+
marginTop: 6,
|
|
199
|
+
appearance: "none",
|
|
200
|
+
border: "none",
|
|
201
|
+
background: "transparent",
|
|
202
|
+
cursor: "pointer",
|
|
203
|
+
padding: 0,
|
|
204
|
+
fontSize: 12,
|
|
205
|
+
color: theme.ink,
|
|
206
|
+
fontFamily: theme.font,
|
|
207
|
+
textDecoration: "underline",
|
|
208
|
+
}, children: "Fix auto top-up" })] })) : autoTopupOpen ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("select", { value: autoTopupPackageId, onChange: (e) => setAutoTopupPackageId(e.target.value), style: {
|
|
209
|
+
flex: 1,
|
|
210
|
+
minWidth: 0,
|
|
211
|
+
fontFamily: theme.font,
|
|
212
|
+
fontSize: 13,
|
|
213
|
+
padding: "6px 10px",
|
|
214
|
+
borderRadius: theme.radiusPill,
|
|
215
|
+
border: `1px solid ${theme.hairline}`,
|
|
216
|
+
color: theme.ink,
|
|
217
|
+
background: theme.canvas,
|
|
218
|
+
}, children: packages.map((pkg) => (_jsxs("option", { value: pkg.id, children: [pkg.name, " \u00B7 ", formatMoney(pkg.price, currency)] }, pkg.id))) }), _jsx("input", { value: autoTopupThreshold, onChange: (e) => setAutoTopupThreshold(e.target.value), inputMode: "numeric", "aria-label": "Threshold credits", style: {
|
|
219
|
+
width: 72,
|
|
220
|
+
fontFamily: theme.font,
|
|
221
|
+
fontSize: 13,
|
|
222
|
+
padding: "6px 10px",
|
|
223
|
+
borderRadius: theme.radiusPill,
|
|
224
|
+
border: `1px solid ${theme.hairline}`,
|
|
225
|
+
color: theme.ink,
|
|
226
|
+
background: theme.canvas,
|
|
227
|
+
} }), _jsx("button", { onClick: submitAutoTopup, style: { ...buttonStyle, flex: "none", padding: "6px 14px", fontSize: 13 }, children: "Turn on" })] }), _jsx("p", { style: { margin: 0, fontSize: 11, color: theme.inkMuted }, children: "Buy that package automatically whenever your balance drops below the credit amount above." })] })) : (_jsx("button", { onClick: () => setAutoTopupOpen(true), style: {
|
|
228
|
+
appearance: "none",
|
|
229
|
+
border: "none",
|
|
230
|
+
background: "transparent",
|
|
231
|
+
cursor: "pointer",
|
|
232
|
+
padding: 0,
|
|
233
|
+
fontSize: 12,
|
|
234
|
+
color: theme.inkMuted,
|
|
235
|
+
fontFamily: theme.font,
|
|
236
|
+
textDecoration: "underline",
|
|
237
|
+
}, children: "Keep me topped up automatically" })) })), onClose ? (_jsx("button", { onClick: onClose, style: {
|
|
166
238
|
width: "100%",
|
|
167
239
|
marginTop: 12,
|
|
168
240
|
appearance: "none",
|
|
@@ -174,5 +246,38 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
174
246
|
fontSize: 12,
|
|
175
247
|
padding: "8px 16px",
|
|
176
248
|
borderRadius: theme.radiusPill,
|
|
177
|
-
}, children: "Not now" })) : null,
|
|
249
|
+
}, children: "Not now" })) : null, referralLink ? (_jsxs("div", { style: {
|
|
250
|
+
marginTop: 16,
|
|
251
|
+
paddingTop: 16,
|
|
252
|
+
borderTop: `1px solid ${theme.hairline}`,
|
|
253
|
+
}, children: [_jsx("div", { style: {
|
|
254
|
+
fontSize: 12,
|
|
255
|
+
fontWeight: 600,
|
|
256
|
+
color: theme.inkMuted,
|
|
257
|
+
marginBottom: 8,
|
|
258
|
+
}, children: resolvedReferralLabel }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("div", { title: referralLink, style: {
|
|
259
|
+
flex: 1,
|
|
260
|
+
minWidth: 0,
|
|
261
|
+
overflow: "hidden",
|
|
262
|
+
textOverflow: "ellipsis",
|
|
263
|
+
whiteSpace: "nowrap",
|
|
264
|
+
fontSize: 13,
|
|
265
|
+
color: theme.ink,
|
|
266
|
+
border: `1px solid ${theme.hairline}`,
|
|
267
|
+
borderRadius: theme.radiusPill,
|
|
268
|
+
padding: "8px 14px",
|
|
269
|
+
}, children: referralLink }), _jsx("button", { onClick: copyReferral, style: {
|
|
270
|
+
flex: "none",
|
|
271
|
+
appearance: "none",
|
|
272
|
+
cursor: "pointer",
|
|
273
|
+
background: "transparent",
|
|
274
|
+
color: theme.ink,
|
|
275
|
+
border: `1px solid ${theme.hairline}`,
|
|
276
|
+
fontFamily: theme.font,
|
|
277
|
+
fontWeight: 500,
|
|
278
|
+
fontSize: 14,
|
|
279
|
+
padding: "8px 16px",
|
|
280
|
+
borderRadius: theme.radiusPill,
|
|
281
|
+
whiteSpace: "nowrap",
|
|
282
|
+
}, children: referralCopied ? "Copied!" : "Copy" })] })] })) : null, !hideBranding && (_jsx("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 10 }, children: _jsx("span", { style: { fontSize: 10, color: theme.inkMuted }, children: "Powered by Crediball." }) }))] }) }));
|
|
178
283
|
}
|
package/dist/ReferralCard.d.ts
CHANGED
|
@@ -15,6 +15,25 @@ export interface ReferralCardProps extends CrediballThemeOverrides {
|
|
|
15
15
|
/** Hide the clicks / converted counters row. */
|
|
16
16
|
hideStats?: boolean;
|
|
17
17
|
style?: CSSProperties;
|
|
18
|
+
/**
|
|
19
|
+
* Preview/demo override. When set, the card renders from this sample data
|
|
20
|
+
* instead of the live `useReferral()` result — used by the dashboard's
|
|
21
|
+
* UI-patterns preview so the card is visible and themeable without a
|
|
22
|
+
* connected app, exactly like `PaywallModal`'s balance/packages preview
|
|
23
|
+
* props. In production you never pass this: the card fetches real data itself.
|
|
24
|
+
*/
|
|
25
|
+
demo?: {
|
|
26
|
+
link?: string | null;
|
|
27
|
+
code?: string;
|
|
28
|
+
clicks?: number;
|
|
29
|
+
rewardCredits?: number;
|
|
30
|
+
referredRewardCredits?: number;
|
|
31
|
+
rewards?: {
|
|
32
|
+
pending: number;
|
|
33
|
+
converted: number;
|
|
34
|
+
creditsEarned: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
18
37
|
}
|
|
19
38
|
/**
|
|
20
39
|
* Drop-in invite card built on useReferral(). Place anywhere inside
|
|
@@ -27,4 +46,4 @@ export interface ReferralCardProps extends CrediballThemeOverrides {
|
|
|
27
46
|
* loading or when the referral program is disabled in the dashboard, and is
|
|
28
47
|
* themed by the same `--crediball-*` variables as every other component.
|
|
29
48
|
*/
|
|
30
|
-
export declare function ReferralCard({ title, description, copyLabel, shareLabel, hideStats, accentColor, style, }: ReferralCardProps): import("react").JSX.Element | null;
|
|
49
|
+
export declare function ReferralCard({ title, description, copyLabel, shareLabel, hideStats, accentColor, style, demo, }: ReferralCardProps): import("react").JSX.Element | null;
|
package/dist/ReferralCard.js
CHANGED
|
@@ -14,8 +14,21 @@ import { useReferral } from "./useReferral";
|
|
|
14
14
|
* loading or when the referral program is disabled in the dashboard, and is
|
|
15
15
|
* themed by the same `--crediball-*` variables as every other component.
|
|
16
16
|
*/
|
|
17
|
-
export function ReferralCard({ title = "Invite friends", description, copyLabel = "Copy", shareLabel = "Share", hideStats = false, accentColor, style, }) {
|
|
18
|
-
const
|
|
17
|
+
export function ReferralCard({ title = "Invite friends", description, copyLabel = "Copy", shareLabel = "Share", hideStats = false, accentColor, style, demo, }) {
|
|
18
|
+
const live = useReferral();
|
|
19
|
+
// Demo/preview data (if any) overrides the live hook result and bypasses the
|
|
20
|
+
// loading/enabled gate, so the card renders in the UI-patterns preview.
|
|
21
|
+
const enabled = demo ? true : live.enabled;
|
|
22
|
+
const link = demo ? (demo.link ?? null) : live.link;
|
|
23
|
+
const code = demo ? (demo.code ?? null) : live.code;
|
|
24
|
+
const clicks = demo ? (demo.clicks ?? 0) : live.clicks;
|
|
25
|
+
const rewards = demo?.rewards ?? live.rewards;
|
|
26
|
+
const rewardCredits = demo ? (demo.rewardCredits ?? 0) : live.rewardCredits;
|
|
27
|
+
const referredRewardCredits = demo ? (demo.referredRewardCredits ?? 0) : live.referredRewardCredits;
|
|
28
|
+
const loading = demo ? false : live.loading;
|
|
29
|
+
const copied = demo ? false : live.copied;
|
|
30
|
+
const copy = live.copy;
|
|
31
|
+
const share = live.share;
|
|
19
32
|
if (loading || !enabled || !code)
|
|
20
33
|
return null;
|
|
21
34
|
const resolvedDescription = description ??
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediball/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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.6.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/react": "^18.3.11",
|