@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
@@ -24,7 +24,7 @@ function XPillSelect(props) {
24
24
  const isAllSelected = props.mode === 'multiple' &&
25
25
  allOptionValues.length > 0 &&
26
26
  allOptionValues.every((value) => selectedValues.includes(value));
27
- const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || '全部' : null;
27
+ const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || 'All' : null;
28
28
  const hasVisiblePillLimit = props.mode === 'multiple' && typeof maxVisiblePills === 'number' && maxVisiblePills >= 0;
29
29
  const visibleSelectedValues = aggregatedSelectedLabel || !hasVisiblePillLimit
30
30
  ? selectedValues
@@ -124,7 +124,7 @@ function XPillSelect(props) {
124
124
  event.stopPropagation();
125
125
  removeValue(selectedValue);
126
126
  }, disabled: disabled, className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'gap-1 px-2.5 py-0.5 text-[11px]' : 'gap-1.5 px-3 py-1 text-xs', themeBgColor, themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: optionLabel, children: jsx("span", { className: cn('truncate', maxPillWidthClassName), children: optionLabel }) }, selectedValue));
127
- }), hiddenSelectedCount > 0 ? (jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `还有 ${hiddenSelectedCount} 项未展开`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsx("span", { className: cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsx(ChevronDownIcon, { className: cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && themeBorderColor), children: [inputEnabled ? (jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
127
+ }), hiddenSelectedCount > 0 ? (jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `${hiddenSelectedCount} items remain unexpanded`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsx("span", { className: cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsx(ChevronDownIcon, { className: cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && themeBorderColor), children: [inputEnabled ? (jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
128
128
  if (event.key !== 'Enter') {
129
129
  return;
130
130
  }
@@ -12,5 +12,5 @@ export type { CreditOverviewData, CreditBucket, CreditBucketStatus, Subscription
12
12
  export { MoneyPrice } from './money-price/money-price';
13
13
  export { getActiveProviderConfigUtil, getCreditsFromPriceIdUtil, getPriceConfigUtil } from './money-price/money-price-config-util';
14
14
  export { buildMoneyPriceData } from './money-price/money-price-data';
15
- export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext } from './money-price/money-price-types';
15
+ export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, MoneyPriceAnimeTone, MoneyPriceStrictDiffAnime, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext } from './money-price/money-price-types';
16
16
  export { UserState } from './money-price/money-price-types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "30.1.0",
3
+ "version": "31.0.0",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "exports": {
6
6
  "./clerk": {
@@ -221,9 +221,9 @@
221
221
  "LICENSE"
222
222
  ],
223
223
  "dependencies": {
224
- "@clerk/localizations": "^4.2.2",
225
- "@clerk/nextjs": "^7.0.5",
226
- "@clerk/shared": "^4.3.1",
224
+ "@clerk/localizations": "^4.6.2",
225
+ "@clerk/nextjs": "^7.3.3",
226
+ "@clerk/shared": "^4.10.2",
227
227
  "@fingerprintjs/fingerprintjs": "^5.1.0",
228
228
  "animejs": "^4.4.1",
229
229
  "class-variance-authority": "^0.7.1",
@@ -243,9 +243,9 @@
243
243
  "tslib": "^2.8.1",
244
244
  "unified": "^11.0.5",
245
245
  "zod": "^4.3.6",
246
- "@windrun-huaiin/base-ui": "^30.1.0",
247
- "@windrun-huaiin/lib": "^30.0.0",
248
- "@windrun-huaiin/contracts": "^30.0.0"
246
+ "@windrun-huaiin/base-ui": "^31.0.0",
247
+ "@windrun-huaiin/lib": "^31.0.0",
248
+ "@windrun-huaiin/contracts": "^31.0.0"
249
249
  },
250
250
  "peerDependencies": {
251
251
  "clsx": "^2.1.1",
@@ -281,6 +281,12 @@
281
281
  "publishConfig": {
282
282
  "access": "public"
283
283
  },
284
+ "homepage": "https://d8ger.com",
285
+ "repository": {
286
+ "type": "git",
287
+ "url": "git+https://github.com/caofanCPU/next-ai-build.git",
288
+ "directory": "packages/third-ui"
289
+ },
284
290
  "scripts": {
285
291
  "build": "rollup -c rollup.config.mjs",
286
292
  "build:prod": "rollup -c rollup.config.mjs",
@@ -188,7 +188,7 @@ export function AIPromptTextarea({
188
188
  }
189
189
  }
190
190
 
191
- // 渲染标题组件
191
+ // Render the title component.
192
192
  const renderTitle = () => {
193
193
  if (!title?.trim()) return null
194
194
 
@@ -200,7 +200,7 @@ export function AIPromptTextarea({
200
200
  )
201
201
  }
202
202
 
203
- // 渲染textarea组件
203
+ // Render the textarea component.
204
204
  const renderTextarea = (isEmbedded = false) => (
205
205
  <textarea
206
206
  ref={textareaRef}
@@ -220,7 +220,7 @@ export function AIPromptTextarea({
220
220
  />
221
221
  )
222
222
 
223
- // 渲染单词计数
223
+ // Render the word count.
224
224
  const renderWordCount = () => {
225
225
  if (!showWordCount) return null
226
226
 
@@ -238,7 +238,7 @@ export function AIPromptTextarea({
238
238
  )
239
239
  }
240
240
 
241
- // 如果有标题且需要嵌入,则渲染内部标题布局
241
+ // Render the embedded title layout when a title is present and embedded mode is enabled.
242
242
  if (embed && (title)) {
243
243
  return (
244
244
  <div className="space-y-2">
@@ -256,7 +256,7 @@ export function AIPromptTextarea({
256
256
  )
257
257
  }
258
258
 
259
- // 默认布局:外部标题或无标题
259
+ // Default layout: external title or no title.
260
260
  return (
261
261
  <div className="space-y-2">
262
262
  {renderTitle()}
@@ -264,4 +264,4 @@ export function AIPromptTextarea({
264
264
  {renderWordCount()}
265
265
  </div>
266
266
  )
267
- }
267
+ }
@@ -0,0 +1,16 @@
1
+ export const clerkAuthModalAppearance = {
2
+ elements: {
3
+ modalContent: '!items-start !pt-16 sm:!pt-24',
4
+ cardBox: '!mt-0',
5
+ },
6
+ };
7
+
8
+ export const clerkAuthPageAppearance = {
9
+ elements: {
10
+ rootBox: 'w-full',
11
+ cardBox: 'mx-auto',
12
+ },
13
+ };
14
+
15
+ export const clerkAuthPageContainerClassName =
16
+ 'flex min-h-[calc(100dvh-var(--fd-banner-height,0px)-var(--fd-header-height,3.5rem))] w-full items-start justify-center px-6 pt-[calc(var(--fd-header-height,3.5rem)+2rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-header-height,3.5rem)+4.5rem)] md:pb-8';
@@ -7,9 +7,7 @@
7
7
 
8
8
  import { SignUpWithFingerprint } from './signup-with-fingerprint-client';
9
9
  import { SignInWithFingerprint } from './signin-with-fingerprint-client';
10
-
11
- const clerkPageContainerClassName =
12
- 'flex min-h-dvh w-full items-start justify-center px-6 pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1.5rem)] md:pb-8';
10
+ import { clerkAuthPageContainerClassName } from './clerk-auth-appearance';
13
11
 
14
12
  /**
15
13
  * Create a SignUp page with fingerprint support
@@ -18,7 +16,7 @@ const clerkPageContainerClassName =
18
16
  export function createSignUpPageWithFingerprint() {
19
17
  return function SignUpPage() {
20
18
  return (
21
- <div className={clerkPageContainerClassName}>
19
+ <div className={clerkAuthPageContainerClassName}>
22
20
  <SignUpWithFingerprint />
23
21
  </div>
24
22
  );
@@ -32,7 +30,7 @@ export function createSignUpPageWithFingerprint() {
32
30
  export function createSignInPageWithFingerprint() {
33
31
  return function SignInPage() {
34
32
  return (
35
- <div className={clerkPageContainerClassName}>
33
+ <div className={clerkAuthPageContainerClassName}>
36
34
  <SignInWithFingerprint />
37
35
  </div>
38
36
  );
@@ -1,14 +1,15 @@
1
1
  import { SignIn, SignUp, Waitlist } from '@clerk/nextjs';
2
-
3
- const clerkPageContainerClassName =
4
- 'flex min-h-dvh w-full items-start justify-center px-6 pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1.5rem)] md:pb-8';
2
+ import {
3
+ clerkAuthPageAppearance,
4
+ clerkAuthPageContainerClassName,
5
+ } from './clerk-auth-appearance';
5
6
 
6
7
  // Legacy page generators (for backward compatibility)
7
8
  export function createSignInPage() {
8
9
  return function SignInPage() {
9
10
  return (
10
- <div className={clerkPageContainerClassName}>
11
- <SignIn />
11
+ <div className={clerkAuthPageContainerClassName}>
12
+ <SignIn appearance={clerkAuthPageAppearance} />
12
13
  </div>
13
14
  );
14
15
  };
@@ -17,8 +18,8 @@ export function createSignInPage() {
17
18
  export function createSignUpPage() {
18
19
  return function SignUpPage() {
19
20
  return (
20
- <div className={clerkPageContainerClassName}>
21
- <SignUp />
21
+ <div className={clerkAuthPageContainerClassName}>
22
+ <SignUp appearance={clerkAuthPageAppearance} />
22
23
  </div>
23
24
  );
24
25
  };
@@ -27,7 +28,7 @@ export function createSignUpPage() {
27
28
  export function createWaitlistPage() {
28
29
  return function WaitlistPage() {
29
30
  return (
30
- <div className={clerkPageContainerClassName}>
31
+ <div className={clerkAuthPageContainerClassName}>
31
32
  <Waitlist />
32
33
  </div>
33
34
  );
@@ -4,6 +4,7 @@ import { useState } from 'react';
4
4
  import { ClerkLoaded, ClerkLoading, SignInButton, UserButton, useAuth } from "@clerk/nextjs";
5
5
  import { ReceiptTextIcon, ShieldUserIcon } from '@windrun-huaiin/base-ui/icons';
6
6
  import { themeButtonGradientClass } from '@windrun-huaiin/base-ui/lib';
7
+ import { clerkAuthModalAppearance } from './clerk-auth-appearance';
7
8
  import { SignUpButtonWithFingerprint } from './signup-button-with-fingerprint-client';
8
9
 
9
10
  interface ClerkUserData {
@@ -51,11 +52,19 @@ export function ClerkUserClient({ data }: { data: ClerkUserData }) {
51
52
  )}
52
53
  </div>
53
54
  )}
54
- <SignInButton mode={data.clerkAuthInModal ? 'modal' : 'redirect'}>
55
- <button 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">
56
- {data.signIn}
57
- </button>
58
- </SignInButton>
55
+ {data.clerkAuthInModal ? (
56
+ <SignInButton mode="modal" appearance={clerkAuthModalAppearance}>
57
+ <button 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">
58
+ {data.signIn}
59
+ </button>
60
+ </SignInButton>
61
+ ) : (
62
+ <SignInButton mode="redirect">
63
+ <button 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">
64
+ {data.signIn}
65
+ </button>
66
+ </SignInButton>
67
+ )}
59
68
  </>
60
69
  )}
61
70
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Fingerprint Client Utilities
3
- * 客户端专用的指纹生成和管理逻辑
4
- * 只能在浏览器环境中使用
3
+ * Client-only fingerprint generation and management logic.
4
+ * Must be used only in browser environments.
5
5
  */
6
6
 
7
7
  import FingerprintJS from '@fingerprintjs/fingerprintjs';
@@ -41,24 +41,24 @@ type FirstTouchData = {
41
41
  };
42
42
 
43
43
  /**
44
- * 检查浏览器存储(localStorage cookie)中的指纹 ID
45
- * 返回有效的 ID null
44
+ * Check fingerprint ID in browser storage, including localStorage and cookies.
45
+ * Returns a valid ID or null.
46
46
  */
47
47
  function checkStoredFingerprintId(): string | null {
48
48
  if (typeof window === 'undefined') {
49
49
  return null;
50
50
  }
51
51
 
52
- // 优先检查 localStorage
52
+ // Prefer localStorage.
53
53
  const localStorageId = getLocalStorageValue(FINGERPRINT_STORAGE_KEY);
54
54
  if (localStorageId && isValidFingerprintId(localStorageId)) {
55
55
  return localStorageId;
56
56
  }
57
57
 
58
- // 检查 cookie
58
+ // Check cookies.
59
59
  const cookieId = getCookieValue(FINGERPRINT_COOKIE_NAME);
60
60
  if (cookieId && isValidFingerprintId(cookieId)) {
61
- // 同步到 localStorage
61
+ // Sync back to localStorage.
62
62
  setLocalStorageValue(FINGERPRINT_STORAGE_KEY, cookieId);
63
63
  return cookieId;
64
64
  }
@@ -203,34 +203,34 @@ export function getOrCreateFirstTouchData(): FirstTouchData | null {
203
203
  }
204
204
 
205
205
  /**
206
- * 生成基于真实浏览器特征的fingerprint ID
207
- * 使用 FingerprintJS 收集浏览器特征并生成唯一标识
206
+ * Generate a fingerprint ID from real browser characteristics.
207
+ * Uses FingerprintJS to collect browser signals and create a stable identifier.
208
208
  */
209
209
  export async function generateFingerprintId(): Promise<string> {
210
210
  if (typeof window === 'undefined') {
211
211
  throw new Error('generateFingerprintId can only be used in browser environment');
212
212
  }
213
213
 
214
- // 检查现有 ID
214
+ // Check for an existing ID.
215
215
  const existingId = checkStoredFingerprintId();
216
216
  if (existingId) {
217
217
  return existingId;
218
218
  }
219
219
 
220
220
  try {
221
- // 使用 FingerprintJS 生成指纹
221
+ // Generate a fingerprint with FingerprintJS.
222
222
  const fp = await FingerprintJS.load();
223
223
  const result = await fp.get();
224
224
  const fingerprintId = `fp_${result.visitorId}`;
225
225
 
226
- // 存储到 localStorage cookie
226
+ // Store in localStorage and cookies.
227
227
  setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fingerprintId);
228
228
  setCookie(FINGERPRINT_COOKIE_NAME, fingerprintId, 365);
229
229
 
230
230
  return fingerprintId;
231
231
  } catch (error) {
232
232
  console.warn('Failed to generate fingerprint with FingerprintJS:', error);
233
- // 降级方案:生成基于时间戳和随机数的 ID
233
+ // Fallback: generate an ID from timestamp and randomness.
234
234
  const fallbackId = `fp_fallback_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
235
235
  setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fallbackId);
236
236
  setCookie(FINGERPRINT_COOKIE_NAME, fallbackId, 365);
@@ -240,14 +240,14 @@ export async function generateFingerprintId(): Promise<string> {
240
240
  }
241
241
 
242
242
  /**
243
- * 获取当前的fingerprint ID
243
+ * Get the current fingerprint ID.
244
244
  */
245
245
  export function getFingerprintId(): string | null {
246
246
  return checkStoredFingerprintId();
247
247
  }
248
248
 
249
249
  /**
250
- * 设置fingerprint ID到存储
250
+ * Store a fingerprint ID.
251
251
  */
252
252
  export function setFingerprintId(fingerprintId: string): void {
253
253
  if (typeof window === 'undefined') {
@@ -263,7 +263,7 @@ export function setFingerprintId(fingerprintId: string): void {
263
263
  }
264
264
 
265
265
  /**
266
- * 清除fingerprint ID
266
+ * Clear the fingerprint ID.
267
267
  */
268
268
  export function clearFingerprintId(): void {
269
269
  if (typeof window === 'undefined') {
@@ -275,8 +275,8 @@ export function clearFingerprintId(): void {
275
275
  }
276
276
 
277
277
  /**
278
- * 获取或生成fingerprint ID
279
- * 如果不存在则自动生成新的
278
+ * Get or generate a fingerprint ID.
279
+ * Automatically creates one when none exists.
280
280
  */
281
281
  export async function getOrGenerateFingerprintId(): Promise<string> {
282
282
  const existingId = checkStoredFingerprintId();
@@ -288,7 +288,7 @@ export async function getOrGenerateFingerprintId(): Promise<string> {
288
288
  }
289
289
 
290
290
  /**
291
- * 创建包含fingerprint ID的fetch headers
291
+ * Create fetch headers containing the fingerprint ID.
292
292
  */
293
293
  export async function createFingerprintHeaders(): Promise<Record<string, string>> {
294
294
  const fingerprintId = await getOrGenerateFingerprintId();
@@ -330,7 +330,7 @@ export function createFingerprintFetch() {
330
330
  };
331
331
  }
332
332
 
333
- // Cookie 辅助函数 (私有)
333
+ // Private cookie helpers.
334
334
  function getCookieValue(name: string): string | null {
335
335
  if (typeof document === 'undefined') {
336
336
  return null;
@@ -109,7 +109,7 @@ function useFingerprintStatusTranslations(): FingerprintStatusTranslations {
109
109
 
110
110
  /**
111
111
  * Fingerprint Provider Component
112
- * 为应用提供fingerprint和匿名用户管理功能
112
+ * Provides fingerprint and anonymous user management for the application.
113
113
  */
114
114
  export function FingerprintProvider({
115
115
  children,
@@ -137,7 +137,7 @@ export function useFingerprintContext(): FingerprintContextType {
137
137
 
138
138
  /**
139
139
  * Safe hook to use fingerprint context - returns null if no provider
140
- * 安全版本的fingerprint context hook - 如果没有Provider则返回null
140
+ * Returns null when no provider is available.
141
141
  */
142
142
  export function useFingerprintContextSafe(): FingerprintContextType | null {
143
143
  const context = useContext(FingerprintContext);
@@ -162,7 +162,7 @@ export function withFingerprint<P extends object>(
162
162
  }
163
163
 
164
164
  /**
165
- * 组件:显示用户状态和积分信息(用于调试)
165
+ * Component for displaying user status and credit information for debugging.
166
166
  */
167
167
  export function FingerprintStatus() {
168
168
  const translations = useFingerprintStatusTranslations();
@@ -400,7 +400,7 @@ export function FingerprintStatus() {
400
400
 
401
401
  return (
402
402
  <>
403
- {/* 灯泡按钮 */}
403
+ {/* Lightbulb button */}
404
404
  {!isOpen && (
405
405
  <button
406
406
  onClick={handleToggle}
@@ -417,7 +417,7 @@ export function FingerprintStatus() {
417
417
  </button>
418
418
  )}
419
419
 
420
- {/* 面板 */}
420
+ {/* Panel */}
421
421
  {isOpen && (
422
422
  <>
423
423
  <div onClick={handleBackdropClick} className="fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" />
@@ -622,9 +622,9 @@ export function FingerprintStatus() {
622
622
  }
623
623
 
624
624
 
625
- /* ==================== 新增辅助组件 ==================== */
625
+ /* ==================== Helper Components ==================== */
626
626
 
627
- // 标题行:左侧图标+标题,右侧信息(右对齐)
627
+ // Header row with icon and title on the left, right-aligned metadata on the right.
628
628
  function PanelHeader({ icon, title, rightInfo }: { icon: React.ReactNode; title: string; rightInfo: React.ReactNode }) {
629
629
  return (
630
630
  <div className="flex items-center justify-between mb-3">
@@ -643,7 +643,7 @@ function PanelHeader({ icon, title, rightInfo }: { icon: React.ReactNode; title:
643
643
  );
644
644
  }
645
645
 
646
- // 复用:普通 PanelSection 支持右侧信息
646
+ // Reusable PanelSection with optional right-side metadata.
647
647
  interface PanelSectionProps {
648
648
  icon: React.ReactNode;
649
649
  title: string;
@@ -727,19 +727,19 @@ function StatusTag({
727
727
  const normalized = value.toLowerCase();
728
728
 
729
729
  const colorMap: Record<string, string> = {
730
- // 绿色:正常/活跃
730
+ // Green: normal or active.
731
731
  registered: 'bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300',
732
732
  active: 'bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300',
733
733
  trialing: 'bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300',
734
734
 
735
- // 灰色:失效/删除
735
+ // Gray: inactive or deleted.
736
736
  canceled: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
737
737
  frozen: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
738
738
  deleted: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
739
739
  expired: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
740
740
  past_due: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
741
741
 
742
- // 橙色:待处理/异常
742
+ // Amber: pending or exceptional states.
743
743
  pending: 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300',
744
744
  failed: 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300',
745
745
  unpaid: 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300',
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Fingerprint Server Utilities
3
- * 服务端专用的指纹ID提取和验证逻辑
4
- * 可以安全地在服务端使用,不依赖浏览器API或FingerprintJS
3
+ * Server-only fingerprint ID extraction and validation logic.
4
+ * Safe for server usage without browser APIs or FingerprintJS.
5
5
  */
6
6
 
7
7
  import {
@@ -11,16 +11,16 @@ import {
11
11
  } from './fingerprint-shared';
12
12
 
13
13
  /**
14
- * 从请求中提取fingerprint ID
15
- * 优先级:header > cookie > query参数
16
- * 可以安全地在服务端使用
14
+ * Extract fingerprint ID from a request.
15
+ * Priority: header > cookie > query parameter.
16
+ * Safe to use on the server.
17
17
  */
18
18
  export function extractFingerprintId(
19
19
  headers: Headers | Record<string, string>,
20
20
  cookies?: Record<string, string>,
21
21
  query?: Record<string, string | undefined>
22
22
  ): string | null {
23
- // 1. header中获取
23
+ // 1. Read from header.
24
24
  const headerValue = headers instanceof Headers
25
25
  ? headers.get(FINGERPRINT_HEADER_NAME)
26
26
  : headers[FINGERPRINT_HEADER_NAME];
@@ -29,7 +29,7 @@ export function extractFingerprintId(
29
29
  return headerValue;
30
30
  }
31
31
 
32
- // 2. cookie中获取
32
+ // 2. Read from cookie.
33
33
  if (cookies) {
34
34
  const cookieValue = cookies[FINGERPRINT_COOKIE_NAME];
35
35
  if (cookieValue && isValidFingerprintId(cookieValue)) {
@@ -37,7 +37,7 @@ export function extractFingerprintId(
37
37
  }
38
38
  }
39
39
 
40
- // 3. query参数中获取
40
+ // 3. Read from query parameters.
41
41
  if (query) {
42
42
  const queryValue = query.fingerprint_id || query.fp_id;
43
43
  if (queryValue && isValidFingerprintId(queryValue)) {
@@ -49,22 +49,22 @@ export function extractFingerprintId(
49
49
  }
50
50
 
51
51
  /**
52
- * 生成服务端降级fingerprint ID
53
- * 当客户端无法生成fingerprint时使用
54
- * 可以安全地在服务端使用
52
+ * Generate a server-side fallback fingerprint ID.
53
+ * Used when the client cannot generate a fingerprint.
54
+ * Safe to use on the server.
55
55
  */
56
56
  export function generateServerFingerprintId(): string {
57
57
  return `fp_server_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
58
58
  }
59
59
 
60
60
  /**
61
- * Next.js Request对象中提取fingerprint ID
62
- * 便捷方法,适用于Next.js API路由
61
+ * Extract fingerprint ID from a Next.js Request object.
62
+ * Convenience helper for Next.js API routes.
63
63
  */
64
64
  export function extractFingerprintFromNextRequest(request: Request): string | null {
65
65
  const headers = request.headers;
66
66
 
67
- // 尝试从cookies获取(需要解析cookie header
67
+ // Try cookies by parsing the cookie header.
68
68
  const cookieHeader = headers.get('cookie');
69
69
  const cookies: Record<string, string> = {};
70
70
 
@@ -77,7 +77,7 @@ export function extractFingerprintFromNextRequest(request: Request): string | nu
77
77
  });
78
78
  }
79
79
 
80
- // 尝试从URL query参数获取
80
+ // Try URL query parameters.
81
81
  const url = new URL(request.url);
82
82
  const query: Record<string, string> = {};
83
83
  url.searchParams.forEach((value, key) => {
@@ -93,8 +93,8 @@ type NextCookiesLike = {
93
93
  };
94
94
 
95
95
  /**
96
- * Next.js runtime提供的headers/cookies实例里提取fingerprint ID
97
- * App Router服务端组件和Server Actions直接复用
96
+ * Extract fingerprint ID from Next.js runtime headers/cookies stores.
97
+ * Reusable in App Router server components and Server Actions.
98
98
  */
99
99
  export function extractFingerprintFromNextStores(params: {
100
100
  headers: NextHeadersLike;
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Fingerprint Shared Utilities
3
- * 客户端和服务端共享的常量、类型和验证逻辑
3
+ * Shared constants, types, and validation logic for client and server.
4
4
  */
5
5
 
6
- // Fingerprint ID的存储键和header
6
+ // Storage keys and header names for fingerprint IDs.
7
7
  export const FINGERPRINT_STORAGE_KEY = '__x_fingerprint_id';
8
8
  export const FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = '__x_fingerprint_debug_override';
9
9
  export const FINGERPRINT_HEADER_NAME = 'x-fingerprint-id-v8';
@@ -15,16 +15,16 @@ export const FINGERPRINT_FIRST_TOUCH_HEADER = 'x-first-touch';
15
15
  export const FINGERPRINT_DEBUG_PREFIX = 'fp_test_dbg_';
16
16
 
17
17
  /**
18
- * 验证fingerprint ID格式
19
- * 可以在客户端和服务端使用
18
+ * Validate fingerprint ID format.
19
+ * Safe to use on both client and server.
20
20
  */
21
21
  export function isValidFingerprintId(fingerprintId: string): boolean {
22
22
  if (!fingerprintId) return false;
23
- // 支持多种格式:
24
- // - fp_ + FingerprintJS visitorId (变长字符串)
25
- // - fp_fallback_ + 时间戳_随机字符串 (客户端降级方案)
26
- // - fp_server_ + 时间戳_随机字符串 (服务端降级)
27
- // - fp_test_dbg_ + 时间戳_随机字符串 (调试并发测试)
23
+ // Supported formats:
24
+ // - fp_ + FingerprintJS visitorId with variable length.
25
+ // - fp_fallback_ + timestamp + random string for client fallback.
26
+ // - fp_server_ + timestamp + random string for server fallback.
27
+ // - fp_test_dbg_ + timestamp + random string for debug concurrency tests.
28
28
  return /^fp(_fallback|_server|_test_dbg)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
29
29
  }
30
30
 
@@ -36,7 +36,7 @@ export function isDebugFingerprintId(fingerprintId: string | null | undefined):
36
36
  return fingerprintId.startsWith(FINGERPRINT_DEBUG_PREFIX);
37
37
  }
38
38
 
39
- // 常量导出
39
+ // Exported constants.
40
40
  export const FINGERPRINT_CONSTANTS = {
41
41
  STORAGE_KEY: FINGERPRINT_STORAGE_KEY,
42
42
  DEBUG_OVERRIDE_STORAGE_KEY: FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY,
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Fingerprint System Types
3
- * 指纹识别系统的类型定义
4
3
  */
5
4
 
6
5
  export interface XUser {