@crediball/react 0.9.0 → 0.10.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.js +24 -2
- package/dist/PaywallModal.d.ts +10 -1
- package/dist/PaywallModal.js +40 -2
- package/dist/ReferralCard.d.ts +20 -1
- package/dist/ReferralCard.js +15 -2
- package/package.json +1 -1
|
@@ -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() {
|
|
@@ -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.
|
|
@@ -230,7 +252,7 @@ export function CrediballProvider({ children, publishableKey, userId, apiUrl, po
|
|
|
230
252
|
// layout box. Host props/CSS are overridden by design (dashboard wins).
|
|
231
253
|
const themeVars = applyRemoteTheme ? themeToCssVars(uiTheme) : {};
|
|
232
254
|
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 })] }));
|
|
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 })] }));
|
|
234
256
|
const configCtx = useMemo(() => ({ publishableKey, userId, apiUrl }), [publishableKey, userId, apiUrl]);
|
|
235
257
|
return (_jsx(CrediballContext.Provider, { value: ctx, children: _jsx(CrediballConfigContext.Provider, { value: configCtx, children: hasThemeVars ? (_jsx("div", { style: { display: "contents", ...themeVars }, children: inner })) : (inner) }) }));
|
|
236
258
|
}
|
package/dist/PaywallModal.d.ts
CHANGED
|
@@ -96,6 +96,15 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
|
96
96
|
usageLabel?: string;
|
|
97
97
|
/** Hide the "Powered by Crediball" footer line (default false). */
|
|
98
98
|
hideBranding?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* The user's referral invite link. When provided, a "Refer a friend" row with
|
|
101
|
+
* a copyable link is shown at the bottom of the modal — a way to earn credits
|
|
102
|
+
* without paying. Wired automatically by <CrediballProvider> when referrals are
|
|
103
|
+
* enabled in the dashboard; pass it yourself when using the modal standalone.
|
|
104
|
+
*/
|
|
105
|
+
referralLink?: string | null;
|
|
106
|
+
/** Credits earned per successful referral, shown in the refer-a-friend blurb. */
|
|
107
|
+
referralReward?: number;
|
|
99
108
|
style?: CSSProperties;
|
|
100
109
|
}
|
|
101
110
|
/**
|
|
@@ -103,4 +112,4 @@ export interface PaywallModalProps extends CrediballThemeOverrides {
|
|
|
103
112
|
* continue. The host controls when it appears; shows a small "Powered by
|
|
104
113
|
* Crediball" line by default (set `hideBranding` to remove it).
|
|
105
114
|
*/
|
|
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;
|
|
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;
|
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, 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, }) {
|
|
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,7 @@ 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);
|
|
34
35
|
useEffect(() => {
|
|
35
36
|
const el = overlayRef.current;
|
|
36
37
|
if (!el || typeof ResizeObserver === "undefined")
|
|
@@ -56,6 +57,13 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
56
57
|
setCancelPending(false);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
async function copyReferral() {
|
|
61
|
+
if (!referralLink || typeof navigator === "undefined" || !navigator.clipboard)
|
|
62
|
+
return;
|
|
63
|
+
await navigator.clipboard.writeText(referralLink);
|
|
64
|
+
setReferralCopied(true);
|
|
65
|
+
setTimeout(() => setReferralCopied(false), 2000);
|
|
66
|
+
}
|
|
59
67
|
function submitCustom() {
|
|
60
68
|
const amt = Number(customValue);
|
|
61
69
|
if (!Number.isFinite(amt) || amt <= 0) {
|
|
@@ -162,7 +170,37 @@ export function PaywallModal({ open, packages, onSelectPackage, subscriptionPlan
|
|
|
162
170
|
color: theme.ink,
|
|
163
171
|
background: theme.canvas,
|
|
164
172
|
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,
|
|
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,
|
|
175
|
+
paddingTop: 16,
|
|
176
|
+
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: {
|
|
180
|
+
flex: 1,
|
|
181
|
+
minWidth: 0,
|
|
182
|
+
overflow: "hidden",
|
|
183
|
+
textOverflow: "ellipsis",
|
|
184
|
+
whiteSpace: "nowrap",
|
|
185
|
+
fontSize: 13,
|
|
186
|
+
color: theme.ink,
|
|
187
|
+
border: `1px solid ${theme.hairline}`,
|
|
188
|
+
borderRadius: theme.radiusPill,
|
|
189
|
+
padding: "8px 14px",
|
|
190
|
+
}, children: referralLink }), _jsx("button", { onClick: copyReferral, style: {
|
|
191
|
+
flex: "none",
|
|
192
|
+
appearance: "none",
|
|
193
|
+
cursor: "pointer",
|
|
194
|
+
background: "transparent",
|
|
195
|
+
color: theme.ink,
|
|
196
|
+
border: `1px solid ${theme.hairline}`,
|
|
197
|
+
fontFamily: theme.font,
|
|
198
|
+
fontWeight: 500,
|
|
199
|
+
fontSize: 14,
|
|
200
|
+
padding: "8px 16px",
|
|
201
|
+
borderRadius: theme.radiusPill,
|
|
202
|
+
whiteSpace: "nowrap",
|
|
203
|
+
}, children: referralCopied ? "Copied!" : "Copy" })] })] })) : null, onClose ? (_jsx("button", { onClick: onClose, style: {
|
|
166
204
|
width: "100%",
|
|
167
205
|
marginTop: 12,
|
|
168
206
|
appearance: "none",
|
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