Commit aa64056d authored by tom's avatar tom

skeletons for txs list

parent 22b24070
......@@ -65,6 +65,7 @@ export const base: Transaction = {
type: 2,
value: '42000000000000000000',
actions: [],
has_error_in_internal_txs: false,
};
export const withContractCreation: Transaction = {
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Transactions from 'ui/pages/Transactions';
import Page from 'ui/shared/Page/Page';
const Transactions = dynamic(() => import('ui/pages/Transactions'), { ssr: false });
const TxsPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head><title>{ title }</title></Head>
<Page>
<Transactions/>
</Page>
</>
);
};
......
import type { Address } from 'types/api/address';
import type { Address, AddressTransactionsResponse } from 'types/api/address';
import { ADDRESS_HASH } from './addressParams';
import { TOKEN_INFO_ERC_20 } from './token';
import { TXS } from './tx';
export const ADDRESS_INFO: Address = {
block_number_balance_updated_at: 8774377,
......@@ -32,3 +33,5 @@ export const ADDRESS_INFO: Address = {
watchlist_names: [],
watchlist_address_id: null,
};
export const ADDRESS_TXS: AddressTransactionsResponse = TXS as AddressTransactionsResponse;
import type { Block, BlocksResponse } from 'types/api/block';
import type { Block, BlocksResponse, BlockTransactionsResponse } from 'types/api/block';
import { ADDRESS_PARAMS } from './addressParams';
import { TX } from './tx';
export const BLOCK_HASH = '0x8fa7b9e5e5e79deeb62d608db22ba9a5cb45388c7ebb9223ae77331c6080dc70';
......@@ -44,3 +45,12 @@ export const BLOCKS: BlocksResponse = {
items_count: 50,
},
};
export const BLOCK_TXS: BlockTransactionsResponse = {
items: Array(50).fill(TX),
next_page_params: {
block_number: 9004925,
index: 49,
items_count: 50,
},
};
import type { Transaction, TransactionsResponse } from 'types/api/transaction';
import { ADDRESS_PARAMS } from './addressParams';
export const TX_HASH = '0x3ed9d81e7c1001bdda1caa1dc62c0acbbe3d2c671cdc20dc1e65efdaa4186967';
export const TX: Transaction = {
timestamp: '2022-11-11T11:11:11.000000Z',
fee: {
type: 'actual',
value: '2100000000000000',
},
gas_limit: '21000',
block: 9004925,
status: 'ok',
method: 'placeholder',
confirmations: 71,
type: 0,
exchange_rate: '1828.71',
to: ADDRESS_PARAMS,
tx_burnt_fee: null,
max_fee_per_gas: null,
result: 'success',
hash: '0x2b824349b320cfa72f292ab26bf525adb00083ba9fa097141896c3c8c74567cc',
gas_price: '100000000000',
priority_fee: null,
base_fee_per_gas: '24',
from: ADDRESS_PARAMS,
token_transfers: null,
tx_types: [
'coin_transfer',
],
gas_used: '21000',
created_contract: null,
position: 0,
nonce: 295929,
has_error_in_internal_txs: false,
actions: [],
decoded_input: null,
token_transfers_overflow: false,
raw_input: '0x',
value: '42000420000000000000',
max_priority_fee_per_gas: null,
revert_reason: null,
confirmation_duration: [
0,
14545,
],
tx_tag: null,
};
export const TXS: TransactionsResponse = {
items: Array(50).fill(TX),
next_page_params: {
block_number: 9005713,
index: 5,
items_count: 50,
filter: 'validated',
},
};
......@@ -47,6 +47,7 @@ export type Transaction = {
l1_fee_scalar?: string;
l1_gas_price?: string;
l1_gas_used?: string;
has_error_in_internal_txs: boolean | null;
}
export type TransactionsResponse = TransactionsResponseValidated | TransactionsResponsePending;
......
import { chakra, Icon, Tooltip, Hide } from '@chakra-ui/react';
import { chakra, Icon, Tooltip, Hide, Skeleton, Flex } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -13,15 +13,27 @@ interface Props {
address: string;
type: CsvExportType;
className?: string;
isLoading?: boolean;
}
const AddressCsvExportLink = ({ className, address, type }: Props) => {
const AddressCsvExportLink = ({ className, address, type, isLoading }: Props) => {
const isMobile = useIsMobile();
if (!appConfig.reCaptcha.siteKey) {
return null;
}
if (isLoading) {
return (
<Flex className={ className } flexShrink={ 0 } alignItems="center">
<Skeleton boxSize={{ base: '32px', lg: 6 }} borderRadius="base"/>
<Hide ssr={ false } below="lg">
<Skeleton w="112px" h={ 6 } ml={ 1 }/>
</Hide>
</Flex>
);
}
return (
<Tooltip isDisabled={ !isMobile } label="Download CSV">
<LinkInternal
......
......@@ -13,6 +13,7 @@ import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { ADDRESS_TXS } from 'stubs/address';
import ActionBar from 'ui/shared/ActionBar';
import Pagination from 'ui/shared/Pagination';
import TxsContent from 'ui/txs/TxsContent';
......@@ -41,8 +42,14 @@ const AddressTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}
pathParams: { hash: currentAddress },
filters: { filter: filterValue },
scrollRef,
options: {
placeholderData: ADDRESS_TXS,
},
});
// addressTxsQuery.isPlaceholderData = true;
// addressTxsQuery.pagination.isLoading = true;
const handleFilterChange = React.useCallback((val: string | Array<string>) => {
const newVal = getFilterValue(val);
......@@ -112,7 +119,7 @@ const AddressTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}
topic: `addresses:${ currentAddress?.toLowerCase() }`,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: addressTxsQuery.pagination.page !== 1,
isDisabled: addressTxsQuery.pagination.page !== 1 || addressTxsQuery.isPlaceholderData,
});
useSocketMessage({
......@@ -132,15 +139,23 @@ const AddressTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}
defaultFilter={ filterValue }
onFilterChange={ handleFilterChange }
isActive={ Boolean(filterValue) }
isLoading={ addressTxsQuery.pagination.isLoading }
/>
);
return (
<>
{ !isMobile && (
<ActionBar mt={ -6 } showShadow={ addressTxsQuery.isLoading }>
<ActionBar mt={ -6 }>
{ filter }
{ currentAddress && <AddressCsvExportLink address={ currentAddress } type="transactions" ml="auto"/> }
{ currentAddress && (
<AddressCsvExportLink
address={ currentAddress }
type="transactions"
ml="auto"
isLoading={ addressTxsQuery.pagination.isLoading }
/>
) }
{ addressTxsQuery.isPaginationVisible && <Pagination { ...addressTxsQuery.pagination } ml={ 8 }/> }
</ActionBar>
) }
......
......@@ -16,9 +16,10 @@ interface Props {
isActive: boolean;
defaultFilter: AddressFromToFilter;
onFilterChange: (nextValue: string | Array<string>) => void;
isLoading?: boolean;
}
const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive }: Props) => {
const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }: Props) => {
const { isOpen, onToggle } = useDisclosure();
return (
......@@ -26,6 +27,7 @@ const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive }: Props) =>
<MenuButton>
<FilterButton
isActive={ isOpen || isActive }
isLoading={ isLoading }
onClick={ onToggle }
as="div"
/>
......
......@@ -10,7 +10,7 @@ import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import { BLOCK } from 'stubs/block';
import { BLOCK, BLOCK_TXS } from 'stubs/block';
import BlockDetails from 'ui/block/BlockDetails';
import BlockWithdrawals from 'ui/block/BlockWithdrawals';
import TextAd from 'ui/shared/ad/TextAd';
......@@ -47,6 +47,7 @@ const BlockPageContent = () => {
pathParams: { height },
options: {
enabled: Boolean(!blockQuery.isPlaceholderData && blockQuery.data?.height && tab === 'txs'),
placeholderData: BLOCK_TXS,
},
});
......
import { Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -8,7 +7,7 @@ import appConfig from 'configs/app/config';
import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import Page from 'ui/shared/Page/Page';
import { TXS } from 'stubs/tx';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TxsContent from 'ui/txs/TxsContent';
......@@ -28,6 +27,9 @@ const Transactions = () => {
const txsQuery = useQueryWithPages({
resourceName: filter === 'validated' ? 'txs_validated' : 'txs_pending',
filters: { filter },
options: {
placeholderData: TXS,
},
});
const { num, socketAlert } = useNewTxsSocket();
......@@ -47,8 +49,7 @@ const Transactions = () => {
];
return (
<Page>
<Box h="100%">
<>
<PageTitle text="Transactions" withTextAd/>
<RoutedTabs
tabs={ tabs }
......@@ -56,8 +57,7 @@ const Transactions = () => {
rightSlot={ <TxsTabSlot pagination={ txsQuery.pagination } isPaginationVisible={ txsQuery.isPaginationVisible && !isMobile }/> }
stickyEnabled={ !isMobile }
/>
</Box>
</Page>
</>
);
};
......
......@@ -3,6 +3,7 @@ import {
useColorModeValue,
chakra,
Button,
Skeleton,
} from '@chakra-ui/react';
import React from 'react';
......@@ -10,14 +11,19 @@ import infoIcon from 'icons/info.svg';
interface Props {
isOpen?: boolean;
isLoading?: boolean;
className?: string;
onClick?: () => void;
}
const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const AdditionalInfoButton = ({ isOpen, onClick, className, isLoading }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const infoBgColor = useColorModeValue('blue.50', 'gray.600');
if (isLoading) {
return <Skeleton boxSize={ 6 } borderRadius="sm"/>;
}
return (
<Button
variant="unstyled"
......
import { Tag, chakra } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react';
import Tag from 'ui/shared/chakra/Tag';
interface Props {
isIn: boolean;
isOut: boolean;
className?: string;
isLoading?: boolean;
}
const InOutTag = ({ isIn, isOut, className }: Props) => {
const InOutTag = ({ isIn, isOut, className, isLoading }: Props) => {
if (!isIn && !isOut) {
return null;
}
......@@ -20,6 +23,7 @@ const InOutTag = ({ isIn, isOut, className }: Props) => {
colorScheme={ colorScheme }
display="flex"
justifyContent="center"
isLoading={ isLoading }
>
{ isOut ? 'OUT' : 'IN' }
</Tag>
......
......@@ -61,6 +61,7 @@ const SocketNewItemsNotice = chakra(({ children, className, url, num, alert, typ
py="6px"
fontWeight={ 400 }
fontSize="sm"
lineHeight={ 5 }
bgColor={ bgColor }
color={ color }
>
......@@ -77,7 +78,7 @@ export const Desktop = ({ ...props }: Props) => {
return (
<SocketNewItemsNotice
borderRadius={ props.isLoading ? 'sm' : 0 }
h={ props.isLoading ? 4 : 'auto' }
h={ props.isLoading ? 5 : 'auto' }
maxW={ props.isLoading ? '215px' : undefined }
w="100%"
mx={ props.isLoading ? 4 : 0 }
......
import { Tag, TagLabel, TagLeftIcon, Tooltip } from '@chakra-ui/react';
import { TagLabel, TagLeftIcon, Tooltip } from '@chakra-ui/react';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
......@@ -6,13 +6,15 @@ import type { Transaction } from 'types/api/transaction';
import errorIcon from 'icons/status/error.svg';
import pendingIcon from 'icons/status/pending.svg';
import successIcon from 'icons/status/success.svg';
import Tag from 'ui/shared/chakra/Tag';
export interface Props {
status: Transaction['status'];
errorText?: string | null;
isLoading?: boolean;
}
const TxStatus = ({ status, errorText }: Props) => {
const TxStatus = ({ status, errorText, isLoading }: Props) => {
let label;
let icon;
let colorScheme;
......@@ -39,7 +41,7 @@ const TxStatus = ({ status, errorText }: Props) => {
return (
<Tooltip label={ errorText }>
<Tag colorScheme={ colorScheme } display="inline-flex">
<Tag colorScheme={ colorScheme } display="inline-flex" isLoading={ isLoading }>
<TagLeftIcon boxSize={ 2.5 } as={ icon }/>
<TagLabel>{ label }</TagLabel>
</Tag>
......
......@@ -8,7 +8,7 @@ interface Props extends TagProps {
isLoading?: boolean;
}
const Tag = ({ isLoading, ...props }: Props) => {
const Tag = ({ isLoading, ...props }: Props, ref: React.LegacyRef<HTMLDivElement>) => {
if (props.isTruncated && typeof props.children === 'string') {
if (!props.children) {
......@@ -18,16 +18,16 @@ const Tag = ({ isLoading, ...props }: Props) => {
return (
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm" maxW="100%">
<TruncatedTextTooltip label={ props.children }>
<ChakraTag { ...props }/>
<ChakraTag { ...props } ref={ ref }/>
</TruncatedTextTooltip>
</Skeleton>
);
}
return (
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm" maxW="100%">
<ChakraTag { ...props }/>
<ChakraTag { ...props } ref={ ref }/>
</Skeleton>
);
};
export default React.memo(Tag);
export default React.memo(React.forwardRef(Tag));
import type { As } from '@chakra-ui/react';
import { Box, Button, Circle, Icon, useColorModeValue } from '@chakra-ui/react';
import { Skeleton, Box, Button, Circle, Icon, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import filterIcon from 'icons/filter.svg';
......@@ -8,15 +8,20 @@ const FilterIcon = <Icon as={ filterIcon } boxSize={ 5 } mr={{ base: 0, lg: 2 }}
interface Props {
isActive?: boolean;
isLoading?: boolean;
appliedFiltersNum?: number;
onClick: () => void;
as?: As;
}
const FilterButton = ({ isActive, appliedFiltersNum, onClick, as }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const FilterButton = ({ isActive, isLoading, appliedFiltersNum, onClick, as }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const badgeColor = useColorModeValue('white', 'black');
const badgeBgColor = useColorModeValue('blue.700', 'gray.50');
if (isLoading) {
return <Skeleton w={{ base: 9, lg: '78px' }} h={ 8 } borderRadius="base"/>;
}
return (
<Button
ref={ ref }
......
......@@ -20,9 +20,10 @@ interface Props<Sort extends string> {
options: Array<Option<Sort>>;
sort: Sort | undefined;
setSort: (value: Sort | undefined) => void;
isLoading?: boolean;
}
const Sort = <Sort extends string>({ sort, setSort, options }: Props<Sort>) => {
const Sort = <Sort extends string>({ sort, setSort, options, isLoading }: Props<Sort>) => {
const { isOpen, onToggle } = useDisclosure();
const setSortingFromMenu = React.useCallback((val: string | Array<string>) => {
......@@ -36,6 +37,7 @@ const Sort = <Sort extends string>({ sort, setSort, options }: Props<Sort>) => {
<SortButton
isActive={ isOpen || Boolean(sort) }
onClick={ onToggle }
isLoading={ isLoading }
/>
</MenuButton>
<MenuList minWidth="240px" zIndex="popover">
......
import { Icon, IconButton, chakra } from '@chakra-ui/react';
import { Icon, IconButton, chakra, Skeleton } from '@chakra-ui/react';
import React from 'react';
import upDownArrow from 'icons/arrows/up-down.svg';
......@@ -7,9 +7,14 @@ type Props = {
onClick: () => void;
isActive: boolean;
className?: string;
isLoading?: boolean;
}
const SortButton = ({ onClick, isActive, className }: Props) => {
const SortButton = ({ onClick, isActive, className, isLoading }: Props) => {
if (isLoading) {
return <Skeleton className={ className } w="36px" h="32px" borderRadius="base"/>;
}
return (
<IconButton
icon={ <Icon as={ upDownArrow } boxSize={ 5 }/> }
......
......@@ -27,9 +27,10 @@ type Props =
tx: Transaction;
}) & {
isMobile?: boolean;
isLoading?: boolean;
}
const TxAdditionalInfo = ({ hash, tx, isMobile }: Props) => {
const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const content = hash !== undefined ? <TxAdditionalInfoContainer hash={ hash }/> : <TxAdditionalInfoContent tx={ tx }/>;
......@@ -37,7 +38,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile }: Props) => {
if (isMobile) {
return (
<>
<AdditionalInfoButton onClick={ onOpen }/>
<AdditionalInfoButton onClick={ onOpen } isLoading={ isLoading }/>
<Modal isOpen={ isOpen } onClose={ onClose } size="full">
<ModalContent paddingTop={ 4 }>
<ModalCloseButton/>
......@@ -52,7 +53,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile }: Props) => {
{ ({ isOpen }) => (
<>
<PopoverTrigger>
<AdditionalInfoButton isOpen={ isOpen }/>
<AdditionalInfoButton isOpen={ isOpen } isLoading={ isLoading }/>
</PopoverTrigger>
<PopoverContent border="1px solid" borderColor="divider">
<PopoverBody>
......
import { Tag } from '@chakra-ui/react';
import React from 'react';
import type { TransactionType } from 'types/api/transaction';
import Tag from 'ui/shared/chakra/Tag';
export interface Props {
types: Array<TransactionType>;
isLoading?: boolean;
}
const TYPES_ORDER = [ 'token_creation', 'contract_creation', 'token_transfer', 'contract_call', 'coin_transfer' ];
const TxType = ({ types }: Props) => {
const TxType = ({ types, isLoading }: Props) => {
const typeToShow = types.sort((t1, t2) => TYPES_ORDER.indexOf(t1) - TYPES_ORDER.indexOf(t2))[0];
let label;
......@@ -43,7 +45,7 @@ const TxType = ({ types }: Props) => {
}
return (
<Tag colorScheme={ colorScheme }>
<Tag colorScheme={ colorScheme } isLoading={ isLoading }>
{ label }
</Tag>
);
......
......@@ -8,7 +8,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import AddressCsvExportLink from 'ui/address/AddressCsvExportLink';
import DataListDisplay from 'ui/shared/DataListDisplay';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TxsHeaderMobile from './TxsHeaderMobile';
import TxsListItem from './TxsListItem';
......@@ -45,7 +45,7 @@ const TxsContent = ({
hasLongSkeleton,
top,
}: Props) => {
const { data, isLoading, isError, setSortByField, setSortByValue, sorting } = useTxsSort(query);
const { data, isPlaceholderData, isError, setSortByField, setSortByValue, sorting } = useTxsSort(query);
const isMobile = useIsMobile();
const content = data?.items ? (
......@@ -53,22 +53,21 @@ const TxsContent = ({
<Show below="lg" ssr={ false }>
<Box>
{ showSocketInfo && (
<SocketNewItemsNotice
<SocketNewItemsNotice.Mobile
url={ window.location.href }
num={ socketInfoNum }
alert={ socketInfoAlert }
borderBottomRadius={ 0 }
>
{ ({ content }) => <Box>{ content }</Box> }
</SocketNewItemsNotice>
isLoading={ isPlaceholderData }
/>
) }
{ data.items.map(tx => (
{ data.items.map((tx, index) => (
<TxsListItem
key={ tx.hash + (isPlaceholderData ? index : '') }
tx={ tx }
key={ tx.hash }
showBlockInfo={ showBlockInfo }
currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement }
isLoading={ isPlaceholderData }
/>
)) }
</Box>
......@@ -85,6 +84,7 @@ const TxsContent = ({
top={ top || query.isPaginationVisible ? 80 : 0 }
currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement }
isLoading={ isPlaceholderData }
/>
</Hide>
</>
......@@ -97,15 +97,18 @@ const TxsContent = ({
setSorting={ setSortByValue }
paginationProps={ query.pagination }
showPagination={ query.isPaginationVisible }
isLoading={ query.pagination.isLoading }
filterComponent={ filter }
linkSlot={ currentAddress ? <AddressCsvExportLink address={ currentAddress } type="transactions" ml={ 2 }/> : null }
linkSlot={ currentAddress ?
<AddressCsvExportLink address={ currentAddress } type="transactions" ml={ 2 } isLoading={ query.pagination.isLoading }/> : null
}
/>
) : null;
return (
<DataListDisplay
isError={ isError }
isLoading={ isLoading }
isLoading={ false }
items={ data?.items }
skeletonProps={{
isLongSkeleton: hasLongSkeleton,
......
......@@ -28,9 +28,10 @@ type Props = {
showPagination?: boolean;
filterComponent?: React.ReactNode;
linkSlot?: React.ReactNode;
isLoading?: boolean;
}
const TxsHeaderMobile = ({ filterComponent, sorting, setSorting, paginationProps, className, showPagination = true, linkSlot }: Props) => {
const TxsHeaderMobile = ({ filterComponent, sorting, setSorting, paginationProps, className, showPagination = true, linkSlot, isLoading }: Props) => {
return (
<ActionBar className={ className }>
<HStack>
......@@ -39,6 +40,7 @@ const TxsHeaderMobile = ({ filterComponent, sorting, setSorting, paginationProps
options={ SORT_OPTIONS }
setSort={ setSorting }
sort={ sorting }
isLoading={ isLoading }
/>
{ /* api is not implemented */ }
{ /* <FilterInput
......
......@@ -3,7 +3,7 @@ import {
Box,
Flex,
Icon,
Text,
Skeleton,
} from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -31,12 +31,13 @@ type Props = {
showBlockInfo: boolean;
currentAddress?: string;
enableTimeIncrement?: boolean;
isLoading?: boolean;
}
const TAG_WIDTH = 48;
const ARROW_WIDTH = 24;
const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => {
const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => {
const dataTo = tx.to ? tx.to : tx.created_contract;
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
......@@ -48,53 +49,61 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
<ListItemMobile display="block" width="100%" isAnimated key={ tx.hash }>
<Flex justifyContent="space-between" mt={ 4 }>
<HStack>
<TxType types={ tx.tx_types }/>
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined }/>
<TxType types={ tx.tx_types } isLoading={ isLoading }/>
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined } isLoading={ isLoading }/>
</HStack>
<TxAdditionalInfo tx={ tx } isMobile/>
<TxAdditionalInfo tx={ tx } isMobile isLoading={ isLoading }/>
</Flex>
<Flex justifyContent="space-between" lineHeight="24px" mt={ 3 } alignItems="center">
<Flex>
<Skeleton isLoaded={ !isLoading } boxSize="30px" mr={ 2 } borderRadius="base">
<Icon
as={ transactionIcon }
boxSize="30px"
mr={ 2 }
color="link"
/>
</Skeleton>
<Address width="100%">
<AddressLink
hash={ tx.hash }
type="transaction"
fontWeight="700"
truncation="constant"
isLoading={ isLoading }
/>
</Address>
</Flex>
{ tx.timestamp && <Text variant="secondary" fontWeight="400" fontSize="sm">{ timeAgo }</Text> }
{ tx.timestamp && (
<Skeleton isLoaded={ !isLoading } color="text_secondary" fontWeight="400" fontSize="sm">
<span>{ timeAgo }</span>
</Skeleton>
) }
</Flex>
{ tx.method && (
<Flex mt={ 3 }>
<Text as="span" whiteSpace="pre">Method </Text>
<Text
as="span"
variant="secondary"
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Method </Skeleton>
<Skeleton
isLoaded={ !isLoading }
color="text_secondary"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{ tx.method }
</Text>
<span>{ tx.method }</span>
</Skeleton>
</Flex>
) }
{ showBlockInfo && tx.block !== null && (
<Box mt={ 2 }>
<Text as="span">Block </Text>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Block </Skeleton>
<Skeleton isLoaded={ !isLoading } display="inline-block">
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: tx.block.toString() } }) }>{ tx.block }</LinkInternal>
</Skeleton>
</Box>
) }
<Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address maxWidth={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon address={ tx.from }/>
<AddressIcon address={ tx.from } isLoading={ isLoading }/>
<AddressLink
type="address"
hash={ tx.from.hash }
......@@ -102,21 +111,23 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
fontWeight="500"
ml={ 2 }
isDisabled={ isOut }
isLoading={ isLoading }
/>
{ !isOut && <CopyToClipboard text={ tx.from.hash }/> }
{ !isOut && <CopyToClipboard text={ tx.from.hash } isLoading={ isLoading }/> }
</Address>
{ (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mx={ 2 }/> : (
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mx={ 2 } isLoading={ isLoading }/> : (
<Skeleton isLoaded={ !isLoading } mx={ 2 } boxSize={ 6 }>
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mx={ 2 }
color="gray.500"
/>
</Skeleton>
) }
{ dataTo ? (
<Address maxWidth={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon address={ dataTo }/>
<AddressIcon address={ dataTo } isLoading={ isLoading }/>
<AddressLink
type="address"
hash={ dataTo.hash }
......@@ -124,18 +135,19 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
fontWeight="500"
ml={ 2 }
isDisabled={ isIn }
isLoading={ isLoading }
/>
{ !isIn && <CopyToClipboard text={ dataTo.hash }/> }
{ !isIn && <CopyToClipboard text={ dataTo.hash } isLoading={ isLoading }/> }
</Address>
) : '-' }
</Flex>
<Box mt={ 2 }>
<Text as="span">Value { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).toFormat() }</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Value { appConfig.network.currency.symbol } </Skeleton>
<Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary">{ getValueWithUnit(tx.value).toFormat() }</Skeleton>
</Box>
<Box mt={ 2 } mb={ 3 }>
<Text as="span">Fee { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).toFormat() }</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Fee { appConfig.network.currency.symbol } </Skeleton>
<Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary">{ getValueWithUnit(tx.fee.value).toFormat() }</Skeleton>
</Box>
</ListItemMobile>
);
......
import { Link, Table, Tbody, Tr, Th, Td, Icon } from '@chakra-ui/react';
import { Link, Table, Tbody, Tr, Th, Icon } from '@chakra-ui/react';
import { AnimatePresence } from 'framer-motion';
import React from 'react';
......@@ -7,7 +7,7 @@ import type { Sort } from 'types/client/txs-sort';
import appConfig from 'configs/app/config';
import rightArrowIcon from 'icons/arrows/east.svg';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TheadSticky from 'ui/shared/TheadSticky';
import TxsTableItem from './TxsTableItem';
......@@ -23,6 +23,7 @@ type Props = {
socketInfoNum?: number;
currentAddress?: string;
enableTimeIncrement?: boolean;
isLoading?: boolean;
}
const TxsTable = ({
......@@ -36,6 +37,7 @@ const TxsTable = ({
socketInfoNum,
currentAddress,
enableTimeIncrement,
isLoading,
}: Props) => {
return (
<Table variant="simple" minWidth="950px" size="xs">
......@@ -67,18 +69,22 @@ const TxsTable = ({
</TheadSticky>
<Tbody>
{ showSocketInfo && (
<SocketNewItemsNotice borderRadius={ 0 } url={ window.location.href } alert={ socketInfoAlert } num={ socketInfoNum }>
{ ({ content }) => <Tr><Td colSpan={ 10 } p={ 0 }>{ content }</Td></Tr> }
</SocketNewItemsNotice>
<SocketNewItemsNotice.Desktop
url={ window.location.href }
alert={ socketInfoAlert }
num={ socketInfoNum }
isLoading={ isLoading }
/>
) }
<AnimatePresence initial={ false }>
{ txs.map((item) => (
{ txs.map((item, index) => (
<TxsTableItem
key={ item.hash }
key={ item.hash + (isLoading ? index : '') }
tx={ item }
showBlockInfo={ showBlockInfo }
currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement }
isLoading={ isLoading }
/>
)) }
</AnimatePresence>
......
import {
Tr,
Td,
Tag,
Icon,
VStack,
Text,
Show,
Hide,
Flex,
Skeleton,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { route } from 'nextjs-routes';
......@@ -20,11 +19,11 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import Tag from 'ui/shared/chakra/Tag';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import CurrencyValue from 'ui/shared/CurrencyValue';
import InOutTag from 'ui/shared/InOutTag';
import LinkInternal from 'ui/shared/LinkInternal';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import TxStatus from 'ui/shared/TxStatus';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
......@@ -35,9 +34,10 @@ type Props = {
showBlockInfo: boolean;
currentAddress?: string;
enableTimeIncrement?: boolean;
isLoading?: boolean;
}
const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => {
const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, isLoading }: Props) => {
const dataTo = tx.to ? tx.to : tx.created_contract;
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
const isIn = Boolean(currentAddress && currentAddress === dataTo?.hash);
......@@ -46,17 +46,34 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const addressFrom = (
<Address w="100%">
<AddressIcon address={ tx.from }/>
<AddressLink type="address" hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isOut }/>
{ !isOut && <CopyToClipboard text={ tx.from.hash }/> }
<AddressIcon address={ tx.from } isLoading={ isLoading }/>
<AddressLink
type="address"
hash={ tx.from.hash }
alias={ tx.from.name }
fontWeight="500" ml={ 2 }
truncation="constant"
isDisabled={ isOut }
isLoading={ isLoading }
/>
{ !isOut && <CopyToClipboard text={ tx.from.hash } isLoading={ isLoading }/> }
</Address>
);
const addressTo = dataTo ? (
<Address w="100%">
<AddressIcon address={ dataTo }/>
<AddressLink type="address" hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isIn }/>
{ !isIn && <CopyToClipboard text={ dataTo.hash }/> }
<AddressIcon address={ dataTo } isLoading={ isLoading }/>
<AddressLink
type="address"
hash={ dataTo.hash }
alias={ dataTo.name }
fontWeight="500"
ml={ 2 }
truncation="constant"
isDisabled={ isIn }
isLoading={ isLoading }
/>
{ !isIn && <CopyToClipboard text={ dataTo.hash } isLoading={ isLoading }/> }
</Address>
) : '-';
......@@ -70,7 +87,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
key={ tx.hash }
>
<Td pl={ 4 }>
<TxAdditionalInfo tx={ tx }/>
<TxAdditionalInfo tx={ tx } isLoading={ isLoading }/>
</Td>
<Td pr={ 4 }>
<VStack alignItems="start" lineHeight="24px">
......@@ -79,29 +96,34 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
hash={ tx.hash }
type="transaction"
fontWeight="700"
isLoading={ isLoading }
/>
</Address>
{ tx.timestamp && <Text color="gray.500" fontWeight="400">{ timeAgo }</Text> }
{ tx.timestamp && <Skeleton color="text_secondary" fontWeight="400" isLoaded={ !isLoading }><span>{ timeAgo }</span></Skeleton> }
</VStack>
</Td>
<Td>
<VStack alignItems="start">
<TxType types={ tx.tx_types }/>
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined }/>
<TxType types={ tx.tx_types } isLoading={ isLoading }/>
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined } isLoading={ isLoading }/>
</VStack>
</Td>
<Td whiteSpace="nowrap">
{ tx.method && (
<TruncatedTextTooltip label={ tx.method }>
<Tag colorScheme={ tx.method === 'Multicall' ? 'teal' : 'gray' }>
<Tag colorScheme={ tx.method === 'Multicall' ? 'teal' : 'gray' } isLoading={ isLoading } isTruncated>
{ tx.method }
</Tag>
</TruncatedTextTooltip>
) }
</Td>
{ showBlockInfo && (
<Td>
{ tx.block && <LinkInternal href={ route({ pathname: '/block/[height]', query: { height: tx.block.toString() } }) }>{ tx.block }</LinkInternal> }
{ tx.block && (
<Skeleton isLoaded={ !isLoading } display="inline-block">
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: tx.block.toString() } }) }>
{ tx.block }
</LinkInternal>
</Skeleton>
) }
</Td>
) }
<Show above="xl" ssr={ false }>
......@@ -110,9 +132,11 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
</Td>
<Td px={ 0 }>
{ (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mr={ 2 }/> :
<Icon as={ rightArrowIcon } boxSize={ 6 } mx="6px" color="gray.500"/>
}
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mr={ 2 } isLoading={ isLoading }/> : (
<Skeleton mx="6px" isLoaded={ !isLoading } boxSize={ 6 }>
<Icon as={ rightArrowIcon } boxSize={ 6 } color="gray.500"/>
</Skeleton>
) }
</Td>
<Td>
{ addressTo }
......@@ -122,14 +146,16 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
<Td colSpan={ 3 }>
<Flex alignItems="center">
{ (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut } width="48px"/> :
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" isLoading={ isLoading }/> :
(
<Skeleton isLoaded={ !isLoading } boxSize={ 6 }>
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
color="gray.500"
transform="rotate(90deg)"
/>
</Skeleton>
)
}
<VStack alignItems="start" overflow="hidden" ml={ 1 }>
......@@ -140,10 +166,10 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
</Td>
</Hide>
<Td isNumeric>
<CurrencyValue value={ tx.value } accuracy={ 8 }/>
<CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading }/>
</Td>
<Td isNumeric>
<CurrencyValue value={ tx.fee.value } accuracy={ 8 }/>
<CurrencyValue value={ tx.fee.value } accuracy={ 8 } isLoading={ isLoading }/>
</Td>
</Tr>
);
......
......@@ -20,6 +20,10 @@ export default function useTxsSort(
const [ sorting, setSorting ] = React.useState<Sort>(cookies.get(cookies.NAMES.TXS_SORT) as Sort);
const setSortByField = React.useCallback((field: 'val' | 'fee') => () => {
if (queryResult.isPlaceholderData) {
return;
}
setSorting((prevVal) => {
let newVal: Sort = '';
if (field === 'val') {
......@@ -43,7 +47,7 @@ export default function useTxsSort(
cookies.set(cookies.NAMES.TXS_SORT, newVal);
return newVal;
});
}, [ ]);
}, [ queryResult.isPlaceholderData ]);
const setSortByValue = React.useCallback((value: Sort | undefined) => {
setSorting((prevVal: Sort) => {
......
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