@windrun-huaiin/third-ui 30.1.0 → 31.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +109 -143
  2. package/dist/ai/ai-prompt-textarea.js +5 -5
  3. package/dist/ai/ai-prompt-textarea.mjs +5 -5
  4. package/dist/clerk/clerk-auth-appearance.d.ts +13 -0
  5. package/dist/clerk/clerk-auth-appearance.js +19 -0
  6. package/dist/clerk/clerk-auth-appearance.mjs +15 -0
  7. package/dist/clerk/clerk-auth-modal-appearance.d.ts +12 -0
  8. package/dist/clerk/clerk-auth-modal-appearance.js +17 -0
  9. package/dist/clerk/clerk-auth-modal-appearance.mjs +14 -0
  10. package/dist/clerk/clerk-page-context-generator.js +3 -3
  11. package/dist/clerk/clerk-page-context-generator.mjs +3 -3
  12. package/dist/clerk/clerk-page-generator.js +4 -4
  13. package/dist/clerk/clerk-page-generator.mjs +4 -4
  14. package/dist/clerk/clerk-user-client.js +2 -1
  15. package/dist/clerk/clerk-user-client.mjs +2 -1
  16. package/dist/clerk/fingerprint/fingerprint-client.d.ts +10 -10
  17. package/dist/clerk/fingerprint/fingerprint-client.js +20 -20
  18. package/dist/clerk/fingerprint/fingerprint-client.mjs +20 -20
  19. package/dist/clerk/fingerprint/fingerprint-provider.d.ts +3 -3
  20. package/dist/clerk/fingerprint/fingerprint-provider.js +8 -8
  21. package/dist/clerk/fingerprint/fingerprint-provider.mjs +8 -8
  22. package/dist/clerk/fingerprint/fingerprint-server.d.ts +12 -12
  23. package/dist/clerk/fingerprint/fingerprint-server.js +17 -17
  24. package/dist/clerk/fingerprint/fingerprint-server.mjs +17 -17
  25. package/dist/clerk/fingerprint/fingerprint-shared.d.ts +3 -3
  26. package/dist/clerk/fingerprint/fingerprint-shared.js +10 -10
  27. package/dist/clerk/fingerprint/fingerprint-shared.mjs +10 -10
  28. package/dist/clerk/fingerprint/types.d.ts +0 -1
  29. package/dist/clerk/fingerprint/use-fingerprint.js +7 -7
  30. package/dist/clerk/fingerprint/use-fingerprint.mjs +7 -7
  31. package/dist/clerk/signin-with-fingerprint-client.d.ts +2 -2
  32. package/dist/clerk/signin-with-fingerprint-client.js +7 -6
  33. package/dist/clerk/signin-with-fingerprint-client.mjs +7 -6
  34. package/dist/clerk/signup-button-with-fingerprint-client.js +6 -4
  35. package/dist/clerk/signup-button-with-fingerprint-client.mjs +6 -4
  36. package/dist/clerk/signup-with-fingerprint-client.d.ts +2 -2
  37. package/dist/clerk/signup-with-fingerprint-client.js +7 -6
  38. package/dist/clerk/signup-with-fingerprint-client.mjs +7 -6
  39. package/dist/fuma/heavy/mermaid.js +1 -1
  40. package/dist/fuma/heavy/mermaid.mjs +1 -1
  41. package/dist/fuma/site-x.js +0 -1
  42. package/dist/fuma/site-x.mjs +0 -1
  43. package/dist/main/calendar/calendar-date-range-input.js +1 -1
  44. package/dist/main/calendar/calendar-date-range-input.mjs +1 -1
  45. package/dist/main/credit/types.d.ts +8 -8
  46. package/dist/main/money-price/index.d.ts +1 -1
  47. package/dist/main/money-price/money-price-button.js +10 -10
  48. package/dist/main/money-price/money-price-button.mjs +10 -10
  49. package/dist/main/money-price/money-price-config-util.d.ts +30 -30
  50. package/dist/main/money-price/money-price-config-util.js +48 -48
  51. package/dist/main/money-price/money-price-config-util.mjs +48 -48
  52. package/dist/main/money-price/money-price-interactive.js +30 -18
  53. package/dist/main/money-price/money-price-interactive.mjs +30 -18
  54. package/dist/main/money-price/money-price-types.d.ts +7 -1
  55. package/dist/main/money-price/money-price-types.js +2 -2
  56. package/dist/main/money-price/money-price-types.mjs +2 -2
  57. package/dist/main/money-price/server.d.ts +1 -1
  58. package/dist/main/pill-select/x-pill-select.js +2 -2
  59. package/dist/main/pill-select/x-pill-select.mjs +2 -2
  60. package/dist/main/server.d.ts +1 -1
  61. package/package.json +13 -7
  62. package/src/ai/ai-prompt-textarea.tsx +6 -6
  63. package/src/clerk/clerk-auth-appearance.ts +16 -0
  64. package/src/clerk/clerk-page-context-generator.tsx +3 -5
  65. package/src/clerk/clerk-page-generator.tsx +9 -8
  66. package/src/clerk/clerk-user-client.tsx +14 -5
  67. package/src/clerk/fingerprint/fingerprint-client.ts +20 -20
  68. package/src/clerk/fingerprint/fingerprint-provider.tsx +11 -11
  69. package/src/clerk/fingerprint/fingerprint-server.ts +17 -17
  70. package/src/clerk/fingerprint/fingerprint-shared.ts +10 -10
  71. package/src/clerk/fingerprint/types.ts +0 -1
  72. package/src/clerk/fingerprint/use-fingerprint.ts +7 -7
  73. package/src/clerk/signin-with-fingerprint-client.tsx +7 -7
  74. package/src/clerk/signup-button-with-fingerprint-client.tsx +7 -5
  75. package/src/clerk/signup-with-fingerprint-client.tsx +7 -7
  76. package/src/fuma/base/custom-home-layout.tsx +4 -4
  77. package/src/fuma/heavy/mermaid.tsx +1 -1
  78. package/src/fuma/site-x.tsx +0 -1
  79. package/src/main/calendar/calendar-date-range-input.tsx +1 -1
  80. package/src/main/credit/types.ts +8 -8
  81. package/src/main/gallery/gallery-mobile-swiper.tsx +0 -1
  82. package/src/main/gallery/gallery-server.tsx +2 -2
  83. package/src/main/money-price/index.ts +2 -0
  84. package/src/main/money-price/money-price-button.tsx +10 -10
  85. package/src/main/money-price/money-price-config-util.ts +49 -49
  86. package/src/main/money-price/money-price-interactive.tsx +40 -20
  87. package/src/main/money-price/money-price-types.ts +21 -14
  88. package/src/main/money-price/server.ts +2 -0
  89. package/src/main/pill-select/x-pill-select.tsx +2 -2
  90. package/src/main/server.ts +3 -1
  91. package/src/styles/third-ui.css +8 -0
@@ -21,7 +21,7 @@ import { FINGERPRINT_SOURCE_REFER, isDebugFingerprintId, isValidFingerprintId }
21
21
  * Accepts configuration to customize API endpoint and behavior
22
22
  */
23
23
  export function useFingerprint(config: FingerprintConfig): UseFingerprintResult {
24
- // 服务端渲染检查
24
+ // Server-side rendering guard.
25
25
  if (typeof window === 'undefined') {
26
26
  return {
27
27
  fingerprintId: null,
@@ -51,7 +51,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
51
51
  }, []);
52
52
 
53
53
  /**
54
- * 第一阶段:初始化fingerprint ID
54
+ * Phase 1: initialize fingerprint ID.
55
55
  */
56
56
  const initializeFingerprintId = useCallback(async () => {
57
57
  try {
@@ -68,7 +68,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
68
68
  }, []);
69
69
 
70
70
  /**
71
- * 第二阶段:初始化匿名用户
71
+ * Phase 2: initialize anonymous user.
72
72
  */
73
73
  const initializeAnonymousUser = useCallback(async () => {
74
74
  if (!fingerprintId) {
@@ -140,7 +140,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
140
140
  }, [fingerprintId, config.apiEndpoint, isInitialized]);
141
141
 
142
142
  /**
143
- * 刷新用户数据 - 使用POST请求(后端支持upsert逻辑)
143
+ * Refresh user data with a POST request; the backend supports upsert semantics.
144
144
  */
145
145
  const refreshUserData = useCallback(async () => {
146
146
  if (!fingerprintId) {
@@ -180,7 +180,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
180
180
  }
181
181
  }, [fingerprintId, config.apiEndpoint]);
182
182
 
183
- // 第一阶段:页面加载完成后生成指纹ID
183
+ // Phase 1: generate fingerprint ID after page load.
184
184
  useEffect(() => {
185
185
  const initFingerprint = async () => {
186
186
  setIsLoading(true);
@@ -191,11 +191,11 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
191
191
  initFingerprint();
192
192
  }, [initializeFingerprintId]);
193
193
 
194
- // 第二阶段:有指纹ID后直接初始化用户(后端支持upsert逻辑)
194
+ // Phase 2: initialize the user once a fingerprint ID is available; the backend supports upsert semantics.
195
195
  useEffect(() => {
196
196
  if (!fingerprintId || isInitialized || isLoading || error || config.autoInitialize === false) return;
197
197
 
198
- // 直接使用 POST 请求,后端会处理查询-不存在则创建的逻辑
198
+ // Use POST directly; the backend handles lookup and create-if-missing behavior.
199
199
  initializeAnonymousUser();
200
200
  }, [fingerprintId, isInitialized, isLoading, error, initializeAnonymousUser, config.autoInitialize]);
201
201
 
@@ -2,17 +2,18 @@
2
2
 
3
3
  import { SignIn } from '@clerk/nextjs';
4
4
  import { useEffect } from 'react';
5
+ import { clerkAuthPageAppearance } from './clerk-auth-appearance';
5
6
  import { useFingerprintContextSafe } from './fingerprint/fingerprint-provider';
6
7
 
7
8
  /**
8
9
  * SignIn component with fingerprint awareness
9
- * 如果没有FingerprintProvider,会优雅降级为普通SignIn组件
10
- * 如果有FingerprintProvider,会处理fingerprint相关逻辑
10
+ * Falls back to the standard SignIn component when FingerprintProvider is absent.
11
+ * Handles fingerprint-related metadata when FingerprintProvider is available.
11
12
  */
12
13
  export function SignInWithFingerprint() {
13
14
  const fingerprintContext = useFingerprintContextSafe();
14
15
 
15
- // 如果没有fingerprint context,使用默认值
16
+ // Use defaults when fingerprint context is unavailable.
16
17
  const {
17
18
  fingerprintId = null,
18
19
  xUser = null,
@@ -20,19 +21,18 @@ export function SignInWithFingerprint() {
20
21
  initializeAnonymousUser = async () => {}
21
22
  } = fingerprintContext || {};
22
23
 
23
- // 准备传递给Clerkmetadata,包含匿名用户信息
24
+ // Prepare Clerk metadata with anonymous user information.
24
25
  const unsafeMetadata = {
25
26
  user_id: xUser?.userId || null,
26
27
  fingerprint_id: fingerprintId || null,
27
28
  };
28
29
 
29
- // 确保匿名用户已初始化
30
+ // Ensure the anonymous user has been initialized.
30
31
  useEffect(() => {
31
32
  if (!isInitialized && fingerprintId) {
32
33
  initializeAnonymousUser();
33
34
  }
34
35
  }, [fingerprintId, isInitialized, initializeAnonymousUser]);
35
36
 
36
- return <SignIn unsafeMetadata={unsafeMetadata} />;
37
+ return <SignIn appearance={clerkAuthPageAppearance} unsafeMetadata={unsafeMetadata} />;
37
38
  }
38
-
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { SignUpButton, useClerk } from '@clerk/nextjs';
4
4
  import { useEffect } from 'react';
5
+ import { clerkAuthModalAppearance } from './clerk-auth-appearance';
5
6
  import { useFingerprintContextSafe } from './fingerprint/fingerprint-provider';
6
7
 
7
8
  interface SignUpButtonWithFingerprintProps {
@@ -15,7 +16,7 @@ export function SignUpButtonWithFingerprint({
15
16
  }: SignUpButtonWithFingerprintProps) {
16
17
  if (mode === 'redirect') {
17
18
  return (
18
- // 重定向模式则直接跳转到自定义注册页面
19
+ // Redirect mode navigates directly to the custom sign-up page.
19
20
  <SignUpButton>
20
21
  <button
21
22
  className="w-16 sm:w-20 h-8 sm:h-9 px-1.5 sm:px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-xs sm:text-sm whitespace-nowrap"
@@ -26,10 +27,10 @@ export function SignUpButtonWithFingerprint({
26
27
  )
27
28
  }
28
29
 
29
- // 弹框模式则需要自定义注册按钮
30
+ // Modal mode requires a custom sign-up button.
30
31
  const fingerprintContext = useFingerprintContextSafe();
31
32
 
32
- // 如果没有fingerprint context,使用默认值
33
+ // Use defaults when fingerprint context is unavailable.
33
34
  const {
34
35
  fingerprintId = null,
35
36
  xUser = null,
@@ -43,7 +44,7 @@ export function SignUpButtonWithFingerprint({
43
44
  fingerprint_id: fingerprintId || null,
44
45
  };
45
46
 
46
- // 确保匿名用户已初始化
47
+ // Ensure the anonymous user has been initialized.
47
48
  useEffect(() => {
48
49
  if (!isInitialized && fingerprintId) {
49
50
  initializeAnonymousUser();
@@ -55,6 +56,7 @@ export function SignUpButtonWithFingerprint({
55
56
 
56
57
  const handleClick = () => {
57
58
  openSignUp({
59
+ appearance: clerkAuthModalAppearance,
58
60
  unsafeMetadata,
59
61
  });
60
62
  };
@@ -67,4 +69,4 @@ export function SignUpButtonWithFingerprint({
67
69
  {signUp}
68
70
  </button>
69
71
  );
70
- }
72
+ }
@@ -2,17 +2,18 @@
2
2
 
3
3
  import { SignUp } from '@clerk/nextjs';
4
4
  import { useEffect } from 'react';
5
+ import { clerkAuthPageAppearance } from './clerk-auth-appearance';
5
6
  import { useFingerprintContextSafe } from './fingerprint/fingerprint-provider';
6
7
 
7
8
  /**
8
9
  * SignUp component with fingerprint awareness
9
- * 如果没有FingerprintProvider,会优雅降级为普通SignUp组件
10
- * 如果有FingerprintProvider,会处理fingerprint相关逻辑
10
+ * Falls back to the standard SignUp component when FingerprintProvider is absent.
11
+ * Handles fingerprint-related metadata when FingerprintProvider is available.
11
12
  */
12
13
  export function SignUpWithFingerprint() {
13
14
  const fingerprintContext = useFingerprintContextSafe();
14
15
 
15
- // 如果没有fingerprint context,使用默认值
16
+ // Use defaults when fingerprint context is unavailable.
16
17
  const {
17
18
  fingerprintId = null,
18
19
  xUser = null,
@@ -20,19 +21,18 @@ export function SignUpWithFingerprint() {
20
21
  initializeAnonymousUser = async () => {}
21
22
  } = fingerprintContext || {};
22
23
 
23
- // 准备传递给Clerkmetadata,包含匿名用户信息
24
+ // Prepare Clerk metadata with anonymous user information.
24
25
  const unsafeMetadata = {
25
26
  user_id: xUser?.userId || null,
26
27
  fingerprint_id: fingerprintId || null,
27
28
  };
28
29
 
29
- // 确保匿名用户已初始化
30
+ // Ensure the anonymous user has been initialized.
30
31
  useEffect(() => {
31
32
  if (!isInitialized && fingerprintId) {
32
33
  initializeAnonymousUser();
33
34
  }
34
35
  }, [fingerprintId, isInitialized, initializeAnonymousUser]);
35
36
 
36
- return <SignUp unsafeMetadata={unsafeMetadata} />;
37
+ return <SignUp appearance={clerkAuthPageAppearance} unsafeMetadata={unsafeMetadata} />;
37
38
  }
38
-
@@ -11,10 +11,10 @@ import {
11
11
  type MobileMenuAction,
12
12
  } from './custom-header';
13
13
 
14
- // - bannerHeight/headerHeight 换成你项目期望的 rem 值即可(如果没有 Banner 就把 bannerHeight 设成 0)。
15
- // - layoutStyle 同时把变量传给 HomeLayout main 元素,这样内容整体会往下错开,不需要 has-banner/no-banner class。
16
- // - CustomHomeHeader 直接接受 HomeLayout 的各类 props(linksnavsearchTogglethemeSwitchi18n 等),内部会复用 Fumadocs 原本的导航功能。
17
- // - Banner 部分仍然可以用你现有的 FumaBannerSuit(或者任何自定义 Banner 组件),因为 Header 是固定定位、z-index 也处理好了,开关只影响 bannerHeight
14
+ // - Set bannerHeight/headerHeight to the rem values expected by the project. Use bannerHeight = 0 when there is no banner.
15
+ // - layoutStyle passes the variables to HomeLayout's main element, offsetting content without has-banner/no-banner classes.
16
+ // - CustomHomeHeader accepts HomeLayout props such as links, nav, searchToggle, themeSwitch, and i18n, then reuses Fumadocs navigation behavior.
17
+ // - The banner can still use FumaBannerSuit or any custom banner component. Header positioning and z-index are already handled; the toggle only affects bannerHeight.
18
18
 
19
19
  export interface CustomHomeLayoutProps {
20
20
  locale: string;
@@ -179,7 +179,7 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, handDra
179
179
  // prevent browser-level zoom (touchpad pinch/shortcut) from taking effect when the dialog is open
180
180
  useEffect(() => {
181
181
  if (!open) return;
182
- // 初次打开时,默认放大到 400%
182
+ // Init default zoom out 400%
183
183
  resetTransform();
184
184
  const onGlobalWheel = (ev: WheelEvent) => {
185
185
  if (ev.ctrlKey || ev.metaKey) {
@@ -10,7 +10,6 @@ export type SiteXProps = Omit<HTMLAttributes<HTMLSpanElement>, 'type'> & {
10
10
  };
11
11
 
12
12
  export async function SiteX({ locale, type, namespace, tKey, className, ...props }: SiteXProps) {
13
- // 默认命名空间和key
14
13
  let ns = namespace;
15
14
  let key = tKey;
16
15
  if (!ns) {
@@ -29,7 +29,7 @@ export type CalendarDateRangeInputProps = {
29
29
 
30
30
  type DateRangeInputPressKey = 'clear';
31
31
 
32
- const DEFAULT_PLACEHOLDER = '滑动窗口日期';
32
+ const DEFAULT_PLACEHOLDER = 'Slide pick Date';
33
33
  const DEFAULT_RANGE_DAYS = 7;
34
34
  const CLEAR_PRESS_FEEDBACK_MS = 180;
35
35
 
@@ -47,21 +47,21 @@ export interface CreditCTAConfig {
47
47
  export type CreditBucketStatus = 'active' | 'expiringSoon' | 'expired';
48
48
 
49
49
  export interface CreditBucket {
50
- /** 业务方自定义的积分类型标识,用于映射翻译或埋点 */
50
+ /** Business-defined credit type identifier for translation mapping or analytics. */
51
51
  kind: string;
52
- /** 若提供则使用该名称,否则由组件根据 kind 使用默认翻译 */
52
+ /** Display name override; otherwise the component uses the default translation for kind. */
53
53
  label?: string;
54
- /** 当前积分余额 */
54
+ /** Current credit balance. */
55
55
  balance: number;
56
- /** 该类型积分的额度上限 */
56
+ /** Credit limit for this credit type. */
57
57
  limit: number;
58
- /** 可选状态标签,用于强调过期等状态 */
58
+ /** Optional status label for highlighting states such as expiration. */
59
59
  status?: CreditBucketStatus;
60
- /** 积分过期时间(本地时区字符串),用于组件内部推导状态 */
60
+ /** Credit expiration time as a local time string, used to derive component state. */
61
61
  expiresAt?: string;
62
- /** 进度百分比(0-100);未提供时组件按 balance/limit 计算 */
62
+ /** Progress percentage from 0 to 100; computed from balance/limit when omitted. */
63
63
  progressPercent?: number;
64
- /** 任何额外说明,如剩余天数、使用限制等 */
64
+ /** Additional details, such as remaining days or usage limits. */
65
65
  description?: string;
66
66
  }
67
67
 
@@ -19,7 +19,6 @@ const swiperThemeStyle = {
19
19
  export function GalleryMobileSwiper({ items }: Props) {
20
20
  return (
21
21
  <div className="block sm:hidden px-4">
22
- {/* 外层容器:强制 maxWidth,防止任何溢出 */}
23
22
  <div
24
23
  className="w-full overflow-hidden"
25
24
  style={{ maxWidth: "min(calc(100vw - 48px), 350px)", margin: "0 auto" }}
@@ -33,10 +33,10 @@ export async function Gallery({ locale, sectionClassName, button }: GalleryProps
33
33
  </h2>
34
34
  <p className="text-center max-w-2xl mx-auto mb-16">{data.description}</p>
35
35
 
36
- {/* 移动端轮播 */}
36
+ {/* Mobile swiper */}
37
37
  <GalleryMobileSwiper items={data.items} />
38
38
 
39
- {/* 桌面端网格 */}
39
+ {/* Desktop grid */}
40
40
  <GalleryDesktopGrid items={data.items} />
41
41
 
42
42
  {button && <div className="text-center mx-auto mt-12 max-w-[85vw]">{button}</div>}
@@ -5,6 +5,8 @@ export { MoneyPriceButton } from './money-price-button';
5
5
  export type {
6
6
  MoneyPriceConfig,
7
7
  MoneyPriceData,
8
+ MoneyPriceAnimeTone,
9
+ MoneyPriceStrictDiffAnime,
8
10
  InitUserContext,
9
11
  MoneyPriceInteractiveProps,
10
12
  MoneyPriceButtonProps,
@@ -44,9 +44,9 @@ export function MoneyPriceButton({
44
44
  return 0;
45
45
  };
46
46
 
47
- // OneTime 模式的按钮配置
47
+ // Button configuration for one-time mode.
48
48
  const getOnetimeButtonConfig = () => {
49
- // 匿名用户:所有卡片都显示登录按钮
49
+ // Anonymous users: show the sign-in button on every card.
50
50
  if (!isAuthenticated) {
51
51
  return {
52
52
  text: texts.getStarted,
@@ -57,12 +57,12 @@ export function MoneyPriceButton({
57
57
  }
58
58
 
59
59
  if (subscriptionStatus === UserState.Anonymous) {
60
- // 已登录但状态未知 视为 FreeUser
60
+ // Signed in but status unknown: treat as FreeUser.
61
61
  console.warn('Clerk is authed OK but user is anonymous!');
62
62
  return { text: '', disabled: true, hidden: true };
63
63
  }
64
64
 
65
- // 登录用户:OneTime 模式下所有卡片都显示购买积分按钮
65
+ // Signed-in users: show the buy-credits button on every card in one-time mode.
66
66
  return {
67
67
  text: texts.buyCredits || texts.upgrade,
68
68
  onClick: () => onAction(planKey, billingType),
@@ -71,9 +71,9 @@ export function MoneyPriceButton({
71
71
  };
72
72
  };
73
73
 
74
- // 订阅模式的按钮配置
74
+ // Button configuration for subscription mode.
75
75
  const getSubscriptionButtonConfig = () => {
76
- // 匿名用户
76
+ // Anonymous users.
77
77
  if (!isAuthenticated) {
78
78
  const getButtonText = () => {
79
79
  switch (planKey) {
@@ -96,7 +96,7 @@ export function MoneyPriceButton({
96
96
  };
97
97
  }
98
98
 
99
- // 已登录用户
99
+ // Signed-in users.
100
100
  switch (subscriptionStatus) {
101
101
  case UserState.FreeUser: {
102
102
  if (planTier === 'F1') {
@@ -115,7 +115,7 @@ export function MoneyPriceButton({
115
115
  }
116
116
 
117
117
  case UserState.ProUser: {
118
- // 不允许降级到 Free
118
+ // Do not allow downgrades to Free.
119
119
  if (planTier === 'F1') {
120
120
  return { hidden: true };
121
121
  }
@@ -209,13 +209,13 @@ export function MoneyPriceButton({
209
209
  }
210
210
 
211
211
  default:
212
- // 已登录但状态未知 视为 FreeUser
212
+ // Signed in but status unknown: treat as FreeUser.
213
213
  console.warn('Clerk is authed OK but user is anonymous!');
214
214
  return { text: '', disabled: true, hidden: true };
215
215
  }
216
216
  };
217
217
 
218
- // 主要的按钮配置函数
218
+ // Main button configuration function.
219
219
  const getButtonConfig = () => {
220
220
  if (billingType === 'onetime') {
221
221
  return getOnetimeButtonConfig();
@@ -1,27 +1,27 @@
1
1
  /**
2
2
  * Money Price Configuration
3
- * 价格组件配置文件
3
+ * Pricing component configuration.
4
4
  */
5
5
 
6
6
  import type { MoneyPriceConfig, PaymentProviderConfig, EnhancePricePlan } from './money-price-types';
7
7
 
8
8
  /**
9
- * 获取当前激活的支付供应商配置
9
+ * Get the currently active payment provider configuration.
10
10
  *
11
- * 🔒 安全设计:
12
- * - util层负责从config中提取激活的provider配置
13
- * - 只返回提取的结果,不暴露任何config结构
14
- * - 调用方(应用层)通过wrapper隐藏config对象
11
+ * Security design:
12
+ * - The utility layer extracts the active provider configuration from the config.
13
+ * - Only the extracted result is returned; the full config structure is not exposed.
14
+ * - Application-level wrappers hide the config object from callers.
15
15
  *
16
- * @param config - MoneyPriceConfig对象(由应用层提供)
17
- * @returns 当前激活的支付供应商配置
16
+ * @param config - MoneyPriceConfig object provided by the application layer.
17
+ * @returns The currently active payment provider configuration.
18
18
  */
19
19
  export function getActiveProviderConfigUtil(config: MoneyPriceConfig): PaymentProviderConfig {
20
20
  const provider = config.activeProvider;
21
21
  return config.paymentProviders[provider];
22
22
  }
23
23
 
24
- // 辅助函数:获取特定产品的价格信息
24
+ // Helper: get pricing information for a specific product.
25
25
  export function getProductPricing(
26
26
  productKey: 'F1' | 'P2' | 'U3',
27
27
  billingType: string,
@@ -30,10 +30,10 @@ export function getProductPricing(
30
30
  ): EnhancePricePlan {
31
31
  const providerConfig = config.paymentProviders[provider];
32
32
 
33
- // 如果是 onetime 类型,尝试从积分包中获取
33
+ // For one-time billing, try to resolve pricing from credit packs.
34
34
  if (billingType === 'onetime') {
35
35
  const creditPacks = providerConfig.creditPackProducts;
36
- // 直接使用相同的 keyF1->F1, P2->P2, U3->U3
36
+ // Use the same product key directly: F1 -> F1, P2 -> P2, U3 -> U3.
37
37
  if (creditPacks && creditPacks[productKey]) {
38
38
  const pack = creditPacks[productKey];
39
39
  return {
@@ -45,7 +45,7 @@ export function getProductPricing(
45
45
  }
46
46
  }
47
47
 
48
- // 否则从订阅产品中获取
48
+ // Otherwise resolve pricing from subscription products.
49
49
  const products = providerConfig.subscriptionProducts || providerConfig.products;
50
50
  if (products && products[productKey] && products[productKey].plans[billingType]) {
51
51
  return products[productKey].plans[billingType];
@@ -54,19 +54,19 @@ export function getProductPricing(
54
54
  throw new Error(`Product pricing not found for ${productKey} ${billingType}`);
55
55
  }
56
56
 
57
- // ============ 安全的util函数 - 只接收简单的映射表参数,不暴露任何config细节 ============
57
+ // ============ Safe utility functions: accept only simple mapping inputs and do not expose config internals ============
58
58
 
59
59
  /**
60
- * 根据 priceId 获取对应的积分数量
60
+ * Get the credit amount for a price ID.
61
61
  *
62
- * 🔒 安全设计:
63
- * - util层负责解析config,提取所需数据
64
- * - 只返回查询结果,不暴露任何config结构
65
- * - 调用方(应用层)通过wrapper隐藏config对象
62
+ * Security design:
63
+ * - The utility layer parses the config and extracts only the required data.
64
+ * - Only the query result is returned; the full config structure is not exposed.
65
+ * - Application-level wrappers hide the config object from callers.
66
66
  *
67
- * @param priceId - 查询的价格ID
68
- * @param config - MoneyPriceConfig对象(由应用层提供)
69
- * @returns 对应的积分数量,或null
67
+ * @param priceId - Price ID to query.
68
+ * @param config - MoneyPriceConfig object provided by the application layer.
69
+ * @returns The matching credit amount, or null.
70
70
  */
71
71
  export function getCreditsFromPriceIdUtil(
72
72
  priceId: string | undefined,
@@ -76,9 +76,9 @@ export function getCreditsFromPriceIdUtil(
76
76
  return null;
77
77
  }
78
78
 
79
- // 遍历所有支付提供商
79
+ // Iterate through all payment providers.
80
80
  for (const provider of Object.values(config.paymentProviders)) {
81
- // 遍历订阅产品
81
+ // Iterate through subscription products.
82
82
  const subscriptionProducts = (
83
83
  provider.subscriptionProducts || provider.products
84
84
  ) as Record<string, any>;
@@ -96,7 +96,7 @@ export function getCreditsFromPriceIdUtil(
96
96
  }
97
97
  }
98
98
 
99
- // 遍历积分包产品
99
+ // Iterate through credit pack products.
100
100
  const creditPacks = provider.creditPackProducts as Record<string, any>;
101
101
  if (creditPacks) {
102
102
  for (const pack of Object.values(creditPacks)) {
@@ -112,23 +112,23 @@ export function getCreditsFromPriceIdUtil(
112
112
  }
113
113
 
114
114
  /**
115
- * 根据查询参数获取价格配置
115
+ * Get price configuration by query parameters.
116
116
  *
117
- * 支持三种查询方式:
118
- * 1. priceId 直接查询
119
- * 2. plan + billingType 查询
120
- * 3. plan 查询
117
+ * Supported query modes:
118
+ * 1. Query directly by priceId.
119
+ * 2. Query by plan and billingType.
120
+ * 3. Query by plan.
121
121
  *
122
- * 🔒 安全设计:
123
- * - util层负责解析config,提取和匹配数据
124
- * - 只返回查询结果,不暴露任何config结构
125
- * - 调用方(应用层)通过wrapper隐藏config对象
122
+ * Security design:
123
+ * - The utility layer parses the config and extracts only matching data.
124
+ * - Only the query result is returned; the full config structure is not exposed.
125
+ * - Application-level wrappers hide the config object from callers.
126
126
  *
127
- * @param priceId - 查询的价格ID(可选)
128
- * @param plan - 查询的套餐名称如'P2''U3'(可选)
129
- * @param billingType - 查询的计费类型如'monthly''yearly'(可选)
130
- * @param config - MoneyPriceConfig对象(由应用层提供)
131
- * @returns 匹配的价格配置,包含计算好的元数据(priceNamedescriptioninterval
127
+ * @param priceId - Optional price ID to query.
128
+ * @param plan - Optional plan name, such as 'P2' or 'U3'.
129
+ * @param billingType - Optional billing type, such as 'monthly' or 'yearly'.
130
+ * @param config - MoneyPriceConfig object provided by the application layer.
131
+ * @returns The matching price config with derived metadata: priceName, description, and interval.
132
132
  */
133
133
  export function getPriceConfigUtil(
134
134
  priceId: string | undefined,
@@ -136,9 +136,9 @@ export function getPriceConfigUtil(
136
136
  billingType: string | undefined,
137
137
  config: MoneyPriceConfig
138
138
  ): (EnhancePricePlan & { priceName: string; description: string; interval?: string }) | null {
139
- // 遍历所有支付提供商
139
+ // Iterate through all payment providers.
140
140
  for (const provider of Object.values(config.paymentProviders)) {
141
- // 遍历订阅产品
141
+ // Iterate through subscription products.
142
142
  const subscriptionProducts = (
143
143
  provider.subscriptionProducts || provider.products
144
144
  ) as Record<string, any>;
@@ -149,8 +149,8 @@ export function getPriceConfigUtil(
149
149
  for (const [billingKey, planConfig] of Object.entries(product.plans)) {
150
150
  const plan_config = planConfig as any;
151
151
 
152
- // 匹配逻辑:按优先级尝试
153
- // 1. priceId精确匹配(优先级最高)
152
+ // Matching order by priority.
153
+ // 1. Exact priceId match with highest priority.
154
154
  if (priceId && plan_config.priceId === priceId) {
155
155
  return {
156
156
  ...plan_config,
@@ -160,7 +160,7 @@ export function getPriceConfigUtil(
160
160
  };
161
161
  }
162
162
 
163
- // 2. planbillingType同时匹配
163
+ // 2. Match by both plan and billingType.
164
164
  if (!priceId && plan && billingType) {
165
165
  if (productKey === plan && billingKey === billingType) {
166
166
  return {
@@ -172,7 +172,7 @@ export function getPriceConfigUtil(
172
172
  }
173
173
  }
174
174
 
175
- // 3. plan匹配(billingType为空时)
175
+ // 3. Match by plan when billingType is empty.
176
176
  if (!priceId && !billingType && plan && productKey === plan) {
177
177
  return {
178
178
  ...plan_config,
@@ -186,13 +186,13 @@ export function getPriceConfigUtil(
186
186
  }
187
187
  }
188
188
 
189
- // 遍历积分包产品
189
+ // Iterate through credit pack products.
190
190
  const creditPacks = provider.creditPackProducts as Record<string, any>;
191
191
  if (creditPacks) {
192
192
  for (const [packKey, pack] of Object.entries(creditPacks)) {
193
193
  const pack_typed = pack as any;
194
194
 
195
- // 积分包匹配
195
+ // Credit pack match.
196
196
  if (priceId && pack_typed.priceId === priceId) {
197
197
  return {
198
198
  priceId: pack_typed.priceId,
@@ -205,7 +205,7 @@ export function getPriceConfigUtil(
205
205
  };
206
206
  }
207
207
 
208
- // plan和onetime匹配
208
+ // Match by plan and one-time billing.
209
209
  if (!priceId && plan && billingType === 'onetime') {
210
210
  if (packKey === plan) {
211
211
  return {
@@ -220,7 +220,7 @@ export function getPriceConfigUtil(
220
220
  }
221
221
  }
222
222
 
223
- // plan匹配(billingType为空时也能找到first积分包)
223
+ // Match by plan; also resolves the first credit pack when billingType is empty.
224
224
  if (!priceId && !billingType && plan && packKey === plan) {
225
225
  return {
226
226
  priceId: pack_typed.priceId,
@@ -237,4 +237,4 @@ export function getPriceConfigUtil(
237
237
  }
238
238
 
239
239
  return null;
240
- }
240
+ }