@windrun-huaiin/third-ui 20.0.0 → 20.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 +58 -49
- package/dist/clerk/fingerprint/fingerprint-provider.mjs +58 -49
- package/dist/main/alert-dialog/ads-alert-dialog.d.ts +15 -0
- package/dist/main/alert-dialog/ads-alert-dialog.js +24 -0
- package/dist/main/alert-dialog/ads-alert-dialog.mjs +22 -0
- package/dist/main/alert-dialog/confirm-dialog.d.ts +15 -0
- package/dist/main/alert-dialog/confirm-dialog.js +40 -0
- package/dist/main/alert-dialog/confirm-dialog.mjs +38 -0
- package/dist/main/alert-dialog/dialog-styles.d.ts +14 -0
- package/dist/main/alert-dialog/dialog-styles.js +35 -0
- package/dist/main/alert-dialog/dialog-styles.mjs +20 -0
- package/dist/main/alert-dialog/high-priority-confirm-dialog.d.ts +12 -0
- package/dist/main/alert-dialog/high-priority-confirm-dialog.js +23 -0
- package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +21 -0
- package/dist/main/alert-dialog/index.d.ts +4 -0
- package/dist/main/alert-dialog/info-dialog.d.ts +13 -0
- package/dist/main/alert-dialog/info-dialog.js +50 -0
- package/dist/main/alert-dialog/info-dialog.mjs +48 -0
- package/dist/main/index.d.ts +1 -1
- package/dist/main/index.js +7 -1
- package/dist/main/index.mjs +4 -1
- package/package.json +2 -2
- package/src/clerk/fingerprint/fingerprint-provider.tsx +155 -62
- package/src/main/{ads-alert-dialog.tsx → alert-dialog/ads-alert-dialog.tsx} +46 -29
- package/src/main/alert-dialog/confirm-dialog.tsx +131 -0
- package/src/main/alert-dialog/dialog-styles.ts +73 -0
- package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +94 -0
- package/src/main/alert-dialog/index.ts +7 -0
- package/src/main/alert-dialog/info-dialog.tsx +139 -0
- package/src/main/index.ts +1 -1
|
@@ -6,6 +6,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
6
6
|
var icons = require('@windrun-huaiin/base-ui/icons');
|
|
7
7
|
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
8
8
|
var utils = require('@windrun-huaiin/lib/utils');
|
|
9
|
+
var nextIntl = require('next-intl');
|
|
9
10
|
var React = require('react');
|
|
10
11
|
var useFingerprint = require('./use-fingerprint.js');
|
|
11
12
|
var ui = require('@windrun-huaiin/base-ui/ui');
|
|
@@ -14,6 +15,10 @@ var fingerprintDebug = require('./fingerprint-debug.js');
|
|
|
14
15
|
var fingerprintShared = require('./fingerprint-shared.js');
|
|
15
16
|
|
|
16
17
|
const FingerprintContext = React.createContext(undefined);
|
|
18
|
+
function useFingerprintStatusTranslations() {
|
|
19
|
+
const messages = nextIntl.useMessages();
|
|
20
|
+
return messages.fingerprint;
|
|
21
|
+
}
|
|
17
22
|
/**
|
|
18
23
|
* Fingerprint Provider Component
|
|
19
24
|
* 为应用提供fingerprint和匿名用户管理功能
|
|
@@ -53,6 +58,7 @@ function withFingerprint(Component, config) {
|
|
|
53
58
|
* 组件:显示用户状态和积分信息(用于调试)
|
|
54
59
|
*/
|
|
55
60
|
function FingerprintStatus() {
|
|
61
|
+
const translations = useFingerprintStatusTranslations();
|
|
56
62
|
const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, } = useFingerprintContext();
|
|
57
63
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
58
64
|
const [panelMode, setPanelMode] = React.useState('info');
|
|
@@ -92,7 +98,7 @@ function FingerprintStatus() {
|
|
|
92
98
|
return [
|
|
93
99
|
{
|
|
94
100
|
key: 'paid',
|
|
95
|
-
label:
|
|
101
|
+
label: translations.creditBuckets.paid,
|
|
96
102
|
icon: jsxRuntime.jsx(icons.Settings2Icon, { className: "size-4 text-green-500 dark:text-green-300" }),
|
|
97
103
|
balance: xCredit.balancePaid,
|
|
98
104
|
total: xCredit.totalPaidLimit,
|
|
@@ -101,7 +107,7 @@ function FingerprintStatus() {
|
|
|
101
107
|
},
|
|
102
108
|
{
|
|
103
109
|
key: 'oneTimePaid',
|
|
104
|
-
label:
|
|
110
|
+
label: translations.creditBuckets.oneTimePaid,
|
|
105
111
|
icon: jsxRuntime.jsx(icons.CoinsIcon, { className: "size-4 text-amber-500 dark:text-amber-300" }),
|
|
106
112
|
balance: xCredit.balanceOneTimePaid,
|
|
107
113
|
total: xCredit.totalOneTimePaidLimit,
|
|
@@ -110,7 +116,7 @@ function FingerprintStatus() {
|
|
|
110
116
|
},
|
|
111
117
|
{
|
|
112
118
|
key: 'free',
|
|
113
|
-
label:
|
|
119
|
+
label: translations.creditBuckets.free,
|
|
114
120
|
icon: jsxRuntime.jsx(icons.GiftIcon, { className: "size-4 text-purple-500 dark:text-purple-300" }),
|
|
115
121
|
balance: xCredit.balanceFree,
|
|
116
122
|
total: xCredit.totalFreeLimit,
|
|
@@ -118,15 +124,15 @@ function FingerprintStatus() {
|
|
|
118
124
|
end: xCredit.freeEnd,
|
|
119
125
|
},
|
|
120
126
|
];
|
|
121
|
-
}, [xCredit]);
|
|
127
|
+
}, [translations.creditBuckets.free, translations.creditBuckets.oneTimePaid, translations.creditBuckets.paid, xCredit]);
|
|
122
128
|
const subscriptionStatus = React.useMemo(() => {
|
|
123
129
|
var _a, _b;
|
|
124
130
|
if (!xSubscription) {
|
|
125
131
|
return {
|
|
126
|
-
status:
|
|
132
|
+
status: translations.placeholders.subscriptionStatusNever,
|
|
127
133
|
priceName: '--',
|
|
128
134
|
creditsAllocated: '--',
|
|
129
|
-
period:
|
|
135
|
+
period: translations.placeholders.subscriptionPeriodUnavailable,
|
|
130
136
|
};
|
|
131
137
|
}
|
|
132
138
|
return {
|
|
@@ -135,9 +141,9 @@ function FingerprintStatus() {
|
|
|
135
141
|
creditsAllocated: typeof xSubscription.creditsAllocated === 'number'
|
|
136
142
|
? formatNumber(xSubscription.creditsAllocated)
|
|
137
143
|
: '--',
|
|
138
|
-
period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd),
|
|
144
|
+
period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd, translations),
|
|
139
145
|
};
|
|
140
|
-
}, [xSubscription]);
|
|
146
|
+
}, [translations.placeholders.subscriptionPeriodUnavailable, translations.placeholders.subscriptionStatusNever, xSubscription]);
|
|
141
147
|
const userStatus = (xUser === null || xUser === void 0 ? void 0 : xUser.status) || '--';
|
|
142
148
|
const totalCredits = formatNumber(xCredit === null || xCredit === void 0 ? void 0 : xCredit.totalBalance);
|
|
143
149
|
const subStatus = subscriptionStatus.status;
|
|
@@ -145,22 +151,22 @@ function FingerprintStatus() {
|
|
|
145
151
|
const runContextParallelInitTest = () => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
146
152
|
const debugFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : fingerprintDebug.getOrCreateDebugFingerprintOverride();
|
|
147
153
|
if (!debugFingerprintId) {
|
|
148
|
-
setTestResult(
|
|
154
|
+
setTestResult(translations.messages.testFingerprintNotReady);
|
|
149
155
|
return;
|
|
150
156
|
}
|
|
151
157
|
setActiveDebugFingerprintId(debugFingerprintId);
|
|
152
158
|
setIsRunningTest(true);
|
|
153
|
-
setTestResult(
|
|
159
|
+
setTestResult(tpl(translations.messages.runningFrontendPreventionTest, { fingerprintId: debugFingerprintId }));
|
|
154
160
|
try {
|
|
155
161
|
yield Promise.all([
|
|
156
162
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
157
163
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
158
164
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
159
165
|
]);
|
|
160
|
-
setTestResult(
|
|
166
|
+
setTestResult(tpl(translations.messages.frontendPreventionTestFinished, { fingerprintId: debugFingerprintId }));
|
|
161
167
|
}
|
|
162
168
|
catch (testError) {
|
|
163
|
-
setTestResult(
|
|
169
|
+
setTestResult(tpl(translations.messages.frontendPreventionTestFailed, { error: formatErrorMessage(testError, translations) }));
|
|
164
170
|
}
|
|
165
171
|
finally {
|
|
166
172
|
setIsRunningTest(false);
|
|
@@ -180,7 +186,7 @@ function FingerprintStatus() {
|
|
|
180
186
|
});
|
|
181
187
|
if (!response.ok) {
|
|
182
188
|
const errorData = yield response.json().catch(() => ({}));
|
|
183
|
-
throw new Error(errorData.error ||
|
|
189
|
+
throw new Error(errorData.error || translations.messages.failedToInitializeAnonymousUser);
|
|
184
190
|
}
|
|
185
191
|
yield response.json().catch(() => ({}));
|
|
186
192
|
}
|
|
@@ -191,12 +197,12 @@ function FingerprintStatus() {
|
|
|
191
197
|
const runRawParallelPostTest = () => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
192
198
|
const normalizedFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : fingerprintDebug.getOrCreateDebugFingerprintOverride();
|
|
193
199
|
if (!normalizedFingerprintId) {
|
|
194
|
-
setTestResult(
|
|
200
|
+
setTestResult(translations.messages.testFingerprintNotReady);
|
|
195
201
|
return;
|
|
196
202
|
}
|
|
197
203
|
setActiveDebugFingerprintId(normalizedFingerprintId);
|
|
198
204
|
setIsRunningTest(true);
|
|
199
|
-
setTestResult(
|
|
205
|
+
setTestResult(tpl(translations.messages.runningBackendIdempotencyTest, { fingerprintId: normalizedFingerprintId }));
|
|
200
206
|
try {
|
|
201
207
|
const fingerprintHeaders = yield fingerprintClient.createFingerprintHeaders();
|
|
202
208
|
const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
|
|
@@ -227,16 +233,16 @@ function FingerprintStatus() {
|
|
|
227
233
|
.filter((payload) => !payload.ok)
|
|
228
234
|
.map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
|
|
229
235
|
setTestResult([
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
failedStatuses.length > 0 ?
|
|
236
|
+
translations.messages.backendIdempotencyTestDone,
|
|
237
|
+
tpl(translations.messages.createdCount, { count: createdUserIds.length }),
|
|
238
|
+
tpl(translations.messages.reusedCount, { count: reusedUserIds.length }),
|
|
239
|
+
tpl(translations.messages.failedCount, { count: failedStatuses.length }),
|
|
240
|
+
tpl(translations.messages.createdUserIds, { value: createdUserIds.join(', ') }),
|
|
241
|
+
failedStatuses.length > 0 ? tpl(translations.messages.failedStatuses, { value: failedStatuses.join(', ') }) : null,
|
|
236
242
|
].filter(Boolean).join('\n'));
|
|
237
243
|
}
|
|
238
244
|
catch (testError) {
|
|
239
|
-
setTestResult(
|
|
245
|
+
setTestResult(tpl(translations.messages.backendIdempotencyTestFailed, { error: formatErrorMessage(testError, translations) }));
|
|
240
246
|
}
|
|
241
247
|
finally {
|
|
242
248
|
setIsRunningTest(false);
|
|
@@ -245,31 +251,31 @@ function FingerprintStatus() {
|
|
|
245
251
|
const regenerateTestFingerprint = () => {
|
|
246
252
|
const nextFingerprintId = fingerprintDebug.regenerateDebugFingerprintOverride();
|
|
247
253
|
setActiveDebugFingerprintId(nextFingerprintId);
|
|
248
|
-
setTestResult(
|
|
254
|
+
setTestResult(tpl(translations.messages.generatedTestFingerprintOverride, { fingerprintId: nextFingerprintId }));
|
|
249
255
|
};
|
|
250
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isOpen && (jsxRuntime.jsx("button", { onClick: handleToggle, type: "button", "aria-label":
|
|
256
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isOpen && (jsxRuntime.jsx("button", { onClick: handleToggle, type: "button", "aria-label": translations.panel.toggleAriaLabel, 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(icons.LightbulbIcon, { 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(icons.ShieldUserIcon, { className: "size-4" }), translations.panel.title] }), 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'
|
|
251
257
|
? utils.cn('border-transparent text-white', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass)
|
|
252
|
-
: themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsxRuntime.jsx("span", { children:
|
|
258
|
+
: themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsxRuntime.jsx("span", { children: translations.panel.testModeLabel }), jsxRuntime.jsx("span", { className: utils.cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
|
|
253
259
|
? 'bg-white/25'
|
|
254
|
-
: '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":
|
|
255
|
-
{ label:
|
|
256
|
-
{ label:
|
|
257
|
-
{ label:
|
|
258
|
-
{ label:
|
|
259
|
-
{ label:
|
|
260
|
-
{ label:
|
|
261
|
-
{ label:
|
|
262
|
-
] }), 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(icons.GemIcon, { className: "size-4" }), title:
|
|
260
|
+
: '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": translations.panel.closeAriaLabel, 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(icons.XIcon, { className: "size-4" }) })] })] }) }), jsxRuntime.jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(icons.FingerprintIcon, { className: "size-4" }), title: translations.sections.user, rightInfo: jsxRuntime.jsx(StatusTag, { value: userStatus, translations: translations }), items: [
|
|
261
|
+
{ label: translations.labels.userId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
|
|
262
|
+
{ label: translations.labels.nickName, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
|
|
263
|
+
{ label: translations.labels.fingerprintId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
|
|
264
|
+
{ label: translations.labels.clerkUserId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
|
|
265
|
+
{ label: translations.labels.email, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
|
|
266
|
+
{ label: translations.labels.stripeCusId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
|
|
267
|
+
{ label: translations.labels.createdAt, value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
|
|
268
|
+
] }), 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(icons.GemIcon, { className: "size-4" }), title: translations.sections.creditsInfo, 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) => {
|
|
263
269
|
const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
|
|
264
|
-
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));
|
|
265
|
-
})) : (jsxRuntime.jsx(EmptyPlaceholder, { label:
|
|
266
|
-
{ label:
|
|
267
|
-
{ label:
|
|
268
|
-
{ label:
|
|
269
|
-
{ label:
|
|
270
|
-
{ label:
|
|
271
|
-
{ label:
|
|
272
|
-
] })] })) : (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(icons.DatabaseZapIcon, { className: "size-4" }), title:
|
|
270
|
+
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, translations) }), jsxRuntime.jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
|
|
271
|
+
})) : (jsxRuntime.jsx(EmptyPlaceholder, { label: translations.placeholders.noCreditsYet, icon: jsxRuntime.jsx(icons.DatabaseZapIcon, { className: "size-4" }) })) })] }), jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(icons.BellIcon, { className: "size-4" }), title: translations.sections.subscription, rightInfo: jsxRuntime.jsx(StatusTag, { value: subStatus, translations: translations }), items: [
|
|
272
|
+
{ label: translations.labels.plan, value: subscriptionStatus.priceName },
|
|
273
|
+
{ label: translations.labels.period, value: subscriptionStatus.period },
|
|
274
|
+
{ label: translations.labels.allocated, value: subscriptionStatus.creditsAllocated },
|
|
275
|
+
{ label: translations.labels.subId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
|
|
276
|
+
{ label: translations.labels.orderId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
|
|
277
|
+
{ label: translations.labels.priceId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
|
|
278
|
+
] })] })) : (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(icons.DatabaseZapIcon, { className: "size-4" }), title: translations.sections.concurrentBaseInfo, rightInfo: jsxRuntime.jsx(StatusTag, { value: isRunningTest ? translations.status.pending : translations.status.idle, translations: translations }) }), 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: translations.labels.realBrowser }), 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: translations.labels.testOverride }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsxRuntime.jsx("div", { 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 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsxRuntime.jsx(ui.CopyableText, { text: activeDebugFingerprintId || '' }) }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": translations.actions.generateNewTestFingerprintAriaLabel, 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(icons.RefreshCcwIcon, { 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: translations.actions.frontendPreventionTest }), 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: translations.actions.backendIdempotencyTest })] }), 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 || translations.placeholders.noTestExecutedYet }) })] })), 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(icons.XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsxRuntime.jsx("span", { children: error })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": translations.actions.dismissErrorAriaLabel, 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(icons.XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
|
|
273
279
|
}
|
|
274
280
|
/* ==================== 新增辅助组件 ==================== */
|
|
275
281
|
// 标题行:左侧图标+标题,右侧信息(右对齐)
|
|
@@ -296,11 +302,11 @@ function computeProgress(balance, total) {
|
|
|
296
302
|
return 0;
|
|
297
303
|
return Math.min(Math.max(ratio, 0), 1);
|
|
298
304
|
}
|
|
299
|
-
function formatRangeText(start, end) {
|
|
305
|
+
function formatRangeText(start, end, translations) {
|
|
300
306
|
const safeStart = start && start.trim() ? start : '';
|
|
301
307
|
const safeEnd = end && end.trim() ? end : '';
|
|
302
308
|
if (!safeStart && !safeEnd) {
|
|
303
|
-
return
|
|
309
|
+
return translations.placeholders.noRecords;
|
|
304
310
|
}
|
|
305
311
|
if (!safeStart) {
|
|
306
312
|
return safeEnd;
|
|
@@ -310,9 +316,9 @@ function formatRangeText(start, end) {
|
|
|
310
316
|
}
|
|
311
317
|
return `${safeStart} - ${safeEnd}`;
|
|
312
318
|
}
|
|
313
|
-
function StatusTag({ value }) {
|
|
319
|
+
function StatusTag({ value, translations, }) {
|
|
314
320
|
if (!value)
|
|
315
|
-
return jsxRuntime.jsx("span", { className: "text-slate-400", children:
|
|
321
|
+
return jsxRuntime.jsx("span", { className: "text-slate-400", children: translations.placeholders.none });
|
|
316
322
|
const normalized = value.toLowerCase();
|
|
317
323
|
const colorMap = {
|
|
318
324
|
// 绿色:正常/活跃
|
|
@@ -335,14 +341,17 @@ function StatusTag({ value }) {
|
|
|
335
341
|
const badgeClass = colorMap[normalized] || defaultColor;
|
|
336
342
|
return (jsxRuntime.jsx("span", { className: utils.cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
|
|
337
343
|
}
|
|
338
|
-
function formatErrorMessage(error) {
|
|
344
|
+
function formatErrorMessage(error, translations) {
|
|
339
345
|
if (error instanceof Error) {
|
|
340
346
|
return error.message;
|
|
341
347
|
}
|
|
342
348
|
if (typeof error === 'string') {
|
|
343
349
|
return error;
|
|
344
350
|
}
|
|
345
|
-
return
|
|
351
|
+
return translations.placeholders.unknownError;
|
|
352
|
+
}
|
|
353
|
+
function tpl(template, values) {
|
|
354
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => { var _a; return String((_a = values[key]) !== null && _a !== void 0 ? _a : ''); });
|
|
346
355
|
}
|
|
347
356
|
|
|
348
357
|
exports.FingerprintProvider = FingerprintProvider;
|
|
@@ -4,6 +4,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
|
4
4
|
import { Settings2Icon, CoinsIcon, GiftIcon, LightbulbIcon, ShieldUserIcon, XIcon, FingerprintIcon, GemIcon, DatabaseZapIcon, BellIcon, RefreshCcwIcon } from '@windrun-huaiin/base-ui/icons';
|
|
5
5
|
import { themeIconColor, themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
|
|
6
6
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
7
|
+
import { useMessages } from 'next-intl';
|
|
7
8
|
import { createContext, useContext, useState, useRef, useEffect, useMemo } from 'react';
|
|
8
9
|
import { useFingerprint } from './use-fingerprint.mjs';
|
|
9
10
|
import { CopyableText } from '@windrun-huaiin/base-ui/ui';
|
|
@@ -12,6 +13,10 @@ import { getOrCreateDebugFingerprintOverride, regenerateDebugFingerprintOverride
|
|
|
12
13
|
import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
|
|
13
14
|
|
|
14
15
|
const FingerprintContext = createContext(undefined);
|
|
16
|
+
function useFingerprintStatusTranslations() {
|
|
17
|
+
const messages = useMessages();
|
|
18
|
+
return messages.fingerprint;
|
|
19
|
+
}
|
|
15
20
|
/**
|
|
16
21
|
* Fingerprint Provider Component
|
|
17
22
|
* 为应用提供fingerprint和匿名用户管理功能
|
|
@@ -51,6 +56,7 @@ function withFingerprint(Component, config) {
|
|
|
51
56
|
* 组件:显示用户状态和积分信息(用于调试)
|
|
52
57
|
*/
|
|
53
58
|
function FingerprintStatus() {
|
|
59
|
+
const translations = useFingerprintStatusTranslations();
|
|
54
60
|
const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, } = useFingerprintContext();
|
|
55
61
|
const [isOpen, setIsOpen] = useState(false);
|
|
56
62
|
const [panelMode, setPanelMode] = useState('info');
|
|
@@ -90,7 +96,7 @@ function FingerprintStatus() {
|
|
|
90
96
|
return [
|
|
91
97
|
{
|
|
92
98
|
key: 'paid',
|
|
93
|
-
label:
|
|
99
|
+
label: translations.creditBuckets.paid,
|
|
94
100
|
icon: jsx(Settings2Icon, { className: "size-4 text-green-500 dark:text-green-300" }),
|
|
95
101
|
balance: xCredit.balancePaid,
|
|
96
102
|
total: xCredit.totalPaidLimit,
|
|
@@ -99,7 +105,7 @@ function FingerprintStatus() {
|
|
|
99
105
|
},
|
|
100
106
|
{
|
|
101
107
|
key: 'oneTimePaid',
|
|
102
|
-
label:
|
|
108
|
+
label: translations.creditBuckets.oneTimePaid,
|
|
103
109
|
icon: jsx(CoinsIcon, { className: "size-4 text-amber-500 dark:text-amber-300" }),
|
|
104
110
|
balance: xCredit.balanceOneTimePaid,
|
|
105
111
|
total: xCredit.totalOneTimePaidLimit,
|
|
@@ -108,7 +114,7 @@ function FingerprintStatus() {
|
|
|
108
114
|
},
|
|
109
115
|
{
|
|
110
116
|
key: 'free',
|
|
111
|
-
label:
|
|
117
|
+
label: translations.creditBuckets.free,
|
|
112
118
|
icon: jsx(GiftIcon, { className: "size-4 text-purple-500 dark:text-purple-300" }),
|
|
113
119
|
balance: xCredit.balanceFree,
|
|
114
120
|
total: xCredit.totalFreeLimit,
|
|
@@ -116,15 +122,15 @@ function FingerprintStatus() {
|
|
|
116
122
|
end: xCredit.freeEnd,
|
|
117
123
|
},
|
|
118
124
|
];
|
|
119
|
-
}, [xCredit]);
|
|
125
|
+
}, [translations.creditBuckets.free, translations.creditBuckets.oneTimePaid, translations.creditBuckets.paid, xCredit]);
|
|
120
126
|
const subscriptionStatus = useMemo(() => {
|
|
121
127
|
var _a, _b;
|
|
122
128
|
if (!xSubscription) {
|
|
123
129
|
return {
|
|
124
|
-
status:
|
|
130
|
+
status: translations.placeholders.subscriptionStatusNever,
|
|
125
131
|
priceName: '--',
|
|
126
132
|
creditsAllocated: '--',
|
|
127
|
-
period:
|
|
133
|
+
period: translations.placeholders.subscriptionPeriodUnavailable,
|
|
128
134
|
};
|
|
129
135
|
}
|
|
130
136
|
return {
|
|
@@ -133,9 +139,9 @@ function FingerprintStatus() {
|
|
|
133
139
|
creditsAllocated: typeof xSubscription.creditsAllocated === 'number'
|
|
134
140
|
? formatNumber(xSubscription.creditsAllocated)
|
|
135
141
|
: '--',
|
|
136
|
-
period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd),
|
|
142
|
+
period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd, translations),
|
|
137
143
|
};
|
|
138
|
-
}, [xSubscription]);
|
|
144
|
+
}, [translations.placeholders.subscriptionPeriodUnavailable, translations.placeholders.subscriptionStatusNever, xSubscription]);
|
|
139
145
|
const userStatus = (xUser === null || xUser === void 0 ? void 0 : xUser.status) || '--';
|
|
140
146
|
const totalCredits = formatNumber(xCredit === null || xCredit === void 0 ? void 0 : xCredit.totalBalance);
|
|
141
147
|
const subStatus = subscriptionStatus.status;
|
|
@@ -143,22 +149,22 @@ function FingerprintStatus() {
|
|
|
143
149
|
const runContextParallelInitTest = () => __awaiter(this, void 0, void 0, function* () {
|
|
144
150
|
const debugFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
|
|
145
151
|
if (!debugFingerprintId) {
|
|
146
|
-
setTestResult(
|
|
152
|
+
setTestResult(translations.messages.testFingerprintNotReady);
|
|
147
153
|
return;
|
|
148
154
|
}
|
|
149
155
|
setActiveDebugFingerprintId(debugFingerprintId);
|
|
150
156
|
setIsRunningTest(true);
|
|
151
|
-
setTestResult(
|
|
157
|
+
setTestResult(tpl(translations.messages.runningFrontendPreventionTest, { fingerprintId: debugFingerprintId }));
|
|
152
158
|
try {
|
|
153
159
|
yield Promise.all([
|
|
154
160
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
155
161
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
156
162
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
157
163
|
]);
|
|
158
|
-
setTestResult(
|
|
164
|
+
setTestResult(tpl(translations.messages.frontendPreventionTestFinished, { fingerprintId: debugFingerprintId }));
|
|
159
165
|
}
|
|
160
166
|
catch (testError) {
|
|
161
|
-
setTestResult(
|
|
167
|
+
setTestResult(tpl(translations.messages.frontendPreventionTestFailed, { error: formatErrorMessage(testError, translations) }));
|
|
162
168
|
}
|
|
163
169
|
finally {
|
|
164
170
|
setIsRunningTest(false);
|
|
@@ -178,7 +184,7 @@ function FingerprintStatus() {
|
|
|
178
184
|
});
|
|
179
185
|
if (!response.ok) {
|
|
180
186
|
const errorData = yield response.json().catch(() => ({}));
|
|
181
|
-
throw new Error(errorData.error ||
|
|
187
|
+
throw new Error(errorData.error || translations.messages.failedToInitializeAnonymousUser);
|
|
182
188
|
}
|
|
183
189
|
yield response.json().catch(() => ({}));
|
|
184
190
|
}
|
|
@@ -189,12 +195,12 @@ function FingerprintStatus() {
|
|
|
189
195
|
const runRawParallelPostTest = () => __awaiter(this, void 0, void 0, function* () {
|
|
190
196
|
const normalizedFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
|
|
191
197
|
if (!normalizedFingerprintId) {
|
|
192
|
-
setTestResult(
|
|
198
|
+
setTestResult(translations.messages.testFingerprintNotReady);
|
|
193
199
|
return;
|
|
194
200
|
}
|
|
195
201
|
setActiveDebugFingerprintId(normalizedFingerprintId);
|
|
196
202
|
setIsRunningTest(true);
|
|
197
|
-
setTestResult(
|
|
203
|
+
setTestResult(tpl(translations.messages.runningBackendIdempotencyTest, { fingerprintId: normalizedFingerprintId }));
|
|
198
204
|
try {
|
|
199
205
|
const fingerprintHeaders = yield createFingerprintHeaders();
|
|
200
206
|
const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
|
|
@@ -225,16 +231,16 @@ function FingerprintStatus() {
|
|
|
225
231
|
.filter((payload) => !payload.ok)
|
|
226
232
|
.map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
|
|
227
233
|
setTestResult([
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
failedStatuses.length > 0 ?
|
|
234
|
+
translations.messages.backendIdempotencyTestDone,
|
|
235
|
+
tpl(translations.messages.createdCount, { count: createdUserIds.length }),
|
|
236
|
+
tpl(translations.messages.reusedCount, { count: reusedUserIds.length }),
|
|
237
|
+
tpl(translations.messages.failedCount, { count: failedStatuses.length }),
|
|
238
|
+
tpl(translations.messages.createdUserIds, { value: createdUserIds.join(', ') }),
|
|
239
|
+
failedStatuses.length > 0 ? tpl(translations.messages.failedStatuses, { value: failedStatuses.join(', ') }) : null,
|
|
234
240
|
].filter(Boolean).join('\n'));
|
|
235
241
|
}
|
|
236
242
|
catch (testError) {
|
|
237
|
-
setTestResult(
|
|
243
|
+
setTestResult(tpl(translations.messages.backendIdempotencyTestFailed, { error: formatErrorMessage(testError, translations) }));
|
|
238
244
|
}
|
|
239
245
|
finally {
|
|
240
246
|
setIsRunningTest(false);
|
|
@@ -243,31 +249,31 @@ function FingerprintStatus() {
|
|
|
243
249
|
const regenerateTestFingerprint = () => {
|
|
244
250
|
const nextFingerprintId = regenerateDebugFingerprintOverride();
|
|
245
251
|
setActiveDebugFingerprintId(nextFingerprintId);
|
|
246
|
-
setTestResult(
|
|
252
|
+
setTestResult(tpl(translations.messages.generatedTestFingerprintOverride, { fingerprintId: nextFingerprintId }));
|
|
247
253
|
};
|
|
248
|
-
return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label":
|
|
254
|
+
return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": translations.panel.toggleAriaLabel, 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(LightbulbIcon, { 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(ShieldUserIcon, { className: "size-4" }), translations.panel.title] }), 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'
|
|
249
255
|
? cn('border-transparent text-white', themeButtonGradientClass, themeButtonGradientHoverClass)
|
|
250
|
-
: themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsx("span", { children:
|
|
256
|
+
: themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsx("span", { children: translations.panel.testModeLabel }), jsx("span", { className: cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
|
|
251
257
|
? 'bg-white/25'
|
|
252
|
-
: '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":
|
|
253
|
-
{ label:
|
|
254
|
-
{ label:
|
|
255
|
-
{ label:
|
|
256
|
-
{ label:
|
|
257
|
-
{ label:
|
|
258
|
-
{ label:
|
|
259
|
-
{ label:
|
|
260
|
-
] }), 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(GemIcon, { className: "size-4" }), title:
|
|
258
|
+
: '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": translations.panel.closeAriaLabel, 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(XIcon, { className: "size-4" }) })] })] }) }), jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxs(Fragment, { children: [jsx(PanelSection, { icon: jsx(FingerprintIcon, { className: "size-4" }), title: translations.sections.user, rightInfo: jsx(StatusTag, { value: userStatus, translations: translations }), items: [
|
|
259
|
+
{ label: translations.labels.userId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
|
|
260
|
+
{ label: translations.labels.nickName, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
|
|
261
|
+
{ label: translations.labels.fingerprintId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
|
|
262
|
+
{ label: translations.labels.clerkUserId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
|
|
263
|
+
{ label: translations.labels.email, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
|
|
264
|
+
{ label: translations.labels.stripeCusId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
|
|
265
|
+
{ label: translations.labels.createdAt, value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
|
|
266
|
+
] }), 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(GemIcon, { className: "size-4" }), title: translations.sections.creditsInfo, rightInfo: jsx("span", { className: cn("font-semibold", themeIconColor), children: totalCredits }) }), jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
|
|
261
267
|
const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
|
|
262
|
-
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));
|
|
263
|
-
})) : (jsx(EmptyPlaceholder, { label:
|
|
264
|
-
{ label:
|
|
265
|
-
{ label:
|
|
266
|
-
{ label:
|
|
267
|
-
{ label:
|
|
268
|
-
{ label:
|
|
269
|
-
{ label:
|
|
270
|
-
] })] })) : (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(DatabaseZapIcon, { className: "size-4" }), title:
|
|
268
|
+
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, translations) }), jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
|
|
269
|
+
})) : (jsx(EmptyPlaceholder, { label: translations.placeholders.noCreditsYet, icon: jsx(DatabaseZapIcon, { className: "size-4" }) })) })] }), jsx(PanelSection, { icon: jsx(BellIcon, { className: "size-4" }), title: translations.sections.subscription, rightInfo: jsx(StatusTag, { value: subStatus, translations: translations }), items: [
|
|
270
|
+
{ label: translations.labels.plan, value: subscriptionStatus.priceName },
|
|
271
|
+
{ label: translations.labels.period, value: subscriptionStatus.period },
|
|
272
|
+
{ label: translations.labels.allocated, value: subscriptionStatus.creditsAllocated },
|
|
273
|
+
{ label: translations.labels.subId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
|
|
274
|
+
{ label: translations.labels.orderId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
|
|
275
|
+
{ label: translations.labels.priceId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
|
|
276
|
+
] })] })) : (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(DatabaseZapIcon, { className: "size-4" }), title: translations.sections.concurrentBaseInfo, rightInfo: jsx(StatusTag, { value: isRunningTest ? translations.status.pending : translations.status.idle, translations: translations }) }), 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: translations.labels.realBrowser }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: translations.labels.testOverride }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("div", { 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 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsx(CopyableText, { text: activeDebugFingerprintId || '' }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": translations.actions.generateNewTestFingerprintAriaLabel, 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(RefreshCcwIcon, { 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: translations.actions.frontendPreventionTest }), 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: translations.actions.backendIdempotencyTest })] }), 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 || translations.placeholders.noTestExecutedYet }) })] })), 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(XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsx("span", { children: error })] }), jsx("button", { type: "button", "aria-label": translations.actions.dismissErrorAriaLabel, 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(XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
|
|
271
277
|
}
|
|
272
278
|
/* ==================== 新增辅助组件 ==================== */
|
|
273
279
|
// 标题行:左侧图标+标题,右侧信息(右对齐)
|
|
@@ -294,11 +300,11 @@ function computeProgress(balance, total) {
|
|
|
294
300
|
return 0;
|
|
295
301
|
return Math.min(Math.max(ratio, 0), 1);
|
|
296
302
|
}
|
|
297
|
-
function formatRangeText(start, end) {
|
|
303
|
+
function formatRangeText(start, end, translations) {
|
|
298
304
|
const safeStart = start && start.trim() ? start : '';
|
|
299
305
|
const safeEnd = end && end.trim() ? end : '';
|
|
300
306
|
if (!safeStart && !safeEnd) {
|
|
301
|
-
return
|
|
307
|
+
return translations.placeholders.noRecords;
|
|
302
308
|
}
|
|
303
309
|
if (!safeStart) {
|
|
304
310
|
return safeEnd;
|
|
@@ -308,9 +314,9 @@ function formatRangeText(start, end) {
|
|
|
308
314
|
}
|
|
309
315
|
return `${safeStart} - ${safeEnd}`;
|
|
310
316
|
}
|
|
311
|
-
function StatusTag({ value }) {
|
|
317
|
+
function StatusTag({ value, translations, }) {
|
|
312
318
|
if (!value)
|
|
313
|
-
return jsx("span", { className: "text-slate-400", children:
|
|
319
|
+
return jsx("span", { className: "text-slate-400", children: translations.placeholders.none });
|
|
314
320
|
const normalized = value.toLowerCase();
|
|
315
321
|
const colorMap = {
|
|
316
322
|
// 绿色:正常/活跃
|
|
@@ -333,14 +339,17 @@ function StatusTag({ value }) {
|
|
|
333
339
|
const badgeClass = colorMap[normalized] || defaultColor;
|
|
334
340
|
return (jsx("span", { className: cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
|
|
335
341
|
}
|
|
336
|
-
function formatErrorMessage(error) {
|
|
342
|
+
function formatErrorMessage(error, translations) {
|
|
337
343
|
if (error instanceof Error) {
|
|
338
344
|
return error.message;
|
|
339
345
|
}
|
|
340
346
|
if (typeof error === 'string') {
|
|
341
347
|
return error;
|
|
342
348
|
}
|
|
343
|
-
return
|
|
349
|
+
return translations.placeholders.unknownError;
|
|
350
|
+
}
|
|
351
|
+
function tpl(template, values) {
|
|
352
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => { var _a; return String((_a = values[key]) !== null && _a !== void 0 ? _a : ''); });
|
|
344
353
|
}
|
|
345
354
|
|
|
346
355
|
export { FingerprintProvider, FingerprintStatus, useFingerprintContext, useFingerprintContextSafe, withFingerprint };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface AdsAlertDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
title: React.ReactNode;
|
|
6
|
+
description: React.ReactNode;
|
|
7
|
+
imgSrc?: string;
|
|
8
|
+
imgHref?: string;
|
|
9
|
+
onCancel?: () => void;
|
|
10
|
+
cancelText?: string;
|
|
11
|
+
confirmText?: string;
|
|
12
|
+
onConfirm?: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function AdsAlertDialog({ open, onOpenChange, title, description, imgSrc, imgHref, cancelText, onCancel, confirmText, onConfirm, }: AdsAlertDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|