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

Details page: fix line height for rows (#1949)

* refactor block page

* token details

* token instance details

* name domain details

* blob details

* user ops details

* zkEvm and zkSync batch details

* tx details

* address details

* update screenshots
parent 86e33cae
......@@ -10,7 +10,7 @@ import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
......@@ -92,12 +92,16 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
>
<AddressNameInfo data={ data } isLoading={ addressQuery.isPlaceholderData }/>
{ data.is_contract && data.creation_tx_hash && data.creator_address_hash && (
<DetailsInfoItem
title="Creator"
<>
<DetailsInfoItem.Label
hint="Transaction and address of creation"
isLoading={ addressQuery.isPlaceholderData }
>
Creator
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={{ hash: data.creator_address_hash }}
truncation="constant"
......@@ -105,48 +109,63 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
<Text whiteSpace="pre"> at txn </Text>
<TxEntity hash={ data.creation_tx_hash } truncation="constant" noIcon noCopy={ false }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.is_contract && data.implementation_address && (
<DetailsInfoItem
title="Implementation"
<>
<DetailsInfoItem.Label
hint="Implementation address of the proxy contract"
columnGap={ 1 }
>
Implementation
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={{ hash: data.implementation_address, name: data.implementation_name, is_contract: true }}
isLoading={ addressQuery.isPlaceholderData }
noIcon
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<AddressBalance data={ data } isLoading={ addressQuery.isPlaceholderData }/>
{ data.has_tokens && (
<DetailsInfoItem
title="Tokens"
<>
<DetailsInfoItem.Label
hint="All tokens in the account and total value"
alignSelf="center"
py={ 0 }
>
{ addressQuery.data ? <TokenSelect onClick={ handleCounterItemClick }/> : <Box py="6px">0</Box> }
</DetailsInfoItem>
Tokens
</DetailsInfoItem.Label>
<DetailsInfoItem.Value py={ addressQuery.data ? 0 : undefined }>
{ addressQuery.data ? <TokenSelect onClick={ handleCounterItemClick }/> : <Box>0</Box> }
</DetailsInfoItem.Value>
</>
) }
{ (config.features.multichainButton.isEnabled || (data.exchange_rate && data.has_tokens)) && (
<DetailsInfoItem
title="Net worth"
<>
<DetailsInfoItem.Label
hint="Total net worth in USD of all tokens for the address"
alignSelf="center"
isLoading={ addressQuery.isPlaceholderData }
>
Net worth
</DetailsInfoItem.Label>
<DetailsInfoItem.Value alignSelf="center">
<AddressNetWorth addressData={ addressQuery.data } addressHash={ addressHash } isLoading={ addressQuery.isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
)
}
<DetailsInfoItem
title="Transactions"
<DetailsInfoItem.Label
hint="Number of transactions related to this address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Transactions
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="transactions_count"
......@@ -158,13 +177,17 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.has_token_transfers && (
<DetailsInfoItem
title="Transfers"
<>
<DetailsInfoItem.Label
hint="Number of transfers to/from this address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Transfers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="token_transfers_count"
......@@ -176,14 +199,19 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ countersQuery.data?.gas_usage_count && (
<DetailsInfoItem
title="Gas used"
<>
<DetailsInfoItem.Label
hint="Gas used by the address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Gas used
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="gas_usage_count"
......@@ -195,14 +223,19 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.has_validated_blocks && (
<DetailsInfoItem
title="Blocks validated"
<>
<DetailsInfoItem.Label
hint="Number of blocks validated by this validator"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Blocks validated
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="validations_count"
......@@ -214,22 +247,27 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.block_number_balance_updated_at && (
<DetailsInfoItem
title="Last balance update"
<>
<DetailsInfoItem.Label
hint="Block number in which the address was updated"
alignSelf="center"
py={{ base: '2px', lg: 1 }}
isLoading={ addressQuery.isPlaceholderData }
>
Last balance update
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<BlockEntity
number={ data.block_number_balance_updated_at }
isLoading={ addressQuery.isPlaceholderData }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsSponsoredItem isLoading={ addressQuery.isPlaceholderData }/>
</Grid>
</>
......
......@@ -10,7 +10,7 @@ import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import NativeTokenIcon from 'ui/shared/NativeTokenIcon';
interface Props {
......@@ -66,13 +66,14 @@ const AddressBalance = ({ data, isLoading }: Props) => {
});
return (
<DetailsInfoItem
title={ `${ currencyUnits.ether } balance` }
hint={ `Address balance in ${ currencyUnits.ether }. Doesn't include ERC20, ERC721 and ERC1155 tokens` }
flexWrap="nowrap"
alignSelf="center"
<>
<DetailsInfoItem.Label
hint={ `${ currencyUnits.ether } balance` }
isLoading={ isLoading }
>
Balance
</DetailsInfoItem.Label>
<DetailsInfoItem.Value alignSelf="center" flexWrap="nowrap">
<NativeTokenIcon boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<CurrencyValue
value={ data.coin_balance || '0' }
......@@ -84,7 +85,8 @@ const AddressBalance = ({ data, isLoading }: Props) => {
flexWrap="wrap"
isLoading={ isLoading }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -3,7 +3,7 @@ import React from 'react';
import type { Address } from 'types/api/address';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props {
......@@ -14,46 +14,58 @@ interface Props {
const AddressNameInfo = ({ data, isLoading }: Props) => {
if (data.token) {
return (
<DetailsInfoItem
title="Token name"
<>
<DetailsInfoItem.Label
hint="Token name and symbol"
isLoading={ isLoading }
>
Token name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TokenEntity
token={ data.token }
isLoading={ isLoading }
noIcon
noCopy
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
}
if (data.is_contract && data.name) {
return (
<DetailsInfoItem
title="Contract name"
<>
<DetailsInfoItem.Label
hint="The name found in the source code of the Contract"
isLoading={ isLoading }
>
Contract name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading }>
{ data.name }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
}
if (data.name) {
return (
<DetailsInfoItem
title="Validator name"
<>
<DetailsInfoItem.Label
hint="The name of the validator"
isLoading={ isLoading }
>
Validator name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading }>
{ data.name }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
}
......
......@@ -55,7 +55,7 @@ const TokenSelect = ({ onClick }: Props) => {
}
return (
<Flex columnGap={ 3 } mt={{ base: '6px', lg: 0 }}>
<Flex columnGap={ 3 } mt={{ base: 1, lg: 0 }}>
{ isMobile ?
<TokenSelectMobile data={ data } isLoading={ tokensIsFetching === 1 }/> :
<TokenSelectDesktop data={ data } isLoading={ tokensIsFetching === 1 }/>
......
......@@ -4,7 +4,7 @@ import React from 'react';
import type { Blob } from 'types/api/blobs';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
......@@ -30,53 +30,73 @@ const BlobInfo = ({ data, isLoading }: Props) => {
</Skeleton>
</GridItem>
) }
{ data.kzg_proof && (
<DetailsInfoItem
title="Proof"
<>
<DetailsInfoItem.Label
hint="Zero knowledge proof. Allows for quick verification of commitment"
isLoading={ isLoading }
>
Proof
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
{ data.kzg_proof }
<CopyToClipboard text={ data.kzg_proof } isLoading={ isLoading }/>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.kzg_commitment && (
<DetailsInfoItem
title="Commitment"
<>
<DetailsInfoItem.Label
hint="Commitment to the data in the blob"
isLoading={ isLoading }
>
Commitment
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
{ data.kzg_commitment }
<CopyToClipboard text={ data.kzg_commitment } isLoading={ isLoading }/>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_data && (
<DetailsInfoItem
title="Size, bytes"
<>
<DetailsInfoItem.Label
hint="Blob size in bytes"
isLoading={ isLoading }
>
Size, bytes
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
{ (data.blob_data.replace('0x', '').length / 2).toLocaleString() }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_data && <DetailsInfoItemDivider/> }
{ data.transaction_hashes[0] && (
<DetailsInfoItem
title="Transaction hash"
<>
<DetailsInfoItem.Label
hint="Hash of the transaction with this blob"
isLoading={ isLoading }
>
Transaction hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TxEntity hash={ data.transaction_hashes[0].transaction_hash } isLoading={ isLoading } noIcon noCopy={ false }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsSponsoredItem isLoading={ isLoading }/>
{ data.blob_data && (
......
This diff is collapsed.
......@@ -7,7 +7,7 @@ import type { Block } from 'types/api/block';
import { WEI, WEI_IN_GWEI, ZERO } from 'lib/consts';
import { space } from 'lib/html-entities';
import { currencyUnits } from 'lib/units';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import IconSvg from 'ui/shared/IconSvg';
import Utilization from 'ui/shared/Utilization/Utilization';
......@@ -33,30 +33,41 @@ const BlockDetailsBlobInfo = ({ data }: Props) => {
<>
{ data.blob_gas_price && (
<DetailsInfoItem
title="Blob gas price"
<>
<DetailsInfoItem.Label
// eslint-disable-next-line max-len
hint="Price per unit of gas used for for blob deployment. Blob gas is independent of normal gas. Both gas prices can affect the priority of transaction execution."
>
Blob gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.blob_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.blob_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_gas_used && (
<DetailsInfoItem
title="Blob gas used"
<>
<DetailsInfoItem.Label
hint="Actual amount of gas used by the blobs in this block"
>
Blob gas used
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.blob_gas_used).toFormat() }</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !burntBlobFees.isEqualTo(ZERO) && (
<DetailsInfoItem
title="Blob burnt fees"
<>
<DetailsInfoItem.Label
hint={ `Amount of ${ currencyUnits.ether } used for blobs in this block` }
>
Blob burnt fees
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" mr={ 2 }/>
{ burntBlobFees.dividedBy(WEI).toFixed() } { currencyUnits.ether }
{ !blobFees.isEqualTo(ZERO) && (
......@@ -66,18 +77,23 @@ const BlockDetailsBlobInfo = ({ data }: Props) => {
</div>
</Tooltip>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.excess_blob_gas && (
<DetailsInfoItem
title="Excess blob gas"
<>
<DetailsInfoItem.Label
hint="A running total of blob gas consumed in excess of the target, prior to the block."
>
Excess blob gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.excess_blob_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.excess_blob_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/>
</>
......
......@@ -8,7 +8,7 @@ import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources';
import dayjs from 'lib/date/dayjs';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity';
import IconSvg from 'ui/shared/IconSvg';
......@@ -30,25 +30,32 @@ const NameDomainDetails = ({ query }: Props) => {
return (
<Grid columnGap={ 8 } rowGap={ 3 } templateColumns={{ base: 'minmax(0, 1fr)', lg: 'max-content minmax(728px, auto)' }}>
{ query.data?.registration_date && (
<DetailsInfoItem
title="Registration date"
<>
<DetailsInfoItem.Label
hint="The date the name was registered"
isLoading={ isLoading }
>
Registration date
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 }/>
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="20px">
{ dayjs(query.data.registration_date).format('llll') }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.expiry_date && (
<DetailsInfoItem
title="Expiration date"
<>
<DetailsInfoItem.Label
// eslint-disable-next-line max-len
hint="The date the name expires, upon which there is a 90 day grace period for the owner to renew. After the 90 days, the name is released to the market"
isLoading={ isLoading }
display="inline-block"
>
Expiration date
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 } mt="-2px"/>
{ hasExpired && (
<>
......@@ -65,13 +72,19 @@ const NameDomainDetails = ({ query }: Props) => {
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline">
<NameDomainExpiryStatus date={ query.data?.expiry_date }/>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.registrant && (
<DetailsInfoItem
title="Registrant"
<>
<DetailsInfoItem.Label
hint="The account that owns the domain name and has the rights to edit its ownership and records"
isLoading={ isLoading }
>
Registrant
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
......@@ -88,13 +101,19 @@ const NameDomainDetails = ({ query }: Props) => {
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.owner && (
<DetailsInfoItem
title="Owner"
<>
<DetailsInfoItem.Label
hint="The account that owns the rights to edit the records of this domain name"
isLoading={ isLoading }
>
Owner
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
......@@ -111,13 +130,19 @@ const NameDomainDetails = ({ query }: Props) => {
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.wrapped_owner && (
<DetailsInfoItem
title="Manager"
<>
<DetailsInfoItem.Label
hint="Owner of this NFT domain in NameWrapper contract"
isLoading={ isLoading }
>
Manager
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
......@@ -134,25 +159,36 @@ const NameDomainDetails = ({ query }: Props) => {
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.tokens.map((token) => (
<DetailsInfoItem
key={ token.type }
title={ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'Wrapped token ID' : 'Token ID' }
<React.Fragment key={ token.type }>
<DetailsInfoItem.Label
hint={ `The ${ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'wrapped ' : '' }token ID of this domain name NFT` }
isLoading={ isLoading }
>
{ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'Wrapped token ID' : 'Token ID' }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
wordBreak="break-all"
whiteSpace="pre-wrap"
>
<NftEntity hash={ token.contract_hash } id={ token.id } isLoading={ isLoading } noIcon/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</React.Fragment>
)) }
{ otherAddresses.length > 0 && (
<DetailsInfoItem
title="Other addresses"
<>
<DetailsInfoItem.Label
hint="Other cryptocurrency addresses added to this domain name"
isLoading={ isLoading }
>
Other addresses
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -167,7 +203,8 @@ const NameDomainDetails = ({ query }: Props) => {
/>
</Flex>
)) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
);
......
import { Text } from '@chakra-ui/react';
import React from 'react';
import ContainerWithScrollY from 'ui/shared/ContainerWithScrollY';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
export const TX_ACTIONS_BLOCK_ID = 'tx-actions';
const SCROLL_GRADIENT_HEIGHT = 48;
......@@ -25,12 +26,16 @@ const DetailsActionsWrapper = ({ children, isLoading, type }: Props) => {
}, []);
return (
<DetailsInfoItem
title={ `${ type === 'tx' ? 'Transaction' : 'User operation' } action` }
<>
<DetailsInfoItem.Label
hint={ `Highlighted events of the ${ type === 'tx' ? 'transaction' : 'user operation' }` }
note={ hasScroll ? 'Scroll to see more' : undefined }
position="relative"
isLoading={ isLoading }
>
<span>{ `${ type === 'tx' ? 'Transaction' : 'User operation' } action` }</span>
{ hasScroll && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">Scroll to see more</Text> }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
position="relative"
>
<ContainerWithScrollY
containerId={ TX_ACTIONS_BLOCK_ID }
......@@ -44,7 +49,9 @@ const DetailsActionsWrapper = ({ children, isLoading, type }: Props) => {
>
{ children }
</ContainerWithScrollY>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
import { GridItem, Flex, Text, Skeleton } from '@chakra-ui/react';
import type { HTMLChakraProps } from '@chakra-ui/system';
import { chakra, GridItem, Flex, Skeleton } from '@chakra-ui/react';
import React from 'react';
import Hint from 'ui/shared/Hint';
interface Props extends Omit<HTMLChakraProps<'div'>, 'title'> {
title: React.ReactNode;
interface LabelProps {
hint?: string;
children: React.ReactNode;
note?: string;
isLoading?: boolean;
className?: string;
id?: string;
}
const DetailsInfoItem = ({ title, hint, note, children, id, isLoading, ...styles }: Props) => {
const Label = chakra(({ hint, children, isLoading, id, className }: LabelProps) => {
return (
<>
<GridItem py={{ base: 1, lg: 2 }} id={ id } lineHeight={ 5 } { ...styles } _notFirst={{ mt: { base: 3, lg: 0 } }}>
<GridItem
id={ id }
className={ className }
py={ 1 }
lineHeight={{ base: 5, lg: 6 }}
_notFirst={{ mt: { base: 3, lg: 0 } }}
>
<Flex columnGap={ 2 } alignItems="flex-start">
{ hint && <Hint label={ hint } isLoading={ isLoading }/> }
<Skeleton isLoaded={ !isLoading }>
<Text fontWeight={{ base: 700, lg: 500 }}>
{ title }
{ note && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">{ note }</Text> }
</Text>
{ hint && <Hint label={ hint } isLoading={ isLoading } my={{ lg: '2px' }}/> }
<Skeleton isLoaded={ !isLoading } fontWeight={{ base: 700, lg: 500 }}>
{ children }
</Skeleton>
</Flex>
</GridItem>
);
});
interface ValueProps {
children: React.ReactNode;
className?: string;
}
const Value = chakra(({ children, className }: ValueProps) => {
return (
<GridItem
className={ className }
display="flex"
alignItems="center"
flexWrap="wrap"
rowGap={ 3 }
pl={{ base: 7, lg: 0 }}
py={{ base: 1, lg: 2 }}
lineHeight={ 5 }
py={ 1 }
lineHeight={{ base: 5, lg: 6 }}
whiteSpace="nowrap"
{ ...styles }
>
{ children }
</GridItem>
</>
);
};
});
export default DetailsInfoItem;
export {
Label,
Value,
};
......@@ -5,7 +5,7 @@ import config from 'configs/app';
import * as cookies from 'lib/cookies';
import useIsMobile from 'lib/hooks/useIsMobile';
import AdBanner from 'ui/shared/ad/AdBanner';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
const feature = config.features.adsBanner;
......@@ -30,13 +30,17 @@ const DetailsSponsoredItem = ({ isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Sponsored"
<>
<DetailsInfoItem.Label
hint="Sponsored banner advertisement"
isLoading={ isLoading }
>
Sponsored
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AdBanner isLoading={ isLoading }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -32,7 +32,7 @@ const PrevNext = ({ className, onClick, prevLabel, nextLabel, isPrevDisabled, is
}
return (
<Box className={ className }>
<Box className={ className } display="flex">
<Tooltip label={ prevLabel }>
<IconButton
aria-label="prev"
......
......@@ -18,7 +18,7 @@ import { TOKEN_COUNTERS } from 'stubs/token';
import type { TokenTabs } from 'ui/pages/Token';
import AppActionButton from 'ui/shared/AppActionButton/AppActionButton';
import useAppActionData from 'ui/shared/AppActionButton/useAppActionData';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import TruncatedValue from 'ui/shared/TruncatedValue';
......@@ -102,77 +102,96 @@ const TokenDetails = ({ tokenQuery }: Props) => {
<Grid
columnGap={ 8 }
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(728px, auto)' }} overflow="hidden"
>
{ exchangeRate && (
<DetailsInfoItem
title="Price"
<>
<DetailsInfoItem.Label
hint="Price per token on the exchanges"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block">
<span>{ `$${ Number(exchangeRate).toLocaleString(undefined, { minimumSignificantDigits: 4 }) }` }</span>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ marketCap && (
<DetailsInfoItem
title="Fully diluted market cap"
<>
<DetailsInfoItem.Label
hint="Total supply * Price"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Fully diluted market cap
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block">
<span>{ `$${ BigNumber(marketCap).toFormat() }` }</span>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Max total supply"
<DetailsInfoItem.Label
hint="The total amount of tokens issued"
isLoading={ tokenQuery.isPlaceholderData }
>
Max total supply
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
alignSelf="center"
wordBreak="break-word"
whiteSpace="pre-wrap"
isLoading={ tokenQuery.isPlaceholderData }
>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } w="100%" display="flex">
<TruncatedValue value={ totalSupplyValue || '0' } maxW="80%" flexShrink={ 0 }/>
<Box flexShrink={ 0 }> </Box>
<TruncatedValue value={ symbol || '' }/>
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
title="Holders"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Number of accounts holding the token"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Holders
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenCountersQuery.isPlaceholderData }>
{ countersItem('token_holders_count') }
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
title="Transfers"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Number of transfer for the token"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Transfers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenCountersQuery.isPlaceholderData }>
{ countersItem('transfers_count') }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ decimals && (
<DetailsInfoItem
title="Decimals"
<>
<DetailsInfoItem.Label
hint="Number of digits that come after the decimal place when displaying token value"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Decimals
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } minW={ 6 }>
{ decimals }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ type !== 'ERC-20' && (
......@@ -186,14 +205,18 @@ const TokenDetails = ({ tokenQuery }: Props) => {
) }
{ (type !== 'ERC-20' && config.UI.views.nft.marketplaces.length === 0 && appActionData && isActionButtonExperiment) && (
<DetailsInfoItem
title="Dapp"
<>
<DetailsInfoItem.Label
hint="Link to the dapp"
alignSelf="center"
py={ 1 }
>
Dapp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
py="1px"
>
<AppActionButton data={ appActionData } height="30px" source="NFT collection"/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsSponsoredItem isLoading={ tokenQuery.isPlaceholderData }/>
......
......@@ -5,7 +5,7 @@ import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata';
import config from 'configs/app';
import AppActionButton from 'ui/shared/AppActionButton/AppActionButton';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TextSeparator from 'ui/shared/TextSeparator';
interface Props {
......@@ -23,12 +23,15 @@ const TokenNftMarketplaces = ({ hash, id, isLoading, appActionData, source, isAc
}
return (
<DetailsInfoItem
title="Marketplaces"
<>
<DetailsInfoItem.Label
hint="Marketplaces trading this NFT"
alignSelf="center"
isLoading={ isLoading }
py={ (appActionData && isActionButtonExperiment) ? 1 : { base: 1, lg: 2 } }
>
Marketplaces
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
py={ (appActionData && isActionButtonExperiment) ? '1px' : '6px' }
>
<Skeleton isLoaded={ !isLoading } display="flex" columnGap={ 3 } flexWrap="wrap" alignItems="center">
{ config.UI.views.nft.marketplaces.map((item) => {
......@@ -56,7 +59,8 @@ const TokenNftMarketplaces = ({ hash, id, isLoading, appActionData, source, isAc
</>
) }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -8,7 +8,7 @@ import useFeatureValue from 'lib/growthbook/useFeatureValue';
import AppActionButton from 'ui/shared/AppActionButton/AppActionButton';
import useAppActionData from 'ui/shared/AppActionButton/useAppActionData';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
......@@ -53,31 +53,41 @@ const TokenInstanceDetails = ({ data, token, scrollRef, isLoading }: Props) => {
overflow="hidden"
>
{ data.is_unique && data.owner && (
<DetailsInfoItem
title="Owner"
<>
<DetailsInfoItem.Label
hint="Current owner of this token instance"
isLoading={ isLoading }
>
Owner
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ data.owner }
isLoading={ isLoading }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<TokenInstanceCreatorAddress hash={ isLoading ? '' : token.address }/>
<DetailsInfoItem
title="Token ID"
<DetailsInfoItem.Label
hint="This token instance unique token ID"
isLoading={ isLoading }
>
Token ID
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Flex alignItems="center" overflow="hidden">
<Skeleton isLoaded={ !isLoading } overflow="hidden" display="inline-block" w="100%">
<HashStringShortenDynamic hash={ data.id }/>
</Skeleton>
<CopyToClipboard text={ data.id } isLoading={ isLoading }/>
</Flex>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<TokenInstanceTransfersCount hash={ isLoading ? '' : token.address } id={ isLoading ? '' : data.id } onClick={ handleCounterItemClick }/>
<TokenNftMarketplaces
isLoading={ isLoading }
hash={ token.address }
......@@ -86,15 +96,18 @@ const TokenInstanceDetails = ({ data, token, scrollRef, isLoading }: Props) => {
source="NFT item"
isActionButtonExperiment={ isActionButtonExperiment }
/>
{ (config.UI.views.nft.marketplaces.length === 0 && appActionData && isActionButtonExperiment) && (
<DetailsInfoItem
title="Dapp"
<>
<DetailsInfoItem.Label
hint="Link to the dapp"
alignSelf="center"
py={ 1 }
>
Dapp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value py="1px">
<AppActionButton data={ appActionData } height="30px" source="NFT item"/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
<NftMedia
......
......@@ -2,7 +2,7 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { ADDRESS_INFO } from 'stubs/address';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props {
......@@ -33,16 +33,20 @@ const TokenInstanceCreatorAddress = ({ hash }: Props) => {
};
return (
<DetailsInfoItem
title="Creator"
<>
<DetailsInfoItem.Label
hint="Address that deployed this token contract"
isLoading={ addressQuery.isPlaceholderData }
>
Creator
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ creatorAddress }
isLoading={ addressQuery.isPlaceholderData }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -5,7 +5,7 @@ import type { TokenInstance } from 'types/api/token';
import type { MetadataAttributes } from 'types/client/token';
import parseMetadata from 'lib/token/parseMetadata';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import LinkExternal from 'ui/shared/links/LinkExternal';
import TruncatedValue from 'ui/shared/TruncatedValue';
......@@ -74,42 +74,55 @@ const TokenInstanceMetadataInfo = ({ data, isLoading }: Props) => {
<>
<DetailsInfoItemDivider/>
{ metadata?.name && (
<DetailsInfoItem
title="Name"
<>
<DetailsInfoItem.Label
hint="NFT name"
isLoading={ isLoading }
>
Name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.name }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ metadata?.description && (
<DetailsInfoItem
title="Description"
<>
<DetailsInfoItem.Label
hint="NFT description"
isLoading={ isLoading }
>
Description
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.description }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ metadata?.attributes && (
<DetailsInfoItem
title="Attributes"
<>
<DetailsInfoItem.Label
hint="NFT attributes"
whiteSpace="normal"
isLoading={ isLoading }
>
<Grid gap={ 2 } templateColumns="repeat(auto-fill,minmax(160px, 1fr))" w="100%">
Attributes
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Grid gap={ 2 } templateColumns="repeat(auto-fill,minmax(160px, 1fr))" w="100%" whiteSpace="normal">
{ metadata.attributes.map((attribute, index) => <Item key={ index } data={ attribute } isLoading={ isLoading }/>) }
</Grid>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</>
);
......
......@@ -4,7 +4,7 @@ import React from 'react';
import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/links/LinkInternal';
interface Props {
......@@ -37,11 +37,14 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => {
undefined;
return (
<DetailsInfoItem
title="Transfers"
<>
<DetailsInfoItem.Label
hint="Number of transfer for the token instance"
isLoading={ transfersCountQuery.isPlaceholderData }
>
Transfers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !transfersCountQuery.isPlaceholderData } display="inline-block">
<LinkInternal
href={ url }
......@@ -50,7 +53,8 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => {
{ transfersCountQuery.data.transfers_count.toLocaleString() }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
import { Flex, Link, useBoolean } from '@chakra-ui/react';
import React from 'react';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props {
......@@ -14,10 +14,13 @@ const TxAllowedPeekers = ({ items }: Props) => {
const [ isExpanded, expand ] = useBoolean(false);
return (
<DetailsInfoItem
title="Allowed peekers"
<>
<DetailsInfoItem.Label
hint="Smart contracts allowed to interact with confidential data"
>
Allowed peekers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Flex flexDir="column" rowGap={ 3 } w="100%">
{ items
.slice(0, isExpanded ? undefined : CUT_LENGTH)
......@@ -34,7 +37,8 @@ const TxAllowedPeekers = ({ items }: Props) => {
{ isExpanded ? 'Hide' : 'Show all' }
</Link>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -8,7 +8,7 @@ import type { ExcludeUndefined } from 'types/utils';
import { currencyUnits } from 'lib/units';
import Tag from 'ui/shared/chakra/Tag';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
......@@ -24,85 +24,110 @@ interface Props {
const TxDetailsWrapped = ({ data }: Props) => {
return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}>
<DetailsInfoItem
title="Transaction hash"
<DetailsInfoItem.Label
hint="Unique character string (TxID) assigned to every verified transaction"
flexWrap="nowrap"
>
Transaction hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<TxEntity hash={ data.hash } noIcon noLink noCopy={ false }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Method"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Transaction method name"
>
Method
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Tag colorScheme="gray">
{ data.method }
</Tag>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
<DetailsInfoItem
title={ data.to?.is_contract ? 'Interacted with contract' : 'To' }
<DetailsInfoItem.Label
hint="Address (external or contract) receiving the transaction"
flexWrap={{ base: 'wrap', lg: 'nowrap' }}
columnGap={ 3 }
>
{ data.to?.is_contract ? 'Interacted with contract' : 'To' }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Flex flexWrap="nowrap" alignItems="center" maxW="100%">
<AddressEntity address={ data.to }/>
</Flex>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
<DetailsInfoItem
title="Value"
<DetailsInfoItem.Label
hint="Value sent in the native token (and USD) if applicable"
>
Value
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.fee.value !== null && (
<DetailsInfoItem
title="Transaction fee"
<>
<DetailsInfoItem.Label
hint="Total transaction fee"
>
Transaction fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.fee.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<TxDetailsGasPrice gasPrice={ data.gas_price }/>
{ data.gas_limit && (
<DetailsInfoItem
title="Gas limit"
<>
<DetailsInfoItem.Label
hint="Maximum amount of gas that can be used by the transaction"
>
Gas limit
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ BigNumber(data.gas_limit).toFormat() }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/>
<TxDetailsOther type={ data.type } nonce={ data.nonce } position={ null }/>
<DetailsInfoItem
title="Raw input"
<DetailsInfoItem.Label
hint="Binary data included with the transaction. See logs tab for additional info"
>
Raw input
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawInputData hex={ data.raw_input }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.decoded_input && (
<DetailsInfoItem
title="Decoded input data"
<>
<DetailsInfoItem.Label
hint="Decoded input data"
>
Decoded input data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<LogDecodedInputData data={ data.decoded_input }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
);
......
......@@ -7,7 +7,7 @@ import config from 'configs/app';
import { ZERO } from 'lib/consts';
import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import IconSvg from 'ui/shared/IconSvg';
const rollupFeature = config.features.rollup;
......@@ -30,14 +30,17 @@ const TxDetailsBurntFees = ({ data, isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Burnt fees"
<>
<DetailsInfoItem.Label
hint={ `
Amount of ${ currencyUnits.ether } burned for this transaction. Equals Block Base Fee per Gas * Gas Used
${ data.blob_gas_price && data.blob_gas_used ? ' + Blob Gas Price * Blob Gas Used' : '' }
` }
isLoading={ isLoading }
>
Burnt fees
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" isLoading={ isLoading }/>
<CurrencyValue
value={ value.toString() }
......@@ -47,7 +50,8 @@ const TxDetailsBurntFees = ({ data, isLoading }: Props) => {
ml={ 2 }
isLoading={ isLoading }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -4,7 +4,7 @@ import React from 'react';
import config from 'configs/app';
import { currencyUnits } from 'lib/units';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
interface Props {
txFee: string | null;
......@@ -18,16 +18,20 @@ const TxDetailsFeePerGas = ({ txFee, gasUsed, isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Fee per gas"
<>
<DetailsInfoItem.Label
hint="Fee per gas"
isLoading={ isLoading }
>
Fee per gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(txFee).dividedBy(10 ** config.chain.currency.decimals).dividedBy(gasUsed).toFixed() }
{ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ currencyUnits.ether }` }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -5,7 +5,7 @@ import React from 'react';
import config from 'configs/app';
import { WEI, WEI_IN_GWEI } from 'lib/consts';
import { currencyUnits } from 'lib/units';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
interface Props {
gasPrice: string | null;
......@@ -18,18 +18,22 @@ const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Gas price"
<>
<DetailsInfoItem.Label
hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage"
isLoading={ isLoading }
>
Gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(gasPrice).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>({ BigNumber(gasPrice).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</span>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -3,17 +3,20 @@ import React from 'react';
import type { Transaction } from 'types/api/transaction';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TextSeparator from 'ui/shared/TextSeparator';
type Props = Pick<Transaction, 'nonce' | 'type' | 'position'>
const TxDetailsOther = ({ nonce, type, position }: Props) => {
return (
<DetailsInfoItem
title="Other"
<>
<DetailsInfoItem.Label
hint="Other data related to this transaction"
>
Other
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{
[
typeof type === 'number' && (
......@@ -43,7 +46,8 @@ const TxDetailsOther = ({ nonce, type, position }: Props) => {
</>
))
}
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -5,7 +5,7 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import { route } from 'nextjs-routes';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
......@@ -40,12 +40,13 @@ const TxDetailsTokenTransfers = ({ data, txHash, isOverflow }: Props) => {
}
return (
<DetailsInfoItem
key={ type }
title={ title }
<React.Fragment key={ type }>
<DetailsInfoItem.Label
hint={ hint }
position="relative"
>
{ title }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value position="relative">
<Flex
flexDirection="column"
alignItems="flex-start"
......@@ -55,7 +56,8 @@ const TxDetailsTokenTransfers = ({ data, txHash, isOverflow }: Props) => {
>
{ items.map((item, index) => <TxDetailsTokenTransfer key={ index } data={ item }/>) }
</Flex>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</React.Fragment>
);
}) }
{ isOverflow && (
......
This diff is collapsed.
......@@ -13,7 +13,7 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -61,10 +61,12 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
overflow="hidden"
>
<DetailsInfoItem
title="Tx batch number"
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Tx batch number
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.number }
</Skeleton>
......@@ -76,23 +78,32 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
isPrevDisabled={ data.number === 0 }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
<DetailsInfoItem
title="Status"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<VerificationSteps steps={ ZKEVM_L2_TX_BATCH_STATUSES } currentStep={ data.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Timestamp"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.timestamp ? <DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/> : 'Undefined' }
</DetailsInfoItem>
<DetailsInfoItem
title="Verify tx hash"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Verify tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.verify_tx_hash ? (
<TxEntityL1
isLoading={ isPlaceholderData }
......@@ -100,44 +111,57 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
maxW="100%"
/>
) : <Text>Pending</Text> }
</DetailsInfoItem>
<DetailsInfoItem
title="Transactions"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Transactions
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
<LinkInternal href={ route({ pathname: '/batches/[number]', query: { number: data.number.toString(), tab: 'txs' } }) }>
{ data.transactions.length } transaction{ data.transactions.length === 1 ? '' : 's' }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
<DetailsInfoItem
title="Global exit root"
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Global exit root
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.global_exit_root }/>
</Skeleton>
<CopyToClipboard text={ data.global_exit_root } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Acc input hash"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Acc input hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.acc_input_hash }/>
</Skeleton>
<CopyToClipboard text={ data.acc_input_hash } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Sequence tx hash"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Sequence tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.sequence_tx_hash ? (
<TxEntityL1
isLoading={ isPlaceholderData }
......@@ -145,17 +169,21 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
maxW="100%"
/>
) : <Text>Pending</Text> }
</DetailsInfoItem>
<DetailsInfoItem
title="State root"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
State root
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.state_root }/>
</Skeleton>
<CopyToClipboard text={ data.state_root } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</Grid>
);
};
......
......@@ -16,7 +16,7 @@ import { currencyUnits } from 'lib/units';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import LinkInternal from 'ui/shared/links/LinkInternal';
......@@ -76,11 +76,13 @@ const ZkSyncL2TxnBatchDetails = ({ query }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
overflow="hidden"
>
<DetailsInfoItem
title="Tx batch number"
<DetailsInfoItem.Label
hint="Batch number indicates the length of batches produced by grouping L2 blocks to be proven on Ethereum."
isLoading={ isPlaceholderData }
>
Tx batch number
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.number }
</Skeleton>
......@@ -92,35 +94,41 @@ const ZkSyncL2TxnBatchDetails = ({ query }: Props) => {
isPrevDisabled={ data.number === 0 }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Status"
<DetailsInfoItem.Label
hint="Status is the short interpretation of the batch lifecycle"
isLoading={ isPlaceholderData }
>
Status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES.slice(1) } currentStep={ data.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Timestamp"
<DetailsInfoItem.Label
hint="Date and time at which batch is produced"
isLoading={ isPlaceholderData }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.timestamp ? <DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/> : 'Undefined' }
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Transactions"
<DetailsInfoItem.Label
hint="Number of transactions inside the batch."
isLoading={ isPlaceholderData }
>
Transactions
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
<LinkInternal href={ route({ pathname: '/batches/[number]', query: { number: data.number.toString(), tab: 'txs' } }) }>
{ txNum } transaction{ txNum === 1 ? '' : 's' }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
......@@ -146,31 +154,38 @@ const ZkSyncL2TxnBatchDetails = ({ query }: Props) => {
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<DetailsInfoItem
title="Root hash"
<DetailsInfoItem.Label
hint="L1 batch root is a hash that summarizes batch data and submitted to the L1"
>
Root hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
alignSelf="flex-start"
>
<TruncatedValue value={ data.root_hash }/>
<CopyToClipboard text={ data.root_hash }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="L1 gas price"
<DetailsInfoItem.Label
hint="Gas price for the batch settlement transaction on L1"
>
L1 gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text mr={ 1 }>{ BigNumber(data.l1_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether }</Text>
<Text variant="secondary">({ BigNumber(data.l1_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="L2 fair gas price"
<DetailsInfoItem.Label
hint={ 'The gas price below which the "baseFee" of the batch should not fall' }
>
L2 fair gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text mr={ 1 }>{ BigNumber(data.l2_fair_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether }</Text>
<Text variant="secondary">({ BigNumber(data.l2_fair_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
......
......@@ -3,7 +3,7 @@ import React from 'react';
import type { ZkSyncBatch } from 'types/api/zkSyncL2';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -23,10 +23,13 @@ interface Props {
const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
return (
<>
<DetailsInfoItem
title="Commit tx hash"
<DetailsInfoItem.Label
hint="Hash of L1 tx on which the batch was committed"
isLoading={ isLoading }
>
Commit tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -45,12 +48,15 @@ const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
) }
</>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Prove tx hash"
<DetailsInfoItem.Label
hint="Hash of L1 tx on which the batch was proven"
isLoading={ isLoading }
>
Prove tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -69,12 +75,15 @@ const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
) }
</>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Execute tx hash"
<DetailsInfoItem.Label
hint="Hash of L1 tx on which the batch was executed and finalized"
isLoading={ isLoading }
>
Execute tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -93,7 +102,7 @@ const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
) }
</>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -2,7 +2,7 @@ import React from 'react';
import type { UserOp } from 'types/api/userOps';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import RawInputData from 'ui/shared/RawInputData';
import UserOpCallDataSwitch from './UserOpCallDataSwitch';
......@@ -33,12 +33,16 @@ const UserOpDecodedCallData = ({ data }: Props) => {
) : null;
return (
<DetailsInfoItem
title="Call data"
<>
<DetailsInfoItem.Label
hint="Data that’s passed to the sender for execution"
>
Call data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawInputData hex={ callData } rightSlot={ toggler }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -2,7 +2,7 @@ import React from 'react';
import type { UserOp } from 'types/api/userOps';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
import UserOpCallDataSwitch from './UserOpCallDataSwitch';
......@@ -33,14 +33,19 @@ const UserOpDecodedCallData = ({ data }: Props) => {
) : null;
return (
<DetailsInfoItem
title="Decoded call data"
<>
<DetailsInfoItem.Label
hint="Decoded call data"
>
Decoded call data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir={{ base: 'column', lg: 'row' }}
alignItems={{ base: 'flex-start', lg: 'center' }}
>
<LogDecodedInputData data={ callData } rightSlot={ toggler }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
This diff is collapsed.
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