Commit 5ace5b01 authored by isstuev's avatar isstuev

deposits on homepage

parent ec0139e6
...@@ -18,7 +18,7 @@ import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } f ...@@ -18,7 +18,7 @@ import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } f
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract'; import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts';
import type { DepositsResponse } from 'types/api/deposits'; import type { DepositsResponse, DepositsItem } from 'types/api/deposits';
import type { IndexingStatus } from 'types/api/indexingStatus'; import type { IndexingStatus } from 'types/api/indexingStatus';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
...@@ -339,6 +339,9 @@ export const RESOURCES = { ...@@ -339,6 +339,9 @@ export const RESOURCES = {
homepage_blocks: { homepage_blocks: {
path: '/api/v2/main-page/blocks', path: '/api/v2/main-page/blocks',
}, },
homepage_deposits: {
path: '/api/v2/main-page/optimism-deposits',
},
homepage_txs: { homepage_txs: {
path: '/api/v2/main-page/transactions', path: '/api/v2/main-page/transactions',
}, },
...@@ -489,6 +492,7 @@ Q extends 'homepage_chart_txs' ? ChartTransactionResponse : ...@@ -489,6 +492,7 @@ Q extends 'homepage_chart_txs' ? ChartTransactionResponse :
Q extends 'homepage_chart_market' ? ChartMarketResponse : Q extends 'homepage_chart_market' ? ChartMarketResponse :
Q extends 'homepage_blocks' ? Array<Block> : Q extends 'homepage_blocks' ? Array<Block> :
Q extends 'homepage_txs' ? Array<Transaction> : Q extends 'homepage_txs' ? Array<Transaction> :
Q extends 'homepage_deposits' ? Array<DepositsItem> :
Q extends 'homepage_indexing_status' ? IndexingStatus : Q extends 'homepage_indexing_status' ? IndexingStatus :
Q extends 'stats_counters' ? Counters : Q extends 'stats_counters' ? Counters :
Q extends 'stats_lines' ? StatsCharts : Q extends 'stats_lines' ? StatsCharts :
......
...@@ -14,6 +14,7 @@ SocketMessage.TxStatusUpdate | ...@@ -14,6 +14,7 @@ SocketMessage.TxStatusUpdate |
SocketMessage.TxRawTrace | SocketMessage.TxRawTrace |
SocketMessage.NewTx | SocketMessage.NewTx |
SocketMessage.NewPendingTx | SocketMessage.NewPendingTx |
SocketMessage.NewDeposits |
SocketMessage.AddressBalance | SocketMessage.AddressBalance |
SocketMessage.AddressCurrentCoinBalance | SocketMessage.AddressCurrentCoinBalance |
SocketMessage.AddressTokenBalance | SocketMessage.AddressTokenBalance |
...@@ -42,6 +43,7 @@ export namespace SocketMessage { ...@@ -42,6 +43,7 @@ export namespace SocketMessage {
export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>; export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type NewDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>;
export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>;
export type AddressCurrentCoinBalance = export type AddressCurrentCoinBalance =
SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>; SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>;
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import * as depositMock from 'mocks/deposits/deposits';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import LatestDeposits from './LatestDeposits';
test('default view +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(buildApiUrl('homepage_deposits'), (route) => route.fulfill({
status: 200,
body: JSON.stringify(depositMock.data.items),
}));
const component = await mount(
<TestApp>
<LatestDeposits/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import { Box, Flex, Text, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
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 LinkInternal from 'ui/shared/LinkInternal';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import LatestDepositsItem from './LatestDepositsItem';
import LatestDepositsItemSkeleton from './LatestDepositsItemSkeleton';
const LatestDeposits = () => {
const isMobile = useIsMobile();
const itemsCount = isMobile ? 2 : 6;
const { data, isLoading, isError } = useApiQuery('homepage_deposits');
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.NewDeposits['handler'] = React.useCallback((payload) => {
setNum(payload.deposits);
}, [ setNum ]);
const channel = useSocketChannel({
topic: 'optimism_deposits:new_deposits',
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: false,
});
useSocketMessage({
channel,
event: 'deposits',
handler: handleNewDepositMessage,
});
if (isLoading) {
return (
<>
<Skeleton h="32px" w="100%" borderBottomLeftRadius={ 0 } borderBottomRightRadius={ 0 }/>
{ Array.from(Array(itemsCount)).map((item, index) => <LatestDepositsItemSkeleton key={ index }/>) }
</>
);
}
if (isError) {
return <Text mt={ 4 }>No data. Please reload page.</Text>;
}
if (data) {
const depositsUrl = route({ pathname: '/deposits' });
return (
<>
<SocketNewItemsNotice borderBottomRadius={ 0 } url={ depositsUrl } num={ num } alert={ socketAlert } type="deposit"/>
<Box mb={{ base: 3, lg: 4 }}>
{ data.slice(0, itemsCount).map((item => <LatestDepositsItem key={ item.l2_tx_hash } item={ item }/>)) }
</Box>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ depositsUrl }>View all deposits</LinkInternal>
</Flex>
</>
);
}
return null;
};
export default LatestDeposits;
import {
Box,
Flex,
Grid,
Icon,
Text,
} from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { DepositsItem } from 'types/api/deposits';
import appConfig from 'configs/app/config';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = {
item: DepositsItem;
}
const LatestTxsItem = ({ item }: Props) => {
const timeAgo = dayjs(item.l1_block_timestamp).fromNow();
const isMobile = useIsMobile();
const l1BlockLink = (
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/block/[height]', query: { height: item.l1_block_number.toString() } }) }
fontWeight={ 700 }
display="inline-flex"
mr={ 2 }
>
<Icon as={ blockIcon } boxSize="30px" mr={ 1 }/>
{ item.l1_block_number }
</LinkExternal>
);
const l1TxLink = (
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: item.l1_tx_hash } }) }
maxW="100%"
display="inline-flex"
alignItems="center"
overflow="hidden"
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l1_tx_hash }/></Box>
</LinkExternal>
);
const l2TxLink = (
<LinkInternal
href={ route({ pathname: '/tx/[hash]', query: { hash: item.l2_tx_hash } }) }
display="flex"
alignItems="center"
overflow="hidden"
w="100%"
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l2_tx_hash }/></Box>
</LinkInternal>
);
const content = (() => {
if (isMobile) {
return (
<>
<Flex justifyContent="space-between" alignItems="center" mb={ 1 }>
{ l1BlockLink }
<Text variant="secondary">{ timeAgo }</Text>
</Flex>
<Grid gridTemplateColumns="56px auto">
<Text lineHeight="30px">L1 txn</Text>
{ l1TxLink }
<Text lineHeight="30px">L2 txn</Text>
{ l2TxLink }
</Grid>
</>
);
}
return (
<Grid width="100%" columnGap={ 4 } rowGap={ 2 } templateColumns="max-content max-content auto" w="100%">
{ l1BlockLink }
<Text lineHeight="30px">L1 txn</Text>
{ l1TxLink }
<Text variant="secondary">{ timeAgo }</Text>
<Text lineHeight="30px">L2 txn</Text>
{ l2TxLink }
</Grid>
);
})();
return (
<Box
width="100%"
borderTop="1px solid"
borderColor="divider"
py={ 4 }
px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor: 'divider' }}
fontSize="sm"
>
{ content }
</Box>
);
};
export default React.memo(LatestTxsItem);
import {
Box,
Flex,
Skeleton,
} from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
const LatestTxsItemSkeleton = () => {
const isMobile = useIsMobile();
return (
<Box
width="100%"
borderTop="1px solid"
borderColor="divider"
py={ 4 }
px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor: 'divider' }}
>
{ isMobile && (
<>
<Flex justifyContent="space-between" alignItems="center" mt={ 1 } mb={ 4 }>
<Skeleton w="120px" h="20px"></Skeleton>
<Skeleton w="80px" h="20px"></Skeleton>
</Flex>
<Skeleton w="100%" h="20px" mb={ 2 }></Skeleton>
<Skeleton w="100%" h="20px" mb={ 2 }></Skeleton>
</>
) }
{ !isMobile && (
<>
<Flex w="100%" mb={ 2 } h="30px" alignItems="center" justifyContent="space-between">
<Skeleton w="120px" h="20px"></Skeleton>
<Skeleton w="calc(100% - 120px - 48px)" h="20px"></Skeleton>
</Flex><Flex w="100%" h="30px" alignItems="center" justifyContent="space-between">
<Skeleton w="120px" h="20px"></Skeleton>
<Skeleton w="calc(100% - 120px - 48px)" h="20px"></Skeleton>
</Flex>
</>
) }
</Box>
);
};
export default LatestTxsItemSkeleton;
import { Box, Heading, Flex, Text, Skeleton } from '@chakra-ui/react'; import { Box, Flex, Text, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -18,10 +18,8 @@ const LatestTransactions = () => { ...@@ -18,10 +18,8 @@ const LatestTransactions = () => {
const { num, socketAlert } = useNewTxsSocket(); const { num, socketAlert } = useNewTxsSocket();
let content;
if (isLoading) { if (isLoading) {
content = ( return (
<> <>
<Skeleton h="32px" w="100%" borderBottomLeftRadius={ 0 } borderBottomRightRadius={ 0 }/> <Skeleton h="32px" w="100%" borderBottomLeftRadius={ 0 } borderBottomRightRadius={ 0 }/>
{ Array.from(Array(txsCount)).map((item, index) => <LatestTxsItemSkeleton key={ index }/>) } { Array.from(Array(txsCount)).map((item, index) => <LatestTxsItemSkeleton key={ index }/>) }
...@@ -30,12 +28,12 @@ const LatestTransactions = () => { ...@@ -30,12 +28,12 @@ const LatestTransactions = () => {
} }
if (isError) { if (isError) {
content = <Text mt={ 4 }>No data. Please reload page.</Text>; return <Text mt={ 4 }>No data. Please reload page.</Text>;
} }
if (data) { if (data) {
const txsUrl = route({ pathname: '/txs' }); const txsUrl = route({ pathname: '/txs' });
content = ( return (
<> <>
<SocketNewItemsNotice borderBottomRadius={ 0 } url={ txsUrl } num={ num } alert={ socketAlert }/> <SocketNewItemsNotice borderBottomRadius={ 0 } url={ txsUrl } num={ num } alert={ socketAlert }/>
<Box mb={{ base: 3, lg: 4 }}> <Box mb={{ base: 3, lg: 4 }}>
...@@ -48,12 +46,7 @@ const LatestTransactions = () => { ...@@ -48,12 +46,7 @@ const LatestTransactions = () => {
); );
} }
return ( return null;
<Box flexGrow={ 1 }>
<Heading as="h4" size="sm" mb={ 4 }>Latest transactions</Heading>
{ content }
</Box>
);
}; };
export default LatestTransactions; export default LatestTransactions;
import { Heading, Tab, Tabs, TabList, TabPanel, TabPanels } from '@chakra-ui/react';
import React from 'react';
import appConfig from 'configs/app/config';
import LatestDeposits from 'ui/home/LatestDeposits';
import LatestTxs from 'ui/home/LatestTxs';
const TransactionsHome = () => {
if (appConfig.L2.isL2Network) {
return (
<>
<Heading as="h4" size="sm" mb={ 4 }>Transactions</Heading>
<Tabs isLazy lazyBehavior="keepMounted" defaultIndex={ 0 } variant="soft-rounded">
<TabList>
<Tab key="txn">Latest txn</Tab>
<Tab key="deposits">Deposits (L1→L2 txn)</Tab>
</TabList>
<TabPanels mt={ 4 }>
<TabPanel key="txn" p={ 0 }>
<LatestTxs/>
</TabPanel>
<TabPanel key="deposits" p={ 0 }>
<LatestDeposits/>
</TabPanel>
</TabPanels>
</Tabs>
</>
);
}
return (
<>
<Heading as="h4" size="sm" mb={ 4 }>Latest transactions</Heading>
<LatestTxs/>
</>
);
};
export default TransactionsHome;
...@@ -4,8 +4,8 @@ import React from 'react'; ...@@ -4,8 +4,8 @@ import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import ChainIndicators from 'ui/home/indicators/ChainIndicators'; import ChainIndicators from 'ui/home/indicators/ChainIndicators';
import LatestBlocks from 'ui/home/LatestBlocks'; import LatestBlocks from 'ui/home/LatestBlocks';
import LatestTxs from 'ui/home/LatestTxs';
import Stats from 'ui/home/Stats'; import Stats from 'ui/home/Stats';
import Transactions from 'ui/home/Transactions';
import AdBanner from 'ui/shared/ad/AdBanner'; import AdBanner from 'ui/shared/ad/AdBanner';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import ColorModeToggler from 'ui/snippets/header/ColorModeToggler'; import ColorModeToggler from 'ui/snippets/header/ColorModeToggler';
...@@ -52,7 +52,9 @@ const Home = () => { ...@@ -52,7 +52,9 @@ const Home = () => {
<AdBanner mt={{ base: 6, lg: 8 }} justifyContent="center"/> <AdBanner mt={{ base: 6, lg: 8 }} justifyContent="center"/>
<Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 8 }> <Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 8 }>
<LatestBlocks/> <LatestBlocks/>
<LatestTxs/> <Box flexGrow={ 1 }>
<Transactions/>
</Box>
</Flex> </Flex>
</Page> </Page>
); );
......
...@@ -7,7 +7,7 @@ interface InjectedProps { ...@@ -7,7 +7,7 @@ interface InjectedProps {
} }
interface Props { interface Props {
type?: 'transaction' | 'token_transfer'; type?: 'transaction' | 'token_transfer' | 'deposit';
children?: (props: InjectedProps) => JSX.Element; children?: (props: InjectedProps) => JSX.Element;
className?: string; className?: string;
url: string; url: string;
...@@ -23,7 +23,19 @@ const SocketNewItemsNotice = ({ children, className, url, num, alert, type = 'tr ...@@ -23,7 +23,19 @@ const SocketNewItemsNotice = ({ children, className, url, num, alert, type = 'tr
return alert; return alert;
} }
const name = type === 'token_transfer' ? 'token transfer' : 'transaction'; let name;
switch (type) {
case 'token_transfer':
name = 'token transfer';
break;
case 'deposit':
name = 'deposit';
break;
default:
name = 'transaction';
break;
}
if (!num) { if (!num) {
return `scanning new ${ name }s...`; return `scanning new ${ name }s...`;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment