Commit d67d6621 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #1463 from blockscout/tom2drum/issue-1446

Highlight same addresses in table views
parents a58e467b 22699d6c
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 } ...@@ -38,8 +38,8 @@ const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }
<MenuList zIndex={ 2 }> <MenuList zIndex={ 2 }>
<MenuOptionGroup defaultValue={ defaultFilter || 'all' } title="Address" type="radio" onChange={ onFilterChange }> <MenuOptionGroup defaultValue={ defaultFilter || 'all' } title="Address" type="radio" onChange={ onFilterChange }>
<MenuItemOption value="all">All</MenuItemOption> <MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="from">From</MenuItemOption> <MenuItemOption value="from">Outgoing transactions</MenuItemOption>
<MenuItemOption value="to">To</MenuItemOption> <MenuItemOption value="to">Incoming transactions</MenuItemOption>
</MenuOptionGroup> </MenuOptionGroup>
</MenuList> </MenuList>
</Menu> </Menu>
......
import { Flex, Box, HStack, Skeleton } from '@chakra-ui/react'; import { Flex, HStack, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -6,12 +6,10 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; ...@@ -6,12 +6,10 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; 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 ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
...@@ -35,9 +33,6 @@ const TxInternalsListItem = ({ ...@@ -35,9 +33,6 @@ const TxInternalsListItem = ({
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
const toData = to ? to : createdContract; const toData = to ? to : createdContract;
const isOut = Boolean(currentAddress && currentAddress === from.hash);
const isIn = Boolean(currentAddress && currentAddress === toData?.hash);
return ( return (
<ListItemMobile rowGap={ 3 }> <ListItemMobile rowGap={ 3 }>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
...@@ -65,28 +60,13 @@ const TxInternalsListItem = ({ ...@@ -65,28 +60,13 @@ const TxInternalsListItem = ({
lineHeight={ 5 } lineHeight={ 5 }
/> />
</HStack> </HStack>
<Box w="100%" display="flex" columnGap={ 3 }> <AddressFromTo
<AddressEntity from={ from }
address={ from } to={ toData }
isLoading={ isLoading } current={ currentAddress }
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 } isLoading={ isLoading }
noLink={ isIn } w="100%"
noCopy={ isIn }
width="calc((100% - 48px) / 2)"
/> />
) }
</Box>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value { config.chain.currency.symbol }</Skeleton> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value { config.chain.currency.symbol }</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" minW={ 6 }> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" minW={ 6 }>
......
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import AddressIntTxsTableItem from './AddressIntTxsTableItem'; import AddressIntTxsTableItem from './AddressIntTxsTableItem';
...@@ -16,15 +17,14 @@ interface Props { ...@@ -16,15 +17,14 @@ interface Props {
const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => { const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => {
return ( return (
<AddressHighlightProvider>
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ 80 }> <Thead top={ 80 }>
<Tr> <Tr>
<Th width="15%">Parent txn hash</Th> <Th width="15%">Parent txn hash</Th>
<Th width="15%">Type</Th> <Th width="15%">Type</Th>
<Th width="10%">Block</Th> <Th width="10%">Block</Th>
<Th width="20%">From</Th> <Th width="40%">From/To</Th>
<Th width="48px" px={ 0 }/>
<Th width="20%">To</Th>
<Th width="20%" isNumeric> <Th width="20%" isNumeric>
Value { config.chain.currency.symbol } Value { config.chain.currency.symbol }
</Th> </Th>
...@@ -41,6 +41,8 @@ const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => { ...@@ -41,6 +41,8 @@ const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => {
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
</AddressHighlightProvider>
); );
}; };
......
...@@ -6,12 +6,10 @@ import type { InternalTransaction } from 'types/api/internalTransaction'; ...@@ -6,12 +6,10 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; 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 TxStatus from 'ui/shared/statusTag/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
...@@ -34,9 +32,6 @@ const AddressIntTxsTableItem = ({ ...@@ -34,9 +32,6 @@ const AddressIntTxsTableItem = ({
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
const toData = to ? to : createdContract; const toData = to ? to : createdContract;
const isOut = Boolean(currentAddress && currentAddress === from.hash);
const isIn = Boolean(currentAddress && currentAddress === toData?.hash);
const timeAgo = useTimeAgoIncrement(timestamp, true); const timeAgo = useTimeAgoIncrement(timestamp, true);
return ( return (
...@@ -77,29 +72,13 @@ const AddressIntTxsTableItem = ({ ...@@ -77,29 +72,13 @@ const AddressIntTxsTableItem = ({
/> />
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<AddressEntity <AddressFromTo
address={ from } from={ from }
to={ toData }
current={ currentAddress }
isLoading={ isLoading } isLoading={ isLoading }
noLink={ isOut }
noCopy={ isOut }
/> />
</Td> </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"> <Td isNumeric verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block" minW={ 6 }> <Skeleton isLoaded={ !isLoading } display="inline-block" minW={ 6 }>
{ BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } { BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
......
...@@ -6,6 +6,7 @@ import React from 'react'; ...@@ -6,6 +6,7 @@ import React from 'react';
import type { Block } from 'types/api/block'; import type { Block } from 'types/api/block';
import config from 'configs/app'; import config from 'configs/app';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlocksTableItem from 'ui/blocks/BlocksTableItem'; import BlocksTableItem from 'ui/blocks/BlocksTableItem';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
...@@ -37,6 +38,7 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum ...@@ -37,6 +38,7 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum
(!isRollup && !config.UI.views.block.hiddenFields?.burnt_fees ? FEES_COL_WEIGHT : 0); (!isRollup && !config.UI.views.block.hiddenFields?.burnt_fees ? FEES_COL_WEIGHT : 0);
return ( return (
<AddressHighlightProvider>
<Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }> <Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }>
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
...@@ -74,6 +76,7 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum ...@@ -74,6 +76,7 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum
</AnimatePresence> </AnimatePresence>
</Tbody> </Tbody>
</Table> </Table>
</AddressHighlightProvider>
); );
}; };
......
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket'; import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
...@@ -42,6 +43,7 @@ const LatestTransactions = () => { ...@@ -42,6 +43,7 @@ const LatestTransactions = () => {
/> />
))) } ))) }
</Box> </Box>
<AddressHighlightProvider>
<Box mb={ 4 } display={{ base: 'none', lg: 'block' }}> <Box mb={ 4 } display={{ base: 'none', lg: 'block' }}>
{ data.slice(0, txsCount).map(((tx, index) => ( { data.slice(0, txsCount).map(((tx, index) => (
<LatestTxsItem <LatestTxsItem
...@@ -51,6 +53,7 @@ const LatestTransactions = () => { ...@@ -51,6 +53,7 @@ const LatestTransactions = () => {
/> />
))) } ))) }
</Box> </Box>
</AddressHighlightProvider>
<Flex justifyContent="center"> <Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ txsUrl }>View all transactions</LinkInternal> <LinkInternal fontSize="sm" href={ txsUrl }>View all transactions</LinkInternal>
</Flex> </Flex>
......
...@@ -13,9 +13,8 @@ import type { Transaction } from 'types/api/transaction'; ...@@ -13,9 +13,8 @@ import type { Transaction } from 'types/api/transaction';
import config from 'configs/app'; import config from 'configs/app';
import getValueWithUnit from 'lib/getValueWithUnit'; import getValueWithUnit from 'lib/getValueWithUnit';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; 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 TxEntity from 'ui/shared/entities/tx/TxEntity';
import IconSvg from 'ui/shared/IconSvg';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
...@@ -34,7 +33,10 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -34,7 +33,10 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
return ( return (
<Grid <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 } gridGap={ 8 }
width="100%" width="100%"
minW="700px" minW="700px"
...@@ -45,16 +47,17 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -45,16 +47,17 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
display={{ base: 'none', lg: 'grid' }} display={{ base: 'none', lg: 'grid' }}
> >
<Flex overflow="hidden" w="100%"> <Flex overflow="hidden" w="100%">
<TxAdditionalInfo tx={ tx } isLoading={ isLoading }/> <TxAdditionalInfo tx={ tx } isLoading={ isLoading } my="3px"/>
<Box ml={ 3 } w="calc(100% - 40px)"> <Box ml={ 3 } w="calc(100% - 40px)">
<HStack flexWrap="wrap"> <HStack flexWrap="wrap" my="3px">
<TxType types={ tx.tx_types } isLoading={ isLoading }/> <TxType types={ tx.tx_types } isLoading={ isLoading }/>
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined } isLoading={ isLoading }/> <TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined } isLoading={ isLoading }/>
<TxWatchListTags tx={ tx } isLoading={ isLoading }/> <TxWatchListTags tx={ tx } isLoading={ isLoading }/>
</HStack> </HStack>
<Flex <Flex
mt={ 2 }
alignItems="center" alignItems="center"
mt="7px"
mb="3px"
> >
<TxEntity <TxEntity
isLoading={ isLoading } isLoading={ isLoading }
...@@ -76,43 +79,21 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -76,43 +79,21 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
</Flex> </Flex>
</Box> </Box>
</Flex> </Flex>
<Grid alignItems="center" alignSelf="flex-start" templateColumns="24px auto"> <AddressFromTo
<IconSvg from={ tx.from }
name="arrows/east" to={ dataTo }
boxSize={ 6 }
color="gray.500"
transform="rotate(90deg)"
isLoading={ isLoading } isLoading={ isLoading }
mode="compact"
/> />
<Box overflow="hidden" ml={ 1 }> <Flex flexDir="column">
<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>
{ !config.UI.views.tx.hiddenFields?.value && ( { !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" whiteSpace="pre">{ config.chain.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text> <Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text>
</Skeleton> </Skeleton>
) } ) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && ( { !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> <Text as="span">Fee </Text>
{ tx.stability_fee ? ( { tx.stability_fee ? (
<TxFeeStability data={ tx.stability_fee } accuracy={ 5 } color="text_secondary" hideUsd/> <TxFeeStability data={ tx.stability_fee } accuracy={ 5 } color="text_secondary" hideUsd/>
...@@ -121,7 +102,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -121,7 +102,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
) } ) }
</Skeleton> </Skeleton>
) } ) }
</Box> </Flex>
</Grid> </Grid>
); );
}; };
......
...@@ -12,9 +12,8 @@ import type { Transaction } from 'types/api/transaction'; ...@@ -12,9 +12,8 @@ import type { Transaction } from 'types/api/transaction';
import config from 'configs/app'; import config from 'configs/app';
import getValueWithUnit from 'lib/getValueWithUnit'; import getValueWithUnit from 'lib/getValueWithUnit';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; 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 TxEntity from 'ui/shared/entities/tx/TxEntity';
import IconSvg from 'ui/shared/IconSvg';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
...@@ -66,31 +65,14 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -66,31 +65,14 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
</Skeleton> </Skeleton>
) } ) }
</Flex> </Flex>
<Flex alignItems="center" mb={ 3 }> <AddressFromTo
<AddressEntity from={ tx.from }
to={ dataTo }
isLoading={ isLoading } 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" fontSize="sm"
fontWeight="500" fontWeight="500"
mb={ 3 }
/> />
) }
</Flex>
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content"> <Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content">
<Text as="span">Value { config.chain.currency.symbol } </Text> <Text as="span">Value { config.chain.currency.symbol } </Text>
......
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 React, { useEffect, useState } from 'react';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -14,6 +14,7 @@ const CopyToClipboard = ({ text, className, isLoading }: Props) => { ...@@ -14,6 +14,7 @@ const CopyToClipboard = ({ text, className, isLoading }: Props) => {
const [ copied, setCopied ] = useState(false); const [ copied, setCopied ] = useState(false);
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107 // have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const iconColor = useColorModeValue('gray.400', 'gray.500');
useEffect(() => { useEffect(() => {
if (hasCopied) { if (hasCopied) {
...@@ -34,7 +35,7 @@ const CopyToClipboard = ({ text, className, isLoading }: Props) => { ...@@ -34,7 +35,7 @@ const CopyToClipboard = ({ text, className, isLoading }: Props) => {
icon={ <IconSvg name="copy" boxSize={ 5 }/> } icon={ <IconSvg name="copy" boxSize={ 5 }/> }
w="20px" w="20px"
h="20px" h="20px"
color="gray.400" color={ iconColor }
variant="simple" variant="simple"
display="inline-block" display="inline-block"
flexShrink={ 0 } flexShrink={ 0 }
......
...@@ -12,9 +12,9 @@ interface Props extends HTMLChakraProps<'div'> { ...@@ -12,9 +12,9 @@ interface Props extends HTMLChakraProps<'div'> {
isLoading?: boolean; isLoading?: boolean;
} }
const IconSvg = ({ name, isLoading, ...props }: Props) => { const IconSvg = ({ name, isLoading, ...props }: Props, ref: React.ForwardedRef<HTMLDivElement>) => {
return ( return (
<Skeleton isLoaded={ !isLoading } display="inline-block" { ...props }> <Skeleton isLoaded={ !isLoading } display="inline-block" { ...props } ref={ ref }>
<chakra.svg w="100%" h="100%"> <chakra.svg w="100%" h="100%">
<use href={ `${ href }#${ name }` }/> <use href={ `${ href }#${ name }` }/>
</chakra.svg> </chakra.svg>
...@@ -22,4 +22,4 @@ const IconSvg = ({ name, isLoading, ...props }: Props) => { ...@@ -22,4 +22,4 @@ const IconSvg = ({ name, isLoading, ...props }: Props) => {
); );
}; };
export default IconSvg; export default React.forwardRef(IconSvg);
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 = ({ ...@@ -35,7 +35,7 @@ const TokenTransferFilter = ({
const isInitialLoading = useIsInitialLoading(isLoading); const isInitialLoading = useIsInitialLoading(isLoading);
return ( return (
<PopoverFilter appliedFiltersNum={ appliedFiltersNum } contentProps={{ w: '200px' }} isLoading={ isInitialLoading }> <PopoverFilter appliedFiltersNum={ appliedFiltersNum } contentProps={{ w: '220px' }} isLoading={ isInitialLoading }>
{ withAddressFilter && ( { withAddressFilter && (
<> <>
<Text variant="secondary" fontWeight={ 600 }>Address</Text> <Text variant="secondary" fontWeight={ 600 }>Address</Text>
...@@ -49,8 +49,8 @@ const TokenTransferFilter = ({ ...@@ -49,8 +49,8 @@ const TokenTransferFilter = ({
> >
<Stack spacing={ 4 }> <Stack spacing={ 4 }>
<Radio value="all"><Text fontSize="md">All</Text></Radio> <Radio value="all"><Text fontSize="md">All</Text></Radio>
<Radio value="from"><Text fontSize="md">From</Text></Radio> <Radio value="from"><Text fontSize="md">Outgoing transfers</Text></Radio>
<Radio value="to"><Text fontSize="md">To</Text></Radio> <Radio value="to"><Text fontSize="md">Incoming transfers</Text></Radio>
</Stack> </Stack>
</RadioGroup> </RadioGroup>
</> </>
......
...@@ -5,13 +5,11 @@ import type { TokenTransfer } from 'types/api/tokenTransfer'; ...@@ -5,13 +5,11 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; 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 ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers'; import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
...@@ -45,7 +43,6 @@ const TokenTransferListItem = ({ ...@@ -45,7 +43,6 @@ const TokenTransferListItem = ({
decimals: total.decimals || '0', decimals: total.decimals || '0',
}) : { usd: null, valueStr: null }; }) : { usd: null, valueStr: null };
const addressWidth = `calc((100% - ${ baseAddress ? '50px - 24px' : '24px - 24px' }) / 2)`;
return ( return (
<ListItemMobile rowGap={ 3 } isAnimated> <ListItemMobile rowGap={ 3 } isAnimated>
<Flex w="100%" justifyContent="space-between"> <Flex w="100%" justifyContent="space-between">
...@@ -80,36 +77,13 @@ const TokenTransferListItem = ({ ...@@ -80,36 +77,13 @@ const TokenTransferListItem = ({
) } ) }
</Flex> </Flex>
) } ) }
<Flex w="100%" columnGap={ 3 }> <AddressFromTo
<AddressEntity from={ from }
address={ from } to={ to }
current={ baseAddress }
isLoading={ isLoading } isLoading={ isLoading }
noLink={ baseAddress === from.hash } w="100%"
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>
{ valueStr && ( { valueStr && (
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 } flexShrink={ 0 }>Value</Skeleton> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 } flexShrink={ 0 }>Value</Skeleton>
......
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import TokenTransferTableItem from 'ui/shared/TokenTransfer/TokenTransferTableItem'; import TokenTransferTableItem from 'ui/shared/TokenTransfer/TokenTransferTableItem';
...@@ -32,17 +33,16 @@ const TokenTransferTable = ({ ...@@ -32,17 +33,16 @@ const TokenTransferTable = ({
}: Props) => { }: Props) => {
return ( return (
<AddressHighlightProvider>
<Table variant="simple" size="sm" minW="950px"> <Table variant="simple" size="sm" minW="950px">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
{ showTxInfo && <Th width="44px"></Th> } { showTxInfo && <Th width="44px"></Th> }
<Th width="185px">Token</Th> <Th width="185px">Token</Th>
<Th width="160px">Token ID</Th> <Th width="160px">Token ID</Th>
{ showTxInfo && <Th width="25%">Txn hash</Th> } { showTxInfo && <Th width="20%">Txn hash</Th> }
<Th width="25%">From</Th> <Th width="50%">From/To</Th>
{ baseAddress && <Th width="50px" px={ 0 }/> } <Th width="30%" isNumeric>Value</Th>
<Th width="25%">To</Th>
<Th width="25%" isNumeric>Value</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
...@@ -67,6 +67,7 @@ const TokenTransferTable = ({ ...@@ -67,6 +67,7 @@ const TokenTransferTable = ({
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
</AddressHighlightProvider>
); );
}; };
......
...@@ -5,12 +5,11 @@ import type { TokenTransfer } from 'types/api/tokenTransfer'; ...@@ -5,12 +5,11 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import InOutTag from 'ui/shared/InOutTag';
import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers'; import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
...@@ -85,36 +84,13 @@ const TokenTransferTableItem = ({ ...@@ -85,36 +84,13 @@ const TokenTransferTableItem = ({
</Td> </Td>
) } ) }
<Td> <Td>
<AddressEntity <AddressFromTo
address={ from } from={ from }
to={ to }
current={ baseAddress }
isLoading={ isLoading } isLoading={ isLoading }
my="5px" mt={ 1 }
noLink={ baseAddress === from.hash } mode={{ lg: 'compact', xl: 'long' }}
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 }
/> />
</Td> </Td>
<Td isNumeric verticalAlign="top"> <Td isNumeric verticalAlign="top">
......
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();
});
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);
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();
});
});
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));
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';
}
import { Box } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import * as addressMock from 'mocks/address/address'; import * as addressMock from 'mocks/address/address';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
...@@ -140,9 +142,13 @@ test('customization', async({ mount }) => { ...@@ -140,9 +142,13 @@ test('customization', async({ mount }) => {
test('hover', async({ page, mount }) => { test('hover', async({ page, mount }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<AddressHighlightProvider>
<Box p={ 3 }>
<AddressEntity <AddressEntity
address={ addressMock.withoutName } address={ addressMock.withoutName }
/> />
</Box>
</AddressHighlightProvider>
</TestApp>, </TestApp>,
); );
......
import type { As } from '@chakra-ui/react'; 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 _omit from 'lodash/omit';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,7 @@ import type { AddressParam } from 'types/api/addressParams'; ...@@ -7,6 +7,7 @@ import type { AddressParam } from 'types/api/addressParams';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import { useAddressHighlightContext } from 'lib/contexts/addressHighlight';
import * as EntityBase from 'ui/shared/entities/base/components'; import * as EntityBase from 'ui/shared/entities/base/components';
import { getIconProps } from '../base/utils'; import { getIconProps } from '../base/utils';
...@@ -146,8 +147,35 @@ const AddressEntry = (props: EntityProps) => { ...@@ -146,8 +147,35 @@ const AddressEntry = (props: EntityProps) => {
const linkProps = _omit(props, [ 'className' ]); const linkProps = _omit(props, [ 'className' ]);
const partsProps = _omit(props, [ 'className', 'onClick' ]); const partsProps = _omit(props, [ 'className', 'onClick' ]);
const context = useAddressHighlightContext();
const highlightedBgColor = useColorModeValue('blue.50', 'blue.900');
const highlightedBorderColor = useColorModeValue('blue.200', 'blue.600');
return ( 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 }/> <Icon { ...partsProps }/>
<Link { ...linkProps }> <Link { ...linkProps }>
<Content { ...partsProps }/> <Content { ...partsProps }/>
......
...@@ -33,14 +33,17 @@ export interface EntityBaseProps { ...@@ -33,14 +33,17 @@ export interface EntityBaseProps {
export interface ContainerBaseProps extends Pick<EntityBaseProps, 'className'> { export interface ContainerBaseProps extends Pick<EntityBaseProps, 'className'> {
children: React.ReactNode; 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 ( return (
<Flex <Flex
className={ className } className={ className }
alignItems="center" alignItems="center"
minWidth={ 0 } // for content truncation - https://css-tricks.com/flexbox-truncated-text/ minWidth={ 0 } // for content truncation - https://css-tricks.com/flexbox-truncated-text/
{ ...props }
> >
{ children } { children }
</Flex> </Flex>
......
...@@ -5,6 +5,7 @@ import React from 'react'; ...@@ -5,6 +5,7 @@ import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
...@@ -58,6 +59,7 @@ const TokenInventory = ({ inventoryQuery, tokenQuery, ownerFilter }: Props) => { ...@@ -58,6 +59,7 @@ const TokenInventory = ({ inventoryQuery, tokenQuery, ownerFilter }: Props) => {
const token = tokenQuery.data; const token = tokenQuery.data;
const content = items && token ? ( const content = items && token ? (
<AddressHighlightProvider>
<Grid <Grid
w="100%" w="100%"
columnGap={{ base: 3, lg: 6 }} columnGap={{ base: 3, lg: 6 }}
...@@ -73,6 +75,7 @@ const TokenInventory = ({ inventoryQuery, tokenQuery, ownerFilter }: Props) => { ...@@ -73,6 +75,7 @@ const TokenInventory = ({ inventoryQuery, tokenQuery, ownerFilter }: Props) => {
/> />
)) } )) }
</Grid> </Grid>
</AddressHighlightProvider>
) : null; ) : null;
return ( return (
......
...@@ -5,11 +5,10 @@ import type { TokenTransfer } from 'types/api/tokenTransfer'; ...@@ -5,11 +5,10 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import AddressEntityWithTokenFilter from 'ui/shared/entities/address/AddressEntityWithTokenFilter';
import NftEntity from 'ui/shared/entities/nft/NftEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import IconSvg from 'ui/shared/IconSvg';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TruncatedValue from 'ui/shared/TruncatedValue'; import TruncatedValue from 'ui/shared/TruncatedValue';
...@@ -53,23 +52,14 @@ const TokenTransferListItem = ({ ...@@ -53,23 +52,14 @@ const TokenTransferListItem = ({
) } ) }
</Flex> </Flex>
{ method && <Tag isLoading={ isLoading }>{ method }</Tag> } { method && <Tag isLoading={ isLoading }>{ method }</Tag> }
<Flex w="100%" columnGap={ 3 }> <AddressFromTo
<AddressEntityWithTokenFilter from={ from }
address={ from } to={ to }
isLoading={ isLoading } isLoading={ isLoading }
tokenHash={ token.address } tokenHash={ token.address }
width="50%" w="100%"
fontWeight="500" 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>
{ valueStr && (token.type === 'ERC-20' || token.type === 'ERC-1155') && ( { valueStr && (token.type === 'ERC-20' || token.type === 'ERC-1155') && (
<Grid gap={ 2 } templateColumns={ `1fr auto auto${ usd ? ' auto' : '' }` }> <Grid gap={ 2 } templateColumns={ `1fr auto auto${ usd ? ' auto' : '' }` }>
<Skeleton isLoaded={ !isLoading } flexShrink={ 0 } fontWeight={ 500 }> <Skeleton isLoaded={ !isLoading } flexShrink={ 0 } fontWeight={ 500 }>
......
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import TruncatedValue from 'ui/shared/TruncatedValue'; import TruncatedValue from 'ui/shared/TruncatedValue';
...@@ -24,17 +25,16 @@ const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socket ...@@ -24,17 +25,16 @@ const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socket
const tokenType = data[0].token.type; const tokenType = data[0].token.type;
return ( return (
<Table variant="simple" size="sm" minW="950px"> <AddressHighlightProvider>
<Table variant="simple" size="sm">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width={ tokenType === 'ERC-1155' ? '60%' : '80%' }>Txn hash</Th> <Th width={ tokenType === 'ERC-1155' ? '50%' : '75%' }>Txn hash</Th>
<Th width="164px">Method</Th> <Th width="164px">Method</Th>
<Th width="160px">From</Th> <Th width={{ lg: '200px', xl: '420px' }}>From/To</Th>
<Th width="36px" px={ 0 }/> { (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && <Th width="25%" isNumeric={ tokenType === 'ERC-721' }>Token ID</Th> }
<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') && ( { (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && (
<Th width="20%" isNumeric> <Th width="25%" isNumeric>
<TruncatedValue value={ `Value ${ token?.symbol || '' }` } w="100%" verticalAlign="middle"/> <TruncatedValue value={ `Value ${ token?.symbol || '' }` } w="100%" verticalAlign="middle"/>
</Th> </Th>
) } ) }
...@@ -60,6 +60,7 @@ const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socket ...@@ -60,6 +60,7 @@ const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socket
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
</AddressHighlightProvider>
); );
}; };
......
...@@ -5,11 +5,10 @@ import type { TokenTransfer } from 'types/api/tokenTransfer'; ...@@ -5,11 +5,10 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import AddressEntityWithTokenFilter from 'ui/shared/entities/address/AddressEntityWithTokenFilter';
import NftEntity from 'ui/shared/entities/nft/NftEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import IconSvg from 'ui/shared/IconSvg';
type Props = TokenTransfer & { tokenId?: string; isLoading?: boolean } type Props = TokenTransfer & { tokenId?: string; isLoading?: boolean }
...@@ -60,24 +59,13 @@ const TokenTransferTableItem = ({ ...@@ -60,24 +59,13 @@ const TokenTransferTableItem = ({
) : null } ) : null }
</Td> </Td>
<Td> <Td>
<AddressEntityWithTokenFilter <AddressFromTo
address={ from } from={ from }
to={ to }
isLoading={ isLoading } isLoading={ isLoading }
truncation="constant" mt="5px"
mode={{ lg: 'compact', xl: 'long' }}
tokenHash={ token.address } 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> </Td>
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && ( { (token.type === 'ERC-721' || token.type === 'ERC-1155') && (
......
...@@ -4,9 +4,8 @@ import React from 'react'; ...@@ -4,9 +4,8 @@ import React from 'react';
import type { TokenTransfer as TTokenTransfer, Erc20TotalPayload, Erc721TotalPayload, Erc1155TotalPayload } from 'types/api/tokenTransfer'; import type { TokenTransfer as TTokenTransfer, Erc20TotalPayload, Erc721TotalPayload, Erc1155TotalPayload } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue'; 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 TokenEntity from 'ui/shared/entities/token/TokenEntity';
import IconSvg from 'ui/shared/IconSvg';
import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet'; import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet';
interface Props { interface Props {
...@@ -75,11 +74,13 @@ const TxDetailsTokenTransfer = ({ data }: Props) => { ...@@ -75,11 +74,13 @@ const TxDetailsTokenTransfer = ({ data }: Props) => {
flexDir="row" flexDir="row"
w="100%" w="100%"
> >
<Flex alignItems="center" fontWeight="500"> <AddressFromTo
<AddressEntity address={ data.from } truncation="constant" noIcon maxW="150px"/> from={ data.from }
<IconSvg name="arrows/east" boxSize={ 5 } mx={ 2 } color="gray.500"/> to={ data.to }
<AddressEntity address={ data.to } truncation="constant" noIcon maxW="150px"/> truncation="constant"
</Flex> noIcon
fontWeight="500"
/>
<Flex flexDir="column" rowGap={ 5 } w="100%" overflow="hidden" fontWeight={ 500 }> <Flex flexDir="column" rowGap={ 5 } w="100%" overflow="hidden" fontWeight={ 500 }>
{ content } { content }
</Flex> </Flex>
......
import { Flex, Box, HStack, Skeleton } from '@chakra-ui/react'; import { Flex, HStack, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; 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 ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
...@@ -24,21 +23,13 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit: ...@@ -24,21 +23,13 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit:
{ typeTitle && <Tag colorScheme="cyan" isLoading={ isLoading }>{ typeTitle }</Tag> } { typeTitle && <Tag colorScheme="cyan" isLoading={ isLoading }>{ typeTitle }</Tag> }
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error } isLoading={ isLoading }/> <TxStatus status={ success ? 'ok' : 'error' } errorText={ error } isLoading={ isLoading }/>
</Flex> </Flex>
<Box w="100%" display="flex" columnGap={ 3 } fontWeight="500"> <AddressFromTo
<AddressEntity from={ from }
address={ from } to={ toData }
isLoading={ isLoading } isLoading={ isLoading }
width="calc((100% - 48px) / 2)" w="100%"
fontWeight="500"
/> />
<IconSvg name="arrows/east" boxSize={ 6 } color="gray.500" isLoading={ isLoading }/>
{ toData && (
<AddressEntity
address={ toData }
isLoading={ isLoading }
width="calc((100% - 48px) / 2)"
/>
) }
</Box>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value { config.chain.currency.symbol }</Skeleton> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value { config.chain.currency.symbol }</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary">
......
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem'; import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
...@@ -21,13 +22,12 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) = ...@@ -21,13 +22,12 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) =
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
<AddressHighlightProvider>
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="28%">Type</Th> <Th width="28%">Type</Th>
<Th width="20%">From</Th> <Th width="40%">From/To</Th>
<Th width="24px" px={ 0 }/>
<Th width="20%">To</Th>
<Th width="16%" isNumeric> <Th width="16%" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }>
{ sort?.includes('value') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('value') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
...@@ -48,6 +48,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) = ...@@ -48,6 +48,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) =
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
</AddressHighlightProvider>
); );
}; };
......
...@@ -5,9 +5,8 @@ import React from 'react'; ...@@ -5,9 +5,8 @@ import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
import config from 'configs/app'; import config from 'configs/app';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; 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 TxStatus from 'ui/shared/statusTag/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
...@@ -32,22 +31,12 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit: ...@@ -32,22 +31,12 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
</Flex> </Flex>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<AddressEntity <AddressFromTo
address={ from } from={ from }
to={ toData }
isLoading={ isLoading } isLoading={ isLoading }
/> />
</Td> </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"> <Td isNumeric verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block"> <Skeleton isLoaded={ !isLoading } display="inline-block">
{ BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } { BigNumber(value).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
......
...@@ -8,6 +8,7 @@ import React from 'react'; ...@@ -8,6 +8,7 @@ import React from 'react';
import type { TxStateChange } from 'types/api/txStateChanges'; import type { TxStateChange } from 'types/api/txStateChanges';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import TxStateTableItem from 'ui/tx/state/TxStateTableItem'; import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
...@@ -19,6 +20,7 @@ interface Props { ...@@ -19,6 +20,7 @@ interface Props {
const TxStateTable = ({ data, isLoading, top }: Props) => { const TxStateTable = ({ data, isLoading, top }: Props) => {
return ( return (
<AddressHighlightProvider>
<Table variant="simple" minWidth="1000px" size="sm" w="100%"> <Table variant="simple" minWidth="1000px" size="sm" w="100%">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
...@@ -34,6 +36,7 @@ const TxStateTable = ({ data, isLoading, top }: Props) => { ...@@ -34,6 +36,7 @@ const TxStateTable = ({ data, isLoading, top }: Props) => {
{ data.map((item, index) => <TxStateTableItem data={ item } key={ index } isLoading={ isLoading }/>) } { data.map((item, index) => <TxStateTableItem data={ item } key={ index } isLoading={ isLoading }/>) }
</Tbody> </Tbody>
</Table> </Table>
</AddressHighlightProvider>
); );
}; };
......
...@@ -27,7 +27,8 @@ const TxStateTableItem = ({ data, isLoading }: Props) => { ...@@ -27,7 +27,8 @@ const TxStateTableItem = ({ data, isLoading }: Props) => {
address={ data.address } address={ data.address }
isLoading={ isLoading } isLoading={ isLoading }
truncation="constant" truncation="constant"
py="7px" my="7px"
w="min-content"
/> />
</Td> </Td>
<Td isNumeric><Box py="7px">{ before }</Box></Td> <Td isNumeric><Box py="7px">{ before }</Box></Td>
......
import { import {
chakra,
Modal, Modal,
ModalContent, ModalContent,
ModalCloseButton, ModalCloseButton,
...@@ -28,9 +29,10 @@ type Props = ...@@ -28,9 +29,10 @@ type Props =
}) & { }) & {
isMobile?: boolean; isMobile?: boolean;
isLoading?: 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 { isOpen, onOpen, onClose } = useDisclosure();
const content = hash !== undefined ? <TxAdditionalInfoContainer hash={ hash }/> : <TxAdditionalInfoContent tx={ tx }/>; const content = hash !== undefined ? <TxAdditionalInfoContainer hash={ hash }/> : <TxAdditionalInfoContent tx={ tx }/>;
...@@ -38,7 +40,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => { ...@@ -38,7 +40,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => {
if (isMobile) { if (isMobile) {
return ( return (
<> <>
<AdditionalInfoButton onClick={ onOpen } isLoading={ isLoading }/> <AdditionalInfoButton onClick={ onOpen } isLoading={ isLoading } className={ className }/>
<Modal isOpen={ isOpen } onClose={ onClose } size="full"> <Modal isOpen={ isOpen } onClose={ onClose } size="full">
<ModalContent paddingTop={ 4 }> <ModalContent paddingTop={ 4 }>
<ModalCloseButton/> <ModalCloseButton/>
...@@ -53,7 +55,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => { ...@@ -53,7 +55,7 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => {
{ ({ isOpen }) => ( { ({ isOpen }) => (
<> <>
<PopoverTrigger> <PopoverTrigger>
<AdditionalInfoButton isOpen={ isOpen } isLoading={ isLoading }/> <AdditionalInfoButton isOpen={ isOpen } isLoading={ isLoading } className={ className }/>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent border="1px solid" borderColor="divider"> <PopoverContent border="1px solid" borderColor="divider">
<PopoverBody> <PopoverBody>
...@@ -66,4 +68,4 @@ const TxAdditionalInfo = ({ hash, tx, isMobile, isLoading }: Props) => { ...@@ -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'; ...@@ -11,11 +11,9 @@ import config from 'configs/app';
import getValueWithUnit from 'lib/getValueWithUnit'; import getValueWithUnit from 'lib/getValueWithUnit';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import { space } from 'lib/html-entities'; 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 BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; 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 ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFeeStability from 'ui/shared/tx/TxFeeStability';
...@@ -31,15 +29,9 @@ type Props = { ...@@ -31,15 +29,9 @@ type Props = {
isLoading?: boolean; isLoading?: boolean;
} }
const TAG_WIDTH = 48;
const ARROW_WIDTH = 24;
const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => { const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeIncrement }: Props) => {
const dataTo = tx.to ? tx.to : tx.created_contract; const dataTo = tx.to ? tx.to : tx.created_contract;
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
const isIn = Boolean(currentAddress && currentAddress === tx.to?.hash);
const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement); const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement);
return ( return (
...@@ -89,37 +81,14 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI ...@@ -89,37 +81,14 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI
/> />
</Flex> </Flex>
) } ) }
<Flex alignItems="center" height={ 6 } mt={ 6 }> <AddressFromTo
<AddressEntity from={ tx.from }
address={ tx.from } to={ dataTo }
current={ currentAddress }
isLoading={ isLoading } isLoading={ isLoading }
noLink={ isOut } mt={ 6 }
noCopy={ isOut }
w={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }
fontWeight="500" 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>
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<Flex mt={ 2 } columnGap={ 2 }> <Flex mt={ 2 } columnGap={ 2 }>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Value</Skeleton> <Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Value</Skeleton>
......
...@@ -17,6 +17,8 @@ test.describe('base view', () => { ...@@ -17,6 +17,8 @@ test.describe('base view', () => {
</TestApp>, </TestApp>,
); );
await component.getByText('kitty').first().hover();
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -31,6 +33,8 @@ test.describe('base view', () => { ...@@ -31,6 +33,8 @@ test.describe('base view', () => {
</TestApp>, </TestApp>,
); );
await component.getByText('kitty').first().hover();
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
}); });
......
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 { AnimatePresence } from 'framer-motion';
import React from 'react'; import React from 'react';
import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction'; import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction';
import config from 'configs/app'; import config from 'configs/app';
import { AddressHighlightProvider } from 'lib/contexts/addressHighlight';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TheadSticky from 'ui/shared/TheadSticky'; import TheadSticky from 'ui/shared/TheadSticky';
...@@ -39,6 +40,7 @@ const TxsTable = ({ ...@@ -39,6 +40,7 @@ const TxsTable = ({
isLoading, isLoading,
}: Props) => { }: Props) => {
return ( return (
<AddressHighlightProvider>
<Table variant="simple" minWidth="950px" size="xs"> <Table variant="simple" minWidth="950px" size="xs">
<TheadSticky top={ top }> <TheadSticky top={ top }>
<Tr> <Tr>
...@@ -47,14 +49,7 @@ const TxsTable = ({ ...@@ -47,14 +49,7 @@ const TxsTable = ({
<Th width="160px">Type</Th> <Th width="160px">Type</Th>
<Th width="20%">Method</Th> <Th width="20%">Method</Th>
{ showBlockInfo && <Th width="18%">Block</Th> } { showBlockInfo && <Th width="18%">Block</Th> }
<Th width={{ xl: '152px', base: '86px' }}> <Th width={{ base: '224px', xl: '360px' }}>From/To</Th>
<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 && ( { !config.UI.views.tx.hiddenFields?.value && (
<Th width="20%" isNumeric> <Th width="20%" isNumeric>
<Link onClick={ sort('value') } display="flex" justifyContent="end"> <Link onClick={ sort('value') } display="flex" justifyContent="end">
...@@ -98,6 +93,7 @@ const TxsTable = ({ ...@@ -98,6 +93,7 @@ const TxsTable = ({
</AnimatePresence> </AnimatePresence>
</Tbody> </Tbody>
</Table> </Table>
</AddressHighlightProvider>
); );
}; };
......
...@@ -2,11 +2,7 @@ import { ...@@ -2,11 +2,7 @@ import {
Tr, Tr,
Td, Td,
VStack, VStack,
Show,
Hide,
Flex,
Skeleton, Skeleton,
Box,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React from 'react'; import React from 'react';
...@@ -15,13 +11,11 @@ import type { Transaction } from 'types/api/transaction'; ...@@ -15,13 +11,11 @@ import type { Transaction } from 'types/api/transaction';
import config from 'configs/app'; import config from 'configs/app';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import AddressFromTo from 'ui/shared/address/AddressFromTo';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import CurrencyValue from 'ui/shared/CurrencyValue'; import CurrencyValue from 'ui/shared/CurrencyValue';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; 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 TxStatus from 'ui/shared/statusTag/TxStatus';
import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxFeeStability from 'ui/shared/tx/TxFeeStability';
import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags';
...@@ -39,35 +33,8 @@ type Props = { ...@@ -39,35 +33,8 @@ type Props = {
const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, isLoading }: Props) => { const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, isLoading }: Props) => {
const dataTo = tx.to ? tx.to : tx.created_contract; const dataTo = tx.to ? tx.to : tx.created_contract;
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
const isIn = Boolean(currentAddress && currentAddress === dataTo?.hash);
const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement); 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 ( return (
<Tr <Tr
as={ motion.tr } as={ motion.tr }
...@@ -120,42 +87,16 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, ...@@ -120,42 +87,16 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
) } ) }
</Td> </Td>
) } ) }
<Show above="xl" ssr={ false }>
<Td> <Td>
{ addressFrom } <AddressFromTo
</Td> from={ tx.from }
<Td px={ 0 }> to={ dataTo }
{ (isIn || isOut) ? current={ currentAddress }
<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 } isLoading={ isLoading }
mt="2px"
mode={{ lg: 'compact', xl: 'long' }}
/> />
) }
<VStack alignItems="start" overflow="hidden" ml={ 1 }>
{ addressFrom }
{ addressTo }
</VStack>
</Flex>
</Td> </Td>
</Hide>
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<Td isNumeric> <Td isNumeric>
<CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading }/> <CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading }/>
......
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