@windrun-huaiin/third-ui 31.3.5 → 31.3.7
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-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 +21 -1
- package/dist/main/credit/credit-overview-nav-client.js +109 -31
- package/dist/main/credit/credit-overview-nav-client.mjs +109 -33
- package/dist/main/credit/index.d.ts +1 -1
- package/dist/main/credit/index.js +2 -0
- package/dist/main/credit/index.mjs +1 -1
- package/package.json +3 -3
- package/src/main/credit/credit-overview-client.tsx +36 -25
- package/src/main/credit/credit-overview-nav-client.tsx +165 -29
- package/src/main/credit/index.ts +5 -1
|
@@ -17,7 +17,7 @@ const formatNumber = (locale, value) => new Intl.NumberFormat(locale, {
|
|
|
17
17
|
maximumFractionDigits: 0,
|
|
18
18
|
}).format(value);
|
|
19
19
|
function CreditOverviewClient({ data, locale, translations, className, expiringSoonThresholdDays = 7, customActionHandlers, }) {
|
|
20
|
-
var _a;
|
|
20
|
+
var _a, _b;
|
|
21
21
|
const { redirectToSignIn } = nextjs.useClerk();
|
|
22
22
|
const navPopover = creditNavButton.useCreditNavPopover();
|
|
23
23
|
const isMobile = (_a = navPopover === null || navPopover === void 0 ? void 0 : navPopover.isMobile) !== null && _a !== void 0 ? _a : false;
|
|
@@ -59,6 +59,9 @@ function CreditOverviewClient({ data, locale, translations, className, expiringS
|
|
|
59
59
|
const subscription = data.subscription;
|
|
60
60
|
const pricingContext = data.pricingContext;
|
|
61
61
|
const ctaBehaviors = data.ctaBehaviors;
|
|
62
|
+
const billingOptions = (_b = pricingContext === null || pricingContext === void 0 ? void 0 : pricingContext.moneyPriceData.billingSwitch.options) !== null && _b !== void 0 ? _b : [];
|
|
63
|
+
const subscriptionBillingEnabled = billingOptions.some((option) => option.key === 'monthly' || option.key === 'yearly');
|
|
64
|
+
const onetimeBillingEnabled = billingOptions.some((option) => option.key === 'onetime');
|
|
62
65
|
const getModalMoneyPriceData = React.useCallback((mode) => {
|
|
63
66
|
if (!pricingContext) {
|
|
64
67
|
return null;
|
|
@@ -232,10 +235,10 @@ function CreditOverviewClient({ data, locale, translations, className, expiringS
|
|
|
232
235
|
userToggledRef.current = true;
|
|
233
236
|
setBucketExpanded(true);
|
|
234
237
|
}, []);
|
|
235
|
-
return (jsxRuntime.jsxs("section", { className: utils.cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxRuntime.jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsxRuntime.jsx(icons.GemIcon, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsxRuntime.jsx("span", { className: "text-base sm:text-lg", children: translations.totalLabel })] }), jsxRuntime.jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), jsxRuntime.jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsxRuntime.jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsxRuntime.jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsxRuntime.jsx("div", { className: "pt-2 sm:pt-0", children: jsxRuntime.jsx(gradientButton.GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsxRuntime.jsx(icons.Settings2Icon, {}) : jsxRuntime.jsx(icons.BellIcon, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] }), jsxRuntime.jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsxRuntime.jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxRuntime.jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsxRuntime.jsx("h3", { className: "text-base text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsxRuntime.jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsxRuntime.jsx(icons.ChevronUpIcon, { className: "h-4 w-4" })) : (jsxRuntime.jsx(icons.ChevronDownIcon, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsxRuntime.jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
|
|
238
|
+
return (jsxRuntime.jsxs("section", { className: utils.cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxRuntime.jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsxRuntime.jsx(icons.GemIcon, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsxRuntime.jsx("span", { className: "text-base sm:text-lg", children: translations.totalLabel })] }), jsxRuntime.jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), subscriptionBillingEnabled ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsxRuntime.jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsxRuntime.jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsxRuntime.jsx("div", { className: "pt-2 sm:pt-0", children: jsxRuntime.jsx(gradientButton.GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsxRuntime.jsx(icons.Settings2Icon, {}) : jsxRuntime.jsx(icons.BellIcon, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] })) : null] }), jsxRuntime.jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsxRuntime.jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxRuntime.jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsxRuntime.jsx("h3", { className: "text-base text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsxRuntime.jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsxRuntime.jsx(icons.ChevronUpIcon, { className: "h-4 w-4" })) : (jsxRuntime.jsx(icons.ChevronDownIcon, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsxRuntime.jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
|
|
236
239
|
const balanceDisplay = formatNumber(locale, bucket.balance);
|
|
237
240
|
return (jsxRuntime.jsxs("li", { "data-credit-kind": bucket.kind, className: "rounded-2xl border border-slate-200/70 bg-white/85 px-3 py-3 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 sm:px-4", children: [jsxRuntime.jsxs("div", { className: "grid grid-cols-[1fr_auto] items-center gap-3 text-xs sm:text-sm", children: [jsxRuntime.jsx("span", { className: "flex min-w-0 items-center gap-2", children: jsxRuntime.jsx("span", { className: "max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm", children: bucket.computedLabel }) }), jsxRuntime.jsx("span", { className: "flex min-w-0 justify-end", children: jsxRuntime.jsx("span", { className: "text-right text-base font-semibold leading-tight text-gray-500 dark:text-slate-100 sm:text-lg", title: balanceDisplay, children: balanceDisplay }) })] }), jsxRuntime.jsx("div", { className: "mt-3 flex justify-end gap-2", children: jsxRuntime.jsxs("span", { className: "text-[11px] text-gray-500 dark:text-slate-100 sm:text-xs", children: [translations.expiredAtLabel, ": ", bucket.expiresAt] }) })] }, bucket.kind));
|
|
238
|
-
}) })) : (jsxRuntime.jsx("button", { type: "button", onClick: expandBuckets, className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 hover:text-purple-500", children: translations.expandDetail }))) : (jsxRuntime.jsx("div", { className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform dark:border-slate-800/60 dark:bg-slate-900/60 text-center", children: translations.bucketsEmpty })), jsxRuntime.jsx(gradientButton.GradientButton, { title: translations.onetimeBuy, icon: jsxRuntime.jsx(icons.ShoppingCartIcon, {}), align: "center", className: "w-full text-sm sm:text-base", onClick: handleOnetimeAction })] })] }));
|
|
241
|
+
}) })) : (jsxRuntime.jsx("button", { type: "button", onClick: expandBuckets, className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 hover:text-purple-500", children: translations.expandDetail }))) : (jsxRuntime.jsx("div", { className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform dark:border-slate-800/60 dark:bg-slate-900/60 text-center", children: translations.bucketsEmpty })), onetimeBillingEnabled ? (jsxRuntime.jsx(gradientButton.GradientButton, { title: translations.onetimeBuy, icon: jsxRuntime.jsx(icons.ShoppingCartIcon, {}), align: "center", className: "w-full text-sm sm:text-base", onClick: handleOnetimeAction })) : null] })] }));
|
|
239
242
|
}
|
|
240
243
|
function deriveStatus(expiresAt, thresholdDays = 7) {
|
|
241
244
|
if (!expiresAt) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { __awaiter } from 'tslib';
|
|
3
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { useClerk } from '@clerk/nextjs';
|
|
5
5
|
import { GradientButton } from '../buttons/gradient-button.mjs';
|
|
6
6
|
import { useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
|
|
@@ -15,7 +15,7 @@ const formatNumber = (locale, value) => new Intl.NumberFormat(locale, {
|
|
|
15
15
|
maximumFractionDigits: 0,
|
|
16
16
|
}).format(value);
|
|
17
17
|
function CreditOverviewClient({ data, locale, translations, className, expiringSoonThresholdDays = 7, customActionHandlers, }) {
|
|
18
|
-
var _a;
|
|
18
|
+
var _a, _b;
|
|
19
19
|
const { redirectToSignIn } = useClerk();
|
|
20
20
|
const navPopover = useCreditNavPopover();
|
|
21
21
|
const isMobile = (_a = navPopover === null || navPopover === void 0 ? void 0 : navPopover.isMobile) !== null && _a !== void 0 ? _a : false;
|
|
@@ -57,6 +57,9 @@ function CreditOverviewClient({ data, locale, translations, className, expiringS
|
|
|
57
57
|
const subscription = data.subscription;
|
|
58
58
|
const pricingContext = data.pricingContext;
|
|
59
59
|
const ctaBehaviors = data.ctaBehaviors;
|
|
60
|
+
const billingOptions = (_b = pricingContext === null || pricingContext === void 0 ? void 0 : pricingContext.moneyPriceData.billingSwitch.options) !== null && _b !== void 0 ? _b : [];
|
|
61
|
+
const subscriptionBillingEnabled = billingOptions.some((option) => option.key === 'monthly' || option.key === 'yearly');
|
|
62
|
+
const onetimeBillingEnabled = billingOptions.some((option) => option.key === 'onetime');
|
|
60
63
|
const getModalMoneyPriceData = useCallback((mode) => {
|
|
61
64
|
if (!pricingContext) {
|
|
62
65
|
return null;
|
|
@@ -230,10 +233,10 @@ function CreditOverviewClient({ data, locale, translations, className, expiringS
|
|
|
230
233
|
userToggledRef.current = true;
|
|
231
234
|
setBucketExpanded(true);
|
|
232
235
|
}, []);
|
|
233
|
-
return (jsxs("section", { className: cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsx(GemIcon, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsx("span", { className: "text-base sm:text-lg", children: translations.totalLabel })] }), jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsx("div", { className: "pt-2 sm:pt-0", children: jsx(GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsx(Settings2Icon, {}) : jsx(BellIcon, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] }), jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsx("h3", { className: "text-base text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsx(ChevronUpIcon, { className: "h-4 w-4" })) : (jsx(ChevronDownIcon, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
|
|
236
|
+
return (jsxs("section", { className: cn("flex flex-col gap-2 p-2 shadow-inner rounded-xl bg-white dark:bg-slate-900", className), children: [jsxs("header", { className: "relative rounded-2xl bg-linear-to-bl from-indigo-200/60 via-indigo-400/90 to-purple-200/50 p-4 shadow-inner dark:from-indigo-300/20 dark:via-slate-400 dark:to-slate-500/50 sm:p-6", children: [jsxs("div", { className: "flex flex-col gap-2 sm:gap-3", children: [jsxs("div", { className: "flex items-center justify-start rounded-full ", children: [jsx(GemIcon, { "aria-hidden": true, className: "mr-2 h-6 w-6 sm:h-8 sm:w-8" }), jsx("span", { className: "text-base sm:text-lg", children: translations.totalLabel })] }), jsx("div", { className: "flex justify-center text-3xl font-semibold leading-tight sm:text-4xl", children: formatNumber(locale, data.totalBalance) }), subscriptionBillingEnabled ? (jsxs(Fragment, { children: [jsxs("div", { className: "flex-1 flex-col gap-1", children: [jsx("p", { className: "text-xs text-gray-700 dark:text-slate-100 sm:text-sm", children: translations.subscriptionPeriodLabel }), jsx("h4", { className: "text-xl font-semibold sm:text-2xl", children: subscription ? subscription.planName : translations.subscriptionInactive })] }), jsx("div", { className: "pt-2 sm:pt-0", children: jsx(GradientButton, { title: subscription ? translations.subscriptionManage : translations.subscribePay, align: "center", icon: subscription ? jsx(Settings2Icon, {}) : jsx(BellIcon, {}), openInNewTab: false, className: "w-full", onClick: subscription ? handleManageAction : handleSubscribeAction }) })] })) : null] }), jsx("div", { className: "absolute right-3 top-3 sm:right-6 sm:top-6", children: jsx(HoverInfo, { label: translations.totalLabel, description: translations.summaryDescription }) })] }), jsxs("section", { className: "relative flex flex-col gap-3 rounded-2xl border p-4 shadow-inner sm:gap-2 sm:p-5", children: [jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [jsx("h3", { className: "text-base text-gray-500 dark:text-slate-100 sm:text-lg", children: translations.bucketsTitle }), hasBuckets ? (jsx("button", { type: "button", "aria-expanded": bucketExpanded, "aria-label": bucketExpanded ? translations.hiddenDetail : translations.expandDetail, onClick: toggleBucketExpanded, className: "flex h-7 w-7 items-center justify-center rounded-full border border-transparent bg-white text-purple-600 shadow-[0_6px_20px_rgba(99,102,241,0.25)] transition-colors hover:text-purple-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 dark:bg-[#1b1541] dark:text-purple-100 dark:hover:text-purple-50 dark:shadow-[0_6px_22px_rgba(112,86,255,0.35)]", children: bucketExpanded ? (jsx(ChevronUpIcon, { className: "h-4 w-4" })) : (jsx(ChevronDownIcon, { className: "h-4 w-4" })) })) : null] }), hasBuckets ? (bucketExpanded ? (jsx("ul", { className: "flex flex-col gap-2", children: buckets.map((bucket) => {
|
|
234
237
|
const balanceDisplay = formatNumber(locale, bucket.balance);
|
|
235
238
|
return (jsxs("li", { "data-credit-kind": bucket.kind, className: "rounded-2xl border border-slate-200/70 bg-white/85 px-3 py-3 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 sm:px-4", children: [jsxs("div", { className: "grid grid-cols-[1fr_auto] items-center gap-3 text-xs sm:text-sm", children: [jsx("span", { className: "flex min-w-0 items-center gap-2", children: jsx("span", { className: "max-w-full truncate rounded-full bg-purple-50 px-2 py-1 text-xs text-purple-600 shadow-sm dark:bg-purple-500/20 dark:text-purple-100 sm:text-sm", children: bucket.computedLabel }) }), jsx("span", { className: "flex min-w-0 justify-end", children: jsx("span", { className: "text-right text-base font-semibold leading-tight text-gray-500 dark:text-slate-100 sm:text-lg", title: balanceDisplay, children: balanceDisplay }) })] }), jsx("div", { className: "mt-3 flex justify-end gap-2", children: jsxs("span", { className: "text-[11px] text-gray-500 dark:text-slate-100 sm:text-xs", children: [translations.expiredAtLabel, ": ", bucket.expiresAt] }) })] }, bucket.kind));
|
|
236
|
-
}) })) : (jsx("button", { type: "button", onClick: expandBuckets, className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 hover:text-purple-500", children: translations.expandDetail }))) : (jsx("div", { className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform dark:border-slate-800/60 dark:bg-slate-900/60 text-center", children: translations.bucketsEmpty })), jsx(GradientButton, { title: translations.onetimeBuy, icon: jsx(ShoppingCartIcon, {}), align: "center", className: "w-full text-sm sm:text-base", onClick: handleOnetimeAction })] })] }));
|
|
239
|
+
}) })) : (jsx("button", { type: "button", onClick: expandBuckets, className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60 hover:text-purple-500", children: translations.expandDetail }))) : (jsx("div", { className: "w-full rounded-2xl border border-slate-200/70 bg-white/85 p-6 sm:px-4 text-sm shadow-sm transition-transform dark:border-slate-800/60 dark:bg-slate-900/60 text-center", children: translations.bucketsEmpty })), onetimeBillingEnabled ? (jsx(GradientButton, { title: translations.onetimeBuy, icon: jsx(ShoppingCartIcon, {}), align: "center", className: "w-full text-sm sm:text-base", onClick: handleOnetimeAction })) : null] })] }));
|
|
237
240
|
}
|
|
238
241
|
function deriveStatus(expiresAt, thresholdDays = 7) {
|
|
239
242
|
if (!expiresAt) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CreditOverviewTranslations } from './credit-overview-client';
|
|
2
2
|
import type { CreditOverviewData } from './types';
|
|
3
|
+
export declare const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = "windrun-huaiin:credit-overview:refresh";
|
|
3
4
|
export interface CreditOverviewPayload {
|
|
4
5
|
data: CreditOverviewData;
|
|
5
6
|
totalLabel: string;
|
|
@@ -8,5 +9,24 @@ export interface CreditOverviewPayload {
|
|
|
8
9
|
export interface CreditOverviewNavClientProps {
|
|
9
10
|
locale: string;
|
|
10
11
|
endpoint: string;
|
|
12
|
+
/**
|
|
13
|
+
* Billing types enabled by the business app.
|
|
14
|
+
* Defaults are handled by the API/payload builder when omitted.
|
|
15
|
+
*/
|
|
16
|
+
enabledBillingTypes?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Browser event names that should trigger credit overview reload.
|
|
19
|
+
* Business apps can dispatch these events after credit balance changes.
|
|
20
|
+
*
|
|
21
|
+
* Default: ['windrun:credit-overview:refresh']
|
|
22
|
+
* Empty array means event listening is disabled.
|
|
23
|
+
*/
|
|
24
|
+
refreshEvents?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Optional debounce for refresh event bursts.
|
|
27
|
+
* Default: 300ms.
|
|
28
|
+
*/
|
|
29
|
+
refreshDebounceMs?: number;
|
|
11
30
|
}
|
|
12
|
-
export declare function
|
|
31
|
+
export declare function dispatchCreditOverviewRefresh(eventName?: string): void;
|
|
32
|
+
export declare function CreditOverviewNavClient({ locale, endpoint, enabledBillingTypes, refreshEvents, refreshDebounceMs, }: CreditOverviewNavClientProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -8,54 +8,130 @@ var React = require('react');
|
|
|
8
8
|
var creditNavButton = require('./credit-nav-button.js');
|
|
9
9
|
var creditOverviewClient = require('./credit-overview-client.js');
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = 'windrun-huaiin:credit-overview:refresh';
|
|
12
|
+
function normalizeEnabledBillingTypes(enabledBillingTypes) {
|
|
13
|
+
if (!(enabledBillingTypes === null || enabledBillingTypes === void 0 ? void 0 : enabledBillingTypes.length)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return Array.from(new Set(enabledBillingTypes
|
|
17
|
+
.map((billingType) => billingType.trim())
|
|
18
|
+
.filter((billingType) => billingType.length > 0)));
|
|
19
|
+
}
|
|
20
|
+
function buildEnabledBillingTypesKey(enabledBillingTypes) {
|
|
21
|
+
return normalizeEnabledBillingTypes(enabledBillingTypes).join('\n');
|
|
22
|
+
}
|
|
23
|
+
function buildCreditOverviewUrl(endpoint, locale, enabledBillingTypes) {
|
|
12
24
|
const url = new URL(endpoint, window.location.origin);
|
|
13
25
|
url.searchParams.set('locale', locale);
|
|
26
|
+
for (const billingType of enabledBillingTypes) {
|
|
27
|
+
url.searchParams.append('enabledBillingTypes', billingType);
|
|
28
|
+
}
|
|
14
29
|
return url.toString();
|
|
15
30
|
}
|
|
16
|
-
function
|
|
31
|
+
function normalizeRefreshEvents(refreshEvents) {
|
|
32
|
+
const events = refreshEvents !== null && refreshEvents !== void 0 ? refreshEvents : [DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT];
|
|
33
|
+
return Array.from(new Set(events
|
|
34
|
+
.map((eventName) => eventName.trim())
|
|
35
|
+
.filter((eventName) => eventName.length > 0)));
|
|
36
|
+
}
|
|
37
|
+
function buildRefreshEventsKey(refreshEvents) {
|
|
38
|
+
return normalizeRefreshEvents(refreshEvents).join('\n');
|
|
39
|
+
}
|
|
40
|
+
function dispatchCreditOverviewRefresh(eventName = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT) {
|
|
41
|
+
if (typeof window === 'undefined') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
window.dispatchEvent(new CustomEvent(eventName));
|
|
45
|
+
}
|
|
46
|
+
function CreditOverviewNavClient({ locale, endpoint, enabledBillingTypes, refreshEvents, refreshDebounceMs = 300, }) {
|
|
17
47
|
const { isLoaded, isSignedIn, userId } = nextjs.useAuth();
|
|
18
48
|
const [payload, setPayload] = React.useState(null);
|
|
49
|
+
const abortControllerRef = React.useRef(null);
|
|
50
|
+
const isMountedRef = React.useRef(false);
|
|
51
|
+
const refreshEventsKey = buildRefreshEventsKey(refreshEvents);
|
|
52
|
+
const normalizedRefreshEvents = React.useMemo(() => refreshEventsKey.split('\n').filter(Boolean), [refreshEventsKey]);
|
|
53
|
+
const enabledBillingTypesKey = buildEnabledBillingTypesKey(enabledBillingTypes);
|
|
54
|
+
const normalizedEnabledBillingTypes = React.useMemo(() => enabledBillingTypesKey.split('\n').filter(Boolean), [enabledBillingTypesKey]);
|
|
19
55
|
React.useEffect(() => {
|
|
56
|
+
isMountedRef.current = true;
|
|
57
|
+
return () => {
|
|
58
|
+
var _a;
|
|
59
|
+
isMountedRef.current = false;
|
|
60
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
61
|
+
};
|
|
62
|
+
}, []);
|
|
63
|
+
const loadCreditOverview = React.useCallback(() => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
var _a;
|
|
65
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
abortControllerRef.current = controller;
|
|
68
|
+
try {
|
|
69
|
+
const response = yield fetch(buildCreditOverviewUrl(endpoint, locale, normalizedEnabledBillingTypes), {
|
|
70
|
+
cache: 'no-store',
|
|
71
|
+
credentials: 'same-origin',
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
76
|
+
setPayload(null);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const nextPayload = (yield response.json());
|
|
81
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
82
|
+
setPayload(nextPayload);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
87
|
+
setPayload(null);
|
|
88
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
if (abortControllerRef.current === controller) {
|
|
93
|
+
abortControllerRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}), [endpoint, locale, normalizedEnabledBillingTypes]);
|
|
97
|
+
React.useEffect(() => {
|
|
98
|
+
var _a;
|
|
20
99
|
if (!isLoaded) {
|
|
21
100
|
return;
|
|
22
101
|
}
|
|
23
102
|
if (!isSignedIn) {
|
|
103
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
104
|
+
abortControllerRef.current = null;
|
|
24
105
|
setPayload(null);
|
|
25
106
|
return;
|
|
26
107
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
if (!controller.signal.aborted) {
|
|
48
|
-
setPayload(null);
|
|
49
|
-
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
});
|
|
108
|
+
void loadCreditOverview();
|
|
109
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, userId]);
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
if (!isLoaded || !isSignedIn || normalizedRefreshEvents.length === 0) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
let debounceTimer = null;
|
|
115
|
+
const refresh = () => {
|
|
116
|
+
if (debounceTimer) {
|
|
117
|
+
window.clearTimeout(debounceTimer);
|
|
118
|
+
}
|
|
119
|
+
debounceTimer = window.setTimeout(() => {
|
|
120
|
+
void loadCreditOverview();
|
|
121
|
+
}, refreshDebounceMs);
|
|
122
|
+
};
|
|
123
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
124
|
+
window.addEventListener(eventName, refresh);
|
|
53
125
|
}
|
|
54
|
-
loadCreditOverview();
|
|
55
126
|
return () => {
|
|
56
|
-
|
|
127
|
+
if (debounceTimer) {
|
|
128
|
+
window.clearTimeout(debounceTimer);
|
|
129
|
+
}
|
|
130
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
131
|
+
window.removeEventListener(eventName, refresh);
|
|
132
|
+
}
|
|
57
133
|
};
|
|
58
|
-
}, [
|
|
134
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, normalizedRefreshEvents, refreshDebounceMs]);
|
|
59
135
|
if (!payload) {
|
|
60
136
|
return null;
|
|
61
137
|
}
|
|
@@ -63,3 +139,5 @@ function CreditOverviewNavClient({ locale, endpoint, }) {
|
|
|
63
139
|
}
|
|
64
140
|
|
|
65
141
|
exports.CreditOverviewNavClient = CreditOverviewNavClient;
|
|
142
|
+
exports.DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT;
|
|
143
|
+
exports.dispatchCreditOverviewRefresh = dispatchCreditOverviewRefresh;
|
|
@@ -2,62 +2,138 @@
|
|
|
2
2
|
import { __awaiter } from 'tslib';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
import { useAuth } from '@clerk/nextjs';
|
|
5
|
-
import { useState, useEffect } from 'react';
|
|
5
|
+
import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
|
|
6
6
|
import { CreditNavButton } from './credit-nav-button.mjs';
|
|
7
7
|
import { CreditOverviewClient } from './credit-overview-client.mjs';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = 'windrun-huaiin:credit-overview:refresh';
|
|
10
|
+
function normalizeEnabledBillingTypes(enabledBillingTypes) {
|
|
11
|
+
if (!(enabledBillingTypes === null || enabledBillingTypes === void 0 ? void 0 : enabledBillingTypes.length)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return Array.from(new Set(enabledBillingTypes
|
|
15
|
+
.map((billingType) => billingType.trim())
|
|
16
|
+
.filter((billingType) => billingType.length > 0)));
|
|
17
|
+
}
|
|
18
|
+
function buildEnabledBillingTypesKey(enabledBillingTypes) {
|
|
19
|
+
return normalizeEnabledBillingTypes(enabledBillingTypes).join('\n');
|
|
20
|
+
}
|
|
21
|
+
function buildCreditOverviewUrl(endpoint, locale, enabledBillingTypes) {
|
|
10
22
|
const url = new URL(endpoint, window.location.origin);
|
|
11
23
|
url.searchParams.set('locale', locale);
|
|
24
|
+
for (const billingType of enabledBillingTypes) {
|
|
25
|
+
url.searchParams.append('enabledBillingTypes', billingType);
|
|
26
|
+
}
|
|
12
27
|
return url.toString();
|
|
13
28
|
}
|
|
14
|
-
function
|
|
29
|
+
function normalizeRefreshEvents(refreshEvents) {
|
|
30
|
+
const events = refreshEvents !== null && refreshEvents !== void 0 ? refreshEvents : [DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT];
|
|
31
|
+
return Array.from(new Set(events
|
|
32
|
+
.map((eventName) => eventName.trim())
|
|
33
|
+
.filter((eventName) => eventName.length > 0)));
|
|
34
|
+
}
|
|
35
|
+
function buildRefreshEventsKey(refreshEvents) {
|
|
36
|
+
return normalizeRefreshEvents(refreshEvents).join('\n');
|
|
37
|
+
}
|
|
38
|
+
function dispatchCreditOverviewRefresh(eventName = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT) {
|
|
39
|
+
if (typeof window === 'undefined') {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
window.dispatchEvent(new CustomEvent(eventName));
|
|
43
|
+
}
|
|
44
|
+
function CreditOverviewNavClient({ locale, endpoint, enabledBillingTypes, refreshEvents, refreshDebounceMs = 300, }) {
|
|
15
45
|
const { isLoaded, isSignedIn, userId } = useAuth();
|
|
16
46
|
const [payload, setPayload] = useState(null);
|
|
47
|
+
const abortControllerRef = useRef(null);
|
|
48
|
+
const isMountedRef = useRef(false);
|
|
49
|
+
const refreshEventsKey = buildRefreshEventsKey(refreshEvents);
|
|
50
|
+
const normalizedRefreshEvents = useMemo(() => refreshEventsKey.split('\n').filter(Boolean), [refreshEventsKey]);
|
|
51
|
+
const enabledBillingTypesKey = buildEnabledBillingTypesKey(enabledBillingTypes);
|
|
52
|
+
const normalizedEnabledBillingTypes = useMemo(() => enabledBillingTypesKey.split('\n').filter(Boolean), [enabledBillingTypesKey]);
|
|
17
53
|
useEffect(() => {
|
|
54
|
+
isMountedRef.current = true;
|
|
55
|
+
return () => {
|
|
56
|
+
var _a;
|
|
57
|
+
isMountedRef.current = false;
|
|
58
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
const loadCreditOverview = useCallback(() => __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
var _a;
|
|
63
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
abortControllerRef.current = controller;
|
|
66
|
+
try {
|
|
67
|
+
const response = yield fetch(buildCreditOverviewUrl(endpoint, locale, normalizedEnabledBillingTypes), {
|
|
68
|
+
cache: 'no-store',
|
|
69
|
+
credentials: 'same-origin',
|
|
70
|
+
signal: controller.signal,
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
74
|
+
setPayload(null);
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const nextPayload = (yield response.json());
|
|
79
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
80
|
+
setPayload(nextPayload);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
85
|
+
setPayload(null);
|
|
86
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
if (abortControllerRef.current === controller) {
|
|
91
|
+
abortControllerRef.current = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}), [endpoint, locale, normalizedEnabledBillingTypes]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
var _a;
|
|
18
97
|
if (!isLoaded) {
|
|
19
98
|
return;
|
|
20
99
|
}
|
|
21
100
|
if (!isSignedIn) {
|
|
101
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
102
|
+
abortControllerRef.current = null;
|
|
22
103
|
setPayload(null);
|
|
23
104
|
return;
|
|
24
105
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
if (!controller.signal.aborted) {
|
|
46
|
-
setPayload(null);
|
|
47
|
-
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
106
|
+
void loadCreditOverview();
|
|
107
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, userId]);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!isLoaded || !isSignedIn || normalizedRefreshEvents.length === 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let debounceTimer = null;
|
|
113
|
+
const refresh = () => {
|
|
114
|
+
if (debounceTimer) {
|
|
115
|
+
window.clearTimeout(debounceTimer);
|
|
116
|
+
}
|
|
117
|
+
debounceTimer = window.setTimeout(() => {
|
|
118
|
+
void loadCreditOverview();
|
|
119
|
+
}, refreshDebounceMs);
|
|
120
|
+
};
|
|
121
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
122
|
+
window.addEventListener(eventName, refresh);
|
|
51
123
|
}
|
|
52
|
-
loadCreditOverview();
|
|
53
124
|
return () => {
|
|
54
|
-
|
|
125
|
+
if (debounceTimer) {
|
|
126
|
+
window.clearTimeout(debounceTimer);
|
|
127
|
+
}
|
|
128
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
129
|
+
window.removeEventListener(eventName, refresh);
|
|
130
|
+
}
|
|
55
131
|
};
|
|
56
|
-
}, [
|
|
132
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, normalizedRefreshEvents, refreshDebounceMs]);
|
|
57
133
|
if (!payload) {
|
|
58
134
|
return null;
|
|
59
135
|
}
|
|
60
136
|
return (jsx(CreditNavButton, { locale: locale, totalBalance: payload.data.totalBalance, totalLabel: payload.totalLabel, children: jsx(CreditOverviewClient, { locale: locale, data: payload.data, translations: payload.translations }) }));
|
|
61
137
|
}
|
|
62
138
|
|
|
63
|
-
export { CreditOverviewNavClient };
|
|
139
|
+
export { CreditOverviewNavClient, DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT, dispatchCreditOverviewRefresh };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { CreditOverviewClient } from './credit-overview-client';
|
|
2
|
-
export { CreditOverviewNavClient } from './credit-overview-nav-client';
|
|
2
|
+
export { CreditOverviewNavClient, DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT, dispatchCreditOverviewRefresh, } from './credit-overview-nav-client';
|
|
3
3
|
export { CreditNavButton } from './credit-nav-button';
|
|
4
4
|
export type { CreditOverviewTranslations } from './credit-overview-client';
|
|
5
5
|
export type { CreditOverviewNavClientProps, CreditOverviewPayload, } from './credit-overview-nav-client';
|
|
@@ -9,4 +9,6 @@ var creditNavButton = require('./credit-nav-button.js');
|
|
|
9
9
|
|
|
10
10
|
exports.CreditOverviewClient = creditOverviewClient.CreditOverviewClient;
|
|
11
11
|
exports.CreditOverviewNavClient = creditOverviewNavClient.CreditOverviewNavClient;
|
|
12
|
+
exports.DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = creditOverviewNavClient.DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT;
|
|
13
|
+
exports.dispatchCreditOverviewRefresh = creditOverviewNavClient.dispatchCreditOverviewRefresh;
|
|
12
14
|
exports.CreditNavButton = creditNavButton.CreditNavButton;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
export { CreditOverviewClient } from './credit-overview-client.mjs';
|
|
3
|
-
export { CreditOverviewNavClient } from './credit-overview-nav-client.mjs';
|
|
3
|
+
export { CreditOverviewNavClient, DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT, dispatchCreditOverviewRefresh } from './credit-overview-nav-client.mjs';
|
|
4
4
|
export { CreditNavButton } from './credit-nav-button.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "31.3.
|
|
3
|
+
"version": "31.3.7",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./clerk": {
|
|
@@ -243,9 +243,9 @@
|
|
|
243
243
|
"tslib": "^2.8.1",
|
|
244
244
|
"unified": "^11.0.5",
|
|
245
245
|
"zod": "^4.3.6",
|
|
246
|
-
"@windrun-huaiin/base-ui": "^31.0.1",
|
|
247
246
|
"@windrun-huaiin/contracts": "^31.0.0",
|
|
248
|
-
"@windrun-huaiin/lib": "^31.0.3"
|
|
247
|
+
"@windrun-huaiin/lib": "^31.0.3",
|
|
248
|
+
"@windrun-huaiin/base-ui": "^31.0.1"
|
|
249
249
|
},
|
|
250
250
|
"peerDependencies": {
|
|
251
251
|
"clsx": "^2.1.1",
|
|
@@ -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
|
);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useAuth } from '@clerk/nextjs';
|
|
4
|
-
import { useEffect, useState } from 'react';
|
|
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
7
|
import type { CreditOverviewData } from './types';
|
|
8
8
|
|
|
9
|
+
export const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = 'windrun-huaiin:credit-overview:refresh';
|
|
10
|
+
|
|
9
11
|
export interface CreditOverviewPayload {
|
|
10
12
|
data: CreditOverviewData;
|
|
11
13
|
totalLabel: string;
|
|
@@ -15,20 +17,152 @@ export interface CreditOverviewPayload {
|
|
|
15
17
|
export interface CreditOverviewNavClientProps {
|
|
16
18
|
locale: string;
|
|
17
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
|
+
* Browser event names that should trigger credit overview reload.
|
|
27
|
+
* Business apps can dispatch these events after credit balance changes.
|
|
28
|
+
*
|
|
29
|
+
* Default: ['windrun:credit-overview:refresh']
|
|
30
|
+
* Empty array means event listening is disabled.
|
|
31
|
+
*/
|
|
32
|
+
refreshEvents?: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Optional debounce for refresh event bursts.
|
|
35
|
+
* Default: 300ms.
|
|
36
|
+
*/
|
|
37
|
+
refreshDebounceMs?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeEnabledBillingTypes(enabledBillingTypes: string[] | undefined) {
|
|
41
|
+
if (!enabledBillingTypes?.length) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return Array.from(
|
|
46
|
+
new Set(
|
|
47
|
+
enabledBillingTypes
|
|
48
|
+
.map((billingType) => billingType.trim())
|
|
49
|
+
.filter((billingType) => billingType.length > 0),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildEnabledBillingTypesKey(enabledBillingTypes: string[] | undefined) {
|
|
55
|
+
return normalizeEnabledBillingTypes(enabledBillingTypes).join('\n');
|
|
18
56
|
}
|
|
19
57
|
|
|
20
|
-
function buildCreditOverviewUrl(
|
|
58
|
+
function buildCreditOverviewUrl(
|
|
59
|
+
endpoint: string,
|
|
60
|
+
locale: string,
|
|
61
|
+
enabledBillingTypes: string[],
|
|
62
|
+
) {
|
|
21
63
|
const url = new URL(endpoint, window.location.origin);
|
|
22
64
|
url.searchParams.set('locale', locale);
|
|
65
|
+
for (const billingType of enabledBillingTypes) {
|
|
66
|
+
url.searchParams.append('enabledBillingTypes', billingType);
|
|
67
|
+
}
|
|
23
68
|
return url.toString();
|
|
24
69
|
}
|
|
25
70
|
|
|
71
|
+
function normalizeRefreshEvents(refreshEvents: string[] | undefined) {
|
|
72
|
+
const events = refreshEvents ?? [DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT];
|
|
73
|
+
return Array.from(
|
|
74
|
+
new Set(
|
|
75
|
+
events
|
|
76
|
+
.map((eventName) => eventName.trim())
|
|
77
|
+
.filter((eventName) => eventName.length > 0),
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildRefreshEventsKey(refreshEvents: string[] | undefined) {
|
|
83
|
+
return normalizeRefreshEvents(refreshEvents).join('\n');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function dispatchCreditOverviewRefresh(
|
|
87
|
+
eventName = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT,
|
|
88
|
+
) {
|
|
89
|
+
if (typeof window === 'undefined') {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
window.dispatchEvent(new CustomEvent(eventName));
|
|
94
|
+
}
|
|
95
|
+
|
|
26
96
|
export function CreditOverviewNavClient({
|
|
27
97
|
locale,
|
|
28
98
|
endpoint,
|
|
99
|
+
enabledBillingTypes,
|
|
100
|
+
refreshEvents,
|
|
101
|
+
refreshDebounceMs = 300,
|
|
29
102
|
}: CreditOverviewNavClientProps) {
|
|
30
103
|
const { isLoaded, isSignedIn, userId } = useAuth();
|
|
31
104
|
const [payload, setPayload] = useState<CreditOverviewPayload | null>(null);
|
|
105
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
106
|
+
const isMountedRef = useRef(false);
|
|
107
|
+
const refreshEventsKey = buildRefreshEventsKey(refreshEvents);
|
|
108
|
+
const normalizedRefreshEvents = useMemo(
|
|
109
|
+
() => refreshEventsKey.split('\n').filter(Boolean),
|
|
110
|
+
[refreshEventsKey],
|
|
111
|
+
);
|
|
112
|
+
const enabledBillingTypesKey = buildEnabledBillingTypesKey(enabledBillingTypes);
|
|
113
|
+
const normalizedEnabledBillingTypes = useMemo(
|
|
114
|
+
() => enabledBillingTypesKey.split('\n').filter(Boolean),
|
|
115
|
+
[enabledBillingTypesKey],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
isMountedRef.current = true;
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
isMountedRef.current = false;
|
|
123
|
+
abortControllerRef.current?.abort();
|
|
124
|
+
};
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
const loadCreditOverview = useCallback(async () => {
|
|
128
|
+
abortControllerRef.current?.abort();
|
|
129
|
+
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
abortControllerRef.current = controller;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const response = await fetch(buildCreditOverviewUrl(
|
|
135
|
+
endpoint,
|
|
136
|
+
locale,
|
|
137
|
+
normalizedEnabledBillingTypes,
|
|
138
|
+
), {
|
|
139
|
+
cache: 'no-store',
|
|
140
|
+
credentials: 'same-origin',
|
|
141
|
+
signal: controller.signal,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
146
|
+
setPayload(null);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const nextPayload = (await response.json()) as CreditOverviewPayload | null;
|
|
152
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
153
|
+
setPayload(nextPayload);
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
157
|
+
setPayload(null);
|
|
158
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
159
|
+
}
|
|
160
|
+
} finally {
|
|
161
|
+
if (abortControllerRef.current === controller) {
|
|
162
|
+
abortControllerRef.current = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}, [endpoint, locale, normalizedEnabledBillingTypes]);
|
|
32
166
|
|
|
33
167
|
useEffect(() => {
|
|
34
168
|
if (!isLoaded) {
|
|
@@ -36,44 +170,46 @@ export function CreditOverviewNavClient({
|
|
|
36
170
|
}
|
|
37
171
|
|
|
38
172
|
if (!isSignedIn) {
|
|
173
|
+
abortControllerRef.current?.abort();
|
|
174
|
+
abortControllerRef.current = null;
|
|
39
175
|
setPayload(null);
|
|
40
176
|
return;
|
|
41
177
|
}
|
|
42
178
|
|
|
43
|
-
|
|
179
|
+
void loadCreditOverview();
|
|
180
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, userId]);
|
|
44
181
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
signal: controller.signal,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
if (!controller.signal.aborted) {
|
|
54
|
-
setPayload(null);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (!isLoaded || !isSignedIn || normalizedRefreshEvents.length === 0) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
58
186
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!controller.signal.aborted) {
|
|
65
|
-
setPayload(null);
|
|
66
|
-
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
67
|
-
}
|
|
187
|
+
let debounceTimer: number | null = null;
|
|
188
|
+
|
|
189
|
+
const refresh = () => {
|
|
190
|
+
if (debounceTimer) {
|
|
191
|
+
window.clearTimeout(debounceTimer);
|
|
68
192
|
}
|
|
69
|
-
}
|
|
70
193
|
|
|
71
|
-
|
|
194
|
+
debounceTimer = window.setTimeout(() => {
|
|
195
|
+
void loadCreditOverview();
|
|
196
|
+
}, refreshDebounceMs);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
200
|
+
window.addEventListener(eventName, refresh);
|
|
201
|
+
}
|
|
72
202
|
|
|
73
203
|
return () => {
|
|
74
|
-
|
|
204
|
+
if (debounceTimer) {
|
|
205
|
+
window.clearTimeout(debounceTimer);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
209
|
+
window.removeEventListener(eventName, refresh);
|
|
210
|
+
}
|
|
75
211
|
};
|
|
76
|
-
}, [
|
|
212
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, normalizedRefreshEvents, refreshDebounceMs]);
|
|
77
213
|
|
|
78
214
|
if (!payload) {
|
|
79
215
|
return null;
|
package/src/main/credit/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
export { CreditOverviewClient } from './credit-overview-client';
|
|
4
|
-
export {
|
|
4
|
+
export {
|
|
5
|
+
CreditOverviewNavClient,
|
|
6
|
+
DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT,
|
|
7
|
+
dispatchCreditOverviewRefresh,
|
|
8
|
+
} from './credit-overview-nav-client';
|
|
5
9
|
export { CreditNavButton } from './credit-nav-button';
|
|
6
10
|
export type { CreditOverviewTranslations } from './credit-overview-client';
|
|
7
11
|
export type {
|