From 70992310e641cf9c948b82cee51b729b7ec29e09 Mon Sep 17 00:00:00 2001 From: sadiq1971 Date: Wed, 3 Jun 2026 16:19:20 +0600 Subject: [PATCH 1/2] fix(dapp): pre-select the clicked token on the Transfer page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A balances row's "Send →" opened Transfer but always defaulted to the first token in the list rather than the one clicked. Thread the chosen token address from the balances row through App into TransferPage, which now pre-selects the matching token (falling back to the first token when opened via plain sidebar navigation). Closes #66 --- .claude/scheduled_tasks.lock | 1 + packages/dapp/src/App.tsx | 35 +++++++++++++++++-- .../dapp/src/pages/DashboardBalancesPage.tsx | 8 ++++- packages/dapp/src/pages/TransferPage.tsx | 20 +++++++++-- 4 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..1236ac7 --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"b8586f35-35cb-4ee2-a80e-3ac683758a43","pid":63405,"procStart":"Tue Jun 2 07:04:56 2026","acquiredAt":1780481630395} \ No newline at end of file diff --git a/packages/dapp/src/App.tsx b/packages/dapp/src/App.tsx index 3c33c5f..61999b9 100644 --- a/packages/dapp/src/App.tsx +++ b/packages/dapp/src/App.tsx @@ -44,6 +44,9 @@ export default function App() { const [connectError, setConnectError] = useState(null); const [reconnecting, setReconnecting] = useState(false); const [dashboardTab, setDashboardTab] = useState(readTabFromHash); + // Token address to pre-select when opening the Transfer tab from a balances + // row's "Send →". Null on plain nav so Transfer falls back to the first token. + const [transferToken, setTransferToken] = useState(null); // Mirror dashboardTab into the URL hash so a refresh on /#balances stays on // Balances instead of snapping back to Profile. Only mutate the hash while @@ -120,10 +123,24 @@ export default function App() { mm.disconnect(); setProfile(null); clearAllSessions(); + setTransferToken(null); setDashboardTab("profile"); setPage("landing"); } + // Plain tab navigation (sidebar). Clears any pending pre-selection so the + // Transfer tab only honours a token when reached via a "Send →" row action. + const handleTabChange = useCallback((tab: DashboardTab) => { + setTransferToken(null); + setDashboardTab(tab); + }, []); + + // "Send →" on a balances row: pre-select that token, then open Transfer. + const handleSendToken = useCallback((tokenAddress: string) => { + setTransferToken(tokenAddress); + setDashboardTab("transfer"); + }, []); + const handleCustodial = useCallback(() => { setMode("custodial"); setPage("custodial-pending"); @@ -330,16 +347,28 @@ export default function App() { const sharedProps = { address, activeTab: dashboardTab, - onTabChange: setDashboardTab, + onTabChange: handleTabChange, onDisconnect: handleDisconnect, }; if (dashboardTab === "balances") { - return ; + return ( + + ); } if (dashboardTab === "transfer") { - return ; + return ( + + ); } if (dashboardTab === "activity") { diff --git a/packages/dapp/src/pages/DashboardBalancesPage.tsx b/packages/dapp/src/pages/DashboardBalancesPage.tsx index 4da0d54..b3ef2d6 100644 --- a/packages/dapp/src/pages/DashboardBalancesPage.tsx +++ b/packages/dapp/src/pages/DashboardBalancesPage.tsx @@ -35,6 +35,8 @@ interface Props { onTabChange: (tab: DashboardTab) => void; onDisconnect: () => void; keyMode: "custodial" | "external"; + /** Open the Transfer tab with this token pre-selected. */ + onSendToken: (tokenAddress: string) => void; } type OfferRowState = "idle" | "preparing" | "signing" | "executing"; @@ -170,6 +172,7 @@ export function DashboardBalancesPage({ onTabChange, onDisconnect, keyMode, + onSendToken, }: Props) { const [fetchState, setFetchState] = useState(null); const [offers, setOffers] = useState(null); @@ -444,7 +447,10 @@ export function DashboardBalancesPage({

{token.symbol}

- diff --git a/packages/dapp/src/pages/TransferPage.tsx b/packages/dapp/src/pages/TransferPage.tsx index 2bad065..f265e60 100644 --- a/packages/dapp/src/pages/TransferPage.tsx +++ b/packages/dapp/src/pages/TransferPage.tsx @@ -42,6 +42,8 @@ interface Props { onTabChange: (tab: DashboardTab) => void; onDisconnect: () => void; keyMode: "custodial" | "external"; + /** Token address to pre-select on mount (set when opened via a balances "Send →"). */ + preselectTokenAddress?: string | null; } function TokenAvatar({ symbol }: { symbol: string }) { @@ -229,7 +231,14 @@ function TokenDropdown({ tokens, selected, balances, onSelect }: TokenDropdownPr ); } -export function TransferPage({ address, activeTab, onTabChange, onDisconnect, keyMode }: Props) { +export function TransferPage({ + address, + activeTab, + onTabChange, + onDisconnect, + keyMode, + preselectTokenAddress, +}: Props) { const [step, setStep] = useState("details"); const [tokens, setTokens] = useState([]); const [tokensLoading, setTokensLoading] = useState(true); @@ -259,7 +268,12 @@ export function TransferPage({ address, activeTab, onTabChange, onDisconnect, ke const list = await getTokens(NETWORK.middlewareUrl); if (cancelled) return; setTokens(list); - if (list.length > 0) setSelectedToken(list[0]); + if (list.length > 0) { + const match = preselectTokenAddress + ? list.find((t) => t.address.toLowerCase() === preselectTokenAddress.toLowerCase()) + : undefined; + setSelectedToken(match ?? list[0]); + } const entries = await Promise.all( list.map(async (t) => { try { @@ -282,7 +296,7 @@ export function TransferPage({ address, activeTab, onTabChange, onDisconnect, ke return () => { cancelled = true; }; - }, [address]); + }, [address, preselectTokenAddress]); // Auto-run prepare when entering sign step (non-custodial only). // Depends only on [step]: fires once on entry; form fields are captured via closure From 59ce208eab74a8b57494fc5fcec6cc2f12acb62b Mon Sep 17 00:00:00 2001 From: sadiq1971 Date: Wed, 3 Jun 2026 16:41:20 +0600 Subject: [PATCH 2/2] refactor(dapp): decouple token pre-selection from the fetch effect Address review feedback: reading preselectTokenAddress inside the main token-fetch effect's dependency array refetched the whole token list and balances whenever it changed. Capture it in a ref (it's fixed for the page's lifetime) and read it inside the async load, so deps stay [address] and a pre-selection no longer triggers a refetch or loading flash. Also stop tracking .claude/ (accidental scheduled_tasks.lock) and ignore it. --- .claude/scheduled_tasks.lock | 1 - .gitignore | 1 + packages/dapp/src/pages/TransferPage.tsx | 13 ++++++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) delete mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index 1236ac7..0000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"b8586f35-35cb-4ee2-a80e-3ac683758a43","pid":63405,"procStart":"Tue Jun 2 07:04:56 2026","acquiredAt":1780481630395} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8c8ea4c..b1b3798 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist/ .DS_Store .env .env.local +.claude/ diff --git a/packages/dapp/src/pages/TransferPage.tsx b/packages/dapp/src/pages/TransferPage.tsx index f265e60..f6f53a6 100644 --- a/packages/dapp/src/pages/TransferPage.tsx +++ b/packages/dapp/src/pages/TransferPage.tsx @@ -257,6 +257,12 @@ export function TransferPage({ const snap = useSnap(); const isNonCustodial = keyMode === "external"; + // Pre-selection captured at mount — it's fixed for the lifetime of this + // page (only set when navigating in from a balances "Send →"). Held in a + // ref so the fetch effect can honour it without taking it as a dependency, + // which would otherwise refetch the token list/balances on every change. + const preselectRef = useRef(preselectTokenAddress); + // Load token list then fetch all balances useEffect(() => { let cancelled = false; @@ -269,8 +275,9 @@ export function TransferPage({ if (cancelled) return; setTokens(list); if (list.length > 0) { - const match = preselectTokenAddress - ? list.find((t) => t.address.toLowerCase() === preselectTokenAddress.toLowerCase()) + const pre = preselectRef.current; + const match = pre + ? list.find((t) => t.address.toLowerCase() === pre.toLowerCase()) : undefined; setSelectedToken(match ?? list[0]); } @@ -296,7 +303,7 @@ export function TransferPage({ return () => { cancelled = true; }; - }, [address, preselectTokenAddress]); + }, [address]); // Auto-run prepare when entering sign step (non-custodial only). // Depends only on [step]: fires once on entry; form fields are captured via closure