@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.
Files changed (34) hide show
  1. package/dist/main/credit/credit-nav-button.d.ts +3 -2
  2. package/dist/main/credit/credit-nav-button.js +7 -9
  3. package/dist/main/credit/credit-nav-button.mjs +5 -7
  4. package/dist/main/credit/credit-overview-client.js +6 -3
  5. package/dist/main/credit/credit-overview-client.mjs +7 -4
  6. package/dist/main/credit/credit-overview-nav-client.d.ts +12 -2
  7. package/dist/main/credit/credit-overview-nav-client.js +21 -5
  8. package/dist/main/credit/credit-overview-nav-client.mjs +21 -5
  9. package/dist/main/credit/index.d.ts +1 -1
  10. package/dist/main/credit/types.d.ts +1 -0
  11. package/dist/main/money-price/index.d.ts +2 -0
  12. package/dist/main/money-price/index.js +4 -0
  13. package/dist/main/money-price/index.mjs +1 -0
  14. package/dist/main/money-price/money-price-interactive.d.ts +1 -1
  15. package/dist/main/money-price/money-price-interactive.js +7 -5
  16. package/dist/main/money-price/money-price-interactive.mjs +7 -5
  17. package/dist/main/money-price/money-price-types.d.ts +2 -0
  18. package/dist/main/money-price/money-price.d.ts +1 -1
  19. package/dist/main/money-price/money-price.js +2 -2
  20. package/dist/main/money-price/money-price.mjs +2 -2
  21. package/dist/main/money-price/pricing-modal.d.ts +33 -0
  22. package/dist/main/money-price/pricing-modal.js +109 -0
  23. package/dist/main/money-price/pricing-modal.mjs +105 -0
  24. package/package.json +3 -3
  25. package/src/main/credit/credit-nav-button.tsx +15 -56
  26. package/src/main/credit/credit-overview-client.tsx +36 -25
  27. package/src/main/credit/credit-overview-nav-client.tsx +51 -4
  28. package/src/main/credit/index.ts +1 -0
  29. package/src/main/credit/types.ts +1 -0
  30. package/src/main/money-price/index.ts +10 -0
  31. package/src/main/money-price/money-price-interactive.tsx +41 -31
  32. package/src/main/money-price/money-price-types.ts +2 -0
  33. package/src/main/money-price/money-price.tsx +2 -0
  34. 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.6",
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/contracts": "^31.0.0",
248
- "@windrun-huaiin/lib": "^31.0.3"
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, XIcon } from '@windrun-huaiin/base-ui/icons';
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 { dialogThemedOverlayClass } from '../alert-dialog/dialog-styles';
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
- <AlertDialog
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
- <AlertDialogContent
256
- className={cn(
257
- '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',
258
- themeMainBgColor,
259
- )}
260
- overlayClassName={dialogThemedOverlayClass}
261
- >
262
- <AlertDialogHeader className="flex flex-row items-center justify-between border-b border-slate-200 px-6 pt-4 pb-1 dark:border-slate-800">
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
- <div className="flex-1 flex-col gap-1">
387
- <p className="text-xs text-gray-700 dark:text-slate-100 sm:text-sm">
388
- {translations.subscriptionPeriodLabel}
389
- </p>
390
- <h4 className="text-xl font-semibold sm:text-2xl">
391
- {subscription ? subscription.planName : translations.subscriptionInactive}
392
- </h4>
393
- </div>
394
- <div className="pt-2 sm:pt-0">
395
- <GradientButton
396
- title={subscription ? translations.subscriptionManage : translations.subscribePay}
397
- align="center"
398
- icon={subscription ? <Settings2Icon /> : <BellIcon />}
399
- openInNewTab={false}
400
- className="w-full"
401
- onClick={subscription ? handleManageAction : handleSubscribeAction}
402
- />
403
- </div>
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
- <GradientButton
486
- title={translations.onetimeBuy}
487
- icon={<ShoppingCartIcon />}
488
- align="center"
489
- className="w-full text-sm sm:text-base"
490
- onClick={handleOnetimeAction}
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 buildCreditOverviewUrl(endpoint: string, locale: string) {
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(endpoint, locale), {
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}
@@ -16,5 +16,6 @@ export type {
16
16
  CreditOverviewData,
17
17
  CreditBucket,
18
18
  CreditBucketStatus,
19
+ MobileBillingSwitchBehavior,
19
20
  SubscriptionInfo,
20
21
  } from './types';
@@ -5,6 +5,7 @@ import type {
5
5
  } from '../money-price/money-price-types';
6
6
 
7
7
  export type PricingModalMode = 'subscription' | 'onetime';
8
+ export type MobileBillingSwitchBehavior = 'sticky' | 'static';
8
9
 
9
10
  export type CreditActionConfig =
10
11
  | CreditModalAction
@@ -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';