@windrun-huaiin/third-ui 31.3.6 → 31.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/main/credit/credit-nav-button.d.ts +3 -2
- package/dist/main/credit/credit-nav-button.js +7 -9
- package/dist/main/credit/credit-nav-button.mjs +5 -7
- package/dist/main/credit/credit-overview-client.js +6 -3
- package/dist/main/credit/credit-overview-client.mjs +7 -4
- package/dist/main/credit/credit-overview-nav-client.d.ts +12 -2
- package/dist/main/credit/credit-overview-nav-client.js +21 -5
- package/dist/main/credit/credit-overview-nav-client.mjs +21 -5
- package/dist/main/credit/index.d.ts +1 -1
- package/dist/main/credit/types.d.ts +1 -0
- package/dist/main/money-price/index.d.ts +2 -0
- package/dist/main/money-price/index.js +4 -0
- package/dist/main/money-price/index.mjs +1 -0
- package/dist/main/money-price/money-price-interactive.d.ts +1 -1
- package/dist/main/money-price/money-price-interactive.js +7 -5
- package/dist/main/money-price/money-price-interactive.mjs +7 -5
- package/dist/main/money-price/money-price-types.d.ts +2 -0
- package/dist/main/money-price/money-price.d.ts +1 -1
- package/dist/main/money-price/money-price.js +2 -2
- package/dist/main/money-price/money-price.mjs +2 -2
- package/dist/main/money-price/pricing-modal.d.ts +33 -0
- package/dist/main/money-price/pricing-modal.js +109 -0
- package/dist/main/money-price/pricing-modal.mjs +105 -0
- package/package.json +3 -3
- package/src/main/credit/credit-nav-button.tsx +15 -56
- package/src/main/credit/credit-overview-client.tsx +36 -25
- package/src/main/credit/credit-overview-nav-client.tsx +51 -4
- package/src/main/credit/index.ts +1 -0
- package/src/main/credit/types.ts +1 -0
- package/src/main/money-price/index.ts +10 -0
- package/src/main/money-price/money-price-interactive.tsx +41 -31
- package/src/main/money-price/money-price-types.ts +2 -0
- package/src/main/money-price/money-price.tsx +2 -0
- package/src/main/money-price/pricing-modal.tsx +283 -0
|
@@ -5,13 +5,13 @@ import { MoneyPriceInteractive } from './money-price-interactive.mjs';
|
|
|
5
5
|
import { buildMoneyPriceData } from './money-price-data.mjs';
|
|
6
6
|
|
|
7
7
|
function MoneyPrice(_a) {
|
|
8
|
-
return __awaiter(this, arguments, void 0, function* ({ locale, config, checkoutApiEndpoint, customerPortalApiEndpoint, enableClerkModal = false, sectionClassName, enabledBillingTypes, enableSubscriptionUpgrade = true, initUserContext, initialBillingType, }) {
|
|
8
|
+
return __awaiter(this, arguments, void 0, function* ({ locale, config, checkoutApiEndpoint, customerPortalApiEndpoint, enableClerkModal = false, sectionClassName, enabledBillingTypes, mobileBillingSwitchBehavior, enableSubscriptionUpgrade = true, initUserContext, initialBillingType, }) {
|
|
9
9
|
const data = yield buildMoneyPriceData({
|
|
10
10
|
locale,
|
|
11
11
|
currency: config.display.currency,
|
|
12
12
|
enabledBillingTypes,
|
|
13
13
|
});
|
|
14
|
-
return (jsxs("section", { id: "money-pricing", className: cn("px-4 py-4 md:px-16 md:py-8 mx-auto max-w-7xl scroll-mt-10", sectionClassName), children: [jsx("h2", { className: "text-3xl md:text-4xl font-bold text-center mb-3", children: data.title }), jsx("p", { className: "text-center text-gray-600 dark:text-gray-400 mb-4 text-base md:text-lg mx-auto", children: data.subtitle }), jsx(MoneyPriceInteractive, { data: data, config: config, checkoutApiEndpoint: checkoutApiEndpoint, customerPortalApiEndpoint: customerPortalApiEndpoint, enableClerkModal: enableClerkModal, enabledBillingTypes: enabledBillingTypes, enableSubscriptionUpgrade: enableSubscriptionUpgrade, initUserContext: initUserContext, initialBillingType: initialBillingType })] }));
|
|
14
|
+
return (jsxs("section", { id: "money-pricing", className: cn("px-4 py-4 md:px-16 md:py-8 mx-auto max-w-7xl scroll-mt-10", sectionClassName), children: [jsx("h2", { className: "text-3xl md:text-4xl font-bold text-center mb-3", children: data.title }), jsx("p", { className: "text-center text-gray-600 dark:text-gray-400 mb-4 text-base md:text-lg mx-auto", children: data.subtitle }), jsx(MoneyPriceInteractive, { data: data, config: config, checkoutApiEndpoint: checkoutApiEndpoint, customerPortalApiEndpoint: customerPortalApiEndpoint, enableClerkModal: enableClerkModal, enabledBillingTypes: enabledBillingTypes, mobileBillingSwitchBehavior: mobileBillingSwitchBehavior, enableSubscriptionUpgrade: enableSubscriptionUpgrade, initUserContext: initUserContext, initialBillingType: initialBillingType })] }));
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { InitUserContext, MoneyPriceConfig, MoneyPriceData } from './money-price-types';
|
|
3
|
+
export interface OpenPricingModalOptions {
|
|
4
|
+
billingType?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PricingModalProps {
|
|
7
|
+
open: boolean;
|
|
8
|
+
onOpenChange: (open: boolean) => void;
|
|
9
|
+
data: MoneyPriceData;
|
|
10
|
+
config: MoneyPriceConfig;
|
|
11
|
+
billingType?: string;
|
|
12
|
+
checkoutApiEndpoint?: string;
|
|
13
|
+
customerPortalApiEndpoint?: string;
|
|
14
|
+
enableClerkModal?: boolean;
|
|
15
|
+
enabledBillingTypes?: string[];
|
|
16
|
+
mobileBillingSwitchBehavior?: 'sticky' | 'static';
|
|
17
|
+
enableSubscriptionUpgrade?: boolean;
|
|
18
|
+
initUserContext?: InitUserContext;
|
|
19
|
+
isInitLoading?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface PricingModalProviderProps extends Omit<PricingModalProps, 'open' | 'onOpenChange' | 'billingType'> {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
pricingContextEndpoint?: string;
|
|
24
|
+
}
|
|
25
|
+
interface PricingModalContextValue {
|
|
26
|
+
openPricingModal: (options?: OpenPricingModalOptions) => void;
|
|
27
|
+
closePricingModal: () => void;
|
|
28
|
+
isPricingModalOpen: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function PricingModal({ open, onOpenChange, data, config, billingType, checkoutApiEndpoint, customerPortalApiEndpoint, enableClerkModal, enabledBillingTypes, mobileBillingSwitchBehavior, enableSubscriptionUpgrade, initUserContext, isInitLoading, }: PricingModalProps): import("react/jsx-runtime").JSX.Element;
|
|
31
|
+
export declare function PricingModalProvider({ children, pricingContextEndpoint, initUserContext: initUserContextProp, isInitLoading: isInitLoadingProp, ...modalProps }: PricingModalProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export declare function usePricingModal(): PricingModalContextValue;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tslib = require('tslib');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var icons = require('@windrun-huaiin/base-ui/icons');
|
|
7
|
+
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
8
|
+
var ui = require('@windrun-huaiin/base-ui/ui');
|
|
9
|
+
var utils = require('@windrun-huaiin/lib/utils');
|
|
10
|
+
var React = require('react');
|
|
11
|
+
var dialogStyles = require('../alert-dialog/dialog-styles.js');
|
|
12
|
+
var moneyPriceInteractive = require('./money-price-interactive.js');
|
|
13
|
+
|
|
14
|
+
const emptyInitUserContext = {
|
|
15
|
+
fingerprintId: null,
|
|
16
|
+
xUser: null,
|
|
17
|
+
xCredit: null,
|
|
18
|
+
xSubscription: null,
|
|
19
|
+
isClerkAuthenticated: false,
|
|
20
|
+
};
|
|
21
|
+
const PricingModalContext = React.createContext(null);
|
|
22
|
+
function normalizeEnabledBillingTypes(data, enabledBillingTypes) {
|
|
23
|
+
const availableKeys = data.billingSwitch.options.map((option) => option.key);
|
|
24
|
+
const configuredKeys = (enabledBillingTypes === null || enabledBillingTypes === void 0 ? void 0 : enabledBillingTypes.length) ? enabledBillingTypes : availableKeys;
|
|
25
|
+
return Array.from(new Set(configuredKeys
|
|
26
|
+
.map((billingType) => billingType.trim())
|
|
27
|
+
.filter((billingType) => availableKeys.includes(billingType))));
|
|
28
|
+
}
|
|
29
|
+
function resolveBillingType({ data, enabledBillingTypes, requestedBillingType, }) {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
const enabledKeys = normalizeEnabledBillingTypes(data, enabledBillingTypes);
|
|
32
|
+
if (requestedBillingType && enabledKeys.includes(requestedBillingType)) {
|
|
33
|
+
return requestedBillingType;
|
|
34
|
+
}
|
|
35
|
+
if (enabledKeys.includes(data.billingSwitch.defaultKey)) {
|
|
36
|
+
return data.billingSwitch.defaultKey;
|
|
37
|
+
}
|
|
38
|
+
return (_a = enabledKeys[0]) !== null && _a !== void 0 ? _a : (_b = data.billingSwitch.options[0]) === null || _b === void 0 ? void 0 : _b.key;
|
|
39
|
+
}
|
|
40
|
+
function PricingModal({ open, onOpenChange, data, config, billingType, checkoutApiEndpoint, customerPortalApiEndpoint, enableClerkModal = false, enabledBillingTypes, mobileBillingSwitchBehavior, enableSubscriptionUpgrade = true, initUserContext, isInitLoading = false, }) {
|
|
41
|
+
const resolvedBillingType = React.useMemo(() => resolveBillingType({
|
|
42
|
+
data,
|
|
43
|
+
enabledBillingTypes,
|
|
44
|
+
requestedBillingType: billingType,
|
|
45
|
+
}), [billingType, data, enabledBillingTypes]);
|
|
46
|
+
return (jsxRuntime.jsx(ui.AlertDialog, { open: open, onOpenChange: onOpenChange, children: open ? (jsxRuntime.jsxs(ui.AlertDialogContent, { className: utils.cn('mt-5 sm:mt-6 md:mt-10 lg:mt-15 w-[95vw] max-w-[1200px] overflow-hidden border border-slate-200 p-0 shadow-[0_32px_90px_rgba(15,23,42,0.25)] ring-1 ring-black/5 dark:border-white/12 dark:shadow-[0_40px_120px_rgba(0,0,0,0.6)] dark:ring-white/10', lib.themeMainBgColor), overlayClassName: dialogStyles.dialogThemedOverlayClass, children: [jsxRuntime.jsxs(ui.AlertDialogHeader, { className: "flex flex-row items-center justify-between border-b border-slate-200 px-6 pt-4 pb-1 dark:border-slate-800", children: [jsxRuntime.jsx(ui.AlertDialogTitle, { asChild: true, children: jsxRuntime.jsxs("div", { className: "flex flex-wrap items-baseline gap-3 text-slate-900 dark:text-white", children: [jsxRuntime.jsx("span", { className: "text-2xl font-semibold leading-tight", children: data.title }), data.subtitle ? (jsxRuntime.jsx("span", { className: "text-sm font-medium text-slate-500 dark:text-slate-300", children: data.subtitle })) : null] }) }), jsxRuntime.jsx("button", { type: "button", className: "rounded-full p-2 text-gray-400 transition hover:bg-gray-400 hover:text-gray-400 dark:text-white/80 dark:hover:bg-white/80 dark:hover:text-white/80", onClick: () => onOpenChange(false), children: jsxRuntime.jsx(icons.XIcon, { className: "h-6 w-6" }) })] }), jsxRuntime.jsx("div", { className: "max-h-[60vh] overflow-y-auto px-4 pt-2 pb-6 sm:max-h-[80vh]", children: jsxRuntime.jsx("div", { className: "mx-auto w-full", children: jsxRuntime.jsx(moneyPriceInteractive.MoneyPriceInteractive, { data: data, config: config, checkoutApiEndpoint: checkoutApiEndpoint, customerPortalApiEndpoint: customerPortalApiEndpoint, enableClerkModal: enableClerkModal, enabledBillingTypes: enabledBillingTypes, mobileBillingSwitchBehavior: mobileBillingSwitchBehavior, enableSubscriptionUpgrade: enableSubscriptionUpgrade, initialBillingType: resolvedBillingType, disableAutoDetectBilling: resolvedBillingType === 'onetime', initUserContext: initUserContext, isInitLoading: isInitLoading }, resolvedBillingType !== null && resolvedBillingType !== void 0 ? resolvedBillingType : 'default') }) })] })) : null }));
|
|
47
|
+
}
|
|
48
|
+
function PricingModalProvider(_a) {
|
|
49
|
+
var { children, pricingContextEndpoint, initUserContext: initUserContextProp, isInitLoading: isInitLoadingProp = false } = _a, modalProps = tslib.__rest(_a, ["children", "pricingContextEndpoint", "initUserContext", "isInitLoading"]);
|
|
50
|
+
const [open, setOpen] = React.useState(false);
|
|
51
|
+
const [requestedBillingType, setRequestedBillingType] = React.useState();
|
|
52
|
+
const [loadedInitUserContext, setLoadedInitUserContext] = React.useState(initUserContextProp !== null && initUserContextProp !== void 0 ? initUserContextProp : emptyInitUserContext);
|
|
53
|
+
const [isContextLoading, setIsContextLoading] = React.useState(false);
|
|
54
|
+
const loadedContextRef = React.useRef(!!initUserContextProp);
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (initUserContextProp) {
|
|
57
|
+
loadedContextRef.current = true;
|
|
58
|
+
setLoadedInitUserContext(initUserContextProp);
|
|
59
|
+
}
|
|
60
|
+
}, [initUserContextProp]);
|
|
61
|
+
const loadPricingContext = React.useCallback(() => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
if (!pricingContextEndpoint || loadedContextRef.current) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
setIsContextLoading(true);
|
|
66
|
+
try {
|
|
67
|
+
const response = yield fetch(pricingContextEndpoint, {
|
|
68
|
+
credentials: 'same-origin',
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const nextContext = (yield response.json());
|
|
74
|
+
loadedContextRef.current = true;
|
|
75
|
+
setLoadedInitUserContext(nextContext);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.warn('[PricingModalProvider] Failed to load pricing context', error);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
setIsContextLoading(false);
|
|
82
|
+
}
|
|
83
|
+
}), [pricingContextEndpoint]);
|
|
84
|
+
const openPricingModal = React.useCallback((options) => {
|
|
85
|
+
setRequestedBillingType(options === null || options === void 0 ? void 0 : options.billingType);
|
|
86
|
+
setOpen(true);
|
|
87
|
+
void loadPricingContext();
|
|
88
|
+
}, [loadPricingContext]);
|
|
89
|
+
const closePricingModal = React.useCallback(() => {
|
|
90
|
+
setOpen(false);
|
|
91
|
+
}, []);
|
|
92
|
+
const contextValue = React.useMemo(() => ({
|
|
93
|
+
openPricingModal,
|
|
94
|
+
closePricingModal,
|
|
95
|
+
isPricingModalOpen: open,
|
|
96
|
+
}), [closePricingModal, open, openPricingModal]);
|
|
97
|
+
return (jsxRuntime.jsxs(PricingModalContext.Provider, { value: contextValue, children: [children, jsxRuntime.jsx(PricingModal, Object.assign({}, modalProps, { open: open, onOpenChange: setOpen, billingType: requestedBillingType, initUserContext: initUserContextProp !== null && initUserContextProp !== void 0 ? initUserContextProp : loadedInitUserContext, isInitLoading: isInitLoadingProp || isContextLoading }))] }));
|
|
98
|
+
}
|
|
99
|
+
function usePricingModal() {
|
|
100
|
+
const context = React.useContext(PricingModalContext);
|
|
101
|
+
if (!context) {
|
|
102
|
+
throw new Error('usePricingModal must be used within PricingModalProvider.');
|
|
103
|
+
}
|
|
104
|
+
return context;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
exports.PricingModal = PricingModal;
|
|
108
|
+
exports.PricingModalProvider = PricingModalProvider;
|
|
109
|
+
exports.usePricingModal = usePricingModal;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { __rest, __awaiter } from 'tslib';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { XIcon } from '@windrun-huaiin/base-ui/icons';
|
|
5
|
+
import { themeMainBgColor } from '@windrun-huaiin/base-ui/lib';
|
|
6
|
+
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle } from '@windrun-huaiin/base-ui/ui';
|
|
7
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
8
|
+
import { createContext, useMemo, useState, useRef, useEffect, useCallback, useContext } from 'react';
|
|
9
|
+
import { dialogThemedOverlayClass } from '../alert-dialog/dialog-styles.mjs';
|
|
10
|
+
import { MoneyPriceInteractive } from './money-price-interactive.mjs';
|
|
11
|
+
|
|
12
|
+
const emptyInitUserContext = {
|
|
13
|
+
fingerprintId: null,
|
|
14
|
+
xUser: null,
|
|
15
|
+
xCredit: null,
|
|
16
|
+
xSubscription: null,
|
|
17
|
+
isClerkAuthenticated: false,
|
|
18
|
+
};
|
|
19
|
+
const PricingModalContext = createContext(null);
|
|
20
|
+
function normalizeEnabledBillingTypes(data, enabledBillingTypes) {
|
|
21
|
+
const availableKeys = data.billingSwitch.options.map((option) => option.key);
|
|
22
|
+
const configuredKeys = (enabledBillingTypes === null || enabledBillingTypes === void 0 ? void 0 : enabledBillingTypes.length) ? enabledBillingTypes : availableKeys;
|
|
23
|
+
return Array.from(new Set(configuredKeys
|
|
24
|
+
.map((billingType) => billingType.trim())
|
|
25
|
+
.filter((billingType) => availableKeys.includes(billingType))));
|
|
26
|
+
}
|
|
27
|
+
function resolveBillingType({ data, enabledBillingTypes, requestedBillingType, }) {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
const enabledKeys = normalizeEnabledBillingTypes(data, enabledBillingTypes);
|
|
30
|
+
if (requestedBillingType && enabledKeys.includes(requestedBillingType)) {
|
|
31
|
+
return requestedBillingType;
|
|
32
|
+
}
|
|
33
|
+
if (enabledKeys.includes(data.billingSwitch.defaultKey)) {
|
|
34
|
+
return data.billingSwitch.defaultKey;
|
|
35
|
+
}
|
|
36
|
+
return (_a = enabledKeys[0]) !== null && _a !== void 0 ? _a : (_b = data.billingSwitch.options[0]) === null || _b === void 0 ? void 0 : _b.key;
|
|
37
|
+
}
|
|
38
|
+
function PricingModal({ open, onOpenChange, data, config, billingType, checkoutApiEndpoint, customerPortalApiEndpoint, enableClerkModal = false, enabledBillingTypes, mobileBillingSwitchBehavior, enableSubscriptionUpgrade = true, initUserContext, isInitLoading = false, }) {
|
|
39
|
+
const resolvedBillingType = useMemo(() => resolveBillingType({
|
|
40
|
+
data,
|
|
41
|
+
enabledBillingTypes,
|
|
42
|
+
requestedBillingType: billingType,
|
|
43
|
+
}), [billingType, data, enabledBillingTypes]);
|
|
44
|
+
return (jsx(AlertDialog, { open: open, onOpenChange: onOpenChange, children: open ? (jsxs(AlertDialogContent, { className: cn('mt-5 sm:mt-6 md:mt-10 lg:mt-15 w-[95vw] max-w-[1200px] overflow-hidden border border-slate-200 p-0 shadow-[0_32px_90px_rgba(15,23,42,0.25)] ring-1 ring-black/5 dark:border-white/12 dark:shadow-[0_40px_120px_rgba(0,0,0,0.6)] dark:ring-white/10', themeMainBgColor), overlayClassName: dialogThemedOverlayClass, children: [jsxs(AlertDialogHeader, { className: "flex flex-row items-center justify-between border-b border-slate-200 px-6 pt-4 pb-1 dark:border-slate-800", children: [jsx(AlertDialogTitle, { asChild: true, children: jsxs("div", { className: "flex flex-wrap items-baseline gap-3 text-slate-900 dark:text-white", children: [jsx("span", { className: "text-2xl font-semibold leading-tight", children: data.title }), data.subtitle ? (jsx("span", { className: "text-sm font-medium text-slate-500 dark:text-slate-300", children: data.subtitle })) : null] }) }), jsx("button", { type: "button", className: "rounded-full p-2 text-gray-400 transition hover:bg-gray-400 hover:text-gray-400 dark:text-white/80 dark:hover:bg-white/80 dark:hover:text-white/80", onClick: () => onOpenChange(false), children: jsx(XIcon, { className: "h-6 w-6" }) })] }), jsx("div", { className: "max-h-[60vh] overflow-y-auto px-4 pt-2 pb-6 sm:max-h-[80vh]", children: jsx("div", { className: "mx-auto w-full", children: jsx(MoneyPriceInteractive, { data: data, config: config, checkoutApiEndpoint: checkoutApiEndpoint, customerPortalApiEndpoint: customerPortalApiEndpoint, enableClerkModal: enableClerkModal, enabledBillingTypes: enabledBillingTypes, mobileBillingSwitchBehavior: mobileBillingSwitchBehavior, enableSubscriptionUpgrade: enableSubscriptionUpgrade, initialBillingType: resolvedBillingType, disableAutoDetectBilling: resolvedBillingType === 'onetime', initUserContext: initUserContext, isInitLoading: isInitLoading }, resolvedBillingType !== null && resolvedBillingType !== void 0 ? resolvedBillingType : 'default') }) })] })) : null }));
|
|
45
|
+
}
|
|
46
|
+
function PricingModalProvider(_a) {
|
|
47
|
+
var { children, pricingContextEndpoint, initUserContext: initUserContextProp, isInitLoading: isInitLoadingProp = false } = _a, modalProps = __rest(_a, ["children", "pricingContextEndpoint", "initUserContext", "isInitLoading"]);
|
|
48
|
+
const [open, setOpen] = useState(false);
|
|
49
|
+
const [requestedBillingType, setRequestedBillingType] = useState();
|
|
50
|
+
const [loadedInitUserContext, setLoadedInitUserContext] = useState(initUserContextProp !== null && initUserContextProp !== void 0 ? initUserContextProp : emptyInitUserContext);
|
|
51
|
+
const [isContextLoading, setIsContextLoading] = useState(false);
|
|
52
|
+
const loadedContextRef = useRef(!!initUserContextProp);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (initUserContextProp) {
|
|
55
|
+
loadedContextRef.current = true;
|
|
56
|
+
setLoadedInitUserContext(initUserContextProp);
|
|
57
|
+
}
|
|
58
|
+
}, [initUserContextProp]);
|
|
59
|
+
const loadPricingContext = useCallback(() => __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
if (!pricingContextEndpoint || loadedContextRef.current) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
setIsContextLoading(true);
|
|
64
|
+
try {
|
|
65
|
+
const response = yield fetch(pricingContextEndpoint, {
|
|
66
|
+
credentials: 'same-origin',
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const nextContext = (yield response.json());
|
|
72
|
+
loadedContextRef.current = true;
|
|
73
|
+
setLoadedInitUserContext(nextContext);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.warn('[PricingModalProvider] Failed to load pricing context', error);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
setIsContextLoading(false);
|
|
80
|
+
}
|
|
81
|
+
}), [pricingContextEndpoint]);
|
|
82
|
+
const openPricingModal = useCallback((options) => {
|
|
83
|
+
setRequestedBillingType(options === null || options === void 0 ? void 0 : options.billingType);
|
|
84
|
+
setOpen(true);
|
|
85
|
+
void loadPricingContext();
|
|
86
|
+
}, [loadPricingContext]);
|
|
87
|
+
const closePricingModal = useCallback(() => {
|
|
88
|
+
setOpen(false);
|
|
89
|
+
}, []);
|
|
90
|
+
const contextValue = useMemo(() => ({
|
|
91
|
+
openPricingModal,
|
|
92
|
+
closePricingModal,
|
|
93
|
+
isPricingModalOpen: open,
|
|
94
|
+
}), [closePricingModal, open, openPricingModal]);
|
|
95
|
+
return (jsxs(PricingModalContext.Provider, { value: contextValue, children: [children, jsx(PricingModal, Object.assign({}, modalProps, { open: open, onOpenChange: setOpen, billingType: requestedBillingType, initUserContext: initUserContextProp !== null && initUserContextProp !== void 0 ? initUserContextProp : loadedInitUserContext, isInitLoading: isInitLoadingProp || isContextLoading }))] }));
|
|
96
|
+
}
|
|
97
|
+
function usePricingModal() {
|
|
98
|
+
const context = useContext(PricingModalContext);
|
|
99
|
+
if (!context) {
|
|
100
|
+
throw new Error('usePricingModal must be used within PricingModalProvider.');
|
|
101
|
+
}
|
|
102
|
+
return context;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { PricingModal, PricingModalProvider, usePricingModal };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.4.0",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./clerk": {
|
|
@@ -244,8 +244,8 @@
|
|
|
244
244
|
"unified": "^11.0.5",
|
|
245
245
|
"zod": "^4.3.6",
|
|
246
246
|
"@windrun-huaiin/base-ui": "^31.0.1",
|
|
247
|
-
"@windrun-huaiin/
|
|
248
|
-
"@windrun-huaiin/
|
|
247
|
+
"@windrun-huaiin/lib": "^31.0.3",
|
|
248
|
+
"@windrun-huaiin/contracts": "^31.0.0"
|
|
249
249
|
},
|
|
250
250
|
"peerDependencies": {
|
|
251
251
|
"clsx": "^2.1.1",
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
-
import { GemIcon
|
|
5
|
-
import { themeMainBgColor } from '@windrun-huaiin/base-ui/lib';
|
|
4
|
+
import { GemIcon } from '@windrun-huaiin/base-ui/icons';
|
|
6
5
|
import {
|
|
7
|
-
AlertDialog,
|
|
8
|
-
AlertDialogContent,
|
|
9
|
-
AlertDialogHeader,
|
|
10
|
-
AlertDialogTitle,
|
|
11
6
|
DropdownMenu,
|
|
12
7
|
DropdownMenuContent,
|
|
13
8
|
DropdownMenuTrigger,
|
|
@@ -21,16 +16,16 @@ import {
|
|
|
21
16
|
useRef,
|
|
22
17
|
useState,
|
|
23
18
|
} from 'react';
|
|
24
|
-
import { MoneyPriceInteractive } from '../money-price/money-price-interactive';
|
|
25
19
|
import { getActiveProviderConfigUtil } from '../money-price/money-price-config-util';
|
|
26
20
|
import type { MoneyPriceData, SubscriptionProductConfig } from '../money-price/money-price-types';
|
|
27
|
-
import {
|
|
28
|
-
import type { CreditPricingContext, PricingModalMode } from './types';
|
|
21
|
+
import { PricingModal } from '../money-price/pricing-modal';
|
|
22
|
+
import type { CreditPricingContext, MobileBillingSwitchBehavior, PricingModalMode } from './types';
|
|
29
23
|
|
|
30
24
|
interface CreditNavButtonProps {
|
|
31
25
|
locale: string;
|
|
32
26
|
totalBalance: number;
|
|
33
27
|
totalLabel: string;
|
|
28
|
+
mobileBillingSwitchBehavior?: MobileBillingSwitchBehavior;
|
|
34
29
|
children: React.ReactNode;
|
|
35
30
|
}
|
|
36
31
|
|
|
@@ -38,6 +33,7 @@ export function CreditNavButton({
|
|
|
38
33
|
locale,
|
|
39
34
|
totalBalance,
|
|
40
35
|
totalLabel,
|
|
36
|
+
mobileBillingSwitchBehavior,
|
|
41
37
|
children,
|
|
42
38
|
}: CreditNavButtonProps) {
|
|
43
39
|
const [open, setOpen] = useState(false);
|
|
@@ -243,7 +239,7 @@ export function CreditNavButton({
|
|
|
243
239
|
</DropdownMenuContent>
|
|
244
240
|
</DropdownMenu>
|
|
245
241
|
{pricingModal.modalMoneyPriceData && pricingModal.pricingContext ? (
|
|
246
|
-
<
|
|
242
|
+
<PricingModal
|
|
247
243
|
open={pricingModal.open}
|
|
248
244
|
onOpenChange={(open) =>
|
|
249
245
|
setPricingModal((prev) => ({
|
|
@@ -251,52 +247,15 @@ export function CreditNavButton({
|
|
|
251
247
|
open,
|
|
252
248
|
}))
|
|
253
249
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
<AlertDialogTitle asChild>
|
|
264
|
-
<div className="flex flex-wrap items-baseline gap-3 text-slate-900 dark:text-white">
|
|
265
|
-
<span className="text-2xl font-semibold leading-tight">
|
|
266
|
-
{pricingModal.modalMoneyPriceData.title}
|
|
267
|
-
</span>
|
|
268
|
-
{pricingModal.modalMoneyPriceData.subtitle ? (
|
|
269
|
-
<span className="text-sm font-medium text-slate-500 dark:text-slate-300">
|
|
270
|
-
{pricingModal.modalMoneyPriceData.subtitle}
|
|
271
|
-
</span>
|
|
272
|
-
) : null}
|
|
273
|
-
</div>
|
|
274
|
-
</AlertDialogTitle>
|
|
275
|
-
<button
|
|
276
|
-
type="button"
|
|
277
|
-
className="rounded-full p-2 text-gray-400 transition hover:bg-gray-400 hover:text-gray-400 dark:text-white/80 dark:hover:bg-white/80 dark:hover:text-white/80"
|
|
278
|
-
onClick={closePricingModal}
|
|
279
|
-
>
|
|
280
|
-
<XIcon className="h-6 w-6" />
|
|
281
|
-
</button>
|
|
282
|
-
</AlertDialogHeader>
|
|
283
|
-
<div className="max-h-[60vh] sm:max-h-[80vh] overflow-y-auto px-4 pt-2 pb-6">
|
|
284
|
-
<div className="mx-auto w-full">
|
|
285
|
-
<MoneyPriceInteractive
|
|
286
|
-
key={pricingModal.mode}
|
|
287
|
-
data={pricingModal.modalMoneyPriceData}
|
|
288
|
-
config={pricingModal.pricingContext.moneyPriceConfig}
|
|
289
|
-
checkoutApiEndpoint={pricingModal.pricingContext.checkoutApiEndpoint}
|
|
290
|
-
customerPortalApiEndpoint={pricingModal.pricingContext.customerPortalApiEndpoint}
|
|
291
|
-
enableSubscriptionUpgrade={pricingModal.pricingContext.enableSubscriptionUpgrade}
|
|
292
|
-
initialBillingType={modalInitialBillingType}
|
|
293
|
-
disableAutoDetectBilling={isOnetimeModal}
|
|
294
|
-
initUserContext={pricingModal.pricingContext.initUserContext}
|
|
295
|
-
/>
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
</AlertDialogContent>
|
|
299
|
-
</AlertDialog>
|
|
250
|
+
data={pricingModal.modalMoneyPriceData}
|
|
251
|
+
config={pricingModal.pricingContext.moneyPriceConfig}
|
|
252
|
+
checkoutApiEndpoint={pricingModal.pricingContext.checkoutApiEndpoint}
|
|
253
|
+
customerPortalApiEndpoint={pricingModal.pricingContext.customerPortalApiEndpoint}
|
|
254
|
+
mobileBillingSwitchBehavior={mobileBillingSwitchBehavior}
|
|
255
|
+
enableSubscriptionUpgrade={pricingModal.pricingContext.enableSubscriptionUpgrade}
|
|
256
|
+
billingType={modalInitialBillingType}
|
|
257
|
+
initUserContext={pricingModal.pricingContext.initUserContext}
|
|
258
|
+
/>
|
|
300
259
|
) : null}
|
|
301
260
|
</CreditNavPopoverContext.Provider>
|
|
302
261
|
);
|
|
@@ -128,6 +128,11 @@ export function CreditOverviewClient({
|
|
|
128
128
|
const subscription = data.subscription;
|
|
129
129
|
const pricingContext = data.pricingContext;
|
|
130
130
|
const ctaBehaviors = data.ctaBehaviors;
|
|
131
|
+
const billingOptions = pricingContext?.moneyPriceData.billingSwitch.options ?? [];
|
|
132
|
+
const subscriptionBillingEnabled = billingOptions.some(
|
|
133
|
+
(option) => option.key === 'monthly' || option.key === 'yearly',
|
|
134
|
+
);
|
|
135
|
+
const onetimeBillingEnabled = billingOptions.some((option) => option.key === 'onetime');
|
|
131
136
|
const getModalMoneyPriceData = useCallback(
|
|
132
137
|
(mode: PricingModalMode) => {
|
|
133
138
|
if (!pricingContext) {
|
|
@@ -383,24 +388,28 @@ export function CreditOverviewClient({
|
|
|
383
388
|
<div className="flex justify-center text-3xl font-semibold leading-tight sm:text-4xl">
|
|
384
389
|
{formatNumber(locale, data.totalBalance)}
|
|
385
390
|
</div>
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
391
|
+
{subscriptionBillingEnabled ? (
|
|
392
|
+
<>
|
|
393
|
+
<div className="flex-1 flex-col gap-1">
|
|
394
|
+
<p className="text-xs text-gray-700 dark:text-slate-100 sm:text-sm">
|
|
395
|
+
{translations.subscriptionPeriodLabel}
|
|
396
|
+
</p>
|
|
397
|
+
<h4 className="text-xl font-semibold sm:text-2xl">
|
|
398
|
+
{subscription ? subscription.planName : translations.subscriptionInactive}
|
|
399
|
+
</h4>
|
|
400
|
+
</div>
|
|
401
|
+
<div className="pt-2 sm:pt-0">
|
|
402
|
+
<GradientButton
|
|
403
|
+
title={subscription ? translations.subscriptionManage : translations.subscribePay}
|
|
404
|
+
align="center"
|
|
405
|
+
icon={subscription ? <Settings2Icon /> : <BellIcon />}
|
|
406
|
+
openInNewTab={false}
|
|
407
|
+
className="w-full"
|
|
408
|
+
onClick={subscription ? handleManageAction : handleSubscribeAction}
|
|
409
|
+
/>
|
|
410
|
+
</div>
|
|
411
|
+
</>
|
|
412
|
+
) : null}
|
|
404
413
|
</div>
|
|
405
414
|
<div className="absolute right-3 top-3 sm:right-6 sm:top-6">
|
|
406
415
|
<HoverInfo
|
|
@@ -482,13 +491,15 @@ export function CreditOverviewClient({
|
|
|
482
491
|
{translations.bucketsEmpty}
|
|
483
492
|
</div>
|
|
484
493
|
)}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
494
|
+
{onetimeBillingEnabled ? (
|
|
495
|
+
<GradientButton
|
|
496
|
+
title={translations.onetimeBuy}
|
|
497
|
+
icon={<ShoppingCartIcon />}
|
|
498
|
+
align="center"
|
|
499
|
+
className="w-full text-sm sm:text-base"
|
|
500
|
+
onClick={handleOnetimeAction}
|
|
501
|
+
/>
|
|
502
|
+
) : null}
|
|
492
503
|
</section>
|
|
493
504
|
</section>
|
|
494
505
|
);
|
|
@@ -4,7 +4,7 @@ import { useAuth } from '@clerk/nextjs';
|
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { CreditNavButton } from './credit-nav-button';
|
|
6
6
|
import { CreditOverviewClient, type CreditOverviewTranslations } from './credit-overview-client';
|
|
7
|
-
import type { CreditOverviewData } from './types';
|
|
7
|
+
import type { CreditOverviewData, MobileBillingSwitchBehavior } from './types';
|
|
8
8
|
|
|
9
9
|
export const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = 'windrun-huaiin:credit-overview:refresh';
|
|
10
10
|
|
|
@@ -17,6 +17,16 @@ export interface CreditOverviewPayload {
|
|
|
17
17
|
export interface CreditOverviewNavClientProps {
|
|
18
18
|
locale: string;
|
|
19
19
|
endpoint: string;
|
|
20
|
+
/**
|
|
21
|
+
* Billing types enabled by the business app.
|
|
22
|
+
* Defaults are handled by the API/payload builder when omitted.
|
|
23
|
+
*/
|
|
24
|
+
enabledBillingTypes?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Mobile behavior for the pricing modal billing switch.
|
|
27
|
+
* Default: 'sticky'.
|
|
28
|
+
*/
|
|
29
|
+
mobileBillingSwitchBehavior?: MobileBillingSwitchBehavior;
|
|
20
30
|
/**
|
|
21
31
|
* Browser event names that should trigger credit overview reload.
|
|
22
32
|
* Business apps can dispatch these events after credit balance changes.
|
|
@@ -32,9 +42,34 @@ export interface CreditOverviewNavClientProps {
|
|
|
32
42
|
refreshDebounceMs?: number;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
function
|
|
45
|
+
function normalizeEnabledBillingTypes(enabledBillingTypes: string[] | undefined) {
|
|
46
|
+
if (!enabledBillingTypes?.length) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Array.from(
|
|
51
|
+
new Set(
|
|
52
|
+
enabledBillingTypes
|
|
53
|
+
.map((billingType) => billingType.trim())
|
|
54
|
+
.filter((billingType) => billingType.length > 0),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildEnabledBillingTypesKey(enabledBillingTypes: string[] | undefined) {
|
|
60
|
+
return normalizeEnabledBillingTypes(enabledBillingTypes).join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildCreditOverviewUrl(
|
|
64
|
+
endpoint: string,
|
|
65
|
+
locale: string,
|
|
66
|
+
enabledBillingTypes: string[],
|
|
67
|
+
) {
|
|
36
68
|
const url = new URL(endpoint, window.location.origin);
|
|
37
69
|
url.searchParams.set('locale', locale);
|
|
70
|
+
for (const billingType of enabledBillingTypes) {
|
|
71
|
+
url.searchParams.append('enabledBillingTypes', billingType);
|
|
72
|
+
}
|
|
38
73
|
return url.toString();
|
|
39
74
|
}
|
|
40
75
|
|
|
@@ -66,6 +101,8 @@ export function dispatchCreditOverviewRefresh(
|
|
|
66
101
|
export function CreditOverviewNavClient({
|
|
67
102
|
locale,
|
|
68
103
|
endpoint,
|
|
104
|
+
enabledBillingTypes,
|
|
105
|
+
mobileBillingSwitchBehavior,
|
|
69
106
|
refreshEvents,
|
|
70
107
|
refreshDebounceMs = 300,
|
|
71
108
|
}: CreditOverviewNavClientProps) {
|
|
@@ -78,6 +115,11 @@ export function CreditOverviewNavClient({
|
|
|
78
115
|
() => refreshEventsKey.split('\n').filter(Boolean),
|
|
79
116
|
[refreshEventsKey],
|
|
80
117
|
);
|
|
118
|
+
const enabledBillingTypesKey = buildEnabledBillingTypesKey(enabledBillingTypes);
|
|
119
|
+
const normalizedEnabledBillingTypes = useMemo(
|
|
120
|
+
() => enabledBillingTypesKey.split('\n').filter(Boolean),
|
|
121
|
+
[enabledBillingTypesKey],
|
|
122
|
+
);
|
|
81
123
|
|
|
82
124
|
useEffect(() => {
|
|
83
125
|
isMountedRef.current = true;
|
|
@@ -95,7 +137,11 @@ export function CreditOverviewNavClient({
|
|
|
95
137
|
abortControllerRef.current = controller;
|
|
96
138
|
|
|
97
139
|
try {
|
|
98
|
-
const response = await fetch(buildCreditOverviewUrl(
|
|
140
|
+
const response = await fetch(buildCreditOverviewUrl(
|
|
141
|
+
endpoint,
|
|
142
|
+
locale,
|
|
143
|
+
normalizedEnabledBillingTypes,
|
|
144
|
+
), {
|
|
99
145
|
cache: 'no-store',
|
|
100
146
|
credentials: 'same-origin',
|
|
101
147
|
signal: controller.signal,
|
|
@@ -122,7 +168,7 @@ export function CreditOverviewNavClient({
|
|
|
122
168
|
abortControllerRef.current = null;
|
|
123
169
|
}
|
|
124
170
|
}
|
|
125
|
-
}, [endpoint, locale]);
|
|
171
|
+
}, [endpoint, locale, normalizedEnabledBillingTypes]);
|
|
126
172
|
|
|
127
173
|
useEffect(() => {
|
|
128
174
|
if (!isLoaded) {
|
|
@@ -180,6 +226,7 @@ export function CreditOverviewNavClient({
|
|
|
180
226
|
locale={locale}
|
|
181
227
|
totalBalance={payload.data.totalBalance}
|
|
182
228
|
totalLabel={payload.totalLabel}
|
|
229
|
+
mobileBillingSwitchBehavior={mobileBillingSwitchBehavior}
|
|
183
230
|
>
|
|
184
231
|
<CreditOverviewClient
|
|
185
232
|
locale={locale}
|
package/src/main/credit/index.ts
CHANGED
package/src/main/credit/types.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
export { MoneyPriceInteractive } from './money-price-interactive';
|
|
4
4
|
export { MoneyPriceButton } from './money-price-button';
|
|
5
|
+
export {
|
|
6
|
+
PricingModal,
|
|
7
|
+
PricingModalProvider,
|
|
8
|
+
usePricingModal,
|
|
9
|
+
} from './pricing-modal';
|
|
5
10
|
export type {
|
|
6
11
|
MoneyPriceConfig,
|
|
7
12
|
MoneyPriceData,
|
|
@@ -17,4 +22,9 @@ export type {
|
|
|
17
22
|
CreditPackProductConfig,
|
|
18
23
|
UserContext,
|
|
19
24
|
} from './money-price-types';
|
|
25
|
+
export type {
|
|
26
|
+
OpenPricingModalOptions,
|
|
27
|
+
PricingModalProps,
|
|
28
|
+
PricingModalProviderProps,
|
|
29
|
+
} from './pricing-modal';
|
|
20
30
|
export { UserState } from './money-price-types';
|