@b3dotfun/sdk 0.1.69-alpha.17 → 0.1.69-alpha.18
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/cjs/anyspend/utils/chain.js +1 -1
- package/dist/cjs/global-account/better-auth-client.d.ts +1883 -0
- package/dist/cjs/global-account/better-auth-client.js +17 -0
- package/dist/cjs/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +4 -1
- package/dist/cjs/global-account/react/components/B3Provider/B3ConfigProvider.js +2 -1
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.d.ts +4 -1
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +3 -2
- package/dist/cjs/global-account/react/components/B3Provider/BetterAuthProvider.d.ts +16 -0
- package/dist/cjs/global-account/react/components/B3Provider/BetterAuthProvider.js +120 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthResetPassword.d.ts +21 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthResetPassword.js +67 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthSignIn.d.ts +34 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +149 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +9 -3
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.d.ts +6 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +123 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/utils/signInUtils.js +5 -1
- package/dist/cjs/global-account/react/components/index.d.ts +3 -0
- package/dist/cjs/global-account/react/components/index.js +7 -3
- package/dist/cjs/global-account/react/hooks/index.d.ts +1 -0
- package/dist/cjs/global-account/react/hooks/index.js +4 -2
- package/dist/cjs/global-account/react/hooks/useBetterAuth.d.ts +969 -0
- package/dist/cjs/global-account/react/hooks/useBetterAuth.js +142 -0
- package/dist/esm/anyspend/utils/chain.js +1 -1
- package/dist/esm/global-account/better-auth-client.d.ts +1883 -0
- package/dist/esm/global-account/better-auth-client.js +13 -0
- package/dist/esm/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +4 -1
- package/dist/esm/global-account/react/components/B3Provider/B3ConfigProvider.js +2 -1
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.d.ts +4 -1
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +3 -2
- package/dist/esm/global-account/react/components/B3Provider/BetterAuthProvider.d.ts +16 -0
- package/dist/esm/global-account/react/components/B3Provider/BetterAuthProvider.js +115 -0
- package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthResetPassword.d.ts +21 -0
- package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthResetPassword.js +64 -0
- package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthSignIn.d.ts +34 -0
- package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +146 -0
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +9 -3
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.d.ts +6 -0
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +120 -0
- package/dist/esm/global-account/react/components/SignInWithB3/utils/signInUtils.js +5 -1
- package/dist/esm/global-account/react/components/index.d.ts +3 -0
- package/dist/esm/global-account/react/components/index.js +2 -0
- package/dist/esm/global-account/react/hooks/index.d.ts +1 -0
- package/dist/esm/global-account/react/hooks/index.js +1 -0
- package/dist/esm/global-account/react/hooks/useBetterAuth.d.ts +969 -0
- package/dist/esm/global-account/react/hooks/useBetterAuth.js +136 -0
- package/dist/styles/index.css +1 -1
- package/dist/types/global-account/better-auth-client.d.ts +1883 -0
- package/dist/types/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +4 -1
- package/dist/types/global-account/react/components/B3Provider/B3Provider.d.ts +4 -1
- package/dist/types/global-account/react/components/B3Provider/BetterAuthProvider.d.ts +16 -0
- package/dist/types/global-account/react/components/SignInWithB3/BetterAuthResetPassword.d.ts +21 -0
- package/dist/types/global-account/react/components/SignInWithB3/BetterAuthSignIn.d.ts +34 -0
- package/dist/types/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.d.ts +6 -0
- package/dist/types/global-account/react/components/index.d.ts +3 -0
- package/dist/types/global-account/react/hooks/index.d.ts +1 -0
- package/dist/types/global-account/react/hooks/useBetterAuth.d.ts +969 -0
- package/package.json +2 -1
- package/src/anyspend/utils/chain.ts +1 -2
- package/src/global-account/better-auth-client.ts +17 -0
- package/src/global-account/react/components/B3Provider/B3ConfigProvider.tsx +6 -0
- package/src/global-account/react/components/B3Provider/B3Provider.tsx +15 -5
- package/src/global-account/react/components/B3Provider/BetterAuthProvider.tsx +127 -0
- package/src/global-account/react/components/SignInWithB3/BetterAuthResetPassword.tsx +146 -0
- package/src/global-account/react/components/SignInWithB3/BetterAuthSignIn.tsx +375 -0
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +9 -3
- package/src/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.tsx +263 -0
- package/src/global-account/react/components/SignInWithB3/utils/signInUtils.ts +5 -1
- package/src/global-account/react/components/index.ts +3 -0
- package/src/global-account/react/hooks/index.ts +1 -0
- package/src/global-account/react/hooks/useBetterAuth.ts +177 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b3dotfun/sdk",
|
|
3
|
-
"version": "0.1.69-alpha.
|
|
3
|
+
"version": "0.1.69-alpha.18",
|
|
4
4
|
"source": "src/index.ts",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"react-native": "./dist/cjs/index.native.js",
|
|
@@ -329,6 +329,7 @@
|
|
|
329
329
|
"@stripe/stripe-js": "^7.3.1",
|
|
330
330
|
"@thirdweb-dev/wagmi-adapter": "0.2.165",
|
|
331
331
|
"@web3icons/react": "3.16.0",
|
|
332
|
+
"better-auth": "^1.5.6",
|
|
332
333
|
"big.js": "^7.0.1",
|
|
333
334
|
"boring-avatars": "^2.0.4",
|
|
334
335
|
"class-variance-authority": "0.7.0",
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
ABSTRACT_PUBLIC_RPC,
|
|
4
4
|
ARBITRUM_PUBLIC_RPC,
|
|
5
5
|
AVALANCHE_PUBLIC_RPC,
|
|
6
|
-
B3_PUBLIC_RPC,
|
|
7
6
|
BASE_PUBLIC_RPC,
|
|
8
7
|
BSC_PUBLIC_RPC,
|
|
9
8
|
ETHEREUM_PUBLIC_RPC,
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
POLYGON_PUBLIC_RPC,
|
|
13
12
|
} from "@b3dotfun/sdk/anyspend/constants/rpc";
|
|
14
13
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
14
|
+
import { b3Viem } from "@b3dotfun/sdk/shared/constants/chains/b3Viem";
|
|
15
15
|
import invariant from "invariant";
|
|
16
16
|
import {
|
|
17
17
|
Account,
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
Transport,
|
|
26
26
|
WalletClient,
|
|
27
27
|
} from "viem";
|
|
28
|
-
import { b3Viem } from "@b3dotfun/sdk/shared/constants/chains/b3Viem";
|
|
29
28
|
import { abstract, arbitrum, avalanche, base, bsc, mainnet, optimism, polygon } from "viem/chains";
|
|
30
29
|
import { ChainType, IBaseChain, IEVMChain, IHyperliquidChain, ISolanaChain } from "../types/chain";
|
|
31
30
|
import {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createAuthClient } from "better-auth/client";
|
|
2
|
+
import { B3_API_URL } from "../app.shared";
|
|
3
|
+
|
|
4
|
+
export type B3BetterAuthClient = ReturnType<typeof createB3BetterAuthClient>;
|
|
5
|
+
|
|
6
|
+
export function createB3BetterAuthClient(baseURL: string = B3_API_URL) {
|
|
7
|
+
return createAuthClient({
|
|
8
|
+
baseURL,
|
|
9
|
+
basePath: "/auth",
|
|
10
|
+
fetchOptions: {
|
|
11
|
+
credentials: "include",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Default singleton for standard usage
|
|
17
|
+
export const betterAuthClient = createB3BetterAuthClient();
|
|
@@ -15,6 +15,8 @@ const DEFAULT_PERMISSIONS: PermissionsConfig = {
|
|
|
15
15
|
endDate: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year from now
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
export type AuthStrategy = "thirdweb" | "better-auth";
|
|
19
|
+
|
|
18
20
|
export interface B3ConfigContextType {
|
|
19
21
|
accountOverride?: Account;
|
|
20
22
|
automaticallySetFirstEoa: boolean;
|
|
@@ -25,6 +27,7 @@ export interface B3ConfigContextType {
|
|
|
25
27
|
partnerId: string;
|
|
26
28
|
stripePublishableKey?: string;
|
|
27
29
|
createClientReferenceId?: (params: CreateOrderParams | CreateOnrampOrderParams) => Promise<string>;
|
|
30
|
+
authStrategy: AuthStrategy;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const B3ConfigContext = createContext<B3ConfigContextType | null>(null);
|
|
@@ -40,6 +43,7 @@ export function B3ConfigProvider({
|
|
|
40
43
|
partnerId,
|
|
41
44
|
stripePublishableKey,
|
|
42
45
|
createClientReferenceId,
|
|
46
|
+
authStrategy = "thirdweb",
|
|
43
47
|
}: {
|
|
44
48
|
children: React.ReactNode;
|
|
45
49
|
accountOverride?: Account;
|
|
@@ -51,6 +55,7 @@ export function B3ConfigProvider({
|
|
|
51
55
|
partnerId: string;
|
|
52
56
|
stripePublishableKey?: string;
|
|
53
57
|
createClientReferenceId?: (params: CreateOrderParams | CreateOnrampOrderParams) => Promise<string>;
|
|
58
|
+
authStrategy?: AuthStrategy;
|
|
54
59
|
}) {
|
|
55
60
|
return (
|
|
56
61
|
<B3ConfigContext.Provider
|
|
@@ -64,6 +69,7 @@ export function B3ConfigProvider({
|
|
|
64
69
|
partnerId,
|
|
65
70
|
stripePublishableKey,
|
|
66
71
|
createClientReferenceId,
|
|
72
|
+
authStrategy,
|
|
67
73
|
}}
|
|
68
74
|
>
|
|
69
75
|
{children}
|
|
@@ -3,6 +3,7 @@ import { CreateOrderParams } from "@b3dotfun/sdk/anyspend/react/hooks/useAnyspen
|
|
|
3
3
|
import { RelayKitProviderWrapper, TooltipProvider } from "@b3dotfun/sdk/global-account/react";
|
|
4
4
|
import { createWagmiConfig } from "@b3dotfun/sdk/global-account/react/utils/createWagmiConfig";
|
|
5
5
|
import { PermissionsConfig } from "@b3dotfun/sdk/global-account/types/permissions";
|
|
6
|
+
import type { AuthStrategy } from "./B3ConfigProvider";
|
|
6
7
|
import { loadGA4Script } from "@b3dotfun/sdk/global-account/utils/analytics";
|
|
7
8
|
import { WalletProvider } from "@b3dotfun/sdk/wallet/react";
|
|
8
9
|
import "@relayprotocol/relay-kit-ui/styles.css";
|
|
@@ -15,6 +16,7 @@ import { StyleRoot } from "../StyleRoot";
|
|
|
15
16
|
import { setToastContext, ToastProvider, useToastContext } from "../Toast/index";
|
|
16
17
|
import AuthenticationProvider from "./AuthenticationProvider";
|
|
17
18
|
import { B3ConfigProvider } from "./B3ConfigProvider";
|
|
19
|
+
import BetterAuthProvider from "./BetterAuthProvider";
|
|
18
20
|
import { LocalSDKProvider } from "./LocalSDKProvider";
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -42,6 +44,7 @@ export function B3Provider({
|
|
|
42
44
|
defaultPermissions,
|
|
43
45
|
disableBSMNTAuthentication = false,
|
|
44
46
|
queryClient,
|
|
47
|
+
authStrategy = "thirdweb",
|
|
45
48
|
}: {
|
|
46
49
|
theme: "light" | "dark";
|
|
47
50
|
children: React.ReactNode;
|
|
@@ -69,6 +72,8 @@ export function B3Provider({
|
|
|
69
72
|
disableBSMNTAuthentication?: boolean;
|
|
70
73
|
/** Provide your own QueryClient for React Query. If omitted, WalletProvider creates one internally. */
|
|
71
74
|
queryClient?: QueryClient;
|
|
75
|
+
/** Auth strategy: "thirdweb" (default, ecosystem wallet) or "better-auth" (email/password via Better Auth) */
|
|
76
|
+
authStrategy?: AuthStrategy;
|
|
72
77
|
}) {
|
|
73
78
|
// Initialize Google Analytics on mount
|
|
74
79
|
useEffect(() => {
|
|
@@ -104,6 +109,7 @@ export function B3Provider({
|
|
|
104
109
|
stripePublishableKey={stripePublishableKey}
|
|
105
110
|
createClientReferenceId={createClientReferenceId}
|
|
106
111
|
defaultPermissions={defaultPermissions}
|
|
112
|
+
authStrategy={authStrategy}
|
|
107
113
|
>
|
|
108
114
|
<ToastContextConnector />
|
|
109
115
|
<RelayKitProviderWrapper simDuneApiKey={simDuneApiKey}>
|
|
@@ -111,11 +117,15 @@ export function B3Provider({
|
|
|
111
117
|
{/* For the modal https://github.com/b3-fun/b3/blob/main/packages/sdk/src/global-account/react/components/ui/dialog.tsx#L46 */}
|
|
112
118
|
<StyleRoot id="b3-root" />
|
|
113
119
|
</RelayKitProviderWrapper>
|
|
114
|
-
|
|
115
|
-
partnerId={partnerId}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
{authStrategy === "better-auth" ? (
|
|
121
|
+
<BetterAuthProvider partnerId={partnerId} />
|
|
122
|
+
) : (
|
|
123
|
+
<AuthenticationProvider
|
|
124
|
+
partnerId={partnerId}
|
|
125
|
+
automaticallySetFirstEoa={!!automaticallySetFirstEoa}
|
|
126
|
+
defaultEoaProvider={defaultEoaProvider}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
119
129
|
</B3ConfigProvider>
|
|
120
130
|
</LocalSDKProvider>
|
|
121
131
|
</ToastProvider>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import app from "@b3dotfun/sdk/global-account/app";
|
|
2
|
+
import { useAuthStore } from "@b3dotfun/sdk/global-account/react";
|
|
3
|
+
import { B3_AUTH_COOKIE_NAME } from "@b3dotfun/sdk/shared/constants";
|
|
4
|
+
import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
5
|
+
import Cookies from "js-cookie";
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
7
|
+
import { betterAuthClient } from "../../../better-auth-client";
|
|
8
|
+
import { useUserQuery } from "../../hooks/useUserQuery";
|
|
9
|
+
|
|
10
|
+
const debug = debugB3React("BetterAuthProvider");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parallel to AuthenticationProvider for Better Auth strategy.
|
|
14
|
+
*
|
|
15
|
+
* Manages the isAuthenticating lifecycle for Better Auth:
|
|
16
|
+
* 1. On mount, try to restore an existing Feathers JWT (from a previous login)
|
|
17
|
+
* 2. If no Feathers JWT, check for a Better Auth session (e.g. after OAuth redirect)
|
|
18
|
+
* and exchange it for a Feathers JWT
|
|
19
|
+
* 3. If neither exists, set isAuthenticating: false so the login UI renders
|
|
20
|
+
*
|
|
21
|
+
* Also patches app.logout() so any code path that calls it (useAuthentication,
|
|
22
|
+
* useAuth, SignIn component, etc.) automatically clears the Better Auth session.
|
|
23
|
+
*/
|
|
24
|
+
const BetterAuthProvider = ({ partnerId }: { partnerId: string }) => {
|
|
25
|
+
const setIsAuthenticated = useAuthStore(state => state.setIsAuthenticated);
|
|
26
|
+
const setIsAuthenticating = useAuthStore(state => state.setIsAuthenticating);
|
|
27
|
+
const setIsConnected = useAuthStore(state => state.setIsConnected);
|
|
28
|
+
const { setUser } = useUserQuery();
|
|
29
|
+
const hasAttemptedRestore = useRef(false);
|
|
30
|
+
const hasPatched = useRef(false);
|
|
31
|
+
|
|
32
|
+
// Patch app.logout() to also clear the Better Auth session.
|
|
33
|
+
// This ensures any existing logout path (useAuthentication, useAuth, SignIn
|
|
34
|
+
// dropdown, etc.) clears both the Feathers JWT and the Better Auth session.
|
|
35
|
+
// Patch app.logout() to also clear the Better Auth session.
|
|
36
|
+
// Only handles Better Auth signOut — state cleanup (isAuthenticated, isConnected,
|
|
37
|
+
// setUser, localStorage) is handled by useAuthentication/useAuth's own logout.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (hasPatched.current) return;
|
|
40
|
+
hasPatched.current = true;
|
|
41
|
+
|
|
42
|
+
const originalLogout = app.logout.bind(app);
|
|
43
|
+
(app as any).logout = async () => {
|
|
44
|
+
debug("Patched logout: clearing Better Auth session");
|
|
45
|
+
try {
|
|
46
|
+
await betterAuthClient.signOut();
|
|
47
|
+
} catch {
|
|
48
|
+
debug("Better Auth signOut failed (non-critical)");
|
|
49
|
+
}
|
|
50
|
+
return originalLogout();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
(app as any).logout = originalLogout;
|
|
55
|
+
hasPatched.current = false;
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
// Session restore on mount
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (hasAttemptedRestore.current) return;
|
|
62
|
+
hasAttemptedRestore.current = true;
|
|
63
|
+
|
|
64
|
+
const restoreSession = async () => {
|
|
65
|
+
debug("Attempting session restore");
|
|
66
|
+
|
|
67
|
+
// 1. Try existing Feathers JWT first (fastest — no network call to Better Auth)
|
|
68
|
+
try {
|
|
69
|
+
const response = await app.reAuthenticate();
|
|
70
|
+
if (response?.user) {
|
|
71
|
+
debug("Feathers JWT restored", { userId: response.user.userId });
|
|
72
|
+
setUser(response.user);
|
|
73
|
+
setIsAuthenticated(true);
|
|
74
|
+
setIsConnected(true);
|
|
75
|
+
setIsAuthenticating(false);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
debug("No existing Feathers JWT");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Check for a Better Auth session (e.g. after OAuth redirect sets a cookie)
|
|
83
|
+
try {
|
|
84
|
+
const session = await betterAuthClient.getSession();
|
|
85
|
+
if (session.data?.session?.token) {
|
|
86
|
+
debug("Better Auth session found, exchanging for Feathers JWT", {
|
|
87
|
+
betterAuthUserId: session.data.user?.id,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const response = await app.authenticate({
|
|
91
|
+
strategy: "better-auth",
|
|
92
|
+
accessToken: session.data.session.token,
|
|
93
|
+
partnerId,
|
|
94
|
+
} as any);
|
|
95
|
+
|
|
96
|
+
if (response.accessToken) {
|
|
97
|
+
Cookies.set(B3_AUTH_COOKIE_NAME, response.accessToken, {
|
|
98
|
+
secure: true,
|
|
99
|
+
sameSite: "Lax",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (response.user) {
|
|
104
|
+
setUser(response.user);
|
|
105
|
+
setIsAuthenticated(true);
|
|
106
|
+
setIsConnected(true);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
debug("OAuth session exchanged for Feathers JWT", { userId: response.user?.userId });
|
|
110
|
+
setIsAuthenticating(false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
debug("No Better Auth session to restore");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. Nothing found — show login UI
|
|
118
|
+
setIsAuthenticating(false);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
restoreSession();
|
|
122
|
+
}, [setIsAuthenticated, setIsAuthenticating, setIsConnected, setUser, partnerId]);
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default BetterAuthProvider;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Button, Input } from "@b3dotfun/sdk/global-account/react";
|
|
2
|
+
import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useBetterAuth } from "../../hooks/useBetterAuth";
|
|
5
|
+
|
|
6
|
+
const debug = debugB3React("BetterAuthResetPassword");
|
|
7
|
+
|
|
8
|
+
export interface BetterAuthResetPasswordProps {
|
|
9
|
+
/** The reset token from the URL query param */
|
|
10
|
+
token: string;
|
|
11
|
+
/** Called after password is successfully reset */
|
|
12
|
+
onSuccess?: () => void;
|
|
13
|
+
/** Called on error */
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
/** Optional class name */
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Standalone reset password form. Render this on your reset password page.
|
|
21
|
+
* Reads the token from props (extract it from the URL query string).
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```tsx
|
|
25
|
+
* const token = new URLSearchParams(window.location.search).get("token");
|
|
26
|
+
* <BetterAuthResetPassword token={token} onSuccess={() => navigate("/login")} />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function BetterAuthResetPassword({ token, onSuccess, onError, className }: BetterAuthResetPasswordProps) {
|
|
30
|
+
const { resetPassword } = useBetterAuth();
|
|
31
|
+
|
|
32
|
+
const [password, setPassword] = useState("");
|
|
33
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
34
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
35
|
+
const [error, setError] = useState<string | null>(null);
|
|
36
|
+
const [success, setSuccess] = useState(false);
|
|
37
|
+
|
|
38
|
+
const handleSubmit = async () => {
|
|
39
|
+
if (!password) {
|
|
40
|
+
setError("Please enter a new password");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (password.length < 8) {
|
|
45
|
+
setError("Password must be at least 8 characters");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (password !== confirmPassword) {
|
|
50
|
+
setError("Passwords do not match");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
setIsLoading(true);
|
|
56
|
+
setError(null);
|
|
57
|
+
await resetPassword(password, token);
|
|
58
|
+
setSuccess(true);
|
|
59
|
+
debug("Password reset successful");
|
|
60
|
+
onSuccess?.();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : "Password reset failed";
|
|
63
|
+
setError(message);
|
|
64
|
+
debug("Password reset failed", err);
|
|
65
|
+
onError?.(err as Error);
|
|
66
|
+
} finally {
|
|
67
|
+
setPassword("");
|
|
68
|
+
setConfirmPassword("");
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!token) {
|
|
74
|
+
return (
|
|
75
|
+
<div className={`w-full max-w-[400px] px-6 text-center ${className || ""}`}>
|
|
76
|
+
<p className="text-sm text-red-500">Invalid or missing reset token.</p>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className={`w-full max-w-[400px] px-6 ${className || ""}`}>
|
|
83
|
+
<div className="mb-10 text-center">
|
|
84
|
+
<h1 className="text-[28px] font-semibold tracking-tight text-gray-900 dark:text-gray-100">
|
|
85
|
+
{success ? "Password reset" : "Set new password"}
|
|
86
|
+
</h1>
|
|
87
|
+
<p className="mt-3 text-[15px] text-gray-500 dark:text-gray-400">
|
|
88
|
+
{success ? "Your password has been updated." : "Enter your new password below."}
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{success ? (
|
|
93
|
+
<div className="space-y-4 text-center">
|
|
94
|
+
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
|
|
95
|
+
<svg className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
96
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
97
|
+
</svg>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
) : (
|
|
101
|
+
<div className="space-y-5">
|
|
102
|
+
<div>
|
|
103
|
+
<label className="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-700 dark:text-gray-300">
|
|
104
|
+
New password
|
|
105
|
+
</label>
|
|
106
|
+
<Input
|
|
107
|
+
type="password"
|
|
108
|
+
placeholder="At least 8 characters"
|
|
109
|
+
value={password}
|
|
110
|
+
onChange={e => setPassword(e.target.value)}
|
|
111
|
+
disabled={isLoading}
|
|
112
|
+
className="h-11 px-4 text-[15px]"
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div>
|
|
117
|
+
<label className="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-700 dark:text-gray-300">
|
|
118
|
+
Confirm password
|
|
119
|
+
</label>
|
|
120
|
+
<Input
|
|
121
|
+
type="password"
|
|
122
|
+
placeholder="Repeat your password"
|
|
123
|
+
value={confirmPassword}
|
|
124
|
+
onChange={e => setConfirmPassword(e.target.value)}
|
|
125
|
+
disabled={isLoading}
|
|
126
|
+
onKeyDown={e => {
|
|
127
|
+
if (e.key === "Enter") handleSubmit();
|
|
128
|
+
}}
|
|
129
|
+
className="h-11 px-4 text-[15px]"
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{error && <p className="text-sm text-red-500">{error}</p>}
|
|
134
|
+
|
|
135
|
+
<Button
|
|
136
|
+
onClick={handleSubmit}
|
|
137
|
+
disabled={isLoading}
|
|
138
|
+
className="h-11 w-full bg-gray-900 text-[15px] font-medium text-white hover:bg-gray-800 dark:bg-white dark:text-gray-900 dark:hover:bg-gray-100"
|
|
139
|
+
>
|
|
140
|
+
{isLoading ? "Resetting..." : "Reset password"}
|
|
141
|
+
</Button>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|