@windrun-huaiin/third-ui 30.1.0 → 31.0.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/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/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/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/site-x.tsx +0 -1
- package/src/main/calendar/calendar-date-range-input.tsx +1 -1
- 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
|
@@ -24,7 +24,7 @@ function XPillSelect(props) {
|
|
|
24
24
|
const isAllSelected = props.mode === 'multiple' &&
|
|
25
25
|
allOptionValues.length > 0 &&
|
|
26
26
|
allOptionValues.every((value) => selectedValues.includes(value));
|
|
27
|
-
const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || '
|
|
27
|
+
const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || 'All' : null;
|
|
28
28
|
const hasVisiblePillLimit = props.mode === 'multiple' && typeof maxVisiblePills === 'number' && maxVisiblePills >= 0;
|
|
29
29
|
const visibleSelectedValues = aggregatedSelectedLabel || !hasVisiblePillLimit
|
|
30
30
|
? selectedValues
|
|
@@ -124,7 +124,7 @@ function XPillSelect(props) {
|
|
|
124
124
|
event.stopPropagation();
|
|
125
125
|
removeValue(selectedValue);
|
|
126
126
|
}, disabled: disabled, className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'gap-1 px-2.5 py-0.5 text-[11px]' : 'gap-1.5 px-3 py-1 text-xs', themeBgColor, themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: optionLabel, children: jsx("span", { className: cn('truncate', maxPillWidthClassName), children: optionLabel }) }, selectedValue));
|
|
127
|
-
}), hiddenSelectedCount > 0 ? (jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title:
|
|
127
|
+
}), hiddenSelectedCount > 0 ? (jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `${hiddenSelectedCount} items remain unexpanded`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsx("span", { className: cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsx(ChevronDownIcon, { className: cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && themeBorderColor), children: [inputEnabled ? (jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
|
|
128
128
|
if (event.key !== 'Enter') {
|
|
129
129
|
return;
|
|
130
130
|
}
|
package/dist/main/server.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export type { CreditOverviewData, CreditBucket, CreditBucketStatus, Subscription
|
|
|
12
12
|
export { MoneyPrice } from './money-price/money-price';
|
|
13
13
|
export { getActiveProviderConfigUtil, getCreditsFromPriceIdUtil, getPriceConfigUtil } from './money-price/money-price-config-util';
|
|
14
14
|
export { buildMoneyPriceData } from './money-price/money-price-data';
|
|
15
|
-
export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext } from './money-price/money-price-types';
|
|
15
|
+
export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, MoneyPriceAnimeTone, MoneyPriceStrictDiffAnime, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext } from './money-price/money-price-types';
|
|
16
16
|
export { UserState } from './money-price/money-price-types';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "31.0.0",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./clerk": {
|
|
@@ -221,9 +221,9 @@
|
|
|
221
221
|
"LICENSE"
|
|
222
222
|
],
|
|
223
223
|
"dependencies": {
|
|
224
|
-
"@clerk/localizations": "^4.
|
|
225
|
-
"@clerk/nextjs": "^7.
|
|
226
|
-
"@clerk/shared": "^4.
|
|
224
|
+
"@clerk/localizations": "^4.6.2",
|
|
225
|
+
"@clerk/nextjs": "^7.3.3",
|
|
226
|
+
"@clerk/shared": "^4.10.2",
|
|
227
227
|
"@fingerprintjs/fingerprintjs": "^5.1.0",
|
|
228
228
|
"animejs": "^4.4.1",
|
|
229
229
|
"class-variance-authority": "^0.7.1",
|
|
@@ -243,9 +243,9 @@
|
|
|
243
243
|
"tslib": "^2.8.1",
|
|
244
244
|
"unified": "^11.0.5",
|
|
245
245
|
"zod": "^4.3.6",
|
|
246
|
-
"@windrun-huaiin/base-ui": "^
|
|
247
|
-
"@windrun-huaiin/lib": "^
|
|
248
|
-
"@windrun-huaiin/contracts": "^
|
|
246
|
+
"@windrun-huaiin/base-ui": "^31.0.0",
|
|
247
|
+
"@windrun-huaiin/lib": "^31.0.0",
|
|
248
|
+
"@windrun-huaiin/contracts": "^31.0.0"
|
|
249
249
|
},
|
|
250
250
|
"peerDependencies": {
|
|
251
251
|
"clsx": "^2.1.1",
|
|
@@ -281,6 +281,12 @@
|
|
|
281
281
|
"publishConfig": {
|
|
282
282
|
"access": "public"
|
|
283
283
|
},
|
|
284
|
+
"homepage": "https://d8ger.com",
|
|
285
|
+
"repository": {
|
|
286
|
+
"type": "git",
|
|
287
|
+
"url": "git+https://github.com/caofanCPU/next-ai-build.git",
|
|
288
|
+
"directory": "packages/third-ui"
|
|
289
|
+
},
|
|
284
290
|
"scripts": {
|
|
285
291
|
"build": "rollup -c rollup.config.mjs",
|
|
286
292
|
"build:prod": "rollup -c rollup.config.mjs",
|
|
@@ -188,7 +188,7 @@ export function AIPromptTextarea({
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
//
|
|
191
|
+
// Render the title component.
|
|
192
192
|
const renderTitle = () => {
|
|
193
193
|
if (!title?.trim()) return null
|
|
194
194
|
|
|
@@ -200,7 +200,7 @@ export function AIPromptTextarea({
|
|
|
200
200
|
)
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
//
|
|
203
|
+
// Render the textarea component.
|
|
204
204
|
const renderTextarea = (isEmbedded = false) => (
|
|
205
205
|
<textarea
|
|
206
206
|
ref={textareaRef}
|
|
@@ -220,7 +220,7 @@ export function AIPromptTextarea({
|
|
|
220
220
|
/>
|
|
221
221
|
)
|
|
222
222
|
|
|
223
|
-
//
|
|
223
|
+
// Render the word count.
|
|
224
224
|
const renderWordCount = () => {
|
|
225
225
|
if (!showWordCount) return null
|
|
226
226
|
|
|
@@ -238,7 +238,7 @@ export function AIPromptTextarea({
|
|
|
238
238
|
)
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
//
|
|
241
|
+
// Render the embedded title layout when a title is present and embedded mode is enabled.
|
|
242
242
|
if (embed && (title)) {
|
|
243
243
|
return (
|
|
244
244
|
<div className="space-y-2">
|
|
@@ -256,7 +256,7 @@ export function AIPromptTextarea({
|
|
|
256
256
|
)
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
//
|
|
259
|
+
// Default layout: external title or no title.
|
|
260
260
|
return (
|
|
261
261
|
<div className="space-y-2">
|
|
262
262
|
{renderTitle()}
|
|
@@ -264,4 +264,4 @@ export function AIPromptTextarea({
|
|
|
264
264
|
{renderWordCount()}
|
|
265
265
|
</div>
|
|
266
266
|
)
|
|
267
|
-
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const clerkAuthModalAppearance = {
|
|
2
|
+
elements: {
|
|
3
|
+
modalContent: '!items-start !pt-16 sm:!pt-24',
|
|
4
|
+
cardBox: '!mt-0',
|
|
5
|
+
},
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const clerkAuthPageAppearance = {
|
|
9
|
+
elements: {
|
|
10
|
+
rootBox: 'w-full',
|
|
11
|
+
cardBox: 'mx-auto',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const clerkAuthPageContainerClassName =
|
|
16
|
+
'flex min-h-[calc(100dvh-var(--fd-banner-height,0px)-var(--fd-header-height,3.5rem))] w-full items-start justify-center px-6 pt-[calc(var(--fd-header-height,3.5rem)+2rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-header-height,3.5rem)+4.5rem)] md:pb-8';
|
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { SignUpWithFingerprint } from './signup-with-fingerprint-client';
|
|
9
9
|
import { SignInWithFingerprint } from './signin-with-fingerprint-client';
|
|
10
|
-
|
|
11
|
-
const clerkPageContainerClassName =
|
|
12
|
-
'flex min-h-dvh w-full items-start justify-center px-6 pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1.5rem)] md:pb-8';
|
|
10
|
+
import { clerkAuthPageContainerClassName } from './clerk-auth-appearance';
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* Create a SignUp page with fingerprint support
|
|
@@ -18,7 +16,7 @@ const clerkPageContainerClassName =
|
|
|
18
16
|
export function createSignUpPageWithFingerprint() {
|
|
19
17
|
return function SignUpPage() {
|
|
20
18
|
return (
|
|
21
|
-
<div className={
|
|
19
|
+
<div className={clerkAuthPageContainerClassName}>
|
|
22
20
|
<SignUpWithFingerprint />
|
|
23
21
|
</div>
|
|
24
22
|
);
|
|
@@ -32,7 +30,7 @@ export function createSignUpPageWithFingerprint() {
|
|
|
32
30
|
export function createSignInPageWithFingerprint() {
|
|
33
31
|
return function SignInPage() {
|
|
34
32
|
return (
|
|
35
|
-
<div className={
|
|
33
|
+
<div className={clerkAuthPageContainerClassName}>
|
|
36
34
|
<SignInWithFingerprint />
|
|
37
35
|
</div>
|
|
38
36
|
);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { SignIn, SignUp, Waitlist } from '@clerk/nextjs';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
clerkAuthPageAppearance,
|
|
4
|
+
clerkAuthPageContainerClassName,
|
|
5
|
+
} from './clerk-auth-appearance';
|
|
5
6
|
|
|
6
7
|
// Legacy page generators (for backward compatibility)
|
|
7
8
|
export function createSignInPage() {
|
|
8
9
|
return function SignInPage() {
|
|
9
10
|
return (
|
|
10
|
-
<div className={
|
|
11
|
-
<SignIn />
|
|
11
|
+
<div className={clerkAuthPageContainerClassName}>
|
|
12
|
+
<SignIn appearance={clerkAuthPageAppearance} />
|
|
12
13
|
</div>
|
|
13
14
|
);
|
|
14
15
|
};
|
|
@@ -17,8 +18,8 @@ export function createSignInPage() {
|
|
|
17
18
|
export function createSignUpPage() {
|
|
18
19
|
return function SignUpPage() {
|
|
19
20
|
return (
|
|
20
|
-
<div className={
|
|
21
|
-
<SignUp />
|
|
21
|
+
<div className={clerkAuthPageContainerClassName}>
|
|
22
|
+
<SignUp appearance={clerkAuthPageAppearance} />
|
|
22
23
|
</div>
|
|
23
24
|
);
|
|
24
25
|
};
|
|
@@ -27,7 +28,7 @@ export function createSignUpPage() {
|
|
|
27
28
|
export function createWaitlistPage() {
|
|
28
29
|
return function WaitlistPage() {
|
|
29
30
|
return (
|
|
30
|
-
<div className={
|
|
31
|
+
<div className={clerkAuthPageContainerClassName}>
|
|
31
32
|
<Waitlist />
|
|
32
33
|
</div>
|
|
33
34
|
);
|
|
@@ -4,6 +4,7 @@ import { useState } from 'react';
|
|
|
4
4
|
import { ClerkLoaded, ClerkLoading, SignInButton, UserButton, useAuth } from "@clerk/nextjs";
|
|
5
5
|
import { ReceiptTextIcon, ShieldUserIcon } from '@windrun-huaiin/base-ui/icons';
|
|
6
6
|
import { themeButtonGradientClass } from '@windrun-huaiin/base-ui/lib';
|
|
7
|
+
import { clerkAuthModalAppearance } from './clerk-auth-appearance';
|
|
7
8
|
import { SignUpButtonWithFingerprint } from './signup-button-with-fingerprint-client';
|
|
8
9
|
|
|
9
10
|
interface ClerkUserData {
|
|
@@ -51,11 +52,19 @@ export function ClerkUserClient({ data }: { data: ClerkUserData }) {
|
|
|
51
52
|
)}
|
|
52
53
|
</div>
|
|
53
54
|
)}
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
{data.clerkAuthInModal ? (
|
|
56
|
+
<SignInButton mode="modal" appearance={clerkAuthModalAppearance}>
|
|
57
|
+
<button 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">
|
|
58
|
+
{data.signIn}
|
|
59
|
+
</button>
|
|
60
|
+
</SignInButton>
|
|
61
|
+
) : (
|
|
62
|
+
<SignInButton mode="redirect">
|
|
63
|
+
<button 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">
|
|
64
|
+
{data.signIn}
|
|
65
|
+
</button>
|
|
66
|
+
</SignInButton>
|
|
67
|
+
)}
|
|
59
68
|
</>
|
|
60
69
|
)}
|
|
61
70
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fingerprint Client Utilities
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Client-only fingerprint generation and management logic.
|
|
4
|
+
* Must be used only in browser environments.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
|
@@ -41,24 +41,24 @@ type FirstTouchData = {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* Check fingerprint ID in browser storage, including localStorage and cookies.
|
|
45
|
+
* Returns a valid ID or null.
|
|
46
46
|
*/
|
|
47
47
|
function checkStoredFingerprintId(): string | null {
|
|
48
48
|
if (typeof window === 'undefined') {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
//
|
|
52
|
+
// Prefer localStorage.
|
|
53
53
|
const localStorageId = getLocalStorageValue(FINGERPRINT_STORAGE_KEY);
|
|
54
54
|
if (localStorageId && isValidFingerprintId(localStorageId)) {
|
|
55
55
|
return localStorageId;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Check cookies.
|
|
59
59
|
const cookieId = getCookieValue(FINGERPRINT_COOKIE_NAME);
|
|
60
60
|
if (cookieId && isValidFingerprintId(cookieId)) {
|
|
61
|
-
//
|
|
61
|
+
// Sync back to localStorage.
|
|
62
62
|
setLocalStorageValue(FINGERPRINT_STORAGE_KEY, cookieId);
|
|
63
63
|
return cookieId;
|
|
64
64
|
}
|
|
@@ -203,34 +203,34 @@ export function getOrCreateFirstTouchData(): FirstTouchData | null {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
206
|
+
* Generate a fingerprint ID from real browser characteristics.
|
|
207
|
+
* Uses FingerprintJS to collect browser signals and create a stable identifier.
|
|
208
208
|
*/
|
|
209
209
|
export async function generateFingerprintId(): Promise<string> {
|
|
210
210
|
if (typeof window === 'undefined') {
|
|
211
211
|
throw new Error('generateFingerprintId can only be used in browser environment');
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
//
|
|
214
|
+
// Check for an existing ID.
|
|
215
215
|
const existingId = checkStoredFingerprintId();
|
|
216
216
|
if (existingId) {
|
|
217
217
|
return existingId;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
try {
|
|
221
|
-
//
|
|
221
|
+
// Generate a fingerprint with FingerprintJS.
|
|
222
222
|
const fp = await FingerprintJS.load();
|
|
223
223
|
const result = await fp.get();
|
|
224
224
|
const fingerprintId = `fp_${result.visitorId}`;
|
|
225
225
|
|
|
226
|
-
//
|
|
226
|
+
// Store in localStorage and cookies.
|
|
227
227
|
setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fingerprintId);
|
|
228
228
|
setCookie(FINGERPRINT_COOKIE_NAME, fingerprintId, 365);
|
|
229
229
|
|
|
230
230
|
return fingerprintId;
|
|
231
231
|
} catch (error) {
|
|
232
232
|
console.warn('Failed to generate fingerprint with FingerprintJS:', error);
|
|
233
|
-
//
|
|
233
|
+
// Fallback: generate an ID from timestamp and randomness.
|
|
234
234
|
const fallbackId = `fp_fallback_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
235
235
|
setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fallbackId);
|
|
236
236
|
setCookie(FINGERPRINT_COOKIE_NAME, fallbackId, 365);
|
|
@@ -240,14 +240,14 @@ export async function generateFingerprintId(): Promise<string> {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/**
|
|
243
|
-
*
|
|
243
|
+
* Get the current fingerprint ID.
|
|
244
244
|
*/
|
|
245
245
|
export function getFingerprintId(): string | null {
|
|
246
246
|
return checkStoredFingerprintId();
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
/**
|
|
250
|
-
*
|
|
250
|
+
* Store a fingerprint ID.
|
|
251
251
|
*/
|
|
252
252
|
export function setFingerprintId(fingerprintId: string): void {
|
|
253
253
|
if (typeof window === 'undefined') {
|
|
@@ -263,7 +263,7 @@ export function setFingerprintId(fingerprintId: string): void {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
/**
|
|
266
|
-
*
|
|
266
|
+
* Clear the fingerprint ID.
|
|
267
267
|
*/
|
|
268
268
|
export function clearFingerprintId(): void {
|
|
269
269
|
if (typeof window === 'undefined') {
|
|
@@ -275,8 +275,8 @@ export function clearFingerprintId(): void {
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
278
|
+
* Get or generate a fingerprint ID.
|
|
279
|
+
* Automatically creates one when none exists.
|
|
280
280
|
*/
|
|
281
281
|
export async function getOrGenerateFingerprintId(): Promise<string> {
|
|
282
282
|
const existingId = checkStoredFingerprintId();
|
|
@@ -288,7 +288,7 @@ export async function getOrGenerateFingerprintId(): Promise<string> {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
/**
|
|
291
|
-
*
|
|
291
|
+
* Create fetch headers containing the fingerprint ID.
|
|
292
292
|
*/
|
|
293
293
|
export async function createFingerprintHeaders(): Promise<Record<string, string>> {
|
|
294
294
|
const fingerprintId = await getOrGenerateFingerprintId();
|
|
@@ -330,7 +330,7 @@ export function createFingerprintFetch() {
|
|
|
330
330
|
};
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
//
|
|
333
|
+
// Private cookie helpers.
|
|
334
334
|
function getCookieValue(name: string): string | null {
|
|
335
335
|
if (typeof document === 'undefined') {
|
|
336
336
|
return null;
|
|
@@ -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,
|