Commit 05cba220 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into address-balance-chart

parents e6f02a3a 59b7b3bb
......@@ -28,6 +28,7 @@ blockscout:
- 'nginx.ingress.kubernetes.io/cors-allow-credentials: "true"'
- 'nginx.ingress.kubernetes.io/cors-allow-methods: PUT, GET, POST, OPTIONS, DELETE, PATCH'
- 'nginx.ingress.kubernetes.io/enable-cors: "true"'
- 'nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token"'
host:
_default: blockscout.test.blockscout.aws-k8s.blockscout.com
# enable https
......
......@@ -394,4 +394,5 @@ frontend:
NEXT_PUBLIC_APP_HOST:
_default: blockscout.com
NEXT_PUBLIC_NETWORK_EXPLORERS:
_default: "[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address'}}]"
_default: "[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/ethereum/goerli/transaction','address':'/ethereum/ethereum/goerli/address'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address'}}]"
......@@ -9,5 +9,5 @@ export default function getBlockTotalReward(block: Block) {
?.map(({ reward }) => BigNumber(reward))
.reduce((result, item) => result.plus(item), ZERO) || ZERO;
return totalReward.div(WEI).toFixed();
return totalReward.div(WEI);
}
......@@ -36,6 +36,7 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
const fetch = useFetch();
const isMounted = React.useRef(false);
const canGoBackwards = React.useRef(!router.query.page);
const [ hasPagination, setHasPagination ] = React.useState(page > 1);
const queryKey = [ queryName, ...(queryIds || []), { page, filters } ];
......@@ -74,6 +75,7 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
const nextPageQuery = { ...router.query };
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
nextPageQuery.page = String(page + 1);
setHasPagination(true);
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
.then(() => {
......@@ -101,6 +103,7 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
setPage(prev => prev - 1);
page === 2 && queryClient.removeQueries({ queryKey: [ queryName ] });
});
setHasPagination(true);
}, [ router, page, paginationFields, pageParams, queryClient, scrollToTop, queryName ]);
const resetPage = useCallback(() => {
......@@ -118,6 +121,8 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
queryClient.removeQueries({ queryKey: [ queryName ], type: 'inactive' });
}, 100);
});
setHasPagination(true);
}, [ queryClient, queryName, router, paginationFields, scrollToTop ]);
const onFilterChange = useCallback((newFilters: PaginationFilters<QueryName> | undefined) => {
......@@ -156,6 +161,8 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
canGoBackwards: canGoBackwards.current,
};
const isPaginationVisible = hasPagination || (!queryResult.isLoading && !queryResult.isError && pagination.hasNextPage);
React.useEffect(() => {
if (page !== 1 && isMounted.current) {
queryClient.cancelQueries({ queryKey });
......@@ -171,5 +178,5 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
}, 0);
}, []);
return { ...queryResult, pagination, onFilterChange };
return { ...queryResult, pagination, onFilterChange, isPaginationVisible };
}
......@@ -18,7 +18,7 @@ const baseStyle = definePartsStyle({
container: {
borderRadius: 'md',
px: 6,
py: 4,
py: 3,
},
});
......
......@@ -7,7 +7,7 @@ export type HomeStats = {
total_gas_used: string;
transactions_today: string;
gas_used_today: string;
gas_prices: GasPrices;
gas_prices: GasPrices | null;
static_gas_price: string;
market_cap: string;
network_utilization_percentage: number;
......
......@@ -14,12 +14,12 @@ import useSocketMessage from 'lib/socket/useSocketMessage';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Pagination from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import SocketAlert from 'ui/shared/SocketAlert';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressBlocksValidatedListItem from './blocksValidated/AddressBlocksValidatedListItem';
import AddressBlocksValidatedSkeletonMobile from './blocksValidated/AddressBlocksValidatedSkeletonMobile';
import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksValidatedTableItem';
interface Props {
......@@ -79,7 +79,7 @@ const AddressBlocksValidated = ({ addressQuery }: Props) => {
<SkeletonTable columns={ [ '17%', '17%', '16%', '25%', '25%' ] }/>
</Hide>
<Show below="lg">
<AddressBlocksValidatedSkeletonMobile/>
<SkeletonList/>
</Show>
</>
);
......@@ -122,11 +122,9 @@ const AddressBlocksValidated = ({ addressQuery }: Props) => {
);
})();
const isPaginatorHidden = !query.isLoading && !query.isError && query.pagination.page === 1 && !query.pagination.hasNextPage;
return (
<Box>
{ !isPaginatorHidden && (
{ query.isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...query.pagination }/>
</ActionBar>
......
......@@ -35,7 +35,7 @@ const AddressInternalTxs = () => {
const queryIdArray = castArray(queryId);
const queryIdStr = queryIdArray[0];
const { data, isLoading, isError, pagination, onFilterChange } = useQueryWithPages({
const { data, isLoading, isError, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
apiPath: `/node-api/addresses/${ queryId }/internal-transactions`,
queryName: QueryKeys.addressInternalTxs,
queryIds: queryIdArray,
......@@ -43,8 +43,6 @@ const AddressInternalTxs = () => {
scroll: { elem: SCROLL_ELEM, offset: SCROLL_OFFSET },
});
const isPaginatorHidden = !isLoading && !isError && pagination.page === 1 && !pagination.hasNextPage;
const handleFilterChange = React.useCallback((val: string | Array<string>) => {
const newVal = getFilterValue(val);
......@@ -94,7 +92,7 @@ const AddressInternalTxs = () => {
onFilterChange={ handleFilterChange }
isActive={ Boolean(filterValue) }
/>
{ !isPaginatorHidden && <Pagination ml="auto" { ...pagination }/> }
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
{ content }
</Element>
......
......@@ -43,12 +43,6 @@ const AddressTxs = () => {
addressTxsQuery.onFilterChange({ filter: newVal });
}, [ addressTxsQuery ]);
const isPaginatorHidden =
!addressTxsQuery.isLoading &&
!addressTxsQuery.isError &&
addressTxsQuery.pagination.page === 1 &&
!addressTxsQuery.pagination.hasNextPage;
const filter = (
<AddressTxsFilter
defaultFilter={ filterValue }
......@@ -62,7 +56,7 @@ const AddressTxs = () => {
{ !isMobile && (
<ActionBar mt={ -6 }>
{ filter }
{ !isPaginatorHidden && <Pagination { ...addressTxsQuery.pagination }/> }
{ addressTxsQuery.isPaginationVisible && <Pagination { ...addressTxsQuery.pagination }/> }
</ActionBar>
) }
<TxsContent
......
......@@ -8,7 +8,7 @@ import appConfig from 'configs/app/config';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import ListItemMobile from 'ui/shared/ListItemMobile';
import Utilization from 'ui/shared/Utilization/Utilization';
type Props = Block & {
......@@ -21,7 +21,7 @@ const AddressBlocksValidatedListItem = (props: Props) => {
const totalReward = getBlockTotalReward(props);
return (
<AccountListItemMobile rowGap={ 2 }>
<ListItemMobile rowGap={ 2 } isAnimated>
<Flex justifyContent="space-between" w="100%">
<Link href={ blockUrl } fontWeight="700">{ props.height }</Link>
<Text variant="secondary">{ timeAgo }</Text>
......@@ -37,9 +37,9 @@ const AddressBlocksValidatedListItem = (props: Props) => {
</Flex>
<Flex columnGap={ 2 } w="100%">
<Text fontWeight={ 500 } flexShrink={ 0 }>Reward { appConfig.network.currency.symbol }</Text>
<Text variant="secondary">{ totalReward }</Text>
<Text variant="secondary">{ totalReward.toFixed() }</Text>
</Flex>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const AddressBlocksValidatedSkeletonMobile = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Box>
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
rowGap={ 3 }
flexDirection="column"
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_last={{
borderBottomWidth: '1px',
}}
>
<Flex justifyContent="space-between" w="100%" h={ 6 }>
<Skeleton w="100px"/>
<Skeleton w="100px"/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="40px"/>
<Skeleton w="40px"/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="70px"/>
<Skeleton w="70px"/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="100px"/>
<Skeleton w="120px"/>
</Flex>
</Flex>
)) }
</Box>
);
};
export default AddressBlocksValidatedSkeletonMobile;
......@@ -36,7 +36,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => {
</Flex>
</Td>
<Td isNumeric display="flex" justifyContent="end">
{ totalReward }
{ totalReward.toFixed() }
</Td>
</Tr>
);
......
......@@ -9,23 +9,22 @@ import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressCoinBalanceListItem from './AddressCoinBalanceListItem';
import AddressCoinBalanceSkeletonMobile from './AddressCoinBalanceSkeletonMobile';
import AddressCoinBalanceTableItem from './AddressCoinBalanceTableItem';
interface Props {
query: UseQueryResult<AddressCoinBalanceHistoryResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
}
const AddressCoinBalanceHistory = ({ query }: Props) => {
const isPaginatorHidden = !query.isLoading && !query.isError && query.pagination.page === 1 && !query.pagination.hasNextPage;
const content = (() => {
if (query.isLoading) {
return (
......@@ -34,7 +33,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
<SkeletonTable columns={ [ '25%', '25%', '25%', '25%', '120px' ] }/>
</Hide>
<Show below="lg">
<AddressCoinBalanceSkeletonMobile/>
<SkeletonList/>
</Show>
</>
);
......@@ -75,7 +74,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
return (
<Box mt={ 8 }>
{ !isPaginatorHidden && (
{ query.isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...query.pagination }/>
</ActionBar>
......
......@@ -8,9 +8,9 @@ import appConfig from 'configs/app/config';
import { WEI, ZERO } from 'lib/consts';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile';
type Props = AddressCoinBalanceHistoryItem & {
page: number;
......@@ -23,7 +23,7 @@ const AddressCoinBalanceListItem = (props: Props) => {
const timeAgo = useTimeAgoIncrement(props.block_timestamp, props.page === 1);
return (
<AccountListItemMobile rowGap={ 2 }>
<ListItemMobile rowGap={ 2 } isAnimated>
<Flex justifyContent="space-between" w="100%">
<Text fontWeight={ 600 }>{ BigNumber(props.value).div(WEI).toFixed(8) } { appConfig.network.currency.symbol }</Text>
<Stat flexGrow="0">
......@@ -51,7 +51,7 @@ const AddressCoinBalanceListItem = (props: Props) => {
<Text fontWeight={ 500 } flexShrink={ 0 }>Age</Text>
<Text variant="secondary">{ timeAgo }</Text>
</Flex>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const AddressCoinBalanceSkeletonMobile = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Box>
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
rowGap={ 3 }
flexDirection="column"
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_last={{
borderBottomWidth: '1px',
}}
>
<Flex justifyContent="space-between" w="100%" h={ 6 }>
<Skeleton w="170px"/>
<Skeleton w="120px"/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="40px"/>
<Skeleton w="80px"/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="40px"/>
<Skeleton w="150px"/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="30px"/>
<Skeleton w="60px"/>
</Flex>
</Flex>
)) }
</Box>
);
};
export default AddressCoinBalanceSkeletonMobile;
......@@ -8,14 +8,13 @@ const AddressDetailsSkeleton = () => {
<Box>
<Flex align="center">
<SkeletonCircle boxSize={ 6 } flexShrink={ 0 }/>
<Skeleton h={ 6 } w={{ base: '100px', lg: '420px' }} ml={ 2 }/>
<Skeleton h={ 6 } w="24px" ml={ 2 } flexShrink={ 0 }/>
<Skeleton h={ 8 } w="48px" ml={ 3 } flexShrink={ 0 }/>
<Skeleton h={ 8 } w="48px" ml={ 3 } flexShrink={ 0 }/>
<Skeleton h={ 6 } w={{ base: '100px', lg: '420px' }} ml={ 2 } borderRadius="full"/>
<Skeleton h={ 8 } w="36px" ml={ 3 } flexShrink={ 0 }/>
<Skeleton h={ 8 } w="36px" ml={ 3 } flexShrink={ 0 }/>
</Flex>
<Flex align="center" columnGap={ 4 } mt={ 8 }>
<Skeleton h={ 6 } w="200px"/>
<Skeleton h={ 6 } w="80px"/>
<Skeleton h={ 6 } w="200px" borderRadius="full"/>
<Skeleton h={ 6 } w="80px" borderRadius="full"/>
</Flex>
<Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '150px 1fr' }} maxW="1000px" mt={ 8 }>
<DetailsSkeletonRow w="30%"/>
......
......@@ -8,11 +8,11 @@ import appConfig from 'configs/app/config';
import eastArrowIcon from 'icons/arrows/east.svg';
import dayjs from 'lib/date/dayjs';
import link from 'lib/link/link';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import InOutTag from 'ui/shared/InOutTag';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
......@@ -38,7 +38,7 @@ const TxInternalsListItem = ({
const isIn = Boolean(currentAddress && currentAddress === to?.hash);
return (
<AccountListItemMobile rowGap={ 3 }>
<ListItemMobile rowGap={ 3 }>
<Flex>
{ typeTitle && <Tag colorScheme="cyan" mr={ 2 }>{ typeTitle }</Tag> }
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/>
......@@ -71,7 +71,7 @@ const TxInternalsListItem = ({
{ BigNumber(value).div(BigNumber(10 ** appConfig.network.currency.decimals)).toFormat() }
</Text>
</HStack>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
import React from 'react';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
const TxInternalsSkeletonDesktop = () => {
return (
......
......@@ -2,8 +2,8 @@ import React, { useCallback } from 'react';
import type { ApiKey } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import ApiKeySnippet from 'ui/shared/ApiKeySnippet';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
......@@ -23,10 +23,10 @@ const ApiKeyListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<ListItemMobile>
<ApiKeySnippet apiKey={ item.api_key } name={ item.name }/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip, Alert } from '@chakra-ui/react';
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
......@@ -67,7 +67,7 @@ const BlockDetails = () => {
if (isError) {
const is404 = error?.error?.status === 404;
return is404 ? <Alert>This block has not been processed yet.</Alert> : <DataFetchAlert/>;
return is404 ? <span>This block has not been processed yet.</span> : <DataFetchAlert/>;
}
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
......
......@@ -11,13 +11,13 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import BlocksList from 'ui/blocks/BlocksList';
import BlocksSkeletonMobile from 'ui/blocks/BlocksSkeletonMobile';
import BlocksTable from 'ui/blocks/BlocksTable';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
type QueryResult = UseQueryResult<BlocksResponse> & {
pagination: PaginationProps;
......@@ -83,7 +83,7 @@ const BlocksContent = ({ type, query }: Props) => {
return (
<>
<Show below="lg" key="skeleton-mobile" ssr={ false }>
<BlocksSkeletonMobile/>
<SkeletonList/>
</Show>
<Hide below="lg" key="skeleton-desktop" ssr={ false }>
<SkeletonTable columns={ [ '125px', '120px', '21%', '64px', '35%', '22%', '22%' ] }/>
......
......@@ -12,9 +12,9 @@ import { WEI } from 'lib/consts';
import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressLink from 'ui/shared/address/AddressLink';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import ListItemMobile from 'ui/shared/ListItemMobile';
import Utilization from 'ui/shared/Utilization/Utilization';
interface Props {
......@@ -29,7 +29,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
const txFees = BigNumber(data.tx_fees || 0);
return (
<AccountListItemMobile rowGap={ 3 } key={ String(data.height) }>
<ListItemMobile rowGap={ 3 } key={ String(data.height) } isAnimated>
<Flex justifyContent="space-between" w="100%">
<Flex columnGap={ 2 } alignItems="center">
{ isPending && <Spinner size="sm"/> }
......@@ -64,7 +64,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Box>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Reward { appConfig.network.currency.symbol }</Text>
<Text variant="secondary">{ totalReward }</Text>
<Text variant="secondary">{ totalReward.toFixed() }</Text>
</Flex>
<Box>
<Text fontWeight={ 500 }>Burnt fees</Text>
......@@ -76,7 +76,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<Utilization ml={ 4 } value={ burntFees.div(txFees).toNumber() }/>
</Flex>
</Box>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const BlocksSkeletonMobile = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Box>
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
rowGap={ 3 }
flexDirection="column"
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_last={{
borderBottomWidth: '1px',
}}
>
<Flex h={ 6 } justifyContent="space-between">
<Skeleton w="75px"/>
<Skeleton w="90px"/>
</Flex>
<Skeleton h={ 6 } w="130px"/>
<Skeleton h={ 6 } w="180px"/>
<Skeleton h={ 6 } w="60px"/>
<Skeleton h={ 6 } w="100%"/>
<Skeleton h={ 6 } w="170px"/>
<Skeleton h={ 6 } w="100%"/>
</Flex>
)) }
</Box>
);
};
export default BlocksSkeletonMobile;
......@@ -13,9 +13,10 @@ import Pagination from 'ui/shared/Pagination';
interface Props {
pagination: PaginationProps;
isPaginationVisible: boolean;
}
const BlocksTabSlot = ({ pagination }: Props) => {
const BlocksTabSlot = ({ pagination, isPaginationVisible }: Props) => {
const isMobile = useIsMobile();
const fetch = useFetch();
......@@ -41,7 +42,7 @@ const BlocksTabSlot = ({ pagination }: Props) => {
</Text>
</Box>
) }
<Pagination my={ 1 } { ...pagination }/>
{ isPaginationVisible && <Pagination my={ 1 } { ...pagination }/> }
</Flex>
);
};
......
......@@ -23,7 +23,7 @@ const BlocksTable = ({ data, top, page }: Props) => {
<Thead top={ top }>
<Tr>
<Th width="125px">Block</Th>
<Th width="120px">Size</Th>
<Th width="120px">Size, bytes</Th>
<Th width="21%" minW="144px">{ capitalize(getNetworkValidatorTitle()) }</Th>
<Th width="64px" isNumeric>Txn</Th>
<Th width="35%">Gas used</Th>
......
......@@ -48,7 +48,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Flex>
<BlockTimestamp ts={ data.timestamp } isEnabled={ enableTimeIncrement }/>
</Td>
<Td fontSize="sm">{ data.size.toLocaleString('en') } bytes</Td>
<Td fontSize="sm">{ data.size.toLocaleString('en') }</Td>
<Td fontSize="sm">
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant" display="inline-flex" maxW="100%"/>
</Td>
......@@ -60,7 +60,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<GasUsedToTargetRatio ml={ 2 } value={ data.gas_target_percentage || undefined }/>
</Flex>
</Td>
<Td fontSize="sm">{ totalReward }</Td>
<Td fontSize="sm">{ totalReward.toFixed(8) }</Td>
<Td fontSize="sm">
<Flex alignItems="center" columnGap={ 1 }>
<Icon as={ flameIcon } boxSize={ 5 } color={ useColorModeValue('gray.500', 'inherit') }/>
......
......@@ -2,8 +2,8 @@ import React, { useCallback } from 'react';
import type { CustomAbi } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressSnippet from 'ui/shared/AddressSnippet';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
......@@ -23,10 +23,10 @@ const CustomAbiListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<ListItemMobile>
<AddressSnippet address={ item.contract_address_hash } subtitle={ item.name } isContract/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
......@@ -19,7 +19,7 @@ import LatestBlocksItem from './LatestBlocksItem';
import LatestBlocksItemSkeleton from './LatestBlocksItemSkeleton';
const BLOCK_HEIGHT = 166;
const BLOCK_MARGIN = 24;
const BLOCK_MARGIN = 12;
const LatestBlocks = () => {
const isMobile = useIsMobile();
......
......@@ -59,7 +59,7 @@ const LatestBlocksItem = ({ block, h }: Props) => {
<GridItem><Text variant="secondary">{ block.tx_count }</Text></GridItem>
{ /* */ }
<GridItem>Reward</GridItem>
<GridItem><Text variant="secondary">{ totalReward }</Text></GridItem>
<GridItem><Text variant="secondary">{ totalReward.toFixed() }</Text></GridItem>
<GridItem>Miner</GridItem>
<GridItem><AddressLink alias={ block.miner.name } hash={ block.miner.hash } truncation="constant" maxW="100%"/></GridItem>
</Grid>
......
......@@ -12,6 +12,7 @@ import gasIcon from 'icons/gas.svg';
import txIcon from 'icons/transactions.svg';
import walletIcon from 'icons/wallet.svg';
import useFetch from 'lib/hooks/useFetch';
import link from 'lib/link/link';
import StatsGasPrices from './StatsGasPrices';
import StatsItem from './StatsItem';
......@@ -45,13 +46,14 @@ const Stats = () => {
const lastItemTouchStyle = { gridColumn: { base: 'span 2', lg: 'unset' } };
if (data) {
const gasLabel = hasGasTracker ? <StatsGasPrices gasPrices={ data.gas_prices }/> : null;
const gasLabel = hasGasTracker && data.gas_prices ? <StatsGasPrices gasPrices={ data.gas_prices }/> : null;
content = (
<>
<StatsItem
icon={ blockIcon }
title="Total blocks"
value={ Number(data.total_blocks).toLocaleString() }
url={ link('blocks') }
/>
{ hasAvgBlockTime && (
<StatsItem
......@@ -64,6 +66,7 @@ const Stats = () => {
icon={ txIcon }
title="Total transactions"
value={ Number(data.total_transactions).toLocaleString() }
url={ link('txs') }
/>
<StatsItem
icon={ walletIcon }
......@@ -71,7 +74,7 @@ const Stats = () => {
value={ Number(data.total_addresses).toLocaleString() }
_last={ itemsCount % 2 ? lastItemTouchStyle : undefined }
/>
{ hasGasTracker && (
{ hasGasTracker && data.gas_prices && (
<StatsItem
icon={ gasIcon }
title="Gas tracker"
......
import { Box, Flex, Icon, Text, useColorModeValue, chakra, Tooltip } from '@chakra-ui/react';
import { Box, Flex, Icon, Text, useColorModeValue, chakra, Tooltip, LightMode } from '@chakra-ui/react';
import React from 'react';
import infoIcon from 'icons/info.svg';
......@@ -10,11 +10,12 @@ type Props = {
value: string;
className?: string;
tooltipLabel?: React.ReactNode;
url?: string;
}
const LARGEST_BREAKPOINT = '1240px';
const StatsItem = ({ icon, title, value, className, tooltipLabel }: Props) => {
const StatsItem = ({ icon, title, value, className, tooltipLabel, url }: Props) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sxContainer = {} as any;
sxContainer[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`] = { flexDirection: 'column' };
......@@ -38,6 +39,10 @@ const StatsItem = ({ icon, title, value, className, tooltipLabel }: Props) => {
className={ className }
color={ useColorModeValue('black', 'white') }
position="relative"
{ ...(url ? {
as: 'a',
href: url,
} : {}) }
>
<Icon as={ icon } boxSize={ 7 }/>
<Flex
......@@ -49,18 +54,22 @@ const StatsItem = ({ icon, title, value, className, tooltipLabel }: Props) => {
<Text fontWeight={ 500 } fontSize="md" color={ useColorModeValue('black', 'white') }>{ value }</Text>
</Flex>
{ tooltipLabel && (
<Tooltip label={ tooltipLabel } hasArrow={ false } borderRadius="12px" placement="bottom-end" offset={ [ 0, 0 ] }>
<Box
position="absolute"
top={{ base: 'calc(50% - 12px)', lg: '10px', xl: 'calc(50% - 12px)' }}
right="10px">
<Icon
as={ infoIcon }
boxSize={ 6 }
color={ infoColor }
/>
</Box>
</Tooltip>
<LightMode>
<Tooltip label={ tooltipLabel } hasArrow={ false } borderRadius="12px" placement="bottom-end" offset={ [ 0, 0 ] } bgColor="blackAlpha.900">
<Box
position="absolute"
top={{ base: 'calc(50% - 12px)', lg: '10px', xl: 'calc(50% - 12px)' }}
right="10px"
cursor="pointer"
>
<Icon
as={ infoIcon }
boxSize={ 6 }
color={ infoColor }
/>
</Box>
</Tooltip>
</LightMode>
) }
</Flex>
);
......
......@@ -17,8 +17,8 @@ import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
const DATA_LIMIT = 3;
......@@ -63,7 +63,7 @@ const ApiKeysPage: React.FC = () => {
const content = (() => {
if (isLoading && !data) {
const loader = isMobile ? <SkeletonAccountMobile/> : (
const loader = isMobile ? <SkeletonListAccount/> : (
<>
<SkeletonTable columns={ [ '100%', '108px' ] }/>
<Skeleton height="48px" width="156px" marginTop={ 8 }/>
......
import { Flex, Icon, Link, Tooltip } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { QueryKeys } from 'types/client/queries';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import eastArrowIcon from 'icons/arrows/east.svg';
import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import isBrowser from 'lib/isBrowser';
import BlockDetails from 'ui/block/BlockDetails';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
......@@ -22,6 +26,8 @@ const TAB_LIST_PROPS = {
const BlockPageContent = () => {
const router = useRouter();
const isMobile = useIsMobile();
const isInBrowser = isBrowser();
const appProps = useAppContext();
const blockTxsQuery = useQueryWithPages({
apiPath: `/node-api/blocks/${ router.query.id }/transactions`,
......@@ -40,12 +46,23 @@ const BlockPageContent = () => {
{ id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> },
];
const isPaginatorHidden = !blockTxsQuery.isLoading && !blockTxsQuery.isError && blockTxsQuery.pagination.page === 1 && !blockTxsQuery.pagination.hasNextPage;
const hasPagination = !isMobile && router.query.tab === 'txs' && !isPaginatorHidden;
const hasPagination = !isMobile && router.query.tab === 'txs' && blockTxsQuery.isPaginationVisible;
const referrer = isInBrowser ? window.document.referrer : appProps.referrer;
const hasGoBackLink = referrer && referrer.includes('/blocks');
return (
<Page>
<PageTitle text={ `Block #${ router.query.id }` }/>
<Flex alignItems="center" columnGap={ 3 }>
{ hasGoBackLink && (
<Tooltip label="Back to blocks list">
<Link mb={ 6 } display="inline-flex" href={ referrer }>
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/>
</Link>
</Tooltip>
) }
<PageTitle text={ `Block #${ router.query.id }` }/>
</Flex>
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
......
......@@ -55,7 +55,7 @@ const BlocksPageContent = () => {
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ <BlocksTabSlot pagination={ blocksQuery.pagination }/> }
rightSlot={ <BlocksTabSlot pagination={ blocksQuery.pagination } isPaginationVisible={ blocksQuery.isPaginationVisible }/> }
stickyEnabled={ !isMobile }
/>
</Page>
......
......@@ -16,8 +16,8 @@ import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
const CustomAbiPage: React.FC = () => {
const customAbiModalProps = useDisclosure();
......@@ -60,7 +60,7 @@ const CustomAbiPage: React.FC = () => {
const content = (() => {
if (isLoading && !data) {
const loader = isMobile ? <SkeletonAccountMobile/> : (
const loader = isMobile ? <SkeletonListAccount/> : (
<>
<SkeletonTable columns={ [ '100%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
......
import { Flex, Link, Icon, Tag } from '@chakra-ui/react';
import { Flex, Link, Icon, Tag, Tooltip } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -60,14 +60,17 @@ const TransactionPageContent = () => {
return (
<Page>
{ hasGoBackLink && (
<Link mb={ 6 } display="inline-flex" href={ referrer }>
<Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/>
Transactions
</Link>
) }
<Flex alignItems="flex-start" flexDir={{ base: 'column', lg: 'row' }}>
<PageTitle text="Transaction details"/>
<Flex alignItems="center" columnGap={ 3 }>
{ hasGoBackLink && (
<Tooltip label="Back to transactions list">
<Link display="inline-flex" href={ referrer } mb={ 6 }>
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/>
</Link>
</Tooltip>
) }
<PageTitle text="Transaction details"/>
</Flex>
{ data?.tx_tag && <Tag my={ 2 } ml={ 3 }>{ data.tx_tag }</Tag> }
{ explorersLinks.length > 0 && (
<Flex
......
......@@ -43,7 +43,7 @@ const Transactions = () => {
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ <TxsTabSlot pagination={ txsQuery.pagination }/> }
rightSlot={ <TxsTabSlot pagination={ txsQuery.pagination } isPaginationVisible={ txsQuery.isPaginationVisible && !isMobile }/> }
stickyEnabled={ !isMobile }
/>
</Box>
......
......@@ -12,8 +12,8 @@ import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import AddressModal from 'ui/watchlist/AddressModal/AddressModal';
import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
import WatchListItem from 'ui/watchlist/WatchlistTable/WatchListItem';
......@@ -73,7 +73,7 @@ const WatchList: React.FC = () => {
let content;
if (isLoading && !data) {
const loader = isMobile ? <SkeletonAccountMobile showFooterSlot/> : (
const loader = isMobile ? <SkeletonListAccount showFooterSlot/> : (
<>
<SkeletonTable columns={ [ '70%', '30%', '160px', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
......
......@@ -3,8 +3,8 @@ import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressSnippet from 'ui/shared/AddressSnippet';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
interface Props {
......@@ -23,7 +23,7 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<ListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<AddressSnippet address={ item.address_hash }/>
<HStack spacing={ 3 } mt={ 4 }>
......@@ -34,7 +34,7 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</HStack>
</Flex>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
......@@ -9,8 +9,8 @@ import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import AddressModal from './AddressModal/AddressModal';
import AddressTagListItem from './AddressTagTable/AddressTagListItem';
......@@ -57,7 +57,7 @@ const PrivateAddressTags = () => {
);
if (isLoading && !addressTagsData) {
const loader = isMobile ? <SkeletonAccountMobile/> : (
const loader = isMobile ? <SkeletonListAccount/> : (
<>
<SkeletonTable columns={ [ '60%', '40%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
......
......@@ -9,8 +9,8 @@ import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import DeletePrivateTagModal from './DeletePrivateTagModal';
import TransactionModal from './TransactionModal/TransactionModal';
......@@ -60,7 +60,7 @@ const PrivateTransactionTags = () => {
);
if (isLoading && !transactionTagsData) {
const loader = isMobile ? <SkeletonAccountMobile/> : (
const loader = isMobile ? <SkeletonListAccount/> : (
<>
<SkeletonTable columns={ [ '75%', '25%', '108px' ] }/>
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
......
......@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
import type { TransactionTag } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TransactionSnippet from 'ui/shared/TransactionSnippet';
......@@ -23,7 +23,7 @@ const TransactionTagListItem = ({ item, onEditClick, onDeleteClick }: Props) =>
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<ListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<TransactionSnippet hash={ item.transaction_hash }/>
<HStack spacing={ 3 } mt={ 4 }>
......@@ -34,7 +34,7 @@ const TransactionTagListItem = ({ item, onEditClick, onDeleteClick }: Props) =>
</HStack>
</Flex>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
......@@ -3,8 +3,8 @@ import React, { useCallback } from 'react';
import type { PublicTag } from 'types/api/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressSnippet from 'ui/shared/AddressSnippet';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
......@@ -24,7 +24,7 @@ const PublicTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ item, onDeleteClick ]);
return (
<AccountListItemMobile>
<ListItemMobile>
<VStack spacing={ 3 } alignItems="flex-start" maxW="100%">
<VStack spacing={ 4 } alignItems="unset" maxW="100%">
{ item.addresses.map((address) => <AddressSnippet key={ address } address={ address }/>) }
......@@ -49,7 +49,7 @@ const PublicTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</HStack>
</VStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
......@@ -10,8 +10,8 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import PublicTagListItem from 'ui/publicTags/PublicTagTable/PublicTagListItem';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonListAccount from 'ui/shared/skeletons/SkeletonListAccount';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import DeletePublicTagModal from './DeletePublicTagModal';
import PublicTagTable from './PublicTagTable/PublicTagTable';
......@@ -58,7 +58,7 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
);
if (isLoading) {
const loader = isMobile ? <SkeletonAccountMobile/> : (
const loader = isMobile ? <SkeletonListAccount/> : (
<>
<SkeletonTable columns={ [ '50%', '25%', '25%', '108px' ] }/>
<Skeleton height="48px" width="270px" marginTop={ 8 }/>
......
import {
Icon,
Center,
useColorModeValue,
chakra,
Button,
} from '@chakra-ui/react';
import React from 'react';
......@@ -14,13 +14,16 @@ interface Props {
onClick?: () => void;
}
const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React.ForwardedRef<HTMLDivElement>) => {
const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const infoBgColor = useColorModeValue('blue.50', 'gray.600');
const infoColor = useColorModeValue('blue.600', 'blue.300');
return (
<Center
<Button
variant="unstyled"
display="inline-flex"
alignItems="center"
className={ className }
ref={ ref }
background={ isOpen ? infoBgColor : 'unset' }
......@@ -36,7 +39,7 @@ const AdditionalInfoButton = ({ isOpen, onClick, className }: Props, ref: React.
color={ infoColor }
_hover={{ color: 'blue.400' }}
/>
</Center>
</Button>
);
};
......
......@@ -3,11 +3,12 @@ import React from 'react';
interface Props {
hash: string;
isTooltipDisabled?: boolean;
}
const HashStringShorten = ({ hash }: Props) => {
const HashStringShorten = ({ hash, isTooltipDisabled }: Props) => {
return (
<Tooltip label={ hash }>
<Tooltip label={ hash } isDisabled={ isTooltipDisabled }>
{ hash.slice(0, 4) + '...' + hash.slice(-4) }
</Tooltip>
);
......
......@@ -22,9 +22,10 @@ const HEAD_MIN_LENGTH = 4;
interface Props {
hash: string;
fontWeight?: string | number;
isTooltipDisabled?: boolean;
}
const HashStringShortenDynamic = ({ hash, fontWeight = '400' }: Props) => {
const HashStringShortenDynamic = ({ hash, fontWeight = '400', isTooltipDisabled }: Props) => {
const elementRef = useRef<HTMLSpanElement>(null);
const [ displayedString, setDisplayedString ] = React.useState(hash);
......@@ -90,7 +91,7 @@ const HashStringShortenDynamic = ({ hash, fontWeight = '400' }: Props) => {
if (isTruncated) {
return (
<Tooltip label={ hash }>{ content }</Tooltip>
<Tooltip label={ hash } isDisabled={ isTooltipDisabled }>{ content }</Tooltip>
);
}
......
......@@ -5,18 +5,17 @@ import React from 'react';
interface Props {
children: React.ReactNode;
className?: string;
key?: string;
isAnimated?: boolean;
}
const AccountListItemMobile = ({ children, className, key }: Props) => {
const ListItemMobile = ({ children, className, isAnimated }: Props) => {
return (
<Flex
as={ motion.div }
initial={{ opacity: 0, scale: 0.97 }}
initial={ isAnimated ? { opacity: 0, scale: 0.97 } : { opacity: 1, scale: 1 } }
animate={{ opacity: 1, scale: 1 }}
transitionDuration="normal"
transitionTimingFunction="linear"
key={ key }
rowGap={ 6 }
alignItems="flex-start"
flexDirection="column"
......@@ -33,4 +32,4 @@ const AccountListItemMobile = ({ children, className, key }: Props) => {
);
};
export default chakra(AccountListItemMobile);
export default chakra(ListItemMobile);
......@@ -17,11 +17,11 @@ import EmptySearchResult from 'ui/apps/EmptySearchResult';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Pagination from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import { flattenTotal } from 'ui/shared/TokenTransfer/helpers';
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
import TokenTransferSkeletonMobile from 'ui/shared/TokenTransfer/TokenTransferSkeletonMobile';
import TokenTransferTable from 'ui/shared/TokenTransfer/TokenTransferTable';
import { TOKEN_TYPE } from './helpers';
......@@ -61,7 +61,7 @@ const TokenTransfer = ({
{ type: getTokenFilterValue(router.query.type), filter: getAddressFilterValue(router.query.filter) },
);
const { isError, isLoading, data, pagination, onFilterChange } = useQueryWithPages({
const { isError, isLoading, data, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
apiPath: path,
queryName,
queryIds,
......@@ -95,7 +95,7 @@ const TokenTransfer = ({
/>
</Hide>
<Show below="lg">
<TokenTransferSkeletonMobile showTxInfo={ showTxInfo }/>
<SkeletonList/>
</Show>
</>
);
......@@ -138,7 +138,7 @@ const TokenTransfer = ({
onAddressFilterChange={ handleAddressFilterChange }
defaultAddressFilter={ filters.filter }
/>
<Pagination ml="auto" { ...pagination }/>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
) }
{ content }
......
......@@ -7,12 +7,12 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import eastArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AdditionalInfoButton from 'ui/shared/AdditionalInfoButton';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import InOutTag from 'ui/shared/InOutTag';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
......@@ -49,7 +49,7 @@ const TokenTransferListItem = ({
const addressWidth = `calc((100% - ${ baseAddress ? '50px' : '0px' }) / 2)`;
return (
<AccountListItemMobile rowGap={ 3 }>
<ListItemMobile rowGap={ 3 } isAnimated>
<Flex w="100%" flexWrap="wrap" rowGap={ 1 } position="relative">
<TokenSnippet hash={ token.address } w="auto" maxW="calc(100% - 140px)" name={ token.name || 'Unnamed token' }/>
<Tag flexShrink={ 0 } ml={ 2 } mr={ 2 }>{ token.type }</Tag>
......@@ -98,7 +98,7 @@ const TokenTransferListItem = ({
<Text variant="secondary">{ value }</Text>
</Flex>
) }
</AccountListItemMobile>
</ListItemMobile>
);
};
......
import { Skeleton, SkeletonCircle, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const TokenTransferSkeletonMobile = ({ showTxInfo }: { showTxInfo?: boolean }) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Box>
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
rowGap={ 3 }
flexDirection="column"
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_last={{
borderBottomWidth: '1px',
}}
>
<Flex h={ 6 }>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton w="100px" mr={ 2 }/>
<Skeleton w="50px"/>
{ showTxInfo && <Skeleton w="24px" ml="auto"/> }
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="70px"/>
<Skeleton w="24px"/>
<Skeleton w="90px"/>
</Flex>
{ showTxInfo && (
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="70px" flexShrink={ 0 }/>
<Skeleton w="100%"/>
</Flex>
) }
<Flex h={ 6 }>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
<Skeleton w="50px" mr={ 3 }/>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="45px"/>
<Skeleton w="90px"/>
</Flex>
</Flex>
)) }
</Box>
);
};
export default TokenTransferSkeletonMobile;
......@@ -2,6 +2,7 @@ import { Link, chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react'
import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link';
import HashStringShorten from 'ui/shared/HashStringShorten';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
......@@ -18,6 +19,8 @@ interface Props {
}
const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, fontWeight, target = '_self' }: Props) => {
const isMobile = useIsMobile();
let url;
if (type === 'transaction') {
url = link('tx', { id: id || hash });
......@@ -34,16 +37,16 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id,
const content = (() => {
if (alias) {
return (
<Tooltip label={ hash }>
<Tooltip label={ hash } isDisabled={ isMobile }>
<Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ alias }</Box>
</Tooltip>
);
}
switch (truncation) {
case 'constant':
return <HashStringShorten hash={ id || hash }/>;
return <HashStringShorten hash={ id || hash } isTooltipDisabled={ isMobile }/>;
case 'dynamic':
return <HashStringShortenDynamic hash={ id || hash } fontWeight={ fontWeight }/>;
return <HashStringShortenDynamic hash={ id || hash } fontWeight={ fontWeight } isTooltipDisabled={ isMobile }/>;
case 'none':
return <span>{ id || hash }</span>;
}
......
import { Skeleton, SkeletonCircle, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import { Box, Flex, Skeleton, SkeletonCircle, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const TxInternalsSkeletonMobile = () => {
const SkeletonList = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
......@@ -9,38 +9,32 @@ const TxInternalsSkeletonMobile = () => {
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
rowGap={ 3 }
rowGap={ 4 }
flexDirection="column"
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_last={{
borderBottomWidth: '1px',
borderBottomWidth: '0px',
}}
>
<Flex h={ 6 }>
<Skeleton w="100px" mr={ 2 }/>
<Skeleton w="90px"/>
<Flex h={ 4 }>
<Skeleton w="30%" mr={ 2 } borderRadius="full"/>
<Skeleton w="15%" borderRadius="full"/>
</Flex>
<Flex h={ 6 }>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
<Skeleton w={ 6 } mr={ 3 }/>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
</Flex>
<Flex h={ 6 }>
<Skeleton w="70px" mr={ 2 }/>
<Skeleton w="30px"/>
</Flex>
<Flex h={ 6 }>
<Skeleton w="70px" mr={ 2 }/>
<Skeleton w="60px"/>
<Flex h={ 4 }>
<SkeletonCircle boxSize={ 4 } mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 } borderRadius="full"/>
<Skeleton w={ 6 } mr={ 3 } borderRadius="full"/>
<SkeletonCircle boxSize={ 4 } mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 } borderRadius="full"/>
</Flex>
<Skeleton w="75%" h={ 4 } borderRadius="full"/>
<Skeleton w="60%" h={ 4 } borderRadius="full"/>
</Flex>
)) }
</Box>
);
};
export default TxInternalsSkeletonMobile;
export default SkeletonList;
......@@ -5,7 +5,7 @@ interface Props {
showFooterSlot?: boolean;
}
const SkeletonAccountMobile = ({ showFooterSlot }: Props) => {
const SkeletonListAccount = ({ showFooterSlot }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
......@@ -18,9 +18,8 @@ const SkeletonAccountMobile = ({ showFooterSlot }: Props) => {
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_first={{
borderTopWidth: '0',
pt: '0',
_last={{
borderBottomWidth: '0px',
}}
>
<Flex columnGap={ 2 } w="100%" alignItems="center">
......@@ -45,4 +44,4 @@ const SkeletonAccountMobile = ({ showFooterSlot }: Props) => {
);
};
export default SkeletonAccountMobile;
export default SkeletonListAccount;
import { InputGroup, Input, InputLeftAddon, InputLeftElement, Icon, chakra, useColorModeValue } from '@chakra-ui/react';
import { InputGroup, Input, InputLeftElement, Icon, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { ChangeEvent, FormEvent } from 'react';
......@@ -18,15 +18,15 @@ const SearchBarDesktop = ({ onChange, onSubmit, isHomepage }: Props) => {
display={{ base: 'none', lg: 'block' }}
w="100%"
backgroundColor={ isHomepage ? 'white' : 'none' }
borderRadius="10px"
borderRadius="base"
>
<InputGroup>
<InputLeftAddon w="111px">All filters</InputLeftAddon>
<InputLeftElement w={ 6 } ml="132px" mr={ 2.5 }>
<InputLeftElement w={ 6 } ml={ 4 }>
<Icon as={ searchIcon } boxSize={ 6 } color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/>
</InputLeftElement>
<Input
paddingInlineStart="50px"
// paddingInlineStart="50px"
pl="50px"
placeholder="Search by addresses / transactions / block / token... "
ml="1px"
onChange={ onChange }
......
......@@ -65,6 +65,21 @@ const TxDetails = () => {
...toAddress.watchlist_names || [],
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const executionSuccessBadge = toAddress.is_contract && data.result === 'success' ? (
<Tooltip label="Contract execution completed">
<chakra.span display="inline-flex" ml={ 2 } mr={ 1 }>
<Icon as={ successIcon } boxSize={ 4 } color="green.500" cursor="pointer"/>
</chakra.span>
</Tooltip>
) : null;
const executionFailedBadge = toAddress.is_contract && Boolean(data.status) && data.result !== 'success' ? (
<Tooltip label="Error occurred during contract execution">
<chakra.span display="inline-flex" ml={ 2 } mr={ 1 }>
<Icon as={ errorIcon } boxSize={ 4 } color="red.500" cursor="pointer"/>
</chakra.span>
</Tooltip>
) : null;
return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}>
{ socketStatus && (
......@@ -144,40 +159,30 @@ const TxDetails = () => {
) }
</DetailsInfoItem>
<DetailsInfoItem
title={ toAddress.is_contract ? 'Interacted with contract' : 'To' }
title={ data.to?.is_contract ? 'Interacted with contract' : 'To' }
hint="Address (external or contract) receiving the transaction."
flexWrap={{ base: 'wrap', lg: 'nowrap' }}
columnGap={ 3 }
>
{ data.to && data.to.hash ? (
<Address>
<Address alignItems="center">
<AddressIcon hash={ toAddress.hash }/>
<AddressLink ml={ 2 } hash={ toAddress.hash }/>
{ executionSuccessBadge }
{ executionFailedBadge }
<CopyToClipboard text={ toAddress.hash }/>
</Address>
) : (
<Flex width={{ base: '100%', lg: 'auto' }} whiteSpace="pre">
<Flex width={{ base: '100%', lg: 'auto' }} whiteSpace="pre" alignItems="center">
<span>[Contract </span>
<AddressLink hash={ toAddress.hash }/>
<span> created]</span>
{ executionSuccessBadge }
{ executionFailedBadge }
<CopyToClipboard text={ toAddress.hash }/>
</Flex>
) }
{ toAddress.name && <Text>{ toAddress.name }</Text> }
{ toAddress.is_contract && data.result === 'success' && (
<Tooltip label="Contract execution completed">
<chakra.span display="inline-flex">
<Icon as={ successIcon } boxSize={ 4 } color="green.500" cursor="pointer"/>
</chakra.span>
</Tooltip>
) }
{ toAddress.is_contract && Boolean(data.status) && data.result !== 'success' && (
<Tooltip label="Error occured during contract execution">
<chakra.span display="inline-flex">
<Icon as={ errorIcon } boxSize={ 4 } color="red.500" cursor="pointer"/>
</chakra.span>
</Tooltip>
) }
{ addressToTags.length > 0 && (
<Flex columnGap={ 3 }>
{ addressToTags }
......
......@@ -33,8 +33,5 @@ test('base view +@mobile', async({ mount, page }) => {
{ hooksConfig },
);
await page.waitForResponse(API_URL_TX),
await page.waitForResponse(API_URL_TX_INTERNALS),
await expect(component).toHaveScreenshot();
});
......@@ -14,9 +14,9 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert';
// import FilterInput from 'ui/shared/FilterInput';
// import TxInternalsFilter from 'ui/tx/internals/TxInternalsFilter';
import Pagination from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import TxInternalsList from 'ui/tx/internals/TxInternalsList';
import TxInternalsSkeletonDesktop from 'ui/tx/internals/TxInternalsSkeletonDesktop';
import TxInternalsSkeletonMobile from 'ui/tx/internals/TxInternalsSkeletonMobile';
import TxInternalsTable from 'ui/tx/internals/TxInternalsTable';
import type { Sort, SortField } from 'ui/tx/internals/utils';
import TxPendingAlert from 'ui/tx/TxPendingAlert';
......@@ -75,7 +75,7 @@ const TxInternals = () => {
// const [ searchTerm, setSearchTerm ] = React.useState<string>('');
const [ sort, setSort ] = React.useState<Sort>();
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError, pagination } = useQueryWithPages({
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({
apiPath: `/node-api/transactions/${ txInfo.data?.hash }/internal-transactions`,
queryName: QueryKeys.txInternals,
queryIds: txInfo.data?.hash ? [ txInfo.data.hash ] : undefined,
......@@ -83,7 +83,6 @@ const TxInternals = () => {
enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status),
},
});
const isPaginatorHidden = !isLoading && !isError && pagination.page === 1 && !pagination.hasNextPage;
const isMobile = useIsMobile();
......@@ -104,8 +103,8 @@ const TxInternals = () => {
if (isLoading || txInfo.isLoading) {
return (
<>
<Show below="lg"><TxInternalsSkeletonMobile/></Show>
<Hide below="lg"><TxInternalsSkeletonDesktop/></Hide>
<Show below="lg"><SkeletonList/></Show>
<Hide below="lg"><SkeletonTable columns={ [ '28%', '20%', '24px', '20%', '16%', '16%' ] }/></Hide>
</>
);
}
......@@ -131,12 +130,12 @@ const TxInternals = () => {
return isMobile ?
<TxInternalsList data={ filteredData }/> :
<TxInternalsTable data={ filteredData } sort={ sort } onSortToggle={ handleSortToggle } top={ isPaginatorHidden ? 0 : 80 }/>;
<TxInternalsTable data={ filteredData } sort={ sort } onSortToggle={ handleSortToggle } top={ isPaginationVisible ? 80 : 0 }/>;
})();
return (
<Box>
{ !isPaginatorHidden && (
{ isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......
......@@ -16,7 +16,7 @@ import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
const TxLogs = () => {
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError, pagination } = useQueryWithPages({
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({
apiPath: `/node-api/transactions/${ txInfo.data?.hash }/logs`,
queryName: QueryKeys.txLogs,
queryIds: txInfo.data?.hash ? [ txInfo.data.hash ] : undefined,
......@@ -24,7 +24,6 @@ const TxLogs = () => {
enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status),
},
});
const isPaginatorHidden = !isLoading && !isError && pagination.page === 1 && !pagination.hasNextPage;
if (!txInfo.isLoading && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
......@@ -49,7 +48,7 @@ const TxLogs = () => {
return (
<Box>
{ !isPaginatorHidden && (
{ isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......
......@@ -46,6 +46,10 @@ const TxRawTrace = () => {
);
}
if (data.length === 0) {
return <span>There is no raw trace for this transaction.</span>;
}
const text = JSON.stringify(data, undefined, 4);
return (
......
......@@ -6,10 +6,10 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import appConfig from 'configs/app/config';
import eastArrowIcon from 'icons/arrows/east.svg';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
......@@ -20,7 +20,7 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit:
const toData = to ? to : createdContract;
return (
<AccountListItemMobile rowGap={ 3 }>
<ListItemMobile rowGap={ 3 }>
<Flex>
{ typeTitle && <Tag colorScheme="cyan" mr={ 2 }>{ typeTitle }</Tag> }
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/>
......@@ -46,7 +46,7 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit:
<Text fontSize="sm" fontWeight={ 500 }>Gas limit</Text>
<Text fontSize="sm" variant="secondary">{ BigNumber(gasLimit).toFormat() }</Text>
</HStack>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
import React from 'react';
import SkeletonTable from 'ui/shared/SkeletonTable';
const TxInternalsSkeletonDesktop = () => {
return (
<SkeletonTable columns={ [ '28%', '20%', '24px', '20%', '16%', '16%' ] }/>
);
};
export default TxInternalsSkeletonDesktop;
......@@ -8,10 +8,10 @@ import appConfig from 'configs/app/config';
import type { data } from 'data/txState';
import { nbsp } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TxStateStorageItem from './TxStateStorageItem';
......@@ -22,7 +22,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
const hasStorageData = Boolean(storage?.length);
return (
<AccountListItemMobile>
<ListItemMobile>
<AccordionItem isDisabled={ !hasStorageData } border={ 0 } w="100%" display="flex" flexDirection="column">
{ ({ isExpanded }) => (
<>
......@@ -94,7 +94,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
</>
) }
</AccordionItem>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
......@@ -18,11 +18,11 @@ const TxType = ({ types }: Props) => {
switch (typeToShow) {
case 'contract_call':
label = 'Contract call';
colorScheme = 'green';
colorScheme = 'blue';
break;
case 'contract_creation':
label = 'Contract creation';
colorScheme = 'purple';
colorScheme = 'blue';
break;
case 'token_transfer':
label = 'Token transfer';
......@@ -30,15 +30,15 @@ const TxType = ({ types }: Props) => {
break;
case 'token_creation':
label = 'Token creation';
colorScheme = 'cyan';
colorScheme = 'orange';
break;
case 'coin_transfer':
label = 'Coin transfer';
colorScheme = 'teal';
colorScheme = 'orange';
break;
default:
label = 'Transaction';
colorScheme = 'blue';
colorScheme = 'purple';
}
......
......@@ -7,12 +7,12 @@ import type { TxsResponse } from 'types/api/transaction';
import useIsMobile from 'lib/hooks/useIsMobile';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import TxsHeaderMobile from './TxsHeaderMobile';
import TxsListItem from './TxsListItem';
import TxsNewItemNotice from './TxsNewItemNotice';
import TxsSkeletonDesktop from './TxsSkeletonDesktop';
import TxsSkeletonMobile from './TxsSkeletonMobile';
import TxsTable from './TxsTable';
import useTxsSort from './useTxsSort';
......@@ -42,8 +42,13 @@ const TxsContent = ({ filter, query, showBlockInfo = true, showSocketInfo = true
if (isLoading) {
return (
<>
<Show below="lg" ssr={ false }><TxsSkeletonMobile showBlockInfo={ showBlockInfo }/></Show>
<Hide below="lg" ssr={ false }><TxsSkeletonDesktop showBlockInfo={ showBlockInfo }/></Hide>
<Show below="lg" ssr={ false }><SkeletonList/></Show>
<Hide below="lg" ssr={ false }>
<SkeletonTable columns={ showBlockInfo ?
[ '32px', '22%', '160px', '20%', '18%', '292px', '20%', '20%' ] :
[ '32px', '22%', '160px', '20%', '292px', '20%', '20%' ]
}/>
</Hide>
</>
);
}
......
......@@ -80,18 +80,20 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
</Flex>
{ tx.timestamp && <Text variant="secondary" fontWeight="400" fontSize="sm">{ timeAgo }</Text> }
</Flex>
<Flex mt={ 3 }>
<Text as="span" whiteSpace="pre">Method </Text>
<Text
as="span"
variant="secondary"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{ tx.method }
</Text>
</Flex>
{ tx.method && (
<Flex mt={ 3 }>
<Text as="span" whiteSpace="pre">Method </Text>
<Text
as="span"
variant="secondary"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{ tx.method }
</Text>
</Flex>
) }
{ showBlockInfo && tx.block !== null && (
<Box mt={ 2 }>
<Text as="span">Block </Text>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import SkeletonTable from 'ui/shared/SkeletonTable';
interface Props {
showBlockInfo: boolean;
}
const TxsInternalsSkeletonDesktop = ({ showBlockInfo }: Props) => {
return (
<Box mb={ 8 }>
<SkeletonTable columns={ showBlockInfo ?
[ '32px', '20%', '18%', '15%', '11%', '292px', '18%', '18%' ] :
[ '32px', '20%', '18%', '15%', '292px', '18%', '18%' ]
}/>
</Box>
);
};
export default TxsInternalsSkeletonDesktop;
import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
interface Props {
showBlockInfo: boolean;
}
const TxInternalsSkeletonMobile = ({ showBlockInfo }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Box>
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
flexDirection="column"
paddingBottom={ 3 }
paddingTop={ 4 }
borderTopWidth="1px"
borderColor={ borderColor }
_last={{
borderBottomWidth: '1px',
}}
>
<Flex h={ 6 }>
<Skeleton w="100px" mr={ 2 } h={ 6 }/>
<Skeleton w="100px" h={ 6 }/>
</Flex>
<Skeleton w="100%" h="30px" mt={ 3 }/>
<Skeleton w="50%" h={ 6 } mt={ 3 }/>
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
{ showBlockInfo && <Skeleton w="100%" h={ 6 } mt={ 6 }/> }
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
</Flex>
)) }
</Box>
);
};
export default TxInternalsSkeletonMobile;
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
interface Props {
pagination: PaginationProps;
isPaginationVisible: boolean;
}
const TxsTabSlot = ({ pagination }: Props) => {
const isMobile = useIsMobile();
if (isMobile) {
const TxsTabSlot = ({ pagination, isPaginationVisible }: Props) => {
if (!isPaginationVisible) {
return null;
}
......
......@@ -28,21 +28,21 @@ const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo, curr
<TheadSticky top={ top }>
<Tr>
<Th width="54px"></Th>
<Th width="18%">Txn hash</Th>
<Th width="20%">Type</Th>
<Th width="15%">Method</Th>
{ showBlockInfo && <Th width="11%">Block</Th> }
<Th width="22%">Txn hash</Th>
<Th width="160px">Type</Th>
<Th width="20%">Method</Th>
{ showBlockInfo && <Th width="18%">Block</Th> }
<Th width={{ xl: '128px', base: '66px' }}>From</Th>
<Th width={{ xl: currentAddress ? '48px' : '36px', base: '0' }}></Th>
<Th width={{ xl: '128px', base: '66px' }}>To</Th>
<Th width="18%" isNumeric>
<Th width="20%" isNumeric>
<Link onClick={ sort('val') } display="flex" justifyContent="end">
{ sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ `Value ${ appConfig.network.currency.symbol }` }
</Link>
</Th>
<Th width="18%" isNumeric pr={ 5 }>
<Th width="20%" isNumeric pr={ 5 }>
<Link onClick={ sort('fee') } display="flex" justifyContent="end">
{ sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
......
......@@ -153,7 +153,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
</Td>
</Hide>
<Td isNumeric>
<CurrencyValue value={ tx.value }/>
<CurrencyValue value={ tx.value } accuracy={ 8 }/>
</Td>
<Td isNumeric>
<CurrencyValue value={ tx.fee.value } accuracy={ 8 }/>
......
......@@ -6,7 +6,7 @@ import type { TWatchlistItem } from 'types/client/account';
import useFetch from 'lib/hooks/useFetch';
import useToast from 'lib/hooks/useToast';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import WatchListAddressItem from './WatchListAddressItem';
......@@ -79,7 +79,7 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ mutate ]);
return (
<AccountListItemMobile>
<ListItemMobile>
<Box maxW="100%">
<WatchListAddressItem item={ item }/>
<HStack spacing={ 3 } mt={ 6 }>
......@@ -103,7 +103,7 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Flex>
</AccountListItemMobile>
</ListItemMobile>
);
};
......
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