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__/LatestDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/__screenshots__/LatestDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png deleted file mode 100644 index 5e634c846f..0000000000 Binary files a/ui/home/__screenshots__/LatestDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png and /dev/null differ 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/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png new file mode 100644 index 0000000000..2d55f06320 Binary files /dev/null and b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png new file mode 100644 index 0000000000..d3ce207ef7 Binary files /dev/null and b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png new file mode 100644 index 0000000000..c9869ad411 Binary files /dev/null and b/ui/home/latestBatches/__screenshots__/LatestArbitrumL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png similarity index 100% rename from ui/home/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png rename to ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png diff --git a/ui/home/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png similarity index 100% rename from ui/home/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png rename to ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_default_default-view-mobile-dark-mode-1.png diff --git a/ui/home/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png similarity index 100% rename from ui/home/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png rename to ui/home/latestBatches/__screenshots__/LatestZkEvmL2Batches.pw.tsx_mobile_default-view-mobile-dark-mode-1.png 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/home/__screenshots__/LatestDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png similarity index 100% rename from ui/home/__screenshots__/LatestDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png rename to ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_dark-color-mode_default-view-mobile-dark-mode-1.png diff --git a/ui/home/__screenshots__/LatestDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png similarity index 100% rename from ui/home/__screenshots__/LatestDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png rename to ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_default_default-view-mobile-dark-mode-1.png diff --git a/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png new file mode 100644 index 0000000000..588dc52cbb Binary files /dev/null and b/ui/home/latestDeposits/__screenshots__/LatestOptimisticDeposits.pw.tsx_mobile_default-view-mobile-dark-mode-1.png differ 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')) && }