@b3dotfun/sdk 0.0.81 → 0.0.82-alpha.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/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +56 -173
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +1 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.js +38 -36
- package/dist/cjs/global-account/react/hooks/useUserQuery.d.ts +2 -2
- package/dist/cjs/global-account/react/hooks/useUserQuery.js +76 -55
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +55 -172
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +1 -1
- package/dist/esm/global-account/react/hooks/useAuthentication.js +38 -36
- package/dist/esm/global-account/react/hooks/useUserQuery.d.ts +2 -2
- package/dist/esm/global-account/react/hooks/useUserQuery.js +76 -55
- package/dist/styles/index.css +1 -1
- package/dist/types/global-account/react/hooks/useUserQuery.d.ts +2 -2
- package/package.json +1 -1
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +14 -199
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +1 -1
- package/src/global-account/react/hooks/useAuthentication.ts +47 -46
- package/src/global-account/react/hooks/useUserQuery.ts +82 -54
|
@@ -4,13 +4,9 @@ import { toast, useAccountWallet, WalletImage } from "@b3dotfun/sdk/global-accou
|
|
|
4
4
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
5
5
|
import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
|
|
6
6
|
import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
|
|
7
|
-
import { WalletCoinbase, WalletMetamask, WalletRainbow, WalletWalletConnect } from "@web3icons/react";
|
|
8
7
|
import { ChevronLeft, ChevronRightCircle, Wallet, X, ZapIcon } from "lucide-react";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { useSetActiveWallet, useWalletInfo } from "thirdweb/react";
|
|
12
|
-
import { createWallet, WalletId } from "thirdweb/wallets";
|
|
13
|
-
import { useAccount, useConnect, useDisconnect, useWalletClient } from "wagmi";
|
|
8
|
+
import { useConnectModal, useSetActiveWallet, useWalletInfo } from "thirdweb/react";
|
|
9
|
+
import { useAccount, useDisconnect } from "wagmi";
|
|
14
10
|
import { useConnectedWalletDisplay } from "../../hooks/useConnectedWalletDisplay";
|
|
15
11
|
|
|
16
12
|
export enum CryptoPaymentMethodType {
|
|
@@ -37,126 +33,28 @@ export function CryptoPaymentMethod({
|
|
|
37
33
|
}: CryptoPaymentMethodProps) {
|
|
38
34
|
const { connectedEOAWallet: connectedEOAWallet, connectedSmartWallet: connectedSmartWallet } = useAccountWallet();
|
|
39
35
|
const { connector, address } = useAccount();
|
|
40
|
-
const { connect, connectors, isPending } = useConnect();
|
|
41
36
|
const { disconnect } = useDisconnect();
|
|
42
|
-
const {
|
|
43
|
-
const [showWalletModal, setShowWalletModal] = useState(false);
|
|
37
|
+
const { connect: openConnectModal } = useConnectModal();
|
|
44
38
|
const setActiveWallet = useSetActiveWallet();
|
|
45
39
|
const { data: eoaWalletInfo } = useWalletInfo(connectedEOAWallet?.id);
|
|
46
40
|
|
|
47
|
-
const isConnected = !!connectedEOAWallet;
|
|
48
41
|
const globalAddress = connectedSmartWallet?.getAccount()?.address;
|
|
49
42
|
|
|
50
43
|
// Use custom hook to determine wallet display logic
|
|
51
44
|
const { shouldShowConnectedEOA, shouldShowWagmiWallet } = useConnectedWalletDisplay(selectedPaymentMethod);
|
|
52
|
-
console.log("shouldShowWagmiWallet :", shouldShowWagmiWallet);
|
|
53
45
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
56
|
-
const walletMap: Record<string, WalletId> = {
|
|
57
|
-
MetaMask: "io.metamask",
|
|
58
|
-
"Coinbase Wallet": "com.coinbase.wallet",
|
|
59
|
-
Rainbow: "me.rainbow",
|
|
60
|
-
WalletConnect: "walletConnect",
|
|
61
|
-
Phantom: "app.phantom",
|
|
62
|
-
};
|
|
63
|
-
return walletMap[connectorName] || null;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Create thirdweb wallet from wagmi connector
|
|
67
|
-
const createThirdwebWalletFromConnector = async (connectorName: string) => {
|
|
68
|
-
const walletId = getThirdwebWalletId(connectorName);
|
|
69
|
-
if (!walletId) {
|
|
70
|
-
console.warn(`No thirdweb wallet ID found for connector: ${connectorName}`);
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const thirdwebWallet = createWallet(walletId);
|
|
76
|
-
// Connect the wallet to sync with the existing wagmi connection
|
|
77
|
-
await thirdwebWallet.connect({ client });
|
|
78
|
-
return thirdwebWallet;
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error(`Failed to create thirdweb wallet for ${connectorName}:`, error);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Define available wallet connectors
|
|
86
|
-
const availableConnectors = connectors.filter(connector =>
|
|
87
|
-
["MetaMask", "WalletConnect", "Coinbase Wallet", "Rainbow", "Phantom"].includes(connector.name),
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// Define wallet options with icons and info
|
|
91
|
-
const walletOptions = [
|
|
92
|
-
{
|
|
93
|
-
id: "metamask",
|
|
94
|
-
name: "MetaMask",
|
|
95
|
-
icon: <WalletMetamask size={48} />,
|
|
96
|
-
description: "Connect using MetaMask browser extension",
|
|
97
|
-
connector: availableConnectors.find(c => c.name === "MetaMask"),
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
id: "coinbase",
|
|
101
|
-
name: "Coinbase Wallet",
|
|
102
|
-
icon: <WalletCoinbase size={48} />,
|
|
103
|
-
description: "Connect using Coinbase Wallet",
|
|
104
|
-
connector: availableConnectors.find(c => c.name === "Coinbase Wallet"),
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: "rainbow",
|
|
108
|
-
name: "Rainbow",
|
|
109
|
-
icon: <WalletRainbow size={48} />,
|
|
110
|
-
description: "Connect using Rainbow wallet",
|
|
111
|
-
connector: availableConnectors.find(c => c.name === "Rainbow"),
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: "walletconnect",
|
|
115
|
-
name: "WalletConnect",
|
|
116
|
-
icon: <WalletWalletConnect size={48} />,
|
|
117
|
-
description: "Connect using WalletConnect protocol",
|
|
118
|
-
connector: availableConnectors.find(c => c.name === "WalletConnect"),
|
|
119
|
-
},
|
|
120
|
-
].filter(wallet => wallet.connector); // Only show wallets that have available connectors
|
|
121
|
-
|
|
122
|
-
// Reset modal state when closing
|
|
123
|
-
const handleCloseModal = () => {
|
|
124
|
-
setShowWalletModal(false);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// Function to request wallet permissions for specific wallet
|
|
128
|
-
const requestWalletPermissions = async (walletConnector?: any) => {
|
|
46
|
+
// Handle wallet connection using thirdweb modal
|
|
47
|
+
const handleConnectWallet = async () => {
|
|
129
48
|
try {
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
//
|
|
133
|
-
// if (isConnected) {
|
|
134
|
-
// disconnect();
|
|
135
|
-
// // Small delay to ensure disconnection
|
|
136
|
-
// await new Promise(resolve => setTimeout(resolve, 100));
|
|
137
|
-
// }
|
|
138
|
-
await connect({ connector: walletConnector });
|
|
139
|
-
setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
140
|
-
onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
141
|
-
toast.success(`Connected to ${walletConnector.name}`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// If same wallet or no specific wallet, request permissions for account switching
|
|
146
|
-
if (walletClient && "request" in walletClient) {
|
|
147
|
-
await walletClient.request({
|
|
148
|
-
method: "wallet_requestPermissions",
|
|
149
|
-
params: [{ eth_accounts: {} }],
|
|
150
|
-
});
|
|
151
|
-
toast.success("Account selection completed");
|
|
49
|
+
const wallet = await openConnectModal({ client, setActive: false });
|
|
50
|
+
if (wallet) {
|
|
51
|
+
// setActiveWallet(wallet);
|
|
152
52
|
setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
153
53
|
onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
154
|
-
|
|
155
|
-
// Fallback: show modal for manual wallet selection
|
|
156
|
-
setShowWalletModal(true);
|
|
54
|
+
toast.success("Wallet connected");
|
|
157
55
|
}
|
|
158
56
|
} catch (error) {
|
|
159
|
-
console.error("Failed to
|
|
57
|
+
console.error("Failed to connect wallet:", error);
|
|
160
58
|
if (error && typeof error === "object" && "message" in error) {
|
|
161
59
|
const errorMessage = (error as any).message.toLowerCase();
|
|
162
60
|
if (
|
|
@@ -164,12 +62,10 @@ export function CryptoPaymentMethod({
|
|
|
164
62
|
errorMessage.includes("denied") ||
|
|
165
63
|
errorMessage.includes("cancelled")
|
|
166
64
|
) {
|
|
167
|
-
|
|
65
|
+
// User cancelled - no toast needed
|
|
168
66
|
} else {
|
|
169
|
-
toast.error("Failed to
|
|
67
|
+
toast.error("Failed to connect wallet");
|
|
170
68
|
}
|
|
171
|
-
} else {
|
|
172
|
-
toast.error("Failed to open account selection");
|
|
173
69
|
}
|
|
174
70
|
}
|
|
175
71
|
};
|
|
@@ -297,18 +193,9 @@ export function CryptoPaymentMethod({
|
|
|
297
193
|
|
|
298
194
|
{shouldShowWagmiWallet && (
|
|
299
195
|
<button
|
|
300
|
-
onClick={
|
|
196
|
+
onClick={() => {
|
|
301
197
|
setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
302
198
|
onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
303
|
-
|
|
304
|
-
// Create thirdweb wallet from wagmi connector
|
|
305
|
-
if (connector?.name) {
|
|
306
|
-
const thirdwebWallet = await createThirdwebWalletFromConnector(connector.name);
|
|
307
|
-
if (thirdwebWallet) {
|
|
308
|
-
setActiveWallet(thirdwebWallet);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
199
|
toast.success(`Selected ${connector?.name || "wallet"}`);
|
|
313
200
|
}}
|
|
314
201
|
className={cn(
|
|
@@ -398,10 +285,7 @@ export function CryptoPaymentMethod({
|
|
|
398
285
|
<div className="space-y-3">
|
|
399
286
|
{/* Connect Wallet Section */}
|
|
400
287
|
<button
|
|
401
|
-
onClick={
|
|
402
|
-
// Always show wallet selection modal first
|
|
403
|
-
setShowWalletModal(true);
|
|
404
|
-
}}
|
|
288
|
+
onClick={handleConnectWallet}
|
|
405
289
|
className="crypto-payment-method-connect-wallet bg-as-surface-primary border-as-border-secondary hover:border-as-secondary/80 group flex w-full items-center justify-between gap-4 rounded-xl border px-4 py-3.5 transition-all duration-200 hover:shadow-md"
|
|
406
290
|
>
|
|
407
291
|
<div className="flex items-center gap-3">
|
|
@@ -438,75 +322,6 @@ export function CryptoPaymentMethod({
|
|
|
438
322
|
</div>
|
|
439
323
|
</div>
|
|
440
324
|
</div>
|
|
441
|
-
|
|
442
|
-
{/* Wallet Connection Modal */}
|
|
443
|
-
{showWalletModal &&
|
|
444
|
-
createPortal(
|
|
445
|
-
<div className="wallet-connection-modal pointer-events-auto fixed inset-0 z-[9999] flex items-center justify-center bg-black/50">
|
|
446
|
-
<div className="max-h-[80vh] w-[400px] max-w-[90vw] overflow-auto rounded-xl bg-white p-6 dark:bg-gray-900">
|
|
447
|
-
<div className="mb-4 flex items-center justify-between">
|
|
448
|
-
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
|
449
|
-
{isConnected ? "Switch wallet or account" : "Choose wallet to connect"}
|
|
450
|
-
</h3>
|
|
451
|
-
<button
|
|
452
|
-
onClick={handleCloseModal}
|
|
453
|
-
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
454
|
-
>
|
|
455
|
-
<X className="h-5 w-5" />
|
|
456
|
-
</button>
|
|
457
|
-
</div>
|
|
458
|
-
|
|
459
|
-
<div className="space-y-4">
|
|
460
|
-
{/* Custom wallet options */}
|
|
461
|
-
<div className="space-y-3">
|
|
462
|
-
{walletOptions.map(walletOption => {
|
|
463
|
-
const isCurrentWallet = isConnected && connector?.name === walletOption.connector?.name;
|
|
464
|
-
|
|
465
|
-
return (
|
|
466
|
-
<button
|
|
467
|
-
key={walletOption.id}
|
|
468
|
-
onClick={async () => {
|
|
469
|
-
handleCloseModal();
|
|
470
|
-
await requestWalletPermissions(walletOption.connector);
|
|
471
|
-
}}
|
|
472
|
-
disabled={isPending}
|
|
473
|
-
className={`wallet-option w-full rounded-xl border p-4 text-left transition-all hover:shadow-md disabled:opacity-50 ${
|
|
474
|
-
isCurrentWallet
|
|
475
|
-
? "wallet-option--active border-blue-500 bg-blue-50 dark:bg-blue-900/20"
|
|
476
|
-
: "border-gray-200 bg-white hover:border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:hover:border-gray-500"
|
|
477
|
-
}`}
|
|
478
|
-
>
|
|
479
|
-
<div className="flex items-center justify-between">
|
|
480
|
-
<div className="flex items-center gap-3">
|
|
481
|
-
{walletOption.icon}
|
|
482
|
-
|
|
483
|
-
<div>
|
|
484
|
-
<div className="wallet-option-name flex items-center gap-2">
|
|
485
|
-
<div className="text-sm font-semibold text-gray-900 dark:text-white">
|
|
486
|
-
{walletOption.name}
|
|
487
|
-
</div>
|
|
488
|
-
{isCurrentWallet && (
|
|
489
|
-
<span className="rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-800 dark:text-blue-200">
|
|
490
|
-
Connected
|
|
491
|
-
</span>
|
|
492
|
-
)}
|
|
493
|
-
</div>
|
|
494
|
-
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
495
|
-
{isCurrentWallet ? "Switch account or reconnect" : walletOption.description}
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
<ChevronRightCircle className="h-5 w-5 text-gray-400" />
|
|
500
|
-
</div>
|
|
501
|
-
</button>
|
|
502
|
-
);
|
|
503
|
-
})}
|
|
504
|
-
</div>
|
|
505
|
-
</div>
|
|
506
|
-
</div>
|
|
507
|
-
</div>,
|
|
508
|
-
typeof window !== "undefined" ? document.getElementById("b3-root") || document.body : document.body,
|
|
509
|
-
)}
|
|
510
325
|
</div>
|
|
511
326
|
);
|
|
512
327
|
}
|
|
@@ -198,7 +198,7 @@ export function SignInWithB3Flow({
|
|
|
198
198
|
source,
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
-
if (isConnected && isAuthenticated) {
|
|
201
|
+
if (isConnected && isAuthenticated && user) {
|
|
202
202
|
// Mark that login just completed BEFORE opening manage account or closing modal
|
|
203
203
|
// This allows Turnkey modal to show (if enableTurnkey is true)
|
|
204
204
|
if (closeAfterLogin) {
|
|
@@ -151,19 +151,51 @@ export function useAuthentication(partnerId: string) {
|
|
|
151
151
|
[activeWallet, partnerId, authenticate, setIsAuthenticated, setIsAuthenticating, setUser, setHasStartedConnecting],
|
|
152
152
|
);
|
|
153
153
|
|
|
154
|
-
const
|
|
155
|
-
async (
|
|
156
|
-
|
|
154
|
+
const logout = useCallback(
|
|
155
|
+
async (callback?: () => void) => {
|
|
156
|
+
if (activeWallet) {
|
|
157
|
+
debug("@@logout:activeWallet", activeWallet);
|
|
158
|
+
disconnect(activeWallet);
|
|
159
|
+
debug("@@logout:activeWallet", activeWallet);
|
|
160
|
+
}
|
|
157
161
|
|
|
158
|
-
|
|
162
|
+
// Log out of each wallet
|
|
163
|
+
wallets.forEach(wallet => {
|
|
164
|
+
console.log("@@logging out", wallet);
|
|
165
|
+
disconnect(wallet);
|
|
166
|
+
});
|
|
159
167
|
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
// Delete localStorage thirdweb:connected-wallet-ids
|
|
169
|
+
// https://npc-labs.slack.com/archives/C070E6HNG85/p1750185115273099
|
|
170
|
+
if (typeof localStorage !== "undefined") {
|
|
171
|
+
localStorage.removeItem("thirdweb:connected-wallet-ids");
|
|
172
|
+
localStorage.removeItem("wagmi.store");
|
|
173
|
+
localStorage.removeItem("lastAuthProvider");
|
|
174
|
+
localStorage.removeItem("b3-user");
|
|
162
175
|
}
|
|
163
176
|
|
|
164
|
-
|
|
177
|
+
app.logout();
|
|
178
|
+
debug("@@logout:loggedOut");
|
|
165
179
|
|
|
180
|
+
setIsAuthenticated(false);
|
|
181
|
+
setIsConnected(false);
|
|
182
|
+
setUser();
|
|
183
|
+
callback?.();
|
|
184
|
+
},
|
|
185
|
+
[activeWallet, disconnect, wallets, setIsAuthenticated, setUser, setIsConnected],
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const onConnect = useCallback(
|
|
189
|
+
async (_walleAutoConnectedWith: Wallet, allConnectedWallets: Wallet[]) => {
|
|
190
|
+
debug("@@useAuthentication:onConnect", { _walleAutoConnectedWith, allConnectedWallets });
|
|
166
191
|
try {
|
|
192
|
+
const wallet = allConnectedWallets.find(wallet => wallet.id.startsWith("ecosystem."));
|
|
193
|
+
|
|
194
|
+
if (!wallet) {
|
|
195
|
+
throw new Error("No smart wallet found during auto-connect");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
debug("@@useAuthentication:onConnect", { wallet });
|
|
167
199
|
setHasStartedConnecting(true);
|
|
168
200
|
setIsConnected(true);
|
|
169
201
|
setIsAuthenticating(true);
|
|
@@ -177,6 +209,8 @@ export function useAuthentication(partnerId: string) {
|
|
|
177
209
|
debug("@@useAuthentication:onConnect:failed", { error });
|
|
178
210
|
setIsAuthenticated(false);
|
|
179
211
|
setUser(undefined);
|
|
212
|
+
|
|
213
|
+
await logout();
|
|
180
214
|
} finally {
|
|
181
215
|
setIsAuthenticating(false);
|
|
182
216
|
}
|
|
@@ -188,54 +222,21 @@ export function useAuthentication(partnerId: string) {
|
|
|
188
222
|
});
|
|
189
223
|
},
|
|
190
224
|
[
|
|
191
|
-
onConnectCallback,
|
|
192
|
-
authenticateUser,
|
|
193
225
|
isAuthenticated,
|
|
194
226
|
isAuthenticating,
|
|
195
227
|
isConnected,
|
|
196
|
-
setActiveWallet,
|
|
197
228
|
setHasStartedConnecting,
|
|
198
|
-
setIsAuthenticated,
|
|
199
|
-
setIsAuthenticating,
|
|
200
229
|
setIsConnected,
|
|
230
|
+
setIsAuthenticating,
|
|
231
|
+
setActiveWallet,
|
|
232
|
+
authenticateUser,
|
|
233
|
+
onConnectCallback,
|
|
234
|
+
setIsAuthenticated,
|
|
201
235
|
setUser,
|
|
236
|
+
logout,
|
|
202
237
|
],
|
|
203
238
|
);
|
|
204
239
|
|
|
205
|
-
const logout = useCallback(
|
|
206
|
-
async (callback?: () => void) => {
|
|
207
|
-
if (activeWallet) {
|
|
208
|
-
debug("@@logout:activeWallet", activeWallet);
|
|
209
|
-
disconnect(activeWallet);
|
|
210
|
-
debug("@@logout:activeWallet", activeWallet);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Log out of each wallet
|
|
214
|
-
wallets.forEach(wallet => {
|
|
215
|
-
console.log("@@logging out", wallet);
|
|
216
|
-
disconnect(wallet);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// Delete localStorage thirdweb:connected-wallet-ids
|
|
220
|
-
// https://npc-labs.slack.com/archives/C070E6HNG85/p1750185115273099
|
|
221
|
-
if (typeof localStorage !== "undefined") {
|
|
222
|
-
localStorage.removeItem("thirdweb:connected-wallet-ids");
|
|
223
|
-
localStorage.removeItem("wagmi.store");
|
|
224
|
-
localStorage.removeItem("lastAuthProvider");
|
|
225
|
-
localStorage.removeItem("b3-user");
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
app.logout();
|
|
229
|
-
debug("@@logout:loggedOut");
|
|
230
|
-
|
|
231
|
-
setIsAuthenticated(false);
|
|
232
|
-
setIsConnected(false);
|
|
233
|
-
setUser();
|
|
234
|
-
callback?.();
|
|
235
|
-
},
|
|
236
|
-
[activeWallet, disconnect, wallets, setIsAuthenticated, setUser, setIsConnected],
|
|
237
|
-
);
|
|
238
|
-
|
|
239
240
|
const { isLoading: useAutoConnectLoading } = useAutoConnect({
|
|
240
241
|
client,
|
|
241
242
|
wallets: [wallet],
|
|
@@ -1,88 +1,116 @@
|
|
|
1
1
|
import { Users } from "@b3dotfun/b3-api";
|
|
2
2
|
import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { create } from "zustand";
|
|
5
|
+
import { persist } from "zustand/middleware";
|
|
4
6
|
|
|
5
7
|
const debug = debugB3React("useUserQuery");
|
|
6
8
|
|
|
7
9
|
const USER_QUERY_KEY = ["b3-user"];
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (typeof window === "undefined") {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const storedUser = localStorage.getItem("b3-user");
|
|
19
|
-
return storedUser ? JSON.parse(storedUser) : null;
|
|
20
|
-
} catch (error) {
|
|
21
|
-
console.warn("Failed to restore user from localStorage:", error);
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
11
|
+
interface UserStore {
|
|
12
|
+
user: Users | null;
|
|
13
|
+
setUser: (user: Users | undefined) => void;
|
|
14
|
+
clearUser: () => void;
|
|
24
15
|
}
|
|
25
16
|
|
|
26
17
|
/**
|
|
27
|
-
*
|
|
18
|
+
* Zustand store for managing user state
|
|
19
|
+
* Persists user data to localStorage
|
|
28
20
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
21
|
+
const useUserStore = create<UserStore>()(
|
|
22
|
+
persist(
|
|
23
|
+
set => ({
|
|
24
|
+
user: null,
|
|
25
|
+
setUser: (newUser: Users | undefined) => {
|
|
26
|
+
const userToSave = newUser ?? null;
|
|
27
|
+
set({ user: userToSave });
|
|
28
|
+
debug("User updated", userToSave);
|
|
29
|
+
},
|
|
30
|
+
clearUser: () => {
|
|
31
|
+
set({ user: null });
|
|
32
|
+
debug("User cleared");
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
{
|
|
36
|
+
name: "b3-user",
|
|
37
|
+
onRehydrateStorage: () => (_, error) => {
|
|
38
|
+
if (error) {
|
|
39
|
+
console.warn("Failed to rehydrate user store:", error);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
),
|
|
44
|
+
);
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
47
|
* NOTE: THIS IS ONLY MEANT FOR INTERNAL USE, from useOnConnect
|
|
43
48
|
*
|
|
44
|
-
* Custom hook to manage user state with
|
|
49
|
+
* Custom hook to manage user state with Zustand
|
|
45
50
|
* This allows for invalidation and refetching of user data
|
|
46
51
|
*/
|
|
47
52
|
export function useUserQuery() {
|
|
48
|
-
const
|
|
53
|
+
const user = useUserStore(state => state.user);
|
|
54
|
+
const setUserStore = useUserStore(state => state.setUser);
|
|
55
|
+
const clearUserStore = useUserStore(state => state.clearUser);
|
|
49
56
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
// Listen for storage events from other tabs/windows
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const handleStorageChange = (e: StorageEvent) => {
|
|
60
|
+
if (e.key === "b3-user") {
|
|
61
|
+
// Sync with changes from other tabs/windows
|
|
62
|
+
const stored = e.newValue;
|
|
63
|
+
if (stored) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(stored);
|
|
66
|
+
// Zustand persist format: { state: { user: ... }, version: ... }
|
|
67
|
+
const userData = parsed?.state?.user ?? parsed?.user ?? null;
|
|
68
|
+
useUserStore.setState({ user: userData });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn("Failed to parse user from storage event:", error);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
useUserStore.setState({ user: null });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
58
77
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return userToSave;
|
|
65
|
-
},
|
|
66
|
-
onSuccess: data => {
|
|
67
|
-
queryClient.setQueryData(USER_QUERY_KEY, data);
|
|
68
|
-
debug("User updated", data);
|
|
69
|
-
},
|
|
70
|
-
});
|
|
78
|
+
window.addEventListener("storage", handleStorageChange);
|
|
79
|
+
return () => {
|
|
80
|
+
window.removeEventListener("storage", handleStorageChange);
|
|
81
|
+
};
|
|
82
|
+
}, []);
|
|
71
83
|
|
|
72
84
|
// Helper function to set user (maintains backward compatibility)
|
|
73
85
|
const setUser = (newUser?: Users) => {
|
|
74
|
-
|
|
86
|
+
setUserStore(newUser);
|
|
75
87
|
};
|
|
76
88
|
|
|
77
89
|
// Helper function to invalidate and refetch user
|
|
78
90
|
const refetchUser = async () => {
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
// Re-read from localStorage and update store
|
|
92
|
+
// Zustand persist stores data as { state: { user: ... }, version: ... }
|
|
93
|
+
const stored = localStorage.getItem("b3-user");
|
|
94
|
+
if (stored) {
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(stored);
|
|
97
|
+
// Zustand persist format: { state: { user: ... }, version: ... }
|
|
98
|
+
const userData = parsed?.state?.user ?? parsed?.user ?? null;
|
|
99
|
+
useUserStore.setState({ user: userData });
|
|
100
|
+
return userData ?? undefined;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.warn("Failed to refetch user from localStorage:", error);
|
|
103
|
+
// Fallback to current store state
|
|
104
|
+
return useUserStore.getState().user ?? undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
useUserStore.setState({ user: null });
|
|
108
|
+
return undefined;
|
|
81
109
|
};
|
|
82
110
|
|
|
83
111
|
// Helper function to clear user
|
|
84
112
|
const clearUser = () => {
|
|
85
|
-
|
|
113
|
+
clearUserStore();
|
|
86
114
|
};
|
|
87
115
|
|
|
88
116
|
return {
|