From c1cc2f874a47b04d553cb460a633c5246841bcb4 Mon Sep 17 00:00:00 2001 From: isstuev Date: Thu, 1 Aug 2024 16:10:51 +0200 Subject: [PATCH] arbitrum latest batches and deposits --- lib/api/resources.ts | 22 ++- lib/socket/types.ts | 9 +- types/api/arbitrumL2.ts | 2 + ui/home/Stats.tsx | 31 ++-- ui/home/Transactions.tsx | 12 +- ...r-mode_default-view-mobile-dark-mode-1.png | Bin 0 -> 18893 bytes ...efault_default-view-mobile-dark-mode-1.png | Bin 0 -> 18567 bytes ...mobile_default-view-mobile-dark-mode-1.png | Bin 0 -> 15624 bytes .../LatestArbitrumL2Batches.pw.tsx | 15 ++ .../latestBatches/LatestArbitrumL2Batches.tsx | 92 ++++++++++ .../LatestBatchItem.tsx} | 24 +-- .../LatestZkEvmL2Batches.pw.tsx | 0 .../LatestZkEvmL2Batches.tsx | 23 ++- .../latestDeposits/LatestArbitrumDeposits.tsx | 77 +++++++++ ui/home/latestDeposits/LatestDeposits.tsx | 157 ++++++++++++++++++ .../LatestDepositsItem.tsx | 30 ++-- .../LatestOptimisticDeposits.pw.tsx} | 6 +- .../LatestOptimisticDeposits.tsx} | 40 ++--- ui/pages/Home.tsx | 7 +- 19 files changed, 457 insertions(+), 90 deletions(-) create mode 100644 ui/home/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png create mode 100644 ui/home/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png create mode 100644 ui/home/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png create mode 100644 ui/home/latestBatches/LatestArbitrumL2Batches.pw.tsx create mode 100644 ui/home/latestBatches/LatestArbitrumL2Batches.tsx rename ui/home/{LatestZkevmL2BatchItem.tsx => latestBatches/LatestBatchItem.tsx} (75%) rename ui/home/{ => latestBatches}/LatestZkEvmL2Batches.pw.tsx (100%) rename ui/home/{ => latestBatches}/LatestZkEvmL2Batches.tsx (79%) create mode 100644 ui/home/latestDeposits/LatestArbitrumDeposits.tsx create mode 100644 ui/home/latestDeposits/LatestDeposits.tsx rename ui/home/{ => latestDeposits}/LatestDepositsItem.tsx (82%) rename ui/home/{LatestDeposits.pw.tsx => latestDeposits/LatestOptimisticDeposits.pw.tsx} (64%) rename ui/home/{LatestDeposits.tsx => latestDeposits/LatestOptimisticDeposits.tsx} (56%) diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 3f1d6cf6f1..04644efcb6 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -43,10 +43,12 @@ import type { AddressesResponse } from 'types/api/addresses'; import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata'; import type { ArbitrumL2MessagesResponse, + ArbitrumL2MessagesItem, ArbitrumL2TxnBatch, ArbitrumL2TxnBatchesResponse, ArbitrumL2BatchTxs, ArbitrumL2BatchBlocks, + ArbitrumL2TxnBatchesItem, } from 'types/api/arbitrumL2'; import type { TxBlobs, Blob } from 'types/api/blobs'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse, BlockCountdownResponse } from 'types/api/block'; @@ -580,15 +582,21 @@ export const RESOURCES = { homepage_blocks: { path: '/api/v2/main-page/blocks', }, - homepage_deposits: { + homepage_optimistic_deposits: { path: '/api/v2/main-page/optimism-deposits', }, + homepage_arbitrum_deposits: { + path: '/api/v2/main-page/arbitrum/messages/to-rollup', + }, homepage_txs: { path: '/api/v2/main-page/transactions', }, homepage_zkevm_l2_batches: { path: '/api/v2/main-page/zkevm/batches/confirmed', }, + homepage_arbitrum_l2_batches: { + path: '/api/v2/main-page/arbitrum/batches/committed', + }, homepage_txs_watchlist: { path: '/api/v2/main-page/transactions/watchlist', }, @@ -601,6 +609,9 @@ export const RESOURCES = { homepage_zksync_latest_batch: { path: '/api/v2/main-page/zksync/batches/latest-number', }, + homepage_arbitrum_latest_batch: { + path: '/api/v2/main-page/arbitrum/batches/latest-number', + }, // SEARCH quick_search: { @@ -966,11 +977,14 @@ Q extends 'stats_charts_secondary_coin_price' ? ChartSecondaryCoinPriceResponse Q extends 'homepage_blocks' ? Array : Q extends 'homepage_txs' ? Array : Q extends 'homepage_txs_watchlist' ? Array : -Q extends 'homepage_deposits' ? Array : +Q extends 'homepage_optimistic_deposits' ? Array : +Q extends 'homepage_arbitrum_deposits' ? { items: Array } : Q extends 'homepage_zkevm_l2_batches' ? { items: Array } : +Q extends 'homepage_arbitrum_l2_batches' ? { items: Array} : Q extends 'homepage_indexing_status' ? IndexingStatus : Q extends 'homepage_zkevm_latest_batch' ? number : Q extends 'homepage_zksync_latest_batch' ? number : +Q extends 'homepage_arbitrum_latest_batch' ? number : Q extends 'stats_counters' ? stats.Counters : Q extends 'stats_lines' ? stats.LineCharts : Q extends 'stats_line' ? stats.LineChart : @@ -1029,8 +1043,6 @@ Q extends 'verified_contracts' ? VerifiedContractsResponse : Q extends 'verified_contracts_counters' ? VerifiedContractsCounters : Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse : Q extends 'contract_verification_config' ? SmartContractVerificationConfigRaw : -Q extends 'withdrawals' ? WithdrawalsResponse : -Q extends 'withdrawals_counters' ? WithdrawalsCounters : Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse : Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse : Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse : @@ -1097,6 +1109,8 @@ Q extends 'address_mud_tables' ? AddressMudTables : Q extends 'address_mud_tables_count' ? number : Q extends 'address_mud_records' ? AddressMudRecords : Q extends 'address_mud_record' ? AddressMudRecord : +Q extends 'withdrawals' ? WithdrawalsResponse : +Q extends 'withdrawals_counters' ? WithdrawalsCounters : never; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/socket/types.ts b/lib/socket/types.ts index 96ec44ca7e..927b7ec7ea 100644 --- a/lib/socket/types.ts +++ b/lib/socket/types.ts @@ -1,6 +1,7 @@ import type { Channel } from 'phoenix'; import type { AddressCoinBalanceHistoryItem, AddressTokensBalancesSocketMessage } from 'types/api/address'; +import type { NewArbitrumBatchSocketResponse } from 'types/api/arbitrumL2'; import type { NewBlockSocketResponse } from 'types/api/block'; import type { SmartContractVerificationResponse } from 'types/api/contract'; import type { RawTracesResponse } from 'types/api/rawTrace'; @@ -16,7 +17,8 @@ SocketMessage.TxStatusUpdate | SocketMessage.TxRawTrace | SocketMessage.NewTx | SocketMessage.NewPendingTx | -SocketMessage.NewDeposits | +SocketMessage.NewOptimisticDeposits | +SocketMessage.NewArbitrumDeposits | SocketMessage.AddressBalance | SocketMessage.AddressCurrentCoinBalance | SocketMessage.AddressTokenBalance | @@ -36,6 +38,7 @@ SocketMessage.TokenTotalSupply | SocketMessage.TokenInstanceMetadataFetched | SocketMessage.ContractVerification | SocketMessage.NewZkEvmL2Batch | +SocketMessage.NewArbitrumL2Batch | SocketMessage.Unknown; interface SocketMessageParamsGeneric { @@ -53,7 +56,8 @@ export namespace SocketMessage { export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>; export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; - export type NewDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>; + export type NewOptimisticDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>; + export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>; export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; export type AddressCurrentCoinBalance = SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>; @@ -74,5 +78,6 @@ export namespace SocketMessage { export type TokenInstanceMetadataFetched = SocketMessageParamsGeneric<'fetched_token_instance_metadata', TokenInstanceMetadataSocketMessage>; export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>; export type NewZkEvmL2Batch = SocketMessageParamsGeneric<'new_zkevm_confirmed_batch', NewZkEvmBatchSocketResponse>; + export type NewArbitrumL2Batch = SocketMessageParamsGeneric<'new_arbitrum_batch', NewArbitrumBatchSocketResponse>; export type Unknown = SocketMessageParamsGeneric; } diff --git a/types/api/arbitrumL2.ts b/types/api/arbitrumL2.ts index 7e6fd25442..b4f6aabc8a 100644 --- a/types/api/arbitrumL2.ts +++ b/types/api/arbitrumL2.ts @@ -84,3 +84,5 @@ export const ARBITRUM_L2_TX_BATCH_STATUSES = [ ]; export type ArbitrumBatchStatus = typeof ARBITRUM_L2_TX_BATCH_STATUSES[number]; + +export type NewArbitrumBatchSocketResponse = { batch: ArbitrumL2TxnBatchesItem } diff --git a/ui/home/Stats.tsx b/ui/home/Stats.tsx index 685ee00c2f..09e73f081e 100644 --- a/ui/home/Stats.tsx +++ b/ui/home/Stats.tsx @@ -46,13 +46,21 @@ const Stats = () => { }, }); - if (isError || zkEvmLatestBatchQuery.isError || zkSyncLatestBatchQuery.isError) { + const arbitrumLatestBatchQuery = useApiQuery('homepage_arbitrum_latest_batch', { + queryOptions: { + placeholderData: 12345, + enabled: rollupFeature.isEnabled && rollupFeature.type === 'arbitrum', + }, + }); + + if (isError || zkEvmLatestBatchQuery.isError || zkSyncLatestBatchQuery.isError || arbitrumLatestBatchQuery.isError) { return null; } const isLoading = isPlaceholderData || (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && zkEvmLatestBatchQuery.isPlaceholderData) || - (rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && zkSyncLatestBatchQuery.isPlaceholderData); + (rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && zkSyncLatestBatchQuery.isPlaceholderData) || + (rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && arbitrumLatestBatchQuery.isPlaceholderData); const content = (() => { if (!data) { @@ -72,22 +80,21 @@ const Stats = () => { ) : null; + const hasBatches = rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum'); + const latestBatch = + (hasBatches && rollupFeature.type === 'zkEvm' ? zkEvmLatestBatchQuery.data : null) || + (hasBatches && rollupFeature.type === 'zkSync' ? zkSyncLatestBatchQuery.data : null) || + (hasBatches && rollupFeature.type === 'arbitrum' ? arbitrumLatestBatchQuery.data : null) || 0; + const items: Array = [ - rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && { - icon: 'txn_batches_slim' as const, - label: 'Latest batch', - value: (zkEvmLatestBatchQuery.data || 0).toLocaleString(), - href: { pathname: '/batches' as const }, - isLoading, - }, - rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && { + hasBatches && { icon: 'txn_batches_slim' as const, label: 'Latest batch', - value: (zkSyncLatestBatchQuery.data || 0).toLocaleString(), + value: latestBatch.toLocaleString(), href: { pathname: '/batches' as const }, isLoading, }, - !(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync')) && { + !hasBatches && { icon: 'block_slim' as const, label: 'Total blocks', value: Number(data.total_blocks).toLocaleString(), diff --git a/ui/home/Transactions.tsx b/ui/home/Transactions.tsx index 8000dc28f7..843c81a575 100644 --- a/ui/home/Transactions.tsx +++ b/ui/home/Transactions.tsx @@ -3,10 +3,13 @@ import React from 'react'; import config from 'configs/app'; import useHasAccount from 'lib/hooks/useHasAccount'; -import LatestDeposits from 'ui/home/LatestDeposits'; +import LatestOptimisticDeposits from 'ui/home/latestDeposits/LatestOptimisticDeposits'; import LatestTxs from 'ui/home/LatestTxs'; import LatestWatchlistTxs from 'ui/home/LatestWatchlistTxs'; import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll'; + +import LatestArbitrumDeposits from './latestDeposits/LatestArbitrumDeposits'; + const rollupFeature = config.features.rollup; const TAB_LIST_PROPS = { @@ -15,10 +18,13 @@ const TAB_LIST_PROPS = { const TransactionsHome = () => { const hasAccount = useHasAccount(); - if ((rollupFeature.isEnabled && rollupFeature.type === 'optimistic') || hasAccount) { + if ((rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum')) || hasAccount) { const tabs = [ { id: 'txn', title: 'Latest txn', component: }, - rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && { id: 'deposits', title: 'Deposits (L1→L2 txn)', component: }, + rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && + { id: 'deposits', title: 'Deposits (L1→L2 txn)', component: }, + rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && + { id: 'deposits', title: 'Deposits (L1→L2 txn)', component: }, hasAccount && { id: 'watchlist', title: 'Watch list', component: }, ].filter(Boolean); return ( diff --git a/ui/home/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png new file mode 100644 index 0000000000000000000000000000000000000000..12bf45725452df3e7f381b7964ffb4c48375acd9 GIT binary patch literal 18893 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 VnB5b+Ai*|v`|8c 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 literal 0 HcmV?d00001 diff --git a/ui/home/latestBatches/LatestArbitrumL2Batches.pw.tsx b/ui/home/latestBatches/LatestArbitrumL2Batches.pw.tsx new file mode 100644 index 0000000000..373541d1b7 --- /dev/null +++ b/ui/home/latestBatches/LatestArbitrumL2Batches.pw.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { finalized, unfinalized } from 'mocks/arbitrum/txnBatches'; +import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; +import { test, expect } from 'playwright/lib'; + +import LatestArbitrumL2Batches from './LatestArbitrumL2Batches'; + +test('default view +@mobile +@dark-mode', async({ render, mockEnvs, mockApiResponse }) => { + await mockEnvs(ENVS_MAP.arbitrumRollup); + await mockApiResponse('homepage_arbitrum_l2_batches', { items: [ finalized, unfinalized ] }); + + const component = await render(); + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/home/latestBatches/LatestArbitrumL2Batches.tsx b/ui/home/latestBatches/LatestArbitrumL2Batches.tsx new file mode 100644 index 0000000000..d13a9398f6 --- /dev/null +++ b/ui/home/latestBatches/LatestArbitrumL2Batches.tsx @@ -0,0 +1,92 @@ +import { Box, Heading, Flex, Text, VStack } from '@chakra-ui/react'; +import { useQueryClient } from '@tanstack/react-query'; +import { AnimatePresence } from 'framer-motion'; +import React from 'react'; + +import type { SocketMessage } from 'lib/socket/types'; +import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2'; + +import { route } from 'nextjs-routes'; + +import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; +import useIsMobile from 'lib/hooks/useIsMobile'; +import useSocketChannel from 'lib/socket/useSocketChannel'; +import useSocketMessage from 'lib/socket/useSocketMessage'; +import { ARBITRUM_L2_TXN_BATCHES_ITEM } from 'stubs/arbitrumL2'; +import LinkInternal from 'ui/shared/links/LinkInternal'; + +import LatestBatchItem from './LatestBatchItem'; + +const LatestArbitrumL2Batches = () => { + const isMobile = useIsMobile(); + const batchesMaxCount = isMobile ? 2 : 5; + const queryClient = useQueryClient(); + + const { data, isPlaceholderData, isError } = useApiQuery('homepage_arbitrum_l2_batches', { + queryOptions: { + placeholderData: { items: Array(batchesMaxCount).fill(ARBITRUM_L2_TXN_BATCHES_ITEM) }, + }, + }); + + const handleNewBatchMessage: SocketMessage.NewArbitrumL2Batch['handler'] = React.useCallback((payload) => { + queryClient.setQueryData(getResourceKey('homepage_arbitrum_l2_batches'), (prevData: { items: Array } | undefined) => { + const newItems = prevData?.items ? [ ...prevData.items ] : []; + + if (newItems.some((batch => batch.number === payload.batch.number))) { + return { items: newItems }; + } + + return { items: [ payload.batch, ...newItems ].sort((b1, b2) => b2.number - b1.number).slice(0, batchesMaxCount) }; + }); + }, [ queryClient, batchesMaxCount ]); + + const channel = useSocketChannel({ + topic: 'arbitrum:new_batch', + isDisabled: isPlaceholderData || isError, + }); + useSocketMessage({ + channel, + event: 'new_arbitrum_batch', + handler: handleNewBatchMessage, + }); + + let content; + + if (isError) { + content = No data. Please reload page.; + } + + if (data) { + const dataToShow = data.items.slice(0, batchesMaxCount); + + content = ( + <> + + + { dataToShow.map(((batch, index) => ( + + ))) } + + + + View all batches + + + ); + } + + return ( + + Latest batches + { content } + + ); +}; + +export default LatestArbitrumL2Batches; diff --git a/ui/home/LatestZkevmL2BatchItem.tsx b/ui/home/latestBatches/LatestBatchItem.tsx similarity index 75% rename from ui/home/LatestZkevmL2BatchItem.tsx rename to ui/home/latestBatches/LatestBatchItem.tsx index 3c6bdfac42..d148d63779 100644 --- a/ui/home/LatestZkevmL2BatchItem.tsx +++ b/ui/home/latestBatches/LatestBatchItem.tsx @@ -6,21 +6,21 @@ import { import { motion } from 'framer-motion'; import React from 'react'; -import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2'; - import { route } from 'nextjs-routes'; import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; import LinkInternal from 'ui/shared/links/LinkInternal'; -import ZkEvmL2TxnBatchStatus from 'ui/shared/statusTag/ZkEvmL2TxnBatchStatus'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; type Props = { - batch: ZkEvmL2TxnBatchesItem; - isLoading?: boolean; + number: number; + timestamp: string | null; + txCount: number; + status?: React.ReactNode; + isLoading: boolean; } -const LatestZkevmL2BatchItem = ({ batch, isLoading }: Props) => { +const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Props) => { return ( { { mr="auto" /> { Txn - { batch.tx_count } + { txCount } - + { status } ); }; -export default LatestZkevmL2BatchItem; +export default LatestBatchItem; diff --git a/ui/home/LatestZkEvmL2Batches.pw.tsx b/ui/home/latestBatches/LatestZkEvmL2Batches.pw.tsx similarity index 100% rename from ui/home/LatestZkEvmL2Batches.pw.tsx rename to ui/home/latestBatches/LatestZkEvmL2Batches.pw.tsx diff --git a/ui/home/LatestZkEvmL2Batches.tsx b/ui/home/latestBatches/LatestZkEvmL2Batches.tsx similarity index 79% rename from ui/home/LatestZkEvmL2Batches.tsx rename to ui/home/latestBatches/LatestZkEvmL2Batches.tsx index a9fe76c8a8..21f0910828 100644 --- a/ui/home/LatestZkEvmL2Batches.tsx +++ b/ui/home/latestBatches/LatestZkEvmL2Batches.tsx @@ -14,8 +14,9 @@ import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; import { ZKEVM_L2_TXN_BATCHES_ITEM } from 'stubs/zkEvmL2'; import LinkInternal from 'ui/shared/links/LinkInternal'; +import ZkEvmL2TxnBatchStatus from 'ui/shared/statusTag/ZkEvmL2TxnBatchStatus'; -import LatestZkevmL2BatchItem from './LatestZkevmL2BatchItem'; +import LatestBatchItem from './LatestBatchItem'; const LatestZkEvmL2Batches = () => { const isMobile = useIsMobile(); @@ -63,13 +64,19 @@ const LatestZkEvmL2Batches = () => { <> - { dataToShow.map(((batch, index) => ( - - ))) } + { dataToShow.map(((batch, index) => { + const status = ; + return ( + + ); + })) } diff --git a/ui/home/latestDeposits/LatestArbitrumDeposits.tsx b/ui/home/latestDeposits/LatestArbitrumDeposits.tsx new file mode 100644 index 0000000000..2031327410 --- /dev/null +++ b/ui/home/latestDeposits/LatestArbitrumDeposits.tsx @@ -0,0 +1,77 @@ +import { Text } from '@chakra-ui/react'; +import React from 'react'; + +import type { SocketMessage } from 'lib/socket/types'; + +import useApiQuery from 'lib/api/useApiQuery'; +import useGradualIncrement from 'lib/hooks/useGradualIncrement'; +import useIsMobile from 'lib/hooks/useIsMobile'; +import useSocketChannel from 'lib/socket/useSocketChannel'; +import useSocketMessage from 'lib/socket/useSocketMessage'; +import { ARBITRUM_MESSAGES_ITEM } from 'stubs/arbitrumL2'; + +import LatestDeposits from './LatestDeposits'; + +const LatestArbitrumDeposits = () => { + const isMobile = useIsMobile(); + const itemsCount = isMobile ? 2 : 6; + const { data, isPlaceholderData, isError } = useApiQuery('homepage_arbitrum_deposits', { + queryOptions: { + placeholderData: { items: Array(itemsCount).fill(ARBITRUM_MESSAGES_ITEM) }, + }, + }); + + const [ num, setNum ] = useGradualIncrement(0); + const [ socketAlert, setSocketAlert ] = React.useState(''); + + const handleSocketClose = React.useCallback(() => { + setSocketAlert('Connection is lost. Please reload page.'); + }, []); + + const handleSocketError = React.useCallback(() => { + setSocketAlert('An error has occurred while fetching new transactions. Please reload page.'); + }, []); + + const handleNewDepositMessage: SocketMessage.NewArbitrumDeposits['handler'] = React.useCallback((payload) => { + setNum(payload.new_messages_to_rollup_amount); + }, [ setNum ]); + + const channel = useSocketChannel({ + topic: 'arbitrum:new_messages_to_rollup_amount', + onSocketClose: handleSocketClose, + onSocketError: handleSocketError, + isDisabled: false, + }); + + useSocketMessage({ + channel, + event: 'new_messages_to_rollup_amount', + handler: handleNewDepositMessage, + }); + + if (isError) { + return No data. Please reload page.; + } + + if (data) { + return ( + ( + { + l1BlockNumber: item.origination_transaction_block_number, + l1TxHash: item.origination_transaction_hash, + l2TxHash: item.completion_transaction_hash, + timestamp: item.origination_timestamp, + } + )) } + isLoading={ isPlaceholderData } + socketItemsNum={ num } + socketAlert={ socketAlert } + /> + ); + } + + return null; +}; + +export default LatestArbitrumDeposits; diff --git a/ui/home/latestDeposits/LatestDeposits.tsx b/ui/home/latestDeposits/LatestDeposits.tsx new file mode 100644 index 0000000000..0ed6f84a23 --- /dev/null +++ b/ui/home/latestDeposits/LatestDeposits.tsx @@ -0,0 +1,157 @@ +import { + Box, + Flex, + Grid, + Skeleton, +} from '@chakra-ui/react'; +import React from 'react'; + +import { route } from 'nextjs-routes'; + +import useIsMobile from 'lib/hooks/useIsMobile'; +import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; +import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; +import LinkInternal from 'ui/shared/links/LinkInternal'; +import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; +import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; + +type DepositsItem = { + l1BlockNumber: number; + l1TxHash: string; + l2TxHash: string | null; + timestamp: string | null; +} + +type Props = { + isLoading?: boolean; + items: Array; + socketItemsNum: number; + socketAlert?: string; +} + +type ItemProps = { + item: DepositsItem; + isLoading?: boolean; +} + +const LatestDepositsItem = ({ item, isLoading }: ItemProps) => { + const isMobile = useIsMobile(); + + const l1BlockLink = ( + + ); + + const l1TxLink = ( + + ); + + const l2TxLink = item.l2TxHash ? ( + + ) : null; + + const content = (() => { + if (isMobile) { + return ( + <> + + { l1BlockLink } + + + + + L1 txn + + { l1TxLink } + + L2 txn + + { l2TxLink } + + + ); + } + + return ( + + { l1BlockLink } + + L1 txn + + { l1TxLink } + + + L2 txn + + { l2TxLink } + + ); + })(); + + return ( + + { content } + + ); +}; + +const LatestDeposits = ({ isLoading, items, socketAlert, socketItemsNum }: Props) => { + const depositsUrl = route({ pathname: '/deposits' }); + return ( + <> + + + { items.map(((item, index) => ( + + ))) } + + + View all deposits + + + ); +}; + +export default LatestDeposits; diff --git a/ui/home/LatestDepositsItem.tsx b/ui/home/latestDeposits/LatestDepositsItem.tsx similarity index 82% rename from ui/home/LatestDepositsItem.tsx rename to ui/home/latestDeposits/LatestDepositsItem.tsx index a3b9277235..c6b01abcf1 100644 --- a/ui/home/LatestDepositsItem.tsx +++ b/ui/home/latestDeposits/LatestDepositsItem.tsx @@ -6,32 +6,26 @@ import { } from '@chakra-ui/react'; import React from 'react'; -import type { OptimisticL2DepositsItem } from 'types/api/optimisticL2'; - -import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip'; -const feature = config.features.rollup; - type Props = { - item: OptimisticL2DepositsItem; + l1BlockNumber: number; + l1TxHash: string; + l2TxHash: string | null; + timestamp: string | null; isLoading?: boolean; } -const LatestDepositsItem = ({ item, isLoading }: Props) => { +const LatestDepositsItem = ({ l1BlockNumber, l1TxHash, l2TxHash, timestamp, isLoading }: Props) => { const isMobile = useIsMobile(); - if (!feature.isEnabled || feature.type !== 'optimistic') { - return null; - } - const l1BlockLink = ( { const l1TxLink = ( ); - const l2TxLink = ( + const l2TxLink = l2TxHash ? ( - ); + ) : null; const content = (() => { if (isMobile) { @@ -66,7 +60,7 @@ const LatestDepositsItem = ({ item, isLoading }: Props) => { { l1BlockLink } @@ -93,7 +87,7 @@ const LatestDepositsItem = ({ item, isLoading }: Props) => { { l1TxLink } { await mockEnvs(ENVS_MAP.optimisticRollup); - mockApiResponse('homepage_deposits', depositMock.data.items); - const component = await render(); + mockApiResponse('homepage_optimistic_deposits', depositMock.data.items); + const component = await render(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/home/LatestDeposits.tsx b/ui/home/latestDeposits/LatestOptimisticDeposits.tsx similarity index 56% rename from ui/home/LatestDeposits.tsx rename to ui/home/latestDeposits/LatestOptimisticDeposits.tsx index 89558bad0f..5483700146 100644 --- a/ui/home/LatestDeposits.tsx +++ b/ui/home/latestDeposits/LatestOptimisticDeposits.tsx @@ -1,25 +1,21 @@ -import { Box, Flex, Text } from '@chakra-ui/react'; +import { Text } from '@chakra-ui/react'; import React from 'react'; import type { SocketMessage } from 'lib/socket/types'; -import { route } from 'nextjs-routes'; - import useApiQuery from 'lib/api/useApiQuery'; import useGradualIncrement from 'lib/hooks/useGradualIncrement'; import useIsMobile from 'lib/hooks/useIsMobile'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; import { L2_DEPOSIT_ITEM } from 'stubs/L2'; -import LinkInternal from 'ui/shared/links/LinkInternal'; -import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; -import LatestDepositsItem from './LatestDepositsItem'; +import LatestDeposits from './LatestDeposits'; -const LatestDeposits = () => { +const LatestOptimisticDeposits = () => { const isMobile = useIsMobile(); const itemsCount = isMobile ? 2 : 6; - const { data, isPlaceholderData, isError } = useApiQuery('homepage_deposits', { + const { data, isPlaceholderData, isError } = useApiQuery('homepage_optimistic_deposits', { queryOptions: { placeholderData: Array(itemsCount).fill(L2_DEPOSIT_ITEM), }, @@ -36,7 +32,7 @@ const LatestDeposits = () => { setSocketAlert('An error has occurred while fetching new transactions. Please reload page.'); }, []); - const handleNewDepositMessage: SocketMessage.NewDeposits['handler'] = React.useCallback((payload) => { + const handleNewDepositMessage: SocketMessage.NewOptimisticDeposits['handler'] = React.useCallback((payload) => { setNum(payload.deposits); }, [ setNum ]); @@ -58,27 +54,19 @@ const LatestDeposits = () => { } if (data) { - const depositsUrl = route({ pathname: '/deposits' }); return ( - <> - - - { data.slice(0, itemsCount).map(((item, index) => ( - - ))) } - - - View all deposits - - + ( + { l1BlockNumber: item.l1_block_number, l1TxHash: item.l1_tx_hash, l2TxHash: item.l2_tx_hash, timestamp: item.l1_block_timestamp } + )) } + isLoading={ isPlaceholderData } + socketItemsNum={ num } + socketAlert={ socketAlert } + /> ); } return null; }; -export default LatestDeposits; +export default LatestOptimisticDeposits; diff --git a/ui/pages/Home.tsx b/ui/pages/Home.tsx index 458f9a2817..11e5b8fdf8 100644 --- a/ui/pages/Home.tsx +++ b/ui/pages/Home.tsx @@ -4,8 +4,9 @@ import React from 'react'; import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import ChainIndicators from 'ui/home/indicators/ChainIndicators'; +import LatestArbitrumL2Batches from 'ui/home/latestBatches/LatestArbitrumL2Batches'; +import LatestZkEvmL2Batches from 'ui/home/latestBatches/LatestZkEvmL2Batches'; import LatestBlocks from 'ui/home/LatestBlocks'; -import LatestZkEvmL2Batches from 'ui/home/LatestZkEvmL2Batches'; import Stats from 'ui/home/Stats'; import Transactions from 'ui/home/Transactions'; import AdBanner from 'ui/shared/ad/AdBanner'; @@ -61,7 +62,9 @@ const Home = () => { { isMobile && } - { rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' ? : } + { rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && } + { rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && } + { !(rollupFeature.isEnabled && (rollupFeature.type === 'arbitrum' || rollupFeature.type === 'zkEvm')) && }