@crediball/react 0.3.6 → 0.4.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 +67 -8
- package/dist/CrediballProvider.js +99 -10
- package/dist/CreditIndicator.d.ts +9 -4
- package/dist/CreditIndicator.js +16 -10
- package/dist/CreditsBadge.d.ts +6 -3
- package/dist/CreditsBadge.js +17 -18
- package/dist/PaywallModal.d.ts +17 -3
- package/dist/PaywallModal.js +85 -89
- package/dist/creditEvent.d.ts +6 -19
- package/dist/creditEvent.js +6 -29
- package/dist/fetchWatcher.d.ts +15 -9
- package/dist/fetchWatcher.js +29 -37
- package/dist/format.d.ts +4 -0
- package/dist/format.js +13 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +7 -2
- package/dist/theme.d.ts +39 -0
- package/dist/theme.js +43 -0
- package/dist/variables/CrediballBalance.d.ts +26 -0
- package/dist/variables/CrediballBalance.js +19 -0
- package/dist/variables/CrediballLowCreditWarning.d.ts +25 -0
- package/dist/variables/CrediballLowCreditWarning.js +20 -0
- package/dist/variables/CrediballTopUpButton.d.ts +18 -0
- package/dist/variables/CrediballTopUpButton.js +27 -0
- package/dist/variables/CrediballUsage.d.ts +24 -0
- package/dist/variables/CrediballUsage.js +19 -0
- package/package.json +4 -1
|
@@ -1,16 +1,43 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import { type PackageOption, type SubscriptionPlanOption } from "./PaywallModal";
|
|
3
|
+
import { type UsagePeriod } from "@crediball/core";
|
|
2
4
|
export interface CrediballProviderProps {
|
|
3
5
|
children: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Browser-safe key (cb_pub_...) from the Crediball dashboard. When set
|
|
8
|
+
* together with `userId`, the provider fetches and live-updates balance,
|
|
9
|
+
* usage, event costs, and packages — powering useCrediball()'s data fields,
|
|
10
|
+
* the Tier-2 variable components, and PaywallModal's package list. Omit both
|
|
11
|
+
* to use the provider in its original, data-less mode (host supplies
|
|
12
|
+
* everything via props/callbacks).
|
|
13
|
+
*/
|
|
14
|
+
publishableKey?: string;
|
|
15
|
+
/** The end user's id, exactly as passed to track() server-side. Required alongside publishableKey. */
|
|
16
|
+
userId?: string;
|
|
17
|
+
/** Base URL of the Crediball API. Defaults to the hosted instance. */
|
|
18
|
+
apiUrl?: string;
|
|
19
|
+
/** Poll interval while the tab is visible. Default 30s. */
|
|
20
|
+
pollIntervalMs?: number;
|
|
21
|
+
/** Override the default "low credits" threshold (defaults to the highest-cost active event). */
|
|
22
|
+
lowCreditThreshold?: number;
|
|
4
23
|
/**
|
|
5
24
|
* Top-up amounts to offer in the paywall (host decides the meaning, e.g. euros).
|
|
6
25
|
* Match these to the packages you configured in the Crediball dashboard.
|
|
26
|
+
* Ignored once real packages are available (with publishableKey/userId set).
|
|
7
27
|
*/
|
|
8
28
|
amounts?: number[];
|
|
9
29
|
/**
|
|
10
|
-
* Called when the user picks an amount to top up
|
|
11
|
-
* server-side top-up route. If omitted,
|
|
30
|
+
* Called when the user picks an amount to top up (legacy flat-amount flow).
|
|
31
|
+
* Wire this to your checkout or server-side top-up route. If omitted,
|
|
32
|
+
* picking an amount just closes the modal.
|
|
12
33
|
*/
|
|
13
34
|
onTopup?: (amount: number) => void | Promise<void>;
|
|
35
|
+
/** Called when the user picks a one-time package. Call `meter.topup({ userId, packageId })` server-side. */
|
|
36
|
+
onSelectPackage?: (pkg: PackageOption) => void | Promise<void>;
|
|
37
|
+
/** Called when the user picks a subscription plan. Call `meter.subscribe({ userId, planId })` server-side. */
|
|
38
|
+
onSelectPlan?: (plan: SubscriptionPlanOption) => void | Promise<void>;
|
|
39
|
+
/** Called when the user cancels their subscription. Call `meter.cancelSubscription({ userId, subscriptionId })` server-side. */
|
|
40
|
+
onCancelSubscription?: (subscriptionId: string) => void | Promise<void>;
|
|
14
41
|
/**
|
|
15
42
|
* Auto-detect insufficient-credit responses from any fetch() call and open the
|
|
16
43
|
* paywall automatically — no per-call wiring. Defaults to true. Set false to only
|
|
@@ -42,28 +69,60 @@ interface CrediballContextValue {
|
|
|
42
69
|
hidePaywall: () => void;
|
|
43
70
|
/** Whether the paywall is currently open. */
|
|
44
71
|
open: boolean;
|
|
72
|
+
/** Alias of showPaywall — reads better from Tier-2 variable components. */
|
|
73
|
+
openPaywall: () => void;
|
|
74
|
+
/** The user's current credit balance. null until loaded, or always null without publishableKey/userId. */
|
|
75
|
+
balance: number | null;
|
|
76
|
+
/** Credits consumed in the user's current period (subscription cycle, or calendar month). */
|
|
77
|
+
usage: UsagePeriod | null;
|
|
78
|
+
/** Alias of balance — the credits left to spend. */
|
|
79
|
+
remaining: number | null;
|
|
80
|
+
/** True once balance drops below the low-credit threshold. */
|
|
81
|
+
isLow: boolean;
|
|
82
|
+
/** True while the initial fetch is in flight. */
|
|
83
|
+
loading: boolean;
|
|
84
|
+
/** Set when the last fetch failed. */
|
|
85
|
+
error: Error | null;
|
|
86
|
+
/** Credit cost of a tracked event, from the live cost catalog. undefined if unknown. */
|
|
87
|
+
costOf: (event: string) => number | undefined;
|
|
88
|
+
/** Trigger a top-up (delegates to the `onTopup` prop) and refresh balance/usage on completion. */
|
|
89
|
+
topUp: (amount: number) => Promise<void>;
|
|
90
|
+
/** Refetch balance, usage, costs, and packages immediately. No-op without publishableKey/userId. */
|
|
91
|
+
refresh: () => Promise<void>;
|
|
45
92
|
}
|
|
46
93
|
/**
|
|
47
94
|
* Pattern B, automated. Wrap your app once:
|
|
48
95
|
*
|
|
49
|
-
* <CrediballProvider
|
|
96
|
+
* <CrediballProvider
|
|
97
|
+
* publishableKey="cb_pub_..."
|
|
98
|
+
* userId={user.id}
|
|
99
|
+
* onTopup={(eur) => checkout(eur)}
|
|
100
|
+
* >
|
|
50
101
|
* <App />
|
|
51
102
|
* </CrediballProvider>
|
|
52
103
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
104
|
+
* With `publishableKey` + `userId` set, every useCrediball() consumer and every
|
|
105
|
+
* Tier-2 variable component (<CrediballBalance/>, <CrediballUsage/>, ...) gets
|
|
106
|
+
* live balance/usage/cost data automatically. Whenever any request returns 402
|
|
107
|
+
* `insufficient_credits`, the paywall appears automatically — no extra code per
|
|
108
|
+
* call. You can also open it yourself with useCrediball().showPaywall().
|
|
56
109
|
*
|
|
57
110
|
* SSE/streaming routes (text/event-stream, ndjson) are also handled automatically —
|
|
58
111
|
* the watcher scans each chunk for credit errors, so no manual wiring is needed.
|
|
59
112
|
*/
|
|
60
|
-
export declare function CrediballProvider({ children, amounts, onTopup, watchFetch, allowCustom, customMin, customMax, currencySymbol, title, description, accentColor, }: CrediballProviderProps): import("react").JSX.Element;
|
|
113
|
+
export declare function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts, onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch, allowCustom, customMin, customMax, currencySymbol, title, description, accentColor, }: CrediballProviderProps): import("react").JSX.Element;
|
|
61
114
|
/**
|
|
62
|
-
* Access the paywall
|
|
115
|
+
* Access the paywall + live data from anywhere inside <CrediballProvider>.
|
|
63
116
|
*
|
|
64
117
|
* Important: showPaywall() is always safe to call — never add a "paywallAlreadyShown"
|
|
65
118
|
* guard around it. Dismissing the modal ("Not now") does not prevent future calls
|
|
66
119
|
* from reopening it. Call showPaywall() unconditionally on every InsufficientCreditsError.
|
|
67
120
|
*/
|
|
68
121
|
export declare function useCrediball(): CrediballContextValue;
|
|
122
|
+
/**
|
|
123
|
+
* Same as useCrediball(), but returns null instead of throwing outside a
|
|
124
|
+
* <CrediballProvider>. For components (like CreditsBadge) that work either
|
|
125
|
+
* standalone via props or auto-wired via a provider.
|
|
126
|
+
*/
|
|
127
|
+
export declare function useCrediballOptional(): CrediballContextValue | null;
|
|
69
128
|
export {};
|
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
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
8
|
const CrediballContext = createContext(null);
|
|
9
|
+
const EMPTY_SNAPSHOT = {
|
|
10
|
+
balance: null,
|
|
11
|
+
usage: null,
|
|
12
|
+
remaining: null,
|
|
13
|
+
costs: {},
|
|
14
|
+
packages: null,
|
|
15
|
+
isLow: false,
|
|
16
|
+
loading: false,
|
|
17
|
+
error: null,
|
|
18
|
+
};
|
|
19
|
+
const noopSubscribe = () => () => { };
|
|
20
|
+
const getEmptySnapshot = () => EMPTY_SNAPSHOT;
|
|
8
21
|
/**
|
|
9
22
|
* Pattern B, automated. Wrap your app once:
|
|
10
23
|
*
|
|
11
|
-
* <CrediballProvider
|
|
24
|
+
* <CrediballProvider
|
|
25
|
+
* publishableKey="cb_pub_..."
|
|
26
|
+
* userId={user.id}
|
|
27
|
+
* onTopup={(eur) => checkout(eur)}
|
|
28
|
+
* >
|
|
12
29
|
* <App />
|
|
13
30
|
* </CrediballProvider>
|
|
14
31
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
32
|
+
* With `publishableKey` + `userId` set, every useCrediball() consumer and every
|
|
33
|
+
* Tier-2 variable component (<CrediballBalance/>, <CrediballUsage/>, ...) gets
|
|
34
|
+
* live balance/usage/cost data automatically. Whenever any request returns 402
|
|
35
|
+
* `insufficient_credits`, the paywall appears automatically — no extra code per
|
|
36
|
+
* call. You can also open it yourself with useCrediball().showPaywall().
|
|
18
37
|
*
|
|
19
38
|
* SSE/streaming routes (text/event-stream, ndjson) are also handled automatically —
|
|
20
39
|
* the watcher scans each chunk for credit errors, so no manual wiring is needed.
|
|
21
40
|
*/
|
|
22
|
-
export function CrediballProvider({ children, amounts = [5, 10, 20], onTopup, watchFetch = true, allowCustom = false, customMin, customMax, currencySymbol = "€", title, description, accentColor, }) {
|
|
41
|
+
export function CrediballProvider({ children, publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold, amounts = [5, 10, 20], onTopup, onSelectPackage, onSelectPlan, onCancelSubscription, watchFetch = true, allowCustom = false, customMin, customMax, currencySymbol = "€", title, description, accentColor, }) {
|
|
23
42
|
const [open, setOpen] = useState(false);
|
|
24
43
|
const showPaywall = useCallback(() => setOpen(true), []);
|
|
25
44
|
const hidePaywall = useCallback(() => setOpen(false), []);
|
|
@@ -35,19 +54,81 @@ export function CrediballProvider({ children, amounts = [5, 10, 20], onTopup, wa
|
|
|
35
54
|
return;
|
|
36
55
|
return subscribeFetchWatcher(() => showPaywallRef.current());
|
|
37
56
|
}, [watchFetch]);
|
|
38
|
-
|
|
57
|
+
// Shared data client — one poller/stream per (publishableKey, userId, apiUrl),
|
|
58
|
+
// reused by every component reading useCrediball() or a Tier-2 variable.
|
|
59
|
+
const client = useMemo(() => {
|
|
60
|
+
if (!publishableKey || !userId)
|
|
61
|
+
return null;
|
|
62
|
+
return getCrediballClient({ publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold });
|
|
63
|
+
}, [publishableKey, userId, apiUrl, pollIntervalMs, lowCreditThreshold]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!client)
|
|
66
|
+
return;
|
|
67
|
+
client.start();
|
|
68
|
+
return () => client.stop();
|
|
69
|
+
}, [client]);
|
|
70
|
+
const snapshot = useSyncExternalStore(client ? client.subscribe : noopSubscribe, client ? client.getSnapshot : getEmptySnapshot, client ? client.getSnapshot : getEmptySnapshot);
|
|
71
|
+
const costOf = useCallback((event) => client?.costOf(event), [client]);
|
|
72
|
+
const refresh = useCallback(async () => {
|
|
73
|
+
await client?.refresh();
|
|
74
|
+
}, [client]);
|
|
75
|
+
const topUp = useCallback(async (amount) => {
|
|
76
|
+
await onTopup?.(amount);
|
|
77
|
+
notifyBalanceChanged();
|
|
78
|
+
await client?.refresh();
|
|
79
|
+
}, [onTopup, client]);
|
|
80
|
+
const ctx = useMemo(() => ({
|
|
81
|
+
showPaywall,
|
|
82
|
+
hidePaywall,
|
|
83
|
+
open,
|
|
84
|
+
openPaywall: showPaywall,
|
|
85
|
+
balance: snapshot.balance,
|
|
86
|
+
usage: snapshot.usage,
|
|
87
|
+
remaining: snapshot.remaining,
|
|
88
|
+
isLow: snapshot.isLow,
|
|
89
|
+
loading: snapshot.loading,
|
|
90
|
+
error: snapshot.error,
|
|
91
|
+
costOf,
|
|
92
|
+
topUp,
|
|
93
|
+
refresh,
|
|
94
|
+
}), [showPaywall, hidePaywall, open, snapshot, costOf, topUp, refresh]);
|
|
39
95
|
async function handleAdd(amount) {
|
|
40
96
|
try {
|
|
41
|
-
await
|
|
97
|
+
await topUp(amount);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
setOpen(false);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function handleSelectPackage(pkg) {
|
|
104
|
+
try {
|
|
105
|
+
await onSelectPackage?.(pkg);
|
|
106
|
+
notifyBalanceChanged();
|
|
107
|
+
await client?.refresh();
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
setOpen(false);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function handleSelectPlan(plan) {
|
|
114
|
+
try {
|
|
115
|
+
await onSelectPlan?.(plan);
|
|
116
|
+
notifyBalanceChanged();
|
|
117
|
+
await client?.refresh();
|
|
42
118
|
}
|
|
43
119
|
finally {
|
|
44
120
|
setOpen(false);
|
|
45
121
|
}
|
|
46
122
|
}
|
|
47
|
-
|
|
123
|
+
async function handleCancelSubscription(subscriptionId) {
|
|
124
|
+
await onCancelSubscription?.(subscriptionId);
|
|
125
|
+
await client?.refresh();
|
|
126
|
+
}
|
|
127
|
+
const pkgs = snapshot.packages;
|
|
128
|
+
return (_jsxs(CrediballContext.Provider, { value: ctx, children: [children, _jsx(PaywallModal, { open: open, packages: pkgs?.packages, onSelectPackage: handleSelectPackage, subscriptionPlans: pkgs?.subscriptionPlans, onSelectPlan: handleSelectPlan, activeSubscription: pkgs?.activeSubscription, onCancelSubscription: handleCancelSubscription, amounts: amounts, amountPrefix: `Add ${currencySymbol}`, onAdd: handleAdd, onClose: hidePaywall, allowCustom: allowCustom, customMin: customMin, customMax: customMax, currencySymbol: currencySymbol, balance: snapshot.balance ?? undefined, balanceRate: pkgs?.customTopup.enabled ? pkgs.customTopup.rate : undefined, currency: pkgs?.currency, usage: snapshot.usage?.usedCredits ?? undefined, title: title, description: description, accentColor: accentColor })] }));
|
|
48
129
|
}
|
|
49
130
|
/**
|
|
50
|
-
* Access the paywall
|
|
131
|
+
* Access the paywall + live data from anywhere inside <CrediballProvider>.
|
|
51
132
|
*
|
|
52
133
|
* Important: showPaywall() is always safe to call — never add a "paywallAlreadyShown"
|
|
53
134
|
* guard around it. Dismissing the modal ("Not now") does not prevent future calls
|
|
@@ -60,3 +141,11 @@ export function useCrediball() {
|
|
|
60
141
|
}
|
|
61
142
|
return ctx;
|
|
62
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Same as useCrediball(), but returns null instead of throwing outside a
|
|
146
|
+
* <CrediballProvider>. For components (like CreditsBadge) that work either
|
|
147
|
+
* standalone via props or auto-wired via a provider.
|
|
148
|
+
*/
|
|
149
|
+
export function useCrediballOptional() {
|
|
150
|
+
return useContext(CrediballContext);
|
|
151
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { CSSProperties } from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { type CrediballThemeOverrides } from "./theme";
|
|
3
|
+
export interface CreditIndicatorProps extends CrediballThemeOverrides {
|
|
4
|
+
/**
|
|
5
|
+
* Pre-formatted balance (e.g. "6.20"). Omit to read + format it automatically
|
|
6
|
+
* from an ancestor <CrediballProvider publishableKey userId>.
|
|
7
|
+
*/
|
|
8
|
+
balanceLabel?: string;
|
|
5
9
|
/** Text before the balance (default "Credits:"). */
|
|
6
10
|
label?: string;
|
|
11
|
+
/** Defaults to opening the provider's paywall when a <CrediballProvider> is present. */
|
|
7
12
|
onClick?: () => void;
|
|
8
13
|
style?: CSSProperties;
|
|
9
14
|
}
|
|
@@ -11,4 +16,4 @@ export interface CreditIndicatorProps {
|
|
|
11
16
|
* Pattern C — Subtle indicator. A quiet balance chip for a host app's top bar;
|
|
12
17
|
* clickable to open a top-up flow. Minimal and non-intrusive.
|
|
13
18
|
*/
|
|
14
|
-
export declare function CreditIndicator({ balanceLabel, label, onClick, style, }: CreditIndicatorProps): import("react").JSX.Element;
|
|
19
|
+
export declare function CreditIndicator({ balanceLabel, label, onClick, accentColor, style, }: CreditIndicatorProps): import("react").JSX.Element;
|
package/dist/CreditIndicator.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { theme, accentStyle } from "./theme";
|
|
4
|
+
import { formatCredits } from "./format";
|
|
5
|
+
import { useCrediballOptional } from "./CrediballProvider";
|
|
4
6
|
/**
|
|
5
7
|
* Pattern C — Subtle indicator. A quiet balance chip for a host app's top bar;
|
|
6
8
|
* clickable to open a top-up flow. Minimal and non-intrusive.
|
|
7
9
|
*/
|
|
8
|
-
export function CreditIndicator({ balanceLabel, label = "Credits:", onClick, style, }) {
|
|
9
|
-
|
|
10
|
+
export function CreditIndicator({ balanceLabel, label = "Credits:", onClick, accentColor, style, }) {
|
|
11
|
+
const ctx = useCrediballOptional();
|
|
12
|
+
const resolvedLabel = balanceLabel ?? (ctx?.balance != null ? formatCredits(ctx.balance) : "—");
|
|
13
|
+
const resolvedOnClick = onClick ?? ctx?.openPaywall;
|
|
14
|
+
return (_jsxs("button", { onClick: resolvedOnClick, style: {
|
|
10
15
|
display: "inline-flex",
|
|
11
16
|
alignItems: "center",
|
|
12
17
|
gap: 4,
|
|
13
18
|
appearance: "none",
|
|
14
|
-
cursor:
|
|
15
|
-
background:
|
|
16
|
-
border: `1px solid ${
|
|
17
|
-
borderRadius:
|
|
18
|
-
color:
|
|
19
|
-
fontFamily:
|
|
19
|
+
cursor: resolvedOnClick ? "pointer" : "default",
|
|
20
|
+
background: theme.canvas,
|
|
21
|
+
border: `1px solid ${theme.hairline}`,
|
|
22
|
+
borderRadius: theme.radiusPill,
|
|
23
|
+
color: theme.ink,
|
|
24
|
+
fontFamily: theme.font,
|
|
20
25
|
fontSize: 14,
|
|
21
26
|
padding: "4px 12px",
|
|
27
|
+
...accentStyle(accentColor),
|
|
22
28
|
...style,
|
|
23
|
-
}, children: [_jsx("span", { style: { color:
|
|
29
|
+
}, children: [_jsx("span", { style: { color: theme.inkMuted }, children: label }), _jsx("span", { style: { fontWeight: 600 }, children: resolvedLabel })] }));
|
|
24
30
|
}
|
package/dist/CreditsBadge.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { CSSProperties } from "react";
|
|
2
2
|
import { type CrediballThemeOverrides } from "./theme";
|
|
3
3
|
export interface CreditsBadgeProps extends CrediballThemeOverrides {
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The user's raw credit balance (the big number). Omit to read it
|
|
6
|
+
* automatically from an ancestor <CrediballProvider publishableKey userId>.
|
|
7
|
+
*/
|
|
8
|
+
credits?: number;
|
|
6
9
|
/**
|
|
7
10
|
* Credits per one unit of currency. When set, a derived money value is shown
|
|
8
11
|
* underneath (e.g. credits=840, rate=100 → "≈ €8.40"). Omit to hide it.
|
|
@@ -12,7 +15,7 @@ export interface CreditsBadgeProps extends CrediballThemeOverrides {
|
|
|
12
15
|
currency?: string;
|
|
13
16
|
/** Label shown above the balance. */
|
|
14
17
|
label?: string;
|
|
15
|
-
/** Called when the user clicks "Add credits". */
|
|
18
|
+
/** Called when the user clicks "Add credits". Defaults to opening the provider's paywall. */
|
|
16
19
|
onAddCredits?: () => void;
|
|
17
20
|
/** Override the button text. */
|
|
18
21
|
addLabel?: string;
|
package/dist/CreditsBadge.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
function formatMoney(amount, currency) {
|
|
8
|
-
return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(amount);
|
|
9
|
-
}
|
|
3
|
+
import { theme, accentStyle } from "./theme";
|
|
4
|
+
import { formatCredits, formatMoney } from "./format";
|
|
5
|
+
import { useCrediballOptional } from "./CrediballProvider";
|
|
10
6
|
/**
|
|
11
7
|
* Pattern A — Embedded (recommended). Drop into a profile/settings section to
|
|
12
8
|
* show the user's balance with an "Add credits" action. Shows the credit balance
|
|
13
9
|
* as the primary figure and an approximate money value beneath it. No Crediball
|
|
14
10
|
* branding.
|
|
15
11
|
*/
|
|
16
|
-
export function CreditsBadge({ credits, rate, currency = "EUR", label = "Credit balance", onAddCredits, addLabel = "Add credits", accentColor
|
|
12
|
+
export function CreditsBadge({ credits, rate, currency = "EUR", label = "Credit balance", onAddCredits, addLabel = "Add credits", accentColor, style, }) {
|
|
13
|
+
const ctx = useCrediballOptional();
|
|
14
|
+
const resolvedCredits = credits ?? ctx?.balance ?? 0;
|
|
17
15
|
const showMoney = typeof rate === "number" && rate > 0;
|
|
18
16
|
return (_jsxs("div", { style: {
|
|
19
17
|
display: "flex",
|
|
@@ -21,20 +19,21 @@ export function CreditsBadge({ credits, rate, currency = "EUR", label = "Credit
|
|
|
21
19
|
justifyContent: "space-between",
|
|
22
20
|
gap: 16,
|
|
23
21
|
padding: 24,
|
|
24
|
-
border: `1px solid ${
|
|
25
|
-
borderRadius:
|
|
26
|
-
background:
|
|
27
|
-
fontFamily:
|
|
22
|
+
border: `1px solid ${theme.hairline}`,
|
|
23
|
+
borderRadius: theme.radiusCard,
|
|
24
|
+
background: theme.canvas,
|
|
25
|
+
fontFamily: theme.font,
|
|
26
|
+
...accentStyle(accentColor),
|
|
28
27
|
...style,
|
|
29
|
-
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, color:
|
|
28
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, color: theme.inkMuted }, children: label }), _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: {
|
|
30
29
|
appearance: "none",
|
|
31
30
|
border: "none",
|
|
32
31
|
cursor: "pointer",
|
|
33
|
-
background:
|
|
34
|
-
color:
|
|
35
|
-
fontFamily:
|
|
32
|
+
background: theme.accent,
|
|
33
|
+
color: theme.onAccent,
|
|
34
|
+
fontFamily: theme.font,
|
|
36
35
|
fontSize: 14,
|
|
37
|
-
padding: "8px
|
|
38
|
-
borderRadius:
|
|
36
|
+
padding: "8px 16px",
|
|
37
|
+
borderRadius: theme.radiusPill,
|
|
39
38
|
}, children: addLabel })] }));
|
|
40
39
|
}
|
package/dist/PaywallModal.d.ts
CHANGED
|
@@ -72,6 +72,8 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
|
72
72
|
onClose?: () => void;
|
|
73
73
|
/** When true, show a free-form amount field in addition to the preset buttons. */
|
|
74
74
|
allowCustom?: boolean;
|
|
75
|
+
/** Button label for the free-form amount field (default "Add"). */
|
|
76
|
+
addButtonText?: string;
|
|
75
77
|
/** Minimum custom amount (default 1). */
|
|
76
78
|
customMin?: number;
|
|
77
79
|
/** Optional maximum custom amount. */
|
|
@@ -79,14 +81,26 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
|
79
81
|
/** Currency symbol shown beside the custom field (default "€"). */
|
|
80
82
|
currencySymbol?: string;
|
|
81
83
|
/**
|
|
82
|
-
* When provided, a
|
|
83
|
-
*
|
|
84
|
+
* When provided, a compact "Balance" column is shown at the top of the
|
|
85
|
+
* modal (beside "Usage" if that's provided too) so the user can see why
|
|
86
|
+
* the modal appeared.
|
|
84
87
|
*/
|
|
85
88
|
balance?: number;
|
|
86
89
|
/** Credits per one currency unit — used to derive the ≈ money value beside the balance. */
|
|
87
90
|
balanceRate?: number;
|
|
88
91
|
/** ISO 4217 currency for the derived balance money value (default "EUR"). */
|
|
89
92
|
currency?: string;
|
|
93
|
+
/** Credits used this period — shown as a second column beside balance, when provided. */
|
|
94
|
+
usage?: number;
|
|
95
|
+
/** Label above the usage column (default "Usage"). */
|
|
96
|
+
usageLabel?: string;
|
|
97
|
+
/** Hide the "Powered by Crediball" footer line (default false). */
|
|
98
|
+
hideBranding?: boolean;
|
|
90
99
|
style?: CSSProperties;
|
|
91
100
|
}
|
|
92
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Pattern B — Paywall moment. Render when a user has insufficient credits to
|
|
103
|
+
* continue. The host controls when it appears; shows a small "Powered by
|
|
104
|
+
* Crediball" line by default (set `hideBranding` to remove it).
|
|
105
|
+
*/
|
|
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;
|