From 945864af7a09b8a5d07e7068fd37308f3a0c33ca Mon Sep 17 00:00:00 2001 From: defiQUG Date: Thu, 16 Apr 2026 14:48:30 -0700 Subject: [PATCH] Refactor block transaction drilldown state --- frontend/src/hooks/useBlockTransactions.ts | 77 +++++++++++++++++++ frontend/src/pages/blocks/[number].tsx | 58 ++++---------- .../src/services/api/transactions.test.ts | 6 +- frontend/src/services/api/transactions.ts | 22 ++++-- 4 files changed, 109 insertions(+), 54 deletions(-) create mode 100644 frontend/src/hooks/useBlockTransactions.ts diff --git a/frontend/src/hooks/useBlockTransactions.ts b/frontend/src/hooks/useBlockTransactions.ts new file mode 100644 index 0000000..40549d2 --- /dev/null +++ b/frontend/src/hooks/useBlockTransactions.ts @@ -0,0 +1,77 @@ +import { useCallback, useEffect, useState } from 'react' + +import { transactionsApi, type Transaction } from '@/services/api/transactions' + +const DEFAULT_BLOCK_TRANSACTION_PAGE_SIZE = 25 + +interface UseBlockTransactionsOptions { + blockNumber: number + chainId: number + enabled: boolean +} + +export function useBlockTransactions({ blockNumber, chainId, enabled }: UseBlockTransactionsOptions) { + const [transactions, setTransactions] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(false) + const [hasNextPage, setHasNextPage] = useState(false) + const [page, setPage] = useState(1) + + const loadTransactions = useCallback(async () => { + if (!enabled) { + setTransactions([]) + setLoading(false) + setError(false) + setHasNextPage(false) + return + } + + setLoading(true) + setError(false) + + try { + const result = await transactionsApi.listByBlockSafe( + chainId, + blockNumber, + page, + DEFAULT_BLOCK_TRANSACTION_PAGE_SIZE, + ) + setTransactions(result.items) + setHasNextPage(result.hasNextPage) + setError(!result.ok) + } catch (loadError) { + console.error('Failed to load block transactions:', loadError) + setTransactions([]) + setHasNextPage(false) + setError(true) + } finally { + setLoading(false) + } + }, [blockNumber, chainId, enabled, page]) + + useEffect(() => { + if (!enabled) { + setPage(1) + setTransactions([]) + setLoading(false) + setError(false) + setHasNextPage(false) + return + } + + setPage(1) + }, [blockNumber, enabled]) + + useEffect(() => { + void loadTransactions() + }, [loadTransactions]) + + return { + transactions, + loading, + error, + hasNextPage, + page, + setPage, + } +} diff --git a/frontend/src/pages/blocks/[number].tsx b/frontend/src/pages/blocks/[number].tsx index 97fd0a0..52cdf74 100644 --- a/frontend/src/pages/blocks/[number].tsx +++ b/frontend/src/pages/blocks/[number].tsx @@ -8,7 +8,8 @@ import Link from 'next/link' import { DetailRow } from '@/components/common/DetailRow' import PageIntro from '@/components/common/PageIntro' import { formatTimestamp, formatWeiAsEth } from '@/utils/format' -import { transactionsApi, type Transaction } from '@/services/api/transactions' +import { type Transaction } from '@/services/api/transactions' +import { useBlockTransactions } from '@/hooks/useBlockTransactions' export default function BlockDetailPage() { const router = useRouter() @@ -18,13 +19,20 @@ export default function BlockDetailPage() { const chainId = parseInt(process.env.NEXT_PUBLIC_CHAIN_ID || '138') const [block, setBlock] = useState(null) - const [blockTransactions, setBlockTransactions] = useState([]) const [loading, setLoading] = useState(true) - const [transactionsLoading, setTransactionsLoading] = useState(true) - const [transactionsError, setTransactionsError] = useState(false) - const [hasNextTransactionsPage, setHasNextTransactionsPage] = useState(false) - const [transactionPage, setTransactionPage] = useState(1) - const blockTransactionPageSize = 25 + + const { + transactions: blockTransactions, + loading: transactionsLoading, + error: transactionsError, + hasNextPage: hasNextTransactionsPage, + page: transactionPage, + setPage: setTransactionPage, + } = useBlockTransactions({ + blockNumber, + chainId, + enabled: router.isReady && isValidBlock, + }) const loadBlock = useCallback(async () => { setLoading(true) @@ -39,54 +47,18 @@ export default function BlockDetailPage() { } }, [chainId, blockNumber]) - const loadBlockTransactions = useCallback(async () => { - setTransactionsLoading(true) - setTransactionsError(false) - try { - const { ok, data, hasNextPage } = await transactionsApi.listByBlockSafe(chainId, blockNumber, transactionPage, blockTransactionPageSize) - setBlockTransactions(data) - setHasNextTransactionsPage(hasNextPage) - setTransactionsError(!ok) - } catch (error) { - console.error('Failed to load block transactions:', error) - setBlockTransactions([]) - setHasNextTransactionsPage(false) - setTransactionsError(true) - } finally { - setTransactionsLoading(false) - } - }, [blockNumber, blockTransactionPageSize, chainId, transactionPage]) - useEffect(() => { if (!router.isReady) { return } if (!isValidBlock) { setLoading(false) - setTransactionsLoading(false) - setTransactionsError(false) - setHasNextTransactionsPage(false) setBlock(null) - setBlockTransactions([]) return } void loadBlock() }, [isValidBlock, loadBlock, router.isReady]) - useEffect(() => { - if (!router.isReady || !isValidBlock) { - return - } - setTransactionPage(1) - }, [blockNumber, isValidBlock, router.isReady]) - - useEffect(() => { - if (!router.isReady || !isValidBlock) { - return - } - void loadBlockTransactions() - }, [isValidBlock, loadBlockTransactions, router.isReady]) - const gasUtilization = block && block.gas_limit > 0 ? Math.round((block.gas_used / block.gas_limit) * 100) : null diff --git a/frontend/src/services/api/transactions.test.ts b/frontend/src/services/api/transactions.test.ts index 2181b90..7f9146a 100644 --- a/frontend/src/services/api/transactions.test.ts +++ b/frontend/src/services/api/transactions.test.ts @@ -36,9 +36,9 @@ describe('transactionsApi.listByBlockSafe', () => { const result = await transactionsApi.listByBlockSafe(138, 123, 1, 10) expect(result.ok).toBe(true) - expect(result.data).toHaveLength(1) + expect(result.items).toHaveLength(1) expect(result.hasNextPage).toBe(true) - expect(result.data[0]?.hash).toBe('0xabc') + expect(result.items[0]?.hash).toBe('0xabc') expect(fetchMock).toHaveBeenCalledTimes(1) expect(fetchMock.mock.calls[0]?.[0]).toEqual( expect.stringContaining('/api/v2/blocks/123/transactions?page=1&page_size=10'), @@ -52,7 +52,7 @@ describe('transactionsApi.listByBlockSafe', () => { expect(result).toEqual({ ok: false, - data: [], + items: [], hasNextPage: false, }) }) diff --git a/frontend/src/services/api/transactions.ts b/frontend/src/services/api/transactions.ts index 1522507..da7c4d0 100644 --- a/frontend/src/services/api/transactions.ts +++ b/frontend/src/services/api/transactions.ts @@ -77,8 +77,14 @@ export interface TransactionLookupDiagnostic { } export interface BlockTransactionListPage { - data: Transaction[] - next_page_params: Record | null + items: Transaction[] + hasNextPage: boolean +} + +export interface SafeTransactionPage { + ok: boolean + items: T[] + hasNextPage: boolean } const CHAIN_138_PUBLIC_RPC_URL = 'https://rpc-http-pub.d-bis.org' @@ -243,8 +249,8 @@ export const transactionsApi = { const data = Array.isArray(raw?.items) ? raw.items.map((item) => normalizeTransaction(item as never, chainId)) : [] return { data: { - data, - next_page_params: raw?.next_page_params ?? null, + items: data, + hasNextPage: raw?.next_page_params != null, }, } }, @@ -253,16 +259,16 @@ export const transactionsApi = { blockNumber: number, page = 1, pageSize = 25, - ): Promise<{ ok: boolean; data: Transaction[]; hasNextPage: boolean }> => { + ): Promise> => { try { const { data } = await transactionsApi.listByBlock(chainId, blockNumber, page, pageSize) return { ok: true, - data: data.data, - hasNextPage: data.next_page_params != null, + items: data.items, + hasNextPage: data.hasNextPage, } } catch { - return { ok: false, data: [], hasNextPage: false } + return { ok: false, items: [], hasNextPage: false } } }, }