Commit 963aaf40 authored by tom's avatar tom

change PageTitle layout

parent 2dcddbd0
...@@ -15,7 +15,7 @@ const APIDocsPage: NextPage = () => { ...@@ -15,7 +15,7 @@ const APIDocsPage: NextPage = () => {
return ( return (
<Page> <Page>
<PageTitle text="API Documentation"/> <PageTitle title="API Documentation"/>
<Head><title>{ `API for the ${ networkTitle }` }</title></Head> <Head><title>{ `API for the ${ networkTitle }` }</title></Head>
<SwaggerUI/> <SwaggerUI/>
</Page> </Page>
......
...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
const MarketplacePage: NextPage = () => { const MarketplacePage: NextPage = () => {
return ( return (
<Page> <Page>
<PageTitle text="Marketplace"/> <PageTitle title="Marketplace"/>
<Head><title>Blockscout | Marketplace</title></Head> <Head><title>Blockscout | Marketplace</title></Head>
<Marketplace/> <Marketplace/>
......
...@@ -16,7 +16,7 @@ const GraphiqlPage: NextPage = () => { ...@@ -16,7 +16,7 @@ const GraphiqlPage: NextPage = () => {
return ( return (
<Page> <Page>
<Head><title>Graph Page</title></Head> <Head><title>Graph Page</title></Head>
<PageTitle text="GraphQL playground"/> <PageTitle title="GraphQL playground"/>
<GraphQL/> <GraphQL/>
</Page> </Page>
); );
......
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import type { AddressTag, WatchlistName } from './addressParams'; import type { UserTags } from './addressParams';
import type { Block } from './block'; import type { Block } from './block';
import type { InternalTransaction } from './internalTransaction'; import type { InternalTransaction } from './internalTransaction';
import type { TokenInfo, TokenInstance, TokenType } from './token'; import type { TokenInfo, TokenInstance, TokenType } from './token';
import type { TokenTransfer, TokenTransferPagination } from './tokenTransfer'; import type { TokenTransfer, TokenTransferPagination } from './tokenTransfer';
export interface Address { export interface Address extends UserTags {
block_number_balance_updated_at: number | null; block_number_balance_updated_at: number | null;
coin_balance: string | null; coin_balance: string | null;
creator_address_hash: string | null; creator_address_hash: string | null;
...@@ -30,11 +30,8 @@ export interface Address { ...@@ -30,11 +30,8 @@ export interface Address {
is_contract: boolean; is_contract: boolean;
is_verified: boolean; is_verified: boolean;
name: string | null; name: string | null;
private_tags: Array<AddressTag> | null;
public_tags: Array<AddressTag> | null;
token: TokenInfo | null; token: TokenInfo | null;
watchlist_address_id: number | null; watchlist_address_id: number | null;
watchlist_names: Array<WatchlistName> | null;
} }
export interface AddressCounters { export interface AddressCounters {
......
...@@ -9,13 +9,16 @@ export interface WatchlistName { ...@@ -9,13 +9,16 @@ export interface WatchlistName {
display_name: string; display_name: string;
} }
export interface AddressParam { export interface UserTags {
private_tags: Array<AddressTag> | null;
watchlist_names: Array<WatchlistName> | null;
public_tags: Array<AddressTag> | null;
}
export interface AddressParam extends UserTags {
hash: string; hash: string;
implementation_name: string | null; implementation_name: string | null;
name: string | null; name: string | null;
is_contract: boolean; is_contract: boolean;
is_verified: boolean | null; is_verified: boolean | null;
private_tags: Array<AddressTag> | null;
watchlist_names: Array<WatchlistName> | null;
public_tags: Array<AddressTag> | null;
} }
...@@ -51,7 +51,7 @@ const Accounts = () => { ...@@ -51,7 +51,7 @@ const Accounts = () => {
return ( return (
<Page> <Page>
<PageTitle text="Top accounts" withTextAd/> <PageTitle title="Top accounts" withTextAd/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ isLoading }
......
import { Flex, Skeleton, Tag, Box, Icon } from '@chakra-ui/react'; import { Skeleton, Box, Icon } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -22,6 +22,7 @@ import AddressTokenTransfers from 'ui/address/AddressTokenTransfers'; ...@@ -22,6 +22,7 @@ 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 TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers'; import NetworkExplorers from 'ui/shared/NetworkExplorers';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -49,17 +50,6 @@ const AddressPageContent = () => { ...@@ -49,17 +50,6 @@ const AddressPageContent = () => {
queryOptions: { enabled: Boolean(hash) }, queryOptions: { enabled: Boolean(hash) },
}); });
const tags = [
addressQuery.data?.is_contract ? { label: 'contract', display_name: 'Contract' } : { label: 'eoa', display_name: 'EOA' },
addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined,
addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined,
...(addressQuery.data?.private_tags || []),
...(addressQuery.data?.public_tags || []),
...(addressQuery.data?.watchlist_names || []),
]
.filter(Boolean)
.map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const contractTabs = useContractTabs(addressQuery.data); const contractTabs = useContractTabs(addressQuery.data);
const tabs: Array<RoutedTab> = React.useMemo(() => { const tabs: Array<RoutedTab> = React.useMemo(() => {
...@@ -98,12 +88,19 @@ const AddressPageContent = () => { ...@@ -98,12 +88,19 @@ const AddressPageContent = () => {
].filter(Boolean); ].filter(Boolean);
}, [ addressQuery.data, contractTabs, hash ]); }, [ addressQuery.data, contractTabs, hash ]);
const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null; const tags = (
const additionalsRight = ( <EntityTags
<> data={ addressQuery.data }
{ tagsNode } isLoading={ addressQuery.isPlaceholderData }
tagsBefore={ [
addressQuery.data?.is_contract ? { label: 'contract', display_name: 'Contract' } : { label: 'eoa', display_name: 'EOA' },
addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined,
addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined,
] }
contentAfter={
<NetworkExplorers type="address" pathParam={ hash } ml="auto"/> <NetworkExplorers type="address" pathParam={ hash } ml="auto"/>
</> }
/>
); );
const content = addressQuery.isError ? null : <RoutedTabs tabs={ tabs } tabListProps={{ mt: 8 }}/>; const content = addressQuery.isError ? null : <RoutedTabs tabs={ tabs } tabListProps={{ mt: 8 }}/>;
...@@ -128,9 +125,9 @@ const AddressPageContent = () => { ...@@ -128,9 +125,9 @@ const AddressPageContent = () => {
<Skeleton h={ 10 } w="260px" mb={ 6 }/> <Skeleton h={ 10 } w="260px" mb={ 6 }/>
) : ( ) : (
<PageTitle <PageTitle
text={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` } title={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` }
additionalsRight={ additionalsRight }
backLink={ backLink } backLink={ backLink }
contentAfter={ tags }
/> />
) } ) }
<AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/> <AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/>
......
...@@ -132,7 +132,7 @@ const ApiKeysPage: React.FC = () => { ...@@ -132,7 +132,7 @@ const ApiKeysPage: React.FC = () => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageTitle text="API keys"/> <PageTitle title="API keys"/>
{ content } { content }
</Box> </Box>
</Page> </Page>
......
...@@ -103,9 +103,10 @@ const BlockPageContent = () => { ...@@ -103,9 +103,10 @@ const BlockPageContent = () => {
<Skeleton h={ 10 } w="300px" mb={ 6 }/> <Skeleton h={ 10 } w="300px" mb={ 6 }/>
) : ( ) : (
<PageTitle <PageTitle
text={ `Block #${ blockQuery.data?.height }` } title={ `Block #${ blockQuery.data?.height }` }
backLink={ backLink } backLink={ backLink }
additionalsRight={ <NetworkExplorers type="block" pathParam={ height } ml={{ base: 'initial', lg: 'auto' }}/> } contentAfter={ <NetworkExplorers type="block" pathParam={ height } ml={{ base: 'initial', lg: 'auto' }}/> }
withTextAd
/> />
) } ) }
{ blockQuery.isLoading ? <SkeletonTabs/> : ( { blockQuery.isLoading ? <SkeletonTabs/> : (
......
...@@ -42,7 +42,7 @@ const BlocksPageContent = () => { ...@@ -42,7 +42,7 @@ const BlocksPageContent = () => {
return ( return (
<Page> <Page>
<PageTitle text="Blocks" withTextAd/> <PageTitle title="Blocks" withTextAd/>
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS } tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
......
...@@ -98,7 +98,7 @@ const ContractVerification = () => { ...@@ -98,7 +98,7 @@ const ContractVerification = () => {
return ( return (
<Page> <Page>
<PageTitle <PageTitle
text="New smart contract verification" title="New smart contract verification"
backLink={ backLink } backLink={ backLink }
/> />
{ hash && ( { hash && (
......
...@@ -90,7 +90,7 @@ const CsvExport = () => { ...@@ -90,7 +90,7 @@ const CsvExport = () => {
return ( return (
<Page> <Page>
<PageTitle <PageTitle
text="Export data to CSV file" title="Export data to CSV file"
backLink={ backLink } backLink={ backLink }
/> />
<Flex mb={ 10 } whiteSpace="pre-wrap" flexWrap="wrap"> <Flex mb={ 10 } whiteSpace="pre-wrap" flexWrap="wrap">
......
...@@ -115,7 +115,7 @@ const CustomAbiPage: React.FC = () => { ...@@ -115,7 +115,7 @@ const CustomAbiPage: React.FC = () => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageTitle text="Custom ABI"/> <PageTitle title="Custom ABI"/>
{ content } { content }
</Box> </Box>
</Page> </Page>
......
...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
const Graph = () => { const Graph = () => {
return ( return (
<Page> <Page>
<PageTitle text="Charts"/> <PageTitle title="Charts"/>
<Heading as="h2" size="sm" fontWeight="500" mb={ 3 }>Ethereum Daily Transactions & ERC-20 Token Transfer Chart</Heading> <Heading as="h2" size="sm" fontWeight="500" mb={ 3 }>Ethereum Daily Transactions & ERC-20 Token Transfer Chart</Heading>
<Box w="100%" h="400px"> <Box w="100%" h="400px">
<EthereumChart/> <EthereumChart/>
......
...@@ -68,7 +68,7 @@ const L2Deposits = () => { ...@@ -68,7 +68,7 @@ const L2Deposits = () => {
return ( return (
<Page> <Page>
<PageTitle text={ `Deposits (L1${ nbsp }${ rightLineArrow }${ nbsp }L2)` } withTextAd/> <PageTitle title={ `Deposits (L1${ nbsp }${ rightLineArrow }${ nbsp }L2)` } withTextAd/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ isLoading }
......
...@@ -70,7 +70,7 @@ const L2OutputRoots = () => { ...@@ -70,7 +70,7 @@ const L2OutputRoots = () => {
return ( return (
<Page> <Page>
<PageTitle text="Output roots" withTextAd/> <PageTitle title="Output roots" withTextAd/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ isLoading }
......
...@@ -71,7 +71,7 @@ const L2TxnBatches = () => { ...@@ -71,7 +71,7 @@ const L2TxnBatches = () => {
return ( return (
<Page> <Page>
<PageTitle text={ `Tx batches (L2${ nbsp }blocks)` } withTextAd/> <PageTitle title={ `Tx batches (L2${ nbsp }blocks)` } withTextAd/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ isLoading }
......
...@@ -68,7 +68,7 @@ const L2Withdrawals = () => { ...@@ -68,7 +68,7 @@ const L2Withdrawals = () => {
return ( return (
<Page> <Page>
<PageTitle text={ `Withdrawals (L2${ nbsp }${ rightLineArrow }${ nbsp }L1)` } withTextAd/> <PageTitle title={ `Withdrawals (L2${ nbsp }${ rightLineArrow }${ nbsp }L1)` } withTextAd/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ isLoading }
......
...@@ -56,7 +56,7 @@ const Login = () => { ...@@ -56,7 +56,7 @@ const Login = () => {
return ( return (
<Page> <Page>
<VStack gap={ 4 } alignItems="flex-start" maxW="1000px"> <VStack gap={ 4 } alignItems="flex-start" maxW="1000px">
<PageTitle text="Login page 😂"/> <PageTitle title="Login page 😂"/>
{ isFormVisible && ( { isFormVisible && (
<> <>
<Alert status="error" flexDirection="column" alignItems="flex-start"> <Alert status="error" flexDirection="column" alignItems="flex-start">
......
...@@ -55,7 +55,7 @@ const MyProfile = () => { ...@@ -55,7 +55,7 @@ const MyProfile = () => {
return ( return (
<Page> <Page>
<PageTitle text="My profile"/> <PageTitle title="My profile"/>
{ content } { content }
</Page> </Page>
); );
......
...@@ -19,7 +19,7 @@ const PrivateTags = () => { ...@@ -19,7 +19,7 @@ const PrivateTags = () => {
return ( return (
<Page> <Page>
<PageTitle text="Private tags"/> <PageTitle title="Private tags"/>
<RoutedTabs tabs={ TABS }/> <RoutedTabs tabs={ TABS }/>
</Page> </Page>
); );
......
import { Link, Text, Icon } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { animateScroll } from 'react-scroll'; import { animateScroll } from 'react-scroll';
import type { PublicTag } from 'types/api/account'; import type { PublicTag } from 'types/api/account';
import eastArrowIcon from 'icons/arrows/east.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken'; import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
...@@ -32,7 +29,6 @@ const PublicTagsComponent: React.FC = () => { ...@@ -32,7 +29,6 @@ const PublicTagsComponent: React.FC = () => {
const [ formData, setFormData ] = useState<Partial<PublicTag> | undefined>(addressHash ? { addresses: [ addressHash ] } : undefined); const [ formData, setFormData ] = useState<Partial<PublicTag> | undefined>(addressHash ? { addresses: [ addressHash ] } : undefined);
const toast = useToast(); const toast = useToast();
const isMobile = useIsMobile();
useRedirectForInvalidAuthToken(); useRedirectForInvalidAuthToken();
React.useEffect(() => { React.useEffect(() => {
...@@ -88,15 +84,18 @@ const PublicTagsComponent: React.FC = () => { ...@@ -88,15 +84,18 @@ const PublicTagsComponent: React.FC = () => {
header = formData ? 'Request to edit a public tag/label' : 'Request a public tag/label'; header = formData ? 'Request to edit a public tag/label' : 'Request a public tag/label';
} }
const backLink = {
label: 'Public tags',
onClick: onGoBack,
};
return ( return (
<Page> <Page>
{ screen === 'form' && ( <PageTitle
<Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }> title={ header }
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/> backLink={ screen === 'form' ? backLink : undefined }
{ isMobile && <Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text> } display={{ base: 'block', lg: 'inline-flex' }}
</Link> />
) }
<PageTitle text={ header } display={{ base: 'block', lg: 'inline-flex' }} ml={{ base: 0, lg: 3 }}/>
{ content } { content }
</Page> </Page>
); );
......
...@@ -152,7 +152,7 @@ const SearchResultsPageContent = () => { ...@@ -152,7 +152,7 @@ const SearchResultsPageContent = () => {
<Page renderHeader={ renderHeader }> <Page renderHeader={ renderHeader }>
{ isLoading || redirectCheckQuery.isLoading ? { isLoading || redirectCheckQuery.isLoading ?
<Skeleton h={ 10 } mb={ 6 } w="100%" maxW="222px"/> : <Skeleton h={ 10 } mb={ 6 } w="100%" maxW="222px"/> :
<PageTitle text="Search results"/> <PageTitle title="Search results"/>
} }
{ bar } { bar }
{ content } { content }
......
...@@ -34,7 +34,7 @@ const Sol2Uml = () => { ...@@ -34,7 +34,7 @@ const Sol2Uml = () => {
return ( return (
<Page> <Page>
<PageTitle <PageTitle
text="Solidity UML diagram" title="Solidity UML diagram"
backLink={ backLink } backLink={ backLink }
/> />
<Flex mb={ 10 }> <Flex mb={ 10 }>
......
...@@ -26,7 +26,7 @@ const Stats = () => { ...@@ -26,7 +26,7 @@ const Stats = () => {
return ( return (
<Page> <Page>
<PageTitle text={ `${ appConfig.network.name } stats` }/> <PageTitle title={ `${ appConfig.network.name } stats` }/>
<Box mb={{ base: 6, sm: 8 }}> <Box mb={{ base: 6, sm: 8 }}>
<NumberWidgetsList/> <NumberWidgetsList/>
......
import { Box, Icon, Flex } from '@chakra-ui/react'; import { Box, Icon } 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';
...@@ -22,7 +22,7 @@ import * as addressStubs from 'stubs/address'; ...@@ -22,7 +22,7 @@ import * as addressStubs from 'stubs/address';
import * as tokenStubs from 'stubs/token'; import * as tokenStubs from 'stubs/token';
import AddressContract from 'ui/address/AddressContract'; import AddressContract from 'ui/address/AddressContract';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import Tag from 'ui/shared/chakra/Tag'; 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';
import type { Props as PaginationProps } from 'ui/shared/Pagination'; import type { Props as PaginationProps } from 'ui/shared/Pagination';
...@@ -228,38 +228,36 @@ const TokenPageContent = () => { ...@@ -228,38 +228,36 @@ const TokenPageContent = () => {
}; };
}, [ appProps.referrer ]); }, [ appProps.referrer ]);
const tags = [ const tags = (
{ label: tokenQuery.data?.type, display_name: tokenQuery.data?.type }, <EntityTags
...(contractQuery.data?.private_tags || []), data={ contractQuery.data }
...(contractQuery.data?.public_tags || []), isLoading={ tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData }
...(contractQuery.data?.watchlist_names || []), tagsBefore={ [
] tokenQuery.data ? { label: tokenQuery.data?.type, display_name: tokenQuery.data?.type } : undefined,
.filter(Boolean) ] }
.map((tag) => <Tag key={ tag.label } isLoading={ tokenQuery.isPlaceholderData }>{ tag.display_name }</Tag>); contentAfter={
const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null;
const additionalsRight = (
<>
{ tagsNode }
<NetworkExplorers type="token" pathParam={ hashString } ml="auto"/> <NetworkExplorers type="token" pathParam={ hashString } ml="auto"/>
</> }
flexGrow={ 1 }
/>
); );
return ( return (
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
title={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
isLoading={ tokenQuery.isPlaceholderData } isLoading={ tokenQuery.isPlaceholderData }
text={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
backLink={ backLink } backLink={ backLink }
additionalsLeft={ ( beforeTitle={ (
<TokenLogo data={ tokenQuery.data } boxSize={ 6 } isLoading={ tokenQuery.isPlaceholderData }/> <TokenLogo data={ tokenQuery.data } boxSize={ 6 } isLoading={ tokenQuery.isPlaceholderData } display="inline-block" mr={ 2 }/>
) } ) }
additionalsRight={ additionalsRight }
afterTitle={ afterTitle={
verifiedInfoQuery.data?.tokenAddress ? verifiedInfoQuery.data?.tokenAddress ?
<Icon as={ iconSuccess } color="green.500" boxSize={ 4 } verticalAlign="top"/> : <Icon as={ iconSuccess } color="green.500" boxSize={ 4 } verticalAlign="top"/> :
<Box boxSize={ 4 } display="inline-block"/> <Box boxSize={ 4 } display="inline-block"/>
} }
contentAfter={ tags }
/> />
<TokenContractInfo tokenQuery={ tokenQuery } contractQuery={ contractQuery }/> <TokenContractInfo tokenQuery={ tokenQuery } contractQuery={ contractQuery }/>
<TokenVerifiedInfo verifiedInfoQuery={ verifiedInfoQuery } isVerifiedInfoEnabled={ isVerifiedInfoEnabled }/> <TokenVerifiedInfo verifiedInfoQuery={ verifiedInfoQuery } isVerifiedInfoEnabled={ isVerifiedInfoEnabled }/>
......
...@@ -7,7 +7,7 @@ import TokensList from 'ui/tokens/Tokens'; ...@@ -7,7 +7,7 @@ import TokensList from 'ui/tokens/Tokens';
const Tokens = () => { const Tokens = () => {
return ( return (
<Page> <Page>
<PageTitle text="Tokens" withTextAd/> <PageTitle title="Tokens" withTextAd/>
<TokensList/> <TokensList/>
</Page> </Page>
); );
......
import { Flex, Tag } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -8,6 +7,7 @@ import useApiQuery from 'lib/api/useApiQuery'; ...@@ -8,6 +7,7 @@ import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers'; import NetworkExplorers from 'ui/shared/NetworkExplorers';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -34,16 +34,19 @@ const TransactionPageContent = () => { ...@@ -34,16 +34,19 @@ const TransactionPageContent = () => {
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
const { data } = useApiQuery('tx', { const { data, isPlaceholderData } = useApiQuery('tx', {
pathParams: { hash }, pathParams: { hash },
queryOptions: { enabled: Boolean(hash) }, queryOptions: { enabled: Boolean(hash) },
}); });
const additionals = ( const tags = (
<Flex justifyContent="space-between" alignItems="center" flexGrow={ 1 } flexWrap="wrap"> <EntityTags
{ data?.tx_tag && <Tag my={ 2 }>{ data.tx_tag }</Tag> } isLoading={ isPlaceholderData }
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' }}/> <NetworkExplorers type="tx" pathParam={ hash } ml={{ base: 'initial', lg: 'auto' }}/>
</Flex> }
/>
); );
const backLink = React.useMemo(() => { const backLink = React.useMemo(() => {
...@@ -63,9 +66,9 @@ const TransactionPageContent = () => { ...@@ -63,9 +66,9 @@ const TransactionPageContent = () => {
<Page> <Page>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
text="Transaction details" title="Transaction details"
additionalsRight={ additionals }
backLink={ backLink } backLink={ backLink }
contentAfter={ tags }
/> />
<RoutedTabs tabs={ TABS }/> <RoutedTabs tabs={ TABS }/>
</Page> </Page>
......
...@@ -73,7 +73,7 @@ const Transactions = () => { ...@@ -73,7 +73,7 @@ const Transactions = () => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageTitle text="Transactions" withTextAd/> <PageTitle title="Transactions" withTextAd/>
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS } tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
......
...@@ -130,7 +130,7 @@ const VerifiedAddresses = () => { ...@@ -130,7 +130,7 @@ const VerifiedAddresses = () => {
const tokenName = addressInfo ? `${ addressInfo.metadata.tokenName } (${ addressInfo.metadata.tokenSymbol })` : ''; const tokenName = addressInfo ? `${ addressInfo.metadata.tokenName } (${ addressInfo.metadata.tokenSymbol })` : '';
return ( return (
<> <>
<PageTitle text="Token info application form" backLink={ backLink }/> <PageTitle title="Token info application form" backLink={ backLink }/>
<TokenInfoForm <TokenInfoForm
address={ selectedAddress } address={ selectedAddress }
tokenName={ tokenName } tokenName={ tokenName }
...@@ -167,7 +167,7 @@ const VerifiedAddresses = () => { ...@@ -167,7 +167,7 @@ const VerifiedAddresses = () => {
return ( return (
<> <>
<PageTitle text="My verified addresses"/> <PageTitle title="My verified addresses"/>
<AccountPageDescription allowCut={ false }> <AccountPageDescription allowCut={ false }>
<span> <span>
Verify ownership of a smart contract address to easily update information in Blockscout. Verify ownership of a smart contract address to easily update information in Blockscout.
......
...@@ -117,7 +117,7 @@ const VerifiedContracts = () => { ...@@ -117,7 +117,7 @@ const VerifiedContracts = () => {
return ( return (
<Box> <Box>
<PageTitle text="Verified contracts" withTextAd/> <PageTitle title="Verified contracts" withTextAd/>
<VerifiedContractsCounters/> <VerifiedContractsCounters/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
......
...@@ -167,7 +167,7 @@ const WatchList: React.FC = () => { ...@@ -167,7 +167,7 @@ const WatchList: React.FC = () => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageTitle text="Watch list"/> <PageTitle title="Watch list"/>
{ content } { content }
</Box> </Box>
</Page> </Page>
......
...@@ -70,7 +70,7 @@ const Withdrawals = () => { ...@@ -70,7 +70,7 @@ const Withdrawals = () => {
return ( return (
<Page> <Page>
<PageTitle text="Withdrawals" withTextAd/> <PageTitle title="Withdrawals" withTextAd/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ isLoading }
......
import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
import type { UserTags } from 'types/api/addressParams';
import Tag from 'ui/shared/chakra/Tag';
interface TagData {
label: string;
display_name: string;
}
interface Props {
className?: string;
data?: UserTags;
isLoading?: boolean;
tagsBefore?: Array<TagData | undefined>;
tagsAfter?: Array<TagData | undefined>;
contentAfter?: React.ReactNode;
}
const EntityTags = ({ className, data, tagsBefore = [], tagsAfter = [], isLoading, contentAfter }: Props) => {
const tags = [
...tagsBefore,
...(data?.private_tags || []),
...(data?.public_tags || []),
...(data?.watchlist_names || []),
...tagsAfter,
]
.filter(Boolean)
.map((tag) => <Tag key={ tag.label } isLoading={ isLoading }>{ tag.display_name }</Tag>);
if (tags.length === 0) {
return null;
}
return (
<Flex className={ className } columnGap={ 2 } rowGap={ 2 } flexWrap="wrap" alignItems="center">
{ tags }
{ contentAfter }
</Flex>
);
};
export default React.memo(chakra(EntityTags));
// import { Icon } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
// import plusIcon from 'icons/plus.svg';
import * as textAdMock from 'mocks/ad/textAd'; import * as textAdMock from 'mocks/ad/textAd';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import PageTitle from './PageTitle'; import DefaultView from './specs/DefaultView';
import LongNameAndManyTags from './specs/LongNameAndManyTags';
import WithTextAd from './specs/WithTextAd';
test.beforeEach(async({ page }) => { test.beforeEach(async({ page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
...@@ -19,49 +19,38 @@ test.beforeEach(async({ page }) => { ...@@ -19,49 +19,38 @@ test.beforeEach(async({ page }) => {
path: './playwright/image_s.jpg', path: './playwright/image_s.jpg',
}); });
}); });
await page.route('https://example.com/logo.png', (route) => {
return route.fulfill({
status: 200,
path: './playwright/image_s.jpg',
});
});
}); });
test('default view +@mobile', async({ mount }) => { test('default view +@mobile', async({ mount }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<PageTitle <DefaultView/>
text="Title"
/>
</TestApp>, </TestApp>,
); );
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('with text ad, back link and addons +@mobile +@dark-mode', async({ mount }) => { test('with text ad +@mobile', async({ mount }) => {
// https://github.com/microsoft/playwright/issues/15620
// not possible to pass component as a prop in tests
// const left = <Icon as={ plusIcon }/>;
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<PageTitle <WithTextAd/>
text="Title"
withTextAd
backLink={{ label: 'Back', url: 'back' }}
// additionalsLeft={ left }
additionalsRight="Privet"
/>
</TestApp>, </TestApp>,
); );
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('long title with text ad, back link and addons +@mobile', async({ mount }) => { test('with long name and many tags +@mobile', async({ mount }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<PageTitle <LongNameAndManyTags/>
text="This title is long, really long"
withTextAd
backLink={{ label: 'Back', url: 'back' }}
additionalsRight="Privet, kak dela?"
/>
</TestApp>, </TestApp>,
); );
......
import { Heading, Flex, Grid, Tooltip, Icon, Link, chakra, Skeleton } from '@chakra-ui/react'; import { Heading, Flex, Tooltip, Icon, Link, chakra, Box, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
...@@ -8,42 +8,31 @@ import LinkInternal from 'ui/shared/LinkInternal'; ...@@ -8,42 +8,31 @@ import LinkInternal from 'ui/shared/LinkInternal';
type BackLinkProp = { label: string; url: string } | { label: string; onClick: () => void }; type BackLinkProp = { label: string; url: string } | { label: string; onClick: () => void };
type Props = { type Props = {
text: string; title: string;
additionalsLeft?: React.ReactNode;
additionalsRight?: React.ReactNode;
withTextAd?: boolean;
className?: string; className?: string;
backLink?: BackLinkProp; backLink?: BackLinkProp;
beforeTitle?: React.ReactNode;
afterTitle?: React.ReactNode; afterTitle?: React.ReactNode;
contentAfter?: React.ReactNode;
isLoading?: boolean; isLoading?: boolean;
withTextAd?: boolean;
} }
const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLink, className, isLoading, afterTitle }: Props) => { const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
const title = ( if (!props) {
<Skeleton isLoaded={ !isLoading }>
<Heading
as="h1"
size="lg"
flex="none"
wordBreak="break-word"
>
{ text }
{ afterTitle }
</Heading>
</Skeleton>
);
const backLinkComponent = (() => {
if (!backLink) {
return null; return null;
} }
if (props.isLoading) {
return <Skeleton boxSize={ 6 } display="inline-block" borderRadius="base" mr={ 3 } 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"/>;
if ('url' in backLink) { if ('url' in props) {
return ( return (
<Tooltip label={ backLink.label }> <Tooltip label={ props.label }>
<LinkInternal display="inline-flex" href={ backLink.url } h="40px"> <LinkInternal display="inline-flex" href={ props.url } h="40px" mr={ 3 }>
{ icon } { icon }
</LinkInternal> </LinkInternal>
</Tooltip> </Tooltip>
...@@ -51,40 +40,47 @@ const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLi ...@@ -51,40 +40,47 @@ const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLi
} }
return ( return (
<Tooltip label={ backLink.label }> <Tooltip label={ props.label }>
<Link display="inline-flex" onClick={ backLink.onClick } h="40px"> <Link display="inline-flex" onClick={ props.onClick } h="40px" mr={ 3 }>
{ icon } { icon }
</Link> </Link>
</Tooltip> </Tooltip>
); );
})(); };
const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading, afterTitle, beforeTitle }: Props) => {
return ( return (
<Flex <Flex
columnGap={ 3 }
rowGap={ 3 }
alignItems={{ base: 'start', lg: 'center' }}
flexDirection={{ base: 'column', lg: 'row' }}
mb={ 6 }
justifyContent="space-between"
className={ className } className={ className }
> mb={ 6 }
<Flex flexWrap="wrap" columnGap={ 3 } alignItems="center" width={ withTextAd ? 'unset' : '100%' } flexShrink={ 0 }> flexDir="row"
<Grid flexWrap="wrap"
templateColumns={ [ backLinkComponent && 'auto', additionalsLeft && 'auto', '1fr' ].filter(Boolean).join(' ') } rowGap={ 3 }
columnGap={ 3 } columnGap={ 3 }
alignItems="center"
>
<Box>
{ backLink && <BackLink { ...backLink } isLoading={ isLoading }/> }
{ beforeTitle }
<Skeleton
isLoaded={ !isLoading }
display="inline"
verticalAlign={ isLoading ? 'super' : undefined }
>
<Heading
as="h1"
size="lg"
display="inline"
wordBreak="break-word"
w="100%"
> >
{ backLinkComponent }
{ additionalsLeft !== undefined && (
<Flex h="40px" alignItems="center">
{ additionalsLeft }
</Flex>
) }
{ title } { title }
</Grid> </Heading>
{ additionalsRight } </Skeleton>
</Flex> { afterTitle }
{ withTextAd && <TextAd flexShrink={ 100 }/> } </Box>
{ contentAfter }
{ withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto"/> }
</Flex> </Flex>
); );
}; };
......
import { Icon } from '@chakra-ui/react';
import React from 'react';
import type { TokenInfo } from 'types/api/token';
import iconSuccess from 'icons/status/success.svg';
import Tag from 'ui/shared/chakra/Tag';
import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import TokenLogo from 'ui/shared/TokenLogo';
import PageTitle from '../PageTitle';
const DefaultView = () => {
const tokenData: TokenInfo = {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
type: 'ERC-20',
symbol: 'SHAAAAAAAAAAAAA',
name: null,
decimals: '18',
holders: '1',
exchange_rate: null,
total_supply: null,
icon_url: 'https://example.com/logo.png',
};
const backLink = {
label: 'Back to tokens list',
url: 'https://localhost:3000/tokens',
};
const contentAfter = (
<EntityTags
tagsBefore={ [
{ label: 'example', display_name: 'Example label' },
] }
contentAfter={ (
<>
<Tag key="custom" colorScheme="orange" variant="solid">Awesome</Tag>
<NetworkExplorers type="token" pathParam="token-hash" ml="auto"/>
</>
) }
flexGrow={ 1 }
/>
);
return (
<PageTitle
title="Shavukha Token (SHVKH) token"
beforeTitle={ (
<TokenLogo data={ tokenData } boxSize={ 6 } display="inline-block" mr={ 2 }/>
) }
afterTitle={ <Icon as={ iconSuccess } color="green.500" boxSize={ 4 } verticalAlign="top"/> }
backLink={ backLink }
contentAfter={ contentAfter }
/>
);
};
export default DefaultView;
/* eslint-disable max-len */
import { Icon } from '@chakra-ui/react';
import React from 'react';
import type { TokenInfo } from 'types/api/token';
import iconSuccess from 'icons/status/success.svg';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import { publicTag, privateTag, watchlistName } from 'mocks/address/tag';
import Tag from 'ui/shared/chakra/Tag';
import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import TokenLogo from 'ui/shared/TokenLogo';
import PageTitle from '../PageTitle';
const LongNameAndManyTags = () => {
const tokenData: TokenInfo = {
address: '0xa77A39CC9680B10C00af5D4ABFc92e1F07406c64',
decimals: null,
exchange_rate: null,
holders: '294',
icon_url: null,
name: 'Ring ding ding daa baa Baa aramba baa bom baa barooumba Wh-wha-what&#39;s going on-on? Ding, ding This is the Crazy Frog Ding, ding Bem, bem! Ring ding ding ding ding ding Ring ding ding ding bem bem bem Ring ding ding ding ding ding Ring ding ding ding baa b',
symbol: 'BatcoiRing ding ding daa baa Baa aramba baa bom baa barooumba Wh-wha-what&#39;s going on-on? Ding, ding This is the Crazy Frog Ding, ding Bem, bem! Ring ding ding ding ding ding Ring ding ding ding bem bem bem Ring ding ding ding ding ding Ring ding ding ding',
total_supply: '13747',
type: 'ERC-721',
};
const contentAfter = (
<EntityTags
data={{
private_tags: [ privateTag ],
public_tags: [ publicTag ],
watchlist_names: [ watchlistName ],
}}
tagsBefore={ [
{ label: 'example', display_name: 'Example with long name' },
] }
tagsAfter={ [
{ label: 'after_1', display_name: 'Another tag' },
{ label: 'after_2', display_name: 'And yet more' },
] }
contentAfter={ (
<>
<Tag key="custom" colorScheme="orange" variant="solid">Awesome</Tag>
<NetworkExplorers type="token" pathParam="token-hash" ml="auto"/>
</>
) }
flexGrow={ 1 }
/>
);
const tokenSymbolText = ` (${ trimTokenSymbol(tokenData.symbol) })`;
return (
<PageTitle
title={ `${ tokenData?.name }${ tokenSymbolText } token` }
beforeTitle={ (
<TokenLogo data={ tokenData } boxSize={ 6 } display="inline-block" mr={ 2 }/>
) }
afterTitle={ <Icon as={ iconSuccess } color="green.500" boxSize={ 4 } verticalAlign="top"/> }
contentAfter={ contentAfter }
/>
);
};
export default LongNameAndManyTags;
import React from 'react';
import Tag from 'ui/shared/chakra/Tag';
import PageTitle from '../PageTitle';
const WithTextAd = () => {
const backLink = {
label: 'Back to Home',
url: 'https://localhost:3000',
};
return (
<PageTitle
title="Block"
backLink={ backLink }
contentAfter={ <Tag key="custom" colorScheme="orange" variant="solid">Awesome</Tag> }
withTextAd
/>
);
};
export default WithTextAd;
...@@ -145,10 +145,10 @@ const TokenInstanceContent = () => { ...@@ -145,10 +145,10 @@ const TokenInstanceContent = () => {
<> <>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
text={ `${ tokenInstanceQuery.data.token.name || 'Unnamed token' } #${ tokenInstanceQuery.data.id }` } title={ `${ tokenInstanceQuery.data.token.name || 'Unnamed token' } #${ tokenInstanceQuery.data.id }` }
backLink={ backLink } backLink={ backLink }
additionalsLeft={ nftShieldIcon } beforeTitle={ nftShieldIcon }
additionalsRight={ tokenTag } contentAfter={ tokenTag }
/> />
<AddressHeadingInfo address={ address } token={ tokenInstanceQuery.data.token }/> <AddressHeadingInfo address={ address } token={ tokenInstanceQuery.data.token }/>
......
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