Commit 12615746 authored by isstuev's avatar isstuev

latest txs

parent e3d599c8
import type { NextRouter } from 'next/router';
import { useRouter } from 'next/router';
import React from 'react';
import { ROUTES } from 'lib/link/routes';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
function getSocketParams(router: NextRouter) {
if (
router.pathname === ROUTES.txs.pattern &&
(router.query.tab === 'validated' || router.query.tab === undefined) &&
!router.query.block_number
) {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
}
if (router.pathname === ROUTES.network_index.pattern) {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
}
if (router.pathname === ROUTES.txs.pattern && router.query.tab === 'pending' && !router.query.block_number) {
return { topic: 'transactions:new_pending_transaction' as const, event: 'pending_transaction' as const };
}
return {};
}
function assertIsNewTxResponse(response: unknown): response is { transaction: number } {
return typeof response === 'object' && response !== null && 'transaction' in response;
}
function assertIsNewPendingTxResponse(response: unknown): response is { pending_transaction: number } {
return typeof response === 'object' && response !== null && 'pending_transaction' in response;
}
export default function useNewTxsSocket() {
const router = useRouter();
const [ num, setNum ] = React.useState(0);
const [ socketAlert, setSocketAlert ] = React.useState('');
const { topic, event } = getSocketParams(router);
const handleNewTxMessage = React.useCallback((response: { transaction: number } | { pending_transaction: number } | unknown) => {
if (assertIsNewTxResponse(response)) {
setNum((prev) => prev + response.transaction);
}
if (assertIsNewPendingTxResponse(response)) {
setNum((prev) => prev + response.pending_transaction);
}
}, []);
const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please click here to load new transactions.');
}, []);
const handleSocketError = React.useCallback(() => {
setSocketAlert('An error has occurred while fetching new transactions. Please click here to refresh the page.');
}, []);
const channel = useSocketChannel({
topic,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: !topic,
});
useSocketMessage({
channel,
event,
handler: handleNewTxMessage,
});
if (!topic && !event) {
return {};
}
return { num, socketAlert };
}
import handler from 'lib/api/handler';
const getUrl = () => '/v2/main-page/transactions';
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
......@@ -15,4 +15,5 @@ export enum QueryKeys {
chartsTxs = 'charts-txs',
chartsMarket = 'charts-market',
indexBlocks='indexBlocks',
indexTxs='indexTxs',
}
import { Box, Heading, Flex, Link, Text } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link';
import LatestTxsItem from './LatestTxsItem';
import LatestTxsItemSkeleton from './LatestTxsItemSkeleton';
import LatestTxsNotice from './LatestTxsNotice';
const LatestTransactions = () => {
const isMobile = useIsMobile();
const txsCount = isMobile ? 2 : 6;
const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, Array<Transaction>>(
[ QueryKeys.indexTxs ],
async() => await fetch(`/api/index/txs`),
);
let content;
if (isLoading) {
content = (
<>
{ Array.from(Array(txsCount)).map((item, index) => <LatestTxsItemSkeleton key={ index }/>) }
</>
);
}
if (isError) {
content = <Text>There are no transactions. Please reload page.</Text>;
}
if (data) {
content = (
<Box mb={{ base: 3, lg: 6 }}>
{ data.slice(0, txsCount).map((tx => <LatestTxsItem key={ tx.hash } tx={ tx }/>)) }
</Box>
);
}
const txsUrl = link('txs');
return (
<>
<Heading as="h4" fontSize="18px" mb={{ base: 3, lg: 8 }}>Latest transactions</Heading>
{ /* <TxsNewItemNotice mb={{ base: 3, lg: 8 }} url={ txsUrl }>{ ({ content }) => <Box>{ content }</Box> }</TxsNewItemNotice> */ }
<LatestTxsNotice/>
{ content }
<Flex justifyContent="center">
<Link fontSize="sm" href={ txsUrl }>View all transactions</Link>
</Flex>
</>
);
};
export default LatestTransactions;
import {
Box,
Flex,
HStack,
Icon,
Text,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import appConfig from 'configs/app/config';
import rightArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import getValueWithUnit from 'lib/getValueWithUnit';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import TxStatus from 'ui/shared/TxStatus';
import TxType from 'ui/txs/TxType';
type Props = {
tx: Transaction;
}
const LatestBlocksItem = ({ tx }: Props) => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const iconColor = useColorModeValue('blue.600', 'blue.300');
const dataTo = tx.to && tx.to.hash ? tx.to : tx.created_contract;
return (
<Box
width="100%"
borderTop="1px solid"
borderColor={ borderColor }
py={{ base: 4, lg: 6 }}
px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor }}
>
<Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}>
<Box width="100%">
<HStack>
{ tx.tx_types.map(item => <TxType key={ item } type={ item }/>) }
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined }/>
</HStack>
<Flex
mt={ 2 }
alignItems="center"
width="100%"
justifyContent={{ base: 'space-between', lg: 'start' }}
mb={{ base: 6, lg: 0 }}
>
<Flex mr={ 3 }>
<Icon
as={ transactionIcon }
boxSize="30px"
mr={ 2 }
color={ iconColor }
/>
<Address width="100%">
<AddressLink
hash={ tx.hash }
type="transaction"
fontWeight="700"
truncation="constant"
target="_self"
/>
</Address>
</Flex>
<Text variant="secondary" fontWeight="400" fontSize="sm">{ dayjs(tx.timestamp).fromNow() }</Text>
</Flex>
</Box>
<Box>
<Flex alignItems="center" mb={ 3 }>
<Address>
<AddressIcon hash={ tx.from.hash }/>
<AddressLink
hash={ tx.from.hash }
alias={ tx.from.name }
fontWeight="500"
ml={ 2 }
truncation="constant"
fontSize="sm"
/>
</Address>
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mx={ 2 }
color="gray.500"
/>
<Address>
<AddressIcon hash={ dataTo.hash }/>
<AddressLink
hash={ dataTo.hash }
alias={ dataTo.name }
fontWeight="500"
ml={ 2 }
truncation="constant"
fontSize="sm"
/>
</Address>
</Flex>
<Flex fontSize="sm" mt={ 3 } justifyContent="end" flexDirection={{ base: 'column', lg: 'row' }}>
<Box mr={{ base: 0, lg: 2 }} mb={{ base: 2, lg: 0 }}>
<Text as="span">Value { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).toFormat() }</Text>
</Box>
<Box>
<Text as="span">Fee { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).toFormat() }</Text>
</Box>
</Flex>
</Box>
</Flex>
</Box>
);
};
export default LatestBlocksItem;
import {
Box,
Flex,
HStack,
Skeleton,
SkeletonCircle,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
const LatestTxsItemSkeleton = () => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
return (
<Box
width="100%"
borderTop="1px solid"
borderColor={ borderColor }
py={{ base: 4, lg: 6 }}
px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor }}
>
<Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}>
<Box width="100%">
<HStack spacing={ 2 }>
<Skeleton w="101px" h="24px"/>
<Skeleton w="101px" h="24px"/>
</HStack>
<Flex
mt={ 2 }
alignItems="center"
width="100%"
justifyContent={{ base: 'space-between', lg: 'start' }}
mb={{ base: 6, lg: 0 }}
>
<Flex mr={ 3 } alignItems="center">
<Skeleton w="30px" h="30px" mr={ 2 }/>
<Skeleton w="101px" h="12px"/>
</Flex>
<Skeleton w="40px" h="12px"/>
</Flex>
</Box>
<Box>
<Flex alignItems="center" mb={ 3 }>
<SkeletonCircle w="30px" h="30px" mr={ 2 }/>
<Skeleton w="101px" h="12px" mr={ 5 }/>
<SkeletonCircle w="30px" h="30px" mr={ 2 }/>
<Skeleton w="101px" h="12px"/>
</Flex>
<Flex fontSize="sm" mt={ 3 } justifyContent="end" flexDirection={{ base: 'column', lg: 'row' }}>
<Skeleton w="123px" h="12px" mr={{ base: 0, lg: 9 }} mb={{ base: 2, lg: 0 }}/>
<Skeleton w="123px" h="12px"/>
</Flex>
</Box>
</Flex>
</Box>
);
};
export default LatestTxsItemSkeleton;
import { Alert, Spinner, Text, Link, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import link from 'lib/link/link';
interface Props {
className?: string;
}
const LatestTxsNotice = ({ className }: Props) => {
const { num, socketAlert } = useNewTxsSocket();
let content;
if (socketAlert) {
content = 'Connection is lost. Please reload page';
} else if (!num) {
content = (
<>
<Spinner size="sm" mr={ 3 }/>
<Text>scanning new transactions ...</Text>
</>
);
} else {
const txsUrl = link('txs');
content = (
<>
<Spinner size="sm" mr={ 3 }/>
<Text as="span" whiteSpace="pre">+ { num } new transaction{ num > 1 ? 's' : '' }. </Text>
<Link href={ txsUrl }>Show in list</Link>
</>
);
}
return (
<Alert
className={ className }
status="warning"
p={ 4 }
fontWeight={ 400 }
bgColor={ useColorModeValue('orange.50', 'rgba(251, 211, 141, 0.16)') }
borderBottomRadius={ 0 }
>
{ content }
</Alert>
);
};
export default LatestTxsNotice;
import { Box, Heading, Flex } from '@chakra-ui/react';
import { Box, Heading, Flex, LightMode } from '@chakra-ui/react';
import React from 'react';
import LatestBlocks from 'ui/home/LatestBlocks';
import LatestTxs from 'ui/home/LatestTxs';
import Stats from 'ui/home/Stats';
import Page from 'ui/shared/Page/Page';
import SearchBar from 'ui/snippets/searchBar/SearchBar';
......@@ -24,12 +25,12 @@ const Home = () => {
>
Welcome to Blockscout explorer
</Heading>
<SearchBar backgroundColor="white" isHomepage/>
<LightMode><SearchBar backgroundColor="white" isHomepage/></LightMode>
</Box>
<Stats/>
<Flex mt={ 12 } direction={{ base: 'column', lg: 'row' }}>
<Box mr={{ base: 0, lg: 12 }} mb={{ base: 8, lg: 0 }} width={{ base: '100%', lg: '280px' }}><LatestBlocks/></Box>
<Box><Heading as="h4" fontSize="18px" mb={ 8 }>Latest transactions</Heading></Box>
<Box flexGrow={ 1 }><LatestTxs/></Box>
</Flex>
</Page>
);
......
import { Alert, Spinner, Text, Link, chakra } from '@chakra-ui/react';
import type { NextRouter } from 'next/router';
import { useRouter } from 'next/router';
import React from 'react';
import { ROUTES } from 'lib/link/routes';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
interface InjectedProps {
content: React.ReactNode;
......@@ -16,70 +12,13 @@ interface Props {
className?: string;
}
function getSocketParams(router: NextRouter) {
if (router.pathname === ROUTES.txs.pattern && router.query.tab === 'validated' && !router.query.block_number) {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
}
if (router.pathname === ROUTES.txs.pattern && router.query.tab === 'pending' && !router.query.block_number) {
return { topic: 'transactions:new_pending_transaction' as const, event: 'pending_transaction' as const };
}
return {};
}
function assertIsNewTxResponse(response: unknown): response is { transaction: number } {
return typeof response === 'object' && response !== null && 'transaction' in response;
}
function assertIsNewPendingTxResponse(response: unknown): response is { pending_transaction: number } {
return typeof response === 'object' && response !== null && 'pending_transaction' in response;
}
const TxsNewItemNotice = ({ children, className }: Props) => {
const router = useRouter();
const [ num, setNum ] = React.useState(0);
const [ socketAlert, setSocketAlert ] = React.useState('');
const { topic, event } = getSocketParams(router);
const { num, socketAlert } = useNewTxsSocket();
const handleClick = React.useCallback(() => {
window.location.reload();
}, []);
const handleNewTxMessage = React.useCallback((response: { transaction: number } | { pending_transaction: number } | unknown) => {
if (assertIsNewTxResponse(response)) {
setNum((prev) => prev + response.transaction);
}
if (assertIsNewPendingTxResponse(response)) {
setNum((prev) => prev + response.pending_transaction);
}
}, []);
const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please click here to load new transactions.');
}, []);
const handleSocketError = React.useCallback(() => {
setSocketAlert('An error has occurred while fetching new transactions. Please click here to refresh the page.');
}, []);
const channel = useSocketChannel({
topic,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: !topic,
});
useSocketMessage({
channel,
event,
handler: handleNewTxMessage,
});
if (!topic && !event) {
return null;
}
const content = (() => {
if (socketAlert) {
return (
......
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