Merge pull request #1463 from blockscout/tom2drum/issue-1446
Highlight same addresses in table views
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
|
||||
interface AddressHighlightProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TAddressHighlightContext {
|
||||
highlightedAddress: string | null;
|
||||
onMouseEnter: (event: React.MouseEvent) => void;
|
||||
onMouseLeave: (event: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export const AddressHighlightContext = React.createContext<TAddressHighlightContext | null>(null);
|
||||
|
||||
export function AddressHighlightProvider({ children }: AddressHighlightProviderProps) {
|
||||
const [ highlightedAddress, setHighlightedAddress ] = React.useState<string | null>(null);
|
||||
const timeoutId = React.useRef<number | null>(null);
|
||||
|
||||
const onMouseEnter = React.useCallback((event: React.MouseEvent) => {
|
||||
const hash = event.currentTarget.getAttribute('data-hash');
|
||||
if (hash) {
|
||||
timeoutId.current = window.setTimeout(() => {
|
||||
setHighlightedAddress(hash);
|
||||
}, 100);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onMouseLeave = React.useCallback(() => {
|
||||
setHighlightedAddress(null);
|
||||
typeof timeoutId.current === 'number' && window.clearTimeout(timeoutId.current);
|
||||
}, []);
|
||||
|
||||
const value = React.useMemo(() => {
|
||||
return {
|
||||
highlightedAddress,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
};
|
||||
}, [ highlightedAddress, onMouseEnter, onMouseLeave ]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
typeof timeoutId.current === 'number' && window.clearTimeout(timeoutId.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AddressHighlightContext.Provider value={ value }>
|
||||
{ children }
|
||||
</AddressHighlightContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAddressHighlightContext() {
|
||||
const context = React.useContext(AddressHighlightContext);
|
||||
if (context === undefined) {
|
||||
return null;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -38,8 +38,8 @@ const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }
|
||||
<MenuList zIndex={ 2 }>
|
||||
<MenuOptionGroup defaultValue={ defaultFilter || 'all' } title="Address" type="radio" onChange={ onFilterChange }>
|
||||
<MenuItemOption value="all">All</MenuItemOption>
|
||||
<MenuItemOption value="from">From</MenuItemOption>
|
||||
<MenuItemOption value="to">To</MenuItemOption>
|
||||
<MenuItemOption value="from">Outgoing transactions</MenuItemOption>
|
||||
<MenuItemOption value="to">Incoming transactions</MenuItemOption>
|
||||
</MenuOptionGroup>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
@@ -1,4 +1,4 @@
|
||||
import { Flex, Box, HStack, Skeleton } from '@chakra-ui/react';
|
||||
import { Flex, HStack, Skeleton } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
@@ -6,12 +6,10 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
|
||||
@@ -35,9 +33,6 @@ const TxInternalsListItem = ({
|
||||
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
|
||||
const toData = to ? to : createdContract;
|
||||
|
||||
const isOut = Boolean(currentAddress && currentAddress === from.hash);
|
||||
const isIn = Boolean(currentAddress && currentAddress === toData?.hash);
|
||||
|
||||
return (
|
||||
<ListItemMobile rowGap={ 3 }>
|
||||
<Flex columnGap={ 2 }>
|
||||
@@ -65,28 +60,13 @@ const TxInternalsListItem = ({
|
||||
lineHeight={ 5 }
|
||||
/>
|
||||
</HStack>
|
||||
<Box w="100%" display="flex" columnGap={ 3 }>
|
||||
<AddressEntity
|
||||
address={ from }
|
||||
isLoading={ isLoading }
|
||||
noLink={ isOut }
|
||||
noCopy={ isOut }
|
||||
width="calc((100% - 48px) / 2)"
|
||||
/>
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } isLoading={ isLoading }/> :
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" isLoading={ isLoading }/>
|
||||
}
|
||||
{ toData && (
|
||||
<AddressEntity
|
||||
address={ toData }
|
||||
isLoading={ isLoading }
|
||||
noLink={ isIn }
|
||||
noCopy={ isIn }
|
||||
width="calc((100% - 48px) / 2)"
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ toData }
|
||||
current={ currentAddress }
|
||||
isLoading={ isLoading }
|
||||
w="100%"
|
||||
/>
|
||||
<HStack spacing={ 3 }>
|
||||
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value { config.chain.currency.symbol }</Skeleton>
|
||||
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" minW={ 6 }>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
|
||||
import AddressIntTxsTableItem from './AddressIntTxsTableItem';
|
||||
@@ -16,31 +17,32 @@ interface Props {
|
||||
|
||||
const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => {
|
||||
return (
|
||||
<Table variant="simple" size="sm">
|
||||
<Thead top={ 80 }>
|
||||
<Tr>
|
||||
<Th width="15%">Parent txn hash</Th>
|
||||
<Th width="15%">Type</Th>
|
||||
<Th width="10%">Block</Th>
|
||||
<Th width="20%">From</Th>
|
||||
<Th width="48px" px={ 0 }/>
|
||||
<Th width="20%">To</Th>
|
||||
<Th width="20%" isNumeric>
|
||||
Value { config.chain.currency.symbol }
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.map((item, index) => (
|
||||
<AddressIntTxsTableItem
|
||||
key={ item.transaction_hash + '_' + index }
|
||||
{ ...item }
|
||||
currentAddress={ currentAddress }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
<AddressHighlightProvider>
|
||||
<Table variant="simple" size="sm">
|
||||
<Thead top={ 80 }>
|
||||
<Tr>
|
||||
<Th width="15%">Parent txn hash</Th>
|
||||
<Th width="15%">Type</Th>
|
||||
<Th width="10%">Block</Th>
|
||||
<Th width="40%">From/To</Th>
|
||||
<Th width="20%" isNumeric>
|
||||
Value { config.chain.currency.symbol }
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.map((item, index) => (
|
||||
<AddressIntTxsTableItem
|
||||
key={ item.transaction_hash + '_' + index }
|
||||
{ ...item }
|
||||
currentAddress={ currentAddress }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</AddressHighlightProvider>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
|
||||
|
||||
@@ -34,9 +32,6 @@ const AddressIntTxsTableItem = ({
|
||||
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
|
||||
const toData = to ? to : createdContract;
|
||||
|
||||
const isOut = Boolean(currentAddress && currentAddress === from.hash);
|
||||
const isIn = Boolean(currentAddress && currentAddress === toData?.hash);
|
||||
|
||||
const timeAgo = useTimeAgoIncrement(timestamp, true);
|
||||
|
||||
return (
|
||||
@@ -77,29 +72,13 @@ const AddressIntTxsTableItem = ({
|
||||
/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<AddressEntity
|
||||
address={ from }
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ toData }
|
||||
current={ currentAddress }
|
||||
isLoading={ isLoading }
|
||||
noLink={ isOut }
|
||||
noCopy={ isOut }
|
||||
/>
|
||||
</Td>
|
||||
<Td px={ 0 } verticalAlign="middle">
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } isLoading={ isLoading } w="100%"/> :
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" isLoading={ isLoading }/>
|
||||
}
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ toData && (
|
||||
<AddressEntity
|
||||
address={ toData }
|
||||
isLoading={ isLoading }
|
||||
noLink={ isIn }
|
||||
noCopy={ isIn }
|
||||
/>
|
||||
) }
|
||||
</Td>
|
||||
<Td isNumeric verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block" minW={ 6 }>
|
||||
{ BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
|
||||
|
||||
@@ -6,6 +6,7 @@ import React from 'react';
|
||||
import type { Block } from 'types/api/block';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
|
||||
import BlocksTableItem from 'ui/blocks/BlocksTableItem';
|
||||
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
|
||||
@@ -37,43 +38,45 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum
|
||||
(!isRollup && !config.UI.views.block.hiddenFields?.burnt_fees ? FEES_COL_WEIGHT : 0);
|
||||
|
||||
return (
|
||||
<Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }>
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width="125px">Block</Th>
|
||||
<Th width="120px">Size, bytes</Th>
|
||||
{ !config.UI.views.block.hiddenFields?.miner &&
|
||||
<AddressHighlightProvider>
|
||||
<Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }>
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width="125px">Block</Th>
|
||||
<Th width="120px">Size, bytes</Th>
|
||||
{ !config.UI.views.block.hiddenFields?.miner &&
|
||||
<Th width={ `${ VALIDATOR_COL_WEIGHT / widthBase * 100 }%` } minW="160px">{ capitalize(getNetworkValidatorTitle()) }</Th> }
|
||||
<Th width="64px" isNumeric>Txn</Th>
|
||||
<Th width={ `${ GAS_COL_WEIGHT / widthBase * 100 }%` }>Gas used</Th>
|
||||
{ !isRollup && !config.UI.views.block.hiddenFields?.total_reward &&
|
||||
<Th width="64px" isNumeric>Txn</Th>
|
||||
<Th width={ `${ GAS_COL_WEIGHT / widthBase * 100 }%` }>Gas used</Th>
|
||||
{ !isRollup && !config.UI.views.block.hiddenFields?.total_reward &&
|
||||
<Th width={ `${ REWARD_COL_WEIGHT / widthBase * 100 }%` }>Reward { config.chain.currency.symbol }</Th> }
|
||||
{ !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees &&
|
||||
{ !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees &&
|
||||
<Th width={ `${ FEES_COL_WEIGHT / widthBase * 100 }%` }>Burnt fees { config.chain.currency.symbol }</Th> }
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
type="block"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
<AnimatePresence initial={ false }>
|
||||
{ data.map((item, index) => (
|
||||
<BlocksTableItem
|
||||
key={ item.height + (isLoading ? `${ index }_${ page }` : '') }
|
||||
data={ item }
|
||||
enableTimeIncrement={ page === 1 && !isLoading }
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
type="block"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</AnimatePresence>
|
||||
</Tbody>
|
||||
</Table>
|
||||
) }
|
||||
<AnimatePresence initial={ false }>
|
||||
{ data.map((item, index) => (
|
||||
<BlocksTableItem
|
||||
key={ item.height + (isLoading ? `${ index }_${ page }` : '') }
|
||||
data={ item }
|
||||
enableTimeIncrement={ page === 1 && !isLoading }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</AnimatePresence>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
|
||||
import { TX } from 'stubs/tx';
|
||||
@@ -42,15 +43,17 @@ const LatestTransactions = () => {
|
||||
/>
|
||||
))) }
|
||||
</Box>
|
||||
<Box mb={ 4 } display={{ base: 'none', lg: 'block' }}>
|
||||
{ data.slice(0, txsCount).map(((tx, index) => (
|
||||
<LatestTxsItem
|
||||
key={ tx.hash + (isPlaceholderData ? index : '') }
|
||||
tx={ tx }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
))) }
|
||||
</Box>
|
||||
<AddressHighlightProvider>
|
||||
<Box mb={ 4 } display={{ base: 'none', lg: 'block' }}>
|
||||
{ data.slice(0, txsCount).map(((tx, index) => (
|
||||
<LatestTxsItem
|
||||
key={ tx.hash + (isPlaceholderData ? index : '') }
|
||||
tx={ tx }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
))) }
|
||||
</Box>
|
||||
</AddressHighlightProvider>
|
||||
<Flex justifyContent="center">
|
||||
<LinkInternal fontSize="sm" href={ txsUrl }>View all transactions</LinkInternal>
|
||||
</Flex>
|
||||
|
||||
@@ -13,9 +13,8 @@ import type { Transaction } from 'types/api/transaction';
|
||||
import config from 'configs/app';
|
||||
import getValueWithUnit from 'lib/getValueWithUnit';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
|
||||
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
|
||||
@@ -34,7 +33,10 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
|
||||
|
||||
return (
|
||||
<Grid
|
||||
gridTemplateColumns={ columnNum === 2 ? '3fr 2fr' : '3fr 2fr 150px' }
|
||||
gridTemplateColumns={{
|
||||
lg: columnNum === 2 ? '3fr minmax(auto, 180px)' : '3fr minmax(auto, 180px) 150px',
|
||||
xl: columnNum === 2 ? '3fr minmax(auto, 250px)' : '3fr minmax(auto, 275px) 150px',
|
||||
}}
|
||||
gridGap={ 8 }
|
||||
width="100%"
|
||||
minW="700px"
|
||||
@@ -45,16 +47,17 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
|
||||
display={{ base: 'none', lg: 'grid' }}
|
||||
>
|
||||
<Flex overflow="hidden" w="100%">
|
||||
<TxAdditionalInfo tx={ tx } isLoading={ isLoading }/>
|
||||
<TxAdditionalInfo tx={ tx } isLoading={ isLoading } my="3px"/>
|
||||
<Box ml={ 3 } w="calc(100% - 40px)">
|
||||
<HStack flexWrap="wrap">
|
||||
<HStack flexWrap="wrap" my="3px">
|
||||
<TxType types={ tx.tx_types } isLoading={ isLoading }/>
|
||||
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined } isLoading={ isLoading }/>
|
||||
<TxWatchListTags tx={ tx } isLoading={ isLoading }/>
|
||||
</HStack>
|
||||
<Flex
|
||||
mt={ 2 }
|
||||
alignItems="center"
|
||||
mt="7px"
|
||||
mb="3px"
|
||||
>
|
||||
<TxEntity
|
||||
isLoading={ isLoading }
|
||||
@@ -76,43 +79,21 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Grid alignItems="center" alignSelf="flex-start" templateColumns="24px auto">
|
||||
<IconSvg
|
||||
name="arrows/east"
|
||||
boxSize={ 6 }
|
||||
color="gray.500"
|
||||
transform="rotate(90deg)"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
<Box overflow="hidden" ml={ 1 }>
|
||||
<AddressEntity
|
||||
isLoading={ isLoading }
|
||||
address={ tx.from }
|
||||
fontSize="sm"
|
||||
lineHeight={ 6 }
|
||||
fontWeight="500"
|
||||
mb={ 2 }
|
||||
/>
|
||||
{ dataTo && (
|
||||
<AddressEntity
|
||||
isLoading={ isLoading }
|
||||
address={ dataTo }
|
||||
fontSize="sm"
|
||||
lineHeight={ 6 }
|
||||
fontWeight="500"
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
</Grid>
|
||||
<Box>
|
||||
<AddressFromTo
|
||||
from={ tx.from }
|
||||
to={ dataTo }
|
||||
isLoading={ isLoading }
|
||||
mode="compact"
|
||||
/>
|
||||
<Flex flexDir="column">
|
||||
{ !config.UI.views.tx.hiddenFields?.value && (
|
||||
<Skeleton isLoaded={ !isLoading } mb={ 2 }>
|
||||
<Skeleton isLoaded={ !isLoading } my="3px">
|
||||
<Text as="span" whiteSpace="pre">{ config.chain.currency.symbol } </Text>
|
||||
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text>
|
||||
</Skeleton>
|
||||
) }
|
||||
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
|
||||
<Skeleton isLoaded={ !isLoading } display="flex" whiteSpace="pre">
|
||||
<Skeleton isLoaded={ !isLoading } display="flex" whiteSpace="pre" my="3px">
|
||||
<Text as="span">Fee </Text>
|
||||
{ tx.stability_fee ? (
|
||||
<TxFeeStability data={ tx.stability_fee } accuracy={ 5 } color="text_secondary" hideUsd/>
|
||||
@@ -121,7 +102,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
|
||||
) }
|
||||
</Skeleton>
|
||||
) }
|
||||
</Box>
|
||||
</Flex>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,9 +12,8 @@ import type { Transaction } from 'types/api/transaction';
|
||||
import config from 'configs/app';
|
||||
import getValueWithUnit from 'lib/getValueWithUnit';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
|
||||
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
|
||||
@@ -66,31 +65,14 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
|
||||
</Skeleton>
|
||||
) }
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb={ 3 }>
|
||||
<AddressEntity
|
||||
isLoading={ isLoading }
|
||||
address={ tx.from }
|
||||
truncation="constant"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
mr={ 2 }
|
||||
/>
|
||||
<IconSvg
|
||||
name="arrows/east"
|
||||
boxSize={ 6 }
|
||||
color="gray.500"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
{ dataTo && (
|
||||
<AddressEntity
|
||||
isLoading={ isLoading }
|
||||
address={ dataTo }
|
||||
truncation="constant"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
/>
|
||||
) }
|
||||
</Flex>
|
||||
<AddressFromTo
|
||||
from={ tx.from }
|
||||
to={ dataTo }
|
||||
isLoading={ isLoading }
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
mb={ 3 }
|
||||
/>
|
||||
{ !config.UI.views.tx.hiddenFields?.value && (
|
||||
<Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content">
|
||||
<Text as="span">Value { config.chain.currency.symbol } </Text>
|
||||
|
||||
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 138 KiB |
@@ -1,4 +1,4 @@
|
||||
import { IconButton, Tooltip, useClipboard, chakra, useDisclosure, Skeleton } from '@chakra-ui/react';
|
||||
import { IconButton, Tooltip, useClipboard, chakra, useDisclosure, Skeleton, useColorModeValue } from '@chakra-ui/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
@@ -14,6 +14,7 @@ const CopyToClipboard = ({ text, className, isLoading }: Props) => {
|
||||
const [ copied, setCopied ] = useState(false);
|
||||
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const iconColor = useColorModeValue('gray.400', 'gray.500');
|
||||
|
||||
useEffect(() => {
|
||||
if (hasCopied) {
|
||||
@@ -34,7 +35,7 @@ const CopyToClipboard = ({ text, className, isLoading }: Props) => {
|
||||
icon={ <IconSvg name="copy" boxSize={ 5 }/> }
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="gray.400"
|
||||
color={ iconColor }
|
||||
variant="simple"
|
||||
display="inline-block"
|
||||
flexShrink={ 0 }
|
||||
|
||||
@@ -12,9 +12,9 @@ interface Props extends HTMLChakraProps<'div'> {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const IconSvg = ({ name, isLoading, ...props }: Props) => {
|
||||
const IconSvg = ({ name, isLoading, ...props }: Props, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||
return (
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block" { ...props }>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block" { ...props } ref={ ref }>
|
||||
<chakra.svg w="100%" h="100%">
|
||||
<use href={ `${ href }#${ name }` }/>
|
||||
</chakra.svg>
|
||||
@@ -22,4 +22,4 @@ const IconSvg = ({ name, isLoading, ...props }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default IconSvg;
|
||||
export default React.forwardRef(IconSvg);
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
|
||||
interface Props {
|
||||
isIn: boolean;
|
||||
isOut: boolean;
|
||||
className?: string;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const InOutTag = ({ isIn, isOut, className, isLoading }: Props) => {
|
||||
if (!isIn && !isOut) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const colorScheme = isOut ? 'orange' : 'green';
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={ className }
|
||||
colorScheme={ colorScheme }
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
{ isOut ? 'OUT' : 'IN' }
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(chakra(InOutTag));
|
||||
@@ -35,7 +35,7 @@ const TokenTransferFilter = ({
|
||||
const isInitialLoading = useIsInitialLoading(isLoading);
|
||||
|
||||
return (
|
||||
<PopoverFilter appliedFiltersNum={ appliedFiltersNum } contentProps={{ w: '200px' }} isLoading={ isInitialLoading }>
|
||||
<PopoverFilter appliedFiltersNum={ appliedFiltersNum } contentProps={{ w: '220px' }} isLoading={ isInitialLoading }>
|
||||
{ withAddressFilter && (
|
||||
<>
|
||||
<Text variant="secondary" fontWeight={ 600 }>Address</Text>
|
||||
@@ -49,8 +49,8 @@ const TokenTransferFilter = ({
|
||||
>
|
||||
<Stack spacing={ 4 }>
|
||||
<Radio value="all"><Text fontSize="md">All</Text></Radio>
|
||||
<Radio value="from"><Text fontSize="md">From</Text></Radio>
|
||||
<Radio value="to"><Text fontSize="md">To</Text></Radio>
|
||||
<Radio value="from"><Text fontSize="md">Outgoing transfers</Text></Radio>
|
||||
<Radio value="to"><Text fontSize="md">Incoming transfers</Text></Radio>
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
</>
|
||||
|
||||
@@ -5,13 +5,11 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
|
||||
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import NftEntity from 'ui/shared/entities/nft/NftEntity';
|
||||
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers';
|
||||
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
|
||||
@@ -45,7 +43,6 @@ const TokenTransferListItem = ({
|
||||
decimals: total.decimals || '0',
|
||||
}) : { usd: null, valueStr: null };
|
||||
|
||||
const addressWidth = `calc((100% - ${ baseAddress ? '50px - 24px' : '24px - 24px' }) / 2)`;
|
||||
return (
|
||||
<ListItemMobile rowGap={ 3 } isAnimated>
|
||||
<Flex w="100%" justifyContent="space-between">
|
||||
@@ -80,36 +77,13 @@ const TokenTransferListItem = ({
|
||||
) }
|
||||
</Flex>
|
||||
) }
|
||||
<Flex w="100%" columnGap={ 3 }>
|
||||
<AddressEntity
|
||||
address={ from }
|
||||
isLoading={ isLoading }
|
||||
noLink={ baseAddress === from.hash }
|
||||
noCopy={ baseAddress === from.hash }
|
||||
flexShrink={ 0 }
|
||||
width={ addressWidth }
|
||||
/>
|
||||
{ baseAddress ? (
|
||||
<InOutTag
|
||||
isIn={ baseAddress === to.hash }
|
||||
isOut={ baseAddress === from.hash }
|
||||
w="50px"
|
||||
textAlign="center"
|
||||
isLoading={ isLoading }
|
||||
flexShrink={ 0 }
|
||||
/>
|
||||
) :
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" isLoading={ isLoading } flexShrink={ 0 }/>
|
||||
}
|
||||
<AddressEntity
|
||||
address={ to }
|
||||
isLoading={ isLoading }
|
||||
noLink={ baseAddress === to.hash }
|
||||
noCopy={ baseAddress === to.hash }
|
||||
flexShrink={ 0 }
|
||||
width={ addressWidth }
|
||||
/>
|
||||
</Flex>
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ to }
|
||||
current={ baseAddress }
|
||||
isLoading={ isLoading }
|
||||
w="100%"
|
||||
/>
|
||||
{ valueStr && (
|
||||
<Flex columnGap={ 2 } w="100%">
|
||||
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 } flexShrink={ 0 }>Value</Skeleton>
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
|
||||
import type { TokenTransfer } from 'types/api/tokenTransfer';
|
||||
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
import TokenTransferTableItem from 'ui/shared/TokenTransfer/TokenTransferTableItem';
|
||||
@@ -32,41 +33,41 @@ const TokenTransferTable = ({
|
||||
}: Props) => {
|
||||
|
||||
return (
|
||||
<Table variant="simple" size="sm" minW="950px">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
{ showTxInfo && <Th width="44px"></Th> }
|
||||
<Th width="185px">Token</Th>
|
||||
<Th width="160px">Token ID</Th>
|
||||
{ showTxInfo && <Th width="25%">Txn hash</Th> }
|
||||
<Th width="25%">From</Th>
|
||||
{ baseAddress && <Th width="50px" px={ 0 }/> }
|
||||
<Th width="25%">To</Th>
|
||||
<Th width="25%" isNumeric>Value</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
type="token_transfer"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
{ data.map((item, index) => (
|
||||
<TokenTransferTableItem
|
||||
key={ item.tx_hash + item.block_hash + item.log_index + (isLoading ? index : '') }
|
||||
{ ...item }
|
||||
baseAddress={ baseAddress }
|
||||
showTxInfo={ showTxInfo }
|
||||
enableTimeIncrement={ enableTimeIncrement }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
<AddressHighlightProvider>
|
||||
<Table variant="simple" size="sm" minW="950px">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
{ showTxInfo && <Th width="44px"></Th> }
|
||||
<Th width="185px">Token</Th>
|
||||
<Th width="160px">Token ID</Th>
|
||||
{ showTxInfo && <Th width="20%">Txn hash</Th> }
|
||||
<Th width="50%">From/To</Th>
|
||||
<Th width="30%" isNumeric>Value</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
type="token_transfer"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
{ data.map((item, index) => (
|
||||
<TokenTransferTableItem
|
||||
key={ item.tx_hash + item.block_hash + item.log_index + (isLoading ? index : '') }
|
||||
{ ...item }
|
||||
baseAddress={ baseAddress }
|
||||
showTxInfo={ showTxInfo }
|
||||
enableTimeIncrement={ enableTimeIncrement }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,12 +5,11 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
|
||||
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import NftEntity from 'ui/shared/entities/nft/NftEntity';
|
||||
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers';
|
||||
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
|
||||
|
||||
@@ -85,36 +84,13 @@ const TokenTransferTableItem = ({
|
||||
</Td>
|
||||
) }
|
||||
<Td>
|
||||
<AddressEntity
|
||||
address={ from }
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ to }
|
||||
current={ baseAddress }
|
||||
isLoading={ isLoading }
|
||||
my="5px"
|
||||
noLink={ baseAddress === from.hash }
|
||||
noCopy={ baseAddress === from.hash }
|
||||
flexGrow={ 1 }
|
||||
/>
|
||||
</Td>
|
||||
{ baseAddress && (
|
||||
<Td px={ 0 }>
|
||||
<Box mt="3px">
|
||||
<InOutTag
|
||||
isIn={ baseAddress === to.hash }
|
||||
isOut={ baseAddress === from.hash }
|
||||
w="50px"
|
||||
textAlign="center"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
) }
|
||||
<Td>
|
||||
<AddressEntity
|
||||
address={ to }
|
||||
isLoading={ isLoading }
|
||||
my="5px"
|
||||
noLink={ baseAddress === to.hash }
|
||||
noCopy={ baseAddress === to.hash }
|
||||
flexGrow={ 1 }
|
||||
mt={ 1 }
|
||||
mode={{ lg: 'compact', xl: 'long' }}
|
||||
/>
|
||||
</Td>
|
||||
<Td isNumeric verticalAlign="top">
|
||||
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,62 @@
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import * as addressMock from 'mocks/address/address';
|
||||
import TestApp from 'playwright/TestApp';
|
||||
import * as configs from 'playwright/utils/configs';
|
||||
|
||||
import AddressFromTo from './AddressFromTo';
|
||||
|
||||
test.use({ viewport: configs.viewport.mobile });
|
||||
|
||||
test('outgoing txn', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<AddressFromTo
|
||||
from={ addressMock.withoutName }
|
||||
to={{ ...addressMock.withName, hash: '0xa8FCe579a11E551635b9c9CB915BEcd873C51254' }}
|
||||
current={ addressMock.withoutName.hash }
|
||||
/>
|
||||
</TestApp>,
|
||||
);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('incoming txn', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<AddressFromTo
|
||||
from={{ ...addressMock.withName, hash: '0xa8FCe579a11E551635b9c9CB915BEcd873C51254' }}
|
||||
to={ addressMock.withoutName }
|
||||
current={ addressMock.withoutName.hash }
|
||||
/>
|
||||
</TestApp>,
|
||||
);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('compact mode', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<AddressFromTo
|
||||
from={ addressMock.withoutName }
|
||||
to={{ ...addressMock.withName, hash: '0xa8FCe579a11E551635b9c9CB915BEcd873C51254' }}
|
||||
mode="compact"
|
||||
/>
|
||||
</TestApp>,
|
||||
);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('loading state', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<AddressFromTo
|
||||
from={ addressMock.withoutName }
|
||||
to={{ ...addressMock.withName, hash: '0xa8FCe579a11E551635b9c9CB915BEcd873C51254' }}
|
||||
isLoading
|
||||
/>
|
||||
</TestApp>,
|
||||
);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { ThemeTypings } from '@chakra-ui/react';
|
||||
import { Flex, chakra, useBreakpointValue } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressParam } from 'types/api/addressParams';
|
||||
|
||||
import type { EntityProps } from 'ui/shared/entities/address/AddressEntity';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import AddressEntityWithTokenFilter from 'ui/shared/entities/address/AddressEntityWithTokenFilter';
|
||||
|
||||
import AddressFromToIcon from './AddressFromToIcon';
|
||||
import { getTxCourseType } from './utils';
|
||||
|
||||
type Mode = 'compact' | 'long';
|
||||
|
||||
interface Props {
|
||||
from: AddressParam;
|
||||
to: AddressParam | null;
|
||||
current?: string;
|
||||
mode?: Mode | Partial<Record<ThemeTypings['breakpoints'], Mode>>;
|
||||
className?: string;
|
||||
isLoading?: boolean;
|
||||
tokenHash?: string;
|
||||
truncation?: EntityProps['truncation'];
|
||||
noIcon?: boolean;
|
||||
}
|
||||
|
||||
const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading, tokenHash = '', truncation, noIcon }: Props) => {
|
||||
const mode = useBreakpointValue(
|
||||
{
|
||||
base: (typeof modeProp === 'object' ? modeProp.base : modeProp),
|
||||
lg: (typeof modeProp === 'object' ? modeProp.lg : modeProp),
|
||||
xl: (typeof modeProp === 'object' ? modeProp.xl : modeProp),
|
||||
},
|
||||
) ?? 'long';
|
||||
|
||||
const Entity = tokenHash ? AddressEntityWithTokenFilter : AddressEntity;
|
||||
|
||||
if (mode === 'compact') {
|
||||
return (
|
||||
<Flex className={ className } flexDir="column" rowGap={ 3 }>
|
||||
<Flex alignItems="center" columnGap={ 2 }>
|
||||
<AddressFromToIcon
|
||||
isLoading={ isLoading }
|
||||
type={ getTxCourseType(from.hash, to?.hash, current) }
|
||||
transform="rotate(90deg)"
|
||||
/>
|
||||
<Entity
|
||||
address={ from }
|
||||
isLoading={ isLoading }
|
||||
noLink={ current === from.hash }
|
||||
noCopy={ current === from.hash }
|
||||
noIcon={ noIcon }
|
||||
tokenHash={ tokenHash }
|
||||
truncation={ truncation }
|
||||
maxW={ truncation === 'constant' ? undefined : 'calc(100% - 28px)' }
|
||||
w="min-content"
|
||||
/>
|
||||
</Flex>
|
||||
{ to ? (
|
||||
<Entity
|
||||
address={ to }
|
||||
isLoading={ isLoading }
|
||||
noLink={ current === to.hash }
|
||||
noCopy={ current === to.hash }
|
||||
noIcon={ noIcon }
|
||||
tokenHash={ tokenHash }
|
||||
truncation={ truncation }
|
||||
maxW={ truncation === 'constant' ? undefined : 'calc(100% - 28px)' }
|
||||
w="min-content"
|
||||
ml="28px"
|
||||
/>
|
||||
) : <span>-</span> }
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const isOutgoing = current === from.hash;
|
||||
const iconSizeWithMargins = (5 + (isOutgoing ? 4 : 2) + 3) * 4;
|
||||
|
||||
return (
|
||||
<Flex className={ className } alignItems="center">
|
||||
<Entity
|
||||
address={ from }
|
||||
isLoading={ isLoading }
|
||||
noLink={ isOutgoing }
|
||||
noCopy={ isOutgoing }
|
||||
noIcon={ noIcon }
|
||||
tokenHash={ tokenHash }
|
||||
truncation={ truncation }
|
||||
maxW={ truncation === 'constant' ? undefined : `calc(50% - ${ iconSizeWithMargins / 2 }px)` }
|
||||
mr={ isOutgoing ? 4 : 2 }
|
||||
/>
|
||||
<AddressFromToIcon
|
||||
isLoading={ isLoading }
|
||||
type={ getTxCourseType(from.hash, to?.hash, current) }
|
||||
/>
|
||||
{ to ? (
|
||||
<Entity
|
||||
address={ to }
|
||||
isLoading={ isLoading }
|
||||
noLink={ current === to.hash }
|
||||
noCopy={ current === to.hash }
|
||||
noIcon={ noIcon }
|
||||
tokenHash={ tokenHash }
|
||||
truncation={ truncation }
|
||||
maxW={ truncation === 'constant' ? undefined : `calc(50% - ${ iconSizeWithMargins / 2 }px)` }
|
||||
ml={ 3 }
|
||||
/>
|
||||
) : <span>-</span> }
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default chakra(AddressFromTo);
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import TestApp from 'playwright/TestApp';
|
||||
|
||||
import AddressFromToIcon from './AddressFromToIcon';
|
||||
|
||||
test.use({ viewport: { width: 36, height: 36 } });
|
||||
|
||||
[ 'in', 'out', 'self', 'unspecified' ].forEach((type) => {
|
||||
test(`${ type } txn type +@dark-mode`, async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Box p={ 2 }>
|
||||
<AddressFromToIcon type={ type }/>
|
||||
</Box>
|
||||
</TestApp>,
|
||||
);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Tooltip, chakra, useColorModeValue } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import type { TxCourseType } from './utils';
|
||||
|
||||
interface Props {
|
||||
isLoading?: boolean;
|
||||
type: TxCourseType;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const AddressFromToIcon = ({ isLoading, type, className }: Props) => {
|
||||
const styles = {
|
||||
'in': {
|
||||
color: useColorModeValue('green.500', 'green.200'),
|
||||
bgColor: useColorModeValue('green.50', 'green.800'),
|
||||
},
|
||||
out: {
|
||||
color: useColorModeValue('yellow.600', 'yellow.500'),
|
||||
bgColor: useColorModeValue('orange.50', 'yellow.900'),
|
||||
},
|
||||
self: {
|
||||
color: useColorModeValue('blackAlpha.400', 'whiteAlpha.400'),
|
||||
bgColor: useColorModeValue('blackAlpha.50', 'whiteAlpha.50'),
|
||||
},
|
||||
unspecified: {
|
||||
color: useColorModeValue('gray.500', 'gray.300'),
|
||||
bgColor: 'transparent',
|
||||
},
|
||||
};
|
||||
const labels = {
|
||||
'in': 'Incoming txn',
|
||||
out: 'Outgoing txn',
|
||||
self: 'Txn to the same address',
|
||||
};
|
||||
|
||||
const icon = (
|
||||
<IconSvg
|
||||
name="arrows/east"
|
||||
{ ...(styles[type]) }
|
||||
className={ className }
|
||||
isLoading={ isLoading }
|
||||
boxSize={ 5 }
|
||||
flexShrink={ 0 }
|
||||
borderRadius="sm"
|
||||
/>
|
||||
);
|
||||
|
||||
if (type === 'unspecified') {
|
||||
return icon;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={ labels[type] }>
|
||||
{ icon }
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(chakra(AddressFromToIcon));
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 575 B |
|
After Width: | Height: | Size: 536 B |
|
After Width: | Height: | Size: 516 B |
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 531 B |
|
After Width: | Height: | Size: 457 B |
|
After Width: | Height: | Size: 439 B |
@@ -0,0 +1,21 @@
|
||||
export type TxCourseType = 'in' | 'out' | 'self' | 'unspecified';
|
||||
|
||||
export function getTxCourseType(from: string, to: string | undefined, current?: string): TxCourseType {
|
||||
if (current === undefined) {
|
||||
return 'unspecified';
|
||||
}
|
||||
|
||||
if (to && from === to && from === current) {
|
||||
return 'self';
|
||||
}
|
||||
|
||||
if (from === current) {
|
||||
return 'out';
|
||||
}
|
||||
|
||||
if (to && to === current) {
|
||||
return 'in';
|
||||
}
|
||||
|
||||
return 'unspecified';
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import * as addressMock from 'mocks/address/address';
|
||||
import TestApp from 'playwright/TestApp';
|
||||
|
||||
@@ -140,9 +142,13 @@ test('customization', async({ mount }) => {
|
||||
test('hover', async({ page, mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<AddressEntity
|
||||
address={ addressMock.withoutName }
|
||||
/>
|
||||
<AddressHighlightProvider>
|
||||
<Box p={ 3 }>
|
||||
<AddressEntity
|
||||
address={ addressMock.withoutName }
|
||||
/>
|
||||
</Box>
|
||||
</AddressHighlightProvider>
|
||||
</TestApp>,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { As } from '@chakra-ui/react';
|
||||
import { Box, Flex, Skeleton, Tooltip, chakra, VStack } from '@chakra-ui/react';
|
||||
import { Box, Flex, Skeleton, Tooltip, chakra, VStack, useColorModeValue } from '@chakra-ui/react';
|
||||
import _omit from 'lodash/omit';
|
||||
import React from 'react';
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { AddressParam } from 'types/api/addressParams';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import { useAddressHighlightContext } from 'lib/contexts/addressHighlight';
|
||||
import * as EntityBase from 'ui/shared/entities/base/components';
|
||||
|
||||
import { getIconProps } from '../base/utils';
|
||||
@@ -146,8 +147,35 @@ const AddressEntry = (props: EntityProps) => {
|
||||
const linkProps = _omit(props, [ 'className' ]);
|
||||
const partsProps = _omit(props, [ 'className', 'onClick' ]);
|
||||
|
||||
const context = useAddressHighlightContext();
|
||||
const highlightedBgColor = useColorModeValue('blue.50', 'blue.900');
|
||||
const highlightedBorderColor = useColorModeValue('blue.200', 'blue.600');
|
||||
|
||||
return (
|
||||
<Container className={ props.className }>
|
||||
<Container
|
||||
className={ props.className }
|
||||
data-hash={ props.address.hash }
|
||||
onMouseEnter={ context?.onMouseEnter }
|
||||
onMouseLeave={ context?.onMouseLeave }
|
||||
position="relative"
|
||||
_before={ !props.isLoading && context?.highlightedAddress === props.address.hash ? {
|
||||
content: `" "`,
|
||||
position: 'absolute',
|
||||
py: 1,
|
||||
pl: 1,
|
||||
pr: props.noCopy ? 2 : 0,
|
||||
top: '-5px',
|
||||
left: '-5px',
|
||||
width: `100%`,
|
||||
height: '100%',
|
||||
borderRadius: 'base',
|
||||
borderColor: highlightedBorderColor,
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'dashed',
|
||||
bgColor: highlightedBgColor,
|
||||
zIndex: -1,
|
||||
} : undefined }
|
||||
>
|
||||
<Icon { ...partsProps }/>
|
||||
<Link { ...linkProps }>
|
||||
<Content { ...partsProps }/>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -33,14 +33,17 @@ export interface EntityBaseProps {
|
||||
|
||||
export interface ContainerBaseProps extends Pick<EntityBaseProps, 'className'> {
|
||||
children: React.ReactNode;
|
||||
onMouseEnter?: (event: React.MouseEvent) => void;
|
||||
onMouseLeave?: (event: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const Container = chakra(({ className, children }: ContainerBaseProps) => {
|
||||
const Container = chakra(({ className, children, ...props }: ContainerBaseProps) => {
|
||||
return (
|
||||
<Flex
|
||||
className={ className }
|
||||
alignItems="center"
|
||||
minWidth={ 0 } // for content truncation - https://css-tricks.com/flexbox-truncated-text/
|
||||
{ ...props }
|
||||
>
|
||||
{ children }
|
||||
</Flex>
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import type { TokenInfo } from 'types/api/token';
|
||||
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import ActionBar from 'ui/shared/ActionBar';
|
||||
import DataListDisplay from 'ui/shared/DataListDisplay';
|
||||
@@ -58,21 +59,23 @@ const TokenInventory = ({ inventoryQuery, tokenQuery, ownerFilter }: Props) => {
|
||||
const token = tokenQuery.data;
|
||||
|
||||
const content = items && token ? (
|
||||
<Grid
|
||||
w="100%"
|
||||
columnGap={{ base: 3, lg: 6 }}
|
||||
rowGap={{ base: 3, lg: 6 }}
|
||||
gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }}
|
||||
>
|
||||
{ items.map((item, index) => (
|
||||
<TokenInventoryItem
|
||||
key={ token.address + '_' + item.id + (inventoryQuery.isPlaceholderData ? '_' + index : '') }
|
||||
item={ item }
|
||||
isLoading={ inventoryQuery.isPlaceholderData || tokenQuery.isPlaceholderData }
|
||||
token={ token }
|
||||
/>
|
||||
)) }
|
||||
</Grid>
|
||||
<AddressHighlightProvider>
|
||||
<Grid
|
||||
w="100%"
|
||||
columnGap={{ base: 3, lg: 6 }}
|
||||
rowGap={{ base: 3, lg: 6 }}
|
||||
gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }}
|
||||
>
|
||||
{ items.map((item, index) => (
|
||||
<TokenInventoryItem
|
||||
key={ token.address + '_' + item.id + (inventoryQuery.isPlaceholderData ? '_' + index : '') }
|
||||
item={ item }
|
||||
isLoading={ inventoryQuery.isPlaceholderData || tokenQuery.isPlaceholderData }
|
||||
token={ token }
|
||||
/>
|
||||
)) }
|
||||
</Grid>
|
||||
</AddressHighlightProvider>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,11 +5,10 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
|
||||
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntityWithTokenFilter from 'ui/shared/entities/address/AddressEntityWithTokenFilter';
|
||||
import NftEntity from 'ui/shared/entities/nft/NftEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
import TruncatedValue from 'ui/shared/TruncatedValue';
|
||||
|
||||
@@ -53,23 +52,14 @@ const TokenTransferListItem = ({
|
||||
) }
|
||||
</Flex>
|
||||
{ method && <Tag isLoading={ isLoading }>{ method }</Tag> }
|
||||
<Flex w="100%" columnGap={ 3 }>
|
||||
<AddressEntityWithTokenFilter
|
||||
address={ from }
|
||||
isLoading={ isLoading }
|
||||
tokenHash={ token.address }
|
||||
width="50%"
|
||||
fontWeight="500"
|
||||
/>
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" flexShrink={ 0 } isLoading={ isLoading }/>
|
||||
<AddressEntityWithTokenFilter
|
||||
address={ to }
|
||||
isLoading={ isLoading }
|
||||
tokenHash={ token.address }
|
||||
width="50%"
|
||||
fontWeight="500"
|
||||
/>
|
||||
</Flex>
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ to }
|
||||
isLoading={ isLoading }
|
||||
tokenHash={ token.address }
|
||||
w="100%"
|
||||
fontWeight="500"
|
||||
/>
|
||||
{ valueStr && (token.type === 'ERC-20' || token.type === 'ERC-1155') && (
|
||||
<Grid gap={ 2 } templateColumns={ `1fr auto auto${ usd ? ' auto' : '' }` }>
|
||||
<Skeleton isLoaded={ !isLoading } flexShrink={ 0 } fontWeight={ 500 }>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
import type { TokenInfo } from 'types/api/token';
|
||||
import type { TokenTransfer } from 'types/api/tokenTransfer';
|
||||
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
import TruncatedValue from 'ui/shared/TruncatedValue';
|
||||
@@ -24,42 +25,42 @@ const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socket
|
||||
const tokenType = data[0].token.type;
|
||||
|
||||
return (
|
||||
<Table variant="simple" size="sm" minW="950px">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width={ tokenType === 'ERC-1155' ? '60%' : '80%' }>Txn hash</Th>
|
||||
<Th width="164px">Method</Th>
|
||||
<Th width="160px">From</Th>
|
||||
<Th width="36px" px={ 0 }/>
|
||||
<Th width="218px" >To</Th>
|
||||
{ (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric={ tokenType === 'ERC-721' }>Token ID</Th> }
|
||||
{ (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && (
|
||||
<Th width="20%" isNumeric>
|
||||
<TruncatedValue value={ `Value ${ token?.symbol || '' }` } w="100%" verticalAlign="middle"/>
|
||||
</Th>
|
||||
<AddressHighlightProvider>
|
||||
<Table variant="simple" size="sm">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width={ tokenType === 'ERC-1155' ? '50%' : '75%' }>Txn hash</Th>
|
||||
<Th width="164px">Method</Th>
|
||||
<Th width={{ lg: '200px', xl: '420px' }}>From/To</Th>
|
||||
{ (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && <Th width="25%" isNumeric={ tokenType === 'ERC-721' }>Token ID</Th> }
|
||||
{ (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && (
|
||||
<Th width="25%" isNumeric>
|
||||
<TruncatedValue value={ `Value ${ token?.symbol || '' }` } w="100%" verticalAlign="middle"/>
|
||||
</Th>
|
||||
) }
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
type="token_transfer"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
type="token_transfer"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
{ data.map((item, index) => (
|
||||
<TokenTransferTableItem
|
||||
key={ item.tx_hash + item.block_hash + item.log_index + '_' + index }
|
||||
{ ...item }
|
||||
tokenId={ tokenId }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
{ data.map((item, index) => (
|
||||
<TokenTransferTableItem
|
||||
key={ item.tx_hash + item.block_hash + item.log_index + '_' + index }
|
||||
{ ...item }
|
||||
tokenId={ tokenId }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
|
||||
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntityWithTokenFilter from 'ui/shared/entities/address/AddressEntityWithTokenFilter';
|
||||
import NftEntity from 'ui/shared/entities/nft/NftEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
type Props = TokenTransfer & { tokenId?: string; isLoading?: boolean }
|
||||
|
||||
@@ -60,24 +59,13 @@ const TokenTransferTableItem = ({
|
||||
) : null }
|
||||
</Td>
|
||||
<Td>
|
||||
<AddressEntityWithTokenFilter
|
||||
address={ from }
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ to }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
mt="5px"
|
||||
mode={{ lg: 'compact', xl: 'long' }}
|
||||
tokenHash={ token.address }
|
||||
my="5px"
|
||||
/>
|
||||
</Td>
|
||||
<Td px={ 0 }>
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" mt="3px" isLoading={ isLoading }/>
|
||||
</Td>
|
||||
<Td>
|
||||
<AddressEntityWithTokenFilter
|
||||
address={ to }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
tokenHash={ token.address }
|
||||
my="5px"
|
||||
/>
|
||||
</Td>
|
||||
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && (
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
@@ -4,9 +4,8 @@ import React from 'react';
|
||||
import type { TokenTransfer as TTokenTransfer, Erc20TotalPayload, Erc721TotalPayload, Erc1155TotalPayload } from 'types/api/tokenTransfer';
|
||||
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet';
|
||||
|
||||
interface Props {
|
||||
@@ -75,11 +74,13 @@ const TxDetailsTokenTransfer = ({ data }: Props) => {
|
||||
flexDir="row"
|
||||
w="100%"
|
||||
>
|
||||
<Flex alignItems="center" fontWeight="500">
|
||||
<AddressEntity address={ data.from } truncation="constant" noIcon maxW="150px"/>
|
||||
<IconSvg name="arrows/east" boxSize={ 5 } mx={ 2 } color="gray.500"/>
|
||||
<AddressEntity address={ data.to } truncation="constant" noIcon maxW="150px"/>
|
||||
</Flex>
|
||||
<AddressFromTo
|
||||
from={ data.from }
|
||||
to={ data.to }
|
||||
truncation="constant"
|
||||
noIcon
|
||||
fontWeight="500"
|
||||
/>
|
||||
<Flex flexDir="column" rowGap={ 5 } w="100%" overflow="hidden" fontWeight={ 500 }>
|
||||
{ content }
|
||||
</Flex>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Flex, Box, HStack, Skeleton } from '@chakra-ui/react';
|
||||
import { Flex, HStack, Skeleton } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
|
||||
@@ -24,21 +23,13 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit:
|
||||
{ typeTitle && <Tag colorScheme="cyan" isLoading={ isLoading }>{ typeTitle }</Tag> }
|
||||
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error } isLoading={ isLoading }/>
|
||||
</Flex>
|
||||
<Box w="100%" display="flex" columnGap={ 3 } fontWeight="500">
|
||||
<AddressEntity
|
||||
address={ from }
|
||||
isLoading={ isLoading }
|
||||
width="calc((100% - 48px) / 2)"
|
||||
/>
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" isLoading={ isLoading }/>
|
||||
{ toData && (
|
||||
<AddressEntity
|
||||
address={ toData }
|
||||
isLoading={ isLoading }
|
||||
width="calc((100% - 48px) / 2)"
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ toData }
|
||||
isLoading={ isLoading }
|
||||
w="100%"
|
||||
fontWeight="500"
|
||||
/>
|
||||
<HStack spacing={ 3 }>
|
||||
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value { config.chain.currency.symbol }</Skeleton>
|
||||
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary">
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
|
||||
@@ -21,33 +22,33 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) =
|
||||
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
|
||||
|
||||
return (
|
||||
<Table variant="simple" size="sm">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width="28%">Type</Th>
|
||||
<Th width="20%">From</Th>
|
||||
<Th width="24px" px={ 0 }/>
|
||||
<Th width="20%">To</Th>
|
||||
<Th width="16%" isNumeric>
|
||||
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }>
|
||||
{ sort?.includes('value') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
|
||||
<AddressHighlightProvider>
|
||||
<Table variant="simple" size="sm">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width="28%">Type</Th>
|
||||
<Th width="40%">From/To</Th>
|
||||
<Th width="16%" isNumeric>
|
||||
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }>
|
||||
{ sort?.includes('value') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
|
||||
Value { config.chain.currency.symbol }
|
||||
</Link>
|
||||
</Th>
|
||||
<Th width="16%" isNumeric>
|
||||
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('gas-limit') } columnGap={ 1 }>
|
||||
{ sort?.includes('gas-limit') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
|
||||
</Link>
|
||||
</Th>
|
||||
<Th width="16%" isNumeric>
|
||||
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('gas-limit') } columnGap={ 1 }>
|
||||
{ sort?.includes('gas-limit') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
|
||||
Gas limit { config.chain.currency.symbol }
|
||||
</Link>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.map((item, index) => (
|
||||
<TxInternalsTableItem key={ item.transaction_hash + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Link>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.map((item, index) => (
|
||||
<TxInternalsTableItem key={ item.transaction_hash + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import React from 'react';
|
||||
import type { InternalTransaction } from 'types/api/internalTransaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
|
||||
|
||||
@@ -32,22 +31,12 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
<AddressEntity
|
||||
address={ from }
|
||||
<AddressFromTo
|
||||
from={ from }
|
||||
to={ toData }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
</Td>
|
||||
<Td px={ 0 } verticalAlign="middle">
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" isLoading={ isLoading } display="block"/>
|
||||
</Td>
|
||||
<Td verticalAlign="middle">
|
||||
{ toData && (
|
||||
<AddressEntity
|
||||
address={ toData }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
</Td>
|
||||
<Td isNumeric verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
|
||||
|
||||
@@ -8,6 +8,7 @@ import React from 'react';
|
||||
|
||||
import type { TxStateChange } from 'types/api/txStateChanges';
|
||||
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import { default as Thead } from 'ui/shared/TheadSticky';
|
||||
import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
|
||||
|
||||
@@ -19,21 +20,23 @@ interface Props {
|
||||
|
||||
const TxStateTable = ({ data, isLoading, top }: Props) => {
|
||||
return (
|
||||
<Table variant="simple" minWidth="1000px" size="sm" w="100%">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width="140px">Type</Th>
|
||||
<Th width="160px">Address</Th>
|
||||
<Th width="33%" isNumeric>Before</Th>
|
||||
<Th width="33%" isNumeric>After</Th>
|
||||
<Th width="33%" isNumeric>Change</Th>
|
||||
<Th width="150px" minW="80px" maxW="150px">Token ID</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.map((item, index) => <TxStateTableItem data={ item } key={ index } isLoading={ isLoading }/>) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
<AddressHighlightProvider>
|
||||
<Table variant="simple" minWidth="1000px" size="sm" w="100%">
|
||||
<Thead top={ top }>
|
||||
<Tr>
|
||||
<Th width="140px">Type</Th>
|
||||
<Th width="160px">Address</Th>
|
||||
<Th width="33%" isNumeric>Before</Th>
|
||||
<Th width="33%" isNumeric>After</Th>
|
||||
<Th width="33%" isNumeric>Change</Th>
|
||||
<Th width="150px" minW="80px" maxW="150px">Token ID</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.map((item, index) => <TxStateTableItem data={ item } key={ index } isLoading={ isLoading }/>) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ const TxStateTableItem = ({ data, isLoading }: Props) => {
|
||||
address={ data.address }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
py="7px"
|
||||
my="7px"
|
||||
w="min-content"
|
||||
/>
|
||||
</Td>
|
||||
<Td isNumeric><Box py="7px">{ before }</Box></Td>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
chakra,
|
||||
Modal,
|
||||
ModalContent,
|
||||
ModalCloseButton,
|
||||
@@ -28,9 +29,10 @@ type Props =
|
||||
}) & {
|
||||
isMobile?: boolean;
|
||||
isLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => {
|
||||
const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading, className }: Props) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const content = hash !== undefined ? <TxAdditionalInfoContainer hash={ hash }/> : <TxAdditionalInfoContent tx={ tx }/>;
|
||||
@@ -38,7 +40,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => {
|
||||
if (isMobile) {
|
||||
return (
|
||||
<>
|
||||
<AdditionalInfoButton onClick={ onOpen } isLoading={ isLoading }/>
|
||||
<AdditionalInfoButton onClick={ onOpen } isLoading={ isLoading } className={ className }/>
|
||||
<Modal isOpen={ isOpen } onClose={ onClose } size="full">
|
||||
<ModalContent paddingTop={ 4 }>
|
||||
<ModalCloseButton/>
|
||||
@@ -53,7 +55,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => {
|
||||
{ ({ isOpen }) => (
|
||||
<>
|
||||
<PopoverTrigger>
|
||||
<AdditionalInfoButton isOpen={ isOpen } isLoading={ isLoading }/>
|
||||
<AdditionalInfoButton isOpen={ isOpen } isLoading={ isLoading } className={ className }/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent border="1px solid" borderColor="divider">
|
||||
<PopoverBody>
|
||||
@@ -66,4 +68,4 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TxAdditionalInfo);
|
||||
export default React.memo(chakra(TxAdditionalInfo));
|
||||
|
||||
@@ -11,11 +11,9 @@ import config from 'configs/app';
|
||||
import getValueWithUnit from 'lib/getValueWithUnit';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import { space } from 'lib/html-entities';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
|
||||
@@ -31,15 +29,9 @@ type Props = {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const TAG_WIDTH = 48;
|
||||
const ARROW_WIDTH = 24;
|
||||
|
||||
const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => {
|
||||
const dataTo = tx.to ? tx.to : tx.created_contract;
|
||||
|
||||
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
|
||||
const isIn = Boolean(currentAddress && currentAddress === tx.to?.hash);
|
||||
|
||||
const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement);
|
||||
|
||||
return (
|
||||
@@ -89,37 +81,14 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI
|
||||
/>
|
||||
</Flex>
|
||||
) }
|
||||
<Flex alignItems="center" height={ 6 } mt={ 6 }>
|
||||
<AddressEntity
|
||||
address={ tx.from }
|
||||
isLoading={ isLoading }
|
||||
noLink={ isOut }
|
||||
noCopy={ isOut }
|
||||
w={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }
|
||||
fontWeight="500"
|
||||
/>
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mx={ 2 } isLoading={ isLoading }/> : (
|
||||
<IconSvg
|
||||
name="arrows/east"
|
||||
boxSize={ 6 }
|
||||
color="gray.500"
|
||||
isLoading={ isLoading }
|
||||
mx={ 2 }
|
||||
flexShrink={ 0 }
|
||||
/>
|
||||
) }
|
||||
{ dataTo ? (
|
||||
<AddressEntity
|
||||
address={ dataTo }
|
||||
isLoading={ isLoading }
|
||||
noLink={ isIn }
|
||||
noCopy={ isIn }
|
||||
w={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }
|
||||
fontWeight="500"
|
||||
/>
|
||||
) : '-' }
|
||||
</Flex>
|
||||
<AddressFromTo
|
||||
from={ tx.from }
|
||||
to={ dataTo }
|
||||
current={ currentAddress }
|
||||
isLoading={ isLoading }
|
||||
mt={ 6 }
|
||||
fontWeight="500"
|
||||
/>
|
||||
{ !config.UI.views.tx.hiddenFields?.value && (
|
||||
<Flex mt={ 2 } columnGap={ 2 }>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Value</Skeleton>
|
||||
|
||||
@@ -17,6 +17,8 @@ test.describe('base view', () => {
|
||||
</TestApp>,
|
||||
);
|
||||
|
||||
await component.getByText('kitty').first().hover();
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
@@ -31,6 +33,8 @@ test.describe('base view', () => {
|
||||
</TestApp>,
|
||||
);
|
||||
|
||||
await component.getByText('kitty').first().hover();
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Link, Table, Tbody, Tr, Th, Show, Hide } from '@chakra-ui/react';
|
||||
import { Link, Table, Tbody, Tr, Th } from '@chakra-ui/react';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import React from 'react';
|
||||
|
||||
import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
|
||||
import TheadSticky from 'ui/shared/TheadSticky';
|
||||
@@ -39,65 +40,60 @@ const TxsTable = ({
|
||||
isLoading,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Table variant="simple" minWidth="950px" size="xs">
|
||||
<TheadSticky top={ top }>
|
||||
<Tr>
|
||||
<Th width="54px"></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: '152px', base: '86px' }}>
|
||||
<Show above="xl" ssr={ false }>From</Show>
|
||||
<Hide above="xl" ssr={ false }>From / To</Hide>
|
||||
</Th>
|
||||
<Th width={{ xl: currentAddress ? '48px' : '36px', base: currentAddress ? '52px' : '28px' }}></Th>
|
||||
<Th width={{ xl: '152px', base: '86px' }}>
|
||||
<Show above="xl" ssr={ false }>To</Show>
|
||||
</Th>
|
||||
{ !config.UI.views.tx.hiddenFields?.value && (
|
||||
<Th width="20%" isNumeric>
|
||||
<Link onClick={ sort('value') } display="flex" justifyContent="end">
|
||||
{ sorting === 'value-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> }
|
||||
{ sorting === 'value-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> }
|
||||
{ `Value ${ config.chain.currency.symbol }` }
|
||||
</Link>
|
||||
</Th>
|
||||
) }
|
||||
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
|
||||
<Th width="20%" isNumeric pr={ 5 }>
|
||||
<Link onClick={ sort('fee') } display="flex" justifyContent="end">
|
||||
{ sorting === 'fee-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> }
|
||||
{ sorting === 'fee-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> }
|
||||
{ `Fee${ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ config.chain.currency.symbol }` }` }
|
||||
</Link>
|
||||
</Th>
|
||||
) }
|
||||
</Tr>
|
||||
</TheadSticky>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
<AnimatePresence initial={ false }>
|
||||
{ txs.map((item, index) => (
|
||||
<TxsTableItem
|
||||
key={ item.hash + (isLoading ? index : '') }
|
||||
tx={ item }
|
||||
showBlockInfo={ showBlockInfo }
|
||||
currentAddress={ currentAddress }
|
||||
enableTimeIncrement={ enableTimeIncrement }
|
||||
<AddressHighlightProvider>
|
||||
<Table variant="simple" minWidth="950px" size="xs">
|
||||
<TheadSticky top={ top }>
|
||||
<Tr>
|
||||
<Th width="54px"></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={{ base: '224px', xl: '360px' }}>From/To</Th>
|
||||
{ !config.UI.views.tx.hiddenFields?.value && (
|
||||
<Th width="20%" isNumeric>
|
||||
<Link onClick={ sort('value') } display="flex" justifyContent="end">
|
||||
{ sorting === 'value-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> }
|
||||
{ sorting === 'value-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> }
|
||||
{ `Value ${ config.chain.currency.symbol }` }
|
||||
</Link>
|
||||
</Th>
|
||||
) }
|
||||
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
|
||||
<Th width="20%" isNumeric pr={ 5 }>
|
||||
<Link onClick={ sort('fee') } display="flex" justifyContent="end">
|
||||
{ sorting === 'fee-asc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(-90deg)"/> }
|
||||
{ sorting === 'fee-desc' && <IconSvg boxSize={ 5 } name="arrows/east" transform="rotate(90deg)"/> }
|
||||
{ `Fee${ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ config.chain.currency.symbol }` }` }
|
||||
</Link>
|
||||
</Th>
|
||||
) }
|
||||
</Tr>
|
||||
</TheadSticky>
|
||||
<Tbody>
|
||||
{ showSocketInfo && (
|
||||
<SocketNewItemsNotice.Desktop
|
||||
url={ window.location.href }
|
||||
alert={ socketInfoAlert }
|
||||
num={ socketInfoNum }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</AnimatePresence>
|
||||
</Tbody>
|
||||
</Table>
|
||||
) }
|
||||
<AnimatePresence initial={ false }>
|
||||
{ txs.map((item, index) => (
|
||||
<TxsTableItem
|
||||
key={ item.hash + (isLoading ? index : '') }
|
||||
tx={ item }
|
||||
showBlockInfo={ showBlockInfo }
|
||||
currentAddress={ currentAddress }
|
||||
enableTimeIncrement={ enableTimeIncrement }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
)) }
|
||||
</AnimatePresence>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,11 +2,7 @@ import {
|
||||
Tr,
|
||||
Td,
|
||||
VStack,
|
||||
Show,
|
||||
Hide,
|
||||
Flex,
|
||||
Skeleton,
|
||||
Box,
|
||||
} from '@chakra-ui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import React from 'react';
|
||||
@@ -15,13 +11,11 @@ import type { Transaction } from 'types/api/transaction';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
import AddressFromTo from 'ui/shared/address/AddressFromTo';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import CurrencyValue from 'ui/shared/CurrencyValue';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
||||
import TxFeeStability from 'ui/shared/tx/TxFeeStability';
|
||||
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
|
||||
@@ -39,35 +33,8 @@ type Props = {
|
||||
|
||||
const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, isLoading }: Props) => {
|
||||
const dataTo = tx.to ? tx.to : tx.created_contract;
|
||||
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
|
||||
const isIn = Boolean(currentAddress && currentAddress === dataTo?.hash);
|
||||
|
||||
const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement);
|
||||
|
||||
const addressFrom = (
|
||||
<AddressEntity
|
||||
address={ tx.from }
|
||||
isLoading={ isLoading }
|
||||
noCopy={ isOut }
|
||||
noLink={ isOut }
|
||||
truncation="constant"
|
||||
w="100%"
|
||||
py="2px"
|
||||
/>
|
||||
);
|
||||
|
||||
const addressTo = dataTo ? (
|
||||
<AddressEntity
|
||||
address={ dataTo }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
noCopy={ isIn }
|
||||
noLink={ isIn }
|
||||
w="100%"
|
||||
py="2px"
|
||||
/>
|
||||
) : '-';
|
||||
|
||||
return (
|
||||
<Tr
|
||||
as={ motion.tr }
|
||||
@@ -120,42 +87,16 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
|
||||
) }
|
||||
</Td>
|
||||
) }
|
||||
<Show above="xl" ssr={ false }>
|
||||
<Td>
|
||||
{ addressFrom }
|
||||
</Td>
|
||||
<Td px={ 0 }>
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mr={ 2 } isLoading={ isLoading }/> : (
|
||||
<Box mx="6px">
|
||||
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" isLoading={ isLoading }/>
|
||||
</Box>
|
||||
) }
|
||||
</Td>
|
||||
<Td>
|
||||
{ addressTo }
|
||||
</Td>
|
||||
</Show>
|
||||
<Hide above="xl" ssr={ false }>
|
||||
<Td colSpan={ 3 }>
|
||||
<Flex alignItems="center">
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" isLoading={ isLoading }/> : (
|
||||
<IconSvg
|
||||
name="arrows/east"
|
||||
boxSize={ 6 }
|
||||
color="gray.500"
|
||||
transform="rotate(90deg)"
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
<VStack alignItems="start" overflow="hidden" ml={ 1 }>
|
||||
{ addressFrom }
|
||||
{ addressTo }
|
||||
</VStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Hide>
|
||||
<Td>
|
||||
<AddressFromTo
|
||||
from={ tx.from }
|
||||
to={ dataTo }
|
||||
current={ currentAddress }
|
||||
isLoading={ isLoading }
|
||||
mt="2px"
|
||||
mode={{ lg: 'compact', xl: 'long' }}
|
||||
/>
|
||||
</Td>
|
||||
{ !config.UI.views.tx.hiddenFields?.value && (
|
||||
<Td isNumeric>
|
||||
<CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading }/>
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 55 KiB |