@windrun-huaiin/third-ui 14.0.3 → 14.1.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.
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
+ var tslib_es6 = require('../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js');
4
5
  var jsxRuntime = require('react/jsx-runtime');
5
6
  var server = require('@windrun-huaiin/base-ui/components/server');
6
7
  var lib = require('@windrun-huaiin/base-ui/lib');
@@ -8,6 +9,8 @@ var utils = require('@windrun-huaiin/lib/utils');
8
9
  var React = require('react');
9
10
  var useFingerprint = require('./use-fingerprint.js');
10
11
  var ui = require('@windrun-huaiin/base-ui/ui');
12
+ var fingerprintClient = require('./fingerprint-client.js');
13
+ var fingerprintShared = require('./fingerprint-shared.js');
11
14
 
12
15
  const FingerprintContext = React.createContext(undefined);
13
16
  /**
@@ -49,8 +52,12 @@ function withFingerprint(Component, config) {
49
52
  * 组件:显示用户状态和积分信息(用于调试)
50
53
  */
51
54
  function FingerprintStatus() {
52
- const { fingerprintId, xUser, xCredit, xSubscription, error } = useFingerprintContext();
55
+ const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, initializeAnonymousUser, } = useFingerprintContext();
53
56
  const [isOpen, setIsOpen] = React.useState(false);
57
+ const [panelMode, setPanelMode] = React.useState('info');
58
+ const [testFingerprintId, setTestFingerprintId] = React.useState('');
59
+ const [testResult, setTestResult] = React.useState('');
60
+ const [isRunningTest, setIsRunningTest] = React.useState(false);
54
61
  const modalRef = React.useRef(null);
55
62
  const handleToggle = () => setIsOpen(prev => !prev);
56
63
  const handleBackdropClick = (e) => {
@@ -71,13 +78,20 @@ function FingerprintStatus() {
71
78
  console.warn('xUser.fingerprintId is missing:', xUser);
72
79
  }
73
80
  }, [xUser]);
81
+ React.useEffect(() => {
82
+ if (testFingerprintId) {
83
+ return;
84
+ }
85
+ const defaultFingerprintId = buildDebugFingerprintId();
86
+ setTestFingerprintId(defaultFingerprintId);
87
+ }, [testFingerprintId]);
74
88
  const creditBuckets = React.useMemo(() => {
75
89
  if (!xCredit)
76
90
  return [];
77
91
  return [
78
92
  {
79
93
  key: 'paid',
80
- label: '订阅积分',
94
+ label: 'Paid',
81
95
  icon: jsxRuntime.jsx(server.globalLucideIcons.Settings2, { className: "size-4 text-green-500 dark:text-green-300" }),
82
96
  balance: xCredit.balancePaid,
83
97
  total: xCredit.totalPaidLimit,
@@ -86,7 +100,7 @@ function FingerprintStatus() {
86
100
  },
87
101
  {
88
102
  key: 'oneTimePaid',
89
- label: '一次性积分',
103
+ label: 'OneTimePaid',
90
104
  icon: jsxRuntime.jsx(server.globalLucideIcons.Coins, { className: "size-4 text-amber-500 dark:text-amber-300" }),
91
105
  balance: xCredit.balanceOneTimePaid,
92
106
  total: xCredit.totalOneTimePaidLimit,
@@ -95,7 +109,7 @@ function FingerprintStatus() {
95
109
  },
96
110
  {
97
111
  key: 'free',
98
- label: '免费积分',
112
+ label: 'Free',
99
113
  icon: jsxRuntime.jsx(server.globalLucideIcons.Gift, { className: "size-4 text-purple-500 dark:text-purple-300" }),
100
114
  balance: xCredit.balanceFree,
101
115
  total: xCredit.totalFreeLimit,
@@ -108,10 +122,10 @@ function FingerprintStatus() {
108
122
  var _a, _b;
109
123
  if (!xSubscription) {
110
124
  return {
111
- status: '未订阅',
125
+ status: 'Never',
112
126
  priceName: '--',
113
127
  creditsAllocated: '--',
114
- period: '无记录',
128
+ period: 'Unavailable',
115
129
  };
116
130
  }
117
131
  return {
@@ -126,25 +140,121 @@ function FingerprintStatus() {
126
140
  const userStatus = (xUser === null || xUser === void 0 ? void 0 : xUser.status) || '--';
127
141
  const totalCredits = formatNumber(xCredit === null || xCredit === void 0 ? void 0 : xCredit.totalBalance);
128
142
  const subStatus = subscriptionStatus.status;
129
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isOpen && (jsxRuntime.jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: utils.cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsxRuntime.jsx(server.globalLucideIcons.Lightbulb, { className: "size-6 text-white" }) })), isOpen && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxRuntime.jsxs("div", { ref: modalRef, className: utils.cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsxRuntime.jsx("header", { className: "mb-4", children: jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2 text-base font-bold tracking-wider", lib.themeIconColor), children: [jsxRuntime.jsx(server.globalLucideIcons.ShieldUser, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxRuntime.jsx("button", { type: "button", "aria-label": "Close fingerprint panel", className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsxRuntime.jsx(server.globalLucideIcons.X, { className: "size-4" }) })] }) }), jsxRuntime.jsxs("section", { className: "space-y-1", children: [jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(server.globalLucideIcons.Fingerprint, { className: "size-4" }), title: "\u7528\u6237\u4FE1\u606F", rightInfo: jsxRuntime.jsx(StatusTag, { value: userStatus }), items: [
130
- { label: '用户ID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
131
- { label: '用户昵称', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
132
- { label: 'FingerprintID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
133
- { label: 'Clerk用户', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
134
- { label: '邮箱', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
135
- { label: 'Stripe客户', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
136
- { label: '创建时间', value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
137
- ] }), jsxRuntime.jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(server.globalLucideIcons.Gem, { className: "size-4" }), title: "\u79EF\u5206\u4FE1\u606F", rightInfo: jsxRuntime.jsx("span", { className: utils.cn("font-semibold", lib.themeIconColor), children: totalCredits }) }), jsxRuntime.jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
138
- const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
139
- return (jsxRuntime.jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsxRuntime.jsx("span", { children: bucket.label })] }), jsxRuntime.jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsxRuntime.jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsxRuntime.jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxRuntime.jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsxRuntime.jsx("span", { children: formatRangeText(bucket.start, bucket.end) }), jsxRuntime.jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
140
- })) : (jsxRuntime.jsx(EmptyPlaceholder, { label: "\u6682\u65E0\u79EF\u5206\u6570\u636E", icon: jsxRuntime.jsx(server.globalLucideIcons.DatabaseZap, { className: "size-4" }) })) })] }), jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(server.globalLucideIcons.Bell, { className: "size-4" }), title: "\u8BA2\u9605\u4FE1\u606F", rightInfo: jsxRuntime.jsx(StatusTag, { value: subStatus }), items: [
141
- { label: '订阅方案', value: subscriptionStatus.priceName },
142
- { label: '有效期', value: subscriptionStatus.period },
143
- { label: '分配额度', value: subscriptionStatus.creditsAllocated },
144
- { label: '订阅ID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
145
- { label: 'OrderID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
146
- { label: 'Price ID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
147
- ] }), error && (jsxRuntime.jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxRuntime.jsx(server.globalLucideIcons.X, { className: "mt-0.5 size-4" }), jsxRuntime.jsx("span", { children: error })] }))] })] })] }))] }));
143
+ const themedGhostButtonClass = utils.cn('border-slate-200 bg-white/90 hover:bg-slate-50 dark:border-white/10 dark:bg-slate-950/80 dark:hover:bg-slate-900', 'hover:border-current', lib.themeIconColor);
144
+ const runContextParallelInitTest = () => tslib_es6.__awaiter(this, void 0, void 0, function* () {
145
+ setIsRunningTest(true);
146
+ setTestResult('Running context parallel init x3...');
147
+ try {
148
+ yield Promise.all([
149
+ initializeAnonymousUser(),
150
+ initializeAnonymousUser(),
151
+ initializeAnonymousUser(),
152
+ ]);
153
+ setTestResult(`Context parallel init finished. Active fingerprint: ${fingerprintId || '--'}`);
154
+ }
155
+ catch (testError) {
156
+ setTestResult(`Context parallel init failed: ${formatErrorMessage(testError)}`);
157
+ }
158
+ finally {
159
+ setIsRunningTest(false);
160
+ }
161
+ });
162
+ const runRawParallelPostTest = () => tslib_es6.__awaiter(this, void 0, void 0, function* () {
163
+ const normalizedFingerprintId = testFingerprintId.trim();
164
+ if (!normalizedFingerprintId) {
165
+ setTestResult('Please input a valid test fingerprint id.');
166
+ return;
167
+ }
168
+ setIsRunningTest(true);
169
+ setTestResult(`Running raw POST x3 with fingerprint: ${normalizedFingerprintId}`);
170
+ try {
171
+ const fingerprintHeaders = yield fingerprintClient.createFingerprintHeaders();
172
+ const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
173
+ method: 'POST',
174
+ headers: Object.assign(Object.assign({ 'Content-Type': 'application/json', [fingerprintShared.FINGERPRINT_SOURCE_REFER]: document.referrer || '' }, fingerprintHeaders), { 'x-fingerprint-id-v8': normalizedFingerprintId }),
175
+ body: JSON.stringify({ fingerprintId: normalizedFingerprintId }),
176
+ }));
177
+ const responses = yield Promise.all(requests);
178
+ const payloads = yield Promise.all(responses.map((response) => tslib_es6.__awaiter(this, void 0, void 0, function* () {
179
+ var _a, _b;
180
+ const data = yield response.json().catch(() => ({}));
181
+ return {
182
+ ok: response.ok,
183
+ status: response.status,
184
+ isNewUser: typeof (data === null || data === void 0 ? void 0 : data.isNewUser) === 'boolean' ? data.isNewUser : null,
185
+ userId: typeof ((_a = data === null || data === void 0 ? void 0 : data.xUser) === null || _a === void 0 ? void 0 : _a.userId) === 'string' ? data.xUser.userId : '--',
186
+ fingerprintId: typeof ((_b = data === null || data === void 0 ? void 0 : data.xUser) === null || _b === void 0 ? void 0 : _b.fingerprintId) === 'string' ? data.xUser.fingerprintId : normalizedFingerprintId,
187
+ error: typeof (data === null || data === void 0 ? void 0 : data.error) === 'string' ? data.error : null,
188
+ };
189
+ })));
190
+ const createdUserIds = payloads
191
+ .filter((payload) => payload.ok && payload.isNewUser === true && payload.userId !== '--')
192
+ .map((payload) => payload.userId);
193
+ const reusedUserIds = payloads
194
+ .filter((payload) => payload.ok && payload.isNewUser === false && payload.userId !== '--')
195
+ .map((payload) => payload.userId);
196
+ const failedStatuses = payloads
197
+ .filter((payload) => !payload.ok)
198
+ .map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
199
+ setTestResult([
200
+ `Raw POST x3 done.`,
201
+ `created=${createdUserIds.length}`,
202
+ `reused=${reusedUserIds.length}`,
203
+ `failed=${failedStatuses.length}`,
204
+ `createdUserIds=[${createdUserIds.join(', ')}]`,
205
+ failedStatuses.length > 0 ? `failedStatuses=[${failedStatuses.join(', ')}]` : null,
206
+ ].filter(Boolean).join('\n'));
207
+ }
208
+ catch (testError) {
209
+ setTestResult(`Raw POST test failed: ${formatErrorMessage(testError)}`);
210
+ }
211
+ finally {
212
+ setIsRunningTest(false);
213
+ }
214
+ });
215
+ const applyTestFingerprintAndReload = () => {
216
+ const normalizedFingerprintId = testFingerprintId.trim();
217
+ if (!normalizedFingerprintId) {
218
+ setTestResult('Please input a valid test fingerprint id before applying.');
219
+ return;
220
+ }
221
+ try {
222
+ fingerprintClient.setFingerprintId(normalizedFingerprintId);
223
+ setTestResult(`Applied test fingerprint and reloading: ${normalizedFingerprintId}`);
224
+ window.location.reload();
225
+ }
226
+ catch (testError) {
227
+ setTestResult(`Apply test fingerprint failed: ${formatErrorMessage(testError)}`);
228
+ }
229
+ };
230
+ const regenerateTestFingerprint = () => {
231
+ const nextFingerprintId = buildDebugFingerprintId();
232
+ setTestFingerprintId(nextFingerprintId);
233
+ setTestResult(`Generated test fingerprint: ${nextFingerprintId}`);
234
+ };
235
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isOpen && (jsxRuntime.jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: utils.cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsxRuntime.jsx(server.globalLucideIcons.Lightbulb, { className: "size-6 text-white" }) })), isOpen && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxRuntime.jsxs("div", { ref: modalRef, className: utils.cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsxRuntime.jsx("header", { className: "mb-4", children: jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2 text-base font-bold tracking-wider", lib.themeIconColor), children: [jsxRuntime.jsx(server.globalLucideIcons.ShieldUser, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: utils.cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
236
+ ? utils.cn('border-transparent text-white', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass)
237
+ : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsxRuntime.jsx("span", { children: "Concurrent Test" }), jsxRuntime.jsx("span", { className: utils.cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
238
+ ? 'bg-white/25'
239
+ : 'bg-slate-300 dark:bg-slate-700'), children: jsxRuntime.jsx("span", { className: utils.cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": "Close fingerprint panel", className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsxRuntime.jsx(server.globalLucideIcons.X, { className: "size-4" }) })] })] }) }), jsxRuntime.jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(server.globalLucideIcons.Fingerprint, { className: "size-4" }), title: "User", rightInfo: jsxRuntime.jsx(StatusTag, { value: userStatus }), items: [
240
+ { label: 'UserID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
241
+ { label: 'NickName', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
242
+ { label: 'FingerprintID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
243
+ { label: 'ClerkUserID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
244
+ { label: 'Email', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
245
+ { label: 'StripeCusID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
246
+ { label: 'CreatedAt', value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
247
+ ] }), jsxRuntime.jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(server.globalLucideIcons.Gem, { className: "size-4" }), title: "Credits Info", rightInfo: jsxRuntime.jsx("span", { className: utils.cn("font-semibold", lib.themeIconColor), children: totalCredits }) }), jsxRuntime.jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
248
+ const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
249
+ return (jsxRuntime.jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsxRuntime.jsx("span", { children: bucket.label })] }), jsxRuntime.jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsxRuntime.jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsxRuntime.jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxRuntime.jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsxRuntime.jsx("span", { children: formatRangeText(bucket.start, bucket.end) }), jsxRuntime.jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
250
+ })) : (jsxRuntime.jsx(EmptyPlaceholder, { label: "No Credits Yet", icon: jsxRuntime.jsx(server.globalLucideIcons.DatabaseZap, { className: "size-4" }) })) })] }), jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(server.globalLucideIcons.Bell, { className: "size-4" }), title: "Subscription", rightInfo: jsxRuntime.jsx(StatusTag, { value: subStatus }), items: [
251
+ { label: 'Plan', value: subscriptionStatus.priceName },
252
+ { label: 'Period', value: subscriptionStatus.period },
253
+ { label: 'Allocated', value: subscriptionStatus.creditsAllocated },
254
+ { label: 'SubID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
255
+ { label: 'OrderID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
256
+ { label: 'PriceID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
257
+ ] })] })) : (jsxRuntime.jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(server.globalLucideIcons.DatabaseZap, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsxRuntime.jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxRuntime.jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Current Browser" }), jsxRuntime.jsx(ui.CopyableText, { text: fingerprintId || '' })] }), jsxRuntime.jsxs("div", { className: "space-y-1", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Generate New" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsxRuntime.jsx("input", { value: testFingerprintId, onChange: (e) => setTestFingerprintId(e.target.value), className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 outline-none transition focus:border-slate-400 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", placeholder: "fp_test_dbg_20260322_xxx" }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": "Generate new test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsxRuntime.jsx(server.globalLucideIcons.RefreshCcw, { className: "size-4" }) }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: applyTestFingerprintAndReload, "aria-label": "Apply test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsxRuntime.jsx(server.globalLucideIcons.CheckCheck, { className: "size-4" }) })] })] })] }), jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: "Frontend Prevention Test" }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass), children: "Backend Idempotency Test" })] }), jsxRuntime.jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsxRuntime.jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || 'No test executed yet.' }) })] })), error && (jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [jsxRuntime.jsx(server.globalLucideIcons.X, { className: "mt-0.5 size-4 shrink-0" }), jsxRuntime.jsx("span", { children: error })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": "Dismiss error", onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsxRuntime.jsx(server.globalLucideIcons.X, { className: "size-4" }) })] }))] })] })] }))] }));
148
258
  }
149
259
  /* ==================== 新增辅助组件 ==================== */
150
260
  // 标题行:左侧图标+标题,右侧信息(右对齐)
@@ -175,7 +285,7 @@ function formatRangeText(start, end) {
175
285
  const safeStart = start && start.trim() ? start : '';
176
286
  const safeEnd = end && end.trim() ? end : '';
177
287
  if (!safeStart && !safeEnd) {
178
- return '无记录';
288
+ return 'No records';
179
289
  }
180
290
  if (!safeStart) {
181
291
  return safeEnd;
@@ -210,6 +320,20 @@ function StatusTag({ value }) {
210
320
  const badgeClass = colorMap[normalized] || defaultColor;
211
321
  return (jsxRuntime.jsx("span", { className: utils.cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
212
322
  }
323
+ function buildDebugFingerprintId() {
324
+ const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
325
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
326
+ return `fp_test_dbg_${timestamp}_${randomSuffix}`;
327
+ }
328
+ function formatErrorMessage(error) {
329
+ if (error instanceof Error) {
330
+ return error.message;
331
+ }
332
+ if (typeof error === 'string') {
333
+ return error;
334
+ }
335
+ return 'Unknown error';
336
+ }
213
337
 
214
338
  exports.FingerprintProvider = FingerprintProvider;
215
339
  exports.FingerprintStatus = FingerprintStatus;
@@ -1,11 +1,14 @@
1
1
  "use client";
2
+ import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
2
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
4
  import { globalLucideIcons } from '@windrun-huaiin/base-ui/components/server';
4
- import { themeButtonGradientClass, themeButtonGradientHoverClass, themeIconColor } from '@windrun-huaiin/base-ui/lib';
5
+ import { themeIconColor, themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
5
6
  import { cn } from '@windrun-huaiin/lib/utils';
6
7
  import { createContext, useContext, useState, useRef, useEffect, useMemo } from 'react';
7
8
  import { useFingerprint } from './use-fingerprint.mjs';
8
9
  import { CopyableText } from '@windrun-huaiin/base-ui/ui';
10
+ import { setFingerprintId, createFingerprintHeaders } from './fingerprint-client.mjs';
11
+ import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
9
12
 
10
13
  const FingerprintContext = createContext(undefined);
11
14
  /**
@@ -47,8 +50,12 @@ function withFingerprint(Component, config) {
47
50
  * 组件:显示用户状态和积分信息(用于调试)
48
51
  */
49
52
  function FingerprintStatus() {
50
- const { fingerprintId, xUser, xCredit, xSubscription, error } = useFingerprintContext();
53
+ const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, initializeAnonymousUser, } = useFingerprintContext();
51
54
  const [isOpen, setIsOpen] = useState(false);
55
+ const [panelMode, setPanelMode] = useState('info');
56
+ const [testFingerprintId, setTestFingerprintId] = useState('');
57
+ const [testResult, setTestResult] = useState('');
58
+ const [isRunningTest, setIsRunningTest] = useState(false);
52
59
  const modalRef = useRef(null);
53
60
  const handleToggle = () => setIsOpen(prev => !prev);
54
61
  const handleBackdropClick = (e) => {
@@ -69,13 +76,20 @@ function FingerprintStatus() {
69
76
  console.warn('xUser.fingerprintId is missing:', xUser);
70
77
  }
71
78
  }, [xUser]);
79
+ useEffect(() => {
80
+ if (testFingerprintId) {
81
+ return;
82
+ }
83
+ const defaultFingerprintId = buildDebugFingerprintId();
84
+ setTestFingerprintId(defaultFingerprintId);
85
+ }, [testFingerprintId]);
72
86
  const creditBuckets = useMemo(() => {
73
87
  if (!xCredit)
74
88
  return [];
75
89
  return [
76
90
  {
77
91
  key: 'paid',
78
- label: '订阅积分',
92
+ label: 'Paid',
79
93
  icon: jsx(globalLucideIcons.Settings2, { className: "size-4 text-green-500 dark:text-green-300" }),
80
94
  balance: xCredit.balancePaid,
81
95
  total: xCredit.totalPaidLimit,
@@ -84,7 +98,7 @@ function FingerprintStatus() {
84
98
  },
85
99
  {
86
100
  key: 'oneTimePaid',
87
- label: '一次性积分',
101
+ label: 'OneTimePaid',
88
102
  icon: jsx(globalLucideIcons.Coins, { className: "size-4 text-amber-500 dark:text-amber-300" }),
89
103
  balance: xCredit.balanceOneTimePaid,
90
104
  total: xCredit.totalOneTimePaidLimit,
@@ -93,7 +107,7 @@ function FingerprintStatus() {
93
107
  },
94
108
  {
95
109
  key: 'free',
96
- label: '免费积分',
110
+ label: 'Free',
97
111
  icon: jsx(globalLucideIcons.Gift, { className: "size-4 text-purple-500 dark:text-purple-300" }),
98
112
  balance: xCredit.balanceFree,
99
113
  total: xCredit.totalFreeLimit,
@@ -106,10 +120,10 @@ function FingerprintStatus() {
106
120
  var _a, _b;
107
121
  if (!xSubscription) {
108
122
  return {
109
- status: '未订阅',
123
+ status: 'Never',
110
124
  priceName: '--',
111
125
  creditsAllocated: '--',
112
- period: '无记录',
126
+ period: 'Unavailable',
113
127
  };
114
128
  }
115
129
  return {
@@ -124,25 +138,121 @@ function FingerprintStatus() {
124
138
  const userStatus = (xUser === null || xUser === void 0 ? void 0 : xUser.status) || '--';
125
139
  const totalCredits = formatNumber(xCredit === null || xCredit === void 0 ? void 0 : xCredit.totalBalance);
126
140
  const subStatus = subscriptionStatus.status;
127
- return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsx(globalLucideIcons.Lightbulb, { className: "size-6 text-white" }) })), isOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxs("div", { ref: modalRef, className: cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsx("header", { className: "mb-4", children: jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor), children: [jsx(globalLucideIcons.ShieldUser, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsx("button", { type: "button", "aria-label": "Close fingerprint panel", className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsx(globalLucideIcons.X, { className: "size-4" }) })] }) }), jsxs("section", { className: "space-y-1", children: [jsx(PanelSection, { icon: jsx(globalLucideIcons.Fingerprint, { className: "size-4" }), title: "\u7528\u6237\u4FE1\u606F", rightInfo: jsx(StatusTag, { value: userStatus }), items: [
128
- { label: '用户ID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
129
- { label: '用户昵称', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
130
- { label: 'FingerprintID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
131
- { label: 'Clerk用户', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
132
- { label: '邮箱', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
133
- { label: 'Stripe客户', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
134
- { label: '创建时间', value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
135
- ] }), jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsx(PanelHeader, { icon: jsx(globalLucideIcons.Gem, { className: "size-4" }), title: "\u79EF\u5206\u4FE1\u606F", rightInfo: jsx("span", { className: cn("font-semibold", themeIconColor), children: totalCredits }) }), jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
136
- const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
137
- return (jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsx("span", { children: bucket.label })] }), jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsx("span", { children: formatRangeText(bucket.start, bucket.end) }), jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
138
- })) : (jsx(EmptyPlaceholder, { label: "\u6682\u65E0\u79EF\u5206\u6570\u636E", icon: jsx(globalLucideIcons.DatabaseZap, { className: "size-4" }) })) })] }), jsx(PanelSection, { icon: jsx(globalLucideIcons.Bell, { className: "size-4" }), title: "\u8BA2\u9605\u4FE1\u606F", rightInfo: jsx(StatusTag, { value: subStatus }), items: [
139
- { label: '订阅方案', value: subscriptionStatus.priceName },
140
- { label: '有效期', value: subscriptionStatus.period },
141
- { label: '分配额度', value: subscriptionStatus.creditsAllocated },
142
- { label: '订阅ID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
143
- { label: 'OrderID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
144
- { label: 'Price ID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
145
- ] }), error && (jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsx(globalLucideIcons.X, { className: "mt-0.5 size-4" }), jsx("span", { children: error })] }))] })] })] }))] }));
141
+ const themedGhostButtonClass = cn('border-slate-200 bg-white/90 hover:bg-slate-50 dark:border-white/10 dark:bg-slate-950/80 dark:hover:bg-slate-900', 'hover:border-current', themeIconColor);
142
+ const runContextParallelInitTest = () => __awaiter(this, void 0, void 0, function* () {
143
+ setIsRunningTest(true);
144
+ setTestResult('Running context parallel init x3...');
145
+ try {
146
+ yield Promise.all([
147
+ initializeAnonymousUser(),
148
+ initializeAnonymousUser(),
149
+ initializeAnonymousUser(),
150
+ ]);
151
+ setTestResult(`Context parallel init finished. Active fingerprint: ${fingerprintId || '--'}`);
152
+ }
153
+ catch (testError) {
154
+ setTestResult(`Context parallel init failed: ${formatErrorMessage(testError)}`);
155
+ }
156
+ finally {
157
+ setIsRunningTest(false);
158
+ }
159
+ });
160
+ const runRawParallelPostTest = () => __awaiter(this, void 0, void 0, function* () {
161
+ const normalizedFingerprintId = testFingerprintId.trim();
162
+ if (!normalizedFingerprintId) {
163
+ setTestResult('Please input a valid test fingerprint id.');
164
+ return;
165
+ }
166
+ setIsRunningTest(true);
167
+ setTestResult(`Running raw POST x3 with fingerprint: ${normalizedFingerprintId}`);
168
+ try {
169
+ const fingerprintHeaders = yield createFingerprintHeaders();
170
+ const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
171
+ method: 'POST',
172
+ headers: Object.assign(Object.assign({ 'Content-Type': 'application/json', [FINGERPRINT_SOURCE_REFER]: document.referrer || '' }, fingerprintHeaders), { 'x-fingerprint-id-v8': normalizedFingerprintId }),
173
+ body: JSON.stringify({ fingerprintId: normalizedFingerprintId }),
174
+ }));
175
+ const responses = yield Promise.all(requests);
176
+ const payloads = yield Promise.all(responses.map((response) => __awaiter(this, void 0, void 0, function* () {
177
+ var _a, _b;
178
+ const data = yield response.json().catch(() => ({}));
179
+ return {
180
+ ok: response.ok,
181
+ status: response.status,
182
+ isNewUser: typeof (data === null || data === void 0 ? void 0 : data.isNewUser) === 'boolean' ? data.isNewUser : null,
183
+ userId: typeof ((_a = data === null || data === void 0 ? void 0 : data.xUser) === null || _a === void 0 ? void 0 : _a.userId) === 'string' ? data.xUser.userId : '--',
184
+ fingerprintId: typeof ((_b = data === null || data === void 0 ? void 0 : data.xUser) === null || _b === void 0 ? void 0 : _b.fingerprintId) === 'string' ? data.xUser.fingerprintId : normalizedFingerprintId,
185
+ error: typeof (data === null || data === void 0 ? void 0 : data.error) === 'string' ? data.error : null,
186
+ };
187
+ })));
188
+ const createdUserIds = payloads
189
+ .filter((payload) => payload.ok && payload.isNewUser === true && payload.userId !== '--')
190
+ .map((payload) => payload.userId);
191
+ const reusedUserIds = payloads
192
+ .filter((payload) => payload.ok && payload.isNewUser === false && payload.userId !== '--')
193
+ .map((payload) => payload.userId);
194
+ const failedStatuses = payloads
195
+ .filter((payload) => !payload.ok)
196
+ .map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
197
+ setTestResult([
198
+ `Raw POST x3 done.`,
199
+ `created=${createdUserIds.length}`,
200
+ `reused=${reusedUserIds.length}`,
201
+ `failed=${failedStatuses.length}`,
202
+ `createdUserIds=[${createdUserIds.join(', ')}]`,
203
+ failedStatuses.length > 0 ? `failedStatuses=[${failedStatuses.join(', ')}]` : null,
204
+ ].filter(Boolean).join('\n'));
205
+ }
206
+ catch (testError) {
207
+ setTestResult(`Raw POST test failed: ${formatErrorMessage(testError)}`);
208
+ }
209
+ finally {
210
+ setIsRunningTest(false);
211
+ }
212
+ });
213
+ const applyTestFingerprintAndReload = () => {
214
+ const normalizedFingerprintId = testFingerprintId.trim();
215
+ if (!normalizedFingerprintId) {
216
+ setTestResult('Please input a valid test fingerprint id before applying.');
217
+ return;
218
+ }
219
+ try {
220
+ setFingerprintId(normalizedFingerprintId);
221
+ setTestResult(`Applied test fingerprint and reloading: ${normalizedFingerprintId}`);
222
+ window.location.reload();
223
+ }
224
+ catch (testError) {
225
+ setTestResult(`Apply test fingerprint failed: ${formatErrorMessage(testError)}`);
226
+ }
227
+ };
228
+ const regenerateTestFingerprint = () => {
229
+ const nextFingerprintId = buildDebugFingerprintId();
230
+ setTestFingerprintId(nextFingerprintId);
231
+ setTestResult(`Generated test fingerprint: ${nextFingerprintId}`);
232
+ };
233
+ return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsx(globalLucideIcons.Lightbulb, { className: "size-6 text-white" }) })), isOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxs("div", { ref: modalRef, className: cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsx("header", { className: "mb-4", children: jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor), children: [jsx(globalLucideIcons.ShieldUser, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
234
+ ? cn('border-transparent text-white', themeButtonGradientClass, themeButtonGradientHoverClass)
235
+ : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsx("span", { children: "Concurrent Test" }), jsx("span", { className: cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
236
+ ? 'bg-white/25'
237
+ : 'bg-slate-300 dark:bg-slate-700'), children: jsx("span", { className: cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsx("button", { type: "button", "aria-label": "Close fingerprint panel", className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsx(globalLucideIcons.X, { className: "size-4" }) })] })] }) }), jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxs(Fragment, { children: [jsx(PanelSection, { icon: jsx(globalLucideIcons.Fingerprint, { className: "size-4" }), title: "User", rightInfo: jsx(StatusTag, { value: userStatus }), items: [
238
+ { label: 'UserID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
239
+ { label: 'NickName', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
240
+ { label: 'FingerprintID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
241
+ { label: 'ClerkUserID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
242
+ { label: 'Email', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
243
+ { label: 'StripeCusID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
244
+ { label: 'CreatedAt', value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
245
+ ] }), jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsx(PanelHeader, { icon: jsx(globalLucideIcons.Gem, { className: "size-4" }), title: "Credits Info", rightInfo: jsx("span", { className: cn("font-semibold", themeIconColor), children: totalCredits }) }), jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
246
+ const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
247
+ return (jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsx("span", { children: bucket.label })] }), jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsx("span", { children: formatRangeText(bucket.start, bucket.end) }), jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
248
+ })) : (jsx(EmptyPlaceholder, { label: "No Credits Yet", icon: jsx(globalLucideIcons.DatabaseZap, { className: "size-4" }) })) })] }), jsx(PanelSection, { icon: jsx(globalLucideIcons.Bell, { className: "size-4" }), title: "Subscription", rightInfo: jsx(StatusTag, { value: subStatus }), items: [
249
+ { label: 'Plan', value: subscriptionStatus.priceName },
250
+ { label: 'Period', value: subscriptionStatus.period },
251
+ { label: 'Allocated', value: subscriptionStatus.creditsAllocated },
252
+ { label: 'SubID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
253
+ { label: 'OrderID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
254
+ { label: 'PriceID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
255
+ ] })] })) : (jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsx(PanelHeader, { icon: jsx(globalLucideIcons.DatabaseZap, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Current Browser" }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Generate New" }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("input", { value: testFingerprintId, onChange: (e) => setTestFingerprintId(e.target.value), className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 outline-none transition focus:border-slate-400 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", placeholder: "fp_test_dbg_20260322_xxx" }), jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": "Generate new test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsx(globalLucideIcons.RefreshCcw, { className: "size-4" }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: applyTestFingerprintAndReload, "aria-label": "Apply test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsx(globalLucideIcons.CheckCheck, { className: "size-4" }) })] })] })] }), jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: "Frontend Prevention Test" }), jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', themeButtonGradientClass, themeButtonGradientHoverClass), children: "Backend Idempotency Test" })] }), jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || 'No test executed yet.' }) })] })), error && (jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxs("div", { className: "flex items-start gap-2", children: [jsx(globalLucideIcons.X, { className: "mt-0.5 size-4 shrink-0" }), jsx("span", { children: error })] }), jsx("button", { type: "button", "aria-label": "Dismiss error", onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsx(globalLucideIcons.X, { className: "size-4" }) })] }))] })] })] }))] }));
146
256
  }
147
257
  /* ==================== 新增辅助组件 ==================== */
148
258
  // 标题行:左侧图标+标题,右侧信息(右对齐)
@@ -173,7 +283,7 @@ function formatRangeText(start, end) {
173
283
  const safeStart = start && start.trim() ? start : '';
174
284
  const safeEnd = end && end.trim() ? end : '';
175
285
  if (!safeStart && !safeEnd) {
176
- return '无记录';
286
+ return 'No records';
177
287
  }
178
288
  if (!safeStart) {
179
289
  return safeEnd;
@@ -208,5 +318,19 @@ function StatusTag({ value }) {
208
318
  const badgeClass = colorMap[normalized] || defaultColor;
209
319
  return (jsx("span", { className: cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
210
320
  }
321
+ function buildDebugFingerprintId() {
322
+ const timestamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
323
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
324
+ return `fp_test_dbg_${timestamp}_${randomSuffix}`;
325
+ }
326
+ function formatErrorMessage(error) {
327
+ if (error instanceof Error) {
328
+ return error.message;
329
+ }
330
+ if (typeof error === 'string') {
331
+ return error;
332
+ }
333
+ return 'Unknown error';
334
+ }
211
335
 
212
336
  export { FingerprintProvider, FingerprintStatus, useFingerprintContext, useFingerprintContextSafe, withFingerprint };
@@ -53,6 +53,7 @@ export interface UseFingerprintResult {
53
53
  isLoading: boolean;
54
54
  isInitialized: boolean;
55
55
  error: string | null;
56
+ clearError: () => void;
56
57
  initializeAnonymousUser: () => Promise<void>;
57
58
  refreshUserData: () => Promise<void>;
58
59
  }