Commit aa64056d authored by tom's avatar tom

skeletons for txs list

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