@crediball/react 0.10.0 → 0.13.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.
@@ -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
  *
@@ -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
@@ -240,6 +240,18 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
240
240
  await onCancelSubscription?.(subscriptionId);
241
241
  await client?.refresh();
242
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
+ }
243
255
  // Auto-derive the custom-amount field from the dashboard's custom-topup config,
244
256
  // so a developer who enabled it doesn't also have to pass allowCustom/min/max.
245
257
  // Explicit props still win (allowCustom={false} force-hides it).
@@ -252,7 +264,7 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
252
264
  // layout box. Host props/CSS are overridden by design (dashboard wins).
253
265
  const themeVars = applyRemoteTheme ? themeToCssVars(uiTheme) : {};
254
266
  const hasThemeVars = Object.keys(themeVars).length > 0;
255
- 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, referralLink: referral?.link ?? undefined, referralReward: referral?.rewardCredits, 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, customTopupRate: pkgs?.customTopup.enabled ? pkgs.customTopup.rate : undefined, 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 })] }));
256
268
  const configCtx = useMemo(() => ({ publishableKey, userId, apiUrl }), [publishableKey, userId, apiUrl]);
257
269
  return (_jsx(CrediballContext.Provider, { value: ctx, children: _jsx(CrediballConfigContext.Provider, { value: configCtx, children: hasThemeVars ? (_jsx("div", { style: { display: "contents", ...themeVars }, children: inner })) : (inner) }) }));
258
270
  }
@@ -13,7 +13,7 @@ export interface SubscriptionPlanOption {
13
13
  name: string;
14
14
  price: number;
15
15
  credits: number;
16
- period: "monthly" | "yearly";
16
+ period: "weekly" | "monthly" | "yearly";
17
17
  }
18
18
  /** The end user's current active subscription (returned by listPackages() with userId). */
19
19
  export interface ActiveSubscriptionInfo {
@@ -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,26 @@ 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, an opt-in row is
71
+ * shown below the packages so the user can turn on "keep me topped up
72
+ * automatically".
73
+ */
74
+ autoTopup?: AutoTopupInfo | null;
75
+ /**
76
+ * Called when the user opts into auto top-up. Shown whenever `packages` is
77
+ * non-empty — same as `onSelectPackage`, this is optional to invoke (a no-op
78
+ * if you don't wire it, e.g. in a preview with no backend). Call
79
+ * `meter.setupAutoTopupCheckout({ userId, packageId, thresholdCredits,
80
+ * successUrl, cancelUrl })` in your backend — it saves a payment method, no
81
+ * money changes hands in this step — then redirect the browser to the
82
+ * returned `url`.
83
+ */
84
+ onEnableAutoTopup?: (input: {
85
+ packageId: string;
86
+ thresholdCredits: number;
87
+ }) => void;
61
88
  /**
62
89
  * Legacy: flat list of euro amounts. Prefer `packages` + `onSelectPackage`.
63
90
  * When both are provided, `packages` takes precedence.
@@ -78,6 +105,14 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
78
105
  customMin?: number;
79
106
  /** Optional maximum custom amount. */
80
107
  customMax?: number;
108
+ /**
109
+ * Credits granted per 1 currency unit for the custom-amount field (your
110
+ * dashboard's custom top-up rate). When provided, a live "≈ N credits"
111
+ * line appears under the field as the user types, computed the same way
112
+ * the actual charge is (Math.round(amount * rate)) — so it never promises
113
+ * a different number than what gets granted.
114
+ */
115
+ customTopupRate?: number;
81
116
  /** Currency symbol shown beside the custom field (default "€"). */
82
117
  currencySymbol?: string;
83
118
  /**
@@ -105,6 +140,12 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
105
140
  referralLink?: string | null;
106
141
  /** Credits earned per successful referral, shown in the refer-a-friend blurb. */
107
142
  referralReward?: number;
143
+ /**
144
+ * Label above the referral link. `{credits}` is replaced with `referralReward`.
145
+ * Default "Refer a friend · earn {credits} credits". Shown as written (not
146
+ * uppercased), so lowercase/sentence case is preserved.
147
+ */
148
+ referralLabel?: string;
108
149
  style?: CSSProperties;
109
150
  }
110
151
  /**
@@ -112,4 +153,4 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
112
153
  * continue. The host controls when it appears; shows a small "Powered by
113
154
  * Crediball" line by default (set `hideBranding` to remove it).
114
155
  */
115
- 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, referralLink, referralReward, accentColor, style, }: PaywallModalProps): import("react").JSX.Element | null;
156
+ export declare function PaywallModal({ open, packages, onSelectPackage, subscriptionPlans, onSelectPlan, activeSubscription, onCancelSubscription, autoTopup, onEnableAutoTopup, amounts, amountPrefix, title, description, onAdd, onClose, allowCustom, addButtonText, customMin, customMax, customTopupRate, currencySymbol, balance, balanceRate, currency, usage, usageLabel, hideBranding, referralLink, referralReward, referralLabel, accentColor, style, }: PaywallModalProps): import("react").JSX.Element | null;
@@ -3,6 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { theme, accentStyle } from "./theme";
5
5
  import { formatCredits, formatMoney } from "./format";
6
+ /** Shorthand suffix for a billing period, used in plan price labels (e.g. "€9.99/mo"). */
7
+ const PERIOD_SUFFIX = {
8
+ weekly: "wk",
9
+ monthly: "mo",
10
+ yearly: "yr",
11
+ };
6
12
  /** Shared uppercase eyebrow-label style used above the subscribe/buy-credits sections. */
7
13
  function sectionLabelStyle(marginBottom) {
8
14
  return {
@@ -19,7 +25,7 @@ function sectionLabelStyle(marginBottom) {
19
25
  * continue. The host controls when it appears; shows a small "Powered by
20
26
  * Crediball" line by default (set `hideBranding` to remove it).
21
27
  */
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, referralLink, referralReward, accentColor, style, }) {
28
+ 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, customTopupRate, currencySymbol = "€", balance, balanceRate, currency = "EUR", usage, usageLabel = "Usage", hideBranding = false, referralLink, referralReward, referralLabel = "Refer a friend · earn {credits} credits", accentColor, style, }) {
23
29
  // Stack the top-up buttons one-per-line when the dialog itself is narrow.
24
30
  // Measured off the dialog's own box (via ResizeObserver) rather than the
25
31
  // window — in normal use the two are the same width, but this also makes
@@ -32,6 +38,9 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
32
38
  const [cancelPending, setCancelPending] = useState(false);
33
39
  const [cancelDone, setCancelDone] = useState(false);
34
40
  const [referralCopied, setReferralCopied] = useState(false);
41
+ const [autoTopupOpen, setAutoTopupOpen] = useState(false);
42
+ const [autoTopupPackageId, setAutoTopupPackageId] = useState("");
43
+ const [autoTopupThreshold, setAutoTopupThreshold] = useState("");
35
44
  useEffect(() => {
36
45
  const el = overlayRef.current;
37
46
  if (!el || typeof ResizeObserver === "undefined")
@@ -43,6 +52,15 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
43
52
  observer.observe(el);
44
53
  return () => observer.disconnect();
45
54
  }, []);
55
+ // Default the auto-topup picker to the cheapest package + its own credit
56
+ // amount as the suggested threshold, once packages are available.
57
+ useEffect(() => {
58
+ if (!packages || packages.length === 0 || autoTopupPackageId)
59
+ return;
60
+ const cheapest = packages.reduce((min, p) => (p.price < min.price ? p : min), packages[0]);
61
+ setAutoTopupPackageId(cheapest.id);
62
+ setAutoTopupThreshold(String(cheapest.credits));
63
+ }, [packages, autoTopupPackageId]);
46
64
  if (!open)
47
65
  return null;
48
66
  async function handleCancelSubscription() {
@@ -81,6 +99,12 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
81
99
  setCustomError(null);
82
100
  onAdd?.(amt);
83
101
  }
102
+ function submitAutoTopup() {
103
+ const threshold = Number(autoTopupThreshold);
104
+ if (!autoTopupPackageId || !Number.isFinite(threshold) || threshold < 0)
105
+ return;
106
+ onEnableAutoTopup?.({ packageId: autoTopupPackageId, thresholdCredits: threshold });
107
+ }
84
108
  const buttonStyle = {
85
109
  flex: 1,
86
110
  appearance: "none",
@@ -95,6 +119,13 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
95
119
  padding: "8px 16px",
96
120
  borderRadius: theme.radiusPill,
97
121
  };
122
+ const resolvedReferralLabel = referralLabel.replace("{credits}", formatCredits(referralReward ?? 0));
123
+ // Same rounding the actual charge uses (see /api/public/topup/checkout),
124
+ // so this preview never promises a different number than what's granted.
125
+ const customAmountParsed = Number(customValue);
126
+ const customCreditsPreview = customTopupRate && customTopupRate > 0 && Number.isFinite(customAmountParsed) && customAmountParsed > 0
127
+ ? Math.round(customAmountParsed * customTopupRate)
128
+ : null;
98
129
  return (_jsx("div", { ref: overlayRef, role: "dialog", "aria-modal": "true", style: {
99
130
  position: "fixed",
100
131
  inset: 0,
@@ -155,7 +186,7 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
155
186
  display: "flex",
156
187
  justifyContent: "space-between",
157
188
  alignItems: "center",
158
- }, children: [_jsx("span", { children: plan.name }), _jsxs("span", { style: { opacity: 0.85, fontSize: 14 }, children: [formatMoney(plan.price, currency), "/", plan.period === "monthly" ? "mo" : "yr", " \u00B7 ", formatCredits(plan.credits), " cr"] })] }, plan.id))) })] })), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 24 }, children: [(!activeSubscription && subscriptionPlans && subscriptionPlans.length > 0) && (_jsx("div", { style: sectionLabelStyle(-2), children: "Or buy credits" })), activeSubscription && packages && packages.length > 0 && (_jsx("div", { style: sectionLabelStyle(-2), children: "Add extra credits" })), packages && packages.length > 0
189
+ }, children: [_jsx("span", { children: plan.name }), _jsxs("span", { style: { opacity: 0.85, fontSize: 14 }, children: [formatMoney(plan.price, currency), "/", PERIOD_SUFFIX[plan.period], " \u00B7 ", formatCredits(plan.credits), " cr"] })] }, plan.id))) })] })), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 24 }, children: [(!activeSubscription && subscriptionPlans && subscriptionPlans.length > 0) && (_jsx("div", { style: sectionLabelStyle(-2), children: "Or buy credits" })), activeSubscription && packages && packages.length > 0 && (_jsx("div", { style: sectionLabelStyle(-2), children: "Add extra credits" })), packages && packages.length > 0
159
190
  ? packages.map((pkg) => (_jsxs("button", { onClick: () => onSelectPackage?.(pkg), style: { ...buttonStyle, flex: "none", display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [_jsx("span", { children: pkg.name }), _jsxs("span", { style: { opacity: 0.85, fontSize: 14 }, children: [formatMoney(pkg.price, currency), " \u00B7 ", formatCredits(pkg.credits), " cr"] })] }, pkg.id)))
160
191
  : (!activeSubscription && (_jsx("div", { style: { display: "flex", flexDirection: narrow ? "column" : "row", gap: 12 }, children: amounts.map((amount) => (_jsxs("button", { onClick: () => onAdd?.(amount), style: buttonStyle, children: [amountPrefix, amount] }, amount))) })))] }), allowCustom ? (_jsxs("div", { style: { marginTop: 12 }, children: [_jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("span", { style: { fontSize: 14, color: theme.inkMuted }, children: currencySymbol }), _jsx("input", { value: customValue, onChange: (e) => setCustomValue(e.target.value), inputMode: "decimal", placeholder: String(customMin), style: {
161
192
  flex: 1,
@@ -170,13 +201,73 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
170
201
  color: theme.ink,
171
202
  background: theme.canvas,
172
203
  outline: "none",
173
- } }), _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, referralLink ? (_jsxs("div", { style: {
174
- marginTop: 20,
204
+ } }), _jsx("button", { onClick: submitCustom, style: { ...buttonStyle, flex: "none" }, children: addButtonText })] }), customCreditsPreview != null ? (_jsxs("p", { style: { margin: "6px 0 0", fontSize: 12, color: theme.inkMuted }, children: ["\u2248 ", formatCredits(customCreditsPreview), " credits"] })) : null, customError ? (_jsx("p", { style: { margin: "8px 0 0", fontSize: 13, color: theme.accent }, children: customError })) : null] })) : null, 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"
205
+ ? "Auto top-up needs you to re-confirm your payment method."
206
+ : "Auto top-up's last charge failed." }), _jsx("button", { onClick: () => onEnableAutoTopup?.({
207
+ packageId: autoTopup.packageId,
208
+ thresholdCredits: autoTopup.thresholdCredits,
209
+ }), style: {
210
+ marginTop: 6,
211
+ appearance: "none",
212
+ border: "none",
213
+ background: "transparent",
214
+ cursor: "pointer",
215
+ padding: 0,
216
+ fontSize: 12,
217
+ color: theme.ink,
218
+ fontFamily: theme.font,
219
+ textDecoration: "underline",
220
+ }, 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: {
221
+ flex: 1,
222
+ minWidth: 0,
223
+ fontFamily: theme.font,
224
+ fontSize: 13,
225
+ padding: "6px 10px",
226
+ borderRadius: theme.radiusPill,
227
+ border: `1px solid ${theme.hairline}`,
228
+ color: theme.ink,
229
+ background: theme.canvas,
230
+ }, 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: {
231
+ width: 72,
232
+ fontFamily: theme.font,
233
+ fontSize: 13,
234
+ padding: "6px 10px",
235
+ borderRadius: theme.radiusPill,
236
+ border: `1px solid ${theme.hairline}`,
237
+ color: theme.ink,
238
+ background: theme.canvas,
239
+ } }), _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: {
240
+ appearance: "none",
241
+ border: "none",
242
+ background: "transparent",
243
+ cursor: "pointer",
244
+ padding: 0,
245
+ fontSize: 12,
246
+ color: theme.inkMuted,
247
+ fontFamily: theme.font,
248
+ textDecoration: "underline",
249
+ }, children: "Keep me topped up automatically" })) })), onClose ? (_jsx("button", { onClick: onClose, style: {
250
+ width: "100%",
251
+ marginTop: 12,
252
+ appearance: "none",
253
+ border: "none",
254
+ cursor: "pointer",
255
+ background: "transparent",
256
+ color: theme.inkMuted,
257
+ fontFamily: theme.font,
258
+ fontSize: 12,
259
+ padding: "8px 16px",
260
+ borderRadius: theme.radiusPill,
261
+ }, children: "Not now" })) : null, referralLink ? (_jsxs("div", { style: {
262
+ marginTop: 16,
175
263
  paddingTop: 16,
176
264
  borderTop: `1px solid ${theme.hairline}`,
177
- }, children: [_jsx("div", { style: sectionLabelStyle(8), children: referralReward && referralReward > 0
178
- ? `Refer a friend · earn ${formatCredits(referralReward)} credits`
179
- : "Refer a friend" }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("div", { title: referralLink, style: {
265
+ }, children: [_jsx("div", { style: {
266
+ fontSize: 12,
267
+ fontWeight: 600,
268
+ color: theme.inkMuted,
269
+ marginBottom: 8,
270
+ }, children: resolvedReferralLabel }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("div", { title: referralLink, style: {
180
271
  flex: 1,
181
272
  minWidth: 0,
182
273
  overflow: "hidden",
@@ -200,17 +291,5 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
200
291
  padding: "8px 16px",
201
292
  borderRadius: theme.radiusPill,
202
293
  whiteSpace: "nowrap",
203
- }, children: referralCopied ? "Copied!" : "Copy" })] })] })) : null, onClose ? (_jsx("button", { onClick: onClose, style: {
204
- width: "100%",
205
- marginTop: 12,
206
- appearance: "none",
207
- border: "none",
208
- cursor: "pointer",
209
- background: "transparent",
210
- color: theme.inkMuted,
211
- fontFamily: theme.font,
212
- fontSize: 12,
213
- padding: "8px 16px",
214
- borderRadius: theme.radiusPill,
215
- }, children: "Not now" })) : 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." }) }))] }) }));
294
+ }, 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." }) }))] }) }));
216
295
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediball/react",
3
- "version": "0.10.0",
3
+ "version": "0.13.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.4.0"
54
+ "@crediball/core": "^0.7.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/react": "^18.3.11",