Commit 1db67c5b authored by tom goriunov's avatar tom goriunov Committed by GitHub

Page title updates (#1265)

* block page title

* tx page title

* address page title

* token page title

* NFT page title

* [skip ci] update envs for review

* update title for NFT instance page

* update screenshots
parent 973d9496
...@@ -10,7 +10,6 @@ import useApiQuery from 'lib/api/useApiQuery'; ...@@ -10,7 +10,6 @@ import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { ADDRESS_COUNTERS } from 'stubs/address'; import { ADDRESS_COUNTERS } from 'stubs/address';
import AddressCounterItem from 'ui/address/details/AddressCounterItem'; import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem'; import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
...@@ -83,62 +82,76 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -83,62 +82,76 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
} }
return ( return (
<Box> <Grid
<AddressHeadingInfo address={ data } token={ data.token } isLoading={ addressQuery.isPlaceholderData } isLinkDisabled/> columnGap={ 8 }
<Grid rowGap={{ base: 1, lg: 3 }}
mt={ 8 } templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
columnGap={ 8 } >
rowGap={{ base: 1, lg: 3 }} <AddressNameInfo data={ data } isLoading={ addressQuery.isPlaceholderData }/>
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden" { data.is_contract && data.creation_tx_hash && data.creator_address_hash && (
> <DetailsInfoItem
<AddressNameInfo data={ data } isLoading={ addressQuery.isPlaceholderData }/> title="Creator"
{ data.is_contract && data.creation_tx_hash && data.creator_address_hash && ( hint="Transaction and address of creation"
<DetailsInfoItem isLoading={ addressQuery.isPlaceholderData }
title="Creator" >
hint="Transaction and address of creation" <AddressEntity
address={{ hash: data.creator_address_hash }}
truncation="constant"
noIcon
/>
<Text whiteSpace="pre"> at txn </Text>
<TxEntity hash={ data.creation_tx_hash } truncation="constant" noIcon noCopy={ false }/>
</DetailsInfoItem>
) }
{ data.is_contract && data.implementation_address && (
<DetailsInfoItem
title="Implementation"
hint="Implementation address of the proxy contract"
columnGap={ 1 }
>
<AddressEntity
address={{ hash: data.implementation_address, name: data.implementation_name, is_contract: true }}
isLoading={ addressQuery.isPlaceholderData } isLoading={ addressQuery.isPlaceholderData }
> noIcon
<AddressEntity />
address={{ hash: data.creator_address_hash }} </DetailsInfoItem>
truncation="constant" ) }
noIcon <AddressBalance data={ data } isLoading={ addressQuery.isPlaceholderData }/>
/> { data.has_tokens && (
<Text whiteSpace="pre"> at txn </Text>
<TxEntity hash={ data.creation_tx_hash } truncation="constant" noIcon noCopy={ false }/>
</DetailsInfoItem>
) }
{ data.is_contract && data.implementation_address && (
<DetailsInfoItem
title="Implementation"
hint="Implementation address of the proxy contract"
columnGap={ 1 }
>
<AddressEntity
address={{ hash: data.implementation_address, name: data.implementation_name, is_contract: true }}
isLoading={ addressQuery.isPlaceholderData }
noIcon
/>
</DetailsInfoItem>
) }
<AddressBalance data={ data } isLoading={ addressQuery.isPlaceholderData }/>
{ data.has_tokens && (
<DetailsInfoItem
title="Tokens"
hint="All tokens in the account and total value"
alignSelf="center"
py={ 0 }
>
{ addressQuery.data ? <TokenSelect onClick={ handleCounterItemClick }/> : <Box py="6px">0</Box> }
</DetailsInfoItem>
) }
<DetailsInfoItem <DetailsInfoItem
title="Transactions" title="Tokens"
hint="Number of transactions related to this address" hint="All tokens in the account and total value"
alignSelf="center"
py={ 0 }
>
{ addressQuery.data ? <TokenSelect onClick={ handleCounterItemClick }/> : <Box py="6px">0</Box> }
</DetailsInfoItem>
) }
<DetailsInfoItem
title="Transactions"
hint="Number of transactions related to this address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
{ addressQuery.data ? (
<AddressCounterItem
prop="transactions_count"
query={ countersQuery }
address={ data.hash }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
/>
) :
0 }
</DetailsInfoItem>
{ data.has_token_transfers && (
<DetailsInfoItem
title="Transfers"
hint="Number of transfers to/from this address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData } isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
> >
{ addressQuery.data ? ( { addressQuery.data ? (
<AddressCounterItem <AddressCounterItem
prop="transactions_count" prop="token_transfers_count"
query={ countersQuery } query={ countersQuery }
address={ data.hash } address={ data.hash }
onClick={ handleCounterItemClick } onClick={ handleCounterItemClick }
...@@ -147,32 +160,32 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -147,32 +160,32 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
) : ) :
0 } 0 }
</DetailsInfoItem> </DetailsInfoItem>
{ data.has_token_transfers && ( ) }
<DetailsInfoItem <DetailsInfoItem
title="Transfers" title="Gas used"
hint="Number of transfers to/from this address" hint="Gas used by the address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData } isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
> >
{ addressQuery.data ? ( { addressQuery.data ? (
<AddressCounterItem <AddressCounterItem
prop="token_transfers_count" prop="gas_usage_count"
query={ countersQuery } query={ countersQuery }
address={ data.hash } address={ data.hash }
onClick={ handleCounterItemClick } onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData } isAddressQueryLoading={ addressQuery.isPlaceholderData }
/> />
) : ) :
0 } 0 }
</DetailsInfoItem> </DetailsInfoItem>
) } { data.has_validated_blocks && (
<DetailsInfoItem <DetailsInfoItem
title="Gas used" title="Blocks validated"
hint="Gas used by the address" hint="Number of blocks validated by this validator"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData } isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
> >
{ addressQuery.data ? ( { addressQuery.data ? (
<AddressCounterItem <AddressCounterItem
prop="gas_usage_count" prop="validations_count"
query={ countersQuery } query={ countersQuery }
address={ data.hash } address={ data.hash }
onClick={ handleCounterItemClick } onClick={ handleCounterItemClick }
...@@ -181,41 +194,23 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -181,41 +194,23 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
) : ) :
0 } 0 }
</DetailsInfoItem> </DetailsInfoItem>
{ data.has_validated_blocks && ( ) }
<DetailsInfoItem { data.block_number_balance_updated_at && (
title="Blocks validated" <DetailsInfoItem
hint="Number of blocks validated by this validator" title="Last balance update"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData } hint="Block number in which the address was updated"
> alignSelf="center"
{ addressQuery.data ? ( py={{ base: '2px', lg: 1 }}
<AddressCounterItem isLoading={ addressQuery.isPlaceholderData }
prop="validations_count" >
query={ countersQuery } <BlockEntity
address={ data.hash } number={ data.block_number_balance_updated_at }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
/>
) :
0 }
</DetailsInfoItem>
) }
{ data.block_number_balance_updated_at && (
<DetailsInfoItem
title="Last balance update"
hint="Block number in which the address was updated"
alignSelf="center"
py={{ base: '2px', lg: 1 }}
isLoading={ addressQuery.isPlaceholderData } isLoading={ addressQuery.isPlaceholderData }
> />
<BlockEntity </DetailsInfoItem>
number={ data.block_number_balance_updated_at } ) }
isLoading={ addressQuery.isPlaceholderData } <DetailsSponsoredItem isLoading={ addressQuery.isPlaceholderData }/>
/> </Grid>
</DetailsInfoItem>
) }
<DetailsSponsoredItem isLoading={ addressQuery.isPlaceholderData }/>
</Grid>
</Box>
); );
}; };
......
...@@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query'; ...@@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import config from 'configs/app';
import starFilledIcon from 'icons/star_filled.svg'; import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg'; import starOutlineIcon from 'icons/star_outline.svg';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
...@@ -15,7 +16,7 @@ import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal'; ...@@ -15,7 +16,7 @@ import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
interface Props { interface Props {
className?: string; className?: string;
hash: string; hash: string;
watchListId: number | null; watchListId: number | null | undefined;
} }
const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
...@@ -24,6 +25,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { ...@@ -24,6 +25,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const isAccountActionAllowed = useIsAccountActionAllowed(); const isAccountActionAllowed = useIsAccountActionAllowed();
const onFocusCapture = usePreventFocusAfterModalClosing();
const handleClick = React.useCallback(() => { const handleClick = React.useCallback(() => {
if (!isAccountActionAllowed()) { if (!isAccountActionAllowed()) {
...@@ -54,6 +56,10 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { ...@@ -54,6 +56,10 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
}; };
}, [ hash, watchListId ]); }, [ hash, watchListId ]);
if (!config.features.account.isEnabled) {
return null;
}
return ( return (
<> <>
<Tooltip label={ `${ watchListId ? 'Remove address from Watch list' : 'Add address to Watch list' }` }> <Tooltip label={ `${ watchListId ? 'Remove address from Watch list' : 'Add address to Watch list' }` }>
...@@ -68,7 +74,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { ...@@ -68,7 +74,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
flexShrink={ 0 } flexShrink={ 0 }
onClick={ handleClick } onClick={ handleClick }
icon={ <Icon as={ watchListId ? starFilledIcon : starOutlineIcon } boxSize={ 5 }/> } icon={ <Icon as={ watchListId ? starFilledIcon : starOutlineIcon } boxSize={ 5 }/> }
onFocusCapture={ usePreventFocusAfterModalClosing() } onFocusCapture={ onFocusCapture }
/> />
</Tooltip> </Tooltip>
<WatchlistAddModal <WatchlistAddModal
......
...@@ -79,6 +79,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => { ...@@ -79,6 +79,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => {
pr="6px" pr="6px"
onClick={ onOpen } onClick={ onOpen }
icon={ <Icon as={ qrCodeIcon } boxSize={ 5 }/> } icon={ <Icon as={ qrCodeIcon } boxSize={ 5 }/> }
flexShrink={ 0 }
/> />
</Tooltip> </Tooltip>
......
import { Box, Icon } from '@chakra-ui/react'; import { Box, Flex, Icon } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -10,7 +10,6 @@ import iconSuccess from 'icons/status/success.svg'; ...@@ -10,7 +10,6 @@ import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import useContractTabs from 'lib/hooks/useContractTabs'; import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile';
import useIsSafeAddress from 'lib/hooks/useIsSafeAddress'; import useIsSafeAddress from 'lib/hooks/useIsSafeAddress';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { ADDRESS_INFO, ADDRESS_TABS_COUNTERS } from 'stubs/address'; import { ADDRESS_INFO, ADDRESS_TABS_COUNTERS } from 'stubs/address';
...@@ -24,7 +23,12 @@ import AddressTokens from 'ui/address/AddressTokens'; ...@@ -24,7 +23,12 @@ import AddressTokens from 'ui/address/AddressTokens';
import AddressTokenTransfers from 'ui/address/AddressTokenTransfers'; import AddressTokenTransfers from 'ui/address/AddressTokenTransfers';
import AddressTxs from 'ui/address/AddressTxs'; import AddressTxs from 'ui/address/AddressTxs';
import AddressWithdrawals from 'ui/address/AddressWithdrawals'; import AddressWithdrawals from 'ui/address/AddressWithdrawals';
import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton';
import AddressQrCode from 'ui/address/details/AddressQrCode';
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EntityTags from 'ui/shared/EntityTags'; import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers'; import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -41,7 +45,6 @@ const TOKEN_TABS = Object.values(tokenTabsByType); ...@@ -41,7 +45,6 @@ const TOKEN_TABS = Object.values(tokenTabsByType);
const AddressPageContent = () => { const AddressPageContent = () => {
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile();
const appProps = useAppContext(); const appProps = useAppContext();
const tabsScrollRef = React.useRef<HTMLDivElement>(null); const tabsScrollRef = React.useRef<HTMLDivElement>(null);
...@@ -148,14 +151,11 @@ const AddressPageContent = () => { ...@@ -148,14 +151,11 @@ const AddressPageContent = () => {
data={ addressQuery.data } data={ addressQuery.data }
isLoading={ addressQuery.isPlaceholderData } isLoading={ addressQuery.isPlaceholderData }
tagsBefore={ [ tagsBefore={ [
addressQuery.data?.is_contract ? { label: 'contract', display_name: 'Contract' } : { label: 'eoa', display_name: 'EOA' }, !addressQuery.data?.is_contract ? { label: 'eoa', display_name: 'EOA' } : undefined,
addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined, addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined,
addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined, addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined,
isSafeAddress ? { label: 'safe', display_name: 'Multisig: Safe' } : undefined, isSafeAddress ? { label: 'safe', display_name: 'Multisig: Safe' } : undefined,
] } ] }
contentAfter={
<NetworkExplorers type="address" pathParam={ hash } ml="auto" hideText={ isMobile }/>
}
/> />
); );
...@@ -174,6 +174,30 @@ const AddressPageContent = () => { ...@@ -174,6 +174,30 @@ const AddressPageContent = () => {
}; };
}, [ appProps.referrer ]); }, [ appProps.referrer ]);
const isLoading = addressQuery.isPlaceholderData;
const titleSecondRow = (
<Flex alignItems="center" w="100%" columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
<AddressEntity
address={{ ...addressQuery.data, name: '' }}
isLoading={ isLoading }
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
noLink
isSafeAddress={ isSafeAddress }
/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data.token &&
<AddressAddToWallet token={ addressQuery.data.token } variant="button"/> }
{ !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && (
<AddressFavoriteButton hash={ hash } watchListId={ addressQuery.data?.watchlist_address_id }/>
) }
<AddressQrCode address={ addressQuery.data } isLoading={ isLoading }/>
<AccountActionsMenu isLoading={ isLoading }/>
<NetworkExplorers type="address" pathParam={ hash } ml="auto"/>
</Flex>
);
return ( return (
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
...@@ -181,7 +205,8 @@ const AddressPageContent = () => { ...@@ -181,7 +205,8 @@ const AddressPageContent = () => {
title={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` } title={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` }
backLink={ backLink } backLink={ backLink }
contentAfter={ tags } contentAfter={ tags }
isLoading={ addressQuery.isPlaceholderData } secondRow={ titleSecondRow }
isLoading={ isLoading }
/> />
<AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/> <AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/>
{ /* should stay before tabs to scroll up with pagination */ } { /* should stay before tabs to scroll up with pagination */ }
......
import { chakra, Skeleton } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -16,6 +17,7 @@ import { WITHDRAWAL } from 'stubs/withdrawals'; ...@@ -16,6 +17,7 @@ import { WITHDRAWAL } from 'stubs/withdrawals';
import BlockDetails from 'ui/block/BlockDetails'; import BlockDetails from 'ui/block/BlockDetails';
import BlockWithdrawals from 'ui/block/BlockWithdrawals'; import BlockWithdrawals from 'ui/block/BlockWithdrawals';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import NetworkExplorers from 'ui/shared/NetworkExplorers'; import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
...@@ -112,6 +114,24 @@ const BlockPageContent = () => { ...@@ -112,6 +114,24 @@ const BlockPageContent = () => {
}, [ appProps.referrer ]); }, [ appProps.referrer ]);
const title = blockQuery.data?.type === 'reorg' ? `Reorged block #${ blockQuery.data?.height }` : `Block #${ blockQuery.data?.height }`; const title = blockQuery.data?.type === 'reorg' ? `Reorged block #${ blockQuery.data?.height }` : `Block #${ blockQuery.data?.height }`;
const titleSecondRow = (
<>
<Skeleton
isLoaded={ !blockQuery.isPlaceholderData }
fontFamily="heading"
display="flex"
minW={ 0 }
columnGap={ 2 }
fontWeight={ 500 }
>
<chakra.span flexShrink={ 0 }>
{ config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by' }
</chakra.span>
<AddressEntity address={ blockQuery.data?.miner }/>
</Skeleton>
<NetworkExplorers type="block" pathParam={ heightOrHash } ml={{ base: 3, lg: 'auto' }}/>
</>
);
return ( return (
<> <>
...@@ -119,7 +139,7 @@ const BlockPageContent = () => { ...@@ -119,7 +139,7 @@ const BlockPageContent = () => {
<PageTitle <PageTitle
title={ title } title={ title }
backLink={ backLink } backLink={ backLink }
contentAfter={ <NetworkExplorers type="block" pathParam={ heightOrHash } ml={{ base: 'initial', lg: 'auto' }}/> } secondRow={ titleSecondRow }
isLoading={ blockQuery.isPlaceholderData } isLoading={ blockQuery.isPlaceholderData }
/> />
{ blockQuery.isPlaceholderData ? <TabsSkeleton tabs={ tabs }/> : ( { blockQuery.isPlaceholderData ? <TabsSkeleton tabs={ tabs }/> : (
......
import { Box, Icon, Tooltip } from '@chakra-ui/react'; import { Box, Flex, Icon, Tooltip } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
...@@ -23,8 +23,11 @@ import * as addressStubs from 'stubs/address'; ...@@ -23,8 +23,11 @@ import * as addressStubs from 'stubs/address';
import * as tokenStubs from 'stubs/token'; import * as tokenStubs from 'stubs/token';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import AddressContract from 'ui/address/AddressContract'; import AddressContract from 'ui/address/AddressContract';
import AddressQrCode from 'ui/address/details/AddressQrCode';
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EntityTags from 'ui/shared/EntityTags'; import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers'; import NetworkExplorers from 'ui/shared/NetworkExplorers';
...@@ -261,41 +264,56 @@ const TokenPageContent = () => { ...@@ -261,41 +264,56 @@ const TokenPageContent = () => {
[ { label: verifiedInfoQuery.data.projectSector, display_name: verifiedInfoQuery.data.projectSector } ] : [ { label: verifiedInfoQuery.data.projectSector, display_name: verifiedInfoQuery.data.projectSector } ] :
undefined undefined
} }
contentAfter={
<NetworkExplorers type="token" pathParam={ hashString } ml="auto" hideText={ isMobile }/>
}
flexGrow={ 1 } flexGrow={ 1 }
/> />
</> </>
); );
const isLoading = tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData;
const titleSecondRow = (
<Flex alignItems="center" w="100%" minW={ 0 } columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
<AddressEntity
address={{ ...contractQuery.data, name: '' }}
isLoading={ isLoading }
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
/>
{ !isLoading && tokenQuery.data && <AddressAddToWallet token={ tokenQuery.data } variant="button"/> }
<AddressQrCode address={ contractQuery.data } isLoading={ isLoading }/>
<AccountActionsMenu isLoading={ isLoading }/>
<Flex ml={{ base: 0, lg: 'auto' }} columnGap={ 2 } flexGrow={{ base: 1, lg: 0 }}>
<TokenVerifiedInfo verifiedInfoQuery={ verifiedInfoQuery }/>
<NetworkExplorers type="token" pathParam={ hashString } ml={{ base: 'auto', lg: 0 }}/>
</Flex>
</Flex>
);
return ( return (
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
title={ `${ tokenQuery.data?.name || 'Unnamed token' }${ tokenSymbolText }` } title={ `${ tokenQuery.data?.name || 'Unnamed token' }${ tokenSymbolText }` }
isLoading={ tokenQuery.isPlaceholderData } isLoading={ isLoading }
backLink={ backLink } backLink={ backLink }
beforeTitle={ tokenQuery.data ? ( beforeTitle={ tokenQuery.data ? (
<TokenEntity.Icon <TokenEntity.Icon
token={ tokenQuery.data } token={ tokenQuery.data }
isLoading={ tokenQuery.isPlaceholderData } isLoading={ isLoading }
iconSize="lg" iconSize="lg"
/> />
) : null } ) : null }
contentAfter={ titleContentAfter } contentAfter={ titleContentAfter }
secondRow={ titleSecondRow }
/> />
<AddressHeadingInfo
address={ contractQuery.data }
token={ tokenQuery.data }
isLoading={ tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData }
/>
<TokenVerifiedInfo verifiedInfoQuery={ verifiedInfoQuery }/>
<TokenDetails tokenQuery={ tokenQuery }/> <TokenDetails tokenQuery={ tokenQuery }/>
{ /* should stay before tabs to scroll up with pagination */ } { /* should stay before tabs to scroll up with pagination */ }
<Box ref={ scrollRef }></Box> <Box ref={ scrollRef }></Box>
{ tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData ? { isLoading ?
<TabsSkeleton tabs={ tabs }/> : <TabsSkeleton tabs={ tabs }/> :
( (
<RoutedTabs <RoutedTabs
......
import { Box, Icon, Skeleton } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { PaginationParams } from 'ui/shared/pagination/types'; import type { PaginationParams } from 'ui/shared/pagination/types';
import type { RoutedTab } from 'ui/shared/Tabs/types'; import type { RoutedTab } from 'ui/shared/Tabs/types';
import nftIcon from 'icons/nft_shield.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
...@@ -14,9 +13,12 @@ import * as regexp from 'lib/regexp'; ...@@ -14,9 +13,12 @@ import * as regexp from 'lib/regexp';
import { TOKEN_INSTANCE } from 'stubs/token'; import { TOKEN_INSTANCE } from 'stubs/token';
import * as tokenStubs from 'stubs/token'; import * as tokenStubs from 'stubs/token';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import AddressQrCode from 'ui/address/details/AddressQrCode';
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
...@@ -119,10 +121,8 @@ const TokenInstanceContent = () => { ...@@ -119,10 +121,8 @@ const TokenInstanceContent = () => {
throw Error('Token instance fetch failed', { cause: tokenInstanceQuery.error }); throw Error('Token instance fetch failed', { cause: tokenInstanceQuery.error });
} }
const nftShieldIcon = tokenInstanceQuery.isPlaceholderData ?
<Skeleton boxSize={ 6 } display="inline-block" borderRadius="base" mr={ 2 } my={ 2 } verticalAlign="text-bottom"/> :
<Icon as={ nftIcon } boxSize={ 6 } mr={ 2 }/>;
const tokenTag = <Tag isLoading={ tokenInstanceQuery.isPlaceholderData }>{ tokenInstanceQuery.data?.token.type }</Tag>; const tokenTag = <Tag isLoading={ tokenInstanceQuery.isPlaceholderData }>{ tokenInstanceQuery.data?.token.type }</Tag>;
const address = { const address = {
hash: hash || '', hash: hash || '',
is_contract: true, is_contract: true,
...@@ -130,6 +130,9 @@ const TokenInstanceContent = () => { ...@@ -130,6 +130,9 @@ const TokenInstanceContent = () => {
watchlist_names: [], watchlist_names: [],
watchlist_address_id: null, watchlist_address_id: null,
}; };
const isLoading = tokenInstanceQuery.isPlaceholderData;
const appLink = (() => { const appLink = (() => {
if (!tokenInstanceQuery.data?.external_app_url) { if (!tokenInstanceQuery.data?.external_app_url) {
return null; return null;
...@@ -141,20 +144,15 @@ const TokenInstanceContent = () => { ...@@ -141,20 +144,15 @@ const TokenInstanceContent = () => {
new URL('https://' + tokenInstanceQuery.data.external_app_url); new URL('https://' + tokenInstanceQuery.data.external_app_url);
return ( return (
<Skeleton isLoaded={ !tokenInstanceQuery.isPlaceholderData } display="inline-block" fontSize="sm" mt={ 6 }> <LinkExternal href={ url.toString() } variant="subtle" isLoading={ isLoading } ml={{ base: 0, lg: 'auto' }}>
<span>View in app </span> { url.hostname || tokenInstanceQuery.data.external_app_url }
<LinkExternal href={ url.toString() }> </LinkExternal>
{ url.hostname || tokenInstanceQuery.data.external_app_url }
</LinkExternal>
</Skeleton>
); );
} catch (error) { } catch (error) {
return ( return (
<Box fontSize="sm" mt={ 6 }> <LinkExternal href={ tokenInstanceQuery.data.external_app_url } isLoading={ isLoading } ml={{ base: 0, lg: 'auto' }}>
<LinkExternal href={ tokenInstanceQuery.data.external_app_url }>
View in app View in app
</LinkExternal> </LinkExternal>
</Box>
); );
} }
})(); })();
...@@ -167,27 +165,44 @@ const TokenInstanceContent = () => { ...@@ -167,27 +165,44 @@ const TokenInstanceContent = () => {
pagination = holdersQuery.pagination; pagination = holdersQuery.pagination;
} }
const titleSecondRow = (
<Flex alignItems="center" w="100%" minW={ 0 } columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
<TokenEntity
token={ tokenInstanceQuery.data?.token }
isLoading={ isLoading }
noSymbol
noCopy
jointSymbol
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
w="auto"
maxW="700px"
/>
{ !isLoading && tokenInstanceQuery.data && <AddressAddToWallet token={ tokenInstanceQuery.data.token } variant="button"/> }
<AddressQrCode address={ address } isLoading={ isLoading }/>
<AccountActionsMenu isLoading={ isLoading }/>
{ appLink }
</Flex>
);
return ( return (
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
title={ `${ tokenInstanceQuery.data?.token.name || 'Unnamed token' } #${ tokenInstanceQuery.data?.id }` } title={ `ID ${ tokenInstanceQuery.data?.id }` }
backLink={ backLink } backLink={ backLink }
beforeTitle={ nftShieldIcon }
contentAfter={ tokenTag } contentAfter={ tokenTag }
isLoading={ tokenInstanceQuery.isPlaceholderData } secondRow={ titleSecondRow }
isLoading={ isLoading }
/> />
<AddressHeadingInfo address={ address } token={ tokenInstanceQuery.data?.token } isLoading={ tokenInstanceQuery.isPlaceholderData }/> <TokenInstanceDetails data={ tokenInstanceQuery?.data } isLoading={ isLoading } scrollRef={ scrollRef }/>
{ appLink }
<TokenInstanceDetails data={ tokenInstanceQuery?.data } isLoading={ tokenInstanceQuery.isPlaceholderData } scrollRef={ scrollRef }/>
{ /* should stay before tabs to scroll up with pagination */ } { /* should stay before tabs to scroll up with pagination */ }
<Box ref={ scrollRef }></Box> <Box ref={ scrollRef }></Box>
{ tokenInstanceQuery.isPlaceholderData ? <TabsSkeleton tabs={ tabs }/> : ( { isLoading ? <TabsSkeleton tabs={ tabs }/> : (
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } } tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
......
...@@ -6,10 +6,11 @@ import type { RoutedTab } from 'ui/shared/Tabs/types'; ...@@ -6,10 +6,11 @@ import type { RoutedTab } from 'ui/shared/Tabs/types';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { TX } from 'stubs/tx'; import { TX } from 'stubs/tx';
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import EntityTags from 'ui/shared/EntityTags'; import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers'; import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -27,7 +28,6 @@ import TxTokenTransfer from 'ui/tx/TxTokenTransfer'; ...@@ -27,7 +28,6 @@ import TxTokenTransfer from 'ui/tx/TxTokenTransfer';
const TransactionPageContent = () => { const TransactionPageContent = () => {
const router = useRouter(); const router = useRouter();
const appProps = useAppContext(); const appProps = useAppContext();
const isMobile = useIsMobile();
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
...@@ -57,9 +57,6 @@ const TransactionPageContent = () => { ...@@ -57,9 +57,6 @@ const TransactionPageContent = () => {
<EntityTags <EntityTags
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
tagsBefore={ [ data?.tx_tag ? { label: data.tx_tag, display_name: data.tx_tag } : undefined ] } tagsBefore={ [ data?.tx_tag ? { label: data.tx_tag, display_name: data.tx_tag } : undefined ] }
contentAfter={
<NetworkExplorers type="tx" pathParam={ hash } ml={{ base: 'initial', lg: 'auto' }} hideText={ isMobile && Boolean(data?.tx_tag) }/>
}
/> />
); );
...@@ -76,6 +73,14 @@ const TransactionPageContent = () => { ...@@ -76,6 +73,14 @@ const TransactionPageContent = () => {
}; };
}, [ appProps.referrer ]); }, [ appProps.referrer ]);
const titleSecondRow = (
<>
<TxEntity hash={ hash } noLink noCopy={ false } fontWeight={ 500 } mr={ 2 } fontFamily="heading"/>
{ !data?.tx_tag && <AccountActionsMenu mr={{ base: 0, lg: 3 }}/> }
<NetworkExplorers type="tx" pathParam={ hash } ml={{ base: 3, lg: 'auto' }}/>
</>
);
return ( return (
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
...@@ -83,6 +88,7 @@ const TransactionPageContent = () => { ...@@ -83,6 +88,7 @@ const TransactionPageContent = () => {
title="Transaction details" title="Transaction details"
backLink={ backLink } backLink={ backLink }
contentAfter={ tags } contentAfter={ tags }
secondRow={ titleSecondRow }
/> />
{ isPlaceholderData ? ( { isPlaceholderData ? (
<> <>
......
...@@ -21,7 +21,7 @@ import TransactionInput from 'ui/shared/TransactionInput'; ...@@ -21,7 +21,7 @@ import TransactionInput from 'ui/shared/TransactionInput';
const TAG_MAX_LENGTH = 35; const TAG_MAX_LENGTH = 35;
type Props = { type Props = {
data?: TransactionTag; data?: Partial<TransactionTag>;
onClose: () => void; onClose: () => void;
onSuccess: () => Promise<void>; onSuccess: () => Promise<void>;
setAlertVisible: (isAlertVisible: boolean) => void; setAlertVisible: (isAlertVisible: boolean) => void;
......
...@@ -11,10 +11,11 @@ import TransactionForm from './TransactionForm'; ...@@ -11,10 +11,11 @@ import TransactionForm from './TransactionForm';
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
data?: TransactionTag; onSuccess?: () => Promise<void>;
data?: Partial<TransactionTag>;
} }
const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) => {
const title = data ? 'Edit transaction tag' : 'New transaction tag'; const title = data ? 'Edit transaction tag' : 'New transaction tag';
const text = !data ? 'Label any transaction with a private transaction tag (up to 35 chars) to customize your explorer experience.' : ''; const text = !data ? 'Label any transaction with a private transaction tag (up to 35 chars) to customize your explorer experience.' : '';
...@@ -28,13 +29,14 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { ...@@ -28,13 +29,14 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
}, [ data?.id, isOpen ]); }, [ data?.id, isOpen ]);
const handleSuccess = React.useCallback(async() => { const handleSuccess = React.useCallback(async() => {
onSuccess?.();
if (!data?.id) { if (!data?.id) {
mixpanel.logEvent( mixpanel.logEvent(
mixpanel.EventTypes.PRIVATE_TAG, mixpanel.EventTypes.PRIVATE_TAG,
{ Action: 'Submit', 'Page type': PAGE_TYPE_DICT['/account/tag-address'], 'Tag type': 'Tx' }, { Action: 'Submit', 'Page type': PAGE_TYPE_DICT['/account/tag-address'], 'Tag type': 'Tx' },
); );
} }
}, [ data?.id ]); }, [ data?.id, onSuccess ]);
const renderForm = useCallback(() => { const renderForm = useCallback(() => {
return <TransactionForm data={ data } onClose={ onClose } onSuccess={ handleSuccess } setAlertVisible={ setAlertVisible }/>; return <TransactionForm data={ data } onClose={ onClose } onSuccess={ handleSuccess } setAlertVisible={ setAlertVisible }/>;
......
import { Button, Menu, MenuButton, MenuList, Icon, Flex, Skeleton } from '@chakra-ui/react'; import { Button, Menu, MenuButton, MenuList, Icon, Flex, Skeleton, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -8,28 +8,34 @@ import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed'; ...@@ -8,28 +8,34 @@ import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import PrivateTagMenuItem from './PrivateTagMenuItem'; import PrivateTagMenuItem from './items/PrivateTagMenuItem';
import PublicTagMenuItem from './PublicTagMenuItem'; import PublicTagMenuItem from './items/PublicTagMenuItem';
import TokenInfoMenuItem from './TokenInfoMenuItem'; import TokenInfoMenuItem from './items/TokenInfoMenuItem';
interface Props { interface Props {
isLoading?: boolean; isLoading?: boolean;
className?: string;
} }
const AddressActions = ({ isLoading }: Props) => { const AccountActionsMenu = ({ isLoading, className }: Props) => {
const router = useRouter(); const router = useRouter();
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
const isTokenPage = router.pathname === '/token/[hash]'; const isTokenPage = router.pathname === '/token/[hash]';
const isTxPage = router.pathname === '/tx/[hash]';
const isAccountActionAllowed = useIsAccountActionAllowed(); const isAccountActionAllowed = useIsAccountActionAllowed();
const handleButtonClick = React.useCallback(() => { const handleButtonClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Address actions (more button)' }); mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Address actions (more button)' });
}, []); }, []);
if (!config.features.account.isEnabled) {
return null;
}
return ( return (
<Menu> <Menu>
<Skeleton isLoaded={ !isLoading } borderRadius="base"> <Skeleton isLoaded={ !isLoading } borderRadius="base" className={ className }>
<MenuButton <MenuButton
as={ Button } as={ Button }
size="sm" size="sm"
...@@ -45,11 +51,17 @@ const AddressActions = ({ isLoading }: Props) => { ...@@ -45,11 +51,17 @@ const AddressActions = ({ isLoading }: Props) => {
<MenuList minWidth="180px" zIndex="popover"> <MenuList minWidth="180px" zIndex="popover">
{ isTokenPage && config.features.addressVerification.isEnabled && { isTokenPage && config.features.addressVerification.isEnabled &&
<TokenInfoMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/> } <TokenInfoMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/> }
<PrivateTagMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/> <PrivateTagMenuItem
<PublicTagMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/> py={ 2 }
px={ 4 }
hash={ hash }
onBeforeClick={ isAccountActionAllowed }
type={ isTxPage ? 'tx' : 'address' }
/>
{ !isTxPage && <PublicTagMenuItem py={ 2 } px={ 4 } hash={ hash } onBeforeClick={ isAccountActionAllowed }/> }
</MenuList> </MenuList>
</Menu> </Menu>
); );
}; };
export default React.memo(AddressActions); export default React.memo(chakra(AccountActionsMenu));
...@@ -4,25 +4,28 @@ import { useRouter } from 'next/router'; ...@@ -4,25 +4,28 @@ import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
import type { Transaction } from 'types/api/transaction';
import iconPrivateTags from 'icons/privattags.svg'; import iconPrivateTags from 'icons/privattags.svg';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import getPageType from 'lib/mixpanel/getPageType'; import getPageType from 'lib/mixpanel/getPageType';
import PrivateTagModal from 'ui/privateTags/AddressModal/AddressModal'; import AddressModal from 'ui/privateTags/AddressModal/AddressModal';
import TransactionModal from 'ui/privateTags/TransactionModal/TransactionModal';
interface Props { interface Props {
className?: string; className?: string;
hash: string; hash: string;
onBeforeClick: () => boolean; onBeforeClick: () => boolean;
type?: 'address' | 'tx';
} }
const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => { const PrivateTagMenuItem = ({ className, hash, onBeforeClick, type = 'address' }: Props) => {
const modal = useDisclosure(); const modal = useDisclosure();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const queryKey = getResourceKey('address', { pathParams: { hash } }); const queryKey = getResourceKey(type === 'tx' ? 'tx' : 'address', { pathParams: { hash } });
const addressData = queryClient.getQueryData<Address>(queryKey); const queryData = queryClient.getQueryData<Address | Transaction>(queryKey);
const handleClick = React.useCallback(() => { const handleClick = React.useCallback(() => {
if (!onBeforeClick()) { if (!onBeforeClick()) {
...@@ -37,17 +40,23 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => { ...@@ -37,17 +40,23 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => {
modal.onClose(); modal.onClose();
}, [ queryClient, queryKey, modal ]); }, [ queryClient, queryKey, modal ]);
const formData = React.useMemo(() => { if (
return { queryData &&
address_hash: hash, (
}; ('private_tags' in queryData && queryData.private_tags?.length) ||
}, [ hash ]); ('tx_tag' in queryData && queryData.tx_tag)
)
if (addressData?.private_tags?.length) { ) {
return null; return null;
} }
const pageType = getPageType(router.pathname); const pageType = getPageType(router.pathname);
const modalProps = {
isOpen: modal.isOpen,
onClose: modal.onClose,
onSuccess: handleAddPrivateTag,
pageType,
};
return ( return (
<> <>
...@@ -55,13 +64,10 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => { ...@@ -55,13 +64,10 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => {
<Icon as={ iconPrivateTags } boxSize={ 6 } mr={ 2 }/> <Icon as={ iconPrivateTags } boxSize={ 6 } mr={ 2 }/>
<span>Add private tag</span> <span>Add private tag</span>
</MenuItem> </MenuItem>
<PrivateTagModal { type === 'tx' ?
data={ formData } <TransactionModal { ...modalProps } data={{ transaction_hash: hash }}/> :
pageType={ pageType } <AddressModal { ...modalProps } data={{ address_hash: hash }}/>
isOpen={ modal.isOpen } }
onClose={ modal.onClose }
onSuccess={ handleAddPrivateTag }
/>
</> </>
); );
}; };
......
import { Flex } from '@chakra-ui/react';
import React from 'react';
import type { Address } from 'types/api/address';
import type { TokenInfo } from 'types/api/token';
import config from 'configs/app';
import useIsSafeAddress from 'lib/hooks/useIsSafeAddress';
import stripTrailingSlash from 'lib/stripTrailingSlash';
import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton';
import AddressQrCode from 'ui/address/details/AddressQrCode';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressActionsMenu from 'ui/shared/AddressActions/Menu';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import LinkExternal from 'ui/shared/LinkExternal';
interface Props {
address?: Pick<Address, 'hash' | 'is_contract' | 'implementation_name' | 'watchlist_names' | 'watchlist_address_id'>;
token?: TokenInfo | null;
isLinkDisabled?: boolean;
isLoading?: boolean;
}
const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props) => {
const isSafeAddress = useIsSafeAddress(!isLoading && address?.is_contract ? address.hash : undefined);
if (!address) {
return null;
}
const tokenOriginalLink = (() => {
const feature = config.features.bridgedTokens;
if (!token?.foreign_address || !token.origin_chain_id || !feature.isEnabled) {
return null;
}
const chainBaseUrl = feature.chains.find(({ id }) => id === token.origin_chain_id)?.base_url;
if (!chainBaseUrl) {
return null;
}
try {
const url = new URL(stripTrailingSlash(chainBaseUrl) + '/' + token.foreign_address);
return (
<LinkExternal href={ url } variant="subtle" ml="auto">
Original token
</LinkExternal>
);
} catch (error) {
return null;
}
})();
return (
<Flex alignItems="center" flexWrap={{ base: tokenOriginalLink ? 'wrap' : 'nowrap', lg: 'nowrap' }} rowGap={ 3 } columnGap={ 2 }>
<AddressEntity
address={{ ...address, name: '' }}
isLoading={ isLoading }
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
noLink={ isLinkDisabled }
isSafeAddress={ isSafeAddress }
/>
{ !isLoading && address?.is_contract && token && <AddressAddToWallet token={ token }/> }
{ !isLoading && !address.is_contract && config.features.account.isEnabled && (
<AddressFavoriteButton hash={ address.hash } watchListId={ address.watchlist_address_id }/>
) }
<AddressQrCode address={ address } isLoading={ isLoading } flexShrink={ 0 }/>
{ config.features.account.isEnabled && <AddressActionsMenu isLoading={ isLoading }/> }
{ tokenOriginalLink }
</Flex>
);
};
export default AddressHeadingInfo;
...@@ -16,25 +16,42 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props) ...@@ -16,25 +16,42 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props)
const subtleLinkBg = useColorModeValue('gray.100', 'gray.700'); const subtleLinkBg = useColorModeValue('gray.100', 'gray.700');
const styleProps: ChakraProps = (() => { const styleProps: ChakraProps = (() => {
const commonProps = {
fontSize: 'sm',
lineHeight: 5,
display: 'inline-block',
alignItems: 'center',
};
switch (variant) { switch (variant) {
case 'subtle': { case 'subtle': {
return { return {
...commonProps,
px: '10px', px: '10px',
py: '5px', py: '6px',
bgColor: subtleLinkBg, bgColor: subtleLinkBg,
borderRadius: 'base', borderRadius: 'base',
}; };
} }
default:{ default:{
return {}; return commonProps;
} }
} }
})(); })();
if (isLoading) { if (isLoading) {
if (variant === 'subtle') {
return (
<Skeleton className={ className } { ...styleProps } bgColor="inherit">
{ children }
<Box boxSize={ 4 } display="inline-block"/>
</Skeleton>
);
}
return ( return (
<Box className={ className } { ...styleProps } fontSize="sm" lineHeight={ 5 } display="inline-block" alignItems="center"> <Box className={ className } { ...styleProps }>
{ children } { children }
<Skeleton boxSize={ 4 } verticalAlign="middle" display="inline-block"/> <Skeleton boxSize={ 4 } verticalAlign="middle" display="inline-block"/>
</Box> </Box>
...@@ -42,7 +59,7 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props) ...@@ -42,7 +59,7 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props)
} }
return ( return (
<Link className={ className } { ...styleProps } fontSize="sm" lineHeight={ 5 } display="inline-block" alignItems="center" target="_blank" href={ href }> <Link className={ className } { ...styleProps } target="_blank" href={ href }>
{ children } { children }
<Icon as={ arrowIcon } boxSize={ 4 } verticalAlign="middle" color="gray.400"/> <Icon as={ arrowIcon } boxSize={ 4 } verticalAlign="middle" color="gray.400"/>
</Link> </Link>
......
...@@ -12,10 +12,9 @@ interface Props { ...@@ -12,10 +12,9 @@ interface Props {
className?: string; className?: string;
type: keyof TNetworkExplorer['paths']; type: keyof TNetworkExplorer['paths'];
pathParam: string; pathParam: string;
hideText?: boolean;
} }
const NetworkExplorers = ({ className, type, pathParam, hideText }: Props) => { const NetworkExplorers = ({ className, type, pathParam }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onToggle, onClose } = useDisclosure();
const explorersLinks = config.UI.explorers.items const explorersLinks = config.UI.explorers.items
...@@ -41,11 +40,11 @@ const NetworkExplorers = ({ className, type, pathParam, hideText }: Props) => { ...@@ -41,11 +40,11 @@ const NetworkExplorers = ({ className, type, pathParam, hideText }: Props) => {
aria-label="Verify in other explorers" aria-label="Verify in other explorers"
fontWeight={ 500 } fontWeight={ 500 }
px={ 2 } px={ 2 }
h="30px" h="32px"
flexShrink={ 0 }
> >
<Icon as={ explorerIcon } boxSize={ 5 } mr={ hideText ? 0 : 1 }/> <Icon as={ explorerIcon } boxSize={ 5 }/>
{ !hideText && <span>Explorers</span> } <Icon as={ arrowIcon } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 }/>
<Icon as={ arrowIcon } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 1 }/>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent w="240px"> <PopoverContent w="240px">
......
...@@ -16,6 +16,7 @@ type Props = { ...@@ -16,6 +16,7 @@ type Props = {
beforeTitle?: React.ReactNode; beforeTitle?: React.ReactNode;
afterTitle?: React.ReactNode; afterTitle?: React.ReactNode;
contentAfter?: React.ReactNode; contentAfter?: React.ReactNode;
secondRow?: React.ReactNode;
isLoading?: boolean; isLoading?: boolean;
withTextAd?: boolean; withTextAd?: boolean;
} }
...@@ -31,7 +32,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => { ...@@ -31,7 +32,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
return <Skeleton boxSize={ 6 } display="inline-block" borderRadius="base" mr={ 3 } my={ 2 } verticalAlign="text-bottom" isLoaded={ !props.isLoading }/>; return <Skeleton boxSize={ 6 } display="inline-block" borderRadius="base" mr={ 3 } my={ 2 } verticalAlign="text-bottom" isLoaded={ !props.isLoading }/>;
} }
const icon = <Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)" margin="auto"/>; const icon = <Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)" margin="auto" color="gray.400"/>;
if ('url' in props) { if ('url' in props) {
return ( return (
...@@ -52,7 +53,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => { ...@@ -52,7 +53,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
); );
}; };
const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading, afterTitle, beforeTitle }: Props) => { const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading, afterTitle, beforeTitle, secondRow }: Props) => {
const tooltip = useDisclosure(); const tooltip = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [ isTextTruncated, setIsTextTruncated ] = React.useState(false); const [ isTextTruncated, setIsTextTruncated ] = React.useState(false);
...@@ -90,57 +91,62 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa ...@@ -90,57 +91,62 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
}, [ updatedTruncateState ]); }, [ updatedTruncateState ]);
return ( return (
<Flex <Flex className={ className } flexDir="column" rowGap={ 3 } mb={ 6 }>
className={ className } <Flex
mb={ 6 } flexDir="row"
flexDir="row" flexWrap="wrap"
flexWrap="wrap" rowGap={ 3 }
rowGap={ 3 } columnGap={ 3 }
columnGap={ 3 } alignItems="center"
alignItems="center" >
> <Flex h={{ base: 'auto', lg: isLoading ? 10 : 'auto' }} maxW="100%" alignItems="center">
<Flex h={{ base: 'auto', lg: isLoading ? 10 : 'auto' }} maxW="100%" alignItems="center"> { backLink && <BackLink { ...backLink } isLoading={ isLoading }/> }
{ backLink && <BackLink { ...backLink } isLoading={ isLoading }/> } { beforeTitle }
{ beforeTitle } <Skeleton
<Skeleton isLoaded={ !isLoading }
isLoaded={ !isLoading } overflow="hidden"
overflow="hidden"
>
<Tooltip
label={ title }
isOpen={ tooltip.isOpen }
onClose={ tooltip.onClose }
maxW={{ base: 'calc(100vw - 32px)', lg: '500px' }}
closeOnScroll={ isMobile ? true : false }
isDisabled={ !isTextTruncated }
> >
<Heading <Tooltip
ref={ headingRef } label={ title }
as="h1" isOpen={ tooltip.isOpen }
size="lg" onClose={ tooltip.onClose }
whiteSpace="normal" maxW={{ base: 'calc(100vw - 32px)', lg: '500px' }}
wordBreak="break-all" closeOnScroll={ isMobile ? true : false }
style={{ isDisabled={ !isTextTruncated }
WebkitLineClamp: TEXT_MAX_LINES,
WebkitBoxOrient: 'vertical',
display: '-webkit-box',
}}
overflow="hidden"
textOverflow="ellipsis"
onMouseEnter={ tooltip.onOpen }
onMouseLeave={ tooltip.onClose }
onClick={ isMobile ? tooltip.onToggle : undefined }
> >
<span ref={ textRef }> <Heading
{ title } ref={ headingRef }
</span> as="h1"
</Heading> size="lg"
</Tooltip> whiteSpace="normal"
</Skeleton> wordBreak="break-all"
{ afterTitle } style={{
WebkitLineClamp: TEXT_MAX_LINES,
WebkitBoxOrient: 'vertical',
display: '-webkit-box',
}}
overflow="hidden"
textOverflow="ellipsis"
onMouseEnter={ tooltip.onOpen }
onMouseLeave={ tooltip.onClose }
onClick={ isMobile ? tooltip.onToggle : undefined }
>
<span ref={ textRef }>
{ title }
</span>
</Heading>
</Tooltip>
</Skeleton>
{ afterTitle }
</Flex>
{ contentAfter }
{ withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto" w={{ base: '100%', lg: 'auto' }}/> }
</Flex> </Flex>
{ contentAfter } { secondRow && (
{ withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto" w={{ base: '100%', lg: 'auto' }}/> } <Flex alignItems="center" minH={ 10 } overflow="hidden">
{ secondRow }
</Flex>
) }
</Flex> </Flex>
); );
}; };
......
...@@ -4,7 +4,8 @@ import React from 'react'; ...@@ -4,7 +4,8 @@ import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import iconVerifiedToken from 'icons/verified_token.svg'; import iconVerifiedToken from 'icons/verified_token.svg';
import useIsMobile from 'lib/hooks/useIsMobile'; import * as addressMock from 'mocks/address/address';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EntityTags from 'ui/shared/EntityTags'; import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers'; import NetworkExplorers from 'ui/shared/NetworkExplorers';
...@@ -12,8 +13,6 @@ import NetworkExplorers from 'ui/shared/NetworkExplorers'; ...@@ -12,8 +13,6 @@ import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from '../PageTitle'; import PageTitle from '../PageTitle';
const DefaultView = () => { const DefaultView = () => {
const isMobile = useIsMobile();
const tokenData: TokenInfo = { const tokenData: TokenInfo = {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
circulating_market_cap: '117629601.61913824', circulating_market_cap: '117629601.61913824',
...@@ -39,12 +38,23 @@ const DefaultView = () => { ...@@ -39,12 +38,23 @@ const DefaultView = () => {
tagsBefore={ [ tagsBefore={ [
{ label: 'example', display_name: 'Example label' }, { label: 'example', display_name: 'Example label' },
] } ] }
contentAfter={ <NetworkExplorers type="token" pathParam="token-hash" ml="auto" hideText={ isMobile }/> }
flexGrow={ 1 } flexGrow={ 1 }
/> />
</> </>
); );
const secondRow = (
<>
<AddressEntity
address={{ ...addressMock.token, name: '' }}
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
/>
<NetworkExplorers type="token" pathParam={ addressMock.hash } ml="auto"/>
</>
);
return ( return (
<PageTitle <PageTitle
title="Shavukha Token (SHVKH) token" title="Shavukha Token (SHVKH) token"
...@@ -56,6 +66,7 @@ const DefaultView = () => { ...@@ -56,6 +66,7 @@ const DefaultView = () => {
) } ) }
backLink={ backLink } backLink={ backLink }
contentAfter={ contentAfter } contentAfter={ contentAfter }
secondRow={ secondRow }
/> />
); );
}; };
......
...@@ -5,7 +5,6 @@ import React from 'react'; ...@@ -5,7 +5,6 @@ import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import iconVerifiedToken from 'icons/verified_token.svg'; import iconVerifiedToken from 'icons/verified_token.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import { publicTag, privateTag, watchlistName } from 'mocks/address/tag'; import { publicTag, privateTag, watchlistName } from 'mocks/address/tag';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EntityTags from 'ui/shared/EntityTags'; import EntityTags from 'ui/shared/EntityTags';
...@@ -14,8 +13,6 @@ import NetworkExplorers from 'ui/shared/NetworkExplorers'; ...@@ -14,8 +13,6 @@ import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from '../PageTitle'; import PageTitle from '../PageTitle';
const LongNameAndManyTags = () => { const LongNameAndManyTags = () => {
const isMobile = useIsMobile();
const tokenData: TokenInfo = { const tokenData: TokenInfo = {
address: '0xa77A39CC9680B10C00af5D4ABFc92e1F07406c64', address: '0xa77A39CC9680B10C00af5D4ABFc92e1F07406c64',
circulating_market_cap: null, circulating_market_cap: null,
...@@ -45,7 +42,7 @@ const LongNameAndManyTags = () => { ...@@ -45,7 +42,7 @@ const LongNameAndManyTags = () => {
{ label: 'after_1', display_name: 'Another tag' }, { label: 'after_1', display_name: 'Another tag' },
{ label: 'after_2', display_name: 'And yet more' }, { label: 'after_2', display_name: 'And yet more' },
] } ] }
contentAfter={ <NetworkExplorers type="token" pathParam="token-hash" ml="auto" hideText={ isMobile }/> } contentAfter={ <NetworkExplorers type="token" pathParam="token-hash" ml="auto"/> }
flexGrow={ 1 } flexGrow={ 1 }
/> />
</> </>
......
import { Box, chakra, Icon, Skeleton, Tooltip } from '@chakra-ui/react'; import { Box, chakra, Icon, IconButton, Skeleton, Tooltip } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
...@@ -16,10 +16,11 @@ interface Props { ...@@ -16,10 +16,11 @@ interface Props {
className?: string; className?: string;
token: TokenInfo; token: TokenInfo;
isLoading?: boolean; isLoading?: boolean;
variant?: 'icon' | 'button';
iconSize?: number; iconSize?: number;
} }
const AddressAddToWallet = ({ className, token, isLoading, iconSize = 6 }: Props) => { const AddressAddToWallet = ({ className, token, isLoading, variant = 'icon', iconSize = 6 }: Props) => {
const toast = useToast(); const toast = useToast();
const { provider, wallet } = useProvider(); const { provider, wallet } = useProvider();
const addOrSwitchChain = useAddOrSwitchChain(); const addOrSwitchChain = useAddOrSwitchChain();
...@@ -86,9 +87,26 @@ const AddressAddToWallet = ({ className, token, isLoading, iconSize = 6 }: Props ...@@ -86,9 +87,26 @@ const AddressAddToWallet = ({ className, token, isLoading, iconSize = 6 }: Props
return null; return null;
} }
if (variant === 'button') {
return (
<Tooltip label={ `Add token to ${ WALLETS_INFO[wallet].name }` }>
<IconButton
className={ className }
aria-label="Add token to wallet"
variant="outline"
size="sm"
px="6px"
onClick={ handleClick }
icon={ <Icon as={ WALLETS_INFO[wallet].icon } boxSize={ 6 }/> }
flexShrink={ 0 }
/>
</Tooltip>
);
}
return ( return (
<Tooltip label={ `Add token to ${ WALLETS_INFO[wallet].name }` }> <Tooltip label={ `Add token to ${ WALLETS_INFO[wallet].name }` }>
<Box className={ className } display="inline-flex" cursor="pointer" onClick={ handleClick }> <Box className={ className } display="inline-flex" cursor="pointer" onClick={ handleClick } flexShrink={ 0 }>
<Icon as={ WALLETS_INFO[wallet].icon } boxSize={ iconSize }/> <Icon as={ WALLETS_INFO[wallet].icon } boxSize={ iconSize }/>
</Box> </Box>
</Tooltip> </Tooltip>
......
...@@ -86,7 +86,6 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -86,7 +86,6 @@ const TokenDetails = ({ tokenQuery }: Props) => {
return ( return (
<Grid <Grid
mt={ 8 }
columnGap={ 8 } columnGap={ 8 }
rowGap={{ base: 1, lg: 3 }} 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(0, 1fr)' }} overflow="hidden"
......
...@@ -20,10 +20,10 @@ const TriggerButton = ({ isOpen, onClick }: Props, ref: React.ForwardedRef<HTMLB ...@@ -20,10 +20,10 @@ const TriggerButton = ({ isOpen, onClick }: Props, ref: React.ForwardedRef<HTMLB
aria-label="Show project info" aria-label="Show project info"
fontWeight={ 500 } fontWeight={ 500 }
px={ 2 } px={ 2 }
h="30px" h="32px"
> >
<Icon as={ rocketIcon } boxSize={ 4 } mr={ 1 }/> <Icon as={ rocketIcon } boxSize={ 5 } mr={ 1 }/>
<span>Project info</span> <span>Info</span>
<Icon as={ arrowIcon } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 1 }/> <Icon as={ arrowIcon } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 1 }/>
</Button> </Button>
); );
......
import { Flex, Skeleton } from '@chakra-ui/react'; import { Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
...@@ -25,9 +25,9 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => { ...@@ -25,9 +25,9 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => {
if (isLoading) { if (isLoading) {
return ( return (
<> <>
<Skeleton w="130px" h="30px" borderRadius="base"/> <Skeleton w="100px" h="30px" borderRadius="base"/>
<Skeleton w="130px" h="30px" borderRadius="base"/> <Skeleton w="100px" h="30px" borderRadius="base"/>
<Skeleton w="120px" h="30px" borderRadius="base"/> <Skeleton w="80px" h="30px" borderRadius="base"/>
</> </>
); );
} }
...@@ -40,7 +40,9 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => { ...@@ -40,7 +40,9 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => {
try { try {
const url = new URL(data.projectWebsite); const url = new URL(data.projectWebsite);
return ( return (
<LinkExternal href={ data.projectWebsite } variant="subtle">{ url.host }</LinkExternal> <LinkExternal href={ data.projectWebsite } variant="subtle" flexShrink={ 0 }>
{ url.host }
</LinkExternal>
); );
} catch (error) { } catch (error) {
return null; return null;
...@@ -55,7 +57,7 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => { ...@@ -55,7 +57,7 @@ const TokenVerifiedInfo = ({ verifiedInfoQuery }: Props) => {
); );
})(); })();
return <Flex columnGap={ 3 } rowGap={ 3 } mt={ 5 } flexWrap="wrap" _empty={{ display: 'none' }}>{ content }</Flex>; return content;
}; };
export default React.memo(TokenVerifiedInfo); export default React.memo(TokenVerifiedInfo);
...@@ -8,7 +8,6 @@ import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; ...@@ -8,7 +8,6 @@ import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem'; import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import NftMedia from 'ui/shared/nft/NftMedia'; import NftMedia from 'ui/shared/nft/NftMedia';
import TokenNftMarketplaces from 'ui/token/TokenNftMarketplaces'; import TokenNftMarketplaces from 'ui/token/TokenNftMarketplaces';
...@@ -37,7 +36,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { ...@@ -37,7 +36,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => {
return ( return (
<> <>
<Flex alignItems="flex-start" mt={ 8 } flexDir={{ base: 'column-reverse', lg: 'row' }} columnGap={ 6 } rowGap={ 6 }> <Flex alignItems="flex-start" flexDir={{ base: 'column-reverse', lg: 'row' }} columnGap={ 6 } rowGap={ 6 }>
<Grid <Grid
flexGrow={ 1 } flexGrow={ 1 }
columnGap={ 8 } columnGap={ 8 }
...@@ -45,17 +44,6 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { ...@@ -45,17 +44,6 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }} templateColumns={{ base: 'minmax(0, 1fr)', lg: '200px minmax(0, 1fr)' }}
overflow="hidden" overflow="hidden"
> >
<DetailsInfoItem
title="Token"
hint="Token name"
isLoading={ isLoading }
>
<TokenEntity
token={ data.token }
isLoading={ isLoading }
noCopy
/>
</DetailsInfoItem>
{ data.is_unique && data.owner && ( { data.is_unique && data.owner && (
<DetailsInfoItem <DetailsInfoItem
title="Owner" title="Owner"
......
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