@windrun-huaiin/third-ui 30.1.0 → 31.0.1
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/README.md +109 -143
- package/dist/ai/ai-prompt-textarea.js +5 -5
- package/dist/ai/ai-prompt-textarea.mjs +5 -5
- package/dist/clerk/clerk-auth-appearance.d.ts +13 -0
- package/dist/clerk/clerk-auth-appearance.js +19 -0
- package/dist/clerk/clerk-auth-appearance.mjs +15 -0
- package/dist/clerk/clerk-auth-modal-appearance.d.ts +12 -0
- package/dist/clerk/clerk-auth-modal-appearance.js +17 -0
- package/dist/clerk/clerk-auth-modal-appearance.mjs +14 -0
- package/dist/clerk/clerk-page-context-generator.js +3 -3
- package/dist/clerk/clerk-page-context-generator.mjs +3 -3
- package/dist/clerk/clerk-page-generator.js +4 -4
- package/dist/clerk/clerk-page-generator.mjs +4 -4
- package/dist/clerk/clerk-user-client.js +2 -1
- package/dist/clerk/clerk-user-client.mjs +2 -1
- package/dist/clerk/fingerprint/fingerprint-client.d.ts +10 -10
- package/dist/clerk/fingerprint/fingerprint-client.js +20 -20
- package/dist/clerk/fingerprint/fingerprint-client.mjs +20 -20
- package/dist/clerk/fingerprint/fingerprint-provider.d.ts +3 -3
- package/dist/clerk/fingerprint/fingerprint-provider.js +8 -8
- package/dist/clerk/fingerprint/fingerprint-provider.mjs +8 -8
- package/dist/clerk/fingerprint/fingerprint-server.d.ts +12 -12
- package/dist/clerk/fingerprint/fingerprint-server.js +17 -17
- package/dist/clerk/fingerprint/fingerprint-server.mjs +17 -17
- package/dist/clerk/fingerprint/fingerprint-shared.d.ts +3 -3
- package/dist/clerk/fingerprint/fingerprint-shared.js +10 -10
- package/dist/clerk/fingerprint/fingerprint-shared.mjs +10 -10
- package/dist/clerk/fingerprint/types.d.ts +0 -1
- package/dist/clerk/fingerprint/use-fingerprint.js +7 -7
- package/dist/clerk/fingerprint/use-fingerprint.mjs +7 -7
- package/dist/clerk/signin-with-fingerprint-client.d.ts +2 -2
- package/dist/clerk/signin-with-fingerprint-client.js +7 -6
- package/dist/clerk/signin-with-fingerprint-client.mjs +7 -6
- package/dist/clerk/signup-button-with-fingerprint-client.js +6 -4
- package/dist/clerk/signup-button-with-fingerprint-client.mjs +6 -4
- package/dist/clerk/signup-with-fingerprint-client.d.ts +2 -2
- package/dist/clerk/signup-with-fingerprint-client.js +7 -6
- package/dist/clerk/signup-with-fingerprint-client.mjs +7 -6
- package/dist/fuma/heavy/mermaid.js +1 -1
- package/dist/fuma/heavy/mermaid.mjs +1 -1
- package/dist/fuma/mdx/fuma-github-info.d.ts +1 -2
- package/dist/fuma/mdx/fuma-github-info.js +3 -6
- package/dist/fuma/mdx/fuma-github-info.mjs +3 -6
- package/dist/fuma/site-x.js +0 -1
- package/dist/fuma/site-x.mjs +0 -1
- package/dist/main/calendar/calendar-date-range-input.js +1 -1
- package/dist/main/calendar/calendar-date-range-input.mjs +1 -1
- package/dist/main/credit/credit-overview-nav-client.d.ts +12 -0
- package/dist/main/credit/credit-overview-nav-client.js +65 -0
- package/dist/main/credit/credit-overview-nav-client.mjs +63 -0
- package/dist/main/credit/index.d.ts +2 -0
- package/dist/main/credit/index.js +2 -0
- package/dist/main/credit/index.mjs +1 -0
- package/dist/main/credit/types.d.ts +8 -8
- package/dist/main/money-price/index.d.ts +1 -1
- package/dist/main/money-price/money-price-button.js +10 -10
- package/dist/main/money-price/money-price-button.mjs +10 -10
- package/dist/main/money-price/money-price-config-util.d.ts +30 -30
- package/dist/main/money-price/money-price-config-util.js +48 -48
- package/dist/main/money-price/money-price-config-util.mjs +48 -48
- package/dist/main/money-price/money-price-interactive.js +30 -18
- package/dist/main/money-price/money-price-interactive.mjs +30 -18
- package/dist/main/money-price/money-price-types.d.ts +7 -1
- package/dist/main/money-price/money-price-types.js +2 -2
- package/dist/main/money-price/money-price-types.mjs +2 -2
- package/dist/main/money-price/server.d.ts +1 -1
- package/dist/main/pill-select/x-pill-select.js +2 -2
- package/dist/main/pill-select/x-pill-select.mjs +2 -2
- package/dist/main/server.d.ts +1 -1
- package/package.json +13 -7
- package/src/ai/ai-prompt-textarea.tsx +6 -6
- package/src/clerk/clerk-auth-appearance.ts +16 -0
- package/src/clerk/clerk-page-context-generator.tsx +3 -5
- package/src/clerk/clerk-page-generator.tsx +9 -8
- package/src/clerk/clerk-user-client.tsx +14 -5
- package/src/clerk/fingerprint/fingerprint-client.ts +20 -20
- package/src/clerk/fingerprint/fingerprint-provider.tsx +11 -11
- package/src/clerk/fingerprint/fingerprint-server.ts +17 -17
- package/src/clerk/fingerprint/fingerprint-shared.ts +10 -10
- package/src/clerk/fingerprint/types.ts +0 -1
- package/src/clerk/fingerprint/use-fingerprint.ts +7 -7
- package/src/clerk/signin-with-fingerprint-client.tsx +7 -7
- package/src/clerk/signup-button-with-fingerprint-client.tsx +7 -5
- package/src/clerk/signup-with-fingerprint-client.tsx +7 -7
- package/src/fuma/base/custom-home-layout.tsx +4 -4
- package/src/fuma/heavy/mermaid.tsx +1 -1
- package/src/fuma/mdx/fuma-github-info.tsx +3 -8
- package/src/fuma/site-x.tsx +0 -1
- package/src/main/calendar/calendar-date-range-input.tsx +1 -1
- package/src/main/credit/credit-overview-nav-client.tsx +95 -0
- package/src/main/credit/index.ts +5 -0
- package/src/main/credit/types.ts +8 -8
- package/src/main/gallery/gallery-mobile-swiper.tsx +0 -1
- package/src/main/gallery/gallery-server.tsx +2 -2
- package/src/main/money-price/index.ts +2 -0
- package/src/main/money-price/money-price-button.tsx +10 -10
- package/src/main/money-price/money-price-config-util.ts +49 -49
- package/src/main/money-price/money-price-interactive.tsx +40 -20
- package/src/main/money-price/money-price-types.ts +21 -14
- package/src/main/money-price/server.ts +2 -0
- package/src/main/pill-select/x-pill-select.tsx +2 -2
- package/src/main/server.ts +3 -1
- package/src/styles/third-ui.css +8 -0
|
@@ -109,7 +109,7 @@ function useFingerprintStatusTranslations(): FingerprintStatusTranslations {
|
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* Fingerprint Provider Component
|
|
112
|
-
*
|
|
112
|
+
* Provides fingerprint and anonymous user management for the application.
|
|
113
113
|
*/
|
|
114
114
|
export function FingerprintProvider({
|
|
115
115
|
children,
|
|
@@ -137,7 +137,7 @@ export function useFingerprintContext(): FingerprintContextType {
|
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* Safe hook to use fingerprint context - returns null if no provider
|
|
140
|
-
*
|
|
140
|
+
* Returns null when no provider is available.
|
|
141
141
|
*/
|
|
142
142
|
export function useFingerprintContextSafe(): FingerprintContextType | null {
|
|
143
143
|
const context = useContext(FingerprintContext);
|
|
@@ -162,7 +162,7 @@ export function withFingerprint<P extends object>(
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/**
|
|
165
|
-
*
|
|
165
|
+
* Component for displaying user status and credit information for debugging.
|
|
166
166
|
*/
|
|
167
167
|
export function FingerprintStatus() {
|
|
168
168
|
const translations = useFingerprintStatusTranslations();
|
|
@@ -400,7 +400,7 @@ export function FingerprintStatus() {
|
|
|
400
400
|
|
|
401
401
|
return (
|
|
402
402
|
<>
|
|
403
|
-
{/*
|
|
403
|
+
{/* Lightbulb button */}
|
|
404
404
|
{!isOpen && (
|
|
405
405
|
<button
|
|
406
406
|
onClick={handleToggle}
|
|
@@ -417,7 +417,7 @@ export function FingerprintStatus() {
|
|
|
417
417
|
</button>
|
|
418
418
|
)}
|
|
419
419
|
|
|
420
|
-
{/*
|
|
420
|
+
{/* Panel */}
|
|
421
421
|
{isOpen && (
|
|
422
422
|
<>
|
|
423
423
|
<div onClick={handleBackdropClick} className="fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" />
|
|
@@ -622,9 +622,9 @@ export function FingerprintStatus() {
|
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
|
|
625
|
-
/* ====================
|
|
625
|
+
/* ==================== Helper Components ==================== */
|
|
626
626
|
|
|
627
|
-
//
|
|
627
|
+
// Header row with icon and title on the left, right-aligned metadata on the right.
|
|
628
628
|
function PanelHeader({ icon, title, rightInfo }: { icon: React.ReactNode; title: string; rightInfo: React.ReactNode }) {
|
|
629
629
|
return (
|
|
630
630
|
<div className="flex items-center justify-between mb-3">
|
|
@@ -643,7 +643,7 @@ function PanelHeader({ icon, title, rightInfo }: { icon: React.ReactNode; title:
|
|
|
643
643
|
);
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
-
//
|
|
646
|
+
// Reusable PanelSection with optional right-side metadata.
|
|
647
647
|
interface PanelSectionProps {
|
|
648
648
|
icon: React.ReactNode;
|
|
649
649
|
title: string;
|
|
@@ -727,19 +727,19 @@ function StatusTag({
|
|
|
727
727
|
const normalized = value.toLowerCase();
|
|
728
728
|
|
|
729
729
|
const colorMap: Record<string, string> = {
|
|
730
|
-
//
|
|
730
|
+
// Green: normal or active.
|
|
731
731
|
registered: 'bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300',
|
|
732
732
|
active: 'bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300',
|
|
733
733
|
trialing: 'bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300',
|
|
734
734
|
|
|
735
|
-
//
|
|
735
|
+
// Gray: inactive or deleted.
|
|
736
736
|
canceled: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
|
|
737
737
|
frozen: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
|
|
738
738
|
deleted: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
|
|
739
739
|
expired: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
|
|
740
740
|
past_due: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400',
|
|
741
741
|
|
|
742
|
-
//
|
|
742
|
+
// Amber: pending or exceptional states.
|
|
743
743
|
pending: 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300',
|
|
744
744
|
failed: 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300',
|
|
745
745
|
unpaid: 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fingerprint Server Utilities
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Server-only fingerprint ID extraction and validation logic.
|
|
4
|
+
* Safe for server usage without browser APIs or FingerprintJS.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -11,16 +11,16 @@ import {
|
|
|
11
11
|
} from './fingerprint-shared';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Extract fingerprint ID from a request.
|
|
15
|
+
* Priority: header > cookie > query parameter.
|
|
16
|
+
* Safe to use on the server.
|
|
17
17
|
*/
|
|
18
18
|
export function extractFingerprintId(
|
|
19
19
|
headers: Headers | Record<string, string>,
|
|
20
20
|
cookies?: Record<string, string>,
|
|
21
21
|
query?: Record<string, string | undefined>
|
|
22
22
|
): string | null {
|
|
23
|
-
// 1.
|
|
23
|
+
// 1. Read from header.
|
|
24
24
|
const headerValue = headers instanceof Headers
|
|
25
25
|
? headers.get(FINGERPRINT_HEADER_NAME)
|
|
26
26
|
: headers[FINGERPRINT_HEADER_NAME];
|
|
@@ -29,7 +29,7 @@ export function extractFingerprintId(
|
|
|
29
29
|
return headerValue;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// 2.
|
|
32
|
+
// 2. Read from cookie.
|
|
33
33
|
if (cookies) {
|
|
34
34
|
const cookieValue = cookies[FINGERPRINT_COOKIE_NAME];
|
|
35
35
|
if (cookieValue && isValidFingerprintId(cookieValue)) {
|
|
@@ -37,7 +37,7 @@ export function extractFingerprintId(
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// 3.
|
|
40
|
+
// 3. Read from query parameters.
|
|
41
41
|
if (query) {
|
|
42
42
|
const queryValue = query.fingerprint_id || query.fp_id;
|
|
43
43
|
if (queryValue && isValidFingerprintId(queryValue)) {
|
|
@@ -49,22 +49,22 @@ export function extractFingerprintId(
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
52
|
+
* Generate a server-side fallback fingerprint ID.
|
|
53
|
+
* Used when the client cannot generate a fingerprint.
|
|
54
|
+
* Safe to use on the server.
|
|
55
55
|
*/
|
|
56
56
|
export function generateServerFingerprintId(): string {
|
|
57
57
|
return `fp_server_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
61
|
+
* Extract fingerprint ID from a Next.js Request object.
|
|
62
|
+
* Convenience helper for Next.js API routes.
|
|
63
63
|
*/
|
|
64
64
|
export function extractFingerprintFromNextRequest(request: Request): string | null {
|
|
65
65
|
const headers = request.headers;
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// Try cookies by parsing the cookie header.
|
|
68
68
|
const cookieHeader = headers.get('cookie');
|
|
69
69
|
const cookies: Record<string, string> = {};
|
|
70
70
|
|
|
@@ -77,7 +77,7 @@ export function extractFingerprintFromNextRequest(request: Request): string | nu
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
//
|
|
80
|
+
// Try URL query parameters.
|
|
81
81
|
const url = new URL(request.url);
|
|
82
82
|
const query: Record<string, string> = {};
|
|
83
83
|
url.searchParams.forEach((value, key) => {
|
|
@@ -93,8 +93,8 @@ type NextCookiesLike = {
|
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
96
|
+
* Extract fingerprint ID from Next.js runtime headers/cookies stores.
|
|
97
|
+
* Reusable in App Router server components and Server Actions.
|
|
98
98
|
*/
|
|
99
99
|
export function extractFingerprintFromNextStores(params: {
|
|
100
100
|
headers: NextHeadersLike;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fingerprint Shared Utilities
|
|
3
|
-
*
|
|
3
|
+
* Shared constants, types, and validation logic for client and server.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// Storage keys and header names for fingerprint IDs.
|
|
7
7
|
export const FINGERPRINT_STORAGE_KEY = '__x_fingerprint_id';
|
|
8
8
|
export const FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY = '__x_fingerprint_debug_override';
|
|
9
9
|
export const FINGERPRINT_HEADER_NAME = 'x-fingerprint-id-v8';
|
|
@@ -15,16 +15,16 @@ export const FINGERPRINT_FIRST_TOUCH_HEADER = 'x-first-touch';
|
|
|
15
15
|
export const FINGERPRINT_DEBUG_PREFIX = 'fp_test_dbg_';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* Validate fingerprint ID format.
|
|
19
|
+
* Safe to use on both client and server.
|
|
20
20
|
*/
|
|
21
21
|
export function isValidFingerprintId(fingerprintId: string): boolean {
|
|
22
22
|
if (!fingerprintId) return false;
|
|
23
|
-
//
|
|
24
|
-
// - fp_ + FingerprintJS visitorId
|
|
25
|
-
// - fp_fallback_ +
|
|
26
|
-
// - fp_server_ +
|
|
27
|
-
// - fp_test_dbg_ +
|
|
23
|
+
// Supported formats:
|
|
24
|
+
// - fp_ + FingerprintJS visitorId with variable length.
|
|
25
|
+
// - fp_fallback_ + timestamp + random string for client fallback.
|
|
26
|
+
// - fp_server_ + timestamp + random string for server fallback.
|
|
27
|
+
// - fp_test_dbg_ + timestamp + random string for debug concurrency tests.
|
|
28
28
|
return /^fp(_fallback|_server|_test_dbg)?_[a-zA-Z0-9_]+$/.test(fingerprintId);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -36,7 +36,7 @@ export function isDebugFingerprintId(fingerprintId: string | null | undefined):
|
|
|
36
36
|
return fingerprintId.startsWith(FINGERPRINT_DEBUG_PREFIX);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// Exported constants.
|
|
40
40
|
export const FINGERPRINT_CONSTANTS = {
|
|
41
41
|
STORAGE_KEY: FINGERPRINT_STORAGE_KEY,
|
|
42
42
|
DEBUG_OVERRIDE_STORAGE_KEY: FINGERPRINT_DEBUG_OVERRIDE_STORAGE_KEY,
|
|
@@ -21,7 +21,7 @@ import { FINGERPRINT_SOURCE_REFER, isDebugFingerprintId, isValidFingerprintId }
|
|
|
21
21
|
* Accepts configuration to customize API endpoint and behavior
|
|
22
22
|
*/
|
|
23
23
|
export function useFingerprint(config: FingerprintConfig): UseFingerprintResult {
|
|
24
|
-
//
|
|
24
|
+
// Server-side rendering guard.
|
|
25
25
|
if (typeof window === 'undefined') {
|
|
26
26
|
return {
|
|
27
27
|
fingerprintId: null,
|
|
@@ -51,7 +51,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
|
|
|
51
51
|
}, []);
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* Phase 1: initialize fingerprint ID.
|
|
55
55
|
*/
|
|
56
56
|
const initializeFingerprintId = useCallback(async () => {
|
|
57
57
|
try {
|
|
@@ -68,7 +68,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
|
|
|
68
68
|
}, []);
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* Phase 2: initialize anonymous user.
|
|
72
72
|
*/
|
|
73
73
|
const initializeAnonymousUser = useCallback(async () => {
|
|
74
74
|
if (!fingerprintId) {
|
|
@@ -140,7 +140,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
|
|
|
140
140
|
}, [fingerprintId, config.apiEndpoint, isInitialized]);
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
|
-
*
|
|
143
|
+
* Refresh user data with a POST request; the backend supports upsert semantics.
|
|
144
144
|
*/
|
|
145
145
|
const refreshUserData = useCallback(async () => {
|
|
146
146
|
if (!fingerprintId) {
|
|
@@ -180,7 +180,7 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
|
|
|
180
180
|
}
|
|
181
181
|
}, [fingerprintId, config.apiEndpoint]);
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// Phase 1: generate fingerprint ID after page load.
|
|
184
184
|
useEffect(() => {
|
|
185
185
|
const initFingerprint = async () => {
|
|
186
186
|
setIsLoading(true);
|
|
@@ -191,11 +191,11 @@ export function useFingerprint(config: FingerprintConfig): UseFingerprintResult
|
|
|
191
191
|
initFingerprint();
|
|
192
192
|
}, [initializeFingerprintId]);
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// Phase 2: initialize the user once a fingerprint ID is available; the backend supports upsert semantics.
|
|
195
195
|
useEffect(() => {
|
|
196
196
|
if (!fingerprintId || isInitialized || isLoading || error || config.autoInitialize === false) return;
|
|
197
197
|
|
|
198
|
-
//
|
|
198
|
+
// Use POST directly; the backend handles lookup and create-if-missing behavior.
|
|
199
199
|
initializeAnonymousUser();
|
|
200
200
|
}, [fingerprintId, isInitialized, isLoading, error, initializeAnonymousUser, config.autoInitialize]);
|
|
201
201
|
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { SignIn } from '@clerk/nextjs';
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
|
+
import { clerkAuthPageAppearance } from './clerk-auth-appearance';
|
|
5
6
|
import { useFingerprintContextSafe } from './fingerprint/fingerprint-provider';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* SignIn component with fingerprint awareness
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Falls back to the standard SignIn component when FingerprintProvider is absent.
|
|
11
|
+
* Handles fingerprint-related metadata when FingerprintProvider is available.
|
|
11
12
|
*/
|
|
12
13
|
export function SignInWithFingerprint() {
|
|
13
14
|
const fingerprintContext = useFingerprintContextSafe();
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
+
// Use defaults when fingerprint context is unavailable.
|
|
16
17
|
const {
|
|
17
18
|
fingerprintId = null,
|
|
18
19
|
xUser = null,
|
|
@@ -20,19 +21,18 @@ export function SignInWithFingerprint() {
|
|
|
20
21
|
initializeAnonymousUser = async () => {}
|
|
21
22
|
} = fingerprintContext || {};
|
|
22
23
|
|
|
23
|
-
//
|
|
24
|
+
// Prepare Clerk metadata with anonymous user information.
|
|
24
25
|
const unsafeMetadata = {
|
|
25
26
|
user_id: xUser?.userId || null,
|
|
26
27
|
fingerprint_id: fingerprintId || null,
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
//
|
|
30
|
+
// Ensure the anonymous user has been initialized.
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
if (!isInitialized && fingerprintId) {
|
|
32
33
|
initializeAnonymousUser();
|
|
33
34
|
}
|
|
34
35
|
}, [fingerprintId, isInitialized, initializeAnonymousUser]);
|
|
35
36
|
|
|
36
|
-
return <SignIn unsafeMetadata={unsafeMetadata} />;
|
|
37
|
+
return <SignIn appearance={clerkAuthPageAppearance} unsafeMetadata={unsafeMetadata} />;
|
|
37
38
|
}
|
|
38
|
-
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { SignUpButton, useClerk } from '@clerk/nextjs';
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
|
+
import { clerkAuthModalAppearance } from './clerk-auth-appearance';
|
|
5
6
|
import { useFingerprintContextSafe } from './fingerprint/fingerprint-provider';
|
|
6
7
|
|
|
7
8
|
interface SignUpButtonWithFingerprintProps {
|
|
@@ -15,7 +16,7 @@ export function SignUpButtonWithFingerprint({
|
|
|
15
16
|
}: SignUpButtonWithFingerprintProps) {
|
|
16
17
|
if (mode === 'redirect') {
|
|
17
18
|
return (
|
|
18
|
-
//
|
|
19
|
+
// Redirect mode navigates directly to the custom sign-up page.
|
|
19
20
|
<SignUpButton>
|
|
20
21
|
<button
|
|
21
22
|
className="w-16 sm:w-20 h-8 sm:h-9 px-1.5 sm:px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-xs sm:text-sm whitespace-nowrap"
|
|
@@ -26,10 +27,10 @@ export function SignUpButtonWithFingerprint({
|
|
|
26
27
|
)
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
//
|
|
30
|
+
// Modal mode requires a custom sign-up button.
|
|
30
31
|
const fingerprintContext = useFingerprintContextSafe();
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
+
// Use defaults when fingerprint context is unavailable.
|
|
33
34
|
const {
|
|
34
35
|
fingerprintId = null,
|
|
35
36
|
xUser = null,
|
|
@@ -43,7 +44,7 @@ export function SignUpButtonWithFingerprint({
|
|
|
43
44
|
fingerprint_id: fingerprintId || null,
|
|
44
45
|
};
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
+
// Ensure the anonymous user has been initialized.
|
|
47
48
|
useEffect(() => {
|
|
48
49
|
if (!isInitialized && fingerprintId) {
|
|
49
50
|
initializeAnonymousUser();
|
|
@@ -55,6 +56,7 @@ export function SignUpButtonWithFingerprint({
|
|
|
55
56
|
|
|
56
57
|
const handleClick = () => {
|
|
57
58
|
openSignUp({
|
|
59
|
+
appearance: clerkAuthModalAppearance,
|
|
58
60
|
unsafeMetadata,
|
|
59
61
|
});
|
|
60
62
|
};
|
|
@@ -67,4 +69,4 @@ export function SignUpButtonWithFingerprint({
|
|
|
67
69
|
{signUp}
|
|
68
70
|
</button>
|
|
69
71
|
);
|
|
70
|
-
}
|
|
72
|
+
}
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { SignUp } from '@clerk/nextjs';
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
|
+
import { clerkAuthPageAppearance } from './clerk-auth-appearance';
|
|
5
6
|
import { useFingerprintContextSafe } from './fingerprint/fingerprint-provider';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* SignUp component with fingerprint awareness
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Falls back to the standard SignUp component when FingerprintProvider is absent.
|
|
11
|
+
* Handles fingerprint-related metadata when FingerprintProvider is available.
|
|
11
12
|
*/
|
|
12
13
|
export function SignUpWithFingerprint() {
|
|
13
14
|
const fingerprintContext = useFingerprintContextSafe();
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
+
// Use defaults when fingerprint context is unavailable.
|
|
16
17
|
const {
|
|
17
18
|
fingerprintId = null,
|
|
18
19
|
xUser = null,
|
|
@@ -20,19 +21,18 @@ export function SignUpWithFingerprint() {
|
|
|
20
21
|
initializeAnonymousUser = async () => {}
|
|
21
22
|
} = fingerprintContext || {};
|
|
22
23
|
|
|
23
|
-
//
|
|
24
|
+
// Prepare Clerk metadata with anonymous user information.
|
|
24
25
|
const unsafeMetadata = {
|
|
25
26
|
user_id: xUser?.userId || null,
|
|
26
27
|
fingerprint_id: fingerprintId || null,
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
//
|
|
30
|
+
// Ensure the anonymous user has been initialized.
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
if (!isInitialized && fingerprintId) {
|
|
32
33
|
initializeAnonymousUser();
|
|
33
34
|
}
|
|
34
35
|
}, [fingerprintId, isInitialized, initializeAnonymousUser]);
|
|
35
36
|
|
|
36
|
-
return <SignUp unsafeMetadata={unsafeMetadata} />;
|
|
37
|
+
return <SignUp appearance={clerkAuthPageAppearance} unsafeMetadata={unsafeMetadata} />;
|
|
37
38
|
}
|
|
38
|
-
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
type MobileMenuAction,
|
|
12
12
|
} from './custom-header';
|
|
13
13
|
|
|
14
|
-
// - bannerHeight/headerHeight
|
|
15
|
-
// - layoutStyle
|
|
16
|
-
// - CustomHomeHeader
|
|
17
|
-
// -
|
|
14
|
+
// - Set bannerHeight/headerHeight to the rem values expected by the project. Use bannerHeight = 0 when there is no banner.
|
|
15
|
+
// - layoutStyle passes the variables to HomeLayout's main element, offsetting content without has-banner/no-banner classes.
|
|
16
|
+
// - CustomHomeHeader accepts HomeLayout props such as links, nav, searchToggle, themeSwitch, and i18n, then reuses Fumadocs navigation behavior.
|
|
17
|
+
// - The banner can still use FumaBannerSuit or any custom banner component. Header positioning and z-index are already handled; the toggle only affects bannerHeight.
|
|
18
18
|
|
|
19
19
|
export interface CustomHomeLayoutProps {
|
|
20
20
|
locale: string;
|
|
@@ -179,7 +179,7 @@ export function Mermaid({ chart, title, watermarkEnabled, watermarkText, handDra
|
|
|
179
179
|
// prevent browser-level zoom (touchpad pinch/shortcut) from taking effect when the dialog is open
|
|
180
180
|
useEffect(() => {
|
|
181
181
|
if (!open) return;
|
|
182
|
-
//
|
|
182
|
+
// Init default zoom out 400%
|
|
183
183
|
resetTransform();
|
|
184
184
|
const onGlobalWheel = (ev: WheelEvent) => {
|
|
185
185
|
if (ev.ctrlKey || ev.metaKey) {
|
|
@@ -6,7 +6,6 @@ import { ExternalLinkIcon, StarIcon } from '@windrun-huaiin/base-ui/icons';
|
|
|
6
6
|
interface FumaGithubInfoProps {
|
|
7
7
|
owner: string;
|
|
8
8
|
repo: string;
|
|
9
|
-
token?: string;
|
|
10
9
|
className?: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
@@ -16,7 +15,7 @@ interface GitHubRepoData {
|
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
// Loading state component
|
|
19
|
-
function GitHubInfoSkeleton({
|
|
18
|
+
function GitHubInfoSkeleton({ className }: Pick<FumaGithubInfoProps, 'owner' | 'repo' | 'className'>) {
|
|
20
19
|
return (
|
|
21
20
|
<div className={`flex flex-col gap-1.5 p-2 rounded-lg text-sm text-fd-foreground/80 lg:flex-row lg:items-center animate-pulse ${className}`}>
|
|
22
21
|
<div className="flex items-center gap-2">
|
|
@@ -114,7 +113,7 @@ function humanizeNumber(num: number): string {
|
|
|
114
113
|
* - 🎨 Three states: loading, success, error
|
|
115
114
|
* - 💯 Not affected by network issues causing page crashes
|
|
116
115
|
*/
|
|
117
|
-
export function FumaGithubInfo({ owner, repo,
|
|
116
|
+
export function FumaGithubInfo({ owner, repo, className }: FumaGithubInfoProps) {
|
|
118
117
|
const [data, setData] = useState<GitHubRepoData | null>(null);
|
|
119
118
|
const [loading, setLoading] = useState(true);
|
|
120
119
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -133,10 +132,6 @@ export function FumaGithubInfo({ owner, repo, token, className }: FumaGithubInfo
|
|
|
133
132
|
'Accept': 'application/vnd.github.v3+json',
|
|
134
133
|
});
|
|
135
134
|
|
|
136
|
-
if (token) {
|
|
137
|
-
headers.set('Authorization', `Bearer ${token}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
135
|
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
141
136
|
signal: controller.signal,
|
|
142
137
|
headers,
|
|
@@ -170,7 +165,7 @@ export function FumaGithubInfo({ owner, repo, token, className }: FumaGithubInfo
|
|
|
170
165
|
};
|
|
171
166
|
|
|
172
167
|
fetchRepoData();
|
|
173
|
-
}, [owner, repo
|
|
168
|
+
}, [owner, repo]);
|
|
174
169
|
|
|
175
170
|
// Loading state
|
|
176
171
|
if (loading) {
|
package/src/fuma/site-x.tsx
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useAuth } from '@clerk/nextjs';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { CreditNavButton } from './credit-nav-button';
|
|
6
|
+
import { CreditOverviewClient, type CreditOverviewTranslations } from './credit-overview-client';
|
|
7
|
+
import type { CreditOverviewData } from './types';
|
|
8
|
+
|
|
9
|
+
export interface CreditOverviewPayload {
|
|
10
|
+
data: CreditOverviewData;
|
|
11
|
+
totalLabel: string;
|
|
12
|
+
translations: CreditOverviewTranslations;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CreditOverviewNavClientProps {
|
|
16
|
+
locale: string;
|
|
17
|
+
endpoint: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildCreditOverviewUrl(endpoint: string, locale: string) {
|
|
21
|
+
const url = new URL(endpoint, window.location.origin);
|
|
22
|
+
url.searchParams.set('locale', locale);
|
|
23
|
+
return url.toString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function CreditOverviewNavClient({
|
|
27
|
+
locale,
|
|
28
|
+
endpoint,
|
|
29
|
+
}: CreditOverviewNavClientProps) {
|
|
30
|
+
const { isLoaded, isSignedIn, userId } = useAuth();
|
|
31
|
+
const [payload, setPayload] = useState<CreditOverviewPayload | null>(null);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!isLoaded) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!isSignedIn) {
|
|
39
|
+
setPayload(null);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
|
|
45
|
+
async function loadCreditOverview() {
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(buildCreditOverviewUrl(endpoint, locale), {
|
|
48
|
+
credentials: 'same-origin',
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
if (!controller.signal.aborted) {
|
|
54
|
+
setPayload(null);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const nextPayload = (await response.json()) as CreditOverviewPayload | null;
|
|
60
|
+
if (!controller.signal.aborted) {
|
|
61
|
+
setPayload(nextPayload);
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (!controller.signal.aborted) {
|
|
65
|
+
setPayload(null);
|
|
66
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
loadCreditOverview();
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
controller.abort();
|
|
75
|
+
};
|
|
76
|
+
}, [endpoint, isLoaded, isSignedIn, locale, userId]);
|
|
77
|
+
|
|
78
|
+
if (!payload) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<CreditNavButton
|
|
84
|
+
locale={locale}
|
|
85
|
+
totalBalance={payload.data.totalBalance}
|
|
86
|
+
totalLabel={payload.totalLabel}
|
|
87
|
+
>
|
|
88
|
+
<CreditOverviewClient
|
|
89
|
+
locale={locale}
|
|
90
|
+
data={payload.data}
|
|
91
|
+
translations={payload.translations}
|
|
92
|
+
/>
|
|
93
|
+
</CreditNavButton>
|
|
94
|
+
);
|
|
95
|
+
}
|
package/src/main/credit/index.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
export { CreditOverviewClient } from './credit-overview-client';
|
|
4
|
+
export { CreditOverviewNavClient } from './credit-overview-nav-client';
|
|
4
5
|
export { CreditNavButton } from './credit-nav-button';
|
|
5
6
|
export type { CreditOverviewTranslations } from './credit-overview-client';
|
|
7
|
+
export type {
|
|
8
|
+
CreditOverviewNavClientProps,
|
|
9
|
+
CreditOverviewPayload,
|
|
10
|
+
} from './credit-overview-nav-client';
|
|
6
11
|
export type {
|
|
7
12
|
CreditOverviewData,
|
|
8
13
|
CreditBucket,
|