@windrun-huaiin/third-ui 16.0.1 → 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 +4 -4
- 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
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '@windrun-huaiin/base-ui/icons';
|
|
16
16
|
import { themeButtonGradientClass, themeButtonGradientHoverClass, themeIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
17
17
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
18
|
+
import { useMessages } from 'next-intl';
|
|
18
19
|
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
19
20
|
import type { FingerprintContextType, FingerprintProviderProps } from './types';
|
|
20
21
|
import { useFingerprint } from './use-fingerprint';
|
|
@@ -28,6 +29,83 @@ import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared';
|
|
|
28
29
|
|
|
29
30
|
const FingerprintContext = createContext<FingerprintContextType | undefined>(undefined);
|
|
30
31
|
|
|
32
|
+
type FingerprintStatusTranslations = {
|
|
33
|
+
panel: {
|
|
34
|
+
toggleAriaLabel: string;
|
|
35
|
+
title: string;
|
|
36
|
+
testModeLabel: string;
|
|
37
|
+
closeAriaLabel: string;
|
|
38
|
+
};
|
|
39
|
+
sections: {
|
|
40
|
+
user: string;
|
|
41
|
+
creditsInfo: string;
|
|
42
|
+
subscription: string;
|
|
43
|
+
concurrentBaseInfo: string;
|
|
44
|
+
};
|
|
45
|
+
labels: {
|
|
46
|
+
userId: string;
|
|
47
|
+
nickName: string;
|
|
48
|
+
fingerprintId: string;
|
|
49
|
+
clerkUserId: string;
|
|
50
|
+
email: string;
|
|
51
|
+
stripeCusId: string;
|
|
52
|
+
createdAt: string;
|
|
53
|
+
plan: string;
|
|
54
|
+
period: string;
|
|
55
|
+
allocated: string;
|
|
56
|
+
subId: string;
|
|
57
|
+
orderId: string;
|
|
58
|
+
priceId: string;
|
|
59
|
+
realBrowser: string;
|
|
60
|
+
testOverride: string;
|
|
61
|
+
};
|
|
62
|
+
creditBuckets: {
|
|
63
|
+
paid: string;
|
|
64
|
+
oneTimePaid: string;
|
|
65
|
+
free: string;
|
|
66
|
+
};
|
|
67
|
+
placeholders: {
|
|
68
|
+
subscriptionStatusNever: string;
|
|
69
|
+
subscriptionPeriodUnavailable: string;
|
|
70
|
+
noCreditsYet: string;
|
|
71
|
+
noRecords: string;
|
|
72
|
+
noTestExecutedYet: string;
|
|
73
|
+
none: string;
|
|
74
|
+
unknownError: string;
|
|
75
|
+
};
|
|
76
|
+
status: {
|
|
77
|
+
pending: string;
|
|
78
|
+
idle: string;
|
|
79
|
+
};
|
|
80
|
+
actions: {
|
|
81
|
+
frontendPreventionTest: string;
|
|
82
|
+
backendIdempotencyTest: string;
|
|
83
|
+
generateNewTestFingerprintAriaLabel: string;
|
|
84
|
+
dismissErrorAriaLabel: string;
|
|
85
|
+
};
|
|
86
|
+
messages: {
|
|
87
|
+
testFingerprintNotReady: string;
|
|
88
|
+
runningFrontendPreventionTest: string;
|
|
89
|
+
frontendPreventionTestFinished: string;
|
|
90
|
+
frontendPreventionTestFailed: string;
|
|
91
|
+
failedToInitializeAnonymousUser: string;
|
|
92
|
+
runningBackendIdempotencyTest: string;
|
|
93
|
+
backendIdempotencyTestDone: string;
|
|
94
|
+
backendIdempotencyTestFailed: string;
|
|
95
|
+
generatedTestFingerprintOverride: string;
|
|
96
|
+
createdCount: string;
|
|
97
|
+
reusedCount: string;
|
|
98
|
+
failedCount: string;
|
|
99
|
+
createdUserIds: string;
|
|
100
|
+
failedStatuses: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
function useFingerprintStatusTranslations(): FingerprintStatusTranslations {
|
|
105
|
+
const messages = useMessages() as Record<string, unknown>;
|
|
106
|
+
return messages.fingerprint as FingerprintStatusTranslations;
|
|
107
|
+
}
|
|
108
|
+
|
|
31
109
|
/**
|
|
32
110
|
* Fingerprint Provider Component
|
|
33
111
|
* 为应用提供fingerprint和匿名用户管理功能
|
|
@@ -86,6 +164,7 @@ export function withFingerprint<P extends object>(
|
|
|
86
164
|
* 组件:显示用户状态和积分信息(用于调试)
|
|
87
165
|
*/
|
|
88
166
|
export function FingerprintStatus() {
|
|
167
|
+
const translations = useFingerprintStatusTranslations();
|
|
89
168
|
const {
|
|
90
169
|
fingerprintId,
|
|
91
170
|
xUser,
|
|
@@ -136,7 +215,7 @@ export function FingerprintStatus() {
|
|
|
136
215
|
return [
|
|
137
216
|
{
|
|
138
217
|
key: 'paid',
|
|
139
|
-
label:
|
|
218
|
+
label: translations.creditBuckets.paid,
|
|
140
219
|
icon: <Settings2Icon className="size-4 text-green-500 dark:text-green-300" />,
|
|
141
220
|
balance: xCredit.balancePaid,
|
|
142
221
|
total: xCredit.totalPaidLimit,
|
|
@@ -145,7 +224,7 @@ export function FingerprintStatus() {
|
|
|
145
224
|
},
|
|
146
225
|
{
|
|
147
226
|
key: 'oneTimePaid',
|
|
148
|
-
label:
|
|
227
|
+
label: translations.creditBuckets.oneTimePaid,
|
|
149
228
|
icon: <CoinsIcon className="size-4 text-amber-500 dark:text-amber-300" />,
|
|
150
229
|
balance: xCredit.balanceOneTimePaid,
|
|
151
230
|
total: xCredit.totalOneTimePaidLimit,
|
|
@@ -154,7 +233,7 @@ export function FingerprintStatus() {
|
|
|
154
233
|
},
|
|
155
234
|
{
|
|
156
235
|
key: 'free',
|
|
157
|
-
label:
|
|
236
|
+
label: translations.creditBuckets.free,
|
|
158
237
|
icon: <GiftIcon className="size-4 text-purple-500 dark:text-purple-300" />,
|
|
159
238
|
balance: xCredit.balanceFree,
|
|
160
239
|
total: xCredit.totalFreeLimit,
|
|
@@ -162,15 +241,15 @@ export function FingerprintStatus() {
|
|
|
162
241
|
end: xCredit.freeEnd,
|
|
163
242
|
},
|
|
164
243
|
];
|
|
165
|
-
}, [xCredit]);
|
|
244
|
+
}, [translations.creditBuckets.free, translations.creditBuckets.oneTimePaid, translations.creditBuckets.paid, xCredit]);
|
|
166
245
|
|
|
167
246
|
const subscriptionStatus = useMemo(() => {
|
|
168
247
|
if (!xSubscription) {
|
|
169
248
|
return {
|
|
170
|
-
status:
|
|
249
|
+
status: translations.placeholders.subscriptionStatusNever,
|
|
171
250
|
priceName: '--',
|
|
172
251
|
creditsAllocated: '--',
|
|
173
|
-
period:
|
|
252
|
+
period: translations.placeholders.subscriptionPeriodUnavailable,
|
|
174
253
|
};
|
|
175
254
|
}
|
|
176
255
|
return {
|
|
@@ -179,9 +258,9 @@ export function FingerprintStatus() {
|
|
|
179
258
|
creditsAllocated: typeof xSubscription.creditsAllocated === 'number'
|
|
180
259
|
? formatNumber(xSubscription.creditsAllocated)
|
|
181
260
|
: '--',
|
|
182
|
-
period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd),
|
|
261
|
+
period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd, translations),
|
|
183
262
|
};
|
|
184
|
-
}, [xSubscription]);
|
|
263
|
+
}, [translations.placeholders.subscriptionPeriodUnavailable, translations.placeholders.subscriptionStatusNever, xSubscription]);
|
|
185
264
|
|
|
186
265
|
const userStatus = xUser?.status || '--';
|
|
187
266
|
const totalCredits = formatNumber(xCredit?.totalBalance);
|
|
@@ -195,13 +274,13 @@ export function FingerprintStatus() {
|
|
|
195
274
|
const runContextParallelInitTest = async () => {
|
|
196
275
|
const debugFingerprintId = activeDebugFingerprintId ?? getOrCreateDebugFingerprintOverride();
|
|
197
276
|
if (!debugFingerprintId) {
|
|
198
|
-
setTestResult(
|
|
277
|
+
setTestResult(translations.messages.testFingerprintNotReady);
|
|
199
278
|
return;
|
|
200
279
|
}
|
|
201
280
|
|
|
202
281
|
setActiveDebugFingerprintId(debugFingerprintId);
|
|
203
282
|
setIsRunningTest(true);
|
|
204
|
-
setTestResult(
|
|
283
|
+
setTestResult(tpl(translations.messages.runningFrontendPreventionTest, { fingerprintId: debugFingerprintId }));
|
|
205
284
|
|
|
206
285
|
try {
|
|
207
286
|
await Promise.all([
|
|
@@ -209,9 +288,9 @@ export function FingerprintStatus() {
|
|
|
209
288
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
210
289
|
initializeDebugAnonymousUser(debugFingerprintId),
|
|
211
290
|
]);
|
|
212
|
-
setTestResult(
|
|
291
|
+
setTestResult(tpl(translations.messages.frontendPreventionTestFinished, { fingerprintId: debugFingerprintId }));
|
|
213
292
|
} catch (testError) {
|
|
214
|
-
setTestResult(
|
|
293
|
+
setTestResult(tpl(translations.messages.frontendPreventionTestFailed, { error: formatErrorMessage(testError, translations) }));
|
|
215
294
|
} finally {
|
|
216
295
|
setIsRunningTest(false);
|
|
217
296
|
}
|
|
@@ -239,7 +318,7 @@ export function FingerprintStatus() {
|
|
|
239
318
|
|
|
240
319
|
if (!response.ok) {
|
|
241
320
|
const errorData = await response.json().catch(() => ({}));
|
|
242
|
-
throw new Error(errorData.error ||
|
|
321
|
+
throw new Error(errorData.error || translations.messages.failedToInitializeAnonymousUser);
|
|
243
322
|
}
|
|
244
323
|
|
|
245
324
|
await response.json().catch(() => ({}));
|
|
@@ -251,13 +330,13 @@ export function FingerprintStatus() {
|
|
|
251
330
|
const runRawParallelPostTest = async () => {
|
|
252
331
|
const normalizedFingerprintId = activeDebugFingerprintId ?? getOrCreateDebugFingerprintOverride();
|
|
253
332
|
if (!normalizedFingerprintId) {
|
|
254
|
-
setTestResult(
|
|
333
|
+
setTestResult(translations.messages.testFingerprintNotReady);
|
|
255
334
|
return;
|
|
256
335
|
}
|
|
257
336
|
|
|
258
337
|
setActiveDebugFingerprintId(normalizedFingerprintId);
|
|
259
338
|
setIsRunningTest(true);
|
|
260
|
-
setTestResult(
|
|
339
|
+
setTestResult(tpl(translations.messages.runningBackendIdempotencyTest, { fingerprintId: normalizedFingerprintId }));
|
|
261
340
|
|
|
262
341
|
try {
|
|
263
342
|
const fingerprintHeaders = await createFingerprintHeaders();
|
|
@@ -297,16 +376,16 @@ export function FingerprintStatus() {
|
|
|
297
376
|
|
|
298
377
|
setTestResult(
|
|
299
378
|
[
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
failedStatuses.length > 0 ?
|
|
379
|
+
translations.messages.backendIdempotencyTestDone,
|
|
380
|
+
tpl(translations.messages.createdCount, { count: createdUserIds.length }),
|
|
381
|
+
tpl(translations.messages.reusedCount, { count: reusedUserIds.length }),
|
|
382
|
+
tpl(translations.messages.failedCount, { count: failedStatuses.length }),
|
|
383
|
+
tpl(translations.messages.createdUserIds, { value: createdUserIds.join(', ') }),
|
|
384
|
+
failedStatuses.length > 0 ? tpl(translations.messages.failedStatuses, { value: failedStatuses.join(', ') }) : null,
|
|
306
385
|
].filter(Boolean).join('\n')
|
|
307
386
|
);
|
|
308
387
|
} catch (testError) {
|
|
309
|
-
setTestResult(
|
|
388
|
+
setTestResult(tpl(translations.messages.backendIdempotencyTestFailed, { error: formatErrorMessage(testError, translations) }));
|
|
310
389
|
} finally {
|
|
311
390
|
setIsRunningTest(false);
|
|
312
391
|
}
|
|
@@ -315,7 +394,7 @@ export function FingerprintStatus() {
|
|
|
315
394
|
const regenerateTestFingerprint = () => {
|
|
316
395
|
const nextFingerprintId = regenerateDebugFingerprintOverride();
|
|
317
396
|
setActiveDebugFingerprintId(nextFingerprintId);
|
|
318
|
-
setTestResult(
|
|
397
|
+
setTestResult(tpl(translations.messages.generatedTestFingerprintOverride, { fingerprintId: nextFingerprintId }));
|
|
319
398
|
};
|
|
320
399
|
|
|
321
400
|
return (
|
|
@@ -325,7 +404,7 @@ export function FingerprintStatus() {
|
|
|
325
404
|
<button
|
|
326
405
|
onClick={handleToggle}
|
|
327
406
|
type="button"
|
|
328
|
-
aria-label=
|
|
407
|
+
aria-label={translations.panel.toggleAriaLabel}
|
|
329
408
|
className={cn(
|
|
330
409
|
'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',
|
|
331
410
|
themeButtonGradientClass,
|
|
@@ -354,7 +433,7 @@ export function FingerprintStatus() {
|
|
|
354
433
|
<div className="flex items-start justify-between gap-3">
|
|
355
434
|
<div className={cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor)}>
|
|
356
435
|
<ShieldUserIcon className="size-4" />
|
|
357
|
-
|
|
436
|
+
{translations.panel.title}
|
|
358
437
|
</div>
|
|
359
438
|
<div className="flex items-center gap-2">
|
|
360
439
|
<button
|
|
@@ -368,7 +447,7 @@ export function FingerprintStatus() {
|
|
|
368
447
|
)}
|
|
369
448
|
aria-pressed={panelMode === 'test'}
|
|
370
449
|
>
|
|
371
|
-
<span>
|
|
450
|
+
<span>{translations.panel.testModeLabel}</span>
|
|
372
451
|
<span
|
|
373
452
|
className={cn(
|
|
374
453
|
'relative inline-flex h-5 w-9 items-center rounded-full transition-colors',
|
|
@@ -388,7 +467,7 @@ export function FingerprintStatus() {
|
|
|
388
467
|
</button>
|
|
389
468
|
<button
|
|
390
469
|
type="button"
|
|
391
|
-
aria-label=
|
|
470
|
+
aria-label={translations.panel.closeAriaLabel}
|
|
392
471
|
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"
|
|
393
472
|
onClick={() => setIsOpen(false)}
|
|
394
473
|
>
|
|
@@ -403,23 +482,23 @@ export function FingerprintStatus() {
|
|
|
403
482
|
<>
|
|
404
483
|
<PanelSection
|
|
405
484
|
icon={<FingerprintIcon className="size-4" />}
|
|
406
|
-
title=
|
|
407
|
-
rightInfo={<StatusTag value={userStatus} />}
|
|
485
|
+
title={translations.sections.user}
|
|
486
|
+
rightInfo={<StatusTag value={userStatus} translations={translations} />}
|
|
408
487
|
items={[
|
|
409
|
-
{ label:
|
|
410
|
-
{ label:
|
|
411
|
-
{ label:
|
|
412
|
-
{ label:
|
|
413
|
-
{ label:
|
|
414
|
-
{ label:
|
|
415
|
-
{ label:
|
|
488
|
+
{ label: translations.labels.userId, value: <CopyableText text={xUser?.userId || ''} /> },
|
|
489
|
+
{ label: translations.labels.nickName, value: <CopyableText text={xUser?.userName || ''} /> },
|
|
490
|
+
{ label: translations.labels.fingerprintId, value: <CopyableText text={xUser?.fingerprintId || fingerprintId || ''} /> },
|
|
491
|
+
{ label: translations.labels.clerkUserId, value: <CopyableText text={xUser?.clerkUserId || ''} /> },
|
|
492
|
+
{ label: translations.labels.email, value: <CopyableText text={xUser?.email || ''} /> },
|
|
493
|
+
{ label: translations.labels.stripeCusId, value: <CopyableText text={xUser?.stripeCusId || ''} /> },
|
|
494
|
+
{ label: translations.labels.createdAt, value: xUser?.createdAt || '--' },
|
|
416
495
|
]}
|
|
417
496
|
/>
|
|
418
497
|
|
|
419
498
|
<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">
|
|
420
499
|
<PanelHeader
|
|
421
500
|
icon={<GemIcon className="size-4" />}
|
|
422
|
-
title=
|
|
501
|
+
title={translations.sections.creditsInfo}
|
|
423
502
|
rightInfo={<span className={cn("font-semibold", themeIconColor)}>{totalCredits}</span>}
|
|
424
503
|
/>
|
|
425
504
|
<div className="space-y-3">
|
|
@@ -444,29 +523,29 @@ export function FingerprintStatus() {
|
|
|
444
523
|
/>
|
|
445
524
|
</div>
|
|
446
525
|
<div className="mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400">
|
|
447
|
-
<span>{formatRangeText(bucket.start, bucket.end)}</span>
|
|
526
|
+
<span>{formatRangeText(bucket.start, bucket.end, translations)}</span>
|
|
448
527
|
<span>{percent}%</span>
|
|
449
528
|
</div>
|
|
450
529
|
</div>
|
|
451
530
|
);
|
|
452
531
|
})
|
|
453
532
|
) : (
|
|
454
|
-
<EmptyPlaceholder label=
|
|
533
|
+
<EmptyPlaceholder label={translations.placeholders.noCreditsYet} icon={<DatabaseZapIcon className="size-4" />} />
|
|
455
534
|
)}
|
|
456
535
|
</div>
|
|
457
536
|
</div>
|
|
458
537
|
|
|
459
538
|
<PanelSection
|
|
460
539
|
icon={<BellIcon className="size-4" />}
|
|
461
|
-
title=
|
|
462
|
-
rightInfo={<StatusTag value={subStatus} />}
|
|
540
|
+
title={translations.sections.subscription}
|
|
541
|
+
rightInfo={<StatusTag value={subStatus} translations={translations} />}
|
|
463
542
|
items={[
|
|
464
|
-
{ label:
|
|
465
|
-
{ label:
|
|
466
|
-
{ label:
|
|
467
|
-
{ label:
|
|
468
|
-
{ label:
|
|
469
|
-
{ label:
|
|
543
|
+
{ label: translations.labels.plan, value: subscriptionStatus.priceName },
|
|
544
|
+
{ label: translations.labels.period, value: subscriptionStatus.period },
|
|
545
|
+
{ label: translations.labels.allocated, value: subscriptionStatus.creditsAllocated },
|
|
546
|
+
{ label: translations.labels.subId, value: <CopyableText text={xSubscription?.paySubscriptionId || ''} /> },
|
|
547
|
+
{ label: translations.labels.orderId, value: <CopyableText text={xSubscription?.orderId || ''} /> },
|
|
548
|
+
{ label: translations.labels.priceId, value: <CopyableText text={xSubscription?.priceId || ''} /> },
|
|
470
549
|
]}
|
|
471
550
|
/>
|
|
472
551
|
</>
|
|
@@ -474,17 +553,17 @@ export function FingerprintStatus() {
|
|
|
474
553
|
<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">
|
|
475
554
|
<PanelHeader
|
|
476
555
|
icon={<DatabaseZapIcon className="size-4" />}
|
|
477
|
-
title=
|
|
478
|
-
rightInfo={<StatusTag value={isRunningTest ?
|
|
556
|
+
title={translations.sections.concurrentBaseInfo}
|
|
557
|
+
rightInfo={<StatusTag value={isRunningTest ? translations.status.pending : translations.status.idle} translations={translations} />}
|
|
479
558
|
/>
|
|
480
559
|
|
|
481
560
|
<div className="space-y-2 text-xs text-slate-500 dark:text-slate-300">
|
|
482
561
|
<div className="flex items-center justify-between gap-3">
|
|
483
|
-
<span className="text-slate-400 dark:text-slate-500">
|
|
562
|
+
<span className="text-slate-400 dark:text-slate-500">{translations.labels.realBrowser}</span>
|
|
484
563
|
<CopyableText text={fingerprintId || ''} />
|
|
485
564
|
</div>
|
|
486
565
|
<div className="space-y-1">
|
|
487
|
-
<span className="text-slate-400 dark:text-slate-500">
|
|
566
|
+
<span className="text-slate-400 dark:text-slate-500">{translations.labels.testOverride}</span>
|
|
488
567
|
<div className="flex items-center gap-2 py-1">
|
|
489
568
|
<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">
|
|
490
569
|
<CopyableText text={activeDebugFingerprintId || ''} />
|
|
@@ -493,7 +572,7 @@ export function FingerprintStatus() {
|
|
|
493
572
|
type="button"
|
|
494
573
|
disabled={isRunningTest}
|
|
495
574
|
onClick={regenerateTestFingerprint}
|
|
496
|
-
aria-label=
|
|
575
|
+
aria-label={translations.actions.generateNewTestFingerprintAriaLabel}
|
|
497
576
|
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"
|
|
498
577
|
>
|
|
499
578
|
<RefreshCcwIcon className="size-4" />
|
|
@@ -512,7 +591,7 @@ export function FingerprintStatus() {
|
|
|
512
591
|
themedGhostButtonClass
|
|
513
592
|
)}
|
|
514
593
|
>
|
|
515
|
-
|
|
594
|
+
{translations.actions.frontendPreventionTest}
|
|
516
595
|
</button>
|
|
517
596
|
<button
|
|
518
597
|
type="button"
|
|
@@ -525,13 +604,13 @@ export function FingerprintStatus() {
|
|
|
525
604
|
themeButtonGradientHoverClass
|
|
526
605
|
)}
|
|
527
606
|
>
|
|
528
|
-
|
|
607
|
+
{translations.actions.backendIdempotencyTest}
|
|
529
608
|
</button>
|
|
530
609
|
</div>
|
|
531
610
|
|
|
532
611
|
<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">
|
|
533
612
|
<pre className="overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300">
|
|
534
|
-
{testResult ||
|
|
613
|
+
{testResult || translations.placeholders.noTestExecutedYet}
|
|
535
614
|
</pre>
|
|
536
615
|
</div>
|
|
537
616
|
</div>
|
|
@@ -545,7 +624,7 @@ export function FingerprintStatus() {
|
|
|
545
624
|
</div>
|
|
546
625
|
<button
|
|
547
626
|
type="button"
|
|
548
|
-
aria-label=
|
|
627
|
+
aria-label={translations.actions.dismissErrorAriaLabel}
|
|
549
628
|
onClick={clearError}
|
|
550
629
|
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"
|
|
551
630
|
>
|
|
@@ -632,12 +711,16 @@ function computeProgress(balance: number | null | undefined, total: number | nul
|
|
|
632
711
|
return Math.min(Math.max(ratio, 0), 1);
|
|
633
712
|
}
|
|
634
713
|
|
|
635
|
-
function formatRangeText(
|
|
714
|
+
function formatRangeText(
|
|
715
|
+
start: string | null | undefined,
|
|
716
|
+
end: string | null | undefined,
|
|
717
|
+
translations: FingerprintStatusTranslations
|
|
718
|
+
) {
|
|
636
719
|
const safeStart = start && start.trim() ? start : '';
|
|
637
720
|
const safeEnd = end && end.trim() ? end : '';
|
|
638
721
|
|
|
639
722
|
if (!safeStart && !safeEnd) {
|
|
640
|
-
return
|
|
723
|
+
return translations.placeholders.noRecords;
|
|
641
724
|
}
|
|
642
725
|
|
|
643
726
|
if (!safeStart) {
|
|
@@ -651,8 +734,14 @@ function formatRangeText(start: string | null | undefined, end: string | null |
|
|
|
651
734
|
return `${safeStart} - ${safeEnd}`;
|
|
652
735
|
}
|
|
653
736
|
|
|
654
|
-
function StatusTag({
|
|
655
|
-
|
|
737
|
+
function StatusTag({
|
|
738
|
+
value,
|
|
739
|
+
translations,
|
|
740
|
+
}: {
|
|
741
|
+
value: string | undefined | null;
|
|
742
|
+
translations: FingerprintStatusTranslations;
|
|
743
|
+
}) {
|
|
744
|
+
if (!value) return <span className="text-slate-400">{translations.placeholders.none}</span>;
|
|
656
745
|
|
|
657
746
|
const normalized = value.toLowerCase();
|
|
658
747
|
|
|
@@ -689,7 +778,7 @@ function StatusTag({ value }: { value: string | undefined | null }) {
|
|
|
689
778
|
);
|
|
690
779
|
}
|
|
691
780
|
|
|
692
|
-
function formatErrorMessage(error: unknown) {
|
|
781
|
+
function formatErrorMessage(error: unknown, translations: FingerprintStatusTranslations) {
|
|
693
782
|
if (error instanceof Error) {
|
|
694
783
|
return error.message;
|
|
695
784
|
}
|
|
@@ -698,5 +787,9 @@ function formatErrorMessage(error: unknown) {
|
|
|
698
787
|
return error;
|
|
699
788
|
}
|
|
700
789
|
|
|
701
|
-
return
|
|
790
|
+
return translations.placeholders.unknownError;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function tpl(template: string, values: Record<string, string | number>) {
|
|
794
|
+
return template.replace(/\{(\w+)\}/g, (_, key: string) => String(values[key] ?? ''));
|
|
702
795
|
}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useState } from
|
|
4
|
-
import Image from
|
|
5
|
-
import {
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import Image from 'next/image';
|
|
5
|
+
import { BellIcon, ImageOffIcon, XIcon } from '@windrun-huaiin/base-ui/icons';
|
|
6
6
|
import {
|
|
7
7
|
AlertDialog,
|
|
8
|
+
AlertDialogAction,
|
|
8
9
|
AlertDialogContent,
|
|
9
|
-
AlertDialogTitle,
|
|
10
10
|
AlertDialogDescription,
|
|
11
|
-
|
|
12
|
-
} from
|
|
11
|
+
AlertDialogTitle,
|
|
12
|
+
} from '@windrun-huaiin/base-ui/ui';
|
|
13
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
14
|
+
import {
|
|
15
|
+
closeButtonClass,
|
|
16
|
+
dialogContentClass,
|
|
17
|
+
dialogDescriptionClass,
|
|
18
|
+
dialogFooterClass,
|
|
19
|
+
dialogHeaderClass,
|
|
20
|
+
dialogThemedOverlayClass,
|
|
21
|
+
dialogTitleClass,
|
|
22
|
+
primaryButtonClass,
|
|
23
|
+
secondaryButtonClass,
|
|
24
|
+
subtlePrimaryButtonClass,
|
|
25
|
+
} from './dialog-styles';
|
|
13
26
|
|
|
14
27
|
interface AdsAlertDialogProps {
|
|
15
28
|
open: boolean;
|
|
@@ -37,49 +50,52 @@ export function AdsAlertDialog({
|
|
|
37
50
|
onConfirm,
|
|
38
51
|
}: AdsAlertDialogProps) {
|
|
39
52
|
const [imgError, setImgError] = useState(false);
|
|
53
|
+
const handleClose = () => onOpenChange(false);
|
|
40
54
|
|
|
41
55
|
return (
|
|
42
56
|
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
|
43
57
|
<AlertDialogContent
|
|
44
|
-
className=
|
|
58
|
+
className={cn(dialogContentClass, 'max-w-md p-4')}
|
|
59
|
+
overlayClassName={dialogThemedOverlayClass}
|
|
60
|
+
onOverlayClick={handleClose}
|
|
45
61
|
>
|
|
46
|
-
{
|
|
47
|
-
<div className="flex flex-row items-center justify-between mb-2">
|
|
62
|
+
<div className={dialogHeaderClass}>
|
|
48
63
|
<AlertDialogTitle asChild>
|
|
49
|
-
<div className=
|
|
50
|
-
<
|
|
64
|
+
<div className={dialogTitleClass}>
|
|
65
|
+
<span className="inline-flex size-9 shrink-0 items-center justify-center rounded-full bg-neutral-100 text-neutral-600 ring-1 ring-neutral-200 dark:bg-neutral-900 dark:text-neutral-300 dark:ring-neutral-800">
|
|
66
|
+
<BellIcon className="size-5" />
|
|
67
|
+
</span>
|
|
51
68
|
<span className="truncate">{title}</span>
|
|
52
69
|
</div>
|
|
53
70
|
</AlertDialogTitle>
|
|
54
71
|
<button
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
type="button"
|
|
73
|
+
className={closeButtonClass}
|
|
74
|
+
onClick={handleClose}
|
|
57
75
|
aria-label="Close"
|
|
58
|
-
tabIndex={0}
|
|
59
76
|
>
|
|
60
|
-
<XIcon className="
|
|
77
|
+
<XIcon className="size-4" />
|
|
61
78
|
</button>
|
|
62
79
|
</div>
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
<AlertDialogDescription className="text-base font-medium text-neutral-800 dark:text-neutral-100 mb-2">
|
|
80
|
+
|
|
81
|
+
<AlertDialogDescription className={cn(dialogDescriptionClass, 'mb-3 text-base text-neutral-800 dark:text-neutral-100')}>
|
|
66
82
|
{description}
|
|
67
83
|
</AlertDialogDescription>
|
|
68
|
-
|
|
84
|
+
|
|
69
85
|
{imgSrc && (
|
|
70
|
-
<div className="w-full max-w-[400px]
|
|
86
|
+
<div className="relative mb-2 flex h-[220px] w-full max-w-[400px] items-center justify-center overflow-hidden rounded-xl border border-neutral-200 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900">
|
|
71
87
|
{imgError ? (
|
|
72
|
-
<div className="absolute inset-0 flex flex-col items-center justify-center
|
|
73
|
-
<ImageOffIcon className="
|
|
88
|
+
<div className="absolute inset-0 flex flex-col items-center justify-center border border-dashed border-neutral-300 text-sm text-neutral-400 dark:border-neutral-700">
|
|
89
|
+
<ImageOffIcon className="mb-2 size-12" />
|
|
74
90
|
<span>Image loading failed</span>
|
|
75
91
|
</div>
|
|
76
92
|
) : imgHref ? (
|
|
77
|
-
<a href={imgHref} target="_blank" rel="noopener noreferrer" className="block
|
|
93
|
+
<a href={imgHref} target="_blank" rel="noopener noreferrer" className="block h-full w-full">
|
|
78
94
|
<Image
|
|
79
95
|
src={imgSrc}
|
|
80
96
|
alt="image"
|
|
81
97
|
fill
|
|
82
|
-
className="object-contain
|
|
98
|
+
className="rounded-lg object-contain"
|
|
83
99
|
priority={false}
|
|
84
100
|
placeholder="empty"
|
|
85
101
|
unoptimized
|
|
@@ -92,7 +108,7 @@ export function AdsAlertDialog({
|
|
|
92
108
|
src={imgSrc}
|
|
93
109
|
alt="image"
|
|
94
110
|
fill
|
|
95
|
-
className="object-contain
|
|
111
|
+
className="rounded-lg object-contain"
|
|
96
112
|
priority={false}
|
|
97
113
|
placeholder="empty"
|
|
98
114
|
unoptimized
|
|
@@ -102,16 +118,17 @@ export function AdsAlertDialog({
|
|
|
102
118
|
)}
|
|
103
119
|
</div>
|
|
104
120
|
)}
|
|
105
|
-
|
|
121
|
+
|
|
106
122
|
{(cancelText || confirmText) && (
|
|
107
|
-
<div className=
|
|
123
|
+
<div className={dialogFooterClass}>
|
|
108
124
|
{cancelText && (
|
|
109
125
|
<button
|
|
126
|
+
type="button"
|
|
110
127
|
onClick={() => {
|
|
111
128
|
onOpenChange(false);
|
|
112
129
|
onCancel?.();
|
|
113
130
|
}}
|
|
114
|
-
className=
|
|
131
|
+
className={secondaryButtonClass}
|
|
115
132
|
>
|
|
116
133
|
{cancelText}
|
|
117
134
|
</button>
|
|
@@ -122,7 +139,7 @@ export function AdsAlertDialog({
|
|
|
122
139
|
onOpenChange(false);
|
|
123
140
|
onConfirm?.();
|
|
124
141
|
}}
|
|
125
|
-
className=
|
|
142
|
+
className={confirmText && !cancelText ? subtlePrimaryButtonClass : primaryButtonClass}
|
|
126
143
|
>
|
|
127
144
|
{confirmText}
|
|
128
145
|
</AlertDialogAction>
|