@crediball/react 0.11.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.
@@ -264,7 +264,7 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
264
264
  // layout box. Host props/CSS are overridden by design (dashboard wins).
265
265
  const themeVars = applyRemoteTheme ? themeToCssVars(uiTheme) : {};
266
266
  const hasThemeVars = Object.keys(themeVars).length > 0;
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 })] }));
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 })] }));
268
268
  const configCtx = useMemo(() => ({ publishableKey, userId, apiUrl }), [publishableKey, userId, apiUrl]);
269
269
  return (_jsx(CrediballContext.Provider, { value: ctx, children: _jsx(CrediballConfigContext.Provider, { value: configCtx, children: hasThemeVars ? (_jsx("div", { style: { display: "contents", ...themeVars }, children: inner })) : (inner) }) }));
270
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 {
@@ -67,17 +67,19 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
67
67
  onCancelSubscription?: (subscriptionId: string) => void | Promise<void>;
68
68
  /**
69
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".
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
73
  */
74
74
  autoTopup?: AutoTopupInfo | null;
75
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`.
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`.
81
83
  */
82
84
  onEnableAutoTopup?: (input: {
83
85
  packageId: string;
@@ -103,6 +105,14 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
103
105
  customMin?: number;
104
106
  /** Optional maximum custom amount. */
105
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;
106
116
  /** Currency symbol shown beside the custom field (default "€"). */
107
117
  currencySymbol?: string;
108
118
  /**
@@ -143,4 +153,4 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
143
153
  * continue. The host controls when it appears; shows a small "Powered by
144
154
  * Crediball" line by default (set `hideBranding` to remove it).
145
155
  */
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;
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, 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, }) {
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
@@ -114,6 +120,12 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
114
120
  borderRadius: theme.radiusPill,
115
121
  };
116
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;
117
129
  return (_jsx("div", { ref: overlayRef, role: "dialog", "aria-modal": "true", style: {
118
130
  position: "fixed",
119
131
  inset: 0,
@@ -174,7 +186,7 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
174
186
  display: "flex",
175
187
  justifyContent: "space-between",
176
188
  alignItems: "center",
177
- }, 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
178
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)))
179
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: {
180
192
  flex: 1,
@@ -189,9 +201,9 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
189
201
  color: theme.ink,
190
202
  background: theme.canvas,
191
203
  outline: "none",
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"
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"
193
205
  ? "Auto top-up needs you to re-confirm your payment method."
194
- : "Auto top-up's last charge failed." }), _jsx("button", { onClick: () => onEnableAutoTopup({
206
+ : "Auto top-up's last charge failed." }), _jsx("button", { onClick: () => onEnableAutoTopup?.({
195
207
  packageId: autoTopup.packageId,
196
208
  thresholdCredits: autoTopup.thresholdCredits,
197
209
  }), style: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediball/react",
3
- "version": "0.11.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.6.0"
54
+ "@crediball/core": "^0.7.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/react": "^18.3.11",