@windrun-huaiin/third-ui 7.3.4 → 7.3.5

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/faq.js CHANGED
@@ -14,7 +14,6 @@ var faqInteractive = require('./faq-interactive.js');
14
14
  require('next/navigation');
15
15
  require('@clerk/nextjs');
16
16
  require('../clerk/fingerprint/fingerprint-provider.js');
17
- require('react-dom/client');
18
17
  require('next-themes');
19
18
  require('fumadocs-core/framework');
20
19
  require('next/link');
package/dist/main/faq.mjs CHANGED
@@ -12,7 +12,6 @@ import { FAQInteractive } from './faq-interactive.mjs';
12
12
  import 'next/navigation';
13
13
  import '@clerk/nextjs';
14
14
  import '../clerk/fingerprint/fingerprint-provider.mjs';
15
- import 'react-dom/client';
16
15
  import 'next-themes';
17
16
  import 'fumadocs-core/framework';
18
17
  import 'next/link';
@@ -14,7 +14,6 @@ require('next/navigation');
14
14
  var galleryInteractive = require('./gallery-interactive.js');
15
15
  require('@clerk/nextjs');
16
16
  require('../clerk/fingerprint/fingerprint-provider.js');
17
- require('react-dom/client');
18
17
  require('next-themes');
19
18
  require('fumadocs-core/framework');
20
19
  require('next/link');
@@ -12,7 +12,6 @@ import 'next/navigation';
12
12
  import { GalleryInteractive } from './gallery-interactive.mjs';
13
13
  import '@clerk/nextjs';
14
14
  import '../clerk/fingerprint/fingerprint-provider.mjs';
15
- import 'react-dom/client';
16
15
  import 'next-themes';
17
16
  import 'fumadocs-core/framework';
18
17
  import 'next/link';
@@ -8,13 +8,11 @@ var React = require('react');
8
8
  var fingerprintProvider = require('../../clerk/fingerprint/fingerprint-provider.js');
9
9
  var utils = require('@windrun-huaiin/lib/utils');
10
10
  var navigation = require('next/navigation');
11
- var client = require('react-dom/client');
12
11
  var moneyPriceButton = require('./money-price-button.js');
13
12
  var moneyPriceConfigUtil = require('./money-price-config-util.js');
14
13
  var moneyPriceTypes = require('./money-price-types.js');
15
14
 
16
15
  function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath }) {
17
- var _a, _b, _c, _d;
18
16
  const fingerprintContext = fingerprintProvider.useFingerprintContextSafe();
19
17
  const { redirectToSignIn } = nextjs.useClerk();
20
18
  const router = navigation.useRouter();
@@ -22,7 +20,7 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
22
20
  const [isProcessing, setIsProcessing] = React.useState(false);
23
21
  const [tooltip, setTooltip] = React.useState({ show: false, content: '', x: 0, y: 0 });
24
22
  // 确定用户状态
25
- const getUserState = () => {
23
+ const getUserState = React.useCallback(() => {
26
24
  var _a, _b;
27
25
  if (!fingerprintContext)
28
26
  return moneyPriceTypes.UserState.Anonymous;
@@ -36,34 +34,35 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
36
34
  if ((_b = xSubscription.priceName) === null || _b === void 0 ? void 0 : _b.includes('Ultra'))
37
35
  return moneyPriceTypes.UserState.UltraUser;
38
36
  return moneyPriceTypes.UserState.FreeUser;
39
- };
40
- const userContext = {
41
- isAuthenticated: !!((_a = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xUser) === null || _a === void 0 ? void 0 : _a.clerkUserId),
42
- subscriptionStatus: getUserState(),
43
- subscriptionType: ((_c = (_b = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _b === void 0 ? void 0 : _b.priceId) === null || _c === void 0 ? void 0 : _c.includes('yearly')) ? 'yearly' : 'monthly',
44
- subscriptionEndDate: (_d = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _d === void 0 ? void 0 : _d.subPeriodEnd
45
- };
37
+ }, [fingerprintContext]);
38
+ // 优化 userContext 使用 useMemo
39
+ const userContext = React.useMemo(() => {
40
+ var _a, _b, _c, _d;
41
+ return ({
42
+ isAuthenticated: !!((_a = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xUser) === null || _a === void 0 ? void 0 : _a.clerkUserId),
43
+ subscriptionStatus: getUserState(),
44
+ subscriptionType: ((_c = (_b = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _b === void 0 ? void 0 : _b.priceId) === null || _c === void 0 ? void 0 : _c.includes('yearly')) ? 'yearly' : 'monthly',
45
+ subscriptionEndDate: (_d = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _d === void 0 ? void 0 : _d.subPeriodEnd
46
+ });
47
+ }, [fingerprintContext, getUserState]);
46
48
  // 处理登录
47
- const handleLogin = () => {
49
+ const handleLogin = React.useCallback(() => {
48
50
  if (signInPath) {
49
51
  router.push(signInPath);
50
52
  }
51
53
  else {
52
54
  redirectToSignIn();
53
55
  }
54
- };
56
+ }, [signInPath, redirectToSignIn, router]);
55
57
  // 处理升级
56
- const handleUpgrade = (plan, billingType) => tslib_es6.__awaiter(this, void 0, void 0, function* () {
57
- // 如果没有配置 API 端点,跳转到首页
58
+ const handleUpgrade = React.useCallback((plan, billingType) => tslib_es6.__awaiter(this, void 0, void 0, function* () {
58
59
  if (!upgradeApiEndpoint) {
59
60
  router.push('/');
60
61
  return;
61
62
  }
62
63
  setIsProcessing(true);
63
64
  try {
64
- // 获取价格配置
65
65
  const pricing = moneyPriceConfigUtil.getProductPricing(plan, billingType, config.activeProvider, config);
66
- // 调用 API 创建支付会话
67
66
  const response = yield fetch(upgradeApiEndpoint, {
68
67
  method: 'POST',
69
68
  headers: {
@@ -78,7 +77,6 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
78
77
  });
79
78
  const result = yield response.json();
80
79
  if (result.success && result.checkoutUrl) {
81
- // 跳转到支付页面
82
80
  window.location.href = result.checkoutUrl;
83
81
  }
84
82
  else {
@@ -91,14 +89,13 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
91
89
  finally {
92
90
  setIsProcessing(false);
93
91
  }
94
- });
92
+ }), [upgradeApiEndpoint, config, router]);
95
93
  // 更新价格显示
96
- const updatePriceDisplay = (newBillingType) => {
94
+ const updatePriceDisplay = React.useCallback((newBillingType) => {
97
95
  const providerConfig = moneyPriceConfigUtil.getActiveProviderConfig(config);
98
96
  data.plans.forEach((plan) => {
99
97
  const productConfig = providerConfig.products[plan.key];
100
98
  const pricing = productConfig.plans[newBillingType];
101
- // 更新价格值
102
99
  const priceValueElement = document.querySelector(`[data-price-value="${plan.key}"]`);
103
100
  if (priceValueElement) {
104
101
  if (pricing.amount === 0) {
@@ -108,13 +105,11 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
108
105
  priceValueElement.textContent = `${data.currency}${pricing.amount}`;
109
106
  }
110
107
  }
111
- // 更新单位
112
108
  const priceUnitElement = document.querySelector(`[data-price-unit="${plan.key}"]`);
113
109
  if (priceUnitElement) {
114
110
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
115
111
  priceUnitElement.textContent = (billingOption === null || billingOption === void 0 ? void 0 : billingOption.unit) || '/month';
116
112
  }
117
- // 更新原价和折扣
118
113
  const priceOriginalElement = document.querySelector(`[data-price-original="${plan.key}"]`);
119
114
  const priceDiscountElement = document.querySelector(`[data-price-discount="${plan.key}"]`);
120
115
  if (pricing.originalAmount && pricing.discountPercent) {
@@ -136,16 +131,15 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
136
131
  if (priceDiscountElement)
137
132
  priceDiscountElement.style.display = 'none';
138
133
  }
139
- // 更新副标题
140
134
  const priceSubtitleElement = document.querySelector(`[data-price-subtitle="${plan.key}"]`);
141
135
  if (priceSubtitleElement && plan.showBillingSubTitle !== false) {
142
136
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
143
137
  priceSubtitleElement.textContent = (billingOption === null || billingOption === void 0 ? void 0 : billingOption.subTitle) || '';
144
138
  }
145
139
  });
146
- };
140
+ }, [config, data]);
147
141
  // 更新按钮样式
148
- const updateButtonStyles = (newBillingType) => {
142
+ const updateButtonStyles = React.useCallback((newBillingType) => {
149
143
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
150
144
  const yearlyButton = document.querySelector('[data-billing-button="yearly"]');
151
145
  const activeClasses = 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm';
@@ -160,15 +154,14 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
160
154
  yearlyButton.className = utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
161
155
  }
162
156
  }
163
- };
157
+ }, []);
164
158
  // 更新折扣信息
165
- const updateDiscountInfo = (newBillingType) => {
159
+ const updateDiscountInfo = React.useCallback((newBillingType) => {
166
160
  const discountInfoElement = document.querySelector('[data-discount-info]');
167
161
  if (!discountInfoElement)
168
162
  return;
169
163
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
170
164
  const providerConfig = moneyPriceConfigUtil.getActiveProviderConfig(config);
171
- // 检查是否有折扣
172
165
  let hasDiscount = false;
173
166
  let discountPercent = 0;
174
167
  ['pro', 'ultra'].forEach(planKey => {
@@ -179,7 +172,6 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
179
172
  discountPercent = pricing.discountPercent;
180
173
  }
181
174
  });
182
- // 清空内容
183
175
  discountInfoElement.innerHTML = '';
184
176
  if (hasDiscount && (billingOption === null || billingOption === void 0 ? void 0 : billingOption.discountText)) {
185
177
  const discountBadge = document.createElement('span');
@@ -187,39 +179,7 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
187
179
  discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
188
180
  discountInfoElement.appendChild(discountBadge);
189
181
  }
190
- };
191
- // 使用 useRef 存储 root 实例,避免重复创建
192
- const rootsRef = React.useRef(new Map());
193
- // 动态替换按钮
194
- React.useEffect(() => {
195
- data.plans.forEach((plan) => {
196
- const placeholder = document.querySelector(`[data-button-placeholder="${plan.key}"]`);
197
- if (placeholder) {
198
- let root = rootsRef.current.get(plan.key);
199
- // 如果还没有创建 root,则创建一个
200
- if (!root) {
201
- root = client.createRoot(placeholder);
202
- rootsRef.current.set(plan.key, root);
203
- }
204
- // 渲染按钮
205
- root.render(jsxRuntime.jsx(moneyPriceButton.MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }));
206
- }
207
- });
208
- }, [userContext, billingType, isProcessing, data.plans, data.buttonTexts, handleLogin, handleUpgrade]);
209
- // 组件卸载时清理
210
- React.useEffect(() => {
211
- return () => {
212
- rootsRef.current.forEach((root) => {
213
- try {
214
- root.unmount();
215
- }
216
- catch (e) {
217
- // 忽略卸载错误
218
- }
219
- });
220
- rootsRef.current.clear();
221
- };
222
- }, []);
182
+ }, [config, data]);
223
183
  // 处理月付/年付切换和 tooltip 功能
224
184
  React.useEffect(() => {
225
185
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
@@ -242,7 +202,6 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
242
202
  if (yearlyButton) {
243
203
  yearlyButton.addEventListener('click', handleYearlyClick);
244
204
  }
245
- // 添加 tooltip 功能
246
205
  const tooltipHandlers = [];
247
206
  data.plans.forEach((plan) => {
248
207
  var _a;
@@ -274,10 +233,8 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
274
233
  }
275
234
  });
276
235
  });
277
- // 初始化价格显示
278
236
  updatePriceDisplay(billingType);
279
237
  updateDiscountInfo(billingType);
280
- // 清理
281
238
  return () => {
282
239
  if (monthlyButton) {
283
240
  monthlyButton.removeEventListener('click', handleMonthlyClick);
@@ -285,14 +242,13 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
285
242
  if (yearlyButton) {
286
243
  yearlyButton.removeEventListener('click', handleYearlyClick);
287
244
  }
288
- // 清理 tooltip 事件监听器
289
245
  tooltipHandlers.forEach(({ element, handlers }) => {
290
246
  element.removeEventListener('mouseenter', handlers.mouseenter);
291
247
  element.removeEventListener('mousemove', handlers.mousemove);
292
248
  element.removeEventListener('mouseleave', handlers.mouseleave);
293
249
  });
294
250
  };
295
- }, [data]);
251
+ }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo]);
296
252
  // Tooltip 组件
297
253
  const Tooltip = ({ show, content, x, y }) => {
298
254
  if (!show)
@@ -309,7 +265,8 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
309
265
  };
310
266
  return (jsxRuntime.jsx("div", { style: style, className: "bg-gray-700 dark:bg-gray-200 text-gray-100 dark:text-gray-800 text-xs leading-relaxed px-3 py-2 rounded-lg shadow-lg border border-gray-300 dark:border-gray-600 backdrop-blur-sm", children: content }));
311
267
  };
312
- return jsxRuntime.jsx(Tooltip, Object.assign({}, tooltip));
268
+ // 直接渲染按钮,确保宽度占满
269
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [data.plans.map((plan) => (jsxRuntime.jsx("div", { "data-button-placeholder": plan.key, children: jsxRuntime.jsx(moneyPriceButton.MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }) }, plan.key))), jsxRuntime.jsx(Tooltip, Object.assign({}, tooltip))] }));
313
270
  }
314
271
 
315
272
  exports.MoneyPriceInteractive = MoneyPriceInteractive;
@@ -1,18 +1,16 @@
1
1
  "use client";
2
2
  import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.2/node_modules/tslib/tslib.es6.mjs';
3
- import { jsx } from 'react/jsx-runtime';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
4
  import { useClerk } from '@clerk/nextjs';
5
- import React, { useState, useEffect } from 'react';
5
+ import { useState, useCallback, useMemo, useEffect } from 'react';
6
6
  import { useFingerprintContextSafe } from '../../clerk/fingerprint/fingerprint-provider.mjs';
7
7
  import { cn } from '@windrun-huaiin/lib/utils';
8
8
  import { useRouter } from 'next/navigation';
9
- import { createRoot } from 'react-dom/client';
10
9
  import { MoneyPriceButton } from './money-price-button.mjs';
11
- import { getProductPricing, getActiveProviderConfig } from './money-price-config-util.mjs';
10
+ import { getActiveProviderConfig, getProductPricing } from './money-price-config-util.mjs';
12
11
  import { UserState } from './money-price-types.mjs';
13
12
 
14
13
  function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath }) {
15
- var _a, _b, _c, _d;
16
14
  const fingerprintContext = useFingerprintContextSafe();
17
15
  const { redirectToSignIn } = useClerk();
18
16
  const router = useRouter();
@@ -20,7 +18,7 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
20
18
  const [isProcessing, setIsProcessing] = useState(false);
21
19
  const [tooltip, setTooltip] = useState({ show: false, content: '', x: 0, y: 0 });
22
20
  // 确定用户状态
23
- const getUserState = () => {
21
+ const getUserState = useCallback(() => {
24
22
  var _a, _b;
25
23
  if (!fingerprintContext)
26
24
  return UserState.Anonymous;
@@ -34,34 +32,35 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
34
32
  if ((_b = xSubscription.priceName) === null || _b === void 0 ? void 0 : _b.includes('Ultra'))
35
33
  return UserState.UltraUser;
36
34
  return UserState.FreeUser;
37
- };
38
- const userContext = {
39
- isAuthenticated: !!((_a = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xUser) === null || _a === void 0 ? void 0 : _a.clerkUserId),
40
- subscriptionStatus: getUserState(),
41
- subscriptionType: ((_c = (_b = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _b === void 0 ? void 0 : _b.priceId) === null || _c === void 0 ? void 0 : _c.includes('yearly')) ? 'yearly' : 'monthly',
42
- subscriptionEndDate: (_d = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _d === void 0 ? void 0 : _d.subPeriodEnd
43
- };
35
+ }, [fingerprintContext]);
36
+ // 优化 userContext 使用 useMemo
37
+ const userContext = useMemo(() => {
38
+ var _a, _b, _c, _d;
39
+ return ({
40
+ isAuthenticated: !!((_a = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xUser) === null || _a === void 0 ? void 0 : _a.clerkUserId),
41
+ subscriptionStatus: getUserState(),
42
+ subscriptionType: ((_c = (_b = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _b === void 0 ? void 0 : _b.priceId) === null || _c === void 0 ? void 0 : _c.includes('yearly')) ? 'yearly' : 'monthly',
43
+ subscriptionEndDate: (_d = fingerprintContext === null || fingerprintContext === void 0 ? void 0 : fingerprintContext.xSubscription) === null || _d === void 0 ? void 0 : _d.subPeriodEnd
44
+ });
45
+ }, [fingerprintContext, getUserState]);
44
46
  // 处理登录
45
- const handleLogin = () => {
47
+ const handleLogin = useCallback(() => {
46
48
  if (signInPath) {
47
49
  router.push(signInPath);
48
50
  }
49
51
  else {
50
52
  redirectToSignIn();
51
53
  }
52
- };
54
+ }, [signInPath, redirectToSignIn, router]);
53
55
  // 处理升级
54
- const handleUpgrade = (plan, billingType) => __awaiter(this, void 0, void 0, function* () {
55
- // 如果没有配置 API 端点,跳转到首页
56
+ const handleUpgrade = useCallback((plan, billingType) => __awaiter(this, void 0, void 0, function* () {
56
57
  if (!upgradeApiEndpoint) {
57
58
  router.push('/');
58
59
  return;
59
60
  }
60
61
  setIsProcessing(true);
61
62
  try {
62
- // 获取价格配置
63
63
  const pricing = getProductPricing(plan, billingType, config.activeProvider, config);
64
- // 调用 API 创建支付会话
65
64
  const response = yield fetch(upgradeApiEndpoint, {
66
65
  method: 'POST',
67
66
  headers: {
@@ -76,7 +75,6 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
76
75
  });
77
76
  const result = yield response.json();
78
77
  if (result.success && result.checkoutUrl) {
79
- // 跳转到支付页面
80
78
  window.location.href = result.checkoutUrl;
81
79
  }
82
80
  else {
@@ -89,14 +87,13 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
89
87
  finally {
90
88
  setIsProcessing(false);
91
89
  }
92
- });
90
+ }), [upgradeApiEndpoint, config, router]);
93
91
  // 更新价格显示
94
- const updatePriceDisplay = (newBillingType) => {
92
+ const updatePriceDisplay = useCallback((newBillingType) => {
95
93
  const providerConfig = getActiveProviderConfig(config);
96
94
  data.plans.forEach((plan) => {
97
95
  const productConfig = providerConfig.products[plan.key];
98
96
  const pricing = productConfig.plans[newBillingType];
99
- // 更新价格值
100
97
  const priceValueElement = document.querySelector(`[data-price-value="${plan.key}"]`);
101
98
  if (priceValueElement) {
102
99
  if (pricing.amount === 0) {
@@ -106,13 +103,11 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
106
103
  priceValueElement.textContent = `${data.currency}${pricing.amount}`;
107
104
  }
108
105
  }
109
- // 更新单位
110
106
  const priceUnitElement = document.querySelector(`[data-price-unit="${plan.key}"]`);
111
107
  if (priceUnitElement) {
112
108
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
113
109
  priceUnitElement.textContent = (billingOption === null || billingOption === void 0 ? void 0 : billingOption.unit) || '/month';
114
110
  }
115
- // 更新原价和折扣
116
111
  const priceOriginalElement = document.querySelector(`[data-price-original="${plan.key}"]`);
117
112
  const priceDiscountElement = document.querySelector(`[data-price-discount="${plan.key}"]`);
118
113
  if (pricing.originalAmount && pricing.discountPercent) {
@@ -134,16 +129,15 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
134
129
  if (priceDiscountElement)
135
130
  priceDiscountElement.style.display = 'none';
136
131
  }
137
- // 更新副标题
138
132
  const priceSubtitleElement = document.querySelector(`[data-price-subtitle="${plan.key}"]`);
139
133
  if (priceSubtitleElement && plan.showBillingSubTitle !== false) {
140
134
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
141
135
  priceSubtitleElement.textContent = (billingOption === null || billingOption === void 0 ? void 0 : billingOption.subTitle) || '';
142
136
  }
143
137
  });
144
- };
138
+ }, [config, data]);
145
139
  // 更新按钮样式
146
- const updateButtonStyles = (newBillingType) => {
140
+ const updateButtonStyles = useCallback((newBillingType) => {
147
141
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
148
142
  const yearlyButton = document.querySelector('[data-billing-button="yearly"]');
149
143
  const activeClasses = 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm';
@@ -158,15 +152,14 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
158
152
  yearlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
159
153
  }
160
154
  }
161
- };
155
+ }, []);
162
156
  // 更新折扣信息
163
- const updateDiscountInfo = (newBillingType) => {
157
+ const updateDiscountInfo = useCallback((newBillingType) => {
164
158
  const discountInfoElement = document.querySelector('[data-discount-info]');
165
159
  if (!discountInfoElement)
166
160
  return;
167
161
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
168
162
  const providerConfig = getActiveProviderConfig(config);
169
- // 检查是否有折扣
170
163
  let hasDiscount = false;
171
164
  let discountPercent = 0;
172
165
  ['pro', 'ultra'].forEach(planKey => {
@@ -177,7 +170,6 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
177
170
  discountPercent = pricing.discountPercent;
178
171
  }
179
172
  });
180
- // 清空内容
181
173
  discountInfoElement.innerHTML = '';
182
174
  if (hasDiscount && (billingOption === null || billingOption === void 0 ? void 0 : billingOption.discountText)) {
183
175
  const discountBadge = document.createElement('span');
@@ -185,39 +177,7 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
185
177
  discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
186
178
  discountInfoElement.appendChild(discountBadge);
187
179
  }
188
- };
189
- // 使用 useRef 存储 root 实例,避免重复创建
190
- const rootsRef = React.useRef(new Map());
191
- // 动态替换按钮
192
- useEffect(() => {
193
- data.plans.forEach((plan) => {
194
- const placeholder = document.querySelector(`[data-button-placeholder="${plan.key}"]`);
195
- if (placeholder) {
196
- let root = rootsRef.current.get(plan.key);
197
- // 如果还没有创建 root,则创建一个
198
- if (!root) {
199
- root = createRoot(placeholder);
200
- rootsRef.current.set(plan.key, root);
201
- }
202
- // 渲染按钮
203
- root.render(jsx(MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }));
204
- }
205
- });
206
- }, [userContext, billingType, isProcessing, data.plans, data.buttonTexts, handleLogin, handleUpgrade]);
207
- // 组件卸载时清理
208
- useEffect(() => {
209
- return () => {
210
- rootsRef.current.forEach((root) => {
211
- try {
212
- root.unmount();
213
- }
214
- catch (e) {
215
- // 忽略卸载错误
216
- }
217
- });
218
- rootsRef.current.clear();
219
- };
220
- }, []);
180
+ }, [config, data]);
221
181
  // 处理月付/年付切换和 tooltip 功能
222
182
  useEffect(() => {
223
183
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
@@ -240,7 +200,6 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
240
200
  if (yearlyButton) {
241
201
  yearlyButton.addEventListener('click', handleYearlyClick);
242
202
  }
243
- // 添加 tooltip 功能
244
203
  const tooltipHandlers = [];
245
204
  data.plans.forEach((plan) => {
246
205
  var _a;
@@ -272,10 +231,8 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
272
231
  }
273
232
  });
274
233
  });
275
- // 初始化价格显示
276
234
  updatePriceDisplay(billingType);
277
235
  updateDiscountInfo(billingType);
278
- // 清理
279
236
  return () => {
280
237
  if (monthlyButton) {
281
238
  monthlyButton.removeEventListener('click', handleMonthlyClick);
@@ -283,14 +240,13 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
283
240
  if (yearlyButton) {
284
241
  yearlyButton.removeEventListener('click', handleYearlyClick);
285
242
  }
286
- // 清理 tooltip 事件监听器
287
243
  tooltipHandlers.forEach(({ element, handlers }) => {
288
244
  element.removeEventListener('mouseenter', handlers.mouseenter);
289
245
  element.removeEventListener('mousemove', handlers.mousemove);
290
246
  element.removeEventListener('mouseleave', handlers.mouseleave);
291
247
  });
292
248
  };
293
- }, [data]);
249
+ }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo]);
294
250
  // Tooltip 组件
295
251
  const Tooltip = ({ show, content, x, y }) => {
296
252
  if (!show)
@@ -307,7 +263,8 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
307
263
  };
308
264
  return (jsx("div", { style: style, className: "bg-gray-700 dark:bg-gray-200 text-gray-100 dark:text-gray-800 text-xs leading-relaxed px-3 py-2 rounded-lg shadow-lg border border-gray-300 dark:border-gray-600 backdrop-blur-sm", children: content }));
309
265
  };
310
- return jsx(Tooltip, Object.assign({}, tooltip));
266
+ // 直接渲染按钮,确保宽度占满
267
+ return (jsxs(Fragment, { children: [data.plans.map((plan) => (jsx("div", { "data-button-placeholder": plan.key, children: jsx(MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }) }, plan.key))), jsx(Tooltip, Object.assign({}, tooltip))] }));
311
268
  }
312
269
 
313
270
  export { MoneyPriceInteractive };
@@ -2,19 +2,7 @@
2
2
 
3
3
  var tslib_es6 = require('../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.2/node_modules/tslib/tslib.es6.js');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- require('@windrun-huaiin/base-ui/components/server');
6
- require('next-themes');
7
- require('react');
8
- require('fumadocs-core/framework');
9
5
  var utils = require('@windrun-huaiin/lib/utils');
10
- require('next/link');
11
- var gradientButton = require('../../fuma/mdx/gradient-button.js');
12
- require('next/navigation');
13
- require('fumadocs-ui/utils/use-copy-button');
14
- require('fumadocs-core/link');
15
- require('@windrun-huaiin/base-ui/ui');
16
- require('fumadocs-ui/components/ui/collapsible');
17
- require('../../fuma/mdx/banner.js');
18
6
  var server = require('next-intl/server');
19
7
  var moneyPriceConfigUtil = require('./money-price-config-util.js');
20
8
  var moneyPriceInteractive = require('./money-price-interactive.js');
@@ -31,15 +19,11 @@ function MoneyPrice(_a) {
31
19
  buttonTexts: t.raw('buttonTexts'),
32
20
  currency: config.display.currency
33
21
  };
34
- // 获取激活的支付供应商配置
35
22
  const providerConfig = moneyPriceConfigUtil.getActiveProviderConfig(config);
36
23
  const minPlanFeaturesCount = config.display.minFeaturesCount;
37
- // 使用默认计费类型进行静态渲染
38
24
  const defaultBilling = data.billingSwitch.defaultKey;
39
25
  const defaultBillingDisplay = data.billingSwitch.options.find((opt) => opt.key === defaultBilling) || data.billingSwitch.options[0];
40
- // 计算特性数量
41
26
  const maxFeaturesCount = Math.max(...data.plans.map((plan) => { var _a; return ((_a = plan.features) === null || _a === void 0 ? void 0 : _a.length) || 0; }), minPlanFeaturesCount || 0);
42
- // 处理卡片高度对齐
43
27
  const getFeatureRows = (plan) => {
44
28
  const features = plan.features || [];
45
29
  const filled = [...features];
@@ -47,17 +31,14 @@ function MoneyPrice(_a) {
47
31
  filled.push(null);
48
32
  return filled;
49
33
  };
50
- // 静态价格渲染逻辑
51
34
  function renderPrice(plan, billingKey = data.billingSwitch.defaultKey) {
52
35
  const productConfig = providerConfig.products[plan.key];
53
36
  const pricing = productConfig.plans[billingKey];
54
37
  const currentBillingDisplay = data.billingSwitch.options.find((opt) => opt.key === billingKey) || defaultBillingDisplay;
55
38
  const billingSubTitle = (currentBillingDisplay === null || currentBillingDisplay === void 0 ? void 0 : currentBillingDisplay.subTitle) || '';
56
- // 免费计划
57
39
  if (pricing.amount === 0) {
58
40
  return (jsxRuntime.jsxs("div", { className: "flex flex-col items-start w-full", "data-price-container": plan.key, children: [jsxRuntime.jsx("div", { className: "flex items-end gap-2", children: jsxRuntime.jsx("span", { className: "text-4xl font-extrabold text-gray-900 dark:text-gray-100", "data-price-value": plan.key, children: "Free" }) }), jsxRuntime.jsx("div", { className: "flex items-center gap-2 min-h-[24px] mt-1", children: jsxRuntime.jsx("span", { className: utils.cn('text-xs text-gray-700 dark:text-gray-300 font-medium', plan.showBillingSubTitle === false && 'opacity-0 select-none'), "data-price-subtitle": plan.key, children: plan.showBillingSubTitle === false ? '' : billingSubTitle }) })] }));
59
41
  }
60
- // 付费计划
61
42
  const hasDiscount = pricing.discountPercent && pricing.discountPercent > 0;
62
43
  const unit = currentBillingDisplay.unit || '';
63
44
  let discountText = '';
@@ -72,7 +53,6 @@ function MoneyPrice(_a) {
72
53
  ? 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
73
54
  : 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'), "data-billing-button": "yearly", type: "button", children: ((_c = data.billingSwitch.options.find((opt) => opt.key === 'yearly')) === null || _c === void 0 ? void 0 : _c.name) || 'Yearly' })] }) }), jsxRuntime.jsx("div", { className: "h-8 flex items-center justify-center mb-3", "data-discount-info": true, children: (() => {
74
55
  const opt = data.billingSwitch.options.find((opt) => opt.key === data.billingSwitch.defaultKey);
75
- // 检查默认计费类型是否有折扣
76
56
  let hasDiscount = false;
77
57
  let discountPercent = 0;
78
58
  ['pro', 'ultra'].forEach(planKey => {
@@ -86,7 +66,7 @@ function MoneyPrice(_a) {
86
66
  if (!(opt && hasDiscount && opt.discountText))
87
67
  return null;
88
68
  return (jsxRuntime.jsx("span", { className: "px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap", children: opt.discountText.replace('{percent}', String(discountPercent)) }));
89
- })() })] }), jsxRuntime.jsx("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-8", children: data.plans.map((plan, _idx) => (jsxRuntime.jsxs("div", { "data-price-plan": plan.key, className: utils.cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-8 h-full shadow-sm dark:shadow-none', 'hover:border-2 hover:border-purple-500', 'focus-within:border-2 focus-within:border-purple-500'), style: { minHeight: maxFeaturesCount * 100 }, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsxRuntime.jsx("span", { className: "text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsxRuntime.jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), renderPrice(plan), jsxRuntime.jsx("ul", { className: "flex-1 mb-6 mt-4", children: getFeatureRows(plan).map((feature, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-2 mb-2 min-h-[28px]", "data-feature-item": `${plan.key}-${i}`, children: [feature ? (jsxRuntime.jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1", children: feature.icon ? jsxRuntime.jsx("span", { children: feature.icon }) : jsxRuntime.jsx("span", { className: "font-bold", children: "\u2713" }) })) : (jsxRuntime.jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full mr-1", children: "\u00A0" })), feature && feature.tag && (jsxRuntime.jsx("span", { className: "px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle", children: feature.tag })), feature ? (jsxRuntime.jsxs("span", { className: "relative group cursor-pointer text-sm text-gray-800 dark:text-gray-200", children: [feature.description, feature.tooltip && (jsxRuntime.jsx("span", { className: "ml-1 align-middle inline-flex", "data-tooltip-trigger": `${plan.key}-${i}`, "data-tooltip-content": feature.tooltip, children: jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }) }))] })) : (jsxRuntime.jsx("span", { children: "\u00A0" }))] }, i))) }), jsxRuntime.jsx("div", { className: "flex-1" }), jsxRuntime.jsx("div", { "data-button-placeholder": plan.key, children: jsxRuntime.jsx(gradientButton.GradientButton, { title: data.buttonTexts.getStarted, disabled: true, align: "center", className: "w-full" }) })] }, plan.key))) }), jsxRuntime.jsx(moneyPriceInteractive.MoneyPriceInteractive, { data: data, config: config, upgradeApiEndpoint: upgradeApiEndpoint, signInPath: signInPath })] }));
69
+ })() })] }), jsxRuntime.jsx("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-8", children: data.plans.map((plan, _idx) => (jsxRuntime.jsxs("div", { "data-price-plan": plan.key, className: utils.cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-8 h-full shadow-sm dark:shadow-none', 'hover:border-2 hover:border-purple-500', 'focus-within:border-2 focus-within:border-purple-500'), style: { minHeight: maxFeaturesCount * 100 }, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsxRuntime.jsx("span", { className: "text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsxRuntime.jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), renderPrice(plan), jsxRuntime.jsx("ul", { className: "flex-1 mb-6 mt-4", children: getFeatureRows(plan).map((feature, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-2 mb-2 min-h-[28px]", "data-feature-item": `${plan.key}-${i}`, children: [feature ? (jsxRuntime.jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1", children: feature.icon ? jsxRuntime.jsx("span", { children: feature.icon }) : jsxRuntime.jsx("span", { className: "font-bold", children: "\u2713" }) })) : (jsxRuntime.jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full mr-1", children: "\u00A0" })), feature && feature.tag && (jsxRuntime.jsx("span", { className: "px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle", children: feature.tag })), feature ? (jsxRuntime.jsxs("span", { className: "relative group cursor-pointer text-sm text-gray-800 dark:text-gray-200", children: [feature.description, feature.tooltip && (jsxRuntime.jsx("span", { className: "ml-1 align-middle inline-flex", "data-tooltip-trigger": `${plan.key}-${i}`, "data-tooltip-content": feature.tooltip, children: jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }) }))] })) : (jsxRuntime.jsx("span", { children: "\u00A0" }))] }, i))) }), jsxRuntime.jsx("div", { className: "flex-1" }), jsxRuntime.jsx("div", { "data-button-placeholder": plan.key, className: "w-full" })] }, plan.key))) }), jsxRuntime.jsx(moneyPriceInteractive.MoneyPriceInteractive, { data: data, config: config, upgradeApiEndpoint: upgradeApiEndpoint, signInPath: signInPath })] }));
90
70
  });
91
71
  }
92
72
 
@@ -1,18 +1,6 @@
1
1
  import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.2/node_modules/tslib/tslib.es6.mjs';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import '@windrun-huaiin/base-ui/components/server';
4
- import 'next-themes';
5
- import 'react';
6
- import 'fumadocs-core/framework';
7
3
  import { cn } from '@windrun-huaiin/lib/utils';
8
- import 'next/link';
9
- import { GradientButton } from '../../fuma/mdx/gradient-button.mjs';
10
- import 'next/navigation';
11
- import 'fumadocs-ui/utils/use-copy-button';
12
- import 'fumadocs-core/link';
13
- import '@windrun-huaiin/base-ui/ui';
14
- import 'fumadocs-ui/components/ui/collapsible';
15
- import '../../fuma/mdx/banner.mjs';
16
4
  import { getTranslations } from 'next-intl/server';
17
5
  import { getActiveProviderConfig } from './money-price-config-util.mjs';
18
6
  import { MoneyPriceInteractive } from './money-price-interactive.mjs';
@@ -29,15 +17,11 @@ function MoneyPrice(_a) {
29
17
  buttonTexts: t.raw('buttonTexts'),
30
18
  currency: config.display.currency
31
19
  };
32
- // 获取激活的支付供应商配置
33
20
  const providerConfig = getActiveProviderConfig(config);
34
21
  const minPlanFeaturesCount = config.display.minFeaturesCount;
35
- // 使用默认计费类型进行静态渲染
36
22
  const defaultBilling = data.billingSwitch.defaultKey;
37
23
  const defaultBillingDisplay = data.billingSwitch.options.find((opt) => opt.key === defaultBilling) || data.billingSwitch.options[0];
38
- // 计算特性数量
39
24
  const maxFeaturesCount = Math.max(...data.plans.map((plan) => { var _a; return ((_a = plan.features) === null || _a === void 0 ? void 0 : _a.length) || 0; }), minPlanFeaturesCount || 0);
40
- // 处理卡片高度对齐
41
25
  const getFeatureRows = (plan) => {
42
26
  const features = plan.features || [];
43
27
  const filled = [...features];
@@ -45,17 +29,14 @@ function MoneyPrice(_a) {
45
29
  filled.push(null);
46
30
  return filled;
47
31
  };
48
- // 静态价格渲染逻辑
49
32
  function renderPrice(plan, billingKey = data.billingSwitch.defaultKey) {
50
33
  const productConfig = providerConfig.products[plan.key];
51
34
  const pricing = productConfig.plans[billingKey];
52
35
  const currentBillingDisplay = data.billingSwitch.options.find((opt) => opt.key === billingKey) || defaultBillingDisplay;
53
36
  const billingSubTitle = (currentBillingDisplay === null || currentBillingDisplay === void 0 ? void 0 : currentBillingDisplay.subTitle) || '';
54
- // 免费计划
55
37
  if (pricing.amount === 0) {
56
38
  return (jsxs("div", { className: "flex flex-col items-start w-full", "data-price-container": plan.key, children: [jsx("div", { className: "flex items-end gap-2", children: jsx("span", { className: "text-4xl font-extrabold text-gray-900 dark:text-gray-100", "data-price-value": plan.key, children: "Free" }) }), jsx("div", { className: "flex items-center gap-2 min-h-[24px] mt-1", children: jsx("span", { className: cn('text-xs text-gray-700 dark:text-gray-300 font-medium', plan.showBillingSubTitle === false && 'opacity-0 select-none'), "data-price-subtitle": plan.key, children: plan.showBillingSubTitle === false ? '' : billingSubTitle }) })] }));
57
39
  }
58
- // 付费计划
59
40
  const hasDiscount = pricing.discountPercent && pricing.discountPercent > 0;
60
41
  const unit = currentBillingDisplay.unit || '';
61
42
  let discountText = '';
@@ -70,7 +51,6 @@ function MoneyPrice(_a) {
70
51
  ? 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
71
52
  : 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'), "data-billing-button": "yearly", type: "button", children: ((_c = data.billingSwitch.options.find((opt) => opt.key === 'yearly')) === null || _c === void 0 ? void 0 : _c.name) || 'Yearly' })] }) }), jsx("div", { className: "h-8 flex items-center justify-center mb-3", "data-discount-info": true, children: (() => {
72
53
  const opt = data.billingSwitch.options.find((opt) => opt.key === data.billingSwitch.defaultKey);
73
- // 检查默认计费类型是否有折扣
74
54
  let hasDiscount = false;
75
55
  let discountPercent = 0;
76
56
  ['pro', 'ultra'].forEach(planKey => {
@@ -84,7 +64,7 @@ function MoneyPrice(_a) {
84
64
  if (!(opt && hasDiscount && opt.discountText))
85
65
  return null;
86
66
  return (jsx("span", { className: "px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap", children: opt.discountText.replace('{percent}', String(discountPercent)) }));
87
- })() })] }), jsx("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-8", children: data.plans.map((plan, _idx) => (jsxs("div", { "data-price-plan": plan.key, className: cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-8 h-full shadow-sm dark:shadow-none', 'hover:border-2 hover:border-purple-500', 'focus-within:border-2 focus-within:border-purple-500'), style: { minHeight: maxFeaturesCount * 100 }, children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("span", { className: "text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), renderPrice(plan), jsx("ul", { className: "flex-1 mb-6 mt-4", children: getFeatureRows(plan).map((feature, i) => (jsxs("li", { className: "flex items-center gap-2 mb-2 min-h-[28px]", "data-feature-item": `${plan.key}-${i}`, children: [feature ? (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1", children: feature.icon ? jsx("span", { children: feature.icon }) : jsx("span", { className: "font-bold", children: "\u2713" }) })) : (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full mr-1", children: "\u00A0" })), feature && feature.tag && (jsx("span", { className: "px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle", children: feature.tag })), feature ? (jsxs("span", { className: "relative group cursor-pointer text-sm text-gray-800 dark:text-gray-200", children: [feature.description, feature.tooltip && (jsx("span", { className: "ml-1 align-middle inline-flex", "data-tooltip-trigger": `${plan.key}-${i}`, "data-tooltip-content": feature.tooltip, children: jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }) }))] })) : (jsx("span", { children: "\u00A0" }))] }, i))) }), jsx("div", { className: "flex-1" }), jsx("div", { "data-button-placeholder": plan.key, children: jsx(GradientButton, { title: data.buttonTexts.getStarted, disabled: true, align: "center", className: "w-full" }) })] }, plan.key))) }), jsx(MoneyPriceInteractive, { data: data, config: config, upgradeApiEndpoint: upgradeApiEndpoint, signInPath: signInPath })] }));
67
+ })() })] }), jsx("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-8", children: data.plans.map((plan, _idx) => (jsxs("div", { "data-price-plan": plan.key, className: cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-8 h-full shadow-sm dark:shadow-none', 'hover:border-2 hover:border-purple-500', 'focus-within:border-2 focus-within:border-purple-500'), style: { minHeight: maxFeaturesCount * 100 }, children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("span", { className: "text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), renderPrice(plan), jsx("ul", { className: "flex-1 mb-6 mt-4", children: getFeatureRows(plan).map((feature, i) => (jsxs("li", { className: "flex items-center gap-2 mb-2 min-h-[28px]", "data-feature-item": `${plan.key}-${i}`, children: [feature ? (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1", children: feature.icon ? jsx("span", { children: feature.icon }) : jsx("span", { className: "font-bold", children: "\u2713" }) })) : (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full mr-1", children: "\u00A0" })), feature && feature.tag && (jsx("span", { className: "px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle", children: feature.tag })), feature ? (jsxs("span", { className: "relative group cursor-pointer text-sm text-gray-800 dark:text-gray-200", children: [feature.description, feature.tooltip && (jsx("span", { className: "ml-1 align-middle inline-flex", "data-tooltip-trigger": `${plan.key}-${i}`, "data-tooltip-content": feature.tooltip, children: jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }) }))] })) : (jsx("span", { children: "\u00A0" }))] }, i))) }), jsx("div", { className: "flex-1" }), jsx("div", { "data-button-placeholder": plan.key, className: "w-full" })] }, plan.key))) }), jsx(MoneyPriceInteractive, { data: data, config: config, upgradeApiEndpoint: upgradeApiEndpoint, signInPath: signInPath })] }));
88
68
  });
89
69
  }
90
70
 
@@ -14,7 +14,6 @@ var pricePlanInteractive = require('./price-plan-interactive.js');
14
14
  require('@clerk/nextjs');
15
15
  require('../clerk/fingerprint/fingerprint-provider.js');
16
16
  require('next/navigation');
17
- require('react-dom/client');
18
17
  require('next-themes');
19
18
  require('fumadocs-core/framework');
20
19
  require('next/link');
@@ -12,7 +12,6 @@ import { PricePlanInteractive } from './price-plan-interactive.mjs';
12
12
  import '@clerk/nextjs';
13
13
  import '../clerk/fingerprint/fingerprint-provider.mjs';
14
14
  import 'next/navigation';
15
- import 'react-dom/client';
16
15
  import 'next-themes';
17
16
  import 'fumadocs-core/framework';
18
17
  import 'next/link';
@@ -1,4 +1,4 @@
1
- import { __module as coseBase$1 } from '../../../../../_virtual/cose-base2.mjs';
1
+ import { __module as coseBase$1 } from '../../../../../_virtual/cose-base.mjs';
2
2
  import { __require as requireLayoutBase } from '../../../layout-base@1.0.2/node_modules/layout-base/layout-base.mjs';
3
3
 
4
4
  var coseBase = coseBase$1.exports;
@@ -1,4 +1,4 @@
1
- import { __module as coseBase$1 } from '../../../../../_virtual/cose-base.mjs';
1
+ import { __module as coseBase$1 } from '../../../../../_virtual/cose-base2.mjs';
2
2
  import { __require as requireLayoutBase } from '../../../layout-base@2.0.1/node_modules/layout-base/layout-base.mjs';
3
3
 
4
4
  var coseBase = coseBase$1.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "7.3.4",
3
+ "version": "7.3.5",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -4,8 +4,7 @@ import { useClerk } from '@clerk/nextjs';
4
4
  import { useFingerprintContextSafe } from '@third-ui/clerk/fingerprint';
5
5
  import { cn } from '@windrun-huaiin/lib/utils';
6
6
  import { useRouter } from 'next/navigation';
7
- import React, { useEffect, useState } from 'react';
8
- import { createRoot } from 'react-dom/client';
7
+ import React, { useEffect, useState, useMemo, useCallback } from 'react';
9
8
  import { MoneyPriceButton } from './money-price-button';
10
9
  import { getActiveProviderConfig, getProductPricing } from './money-price-config-util';
11
10
  import {
@@ -33,9 +32,9 @@ export function MoneyPriceInteractive({
33
32
  x: number;
34
33
  y: number;
35
34
  }>({ show: false, content: '', x: 0, y: 0 });
36
-
35
+
37
36
  // 确定用户状态
38
- const getUserState = (): UserState => {
37
+ const getUserState = useCallback((): UserState => {
39
38
  if (!fingerprintContext) return UserState.Anonymous;
40
39
  const { xUser, xSubscription } = fingerprintContext;
41
40
 
@@ -44,27 +43,27 @@ export function MoneyPriceInteractive({
44
43
  if (xSubscription.priceName?.includes('Pro')) return UserState.ProUser;
45
44
  if (xSubscription.priceName?.includes('Ultra')) return UserState.UltraUser;
46
45
  return UserState.FreeUser;
47
- };
48
-
49
- const userContext: UserContext = {
46
+ }, [fingerprintContext]);
47
+
48
+ // 优化 userContext 使用 useMemo
49
+ const userContext = useMemo<UserContext>(() => ({
50
50
  isAuthenticated: !!fingerprintContext?.xUser?.clerkUserId,
51
51
  subscriptionStatus: getUserState(),
52
52
  subscriptionType: fingerprintContext?.xSubscription?.priceId?.includes('yearly') ? 'yearly' : 'monthly',
53
53
  subscriptionEndDate: fingerprintContext?.xSubscription?.subPeriodEnd
54
- };
55
-
54
+ }), [fingerprintContext, getUserState]);
55
+
56
56
  // 处理登录
57
- const handleLogin = () => {
57
+ const handleLogin = useCallback(() => {
58
58
  if (signInPath) {
59
59
  router.push(signInPath);
60
60
  } else {
61
61
  redirectToSignIn();
62
62
  }
63
- };
64
-
63
+ }, [signInPath, redirectToSignIn, router]);
64
+
65
65
  // 处理升级
66
- const handleUpgrade = async (plan: string, billingType: string) => {
67
- // 如果没有配置 API 端点,跳转到首页
66
+ const handleUpgrade = useCallback(async (plan: string, billingType: string) => {
68
67
  if (!upgradeApiEndpoint) {
69
68
  router.push('/');
70
69
  return;
@@ -72,7 +71,6 @@ export function MoneyPriceInteractive({
72
71
 
73
72
  setIsProcessing(true);
74
73
  try {
75
- // 获取价格配置
76
74
  const pricing = getProductPricing(
77
75
  plan as 'free' | 'pro' | 'ultra',
78
76
  billingType as 'monthly' | 'yearly',
@@ -80,7 +78,6 @@ export function MoneyPriceInteractive({
80
78
  config
81
79
  );
82
80
 
83
- // 调用 API 创建支付会话
84
81
  const response = await fetch(upgradeApiEndpoint, {
85
82
  method: 'POST',
86
83
  headers: {
@@ -97,7 +94,6 @@ export function MoneyPriceInteractive({
97
94
  const result = await response.json();
98
95
 
99
96
  if (result.success && result.checkoutUrl) {
100
- // 跳转到支付页面
101
97
  window.location.href = result.checkoutUrl;
102
98
  } else {
103
99
  console.error('Failed to create checkout session:', result.error);
@@ -107,17 +103,16 @@ export function MoneyPriceInteractive({
107
103
  } finally {
108
104
  setIsProcessing(false);
109
105
  }
110
- };
111
-
106
+ }, [upgradeApiEndpoint, config, router]);
107
+
112
108
  // 更新价格显示
113
- const updatePriceDisplay = (newBillingType: string) => {
109
+ const updatePriceDisplay = useCallback((newBillingType: string) => {
114
110
  const providerConfig = getActiveProviderConfig(config);
115
111
 
116
112
  data.plans.forEach((plan: any) => {
117
113
  const productConfig = providerConfig.products[plan.key as 'free' | 'pro' | 'ultra'];
118
114
  const pricing = productConfig.plans[newBillingType as 'monthly' | 'yearly'];
119
115
 
120
- // 更新价格值
121
116
  const priceValueElement = document.querySelector(`[data-price-value="${plan.key}"]`) as HTMLElement;
122
117
  if (priceValueElement) {
123
118
  if (pricing.amount === 0) {
@@ -127,14 +122,12 @@ export function MoneyPriceInteractive({
127
122
  }
128
123
  }
129
124
 
130
- // 更新单位
131
125
  const priceUnitElement = document.querySelector(`[data-price-unit="${plan.key}"]`) as HTMLElement;
132
126
  if (priceUnitElement) {
133
127
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
134
128
  priceUnitElement.textContent = billingOption?.unit || '/month';
135
129
  }
136
130
 
137
- // 更新原价和折扣
138
131
  const priceOriginalElement = document.querySelector(`[data-price-original="${plan.key}"]`) as HTMLElement;
139
132
  const priceDiscountElement = document.querySelector(`[data-price-discount="${plan.key}"]`) as HTMLElement;
140
133
 
@@ -158,17 +151,16 @@ export function MoneyPriceInteractive({
158
151
  if (priceDiscountElement) priceDiscountElement.style.display = 'none';
159
152
  }
160
153
 
161
- // 更新副标题
162
154
  const priceSubtitleElement = document.querySelector(`[data-price-subtitle="${plan.key}"]`) as HTMLElement;
163
155
  if (priceSubtitleElement && plan.showBillingSubTitle !== false) {
164
156
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
165
157
  priceSubtitleElement.textContent = billingOption?.subTitle || '';
166
158
  }
167
159
  });
168
- };
169
-
160
+ }, [config, data]);
161
+
170
162
  // 更新按钮样式
171
- const updateButtonStyles = (newBillingType: string) => {
163
+ const updateButtonStyles = useCallback((newBillingType: string) => {
172
164
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]') as HTMLButtonElement;
173
165
  const yearlyButton = document.querySelector('[data-billing-button="yearly"]') as HTMLButtonElement;
174
166
 
@@ -184,17 +176,16 @@ export function MoneyPriceInteractive({
184
176
  yearlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
185
177
  }
186
178
  }
187
- };
188
-
179
+ }, []);
180
+
189
181
  // 更新折扣信息
190
- const updateDiscountInfo = (newBillingType: string) => {
182
+ const updateDiscountInfo = useCallback((newBillingType: string) => {
191
183
  const discountInfoElement = document.querySelector('[data-discount-info]') as HTMLElement;
192
184
  if (!discountInfoElement) return;
193
185
 
194
186
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
195
187
  const providerConfig = getActiveProviderConfig(config);
196
188
 
197
- // 检查是否有折扣
198
189
  let hasDiscount = false;
199
190
  let discountPercent = 0;
200
191
 
@@ -207,7 +198,6 @@ export function MoneyPriceInteractive({
207
198
  }
208
199
  });
209
200
 
210
- // 清空内容
211
201
  discountInfoElement.innerHTML = '';
212
202
 
213
203
  if (hasDiscount && billingOption?.discountText) {
@@ -216,54 +206,8 @@ export function MoneyPriceInteractive({
216
206
  discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
217
207
  discountInfoElement.appendChild(discountBadge);
218
208
  }
219
- };
220
-
221
- // 使用 useRef 存储 root 实例,避免重复创建
222
- const rootsRef = React.useRef<Map<string, any>>(new Map());
209
+ }, [config, data]);
223
210
 
224
- // 动态替换按钮
225
- useEffect(() => {
226
- data.plans.forEach((plan: any) => {
227
- const placeholder = document.querySelector(`[data-button-placeholder="${plan.key}"]`);
228
- if (placeholder) {
229
- let root = rootsRef.current.get(plan.key);
230
-
231
- // 如果还没有创建 root,则创建一个
232
- if (!root) {
233
- root = createRoot(placeholder);
234
- rootsRef.current.set(plan.key, root);
235
- }
236
-
237
- // 渲染按钮
238
- root.render(
239
- <MoneyPriceButton
240
- planKey={plan.key}
241
- userContext={userContext}
242
- billingType={billingType}
243
- onLogin={handleLogin}
244
- onUpgrade={handleUpgrade}
245
- texts={data.buttonTexts}
246
- isProcessing={isProcessing}
247
- />
248
- );
249
- }
250
- });
251
- }, [userContext, billingType, isProcessing, data.plans, data.buttonTexts, handleLogin, handleUpgrade]);
252
-
253
- // 组件卸载时清理
254
- useEffect(() => {
255
- return () => {
256
- rootsRef.current.forEach((root) => {
257
- try {
258
- root.unmount();
259
- } catch (e) {
260
- // 忽略卸载错误
261
- }
262
- });
263
- rootsRef.current.clear();
264
- };
265
- }, []);
266
-
267
211
  // 处理月付/年付切换和 tooltip 功能
268
212
  useEffect(() => {
269
213
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]') as HTMLButtonElement;
@@ -290,7 +234,6 @@ export function MoneyPriceInteractive({
290
234
  yearlyButton.addEventListener('click', handleYearlyClick);
291
235
  }
292
236
 
293
- // 添加 tooltip 功能
294
237
  const tooltipHandlers: Array<{
295
238
  element: HTMLElement;
296
239
  handlers: {
@@ -332,11 +275,9 @@ export function MoneyPriceInteractive({
332
275
  });
333
276
  });
334
277
 
335
- // 初始化价格显示
336
278
  updatePriceDisplay(billingType);
337
279
  updateDiscountInfo(billingType);
338
280
 
339
- // 清理
340
281
  return () => {
341
282
  if (monthlyButton) {
342
283
  monthlyButton.removeEventListener('click', handleMonthlyClick);
@@ -345,15 +286,14 @@ export function MoneyPriceInteractive({
345
286
  yearlyButton.removeEventListener('click', handleYearlyClick);
346
287
  }
347
288
 
348
- // 清理 tooltip 事件监听器
349
289
  tooltipHandlers.forEach(({ element, handlers }) => {
350
290
  element.removeEventListener('mouseenter', handlers.mouseenter);
351
291
  element.removeEventListener('mousemove', handlers.mousemove);
352
292
  element.removeEventListener('mouseleave', handlers.mouseleave);
353
293
  });
354
294
  };
355
- }, [data]);
356
-
295
+ }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo]);
296
+
357
297
  // Tooltip 组件
358
298
  const Tooltip = ({ show, content, x, y }: typeof tooltip) => {
359
299
  if (!show) return null;
@@ -376,6 +316,24 @@ export function MoneyPriceInteractive({
376
316
  </div>
377
317
  );
378
318
  };
379
-
380
- return <Tooltip {...tooltip} />;
319
+
320
+ // 直接渲染按钮,确保宽度占满
321
+ return (
322
+ <>
323
+ {data.plans.map((plan: any) => (
324
+ <div key={plan.key} data-button-placeholder={plan.key}>
325
+ <MoneyPriceButton
326
+ planKey={plan.key}
327
+ userContext={userContext}
328
+ billingType={billingType}
329
+ onLogin={handleLogin}
330
+ onUpgrade={handleUpgrade}
331
+ texts={data.buttonTexts}
332
+ isProcessing={isProcessing}
333
+ />
334
+ </div>
335
+ ))}
336
+ <Tooltip {...tooltip} />
337
+ </>
338
+ );
381
339
  }
@@ -1,10 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { GradientButton } from '@third-ui/fuma/mdx';
3
2
  import { cn } from '@windrun-huaiin/lib/utils';
4
3
  import { getTranslations } from 'next-intl/server';
5
4
  import { getActiveProviderConfig } from './money-price-config-util';
6
5
  import { MoneyPriceInteractive } from './money-price-interactive';
7
- import type { MoneyPriceData, MoneyPriceProps } from './money-price-types';
6
+ import type { MoneyPriceProps, MoneyPriceData } from './money-price-types';
8
7
 
9
8
  export async function MoneyPrice({
10
9
  locale,
@@ -39,23 +38,19 @@ export async function MoneyPrice({
39
38
  currency: config.display.currency
40
39
  };
41
40
 
42
- // 获取激活的支付供应商配置
43
41
  const providerConfig = getActiveProviderConfig(config);
44
42
  const minPlanFeaturesCount = config.display.minFeaturesCount;
45
-
46
- // 使用默认计费类型进行静态渲染
43
+
47
44
  const defaultBilling = data.billingSwitch.defaultKey;
48
45
  const defaultBillingDisplay = data.billingSwitch.options.find(
49
46
  (opt: any) => opt.key === defaultBilling
50
47
  ) || data.billingSwitch.options[0];
51
48
 
52
- // 计算特性数量
53
49
  const maxFeaturesCount = Math.max(
54
50
  ...data.plans.map((plan: any) => plan.features?.length || 0),
55
51
  minPlanFeaturesCount || 0
56
52
  );
57
53
 
58
- // 处理卡片高度对齐
59
54
  const getFeatureRows = (plan: any) => {
60
55
  const features = plan.features || [];
61
56
  const filled = [...features];
@@ -63,7 +58,6 @@ export async function MoneyPrice({
63
58
  return filled;
64
59
  };
65
60
 
66
- // 静态价格渲染逻辑
67
61
  function renderPrice(plan: any, billingKey = data.billingSwitch.defaultKey) {
68
62
  const productConfig = providerConfig.products[plan.key as 'free' | 'pro' | 'ultra'];
69
63
  const pricing = productConfig.plans[billingKey as 'monthly' | 'yearly'];
@@ -72,7 +66,6 @@ export async function MoneyPrice({
72
66
  ) || defaultBillingDisplay;
73
67
  const billingSubTitle = currentBillingDisplay?.subTitle || '';
74
68
 
75
- // 免费计划
76
69
  if (pricing.amount === 0) {
77
70
  return (
78
71
  <div className="flex flex-col items-start w-full" data-price-container={plan.key}>
@@ -92,7 +85,6 @@ export async function MoneyPrice({
92
85
  );
93
86
  }
94
87
 
95
- // 付费计划
96
88
  const hasDiscount = pricing.discountPercent && pricing.discountPercent > 0;
97
89
  const unit = currentBillingDisplay.unit || '';
98
90
  let discountText = '';
@@ -140,7 +132,6 @@ export async function MoneyPrice({
140
132
 
141
133
  return (
142
134
  <section id="money-pricing" className={cn("px-4 py-10 md:px-16 md:py-16 mx-auto max-w-7xl scroll-mt-10", sectionClassName)}>
143
- {/* 标题和副标题 */}
144
135
  <h2 className="text-3xl md:text-4xl font-bold text-center mb-3">
145
136
  {data.title}
146
137
  </h2>
@@ -148,7 +139,6 @@ export async function MoneyPrice({
148
139
  {data.subtitle}
149
140
  </p>
150
141
 
151
- {/* 计费切换按钮 */}
152
142
  <div className="flex flex-col items-center">
153
143
  <div className="flex items-center relative mb-3">
154
144
  <div className="flex bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-full p-1" data-billing-switch>
@@ -179,12 +169,10 @@ export async function MoneyPrice({
179
169
  </div>
180
170
  </div>
181
171
 
182
- {/* 折扣信息 - 默认计费的静态渲染 */}
183
172
  <div className="h-8 flex items-center justify-center mb-3" data-discount-info>
184
173
  {(() => {
185
174
  const opt = data.billingSwitch.options.find((opt: any) => opt.key === data.billingSwitch.defaultKey);
186
175
 
187
- // 检查默认计费类型是否有折扣
188
176
  let hasDiscount = false;
189
177
  let discountPercent = 0;
190
178
 
@@ -211,7 +199,6 @@ export async function MoneyPrice({
211
199
  </div>
212
200
  </div>
213
201
 
214
- {/* 价格卡片区域 */}
215
202
  <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
216
203
  {data.plans.map((plan: any, _idx: number) => (
217
204
  <div
@@ -224,7 +211,6 @@ export async function MoneyPrice({
224
211
  )}
225
212
  style={{ minHeight: maxFeaturesCount*100 }}
226
213
  >
227
- {/* 标题和标签 */}
228
214
  <div className="flex items-center gap-2 mb-2">
229
215
  <span className="text-xl font-bold text-gray-900 dark:text-gray-100">{plan.title}</span>
230
216
  {plan.titleTags && plan.titleTags.map((tag: string, i: number) => (
@@ -234,14 +220,11 @@ export async function MoneyPrice({
234
220
  ))}
235
221
  </div>
236
222
 
237
- {/* 价格和单位/折扣 */}
238
223
  {renderPrice(plan)}
239
224
 
240
- {/* 特性列表 */}
241
225
  <ul className="flex-1 mb-6 mt-4">
242
226
  {getFeatureRows(plan).map((feature: any, i: number) => (
243
227
  <li key={i} className="flex items-center gap-2 mb-2 min-h-[28px]" data-feature-item={`${plan.key}-${i}`}>
244
- {/* 图标 */}
245
228
  {feature ? (
246
229
  <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1">
247
230
  {feature.icon ? <span>{feature.icon}</span> : <span className="font-bold">✓</span>}
@@ -249,13 +232,11 @@ export async function MoneyPrice({
249
232
  ) : (
250
233
  <span className="inline-flex items-center justify-center w-5 h-5 rounded-full mr-1">&nbsp;</span>
251
234
  )}
252
- {/* 标签 */}
253
235
  {feature && feature.tag && (
254
236
  <span className="px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle">
255
237
  {feature.tag}
256
238
  </span>
257
239
  )}
258
- {/* 描述 + 提示 */}
259
240
  {feature ? (
260
241
  <span className="relative group cursor-pointer text-sm text-gray-800 dark:text-gray-200">
261
242
  {feature.description}
@@ -279,23 +260,15 @@ export async function MoneyPrice({
279
260
  ))}
280
261
  </ul>
281
262
 
282
- {/* 占位符,确保卡片高度一致 */}
283
263
  <div className="flex-1" />
284
264
 
285
- {/* 按钮占位,客户端会替换 */}
286
- <div data-button-placeholder={plan.key}>
287
- <GradientButton
288
- title={data.buttonTexts.getStarted}
289
- disabled={true}
290
- align="center"
291
- className="w-full"
292
- />
265
+ <div data-button-placeholder={plan.key} className="w-full">
266
+ {/* MoneyPriceInteractive will render the button here */}
293
267
  </div>
294
268
  </div>
295
269
  ))}
296
270
  </div>
297
271
 
298
- {/* 客户端增强组件 */}
299
272
  <MoneyPriceInteractive
300
273
  data={data}
301
274
  config={config}