@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.
- package/dist/clerk/fingerprint/fingerprint-provider.js +150 -26
- package/dist/clerk/fingerprint/fingerprint-provider.mjs +151 -27
- package/dist/clerk/fingerprint/types.d.ts +1 -0
- package/dist/clerk/fingerprint/use-fingerprint.js +20 -1
- package/dist/clerk/fingerprint/use-fingerprint.mjs +21 -2
- package/package.json +1 -1
- package/src/clerk/fingerprint/fingerprint-provider.tsx +349 -87
- package/src/clerk/fingerprint/types.ts +2 -1
- package/src/clerk/fingerprint/use-fingerprint.ts +23 -2
|
@@ -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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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 };
|