@b3dotfun/sdk 0.0.30 → 0.0.31
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/AnySpend.js +1 -1
- package/dist/cjs/anyspend/react/components/AnySpendBuySpin.js +2 -1
- package/dist/cjs/anyspend/react/components/AnySpendStakeB3.js +2 -1
- package/dist/cjs/anyspend/react/components/AnyspendDepositHype.d.ts +4 -0
- package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +6 -1
- package/dist/cjs/anyspend/react/components/common/ChainTokenIcon.d.ts +1 -1
- package/dist/cjs/anyspend/react/components/common/ChainTokenIcon.js +2 -1
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +23 -28
- package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +2 -2
- package/dist/cjs/anyspend/react/components/common/OrderDetails.js +5 -5
- package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +1 -1
- package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +4 -1
- package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +3 -3
- package/dist/cjs/anyspend/react/components/common/PaySection.js +1 -1
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +2 -5
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +5 -0
- package/dist/cjs/global-account/react/components/LinkAccount/LinkAccount.js +1 -0
- package/dist/cjs/global-account/react/components/ManageAccount/BalanceContent.d.ts +6 -0
- package/dist/cjs/global-account/react/components/ManageAccount/BalanceContent.js +94 -0
- package/dist/cjs/global-account/react/components/ManageAccount/ContentTokens.d.ts +14 -0
- package/dist/cjs/global-account/react/components/ManageAccount/ContentTokens.js +272 -0
- package/dist/cjs/global-account/react/components/ManageAccount/ManageAccount.js +9 -51
- package/dist/cjs/global-account/react/components/ManageAccount/TokenBalanceRow.d.ts +10 -0
- package/dist/cjs/global-account/react/components/ManageAccount/TokenBalanceRow.js +8 -0
- package/dist/cjs/global-account/react/components/TokenIcon.d.ts +11 -0
- package/dist/cjs/global-account/react/components/TokenIcon.js +43 -0
- package/dist/cjs/global-account/react/components/ui/accordion.d.ts +7 -0
- package/dist/cjs/global-account/react/components/ui/accordion.js +53 -0
- package/dist/cjs/global-account/react/components/ui/dialog.js +1 -1
- package/dist/cjs/global-account/react/hooks/index.d.ts +2 -0
- package/dist/cjs/global-account/react/hooks/index.js +5 -1
- package/dist/cjs/global-account/react/hooks/useAnalytics.d.ts +7 -0
- package/dist/cjs/global-account/react/hooks/useAnalytics.js +29 -0
- package/dist/cjs/global-account/react/hooks/useB3BalanceFromAddresses.js +2 -1
- package/dist/cjs/global-account/react/hooks/useNativeBalance.js +2 -1
- package/dist/cjs/global-account/react/hooks/useSimBalance.d.ts +24 -0
- package/dist/cjs/global-account/react/hooks/useSimBalance.js +29 -0
- package/dist/cjs/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +2 -1
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +2 -2
- package/dist/cjs/global-account/react/utils/profileDisplay.js +9 -0
- package/dist/cjs/global-account/utils/analytics.d.ts +16 -0
- package/dist/cjs/global-account/utils/analytics.js +55 -0
- package/dist/cjs/shared/constants/index.d.ts +1 -0
- package/dist/cjs/shared/constants/index.js +2 -1
- package/dist/cjs/shared/generated/chain-networks.json +185 -17
- package/dist/esm/anyspend/react/components/AnySpend.js +1 -1
- package/dist/esm/anyspend/react/components/AnySpendBuySpin.js +2 -1
- package/dist/esm/anyspend/react/components/AnySpendStakeB3.js +2 -1
- package/dist/esm/anyspend/react/components/AnyspendDepositHype.d.ts +4 -0
- package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +5 -1
- package/dist/esm/anyspend/react/components/common/ChainTokenIcon.d.ts +1 -1
- package/dist/esm/anyspend/react/components/common/ChainTokenIcon.js +2 -1
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +22 -27
- package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
- package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +2 -2
- package/dist/esm/anyspend/react/components/common/OrderDetails.js +5 -5
- package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +1 -1
- package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +4 -1
- package/dist/esm/anyspend/react/components/common/PanelOnramp.js +4 -4
- package/dist/esm/anyspend/react/components/common/PaySection.js +1 -1
- package/dist/esm/global-account/react/components/B3DynamicModal.js +2 -5
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +5 -0
- package/dist/esm/global-account/react/components/LinkAccount/LinkAccount.js +1 -0
- package/dist/esm/global-account/react/components/ManageAccount/BalanceContent.d.ts +6 -0
- package/dist/esm/global-account/react/components/ManageAccount/BalanceContent.js +88 -0
- package/dist/esm/global-account/react/components/ManageAccount/ContentTokens.d.ts +14 -0
- package/dist/esm/global-account/react/components/ManageAccount/ContentTokens.js +266 -0
- package/dist/esm/global-account/react/components/ManageAccount/ManageAccount.js +12 -51
- package/dist/esm/global-account/react/components/ManageAccount/TokenBalanceRow.d.ts +10 -0
- package/dist/esm/global-account/react/components/ManageAccount/TokenBalanceRow.js +5 -0
- package/dist/esm/global-account/react/components/TokenIcon.d.ts +11 -0
- package/dist/esm/global-account/react/components/TokenIcon.js +37 -0
- package/dist/esm/global-account/react/components/ui/accordion.d.ts +7 -0
- package/dist/esm/global-account/react/components/ui/accordion.js +14 -0
- package/dist/esm/global-account/react/components/ui/dialog.js +1 -1
- package/dist/esm/global-account/react/hooks/index.d.ts +2 -0
- package/dist/esm/global-account/react/hooks/index.js +2 -0
- package/dist/esm/global-account/react/hooks/useAnalytics.d.ts +7 -0
- package/dist/esm/global-account/react/hooks/useAnalytics.js +26 -0
- package/dist/esm/global-account/react/hooks/useB3BalanceFromAddresses.js +2 -1
- package/dist/esm/global-account/react/hooks/useNativeBalance.js +2 -1
- package/dist/esm/global-account/react/hooks/useSimBalance.d.ts +24 -0
- package/dist/esm/global-account/react/hooks/useSimBalance.js +26 -0
- package/dist/esm/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +2 -1
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +2 -2
- package/dist/esm/global-account/react/utils/profileDisplay.js +9 -0
- package/dist/esm/global-account/utils/analytics.d.ts +16 -0
- package/dist/esm/global-account/utils/analytics.js +50 -0
- package/dist/esm/shared/constants/index.d.ts +1 -0
- package/dist/esm/shared/constants/index.js +1 -0
- package/dist/esm/shared/generated/chain-networks.json +185 -17
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/components/AnyspendDepositHype.d.ts +4 -0
- package/dist/types/anyspend/react/components/common/ChainTokenIcon.d.ts +1 -1
- package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
- package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +4 -1
- package/dist/types/global-account/react/components/ManageAccount/BalanceContent.d.ts +6 -0
- package/dist/types/global-account/react/components/ManageAccount/ContentTokens.d.ts +14 -0
- package/dist/types/global-account/react/components/ManageAccount/TokenBalanceRow.d.ts +10 -0
- package/dist/types/global-account/react/components/TokenIcon.d.ts +11 -0
- package/dist/types/global-account/react/components/ui/accordion.d.ts +7 -0
- package/dist/types/global-account/react/hooks/index.d.ts +2 -0
- package/dist/types/global-account/react/hooks/useAnalytics.d.ts +7 -0
- package/dist/types/global-account/react/hooks/useSimBalance.d.ts +24 -0
- package/dist/types/global-account/react/stores/useModalStore.d.ts +2 -2
- package/dist/types/global-account/utils/analytics.d.ts +16 -0
- package/dist/types/shared/constants/index.d.ts +1 -0
- package/package.json +10 -18
- package/src/anyspend/react/components/AnySpend.tsx +1 -0
- package/src/anyspend/react/components/AnySpendBuySpin.tsx +2 -1
- package/src/anyspend/react/components/AnySpendStakeB3.tsx +3 -2
- package/src/anyspend/react/components/AnyspendDepositHype.tsx +10 -0
- package/src/anyspend/react/components/AnyspendSignatureMint.tsx +4 -4
- package/src/anyspend/react/components/common/ChainTokenIcon.tsx +8 -2
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +56 -107
- package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +12 -3
- package/src/anyspend/react/components/common/OrderDetails.tsx +5 -5
- package/src/anyspend/react/components/common/OrderTokenAmount.tsx +2 -2
- package/src/anyspend/react/components/common/PanelOnramp.tsx +11 -5
- package/src/anyspend/react/components/common/PaySection.tsx +1 -1
- package/src/global-account/react/components/B3DynamicModal.tsx +8 -7
- package/src/global-account/react/components/B3Provider/B3Provider.tsx +6 -0
- package/src/global-account/react/components/LinkAccount/LinkAccount.tsx +2 -1
- package/src/global-account/react/components/ManageAccount/BalanceContent.tsx +228 -0
- package/src/global-account/react/components/ManageAccount/ContentTokens.tsx +568 -0
- package/src/global-account/react/components/ManageAccount/ManageAccount.tsx +86 -341
- package/src/global-account/react/components/ManageAccount/TokenBalanceRow.tsx +46 -0
- package/src/global-account/react/components/TokenIcon.tsx +87 -0
- package/src/global-account/react/components/ui/accordion.tsx +53 -0
- package/src/global-account/react/components/ui/dialog.tsx +1 -1
- package/src/global-account/react/hooks/index.ts +2 -0
- package/src/global-account/react/hooks/useAccountAssets.ts +1 -0
- package/src/global-account/react/hooks/useAnalytics.tsx +30 -0
- package/src/global-account/react/hooks/useB3BalanceFromAddresses.ts +3 -2
- package/src/global-account/react/hooks/useNativeBalance.tsx +2 -1
- package/src/global-account/react/hooks/useSimBalance.ts +56 -0
- package/src/global-account/react/hooks/useUnifiedChainSwitchAndExecute.ts +3 -1
- package/src/global-account/react/stores/useModalStore.ts +2 -2
- package/src/global-account/react/utils/profileDisplay.ts +9 -0
- package/src/global-account/utils/analytics.ts +64 -0
- package/src/shared/constants/index.ts +2 -0
- package/src/shared/generated/chain-networks.json +185 -17
- package/src/{anyspend/types → types}/window.d.ts +5 -1
- package/dist/cjs/index.d.ts +0 -0
- package/dist/cjs/index.js +0 -2
- package/dist/esm/index.d.ts +0 -0
- package/dist/esm/index.js +0 -2
- package/dist/types/index.d.ts +0 -0
- package/src/index.ts +0 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ALL_CHAINS, getExplorerTxUrl } from "../../../../anyspend/index.js";
|
|
3
|
+
import { ChainTokenIcon } from "../../../../anyspend/react/components/common/ChainTokenIcon.js";
|
|
4
|
+
import { Button, TransitionPanel, useAnalytics, useSimBalance, useUnifiedChainSwitchAndExecute, } from "../../../../global-account/react/index.js";
|
|
5
|
+
import { formatDisplayNumber, formatTokenAmount } from "../../../../shared/utils/number.js";
|
|
6
|
+
import { ArrowLeft, CircleHelp, Copy, Loader2, Send } from "lucide-react";
|
|
7
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { NumericFormat } from "react-number-format";
|
|
9
|
+
import { toast } from "sonner";
|
|
10
|
+
import { useActiveAccount } from "thirdweb/react";
|
|
11
|
+
import { encodeFunctionData, erc20Abi, isAddress, parseUnits } from "viem";
|
|
12
|
+
import invariant from "invariant";
|
|
13
|
+
// Panel view enum for managing navigation between token list and send form
|
|
14
|
+
var TokenPanelView;
|
|
15
|
+
(function (TokenPanelView) {
|
|
16
|
+
TokenPanelView[TokenPanelView["LIST"] = 0] = "LIST";
|
|
17
|
+
TokenPanelView[TokenPanelView["SEND"] = 1] = "SEND";
|
|
18
|
+
})(TokenPanelView || (TokenPanelView = {}));
|
|
19
|
+
/**
|
|
20
|
+
* ContentTokens Component
|
|
21
|
+
*
|
|
22
|
+
* Displays user's token balances with ability to send tokens. Features:
|
|
23
|
+
* - Animated transitions between token list and send form
|
|
24
|
+
* - Smart filtering (shows all tokens when ≤5 valuable tokens or no $1+ tokens)
|
|
25
|
+
* - NumericFormat inputs for proper number handling
|
|
26
|
+
* - Focus preservation during transitions (see render functions pattern below)
|
|
27
|
+
*/
|
|
28
|
+
export function ContentTokens({ activeTab }) {
|
|
29
|
+
// === TOKEN FILTERING STATE ===
|
|
30
|
+
const [showAllTokens, setShowAllTokens] = useState(false);
|
|
31
|
+
// === NAVIGATION STATE ===
|
|
32
|
+
const [tokenPanelView, setTokenPanelView] = useState(TokenPanelView.LIST);
|
|
33
|
+
// === SEND FORM STATE ===
|
|
34
|
+
const [selectedToken, setSelectedToken] = useState(null);
|
|
35
|
+
const [recipientAddress, setRecipientAddress] = useState("");
|
|
36
|
+
const [sendAmount, setSendAmount] = useState("");
|
|
37
|
+
const [isSending, setIsSending] = useState(false);
|
|
38
|
+
const [addressError, setAddressError] = useState("");
|
|
39
|
+
// === ANIMATION STATE ===
|
|
40
|
+
// CRITICAL: useRef for animation direction prevents component remounting
|
|
41
|
+
// This ensures input focus is preserved during panel transitions
|
|
42
|
+
const animationDirection = useRef(null);
|
|
43
|
+
// === DATA FETCHING ===
|
|
44
|
+
const account = useActiveAccount();
|
|
45
|
+
const { data: simBalance, refetch: refetchSimBalance, isLoading: isLoadingBalance } = useSimBalance(account?.address);
|
|
46
|
+
// === BLOCKCHAIN INTERACTION ===
|
|
47
|
+
const { switchChainAndExecute } = useUnifiedChainSwitchAndExecute();
|
|
48
|
+
// === ANALYTICS ===
|
|
49
|
+
const { sendAnalyticsEvent } = useAnalytics();
|
|
50
|
+
// === ADDRESS VALIDATION ===
|
|
51
|
+
// Handle recipient address change with real-time validation using viem
|
|
52
|
+
const handleRecipientAddressChange = (value) => {
|
|
53
|
+
setRecipientAddress(value);
|
|
54
|
+
// Only show error if user has typed something and it's invalid
|
|
55
|
+
// Using viem's isAddress for robust EVM address validation
|
|
56
|
+
if (value && !isAddress(value)) {
|
|
57
|
+
setAddressError("Please enter a valid EVM address (0x...)");
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
setAddressError("");
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// === TAB RESET EFFECT ===
|
|
64
|
+
// Reset all state when user switches away from tokens tab
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (activeTab !== "tokens") {
|
|
67
|
+
setTokenPanelView(TokenPanelView.LIST);
|
|
68
|
+
setSelectedToken(null);
|
|
69
|
+
setRecipientAddress("");
|
|
70
|
+
setSendAmount("");
|
|
71
|
+
setIsSending(false);
|
|
72
|
+
setAddressError("");
|
|
73
|
+
animationDirection.current = null;
|
|
74
|
+
}
|
|
75
|
+
}, [activeTab]);
|
|
76
|
+
// === HELPER FUNCTION ===
|
|
77
|
+
// Get current version of selected token from fresh balance data
|
|
78
|
+
// 🔧 FIX: Prevents auto-navigation back to token list when balance refreshes
|
|
79
|
+
// The useSimBalance hook refreshes data, creating new token object references
|
|
80
|
+
// This helper ensures we always get the fresh token data instead of stale references
|
|
81
|
+
const getCurrentSelectedToken = () => {
|
|
82
|
+
if (!selectedToken || !simBalance?.balances) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const found = simBalance.balances.find(token => token.chain_id === selectedToken.chain_id && token.address === selectedToken.address);
|
|
86
|
+
return found || null;
|
|
87
|
+
};
|
|
88
|
+
// ==================================================================================
|
|
89
|
+
// === RENDER FUNCTIONS (NOT COMPONENTS!) ===
|
|
90
|
+
// ==================================================================================
|
|
91
|
+
//
|
|
92
|
+
// 🚨 CRITICAL ARCHITECTURE DECISION:
|
|
93
|
+
// These are render functions, NOT component functions with useCallback!
|
|
94
|
+
//
|
|
95
|
+
// WHY THIS WORKS:
|
|
96
|
+
// ✅ Stable wrapper <div> elements with consistent keys
|
|
97
|
+
// ✅ React never sees these as "new components"
|
|
98
|
+
// ✅ Input focus is preserved during transitions
|
|
99
|
+
// ✅ No component remounting issues
|
|
100
|
+
//
|
|
101
|
+
// WHY useCallback DIDN'T WORK:
|
|
102
|
+
// ❌ useCallback(() => <JSX />, [deps]) still creates new component instances
|
|
103
|
+
// ❌ React treats each render as a different component
|
|
104
|
+
// ❌ Causes remounting and focus loss
|
|
105
|
+
//
|
|
106
|
+
// THE PATTERN:
|
|
107
|
+
// Instead of: {[<ComponentA />, <ComponentB />]}
|
|
108
|
+
// We use: {[<div key="a">{renderA()}</div>, <div key="b">{renderB()}</div>]}
|
|
109
|
+
// ==================================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Renders the send token form panel
|
|
112
|
+
* Includes recipient input, amount input with NumericFormat, and percentage buttons
|
|
113
|
+
*/
|
|
114
|
+
const renderSendTokenPanel = () => {
|
|
115
|
+
// Get fresh token data to prevent stale references
|
|
116
|
+
const currentToken = getCurrentSelectedToken();
|
|
117
|
+
// 🔧 SINGLE FALLBACK STRATEGY:
|
|
118
|
+
// Use fresh token data when available, fall back to selectedToken if needed
|
|
119
|
+
// This prevents duplication of "currentToken || selectedToken" throughout the component
|
|
120
|
+
const displayToken = currentToken || selectedToken;
|
|
121
|
+
// Handle percentage button clicks (25%, 50%, 75%, 100%)
|
|
122
|
+
const handlePercentageClick = (percentage) => {
|
|
123
|
+
if (displayToken) {
|
|
124
|
+
const tokenBalance = (BigInt(displayToken.amount) * BigInt(percentage)) / BigInt(100);
|
|
125
|
+
const amount = formatTokenAmount(tokenBalance, displayToken.decimals, 30, false);
|
|
126
|
+
setSendAmount(amount);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
// Execute token transfer transaction
|
|
130
|
+
const handleSend = async () => {
|
|
131
|
+
if (!displayToken || !recipientAddress || !sendAmount || parseFloat(sendAmount) <= 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setIsSending(true);
|
|
135
|
+
const amountInWei = parseUnits(sendAmount, displayToken.decimals);
|
|
136
|
+
// Prepare analytics event data
|
|
137
|
+
const analyticsData = {
|
|
138
|
+
amount: sendAmount,
|
|
139
|
+
symbol: displayToken.symbol,
|
|
140
|
+
chain_id: displayToken.chain_id,
|
|
141
|
+
address: displayToken.address,
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
invariant(isAddress(recipientAddress), "Recipient address is not a valid address");
|
|
145
|
+
const sendTokenData = encodeFunctionData({
|
|
146
|
+
abi: erc20Abi,
|
|
147
|
+
functionName: "transfer",
|
|
148
|
+
args: [recipientAddress, amountInWei],
|
|
149
|
+
});
|
|
150
|
+
const tx = await switchChainAndExecute(displayToken.chain_id, {
|
|
151
|
+
to: displayToken.address === "native" ? recipientAddress : displayToken.address,
|
|
152
|
+
data: sendTokenData,
|
|
153
|
+
value: displayToken.address === "native" ? amountInWei : BigInt(0),
|
|
154
|
+
});
|
|
155
|
+
if (tx) {
|
|
156
|
+
// Track successful send
|
|
157
|
+
sendAnalyticsEvent("send_token_button_click", {
|
|
158
|
+
...analyticsData,
|
|
159
|
+
success: true,
|
|
160
|
+
tx: getExplorerTxUrl(displayToken.chain_id, tx),
|
|
161
|
+
});
|
|
162
|
+
// Reset form
|
|
163
|
+
setSendAmount("");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
// Track failed send
|
|
168
|
+
sendAnalyticsEvent("send_token_button_click", {
|
|
169
|
+
...analyticsData,
|
|
170
|
+
success: false,
|
|
171
|
+
reason: error.message || "Unknown error",
|
|
172
|
+
});
|
|
173
|
+
// Error
|
|
174
|
+
toast.error(`Failed to send ${displayToken.symbol}: ${error.message || "Unknown error"}`);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
// Wait 1 second to make sure the tx is indexed on sim api.
|
|
178
|
+
setTimeout(async () => {
|
|
179
|
+
// Force refetch to bypass cache and get fresh balance data
|
|
180
|
+
await refetchSimBalance();
|
|
181
|
+
}, 1000);
|
|
182
|
+
setIsSending(false);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
// Show loading state only if no token data is available at all
|
|
186
|
+
if (!displayToken) {
|
|
187
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [_jsx("div", { className: "bg-b3-line/50 mb-4 rounded-full p-4", children: _jsx(Loader2, { className: "text-b3-foreground-muted h-8 w-8 animate-spin" }) }), _jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold mb-2", children: "Loading token data..." }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Please wait while we fetch the latest information" })] }));
|
|
188
|
+
}
|
|
189
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "icon", onClick: () => {
|
|
190
|
+
animationDirection.current = "back";
|
|
191
|
+
setTokenPanelView(TokenPanelView.LIST);
|
|
192
|
+
}, className: "hover:bg-b3-line/60", disabled: isSending, children: _jsx(ArrowLeft, { className: "h-5 w-5" }) }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center", children: ALL_CHAINS[displayToken.chain_id]?.logoUrl ? (_jsx(ChainTokenIcon, { chainUrl: ALL_CHAINS[displayToken.chain_id].logoUrl, tokenUrl: displayToken.token_metadata?.logo, className: "size-8" })) : (_jsx(CircleHelp, { className: "text-b3-react-foreground size-8" })) }), _jsxs("h2", { className: "text-b3-grey font-neue-montreal-semibold text-lg", children: ["Send ", displayToken.symbol] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-b3-grey font-neue-montreal-medium text-sm", children: "Recipient Address" }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "relative", children: [_jsx("input", { type: "text", value: recipientAddress, onChange: e => handleRecipientAddressChange(e.target.value), placeholder: "Enter wallet address (0x...)", className: `border-b3-line bg-b3-background text-b3-grey font-neue-montreal-medium placeholder:text-b3-foreground-muted w-full rounded-xl border px-4 py-3 pr-12 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${addressError ? "border-red-500 focus:border-red-500" : "focus:border-b3-primary-blue"}`, disabled: isSending }), _jsx(Button, { variant: "ghost", size: "icon", className: "hover:bg-b3-line/60 absolute right-2 top-1/2 h-8 w-8 -translate-y-1/2", disabled: isSending, onClick: () => {
|
|
193
|
+
navigator.clipboard.readText().then(text => {
|
|
194
|
+
handleRecipientAddressChange(text);
|
|
195
|
+
});
|
|
196
|
+
}, children: _jsx(Copy, { className: "h-4 w-4" }) })] }), addressError && _jsx("p", { className: "font-neue-montreal-medium text-xs text-red-500", children: addressError })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-b3-grey font-neue-montreal-medium text-sm", children: "Amount" }), _jsxs("div", { className: "space-y-3", children: [_jsx(NumericFormat, { decimalSeparator: ".", allowedDecimalSeparators: [","], thousandSeparator: true, inputMode: "decimal", autoComplete: "off", autoCorrect: "off", type: "text", placeholder: "0.00", minLength: 1, maxLength: 30, spellCheck: "false", className: "border-b3-line bg-b3-background text-b3-grey font-neue-montreal-medium placeholder:text-b3-foreground-muted focus:border-b3-primary-blue w-full rounded-xl border px-4 py-3 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50", pattern: "^[0-9]*[.,]?[0-9]*$", disabled: isSending, value: sendAmount, allowNegative: false, onChange: e => setSendAmount(e.currentTarget.value) }), _jsx("div", { className: "grid grid-cols-4 gap-2", children: [25, 50, 75, 100].map(percentage => (_jsxs(Button, { variant: "outline", onClick: () => handlePercentageClick(percentage), className: "hover:bg-b3-primary-wash border-b3-line text-b3-grey font-neue-montreal-medium text-sm", disabled: isSending, children: [percentage, "%"] }, percentage))) }), _jsxs("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: ["Available: ", formatTokenAmount(BigInt(displayToken.amount), displayToken.decimals), " ", displayToken.symbol] })] })] }), _jsx(Button, { onClick: handleSend, disabled: !recipientAddress ||
|
|
197
|
+
!sendAmount ||
|
|
198
|
+
parseFloat(sendAmount) <= 0 ||
|
|
199
|
+
isSending ||
|
|
200
|
+
!!addressError ||
|
|
201
|
+
!isAddress(recipientAddress), className: "bg-b3-primary-blue hover:bg-b3-primary-blue/90 font-neue-montreal-semibold disabled:bg-b3-line disabled:text-b3-foreground-muted w-full rounded-xl py-3 text-white", children: isSending ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Sending..."] })) : (_jsxs(_Fragment, { children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Send ", displayToken.symbol] })) })] }));
|
|
202
|
+
};
|
|
203
|
+
// Skeleton loading component for token list
|
|
204
|
+
const LoadingIndicator = () => (_jsx("div", { className: "space-y-4", children: _jsx("div", { className: "space-y-1", children: [...Array(3)].map((_, index) => (_jsxs("div", { className: "flex items-center justify-between rounded-xl p-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "bg-b3-line h-10 w-10 animate-pulse rounded-full" }), _jsxs("div", { children: [_jsx("div", { className: "bg-b3-line mb-1 h-4 w-16 animate-pulse rounded" }), _jsx("div", { className: "bg-b3-line h-3 w-24 animate-pulse rounded" })] })] }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "bg-b3-line mb-1 h-4 w-20 animate-pulse rounded" }), _jsx("div", { className: "bg-b3-line h-3 w-16 animate-pulse rounded" })] })] }, index))) }) }));
|
|
205
|
+
/**
|
|
206
|
+
* Renders the token list panel with smart filtering
|
|
207
|
+
* Features intelligent token display logic to reduce noise while ensuring visibility
|
|
208
|
+
*/
|
|
209
|
+
const renderTokenListPanel = () => {
|
|
210
|
+
// Show loading indicator when balance is loading
|
|
211
|
+
if (isLoadingBalance) {
|
|
212
|
+
return _jsx(LoadingIndicator, {});
|
|
213
|
+
}
|
|
214
|
+
// Show empty state when no account or no balance data
|
|
215
|
+
if (!account?.address || !simBalance) {
|
|
216
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [_jsx("div", { className: "bg-b3-line/50 mb-4 rounded-full p-4", children: _jsx(Loader2, { className: "text-b3-foreground-muted h-8 w-8" }) }), _jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold mb-2", children: "No wallet connected" }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Connect your wallet to view token balances" })] }));
|
|
217
|
+
}
|
|
218
|
+
// === SMART FILTERING LOGIC ===
|
|
219
|
+
// Filter tokens with value >= $1 to reduce noise from dust tokens
|
|
220
|
+
const filteredTokens = simBalance?.balances.filter(token => token.value_usd !== undefined && token.value_usd >= 1) || [];
|
|
221
|
+
// 🧠 INTELLIGENT DISPLAY LOGIC:
|
|
222
|
+
// Show all tokens automatically when filtering would be unhelpful:
|
|
223
|
+
// 1. User explicitly requested to show all, OR
|
|
224
|
+
// 2. No tokens with value >= $1 (user has only dust), OR
|
|
225
|
+
// 3. 5 or fewer tokens with value >= $1 (not enough to warrant filtering)
|
|
226
|
+
const shouldShowAllTokens = showAllTokens || filteredTokens.length === 0 || filteredTokens.length <= 5;
|
|
227
|
+
const tokensToShow = shouldShowAllTokens ? simBalance?.balances || [] : filteredTokens;
|
|
228
|
+
const hasHiddenTokens = !shouldShowAllTokens && (simBalance?.balances.length || 0) > filteredTokens.length;
|
|
229
|
+
// Handle token selection and navigate to send form
|
|
230
|
+
const handleTokenClick = (token) => {
|
|
231
|
+
setSelectedToken(token);
|
|
232
|
+
animationDirection.current = "forward"; // Set animation direction BEFORE state change
|
|
233
|
+
setTokenPanelView(TokenPanelView.SEND);
|
|
234
|
+
// Reset form when selecting a new token
|
|
235
|
+
setRecipientAddress("");
|
|
236
|
+
setSendAmount("");
|
|
237
|
+
setIsSending(false);
|
|
238
|
+
setAddressError("");
|
|
239
|
+
};
|
|
240
|
+
// Show empty state when no tokens are available
|
|
241
|
+
if (tokensToShow.length === 0) {
|
|
242
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [_jsx("div", { className: "bg-b3-line/50 mb-4 rounded-full p-4", children: _jsx(CircleHelp, { className: "text-b3-foreground-muted h-8 w-8" }) }), _jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold mb-2", children: "No tokens found" }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "No token balances found in your wallet." })] }));
|
|
243
|
+
}
|
|
244
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "space-y-1", children: tokensToShow.map(token => (_jsxs("div", { className: "hover:bg-b3-line/60 dark:hover:bg-b3-primary-wash/40 group flex cursor-pointer items-center justify-between rounded-xl p-3 transition-all duration-200", onClick: () => handleTokenClick(token), children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full", children: ALL_CHAINS[token.chain_id]?.logoUrl ? (_jsx(ChainTokenIcon, { chainUrl: ALL_CHAINS[token.chain_id].logoUrl, tokenUrl: token.token_metadata?.logo, className: "size-10" })) : (_jsx(CircleHelp, { className: "text-b3-react-foreground size-10" })) }), _jsxs("div", { children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold transition-colors duration-200 group-hover:font-bold group-hover:text-black", children: token.symbol }) }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm transition-colors duration-200 group-hover:text-gray-700", children: token.name })] })] }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold transition-colors duration-200 group-hover:font-bold group-hover:text-black", children: formatTokenAmount(BigInt(token.amount), token.decimals) }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm transition-colors duration-200 group-hover:text-gray-700", children: formatDisplayNumber(token.value_usd, { style: "currency", fractionDigits: 2 }) })] })] }, token.chain_id + "_" + token.address))) }), hasHiddenTokens && !showAllTokens && (_jsx("div", { className: "flex justify-center", children: _jsx(Button, { variant: "ghost", className: "text-b3-primary-blue hover:text-b3-primary-blue/80 font-neue-montreal-semibold text-sm", onClick: () => setShowAllTokens(true), children: "Show more" }) }))] }));
|
|
245
|
+
};
|
|
246
|
+
// === ANIMATION CONFIGURATION ===
|
|
247
|
+
// Memoize variants to prevent re-creation and unwanted re-renders
|
|
248
|
+
const variants = useMemo(() => ({
|
|
249
|
+
enter: (direction) => ({
|
|
250
|
+
x: direction === "back" ? -300 : 300, // Back: slide from left, Forward: slide from right
|
|
251
|
+
opacity: 0,
|
|
252
|
+
}),
|
|
253
|
+
center: { x: 0, opacity: 1 }, // Final position: centered and visible
|
|
254
|
+
exit: (direction) => ({
|
|
255
|
+
x: direction === "back" ? 300 : -300, // Back: slide to right, Forward: slide to left
|
|
256
|
+
opacity: 0,
|
|
257
|
+
}),
|
|
258
|
+
}), []);
|
|
259
|
+
// Memoize transition config for consistent spring animation
|
|
260
|
+
const transition = useMemo(() => ({
|
|
261
|
+
type: "spring",
|
|
262
|
+
stiffness: 300, // Spring tension
|
|
263
|
+
damping: 30, // Spring damping
|
|
264
|
+
}), []);
|
|
265
|
+
return (_jsx(TransitionPanel, { activeIndex: tokenPanelView, className: "min-h-[400px]", custom: animationDirection.current, variants: variants, transition: transition, children: [_jsx("div", { children: renderTokenListPanel() }, "token-list"), _jsx("div", { children: renderSendTokenPanel() }, "send-token")] }));
|
|
266
|
+
}
|
|
@@ -1,46 +1,28 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
-
import { Button,
|
|
3
|
-
import { BankIcon } from "../../../../global-account/react/components/icons/BankIcon.js";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, TabsContentPrimitive, TabsListPrimitive, TabsPrimitive, TabTriggerPrimitive, useAccountAssets, useAuthentication, useGetAllTWSigners, useModalStore, useRemoveSessionKey, } from "../../../../global-account/react/index.js";
|
|
4
3
|
import { SignOutIcon } from "../../../../global-account/react/components/icons/SignOutIcon.js";
|
|
5
|
-
import { SwapIcon } from "../../../../global-account/react/components/icons/SwapIcon.js";
|
|
6
|
-
import { formatUsername } from "../../../../shared/utils/index.js";
|
|
7
4
|
import { formatNumber } from "../../../../shared/utils/formatNumber.js";
|
|
8
5
|
import { client } from "../../../../shared/utils/thirdweb.js";
|
|
9
|
-
import { LinkIcon, Loader2,
|
|
6
|
+
import { BarChart3, Coins, Image, LinkIcon, Loader2, Settings, UnlinkIcon } from "lucide-react";
|
|
10
7
|
import { useState } from "react";
|
|
11
8
|
import { useActiveAccount, useProfiles, useUnlinkProfile } from "thirdweb/react";
|
|
12
9
|
import { formatUnits } from "viem";
|
|
13
|
-
import useFirstEOA from "../../hooks/useFirstEOA.js";
|
|
14
10
|
import { getProfileDisplayInfo } from "../../utils/profileDisplay.js";
|
|
15
11
|
import { AccountAssets } from "../AccountAssets/AccountAssets.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return str;
|
|
19
|
-
return `${str.slice(0, length)}...${str.slice(-length)}`;
|
|
20
|
-
}
|
|
12
|
+
import { ContentTokens } from "./ContentTokens.js";
|
|
13
|
+
import { BalanceContent } from "./BalanceContent.js";
|
|
21
14
|
export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit, chain, partnerId, }) {
|
|
22
15
|
const [revokingSignerId, setRevokingSignerId] = useState(null);
|
|
23
16
|
const account = useActiveAccount();
|
|
24
|
-
const { data:
|
|
25
|
-
const { data: b3Balance } = useB3BalanceFromAddresses(account?.address);
|
|
26
|
-
const { data: nativeBalance } = useNativeBalance(account?.address);
|
|
27
|
-
const { address: eoaAddress } = useFirstEOA();
|
|
28
|
-
const { data: profile } = useProfile({
|
|
29
|
-
address: eoaAddress || account?.address,
|
|
30
|
-
fresh: true,
|
|
31
|
-
});
|
|
32
|
-
const { data: eoaNativeBalance } = useNativeBalance(eoaAddress);
|
|
33
|
-
const { data: eoaB3Balance } = useB3BalanceFromAddresses(eoaAddress);
|
|
17
|
+
const { data: nfts, isLoading } = useAccountAssets(account?.address);
|
|
34
18
|
const { data: signers, refetch: refetchSigners } = useGetAllTWSigners({
|
|
35
19
|
chain,
|
|
36
20
|
accountAddress: account?.address,
|
|
37
21
|
});
|
|
38
|
-
const { setB3ModalOpen,
|
|
39
|
-
const { activeTab = "
|
|
22
|
+
const { setB3ModalOpen, contentType } = useModalStore();
|
|
23
|
+
const { activeTab = "overview", setActiveTab } = contentType;
|
|
40
24
|
const { logout } = useAuthentication(partnerId);
|
|
41
25
|
const [logoutLoading, setLogoutLoading] = useState(false);
|
|
42
|
-
console.log("account", account);
|
|
43
|
-
console.log("eoaAddress", eoaAddress);
|
|
44
26
|
const { removeSessionKey } = useRemoveSessionKey({
|
|
45
27
|
chain,
|
|
46
28
|
onSuccess: tx => {
|
|
@@ -64,24 +46,6 @@ export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit
|
|
|
64
46
|
setB3ModalOpen(false);
|
|
65
47
|
setLogoutLoading(false);
|
|
66
48
|
};
|
|
67
|
-
const BalanceContent = () => {
|
|
68
|
-
const { info: eoaInfo } = useFirstEOA();
|
|
69
|
-
return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "relative", children: [profile?.avatar ? (_jsx("img", { src: profile?.avatar, alt: "Profile", className: "size-24 rounded-full" })) : (_jsx("div", { className: "bg-b3-primary-wash size-24 rounded-full" })), _jsx("div", { className: "bg-b3-grey border-b3-background absolute -bottom-1 -right-1 flex size-8 items-center justify-center rounded-full border-4", children: _jsx(Pencil, { size: 16, className: "text-b3-background" }) })] }), _jsxs("div", { children: [_jsx("h2", { className: "text-b3-grey text-xl font-semibold", children: profile?.displayName || formatUsername(profile?.name || "") }), _jsx("span", { className: "text-b3-foreground-muted", children: formatUsername(profile?.name || "") })] })] }) }), _jsxs("div", { className: "manage-account-address bg-b3-line flex h-11 items-center gap-2 rounded-full px-4", children: [_jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: centerTruncate(account?.address || "") }), _jsx(CopyToClipboard, { text: account?.address || "" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs(Button, { className: "manage-account-deposit bg-b3-primary-wash hover:bg-b3-primary-wash/70 h-[84px] w-full flex-col items-start gap-2 rounded-2xl", onClick: () => {
|
|
70
|
-
setB3ModalOpen(true);
|
|
71
|
-
setB3ModalContentType({
|
|
72
|
-
type: "anySpend",
|
|
73
|
-
defaultActiveTab: "fiat",
|
|
74
|
-
showBackButton: true,
|
|
75
|
-
});
|
|
76
|
-
}, children: [_jsx(BankIcon, { size: 24, className: "text-b3-primary-blue shrink-0" }), _jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Deposit" })] }), _jsxs(Button, { className: "manage-account-swap bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex h-[84px] w-full flex-col items-start gap-2 rounded-2xl", onClick: () => {
|
|
77
|
-
setB3ModalOpen(true);
|
|
78
|
-
setB3ModalContentType({
|
|
79
|
-
type: "anySpend",
|
|
80
|
-
showBackButton: true,
|
|
81
|
-
});
|
|
82
|
-
}, children: [_jsx(SwapIcon, { size: 24, className: "text-b3-primary-blue" }), _jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Swap" })] })] }), _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold", children: "Balance" }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("img", { src: "https://cdn.b3.fun/b3-coin-3d.png", alt: "B3", className: "size-10" }) }), _jsxs("div", { children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: "B3" }) }), _jsxs("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: [b3Balance?.formattedTotal || "0.00", " B3"] })] })] }), _jsxs("div", { className: "text-right", children: [_jsxs("div", { className: "text-b3-grey font-neue-montreal-semibold", children: ["$", b3Balance?.balanceUsdFormatted || "0.00"] }), _jsx("div", { className: "flex items-center gap-1", children: b3Balance?.priceChange24h !== null && b3Balance?.priceChange24h !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Triangle, { className: `size-3 ${b3Balance.priceChange24h >= 0 ? "text-b3-positive fill-b3-positive" : "text-b3-negative fill-b3-negative rotate-180"}` }), _jsxs("span", { className: `font-neue-montreal-medium text-sm ${b3Balance.priceChange24h >= 0 ? "text-b3-positive" : "text-b3-negative"}`, children: [b3Balance.priceChange24h >= 0 ? "+" : "", b3Balance.priceChange24h.toFixed(2), "%"] })] })) : (_jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "--" })) })] })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("img", { src: "https://cdn.b3.fun/ethereum.svg", alt: "ETH", className: "size-10" }) }), _jsxs("div", { children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: "Ethereum" }) }), _jsxs("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: [nativeBalance?.formattedTotal || "0.00", " ETH"] })] })] }), _jsxs("div", { className: "text-right", children: [_jsxs("div", { className: "text-b3-grey font-neue-montreal-semibold", children: ["$", nativeBalance?.formattedTotalUsd || "0.00"] }), _jsx("div", { className: "flex items-center gap-2", children: nativeBalance?.priceChange24h !== null && nativeBalance?.priceChange24h !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Triangle, { className: `size-3 ${nativeBalance.priceChange24h >= 0 ? "text-b3-positive fill-b3-positive" : "text-b3-negative fill-b3-negative rotate-180"}` }), _jsxs("span", { className: `font-neue-montreal-medium text-sm ${nativeBalance.priceChange24h >= 0 ? "text-b3-positive" : "text-b3-negative"}`, children: [nativeBalance.priceChange24h >= 0 ? "+" : "", nativeBalance.priceChange24h.toFixed(2), "%"] })] })) : (_jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "--" })) })] })] })] }), eoaAddress && (_jsxs("div", { className: "space-y-4", children: [_jsxs("h3", { className: "text-b3-grey font-neue-montreal-semibold", children: ["Connected ", eoaInfo?.data?.name || "Wallet"] }), _jsxs("div", { className: "manage-account-address bg-b3-line flex h-11 items-center gap-2 rounded-full px-4", children: [_jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: centerTruncate(eoaAddress) }), _jsx(CopyToClipboard, { text: eoaAddress })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("img", { src: "https://cdn.b3.fun/b3-coin-3d.png", alt: "B3", className: "size-10" }) }), _jsxs("div", { children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: "B3" }) }), _jsxs("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: [eoaB3Balance?.formattedTotal || "0.00", " B3"] })] })] }), _jsxs("div", { className: "text-right", children: [_jsxs("div", { className: "text-b3-grey font-neue-montreal-semibold", children: ["$", eoaB3Balance?.balanceUsdFormatted || "0.00"] }), _jsx("div", { className: "flex items-center gap-1", children: eoaB3Balance?.priceChange24h !== null && eoaB3Balance?.priceChange24h !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Triangle, { className: `size-3 ${eoaB3Balance.priceChange24h >= 0 ? "text-b3-positive fill-b3-positive" : "text-b3-negative fill-b3-negative rotate-180"}` }), _jsxs("span", { className: `font-neue-montreal-medium text-sm ${eoaB3Balance.priceChange24h >= 0 ? "text-b3-positive" : "text-b3-negative"}`, children: [eoaB3Balance.priceChange24h >= 0 ? "+" : "", eoaB3Balance.priceChange24h.toFixed(2), "%"] })] })) : (_jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "--" })) })] })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("img", { src: "https://cdn.b3.fun/ethereum.svg", alt: "ETH", className: "size-10" }) }), _jsxs("div", { children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: "Ethereum" }) }), _jsxs("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: [eoaNativeBalance?.formattedTotal || "0.00", " ETH"] })] })] }), _jsxs("div", { className: "text-right", children: [_jsxs("div", { className: "text-b3-grey font-neue-montreal-semibold", children: ["$", eoaNativeBalance?.formattedTotalUsd || "0.00"] }), _jsx("div", { className: "flex items-center gap-2", children: eoaNativeBalance?.priceChange24h !== null && eoaNativeBalance?.priceChange24h !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Triangle, { className: `size-3 ${eoaNativeBalance.priceChange24h >= 0 ? "text-b3-positive fill-b3-positive" : "text-b3-negative fill-b3-negative rotate-180"}` }), _jsxs("span", { className: `font-neue-montreal-medium text-sm ${eoaNativeBalance.priceChange24h >= 0 ? "text-b3-positive" : "text-b3-negative"}`, children: [eoaNativeBalance.priceChange24h >= 0 ? "+" : "", eoaNativeBalance.priceChange24h.toFixed(2), "%"] })] })) : (_jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "--" })) })] })] })] })), _jsxs("div", { className: "border-b3-line flex items-center justify-between rounded-2xl border p-4", children: [_jsxs("div", { className: "", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", className: "h-4" }), _jsx("h3", { className: "font-neue-montreal-semibold text-b3-grey", children: "Global Account" })] }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm", children: "Your universal account for all B3-powered apps" })] }), _jsx("button", { className: "text-b3-grey hover:text-b3-grey/80 hover:bg-b3-line border-b3-line flex size-12 items-center justify-center rounded-full border", onClick: onLogoutEnhanced, children: logoutLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(SignOutIcon, { size: 16, className: "text-b3-grey" }) })] })] }));
|
|
83
|
-
};
|
|
84
|
-
const AssetsContent = () => (_jsx("div", { className: "grid grid-cols-3 gap-4", children: assets?.nftResponse ? (_jsx(AccountAssets, { nfts: assets.nftResponse, isLoading: isLoading })) : (_jsx("div", { className: "col-span-3 py-12 text-center text-gray-500", children: "No NFTs found" })) }));
|
|
85
49
|
const AppsContent = () => (_jsxs("div", { className: "space-y-4", children: [signers?.map((signer) => (_jsx("div", { className: "rounded-xl border border-gray-200 p-4 dark:border-gray-800", children: _jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 dark:bg-gray-800", children: _jsx("span", { className: "text-xs font-medium text-gray-600 dark:text-gray-400", children: "App" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-medium text-gray-900 dark:text-white", children: signer.partner.name }), _jsxs("div", { className: "mt-2 space-y-1", children: [_jsxs("p", { className: "text-xs text-gray-500", children: ["Added ", new Date(signer.createdAt).toLocaleDateString()] }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Expires ", new Date(Number(signer.endTimestamp) * 1000).toLocaleDateString()] }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Max spend: ", formatNumber(Number(formatUnits(signer.nativeTokenLimitPerTransaction, 18))), " ETH"] })] })] })] }), _jsx(Button, { variant: "outline", size: "sm", className: "border-red-200 text-red-500 hover:border-red-300 hover:text-red-600", onClick: () => handleRevoke(signer), disabled: revokingSignerId === signer.id, children: revokingSignerId === signer.id ? "Revoking..." : "Revoke" })] }) }, signer.id))), !signers?.length && _jsx("div", { className: "py-12 text-center text-gray-500", children: "No connected apps" })] }));
|
|
86
50
|
const SettingsContent = () => {
|
|
87
51
|
const [unlinkingAccountId, setUnlinkingAccountId] = useState(null);
|
|
@@ -97,10 +61,7 @@ export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit
|
|
|
97
61
|
const handleUnlink = async (profile) => {
|
|
98
62
|
setUnlinkingAccountId(profile.title);
|
|
99
63
|
try {
|
|
100
|
-
|
|
101
|
-
client,
|
|
102
|
-
profileToUnlink: profile.originalProfile,
|
|
103
|
-
});
|
|
64
|
+
unlinkProfile({ client, profileToUnlink: profile.originalProfile });
|
|
104
65
|
}
|
|
105
66
|
catch (error) {
|
|
106
67
|
console.error("Error unlinking account:", error);
|
|
@@ -127,12 +88,12 @@ export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit
|
|
|
127
88
|
},
|
|
128
89
|
});
|
|
129
90
|
};
|
|
130
|
-
return (_jsxs("div", { className: "space-y-8", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Linked Accounts" }), _jsxs(Button, { className: "bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2", onClick: handleOpenLinkModal, disabled: isLinking, children: [isLinking ? (_jsx(Loader2, { className: "text-b3-primary-blue animate-spin", size: 16 })) : (_jsx(LinkIcon, { size: 16, className: "text-b3-primary-blue" })), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: isLinking ? "Linking..." : "Link New Account" })] })] }), isLoadingProfiles ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Loader2, { className: "text-b3-grey animate-spin" }) })) : profiles.length > 0 ? (_jsx("div", { className: "space-y-4", children: profiles.map(profile => (_jsxs("div", { className: "bg-b3-line flex items-center justify-between rounded-xl p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [profile.imageUrl ? (_jsx("img", { src: profile.imageUrl, alt: profile.title, className: "size-10 rounded-full" })) : (_jsx("div", { className: "bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: profile.title }), _jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs", children: profile.type.toUpperCase() })] }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: profile.subtitle })] })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "text-b3-grey hover:text-b3-negative", onClick: () => handleUnlink(profile), disabled: unlinkingAccountId === profile.title || isUnlinking, children: unlinkingAccountId === profile.title || isUnlinking ? (_jsx(Loader2, { className: "animate-spin" })) : (_jsx(UnlinkIcon, { size: 16 })) })] }, profile.title))) })) : (_jsx("div", { className: "text-b3-foreground-muted py-8 text-center", children: "No linked accounts found" }))] }), _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Account Preferences" }), _jsx("div", { className: "bg-b3-line rounded-xl p-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Dark Mode" }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Switch between light and dark theme" })] }), _jsx("div", { className: "bg-b3-primary-wash h-6 w-12 rounded-full" })] }) })] }), _jsxs("div", { className: "border-b3-line flex items-center justify-between rounded-2xl border p-4", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", className: "h-4" }), _jsx("h3", { className: "font-neue-montreal-semibold text-b3-grey", children: "Global Account" })] }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm", children: "Your universal account for all B3
|
|
91
|
+
return (_jsxs("div", { className: "space-y-8", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Linked Accounts" }), _jsxs(Button, { className: "bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2", onClick: handleOpenLinkModal, disabled: isLinking, children: [isLinking ? (_jsx(Loader2, { className: "text-b3-primary-blue animate-spin", size: 16 })) : (_jsx(LinkIcon, { size: 16, className: "text-b3-primary-blue" })), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: isLinking ? "Linking..." : "Link New Account" })] })] }), isLoadingProfiles ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Loader2, { className: "text-b3-grey animate-spin" }) })) : profiles.length > 0 ? (_jsx("div", { className: "space-y-4", children: profiles.map(profile => (_jsxs("div", { className: "bg-b3-line flex items-center justify-between rounded-xl p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [profile.imageUrl ? (_jsx("img", { src: profile.imageUrl, alt: profile.title, className: "size-10 rounded-full" })) : (_jsx("div", { className: "bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: profile.title }), _jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs", children: profile.type.toUpperCase() })] }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: profile.subtitle })] })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "text-b3-grey hover:text-b3-negative", onClick: () => handleUnlink(profile), disabled: unlinkingAccountId === profile.title || isUnlinking, children: unlinkingAccountId === profile.title || isUnlinking ? (_jsx(Loader2, { className: "animate-spin" })) : (_jsx(UnlinkIcon, { size: 16 })) })] }, profile.title))) })) : (_jsx("div", { className: "text-b3-foreground-muted py-8 text-center", children: "No linked accounts found" }))] }), _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Account Preferences" }), _jsx("div", { className: "bg-b3-line rounded-xl p-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Dark Mode" }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Switch between light and dark theme" })] }), _jsx("div", { className: "bg-b3-primary-wash h-6 w-12 rounded-full" })] }) })] }), _jsxs("div", { className: "border-b3-line flex items-center justify-between rounded-2xl border p-4", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", className: "h-4" }), _jsx("h3", { className: "font-neue-montreal-semibold text-b3-grey", children: "Global Account" })] }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm", children: "Your universal account for all B3 apps" })] }), _jsx("button", { className: "text-b3-grey hover:text-b3-grey/80 hover:bg-b3-line border-b3-line flex size-12 items-center justify-center rounded-full border", onClick: onLogoutEnhanced, children: logoutLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(SignOutIcon, { size: 16, className: "text-b3-grey" }) })] })] }));
|
|
131
92
|
};
|
|
132
93
|
return (_jsx("div", { className: "b3-manage-account bg-b3-background flex flex-col rounded-xl", children: _jsx("div", { className: "flex-1", children: _jsxs(TabsPrimitive, { defaultValue: activeTab, onValueChange: value => {
|
|
133
94
|
const tab = value;
|
|
134
|
-
if (["
|
|
95
|
+
if (["overview", "tokens", "nfts", "apps", "settings"].includes(tab)) {
|
|
135
96
|
setActiveTab?.(tab);
|
|
136
97
|
}
|
|
137
|
-
}, children: [_jsxs(TabsListPrimitive, { className: "
|
|
98
|
+
}, children: [_jsx("div", { className: "px-4", children: _jsxs(TabsListPrimitive, { className: "grid h-auto grid-cols-2 grid-rows-2 gap-3 rounded-none border-none bg-transparent", children: [_jsxs(TabTriggerPrimitive, { value: "overview", className: "data-[state=active]:bg-b3-primary-blue data-[state=active]:hover:bg-b3-primary-blue data-[state=active]:border-b3-primary-blue group flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white p-2 text-center shadow-sm transition-all duration-200 hover:bg-gray-50 hover:shadow-md data-[state=active]:shadow-lg", children: [_jsx(BarChart3, { size: 20, className: "text-b3-primary-blue shrink-0 group-data-[state=active]:text-white" }), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm group-data-[state=active]:text-white", children: "Overview" })] }), _jsxs(TabTriggerPrimitive, { value: "tokens", className: "data-[state=active]:bg-b3-primary-blue data-[state=active]:hover:bg-b3-primary-blue data-[state=active]:border-b3-primary-blue group flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white p-2 text-center shadow-sm transition-all duration-200 hover:bg-gray-50 hover:shadow-md data-[state=active]:shadow-lg", children: [_jsx(Coins, { size: 20, className: "text-b3-primary-blue shrink-0 group-data-[state=active]:text-white" }), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm group-data-[state=active]:text-white", children: "Tokens" })] }), _jsxs(TabTriggerPrimitive, { value: "nfts", className: "data-[state=active]:bg-b3-primary-blue data-[state=active]:hover:bg-b3-primary-blue data-[state=active]:border-b3-primary-blue group flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white p-2 text-center shadow-sm transition-all duration-200 hover:bg-gray-50 hover:shadow-md data-[state=active]:shadow-lg", children: [_jsx(Image, { size: 20, className: "text-b3-primary-blue shrink-0 group-data-[state=active]:text-white" }), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm group-data-[state=active]:text-white", children: "NFTs" })] }), _jsxs(TabTriggerPrimitive, { value: "settings", className: "data-[state=active]:bg-b3-primary-blue data-[state=active]:hover:bg-b3-primary-blue data-[state=active]:border-b3-primary-blue group flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white p-2 text-center shadow-sm transition-all duration-200 hover:bg-gray-50 hover:shadow-md data-[state=active]:shadow-lg", children: [_jsx(Settings, { size: 20, className: "text-b3-primary-blue shrink-0 group-data-[state=active]:text-white" }), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm group-data-[state=active]:text-white", children: "Settings" })] })] }) }), _jsx(TabsContentPrimitive, { value: "overview", className: "px-4 pb-4 pt-2", children: _jsx(BalanceContent, { onLogout: onLogout, partnerId: partnerId }) }), _jsx(TabsContentPrimitive, { value: "tokens", className: "px-4 pb-4 pt-2", children: _jsx(ContentTokens, { activeTab: activeTab }) }), _jsx(TabsContentPrimitive, { value: "nfts", className: "px-4 pb-4 pt-2", children: _jsx("div", { className: "grid grid-cols-3 gap-4", children: nfts?.nftResponse ? (_jsx(AccountAssets, { nfts: nfts.nftResponse, isLoading: isLoading })) : (_jsx("div", { className: "col-span-3 py-12 text-center text-gray-500", children: "No NFTs found" })) }) }), _jsx(TabsContentPrimitive, { value: "apps", className: "px-4 pb-4 pt-2", children: _jsx(AppsContent, {}) }), _jsx(TabsContentPrimitive, { value: "settings", className: "px-4 pb-4 pt-2", children: _jsx(SettingsContent, {}) })] }) }) }));
|
|
138
99
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
interface TokenBalanceRowProps {
|
|
3
|
+
icon: ReactNode;
|
|
4
|
+
name: string;
|
|
5
|
+
balance: string;
|
|
6
|
+
usdValue: string;
|
|
7
|
+
priceChange?: number | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function TokenBalanceRow({ icon, name, balance, usdValue, priceChange }: TokenBalanceRowProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Triangle } from "lucide-react";
|
|
3
|
+
export function TokenBalanceRow({ icon, name, balance, usdValue, priceChange }) {
|
|
4
|
+
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full", children: icon }), _jsxs("div", { children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: name }) }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: balance })] })] }), _jsxs("div", { className: "text-right", children: [_jsxs("div", { className: "text-b3-grey font-neue-montreal-semibold", children: ["$", usdValue] }), _jsx("div", { className: "flex items-center gap-1", children: priceChange !== null && priceChange !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Triangle, { className: `size-3 ${priceChange >= 0 ? "text-b3-positive fill-b3-positive" : "text-b3-negative fill-b3-negative rotate-180"}` }), _jsxs("span", { className: `font-neue-montreal-medium text-sm ${priceChange >= 0 ? "text-b3-positive" : "text-b3-negative"}`, children: [priceChange >= 0 ? "+" : "", priceChange.toFixed(2), "%"] })] })) : (_jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "--" })) })] })] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface TokenIconProps {
|
|
3
|
+
src: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
size?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const TokenIcon: React.FC<TokenIconProps>;
|
|
9
|
+
export declare const B3TokenIcon: React.FC<Omit<TokenIconProps, "src" | "alt">>;
|
|
10
|
+
export declare const EthereumTokenIcon: React.FC<Omit<TokenIconProps, "src" | "alt">>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../../shared/utils/index.js";
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
// Create a global image cache to prevent re-loading
|
|
5
|
+
const imageCache = new Map();
|
|
6
|
+
export const TokenIcon = ({ src, alt, className, size = 40 }) => {
|
|
7
|
+
const [isLoaded, setIsLoaded] = useState(() => imageCache.get(src) || false);
|
|
8
|
+
const [hasError, setHasError] = useState(false);
|
|
9
|
+
const handleLoad = useCallback(() => {
|
|
10
|
+
imageCache.set(src, true);
|
|
11
|
+
setIsLoaded(true);
|
|
12
|
+
}, [src]);
|
|
13
|
+
const handleError = useCallback(() => {
|
|
14
|
+
setHasError(true);
|
|
15
|
+
}, []);
|
|
16
|
+
// Memoize the image element to prevent unnecessary re-renders
|
|
17
|
+
const imageElement = useMemo(() => (_jsx("img", { src: src, alt: alt, className: cn("transition-opacity duration-200", className, {
|
|
18
|
+
"opacity-100": isLoaded && !hasError,
|
|
19
|
+
"opacity-0": !isLoaded || hasError,
|
|
20
|
+
}), onLoad: handleLoad, onError: handleError, loading: "eager" // Load immediately since these are critical UI elements
|
|
21
|
+
, decoding: "async", style: {
|
|
22
|
+
width: size,
|
|
23
|
+
height: size,
|
|
24
|
+
} })), [src, alt, className, isLoaded, hasError, handleLoad, handleError, size]);
|
|
25
|
+
// Show a placeholder while loading or if there's an error
|
|
26
|
+
const placeholder = useMemo(() => (_jsx("div", { className: cn("bg-b3-primary-wash flex items-center justify-center rounded-full transition-opacity duration-200", {
|
|
27
|
+
"opacity-0": isLoaded && !hasError,
|
|
28
|
+
"opacity-100": !isLoaded || hasError,
|
|
29
|
+
}), style: {
|
|
30
|
+
width: size,
|
|
31
|
+
height: size,
|
|
32
|
+
}, children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-xs", children: alt.charAt(0).toUpperCase() }) })), [alt, isLoaded, hasError, size]);
|
|
33
|
+
return (_jsxs("div", { className: "relative inline-block", style: { width: size, height: size }, children: [placeholder, _jsx("div", { className: "absolute inset-0", children: imageElement })] }));
|
|
34
|
+
};
|
|
35
|
+
// Pre-defined token icons for common tokens
|
|
36
|
+
export const B3TokenIcon = props => (_jsx(TokenIcon, { src: "https://cdn.b3.fun/b3-coin-3d.png", alt: "B3", ...props }));
|
|
37
|
+
export const EthereumTokenIcon = props => (_jsx(TokenIcon, { src: "https://cdn.b3.fun/ethereum.svg", alt: "ETH", ...props }));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
declare const Accordion: React.ForwardRefExoticComponent<(AccordionPrimitive.AccordionSingleProps | AccordionPrimitive.AccordionMultipleProps) & React.RefAttributes<HTMLDivElement>>;
|
|
4
|
+
declare const AccordionItem: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
5
|
+
declare const AccordionTrigger: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
6
|
+
declare const AccordionContent: React.ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
4
|
+
import { ChevronDown } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { cn } from "../../../../shared/utils/index.js";
|
|
7
|
+
const Accordion = AccordionPrimitive.Root;
|
|
8
|
+
const AccordionItem = React.forwardRef(({ className, ...props }, ref) => (_jsx(AccordionPrimitive.Item, { ref: ref, className: cn("border-b3-line border-b", className), ...props })));
|
|
9
|
+
AccordionItem.displayName = "AccordionItem";
|
|
10
|
+
const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (_jsx(AccordionPrimitive.Header, { className: "flex", children: _jsxs(AccordionPrimitive.Trigger, { ref: ref, className: cn("flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", className), ...props, children: [children, _jsx(ChevronDown, { className: "h-4 w-4 shrink-0 transition-transform duration-200" })] }) })));
|
|
11
|
+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
|
12
|
+
const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (_jsx(AccordionPrimitive.Content, { ref: ref, className: "data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm transition-all", ...props, children: _jsx("div", { className: cn("pb-4 pt-0", className), children: children }) })));
|
|
13
|
+
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
|
14
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -12,7 +12,7 @@ const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (_jsx(D
|
|
|
12
12
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
13
13
|
const DialogContent = React.forwardRef(({ className, children, hideCloseButton = false, closeBtnClassName, ...props }, ref) => {
|
|
14
14
|
const container = typeof window !== "undefined" ? document.getElementById("b3-root") : null;
|
|
15
|
-
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn("bg-b3-react-background fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg !outline-none", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 duration-500", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]", "[perspective:1200px] [transform-style:preserve-3d] sm:rounded-xl", "transition-all ease-out", className), ...props, children: [children, !hideCloseButton && (_jsxs(DialogPrimitive.Close, { className: cn("data-[state=open]:bg-b3-react-background data-[state=open]:text-b3-react-muted-foreground absolute right-
|
|
15
|
+
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn("bg-b3-react-background fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg !outline-none", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 duration-500", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]", "[perspective:1200px] [transform-style:preserve-3d] sm:rounded-xl", "transition-all ease-out", className), ...props, children: [children, !hideCloseButton && (_jsxs(DialogPrimitive.Close, { className: cn("modal-close-button data-[state=open]:bg-b3-react-background data-[state=open]:text-b3-react-muted-foreground absolute right-2 top-2 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none dark:data-[state=open]:bg-gray-800 dark:data-[state=open]:text-gray-400", closeBtnClassName), children: [_jsx(X, { className: "h-5 w-5" }), _jsx("span", { className: "sr-only", children: "Close" })] }))] })] }));
|
|
16
16
|
});
|
|
17
17
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
18
18
|
const DialogHeader = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props }));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { useAccountAssets } from "./useAccountAssets";
|
|
2
2
|
export { useAccountWallet } from "./useAccountWallet";
|
|
3
3
|
export { useAddTWSessionKey } from "./useAddTWSessionKey";
|
|
4
|
+
export { useAnalytics } from "./useAnalytics";
|
|
4
5
|
export { useAuthentication } from "./useAuthentication";
|
|
5
6
|
export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
|
|
6
7
|
export { useB3EnsName } from "./useB3EnsName";
|
|
@@ -24,6 +25,7 @@ export { useQueryBSMNT } from "./useQueryBSMNT";
|
|
|
24
25
|
export { useRemoveSessionKey } from "./useRemoveSessionKey";
|
|
25
26
|
export { useRouter } from "./useRouter";
|
|
26
27
|
export { useSearchParamsSSR } from "./useSearchParamsSSR";
|
|
28
|
+
export { useSimBalance } from "./useSimBalance";
|
|
27
29
|
export { useSiwe } from "./useSiwe";
|
|
28
30
|
export { useTokenBalance } from "./useTokenBalance";
|
|
29
31
|
export { useTokenBalancesByChain } from "./useTokenBalancesByChain";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { useAccountAssets } from "./useAccountAssets.js";
|
|
2
2
|
export { useAccountWallet } from "./useAccountWallet.js";
|
|
3
3
|
export { useAddTWSessionKey } from "./useAddTWSessionKey.js";
|
|
4
|
+
export { useAnalytics } from "./useAnalytics.js";
|
|
4
5
|
export { useAuthentication } from "./useAuthentication.js";
|
|
5
6
|
export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses.js";
|
|
6
7
|
export { useB3EnsName } from "./useB3EnsName.js";
|
|
@@ -24,6 +25,7 @@ export { useQueryBSMNT } from "./useQueryBSMNT.js";
|
|
|
24
25
|
export { useRemoveSessionKey } from "./useRemoveSessionKey.js";
|
|
25
26
|
export { useRouter } from "./useRouter.js";
|
|
26
27
|
export { useSearchParamsSSR } from "./useSearchParamsSSR.js";
|
|
28
|
+
export { useSimBalance } from "./useSimBalance.js";
|
|
27
29
|
export { useSiwe } from "./useSiwe.js";
|
|
28
30
|
export { useTokenBalance } from "./useTokenBalance.js";
|
|
29
31
|
export { useTokenBalancesByChain } from "./useTokenBalancesByChain.js";
|