Commit 988d3f6f authored by tom's avatar tom

Merge branch 'contract-verification-form' into fancy-select

parents 954d757f 9f8cc98c
...@@ -50,12 +50,12 @@ blockscout: ...@@ -50,12 +50,12 @@ blockscout:
resources: resources:
limits: limits:
memory: memory:
_default: "4Gi" _default: "5Gi"
cpu: cpu:
_default: "4" _default: "4"
requests: requests:
memory: memory:
_default: "4Gi" _default: "5Gi"
cpu: cpu:
_default: "4" _default: "4"
# enable service to connect to RDS # enable service to connect to RDS
...@@ -143,7 +143,7 @@ blockscout: ...@@ -143,7 +143,7 @@ blockscout:
RUST_VERIFICATION_SERVICE_URL: RUST_VERIFICATION_SERVICE_URL:
_default: http://sc-verifier-svc:80 _default: http://sc-verifier-svc:80
INDEXER_MEMORY_LIMIT: INDEXER_MEMORY_LIMIT:
_default: 3 _default: 5
ACCOUNT_ENABLED: ACCOUNT_ENABLED:
_default: 'true' _default: 'true'
API_V2_ENABLED: API_V2_ENABLED:
...@@ -171,14 +171,14 @@ postgres: ...@@ -171,14 +171,14 @@ postgres:
resources: resources:
limits: limits:
memory: memory:
_default: "5Gi" _default: "6Gi"
cpu: cpu:
_default: "1.5" _default: "2"
requests: requests:
memory: memory:
_default: "5Gi" _default: "6Gi"
cpu: cpu:
_default: "1.5" _default: "2"
# node label # node label
nodeSelector: nodeSelector:
enabled: true enabled: true
......
import _debounce from 'lodash/debounce';
import type { LegacyRef } from 'react';
import React from 'react';
export default function useClientRect<E extends Element>(): [ DOMRect | null, LegacyRef<E> | undefined ] {
const [ rect, setRect ] = React.useState<DOMRect | null>(null);
const nodeRef = React.useRef<E>();
const ref = React.useCallback((node: E) => {
if (node !== null) {
setRect(node.getBoundingClientRect());
}
nodeRef.current = node;
}, []);
React.useEffect(() => {
const content = window.document.querySelector('main');
if (!content) {
return;
}
const resizeHandler = _debounce(() => {
setRect(nodeRef.current?.getBoundingClientRect() ?? null);
}, 100);
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(content);
resizeObserver.observe(window.document.body);
return function cleanup() {
resizeObserver.unobserve(content);
resizeObserver.unobserve(window.document.body);
};
}, [ ]);
return [ rect, ref ];
}
import React from 'react';
// Returns true if component is just mounted (on first render) and false otherwise.
export function useFirstMountState(): boolean {
const isFirst = React.useRef(true);
if (isFirst.current) {
isFirst.current = false;
return true;
}
return isFirst.current;
}
...@@ -25,13 +25,13 @@ export default function useNavItems() { ...@@ -25,13 +25,13 @@ export default function useNavItems() {
return React.useMemo(() => { return React.useMemo(() => {
const mainNavItems = [ const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block'), isNewUi: false }, { text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block'), isNewUi: true },
{ text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx'), isNewUi: false }, { text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx'), isNewUi: true },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false }, { text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute.startsWith('token'), isNewUi: true },
{ text: 'Accounts', url: link('accounts'), icon: walletIcon, isActive: currentRoute === 'accounts', isNewUi: false }, { text: 'Accounts', url: link('accounts'), icon: walletIcon, isActive: currentRoute === 'accounts', isNewUi: true },
isMarketplaceFilled ? isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps', isNewUi: true } : null, { text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute.startsWith('app'), isNewUi: true } : null,
{ text: 'Charts & stats', url: link('stats'), icon: statsIcon, isActive: currentRoute === 'stats', isNewUi: false }, { text: 'Charts & stats', url: link('stats'), icon: statsIcon, isActive: currentRoute === 'stats', isNewUi: true },
// there should be custom site sections like Stats, Faucet, More, etc but never an 'other' // there should be custom site sections like Stats, Faucet, More, etc but never an 'other'
// examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/ // examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/
// at this stage custom menu items is under development, we will implement it later // at this stage custom menu items is under development, we will implement it later
......
import React from 'react';
import { useFirstMountState } from './useFirstMountState';
// React effect hook that ignores the first invocation (e.g. on mount). The signature is exactly the same as the useEffect hook.
const useUpdateEffect: typeof React.useEffect = (effect, deps) => {
const isFirstMount = useFirstMountState();
React.useEffect(() => {
if (!isFirstMount) {
return effect();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
};
export default useUpdateEffect;
import type { ServerResponse } from 'http';
export function appendValue(res: ServerResponse | undefined, name: string, value: number) {
const currentValue = res?.getHeader('Server-Timing') || '';
const nextValue = [
currentValue,
`${ name };dur=${ value }`,
].filter(Boolean).join(',');
res?.setHeader('Server-Timing', nextValue);
}
...@@ -8,7 +8,7 @@ export default function getSeo(params: PageParams) { ...@@ -8,7 +8,7 @@ export default function getSeo(params: PageParams) {
return { return {
title: params ? `${ params.hash } - ${ networkTitle }` : '', title: params ? `${ params.hash } - ${ networkTitle }` : '',
description: params ? description: params ?
`${ params.hash }, balances, and analytics on the on the ${ networkTitle }` : `${ params.hash }, balances and analytics on the ${ networkTitle }` :
'', '',
}; };
} }
...@@ -10,6 +10,7 @@ const cspPolicy = getCspPolicy(); ...@@ -10,6 +10,7 @@ const cspPolicy = getCspPolicy();
export function middleware(req: NextRequest) { export function middleware(req: NextRequest) {
const isPageRequest = req.headers.get('accept')?.includes('text/html'); const isPageRequest = req.headers.get('accept')?.includes('text/html');
const start = Date.now();
if (!isPageRequest) { if (!isPageRequest) {
return; return;
...@@ -25,8 +26,11 @@ export function middleware(req: NextRequest) { ...@@ -25,8 +26,11 @@ export function middleware(req: NextRequest) {
return NextResponse.redirect(authUrl); return NextResponse.redirect(authUrl);
} }
const end = Date.now();
const res = NextResponse.next(); const res = NextResponse.next();
res.headers.append('Content-Security-Policy-Report-Only', cspPolicy); res.headers.append('Content-Security-Policy-Report-Only', cspPolicy);
res.headers.append('Server-Timing', `middleware;dur=${ end - start }`);
return res; return res;
} }
......
...@@ -21,7 +21,7 @@ export const baseResponse: AddressCoinBalanceHistoryResponse = { ...@@ -21,7 +21,7 @@ export const baseResponse: AddressCoinBalanceHistoryResponse = {
block_number: 30367234, block_number: 30367234,
block_timestamp: '2022-10-01T17:55:20Z', block_timestamp: '2022-10-01T17:55:20Z',
delta: '1933020674364000', delta: '1933020674364000',
transaction_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', transaction_hash: null,
value: '10143933697708939226', value: '10143933697708939226',
}, },
{ {
......
import { ColorModeScript } from '@chakra-ui/react'; import { ColorModeScript } from '@chakra-ui/react';
import type { DocumentContext } from 'next/document';
import Document, { Html, Head, Main, NextScript } from 'next/document'; import Document, { Html, Head, Main, NextScript } from 'next/document';
import React from 'react'; import React from 'react';
import * as serverTiming from 'lib/next/serverTiming';
import theme from 'theme'; import theme from 'theme';
class MyDocument extends Document { class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const originalRenderPage = ctx.renderPage;
ctx.renderPage = async() => {
const start = Date.now();
const result = await originalRenderPage();
const end = Date.now();
serverTiming.appendValue(ctx.res, 'renderPage', end - start);
return result;
};
const initialProps = await Document.getInitialProps(ctx);
return initialProps;
}
render() { render() {
return ( return (
<Html lang="en"> <Html lang="en">
......
...@@ -10,6 +10,7 @@ import link from 'lib/link/link'; ...@@ -10,6 +10,7 @@ import link from 'lib/link/link';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import type { Props } from 'lib/next/getServerSideProps'; import type { Props } from 'lib/next/getServerSideProps';
import { getServerSideProps as getServerSidePropsBase } from 'lib/next/getServerSideProps'; import { getServerSideProps as getServerSidePropsBase } from 'lib/next/getServerSideProps';
import * as serverTiming from 'lib/next/serverTiming';
import SearchResults from 'ui/pages/SearchResults'; import SearchResults from 'ui/pages/SearchResults';
const SearchResultsPage: NextPage = () => { const SearchResultsPage: NextPage = () => {
...@@ -27,6 +28,8 @@ const SearchResultsPage: NextPage = () => { ...@@ -27,6 +28,8 @@ const SearchResultsPage: NextPage = () => {
export default SearchResultsPage; export default SearchResultsPage;
export const getServerSideProps: GetServerSideProps<Props> = async({ req, res, resolvedUrl, query }) => { export const getServerSideProps: GetServerSideProps<Props> = async({ req, res, resolvedUrl, query }) => {
const start = Date.now();
try { try {
const q = String(query.q); const q = String(query.q);
const url = buildUrlNode('search_check_redirect', undefined, { q }); const url = buildUrlNode('search_check_redirect', undefined, { q });
...@@ -63,5 +66,9 @@ export const getServerSideProps: GetServerSideProps<Props> = async({ req, res, r ...@@ -63,5 +66,9 @@ export const getServerSideProps: GetServerSideProps<Props> = async({ req, res, r
}; };
} catch (error) {} } catch (error) {}
const end = Date.now();
serverTiming.appendValue(res, 'query.search.check-redirect', end - start);
return getServerSidePropsBase({ req, res, resolvedUrl, query }); return getServerSidePropsBase({ req, res, resolvedUrl, query });
}; };
...@@ -6,7 +6,7 @@ export default async function insertAdPlaceholder(page: Page) { ...@@ -6,7 +6,7 @@ export default async function insertAdPlaceholder(page: Page) {
const adContainer = document.getElementById('adBanner'); const adContainer = document.getElementById('adBanner');
const adReplacer = document.createElement('div'); const adReplacer = document.createElement('div');
adReplacer.style.width = '200px'; adReplacer.style.width = '200px';
adReplacer.style.height = '100px'; adReplacer.style.height = '100%';
adReplacer.style.background = '#f00'; adReplacer.style.background = '#f00';
adContainer?.replaceChildren(adReplacer); adContainer?.replaceChildren(adReplacer);
}); });
......
...@@ -15,6 +15,10 @@ const semanticTokens = { ...@@ -15,6 +15,10 @@ const semanticTokens = {
link_hovered: { link_hovered: {
'default': 'blue.400', 'default': 'blue.400',
}, },
error: {
'default': 'red.400',
_dark: 'red.300',
},
}, },
}; };
......
...@@ -75,10 +75,10 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => { ...@@ -75,10 +75,10 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
if (query.isLoading) { if (query.isLoading) {
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<SkeletonTable columns={ [ '17%', '17%', '16%', '25%', '25%' ] }/> <SkeletonTable columns={ [ '17%', '17%', '16%', '25%', '25%' ] }/>
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
<SkeletonList/> <SkeletonList/>
</Show> </Show>
</> </>
...@@ -95,7 +95,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => { ...@@ -95,7 +95,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ 80 }> <Thead top={ 80 }>
<Tr> <Tr>
...@@ -113,7 +113,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => { ...@@ -113,7 +113,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
</Tbody> </Tbody>
</Table> </Table>
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
{ query.data.items.map((item) => ( { query.data.items.map((item) => (
<AddressBlocksValidatedListItem key={ item.height } { ...item } page={ query.pagination.page }/> <AddressBlocksValidatedListItem key={ item.height } { ...item } page={ query.pagination.page }/>
)) } )) }
......
...@@ -18,11 +18,13 @@ const AddressCoinBalance = () => { ...@@ -18,11 +18,13 @@ const AddressCoinBalance = () => {
const [ socketAlert, setSocketAlert ] = React.useState(false); const [ socketAlert, setSocketAlert ] = React.useState(false);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const scrollRef = React.useRef<HTMLDivElement>(null);
const addressHash = String(router.query?.id); const addressHash = String(router.query?.id);
const coinBalanceQuery = useQueryWithPages({ const coinBalanceQuery = useQueryWithPages({
resourceName: 'address_coin_balance', resourceName: 'address_coin_balance',
pathParams: { id: addressHash }, pathParams: { id: addressHash },
scrollRef,
}); });
const handleSocketError = React.useCallback(() => { const handleSocketError = React.useCallback(() => {
...@@ -65,6 +67,7 @@ const AddressCoinBalance = () => { ...@@ -65,6 +67,7 @@ const AddressCoinBalance = () => {
<> <>
{ socketAlert && <SocketAlert mb={ 6 }/> } { socketAlert && <SocketAlert mb={ 6 }/> }
<AddressCoinBalanceChart addressHash={ addressHash }/> <AddressCoinBalanceChart addressHash={ addressHash }/>
<div ref={ scrollRef }></div>
<AddressCoinBalanceHistory query={ coinBalanceQuery }/> <AddressCoinBalanceHistory query={ coinBalanceQuery }/>
</> </>
); );
......
...@@ -5,6 +5,7 @@ import React from 'react'; ...@@ -5,6 +5,7 @@ import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
import type { ResourceError } from 'lib/api/resources';
import * as addressMock from 'mocks/address/address'; import * as addressMock from 'mocks/address/address';
import * as countersMock from 'mocks/address/counters'; import * as countersMock from 'mocks/address/counters';
import * as tokenBalanceMock from 'mocks/address/tokenBalance'; import * as tokenBalanceMock from 'mocks/address/tokenBalance';
...@@ -36,7 +37,7 @@ test('contract +@mobile', async({ mount, page }) => { ...@@ -36,7 +37,7 @@ test('contract +@mobile', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<AddressDetails addressQuery={{ data: addressMock.contract } as UseQueryResult<Address, unknown>}/> <AddressDetails addressQuery={{ data: addressMock.contract } as UseQueryResult<Address, ResourceError>}/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -65,7 +66,7 @@ test('token', async({ mount, page }) => { ...@@ -65,7 +66,7 @@ test('token', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<MockAddressPage> <MockAddressPage>
<AddressDetails addressQuery={{ data: addressMock.token } as UseQueryResult<Address, unknown>}/> <AddressDetails addressQuery={{ data: addressMock.token } as UseQueryResult<Address, ResourceError>}/>
</MockAddressPage> </MockAddressPage>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
...@@ -86,7 +87,7 @@ test('validator +@mobile', async({ mount, page }) => { ...@@ -86,7 +87,7 @@ test('validator +@mobile', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<AddressDetails addressQuery={{ data: addressMock.validator } as UseQueryResult<Address, unknown>}/> <AddressDetails addressQuery={{ data: addressMock.validator } as UseQueryResult<Address, ResourceError>}/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
......
This diff is collapsed.
...@@ -48,8 +48,8 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE ...@@ -48,8 +48,8 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
if (isLoading) { if (isLoading) {
return ( return (
<> <>
<Show below="lg"><AddressIntTxsSkeletonMobile/></Show> <Show below="lg" ssr={ false }><AddressIntTxsSkeletonMobile/></Show>
<Hide below="lg"><AddressIntTxsSkeletonDesktop/></Hide> <Hide below="lg" ssr={ false }><AddressIntTxsSkeletonDesktop/></Hide>
</> </>
); );
} }
......
...@@ -166,10 +166,10 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -166,10 +166,10 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
if (isLoading) { if (isLoading) {
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<SkeletonTable columns={ [ '44px', '185px', '160px', '25%', '25%', '25%', '25%' ] }/> <SkeletonTable columns={ [ '44px', '185px', '160px', '25%', '25%', '25%', '25%' ] }/>
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
<SkeletonList/> <SkeletonList/>
</Show> </Show>
</> </>
...@@ -191,7 +191,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -191,7 +191,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
const items = data.items.reduce(flattenTotal, []); const items = data.items.reduce(flattenTotal, []);
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<TokenTransferTable <TokenTransferTable
data={ items } data={ items }
baseAddress={ currentAddress } baseAddress={ currentAddress }
...@@ -203,7 +203,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD ...@@ -203,7 +203,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
socketInfoNum={ newItemsCount } socketInfoNum={ newItemsCount }
/> />
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
{ pagination.page === 1 && !tokenFilter && ( { pagination.page === 1 && !tokenFilter && (
<SocketNewItemsNotice <SocketNewItemsNotice
url={ window.location.href } url={ window.location.href }
......
...@@ -27,6 +27,7 @@ const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive }: Props) => ...@@ -27,6 +27,7 @@ const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive }: Props) =>
<FilterButton <FilterButton
isActive={ isOpen || isActive } isActive={ isOpen || isActive }
onClick={ onToggle } onClick={ onToggle }
as="div"
/> />
</MenuButton> </MenuButton>
<MenuList zIndex={ 2 }> <MenuList zIndex={ 2 }>
......
import { Link, Text, Flex } from '@chakra-ui/react'; import { Text, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -8,6 +8,7 @@ import appConfig from 'configs/app/config'; ...@@ -8,6 +8,7 @@ import appConfig from 'configs/app/config';
import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link'; import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
...@@ -23,7 +24,7 @@ const AddressBlocksValidatedListItem = (props: Props) => { ...@@ -23,7 +24,7 @@ const AddressBlocksValidatedListItem = (props: Props) => {
return ( return (
<ListItemMobile rowGap={ 2 } isAnimated> <ListItemMobile rowGap={ 2 } isAnimated>
<Flex justifyContent="space-between" w="100%"> <Flex justifyContent="space-between" w="100%">
<Link href={ blockUrl } fontWeight="700">{ props.height }</Link> <LinkInternal href={ blockUrl } fontWeight="700">{ props.height }</LinkInternal>
<Text variant="secondary">{ timeAgo }</Text> <Text variant="secondary">{ timeAgo }</Text>
</Flex> </Flex>
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
......
import { Link, Td, Tr, Text, Box, Flex } from '@chakra-ui/react'; import { Td, Tr, Text, Box, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -7,6 +7,7 @@ import type { Block } from 'types/api/block'; ...@@ -7,6 +7,7 @@ import type { Block } from 'types/api/block';
import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link'; import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
type Props = Block & { type Props = Block & {
...@@ -21,7 +22,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => { ...@@ -21,7 +22,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => {
return ( return (
<Tr> <Tr>
<Td> <Td>
<Link href={ blockUrl } fontWeight="700">{ props.height }</Link> <LinkInternal href={ blockUrl } fontWeight="700">{ props.height }</LinkInternal>
</Td> </Td>
<Td> <Td>
<Text variant="secondary">{ timeAgo }</Text> <Text variant="secondary">{ timeAgo }</Text>
......
...@@ -25,6 +25,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -25,6 +25,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
title="Balances" title="Balances"
items={ items } items={ items }
isLoading={ isLoading } isLoading={ isLoading }
h="250px"
/> />
); );
}; };
......
...@@ -29,10 +29,10 @@ const AddressCoinBalanceHistory = ({ query }: Props) => { ...@@ -29,10 +29,10 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
if (query.isLoading) { if (query.isLoading) {
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<SkeletonTable columns={ [ '25%', '25%', '25%', '25%', '120px' ] }/> <SkeletonTable columns={ [ '25%', '25%', '25%', '25%', '120px' ] }/>
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
<SkeletonList/> <SkeletonList/>
</Show> </Show>
</> </>
...@@ -45,7 +45,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => { ...@@ -45,7 +45,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ 80 }> <Thead top={ 80 }>
<Tr> <Tr>
...@@ -63,7 +63,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => { ...@@ -63,7 +63,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
</Tbody> </Tbody>
</Table> </Table>
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
{ query.data.items.map((item) => ( { query.data.items.map((item) => (
<AddressCoinBalanceListItem key={ item.block_number } { ...item } page={ query.pagination.page }/> <AddressCoinBalanceListItem key={ item.block_number } { ...item } page={ query.pagination.page }/>
)) } )) }
......
import { Link, Text, Stat, StatHelpText, StatArrow, Flex } from '@chakra-ui/react'; import { Text, Stat, StatHelpText, StatArrow, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -10,6 +10,7 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; ...@@ -10,6 +10,7 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link'; import link from 'lib/link/link';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
type Props = AddressCoinBalanceHistoryItem & { type Props = AddressCoinBalanceHistoryItem & {
...@@ -25,19 +26,19 @@ const AddressCoinBalanceListItem = (props: Props) => { ...@@ -25,19 +26,19 @@ const AddressCoinBalanceListItem = (props: Props) => {
return ( return (
<ListItemMobile rowGap={ 2 } isAnimated> <ListItemMobile rowGap={ 2 } isAnimated>
<Flex justifyContent="space-between" w="100%"> <Flex justifyContent="space-between" w="100%">
<Text fontWeight={ 600 }>{ BigNumber(props.value).div(WEI).toFixed(8) } { appConfig.network.currency.symbol }</Text> <Text fontWeight={ 600 }>{ BigNumber(props.value).div(WEI).dp(8).toFormat() } { appConfig.network.currency.symbol }</Text>
<Stat flexGrow="0"> <Stat flexGrow="0">
<StatHelpText display="flex" mb={ 0 } alignItems="center"> <StatHelpText display="flex" mb={ 0 } alignItems="center">
<StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' }/> <StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' }/>
<Text as="span" color={ isPositiveDelta ? 'green.500' : 'red.500' } fontWeight={ 600 }> <Text as="span" color={ isPositiveDelta ? 'green.500' : 'red.500' } fontWeight={ 600 }>
{ deltaBn.toFormat() } { deltaBn.dp(8).toFormat() }
</Text> </Text>
</StatHelpText> </StatHelpText>
</Stat> </Stat>
</Flex> </Flex>
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
<Text fontWeight={ 500 } flexShrink={ 0 }>Block</Text> <Text fontWeight={ 500 } flexShrink={ 0 }>Block</Text>
<Link href={ blockUrl } fontWeight="700">{ props.block_number }</Link> <LinkInternal href={ blockUrl } fontWeight="700">{ props.block_number }</LinkInternal>
</Flex> </Flex>
{ props.transaction_hash && ( { props.transaction_hash && (
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
......
import { Link, Td, Tr, Text, Stat, StatHelpText, StatArrow } from '@chakra-ui/react'; import { Td, Tr, Text, Stat, StatHelpText, StatArrow } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -9,6 +9,7 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; ...@@ -9,6 +9,7 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link'; import link from 'lib/link/link';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = AddressCoinBalanceHistoryItem & { type Props = AddressCoinBalanceHistoryItem & {
page: number; page: number;
...@@ -23,12 +24,12 @@ const AddressCoinBalanceTableItem = (props: Props) => { ...@@ -23,12 +24,12 @@ const AddressCoinBalanceTableItem = (props: Props) => {
return ( return (
<Tr> <Tr>
<Td> <Td>
<Link href={ blockUrl } fontWeight="700">{ props.block_number }</Link> <LinkInternal href={ blockUrl } fontWeight="700">{ props.block_number }</LinkInternal>
</Td> </Td>
<Td> <Td>
{ props.transaction_hash ? { props.transaction_hash ?
( (
<Address maxW="150px" fontWeight="700"> <Address w="150px" fontWeight="700">
<AddressLink hash={ props.transaction_hash } type="transaction"/> <AddressLink hash={ props.transaction_hash } type="transaction"/>
</Address> </Address>
) : ) :
...@@ -39,14 +40,14 @@ const AddressCoinBalanceTableItem = (props: Props) => { ...@@ -39,14 +40,14 @@ const AddressCoinBalanceTableItem = (props: Props) => {
<Text variant="secondary">{ timeAgo }</Text> <Text variant="secondary">{ timeAgo }</Text>
</Td> </Td>
<Td isNumeric pr={ 1 }> <Td isNumeric pr={ 1 }>
<Text>{ BigNumber(props.value).div(WEI).toFixed(8) }</Text> <Text>{ BigNumber(props.value).div(WEI).dp(8).toFormat() }</Text>
</Td> </Td>
<Td isNumeric display="flex" justifyContent="end"> <Td isNumeric display="flex" justifyContent="end">
<Stat flexGrow="0"> <Stat flexGrow="0">
<StatHelpText display="flex" mb={ 0 } alignItems="center"> <StatHelpText display="flex" mb={ 0 } alignItems="center">
<StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' }/> <StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' }/>
<Text as="span" color={ isPositiveDelta ? 'green.500' : 'red.500' } fontWeight={ 600 }> <Text as="span" color={ isPositiveDelta ? 'green.500' : 'red.500' } fontWeight={ 600 }>
{ deltaBn.toFormat() } { deltaBn.dp(8).toFormat() }
</Text> </Text>
</StatHelpText> </StatHelpText>
</Stat> </Stat>
......
...@@ -8,7 +8,8 @@ import Address from 'ui/shared/address/Address'; ...@@ -8,7 +8,8 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import ExternalLink from 'ui/shared/ExternalLink'; import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import RawDataSnippet from 'ui/shared/RawDataSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet';
import ContractSourceCode from './ContractSourceCode'; import ContractSourceCode from './ContractSourceCode';
...@@ -72,7 +73,9 @@ const ContractCode = () => { ...@@ -72,7 +73,9 @@ const ContractCode = () => {
const decoded = data.decoded_constructor_args const decoded = data.decoded_constructor_args
.map(([ value, { name, type } ], index) => { .map(([ value, { name, type } ], index) => {
const valueEl = type === 'address' ? <Link href={ link('address_index', { id: value }) }>{ value }</Link> : <span>{ value }</span>; const valueEl = type === 'address' ?
<LinkInternal href={ link('address_index', { id: value }) }>{ value }</LinkInternal> :
<span>{ value }</span>;
return ( return (
<Box key={ index }> <Box key={ index }>
<span>Arg [{ index }] { name || '' } ({ type }): </span> <span>Arg [{ index }] { name || '' } ({ type }): </span>
...@@ -98,7 +101,7 @@ const ContractCode = () => { ...@@ -98,7 +101,7 @@ const ContractCode = () => {
return data.external_libraries.map((item) => ( return data.external_libraries.map((item) => (
<Box key={ item.address_hash }> <Box key={ item.address_hash }>
<chakra.span fontWeight={ 500 }>{ item.name }: </chakra.span> <chakra.span fontWeight={ 500 }>{ item.name }: </chakra.span>
<Link href={ link('address_index', { id: item.address_hash }, { tab: 'contract' }) }>{ item.address_hash }</Link> <LinkInternal href={ link('address_index', { id: item.address_hash }, { tab: 'contract' }) }>{ item.address_hash }</LinkInternal>
</Box> </Box>
)); ));
})(); })();
...@@ -110,7 +113,7 @@ const ContractCode = () => { ...@@ -110,7 +113,7 @@ const ContractCode = () => {
{ data.is_verified_via_sourcify && ( { data.is_verified_via_sourcify && (
<Alert status="warning" whiteSpace="pre-wrap" flexWrap="wrap"> <Alert status="warning" whiteSpace="pre-wrap" flexWrap="wrap">
<span>This contract has been { data.is_partially_verified ? 'partially ' : '' }verified via Sourcify. </span> <span>This contract has been { data.is_partially_verified ? 'partially ' : '' }verified via Sourcify. </span>
{ data.sourcify_repo_url && <ExternalLink href={ data.sourcify_repo_url } title="View contract in Sourcify repository" fontSize="md"/> } { data.sourcify_repo_url && <LinkExternal href={ data.sourcify_repo_url } title="View contract in Sourcify repository" fontSize="md"/> }
</Alert> </Alert>
) } ) }
{ data.is_changed_bytecode && ( { data.is_changed_bytecode && (
...@@ -126,7 +129,7 @@ const ContractCode = () => { ...@@ -126,7 +129,7 @@ const ContractCode = () => {
<AddressLink type="address" hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/> <AddressLink type="address" hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/>
</Address> </Address>
<chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span> <chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span>
<Link href={ link('address_contract_verification', { id: data.verified_twin_address_hash }) }>Verify & Publish</Link> <LinkInternal href={ link('address_contract_verification', { id: data.verified_twin_address_hash }) }>Verify & Publish</LinkInternal>
<span> page</span> <span> page</span>
</Alert> </Alert>
) } ) }
......
import { Box, chakra, Link, Spinner } from '@chakra-ui/react'; import { Box, chakra, Spinner } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { ContractMethodWriteResult } from './types'; import type { ContractMethodWriteResult } from './types';
import link from 'lib/link/link'; import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
result: ContractMethodWriteResult; result: ContractMethodWriteResult;
...@@ -30,7 +31,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { ...@@ -30,7 +31,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => {
const isErrorResult = 'message' in result; const isErrorResult = 'message' in result;
const txLink = ( const txLink = (
<Link href={ link('tx', { id: txHash }) }>View transaction details</Link> <LinkInternal href={ link('tx', { id: txHash }) }>View transaction details</LinkInternal>
); );
const content = (() => { const content = (() => {
...@@ -85,7 +86,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { ...@@ -85,7 +86,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => {
alignItems="center" alignItems="center"
whiteSpace="pre-wrap" whiteSpace="pre-wrap"
wordBreak="break-all" wordBreak="break-all"
color={ txInfo.status === 'error' || isErrorResult ? 'red.600' : undefined } color={ txInfo.status === 'error' || isErrorResult ? 'error' : undefined }
> >
{ content } { content }
</Box> </Box>
......
...@@ -13,7 +13,7 @@ import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; ...@@ -13,7 +13,7 @@ import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
interface Props { interface Props {
data: Address; data: Pick<Address, 'block_number_balance_updated_at' | 'coin_balance' | 'hash' | 'exchange_rate'>;
} }
const AddressBalance = ({ data }: Props) => { const AddressBalance = ({ data }: Props) => {
...@@ -63,10 +63,6 @@ const AddressBalance = ({ data }: Props) => { ...@@ -63,10 +63,6 @@ const AddressBalance = ({ data }: Props) => {
handler: handleNewCoinBalanceMessage, handler: handleNewCoinBalanceMessage,
}); });
if (!data.coin_balance) {
return null;
}
return ( return (
<DetailsInfoItem <DetailsInfoItem
title="Balance" title="Balance"
...@@ -82,7 +78,7 @@ const AddressBalance = ({ data }: Props) => { ...@@ -82,7 +78,7 @@ const AddressBalance = ({ data }: Props) => {
fontSize="sm" fontSize="sm"
/> />
<CurrencyValue <CurrencyValue
value={ data.coin_balance } value={ data.coin_balance || '0' }
exchangeRate={ data.exchange_rate } exchangeRate={ data.exchange_rate }
decimals={ String(appConfig.network.currency.decimals) } decimals={ String(appConfig.network.currency.decimals) }
currency={ appConfig.network.currency.symbol } currency={ appConfig.network.currency.symbol }
......
import { Link, 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 BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import NextLink from 'next/link';
import React from 'react'; import React from 'react';
import type { AddressCounters } from 'types/api/address'; import type { AddressCounters } from 'types/api/address';
import link from 'lib/link/link'; import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
prop: keyof AddressCounters; prop: keyof AddressCounters;
...@@ -29,7 +29,7 @@ const AddressCounterItem = ({ prop, query, address, onClick }: Props) => { ...@@ -29,7 +29,7 @@ const AddressCounterItem = ({ prop, query, address, onClick }: Props) => {
const data = query.data?.[prop]; const data = query.data?.[prop];
if (query.isError || data === null || data === undefined) { if (query.isError || data === null || data === undefined) {
return <span>no data</span>; return <span>0</span>;
} }
switch (prop) { switch (prop) {
...@@ -42,11 +42,9 @@ const AddressCounterItem = ({ prop, query, address, onClick }: Props) => { ...@@ -42,11 +42,9 @@ const AddressCounterItem = ({ prop, query, address, onClick }: Props) => {
return <span>0</span>; return <span>0</span>;
} }
return ( return (
<NextLink href={ link('address_index', { id: address }, { tab: PROP_TO_TAB[prop] }) } passHref> <LinkInternal href={ link('address_index', { id: address }, { tab: PROP_TO_TAB[prop] }) } onClick={ onClick }>
<Link onClick={ onClick }>
{ Number(data).toLocaleString() } { Number(data).toLocaleString() }
</Link> </LinkInternal>
</NextLink>
); );
} }
} }
......
import { Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
import link from 'lib/link/link'; import link from 'lib/link/link';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
data: Address; data: Pick<Address, 'name' | 'token' | 'is_contract'>;
} }
const AddressNameInfo = ({ data }: Props) => { const AddressNameInfo = ({ data }: Props) => {
...@@ -17,9 +17,9 @@ const AddressNameInfo = ({ data }: Props) => { ...@@ -17,9 +17,9 @@ const AddressNameInfo = ({ data }: Props) => {
title="Token name" title="Token name"
hint="Token name and symbol" hint="Token name and symbol"
> >
<Link href={ link('token_index', { hash: data.token.address }) }> <LinkInternal href={ link('token_index', { hash: data.token.address }) }>
{ data.token.name } ({ data.token.symbol }) { data.token.name } ({ data.token.symbol })
</Link> </LinkInternal>
</DetailsInfoItem> </DetailsInfoItem>
); );
} }
......
import { Flex, Tag, Icon, Box, HStack, Text, Link } from '@chakra-ui/react'; import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -12,6 +12,7 @@ import Address from 'ui/shared/address/Address'; ...@@ -12,6 +12,7 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import InOutTag from 'ui/shared/InOutTag'; import InOutTag from 'ui/shared/InOutTag';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
import TxStatus from 'ui/shared/TxStatus'; import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
...@@ -49,7 +50,7 @@ const TxInternalsListItem = ({ ...@@ -49,7 +50,7 @@ const TxInternalsListItem = ({
</Flex> </Flex>
<HStack spacing={ 1 }> <HStack spacing={ 1 }>
<Text fontSize="sm" fontWeight={ 500 }>Block</Text> <Text fontSize="sm" fontWeight={ 500 }>Block</Text>
<Link href={ link('block', { id: block.toString() }) }>{ block }</Link> <LinkInternal href={ link('block', { id: block.toString() }) }>{ block }</LinkInternal>
</HStack> </HStack>
<Box w="100%" display="flex" columnGap={ 3 }> <Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
......
import { Tr, Td, Tag, Icon, Box, Flex, Text, Link } from '@chakra-ui/react'; import { Tr, Td, Tag, Icon, Box, Flex, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -12,6 +12,7 @@ import Address from 'ui/shared/address/Address'; ...@@ -12,6 +12,7 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import InOutTag from 'ui/shared/InOutTag'; import InOutTag from 'ui/shared/InOutTag';
import LinkInternal from 'ui/shared/LinkInternal';
import TxStatus from 'ui/shared/TxStatus'; import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
...@@ -57,7 +58,7 @@ const AddressIntTxsTableItem = ({ ...@@ -57,7 +58,7 @@ const AddressIntTxsTableItem = ({
</Flex> </Flex>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Link href={ link('block', { id: block.toString() }) }>{ block }</Link> <LinkInternal href={ link('block', { id: block.toString() }) }>{ block }</LinkInternal>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
......
...@@ -17,7 +17,11 @@ import useSocketMessage from 'lib/socket/useSocketMessage'; ...@@ -17,7 +17,11 @@ import useSocketMessage from 'lib/socket/useSocketMessage';
import TokenSelectDesktop from './TokenSelectDesktop'; import TokenSelectDesktop from './TokenSelectDesktop';
import TokenSelectMobile from './TokenSelectMobile'; import TokenSelectMobile from './TokenSelectMobile';
const TokenSelect = () => { interface Props {
onClick?: () => void;
}
const TokenSelect = ({ onClick }: Props) => {
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
...@@ -88,6 +92,7 @@ const TokenSelect = () => { ...@@ -88,6 +92,7 @@ const TokenSelect = () => {
pr="6px" pr="6px"
icon={ <Icon as={ walletIcon } boxSize={ 5 }/> } icon={ <Icon as={ walletIcon } boxSize={ 5 }/> }
as="a" as="a"
onClick={ onClick }
/> />
</NextLink> </NextLink>
</Box> </Box>
......
...@@ -33,7 +33,7 @@ type Inputs = { ...@@ -33,7 +33,7 @@ type Inputs = {
const NAME_MAX_LENGTH = 255; const NAME_MAX_LENGTH = 255;
const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isDirty }, setError } = useForm<Inputs>({
mode: 'onTouched', mode: 'onTouched',
defaultValues: { defaultValues: {
token: data?.api_key || '', token: data?.api_key || '',
...@@ -147,7 +147,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -147,7 +147,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Button <Button
size="lg" size="lg"
type="submit" type="submit"
disabled={ !isValid || !isDirty } disabled={ !isDirty }
isLoading={ mutation.isLoading } isLoading={ mutation.isLoading }
> >
{ data ? 'Save' : 'Generate API key' } { data ? 'Save' : 'Generate API key' }
......
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react'; import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import NextLink from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
...@@ -23,6 +22,7 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert'; ...@@ -23,6 +22,7 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
import PrevNext from 'ui/shared/PrevNext'; import PrevNext from 'ui/shared/PrevNext';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
...@@ -116,11 +116,9 @@ const BlockDetails = () => { ...@@ -116,11 +116,9 @@ const BlockDetails = () => {
title="Transactions" title="Transactions"
hint="The number of transactions in the block." hint="The number of transactions in the block."
> >
<NextLink href={ link('block', { id: router.query.id }, { tab: 'txs' }) } passHref> <LinkInternal href={ link('block', { id: router.query.id }, { tab: 'txs' }) }>
<Link>
{ data.tx_count } transactions { data.tx_count } transactions
</Link> </LinkInternal>
</NextLink>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title={ appConfig.network.verificationType === 'validation' ? 'Validated by' : 'Mined by' } title={ appConfig.network.verificationType === 'validation' ? 'Validated by' : 'Mined by' }
......
...@@ -46,7 +46,13 @@ const BlocksContent = ({ type, query }: Props) => { ...@@ -46,7 +46,13 @@ const BlocksContent = ({ type, query }: Props) => {
next_page_params: null, next_page_params: null,
}; };
} }
return shouldAddToList ? { ...prevData, items: [ payload.block, ...prevData.items ] } : prevData;
if (!shouldAddToList || prevData.items.some((block => block.height === payload.block.height))) {
return prevData;
}
const newItems = [ payload.block, ...prevData.items ].sort((b1, b2) => b2.height - b1.height);
return { ...prevData, items: newItems };
}); });
}, [ queryClient, type ]); }, [ queryClient, type ]);
......
import { Flex, Link, Spinner, Text, Box, Icon } from '@chakra-ui/react'; import { Flex, Spinner, Text, Box, Icon } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
...@@ -14,6 +14,7 @@ import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; ...@@ -14,6 +14,7 @@ import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlockTimestamp from 'ui/blocks/BlockTimestamp'; import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
...@@ -33,12 +34,12 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => { ...@@ -33,12 +34,12 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<Flex justifyContent="space-between" w="100%"> <Flex justifyContent="space-between" w="100%">
<Flex columnGap={ 2 } alignItems="center"> <Flex columnGap={ 2 } alignItems="center">
{ isPending && <Spinner size="sm"/> } { isPending && <Spinner size="sm"/> }
<Link <LinkInternal
fontWeight={ 600 } fontWeight={ 600 }
href={ link('block', { id: String(data.height) }) } href={ link('block', { id: String(data.height) }) }
> >
{ data.height } { data.height }
</Link> </LinkInternal>
</Flex> </Flex>
<BlockTimestamp ts={ data.timestamp } isEnabled={ enableTimeIncrement }/> <BlockTimestamp ts={ data.timestamp } isEnabled={ enableTimeIncrement }/>
</Flex> </Flex>
......
import { Tr, Td, Link, Flex, Box, Icon, Tooltip, Spinner, useColorModeValue } from '@chakra-ui/react'; import { Tr, Td, Flex, Box, Icon, Tooltip, Spinner, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React from 'react'; import React from 'react';
...@@ -12,6 +12,7 @@ import link from 'lib/link/link'; ...@@ -12,6 +12,7 @@ import link from 'lib/link/link';
import BlockTimestamp from 'ui/blocks/BlockTimestamp'; import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import LinkInternal from 'ui/shared/LinkInternal';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
interface Props { interface Props {
...@@ -38,12 +39,12 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => { ...@@ -38,12 +39,12 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<Flex columnGap={ 2 } alignItems="center" mb={ 2 }> <Flex columnGap={ 2 } alignItems="center" mb={ 2 }>
{ isPending && <Spinner size="sm" flexShrink={ 0 }/> } { isPending && <Spinner size="sm" flexShrink={ 0 }/> }
<Tooltip isDisabled={ data.type !== 'reorg' } label="Chain reorganizations"> <Tooltip isDisabled={ data.type !== 'reorg' } label="Chain reorganizations">
<Link <LinkInternal
fontWeight={ 600 } fontWeight={ 600 }
href={ link('block', { id: String(data.height) }) } href={ link('block', { id: String(data.height) }) }
> >
{ data.height } { data.height }
</Link> </LinkInternal>
</Tooltip> </Tooltip>
</Flex> </Flex>
<BlockTimestamp ts={ data.timestamp } isEnabled={ enableTimeIncrement }/> <BlockTimestamp ts={ data.timestamp } isEnabled={ enableTimeIncrement }/>
......
...@@ -6,6 +6,7 @@ import type { TimeChartData } from 'ui/shared/chart/types'; ...@@ -6,6 +6,7 @@ import type { TimeChartData } from 'ui/shared/chart/types';
import ethTokenTransferData from 'data/charts_eth_token_transfer.json'; import ethTokenTransferData from 'data/charts_eth_token_transfer.json';
import ethTxsData from 'data/charts_eth_txs.json'; import ethTxsData from 'data/charts_eth_txs.json';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useClientRect from 'lib/hooks/useClientRect';
import ChartArea from 'ui/shared/chart/ChartArea'; import ChartArea from 'ui/shared/chart/ChartArea';
import ChartAxis from 'ui/shared/chart/ChartAxis'; import ChartAxis from 'ui/shared/chart/ChartAxis';
import ChartGridLine from 'ui/shared/chart/ChartGridLine'; import ChartGridLine from 'ui/shared/chart/ChartGridLine';
...@@ -16,8 +17,8 @@ import ChartSelectionX from 'ui/shared/chart/ChartSelectionX'; ...@@ -16,8 +17,8 @@ import ChartSelectionX from 'ui/shared/chart/ChartSelectionX';
import ChartTooltip from 'ui/shared/chart/ChartTooltip'; import ChartTooltip from 'ui/shared/chart/ChartTooltip';
// import useBrushX from 'ui/shared/chart/useBrushX'; // import useBrushX from 'ui/shared/chart/useBrushX';
import useChartLegend from 'ui/shared/chart/useChartLegend'; import useChartLegend from 'ui/shared/chart/useChartLegend';
import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController'; import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import calculateInnerSize from 'ui/shared/chart/utils/calculateInnerSize';
const CHART_MARGIN = { bottom: 20, left: 65, right: 30, top: 10 }; const CHART_MARGIN = { bottom: 20, left: 65, right: 30, top: 10 };
const CHART_OFFSET = { const CHART_OFFSET = {
...@@ -27,10 +28,10 @@ const RANGE_DEFAULT_START_DATE = dayjs.min(dayjs(ethTokenTransferData[0].date), ...@@ -27,10 +28,10 @@ const RANGE_DEFAULT_START_DATE = dayjs.min(dayjs(ethTokenTransferData[0].date),
const RANGE_DEFAULT_LAST_DATE = dayjs.max(dayjs(ethTokenTransferData.at(-1)?.date), dayjs(ethTxsData.at(-1)?.date)).toDate(); const RANGE_DEFAULT_LAST_DATE = dayjs.max(dayjs(ethTokenTransferData.at(-1)?.date), dayjs(ethTxsData.at(-1)?.date)).toDate();
const EthereumChart = () => { const EthereumChart = () => {
const ref = React.useRef<SVGSVGElement>(null);
const overlayRef = React.useRef<SVGRectElement>(null); const overlayRef = React.useRef<SVGRectElement>(null);
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN, CHART_OFFSET); const [ rect, ref ] = useClientRect<SVGSVGElement>();
const { innerWidth, innerHeight } = calculateInnerSize(rect, CHART_MARGIN, CHART_OFFSET);
const [ range, setRange ] = React.useState<[ Date, Date ]>([ RANGE_DEFAULT_START_DATE, RANGE_DEFAULT_LAST_DATE ]); const [ range, setRange ] = React.useState<[ Date, Date ]>([ RANGE_DEFAULT_START_DATE, RANGE_DEFAULT_LAST_DATE ]);
const data: TimeChartData = [ const data: TimeChartData = [
...@@ -75,8 +76,8 @@ const EthereumChart = () => { ...@@ -75,8 +76,8 @@ const EthereumChart = () => {
return ( return (
<Box display="inline-block" position="relative" width="100%" height="100%"> <Box display="inline-block" position="relative" width="100%" height="100%">
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref }> <svg width="100%" height="calc(100% - 26px)" ref={ ref }>
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ width ? 1 : 0 }> <g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ rect ? 1 : 0 }>
{ /* BASE GRID LINE */ } { /* BASE GRID LINE */ }
<ChartGridLine <ChartGridLine
type="horizontal" type="horizontal"
......
...@@ -2,17 +2,19 @@ import { useToken } from '@chakra-ui/react'; ...@@ -2,17 +2,19 @@ import { useToken } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import ethTxsData from 'data/charts_eth_txs.json'; import ethTxsData from 'data/charts_eth_txs.json';
import useClientRect from 'lib/hooks/useClientRect';
import ChartLine from 'ui/shared/chart/ChartLine'; import ChartLine from 'ui/shared/chart/ChartLine';
import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController'; import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import calculateInnerSize from 'ui/shared/chart/utils/calculateInnerSize';
import { BlueLineGradient } from 'ui/shared/chart/utils/gradients'; import { BlueLineGradient } from 'ui/shared/chart/utils/gradients';
const CHART_MARGIN = { bottom: 0, left: 0, right: 0, top: 0 }; const CHART_MARGIN = { bottom: 0, left: 0, right: 0, top: 0 };
const DATA = ethTxsData.slice(-30).map((d) => ({ ...d, date: new Date(d.date) })); const DATA = ethTxsData.slice(-30).map((d) => ({ ...d, date: new Date(d.date) }));
const SplineChartExample = () => { const SplineChartExample = () => {
const ref = React.useRef<SVGSVGElement>(null); const [ rect, ref ] = useClientRect<SVGSVGElement>();
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN); const { innerWidth, innerHeight } = calculateInnerSize(rect, CHART_MARGIN);
const color = useToken('colors', 'blue.500'); const color = useToken('colors', 'blue.500');
const { xScale, yScale } = useTimeChartController({ const { xScale, yScale } = useTimeChartController({
data: [ { items: DATA, name: 'spline', color } ], data: [ { items: DATA, name: 'spline', color } ],
...@@ -21,7 +23,7 @@ const SplineChartExample = () => { ...@@ -21,7 +23,7 @@ const SplineChartExample = () => {
}); });
return ( return (
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref }> <svg width="100%" height="100%" ref={ ref }>
<defs> <defs>
<BlueLineGradient.defs/> <BlueLineGradient.defs/>
</defs> </defs>
......
...@@ -59,7 +59,6 @@ const ContractVerificationForm = () => { ...@@ -59,7 +59,6 @@ const ContractVerificationForm = () => {
size="lg" size="lg"
type="submit" type="submit"
mt={ 12 } mt={ 12 }
isDisabled={ !formState.isValid || !formState.isDirty }
isLoading={ formState.isSubmitting } isLoading={ formState.isSubmitting }
loadingText="Verify & publish" loadingText="Verify & publish"
> >
......
...@@ -39,6 +39,7 @@ const ContractVerificationFieldLibraryItem = ({ control, index, fieldsLength, on ...@@ -39,6 +39,7 @@ const ContractVerificationFieldLibraryItem = ({ control, index, fieldsLength, on
isInvalid={ Boolean(error?.name) } isInvalid={ Boolean(error?.name) }
isDisabled={ isDisabled } isDisabled={ isDisabled }
maxLength={ 255 } maxLength={ 255 }
autoComplete="off"
/> />
<InputPlaceholder text="Library name (.sol file)" error={ error?.name }/> <InputPlaceholder text="Library name (.sol file)" error={ error?.name }/>
</FormControl> </FormControl>
...@@ -53,6 +54,7 @@ const ContractVerificationFieldLibraryItem = ({ control, index, fieldsLength, on ...@@ -53,6 +54,7 @@ const ContractVerificationFieldLibraryItem = ({ control, index, fieldsLength, on
isInvalid={ Boolean(error?.address) } isInvalid={ Boolean(error?.address) }
isDisabled={ isDisabled } isDisabled={ isDisabled }
required required
autoComplete="off"
/> />
<InputPlaceholder text="Library address (0x...)" error={ error?.address }/> <InputPlaceholder text="Library address (0x...)" error={ error?.address }/>
</FormControl> </FormControl>
......
...@@ -27,6 +27,7 @@ const ContractVerificationFieldName = ({ hint }: Props) => { ...@@ -27,6 +27,7 @@ const ContractVerificationFieldName = ({ hint }: Props) => {
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
maxLength={ 255 } maxLength={ 255 }
isDisabled={ formState.isSubmitting } isDisabled={ formState.isSubmitting }
autoComplete="off"
/> />
<InputPlaceholder text="Contract name" error={ error }/> <InputPlaceholder text="Contract name" error={ error }/>
</FormControl> </FormControl>
......
...@@ -34,6 +34,7 @@ const ContractVerificationFieldOptimization = () => { ...@@ -34,6 +34,7 @@ const ContractVerificationFieldOptimization = () => {
{ ...field } { ...field }
required required
isDisabled={ formState.isSubmitting } isDisabled={ formState.isSubmitting }
autoComplete="off"
type="number" type="number"
/> />
<InputPlaceholder text="Optimization runs"/> <InputPlaceholder text="Optimization runs"/>
......
...@@ -21,6 +21,8 @@ interface Props { ...@@ -21,6 +21,8 @@ interface Props {
const ContractVerificationFieldSources = ({ accept, multiple, title, className, hint }: Props) => { const ContractVerificationFieldSources = ({ accept, multiple, title, className, hint }: Props) => {
const { setValue, getValues, control, formState } = useFormContext<FormFields>(); const { setValue, getValues, control, formState } = useFormContext<FormFields>();
const error = 'sources' in formState.errors ? formState.errors.sources : undefined;
const handleFileRemove = React.useCallback((index?: number) => { const handleFileRemove = React.useCallback((index?: number) => {
if (index === undefined) { if (index === undefined) {
return; return;
...@@ -57,8 +59,13 @@ const ContractVerificationFieldSources = ({ accept, multiple, title, className, ...@@ -57,8 +59,13 @@ const ContractVerificationFieldSources = ({ accept, multiple, title, className,
</Button> </Button>
</FileInput> </FileInput>
{ field.value && field.value.length > 0 && renderFiles(field.value) } { field.value && field.value.length > 0 && renderFiles(field.value) }
{ error && (
<Box fontSize="sm" mt={ 2 } color="error">
{ error.type === 'required' ? 'Field is required' : error.message }
</Box>
) }
</> </>
), [ accept, multiple, renderFiles ]); ), [ accept, error, multiple, renderFiles ]);
return ( return (
<> <>
......
...@@ -36,7 +36,7 @@ type Inputs = { ...@@ -36,7 +36,7 @@ type Inputs = {
const NAME_MAX_LENGTH = 255; const NAME_MAX_LENGTH = 255;
const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const { control, formState: { errors, isValid, isDirty }, handleSubmit, setError } = useForm<Inputs>({ const { control, formState: { errors, isDirty }, handleSubmit, setError } = useForm<Inputs>({
defaultValues: { defaultValues: {
contract_address_hash: data?.contract_address_hash || '', contract_address_hash: data?.contract_address_hash || '',
name: data?.name || '', name: data?.name || '',
...@@ -174,7 +174,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -174,7 +174,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Button <Button
size="lg" size="lg"
type="submit" type="submit"
disabled={ !isValid || !isDirty } disabled={ !isDirty }
isLoading={ mutation.isLoading } isLoading={ mutation.isLoading }
> >
{ data ? 'Save' : 'Create custom ABI' } { data ? 'Save' : 'Create custom ABI' }
......
import { Box, Heading, Flex, Link, Text, VStack, Skeleton } from '@chakra-ui/react'; import { Box, Heading, Flex, Text, VStack, Skeleton } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import React from 'react'; import React from 'react';
...@@ -12,6 +12,7 @@ import { nbsp } from 'lib/html-entities'; ...@@ -12,6 +12,7 @@ import { nbsp } from 'lib/html-entities';
import link from 'lib/link/link'; import link from 'lib/link/link';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
import LinkInternal from 'ui/shared/LinkInternal';
import LatestBlocksItem from './LatestBlocksItem'; import LatestBlocksItem from './LatestBlocksItem';
import LatestBlocksItemSkeleton from './LatestBlocksItemSkeleton'; import LatestBlocksItemSkeleton from './LatestBlocksItemSkeleton';
...@@ -32,7 +33,11 @@ const LatestBlocks = () => { ...@@ -32,7 +33,11 @@ const LatestBlocks = () => {
const newData = prevData ? [ ...prevData ] : []; const newData = prevData ? [ ...prevData ] : [];
return [ payload.block, ...newData ].slice(0, blocksMaxCount); if (newData.some((block => block.height === payload.block.height))) {
return newData;
}
return [ payload.block, ...newData ].sort((b1, b2) => b2.height - b1.height).slice(0, blocksMaxCount);
}); });
}, [ queryClient, blocksMaxCount ]); }, [ queryClient, blocksMaxCount ]);
...@@ -93,7 +98,7 @@ const LatestBlocks = () => { ...@@ -93,7 +98,7 @@ const LatestBlocks = () => {
</AnimatePresence> </AnimatePresence>
</VStack> </VStack>
<Flex justifyContent="center"> <Flex justifyContent="center">
<Link fontSize="sm" href={ link('blocks') }>View all blocks</Link> <LinkInternal fontSize="sm" href={ link('blocks') }>View all blocks</LinkInternal>
</Flex> </Flex>
</> </>
); );
......
...@@ -5,7 +5,6 @@ import { ...@@ -5,7 +5,6 @@ import {
GridItem, GridItem,
HStack, HStack,
Icon, Icon,
Link,
Text, Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
...@@ -18,6 +17,7 @@ import getBlockTotalReward from 'lib/block/getBlockTotalReward'; ...@@ -18,6 +17,7 @@ import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import link from 'lib/link/link'; import link from 'lib/link/link';
import BlockTimestamp from 'ui/blocks/BlockTimestamp'; import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = { type Props = {
block: Block; block: Block;
...@@ -43,13 +43,13 @@ const LatestBlocksItem = ({ block, h }: Props) => { ...@@ -43,13 +43,13 @@ const LatestBlocksItem = ({ block, h }: Props) => {
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
<HStack spacing={ 2 }> <HStack spacing={ 2 }>
<Icon as={ blockIcon } boxSize="30px" color="link"/> <Icon as={ blockIcon } boxSize="30px" color="link"/>
<Link <LinkInternal
href={ link('block', { id: String(block.height) }) } href={ link('block', { id: String(block.height) }) }
fontSize="xl" fontSize="xl"
fontWeight="500" fontWeight="500"
> >
{ block.height } { block.height }
</Link> </LinkInternal>
</HStack> </HStack>
<BlockTimestamp ts={ block.timestamp } isEnabled fontSize="sm"/> <BlockTimestamp ts={ block.timestamp } isEnabled fontSize="sm"/>
</Flex> </Flex>
......
import { Box, Heading, Flex, Link, Text, Skeleton } from '@chakra-ui/react'; import { Box, Heading, Flex, Text, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket'; import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import link from 'lib/link/link'; import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import LatestTxsItem from './LatestTxsItem'; import LatestTxsItem from './LatestTxsItem';
...@@ -36,12 +37,12 @@ const LatestTransactions = () => { ...@@ -36,12 +37,12 @@ const LatestTransactions = () => {
const txsUrl = link('txs'); const txsUrl = link('txs');
content = ( content = (
<> <>
<SocketNewItemsNotice borderBottomRadius={ 0 } url={ link('txs') } num={ num } alert={ socketAlert }/> <SocketNewItemsNotice borderBottomRadius={ 0 } url={ txsUrl } num={ num } alert={ socketAlert }/>
<Box mb={{ base: 3, lg: 4 }}> <Box mb={{ base: 3, lg: 4 }}>
{ data.slice(0, txsCount).map((tx => <LatestTxsItem key={ tx.hash } tx={ tx }/>)) } { data.slice(0, txsCount).map((tx => <LatestTxsItem key={ tx.hash } tx={ tx }/>)) }
</Box> </Box>
<Flex justifyContent="center"> <Flex justifyContent="center">
<Link fontSize="sm" href={ txsUrl }>View all transactions</Link> <LinkInternal fontSize="sm" href={ txsUrl }>View all transactions</LinkInternal>
</Flex> </Flex>
</> </>
); );
......
...@@ -3,12 +3,13 @@ import React from 'react'; ...@@ -3,12 +3,13 @@ import React from 'react';
import type { TimeChartData } from 'ui/shared/chart/types'; import type { TimeChartData } from 'ui/shared/chart/types';
import useClientRect from 'lib/hooks/useClientRect';
import ChartArea from 'ui/shared/chart/ChartArea'; import ChartArea from 'ui/shared/chart/ChartArea';
import ChartLine from 'ui/shared/chart/ChartLine'; import ChartLine from 'ui/shared/chart/ChartLine';
import ChartOverlay from 'ui/shared/chart/ChartOverlay'; import ChartOverlay from 'ui/shared/chart/ChartOverlay';
import ChartTooltip from 'ui/shared/chart/ChartTooltip'; import ChartTooltip from 'ui/shared/chart/ChartTooltip';
import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController'; import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import calculateInnerSize from 'ui/shared/chart/utils/calculateInnerSize';
interface Props { interface Props {
data: TimeChartData; data: TimeChartData;
...@@ -18,11 +19,11 @@ interface Props { ...@@ -18,11 +19,11 @@ interface Props {
const CHART_MARGIN = { bottom: 5, left: 10, right: 10, top: 0 }; const CHART_MARGIN = { bottom: 5, left: 10, right: 10, top: 0 };
const ChainIndicatorChart = ({ data }: Props) => { const ChainIndicatorChart = ({ data }: Props) => {
const ref = React.useRef<SVGSVGElement>(null);
const overlayRef = React.useRef<SVGRectElement>(null); const overlayRef = React.useRef<SVGRectElement>(null);
const lineColor = useToken('colors', 'blue.500'); const lineColor = useToken('colors', 'blue.500');
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN); const [ rect, ref ] = useClientRect<SVGSVGElement>();
const { innerWidth, innerHeight } = calculateInnerSize(rect, CHART_MARGIN);
const { xScale, yScale } = useTimeChartController({ const { xScale, yScale } = useTimeChartController({
data, data,
width: innerWidth, width: innerWidth,
...@@ -30,8 +31,8 @@ const ChainIndicatorChart = ({ data }: Props) => { ...@@ -30,8 +31,8 @@ const ChainIndicatorChart = ({ data }: Props) => {
}); });
return ( return (
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref } cursor="pointer"> <svg width="100%" height="100%" ref={ ref } cursor="pointer">
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ width ? 1 : 0 }> <g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ rect ? 1 : 0 }>
<ChartArea <ChartArea
data={ data[0].items } data={ data[0].items }
xScale={ xScale } xScale={ xScale }
......
...@@ -34,12 +34,8 @@ const Accounts = () => { ...@@ -34,12 +34,8 @@ const Accounts = () => {
return ( return (
<> <>
{ bar } { bar }
<Show below="lg"> <SkeletonList display={{ base: 'block', lg: 'none' }}/>
<SkeletonList/> <SkeletonTable display={{ base: 'none', lg: 'block' }} columns={ [ '64px', '30%', '20%', '20%', '15%', '15%' ] }/>
</Show>
<Hide below="lg">
<SkeletonTable columns={ [ '64px', '30%', '20%', '20%', '15%', '15%' ] }/>
</Hide>
</> </>
); );
} }
......
...@@ -7,6 +7,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; ...@@ -7,6 +7,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import iconSuccess from 'icons/status/success.svg'; import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import notEmpty from 'lib/notEmpty'; import notEmpty from 'lib/notEmpty';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated'; import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance'; import AddressCoinBalance from 'ui/address/AddressCoinBalance';
...@@ -20,6 +21,7 @@ import AddressTxs from 'ui/address/AddressTxs'; ...@@ -20,6 +21,7 @@ import AddressTxs from 'ui/address/AddressTxs';
import ContractCode from 'ui/address/contract/ContractCode'; import ContractCode from 'ui/address/contract/ContractCode';
import ContractRead from 'ui/address/contract/ContractRead'; import ContractRead from 'ui/address/contract/ContractRead';
import ContractWrite from 'ui/address/contract/ContractWrite'; import ContractWrite from 'ui/address/contract/ContractWrite';
import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
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';
...@@ -37,6 +39,10 @@ const TOKEN_TABS = Object.values(tokenTabsByType); ...@@ -37,6 +39,10 @@ const TOKEN_TABS = Object.values(tokenTabsByType);
const AddressPageContent = () => { const AddressPageContent = () => {
const router = useRouter(); const router = useRouter();
const appProps = useAppContext();
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/accounts');
const tabsScrollRef = React.useRef<HTMLDivElement>(null); const tabsScrollRef = React.useRef<HTMLDivElement>(null);
const addressQuery = useApiQuery('address', { const addressQuery = useApiQuery('address', {
...@@ -113,6 +119,8 @@ const AddressPageContent = () => { ...@@ -113,6 +119,8 @@ const AddressPageContent = () => {
const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null; const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null;
const content = addressQuery.isError ? null : <RoutedTabs tabs={ tabs } tabListProps={{ mt: 8 }}/>;
return ( return (
<Page> <Page>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
...@@ -121,13 +129,16 @@ const AddressPageContent = () => { ...@@ -121,13 +129,16 @@ const AddressPageContent = () => {
) : ( ) : (
<PageTitle <PageTitle
text={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` } text={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` }
additionals={ tagsNode } additionalsRight={ tagsNode }
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to top accounts list"
/> />
) } ) }
<AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/> <AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/>
<AdBanner mt={{ base: 6, lg: 8 }} justifyContent="center"/>
{ /* should stay before tabs to scroll up whith pagination */ } { /* should stay before tabs to scroll up whith pagination */ }
<Box ref={ tabsScrollRef }></Box> <Box ref={ tabsScrollRef }></Box>
{ addressQuery.isLoading ? <SkeletonTabs/> : <RoutedTabs tabs={ tabs } tabListProps={{ mt: 8 }}/> } { addressQuery.isLoading ? <SkeletonTabs/> : content }
</Page> </Page>
); );
}; };
......
...@@ -6,7 +6,6 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; ...@@ -6,7 +6,6 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import isBrowser from 'lib/isBrowser';
import BlockDetails from 'ui/block/BlockDetails'; import BlockDetails from 'ui/block/BlockDetails';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
...@@ -24,7 +23,6 @@ const TAB_LIST_PROPS = { ...@@ -24,7 +23,6 @@ const TAB_LIST_PROPS = {
const BlockPageContent = () => { const BlockPageContent = () => {
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const isInBrowser = isBrowser();
const appProps = useAppContext(); const appProps = useAppContext();
const blockTxsQuery = useQueryWithPages({ const blockTxsQuery = useQueryWithPages({
...@@ -46,15 +44,14 @@ const BlockPageContent = () => { ...@@ -46,15 +44,14 @@ const BlockPageContent = () => {
const hasPagination = !isMobile && router.query.tab === 'txs' && blockTxsQuery.isPaginationVisible; const hasPagination = !isMobile && router.query.tab === 'txs' && blockTxsQuery.isPaginationVisible;
const referrer = isInBrowser ? window.document.referrer : appProps.referrer; const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/blocks');
const hasGoBackLink = referrer && referrer.includes('/blocks');
return ( return (
<Page> <Page>
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
text={ `Block #${ router.query.id }` } text={ `Block #${ router.query.id }` }
backLinkUrl={ hasGoBackLink ? referrer : undefined } backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to blocks list" backLinkLabel="Back to blocks list"
/> />
<RoutedTabs <RoutedTabs
......
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
import link from 'lib/link/link';
import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm'; import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
...@@ -20,13 +21,22 @@ const ContractVerification = () => { ...@@ -20,13 +21,22 @@ const ContractVerification = () => {
const router = useRouter(); const router = useRouter();
const hash = router.query.id?.toString(); const hash = router.query.id?.toString();
const method = router.query.id?.toString();
React.useEffect(() => {
if (method && hash) {
router.replace(link('address_contract_verification', { id: hash }), undefined, { scroll: false, shallow: true });
}
// onMount only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]);
return ( return (
<Page> <Page>
<PageTitle <PageTitle
text="New smart contract verification" text="New smart contract verification"
backLinkUrl={ hasGoBackLink ? referrer : undefined } backLinkUrl={ hasGoBackLink ? referrer : undefined }
backLinkLabel="Back to address" backLinkLabel="Back to contract"
/> />
{ hash && ( { hash && (
<Address> <Address>
......
...@@ -32,10 +32,10 @@ const SearchResultsPageContent = () => { ...@@ -32,10 +32,10 @@ const SearchResultsPageContent = () => {
if (isLoading) { if (isLoading) {
return ( return (
<Box> <Box>
<Show below="lg"> <Show below="lg" ssr={ false }>
<SkeletonList/> <SkeletonList/>
</Show> </Show>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<SkeletonTable columns={ [ '50%', '50%', '150px' ] }/> <SkeletonTable columns={ [ '50%', '50%', '150px' ] }/>
</Hide> </Hide>
</Box> </Box>
......
...@@ -5,6 +5,7 @@ import { token as contract } from 'mocks/address/address'; ...@@ -5,6 +5,7 @@ import { token as contract } from 'mocks/address/address';
import { tokenInfo, tokenCounters } from 'mocks/tokens/tokenInfo'; import { tokenInfo, tokenCounters } from 'mocks/tokens/tokenInfo';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder';
import Token from './Token'; import Token from './Token';
...@@ -22,6 +23,11 @@ const hooksConfig = { ...@@ -22,6 +23,11 @@ const hooksConfig = {
// FIXME: idk why mobile test doesn't work (it's ok locally) // FIXME: idk why mobile test doesn't work (it's ok locally)
// test('base view +@mobile +@dark-mode', async({ mount, page }) => { // test('base view +@mobile +@dark-mode', async({ mount, page }) => {
test('base view +@dark-mode', async({ mount, page }) => { test('base view +@dark-mode', async({ mount, page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: '',
}));
await page.route(TOKEN_API_URL, (route) => route.fulfill({ await page.route(TOKEN_API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify(tokenInfo), body: JSON.stringify(tokenInfo),
...@@ -46,5 +52,7 @@ test('base view +@dark-mode', async({ mount, page }) => { ...@@ -46,5 +52,7 @@ test('base view +@dark-mode', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await insertAdPlaceholder(page);
await expect(component.locator('main')).toHaveScreenshot(); await expect(component.locator('main')).toHaveScreenshot();
}); });
import { Skeleton, Box } from '@chakra-ui/react'; import { Skeleton, Box, Flex, SkeletonCircle } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React, { useEffect } from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd';
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';
import type { Props as PaginationProps } from 'ui/shared/Pagination'; import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TokenLogo from 'ui/shared/TokenLogo';
import TokenContractInfo from 'ui/token/TokenContractInfo'; import TokenContractInfo from 'ui/token/TokenContractInfo';
import TokenDetails from 'ui/token/TokenDetails'; import TokenDetails from 'ui/token/TokenDetails';
import TokenHolders from 'ui/token/TokenHolders/TokenHolders'; import TokenHolders from 'ui/token/TokenHolders/TokenHolders';
...@@ -23,6 +27,10 @@ const TokenPageContent = () => { ...@@ -23,6 +27,10 @@ const TokenPageContent = () => {
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const appProps = useAppContext();
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/tokens');
const scrollRef = React.useRef<HTMLDivElement>(null); const scrollRef = React.useRef<HTMLDivElement>(null);
const tokenQuery = useApiQuery('token', { const tokenQuery = useApiQuery('token', {
...@@ -30,6 +38,20 @@ const TokenPageContent = () => { ...@@ -30,6 +38,20 @@ const TokenPageContent = () => {
queryOptions: { enabled: Boolean(router.query.hash) }, queryOptions: { enabled: Boolean(router.query.hash) },
}); });
useEffect(() => {
if (tokenQuery.data) {
const tokenName = `${ tokenQuery.data.name } (${ tokenQuery.data.symbol })`;
const title = document.getElementsByTagName('title')[0];
if (title) {
title.textContent = title.textContent?.replace(tokenQuery.data.address, tokenName) || title.textContent;
}
const description = document.getElementsByName('description')[0] as HTMLMetaElement;
if (description) {
description.content = description.content.replace(tokenQuery.data.address, tokenName) || description.content;
}
}
}, [ tokenQuery.data ]);
const transfersQuery = useQueryWithPages({ const transfersQuery = useQueryWithPages({
resourceName: 'token_transfers', resourceName: 'token_transfers',
pathParams: { hash: router.query.hash?.toString() }, pathParams: { hash: router.query.hash?.toString() },
...@@ -68,12 +90,27 @@ const TokenPageContent = () => { ...@@ -68,12 +90,27 @@ const TokenPageContent = () => {
return ( return (
<Page> <Page>
{ tokenQuery.isLoading ? { tokenQuery.isLoading ? (
<Skeleton w="500px" h={ 10 } mb={ 6 }/> : <Flex alignItems="center" mb={ 6 }>
<PageTitle text={ `${ tokenQuery.data?.name } (${ tokenQuery.data?.symbol }) token` }/> } <SkeletonCircle w={ 6 } h={ 6 } mr={ 3 }/>
<Skeleton w="500px" h={ 10 }/>
</Flex>
) : (
<>
<TextAd mb={ 6 }/>
<PageTitle
text={ `${ tokenQuery.data?.name } (${ tokenQuery.data?.symbol }) token` }
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to tokens list"
additionalsLeft={ (
<TokenLogo hash={ tokenQuery.data?.address } name={ tokenQuery.data?.name } boxSize={ 6 }/>
) }
/>
</>
) }
<TokenContractInfo tokenQuery={ tokenQuery }/> <TokenContractInfo tokenQuery={ tokenQuery }/>
<TokenDetails tokenQuery={ tokenQuery }/> <TokenDetails tokenQuery={ tokenQuery }/>
<AdBanner mt={{ base: 6, lg: 8 }} justifyContent="center"/>
{ /* should stay before tabs to scroll up whith pagination */ } { /* should stay before tabs to scroll up whith pagination */ }
<Box ref={ scrollRef }></Box> <Box ref={ scrollRef }></Box>
......
...@@ -6,10 +6,9 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; ...@@ -6,10 +6,9 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import isBrowser from 'lib/isBrowser';
import networkExplorers from 'lib/networks/networkExplorers'; import networkExplorers from 'lib/networks/networkExplorers';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import ExternalLink from 'ui/shared/ExternalLink'; import LinkExternal from 'ui/shared/LinkExternal';
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';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
...@@ -33,11 +32,8 @@ const TABS: Array<RoutedTab> = [ ...@@ -33,11 +32,8 @@ const TABS: Array<RoutedTab> = [
const TransactionPageContent = () => { const TransactionPageContent = () => {
const router = useRouter(); const router = useRouter();
const appProps = useAppContext(); const appProps = useAppContext();
const isInBrowser = isBrowser();
const referrer = isInBrowser ? window.document.referrer : appProps.referrer; const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/txs');
const hasGoBackLink = referrer && referrer.includes('/txs');
const { data } = useApiQuery('tx', { const { data } = useApiQuery('tx', {
pathParams: { id: router.query.id?.toString() }, pathParams: { id: router.query.id?.toString() },
...@@ -48,7 +44,7 @@ const TransactionPageContent = () => { ...@@ -48,7 +44,7 @@ const TransactionPageContent = () => {
.filter((explorer) => explorer.paths.tx) .filter((explorer) => explorer.paths.tx)
.map((explorer) => { .map((explorer) => {
const url = new URL(explorer.paths.tx + '/' + router.query.id, explorer.baseUrl); const url = new URL(explorer.paths.tx + '/' + router.query.id, explorer.baseUrl);
return <ExternalLink key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>; return <LinkExternal key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>;
}); });
const additionals = ( const additionals = (
...@@ -73,8 +69,8 @@ const TransactionPageContent = () => { ...@@ -73,8 +69,8 @@ const TransactionPageContent = () => {
<TextAd mb={ 6 }/> <TextAd mb={ 6 }/>
<PageTitle <PageTitle
text="Transaction details" text="Transaction details"
additionals={ additionals } additionalsRight={ additionals }
backLinkUrl={ hasGoBackLink ? referrer : undefined } backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to transactions list" backLinkLabel="Back to transactions list"
/> />
<RoutedTabs tabs={ TABS }/> <RoutedTabs tabs={ TABS }/>
......
...@@ -34,7 +34,7 @@ type Inputs = { ...@@ -34,7 +34,7 @@ type Inputs = {
const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isDirty }, setError } = useForm<Inputs>({
mode: 'onTouched', mode: 'onTouched',
defaultValues: { defaultValues: {
address: data?.address_hash || '', address: data?.address_hash || '',
...@@ -124,7 +124,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -124,7 +124,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Button <Button
size="lg" size="lg"
type="submit" type="submit"
disabled={ !isValid || !isDirty } disabled={ !isDirty }
isLoading={ pending } isLoading={ pending }
> >
{ data ? 'Save changes' : 'Add tag' } { data ? 'Save changes' : 'Add tag' }
......
...@@ -35,7 +35,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => ...@@ -35,7 +35,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) =>
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isDirty }, setError } = useForm<Inputs>({
mode: 'onTouched', mode: 'onTouched',
defaultValues: { defaultValues: {
transaction: data?.transaction_hash || '', transaction: data?.transaction_hash || '',
...@@ -123,7 +123,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => ...@@ -123,7 +123,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) =>
<Button <Button
size="lg" size="lg"
type="submit" type="submit"
disabled={ !isValid || !isDirty } disabled={ !isDirty }
isLoading={ pending } isLoading={ pending }
> >
{ data ? 'Save changes' : 'Add tag' } { data ? 'Save changes' : 'Add tag' }
......
...@@ -61,7 +61,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -61,7 +61,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const inputSize = { base: 'md', lg: 'lg' }; const inputSize = { base: 'md', lg: 'lg' };
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isDirty }, setError } = useForm<Inputs>({
defaultValues: { defaultValues: {
fullName: data?.full_name || '', fullName: data?.full_name || '',
email: data?.email || '', email: data?.email || '',
...@@ -236,7 +236,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -236,7 +236,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
<Button <Button
size="lg" size="lg"
type="submit" type="submit"
disabled={ !isValid || !isDirty } disabled={ !isDirty }
isLoading={ mutation.isLoading } isLoading={ mutation.isLoading }
> >
Send request Send request
......
import { Text, Link, Flex, Icon, Box, chakra } from '@chakra-ui/react'; import { Text, Flex, Icon, Box, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SearchResultItem } from 'types/api/search'; import type { SearchResultItem } from 'types/api/search';
...@@ -11,6 +11,7 @@ import Address from 'ui/shared/address/Address'; ...@@ -11,6 +11,7 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
...@@ -29,9 +30,9 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => { ...@@ -29,9 +30,9 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => {
return ( return (
<Flex alignItems="flex-start"> <Flex alignItems="flex-start">
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name } flexShrink={ 0 }/> <TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name } flexShrink={ 0 }/>
<Link ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 } wordBreak="break-all"> <LinkInternal ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 } wordBreak="break-all">
<chakra.span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/> <chakra.span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
</Link> </LinkInternal>
</Flex> </Flex>
); );
} }
...@@ -54,9 +55,9 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => { ...@@ -54,9 +55,9 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => {
return ( return (
<Flex alignItems="center"> <Flex alignItems="center">
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/> <Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Link fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }> <LinkInternal fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }>
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box> <Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
</Link> </LinkInternal>
</Flex> </Flex>
); );
} }
......
import { Tr, Td, Text, Link, Flex, Icon, Box } from '@chakra-ui/react'; import { Tr, Td, Text, Flex, Icon, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SearchResultItem } from 'types/api/search'; import type { SearchResultItem } from 'types/api/search';
...@@ -11,6 +11,7 @@ import Address from 'ui/shared/address/Address'; ...@@ -11,6 +11,7 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
interface Props { interface Props {
...@@ -29,9 +30,9 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => { ...@@ -29,9 +30,9 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => {
<Td fontSize="sm"> <Td fontSize="sm">
<Flex alignItems="center"> <Flex alignItems="center">
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name } flexShrink={ 0 }/> <TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name } flexShrink={ 0 }/>
<Link ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 } wordBreak="break-all"> <LinkInternal ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 } wordBreak="break-all">
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/> <span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
</Link> </LinkInternal>
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle"> <Td fontSize="sm" verticalAlign="middle">
...@@ -52,11 +53,11 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => { ...@@ -52,11 +53,11 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => {
<Td fontSize="sm"> <Td fontSize="sm">
<Flex alignItems="center" overflow="hidden"> <Flex alignItems="center" overflow="hidden">
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/> <AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
<Link href={ link('address_index', { id: data.address }) } fontWeight={ 700 } overflow="hidden" whiteSpace="nowrap"> <LinkInternal href={ link('address_index', { id: data.address }) } fontWeight={ 700 } overflow="hidden" whiteSpace="nowrap">
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block"> <Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
<HashStringShortenDynamic hash={ data.address }/> <HashStringShortenDynamic hash={ data.address }/>
</Box> </Box>
</Link> </LinkInternal>
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle"> <Td fontSize="sm" verticalAlign="middle">
...@@ -86,9 +87,9 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => { ...@@ -86,9 +87,9 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => {
<Td fontSize="sm"> <Td fontSize="sm">
<Flex alignItems="center"> <Flex alignItems="center">
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/> <Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Link fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }> <LinkInternal fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }>
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box> <Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
</Link> </LinkInternal>
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle"> <Td fontSize="sm" verticalAlign="middle">
......
...@@ -9,7 +9,7 @@ interface Props { ...@@ -9,7 +9,7 @@ interface Props {
className?: string; className?: string;
} }
const ExternalLink = ({ href, title, className }: Props) => { const LinkExternal = ({ href, title, className }: Props) => {
return ( return (
<Link className={ className } fontSize="sm" display="inline-flex" alignItems="center" target="_blank" href={ href }> <Link className={ className } fontSize="sm" display="inline-flex" alignItems="center" target="_blank" href={ href }>
{ title } { title }
...@@ -18,4 +18,4 @@ const ExternalLink = ({ href, title, className }: Props) => { ...@@ -18,4 +18,4 @@ const ExternalLink = ({ href, title, className }: Props) => {
); );
}; };
export default React.memo(chakra(ExternalLink)); export default React.memo(chakra(LinkExternal));
import type { LinkProps } from '@chakra-ui/react';
import { Link } from '@chakra-ui/react';
import NextLink from 'next/link';
import type { LegacyRef } from 'react';
import React from 'react';
// NOTE! use this component only for links to pages that are completely implemented in new UI
const LinkInternal = (props: LinkProps, ref: LegacyRef<HTMLAnchorElement>) => {
if (!props.href) {
return <Link { ...props } ref={ ref }/>;
}
return (
<NextLink href={ props.href } passHref target={ props.target }>
<Link { ...props } ref={ ref }/>
</NextLink>
);
};
export default React.memo(React.forwardRef(LinkInternal));
// 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';
...@@ -32,6 +34,10 @@ test('default view +@mobile', async({ mount }) => { ...@@ -32,6 +34,10 @@ test('default view +@mobile', async({ mount }) => {
}); });
test('with text ad, back link and addons +@mobile +@dark-mode', async({ mount }) => { test('with text ad, back link and addons +@mobile +@dark-mode', 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 <PageTitle
...@@ -39,7 +45,8 @@ test('with text ad, back link and addons +@mobile +@dark-mode', async({ mount }) ...@@ -39,7 +45,8 @@ test('with text ad, back link and addons +@mobile +@dark-mode', async({ mount })
withTextAd withTextAd
backLinkLabel="Back" backLinkLabel="Back"
backLinkUrl="back" backLinkUrl="back"
additionals="Privet" // additionalsLeft={ left }
additionalsRight="Privet"
/> />
</TestApp>, </TestApp>,
); );
...@@ -55,7 +62,7 @@ test('long title with text ad, back link and addons +@mobile', async({ mount }) ...@@ -55,7 +62,7 @@ test('long title with text ad, back link and addons +@mobile', async({ mount })
withTextAd withTextAd
backLinkLabel="Back" backLinkLabel="Back"
backLinkUrl="back" backLinkUrl="back"
additionals="Privet, kak dela?" additionalsRight="Privet, kak dela?"
/> />
</TestApp>, </TestApp>,
); );
......
import { Heading, Flex, Tooltip, Link, Icon, chakra } from '@chakra-ui/react'; import { Heading, Flex, Grid, Tooltip, Icon, chakra } 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';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = { type Props = {
text: string; text: string;
additionals?: React.ReactNode; additionalsLeft?: React.ReactNode;
additionalsRight?: React.ReactNode;
withTextAd?: boolean; withTextAd?: boolean;
className?: string; className?: string;
backLinkLabel?: string; backLinkLabel?: string;
backLinkUrl?: string; backLinkUrl?: string;
} }
const PageTitle = ({ text, additionals, withTextAd, backLinkUrl, backLinkLabel, className }: Props) => { const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLinkUrl, backLinkLabel, className }: Props) => {
const title = ( const title = (
<Heading <Heading
as="h1" as="h1"
size="lg" size="lg"
flex="none" flex="none"
width={ backLinkUrl ? 'calc(100% - 36px)' : '100%' }
> >
{ text } { text }
</Heading> </Heading>
...@@ -36,22 +37,25 @@ const PageTitle = ({ text, additionals, withTextAd, backLinkUrl, backLinkLabel, ...@@ -36,22 +37,25 @@ const PageTitle = ({ text, additionals, withTextAd, backLinkUrl, backLinkLabel,
className={ className } className={ className }
> >
<Flex flexWrap="wrap" columnGap={ 3 } alignItems="center" width={ withTextAd ? 'unset' : '100%' }> <Flex flexWrap="wrap" columnGap={ 3 } alignItems="center" width={ withTextAd ? 'unset' : '100%' }>
<Flex <Grid
flexWrap="nowrap" templateColumns={ [ backLinkUrl && 'auto', additionalsLeft && 'auto', '1fr' ].filter(Boolean).join(' ') }
alignItems="start"
columnGap={ 3 } columnGap={ 3 }
overflow="hidden"
> >
{ backLinkUrl && ( { backLinkUrl && (
<Tooltip label={ backLinkLabel }> <Tooltip label={ backLinkLabel }>
<Link display="inline-flex" href={ backLinkUrl } py={ 2 }> <LinkInternal display="inline-flex" href={ backLinkUrl } h="40px">
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/> <Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)" margin="auto"/>
</Link> </LinkInternal>
</Tooltip> </Tooltip>
) } ) }
{ title } { additionalsLeft !== undefined && (
<Flex h="40px" alignItems="center">
{ additionalsLeft }
</Flex> </Flex>
{ additionals } ) }
{ title }
</Grid>
{ additionalsRight }
</Flex> </Flex>
{ withTextAd && <TextAd flexShrink={ 100 }/> } { withTextAd && <TextAd flexShrink={ 100 }/> }
</Flex> </Flex>
......
...@@ -48,14 +48,14 @@ const TokenTransferListItem = ({ ...@@ -48,14 +48,14 @@ const TokenTransferListItem = ({
const addressWidth = `calc((100% - ${ baseAddress ? '50px' : '0px' }) / 2)`; const addressWidth = `calc((100% - ${ baseAddress ? '50px' : '0px' }) / 2)`;
return ( return (
<ListItemMobile rowGap={ 3 } isAnimated> <ListItemMobile rowGap={ 3 } isAnimated>
<Flex w="100%" flexWrap="wrap" rowGap={ 1 } position="relative"> <Flex w="100%" justifyContent="space-between">
<Flex flexWrap="wrap" rowGap={ 1 } mr={ showTxInfo && txHash ? 2 : 0 }>
<TokenSnippet hash={ token.address } w="auto" maxW="calc(100% - 140px)" name={ token.name || 'Unnamed token' }/> <TokenSnippet hash={ token.address } w="auto" maxW="calc(100% - 140px)" name={ token.name || 'Unnamed token' }/>
<Tag flexShrink={ 0 } ml={ 2 } mr={ 2 }>{ token.type }</Tag> <Tag flexShrink={ 0 } ml={ 2 } mr={ 2 }>{ token.type }</Tag>
<Tag colorScheme="orange">{ getTokenTransferTypeText(type) }</Tag> <Tag colorScheme="orange">{ getTokenTransferTypeText(type) }</Tag>
</Flex>
{ showTxInfo && txHash && ( { showTxInfo && txHash && (
<Flex position="absolute" top={ 0 } right={ 0 }>
<TxAdditionalInfo hash={ txHash } isMobile/> <TxAdditionalInfo hash={ txHash } isMobile/>
</Flex>
) } ) }
</Flex> </Flex>
{ 'token_id' in total && <TokenTransferNft hash={ token.address } id={ total.token_id }/> } { 'token_id' in total && <TokenTransferNft hash={ token.address } id={ total.token_id }/> }
......
...@@ -22,7 +22,7 @@ var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || []; ...@@ -22,7 +22,7 @@ var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
const AdbutlerBanner = ({ className }: { className?: string }) => { const AdbutlerBanner = ({ className }: { className?: string }) => {
return ( return (
<Flex className={ className } id="adBanner"> <Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
<div id="ad-banner"></div> <div id="ad-banner"></div>
<Script id="ad-butler-1">{ scriptText1 }</Script> <Script id="ad-butler-1">{ scriptText1 }</Script>
<Script id="ad-butler-2">{ scriptText2 }</Script> <Script id="ad-butler-2">{ scriptText2 }</Script>
......
...@@ -32,7 +32,7 @@ const CoinzillaBanner = ({ className }: { className?: string }) => { ...@@ -32,7 +32,7 @@ const CoinzillaBanner = ({ className }: { className?: string }) => {
}, [ isInBrowser ]); }, [ isInBrowser ]);
return ( return (
<Flex className={ className } id="adBanner"> <Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
<Script src="https://coinzillatag.com/lib/display.js"/> <Script src="https://coinzillatag.com/lib/display.js"/>
<div className="coinzilla" data-zone="C-26660bf627543e46851"></div> <div className="coinzilla" data-zone="C-26660bf627543e46851"></div>
</Flex> </Flex>
......
import { Link, chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react'; import { chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react';
import type { HTMLAttributeAnchorTarget } from 'react'; import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react'; import React from 'react';
...@@ -6,6 +6,7 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -6,6 +6,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link'; import link from 'lib/link/link';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
import TruncatedTextTooltip from '../TruncatedTextTooltip'; import TruncatedTextTooltip from '../TruncatedTextTooltip';
...@@ -93,7 +94,7 @@ const AddressLink = (props: Props) => { ...@@ -93,7 +94,7 @@ const AddressLink = (props: Props) => {
} }
return ( return (
<Link <LinkInternal
className={ className } className={ className }
href={ url } href={ url }
target={ target } target={ target }
...@@ -101,7 +102,7 @@ const AddressLink = (props: Props) => { ...@@ -101,7 +102,7 @@ const AddressLink = (props: Props) => {
whiteSpace="nowrap" whiteSpace="nowrap"
> >
{ content } { content }
</Link> </LinkInternal>
); );
}; };
......
import { import {
Box, Box,
Center,
chakra,
Flex, Flex,
Grid, Grid,
Icon, Icon,
...@@ -36,13 +38,13 @@ type Props = { ...@@ -36,13 +38,13 @@ type Props = {
title: string; title: string;
description?: string; description?: string;
isLoading: boolean; isLoading: boolean;
chartHeight?: string; className?: string;
isError: boolean; isError: boolean;
} }
const DOWNLOAD_IMAGE_SCALE = 5; const DOWNLOAD_IMAGE_SCALE = 5;
const ChartWidget = ({ items, title, description, isLoading, chartHeight, isError }: Props) => { const ChartWidget = ({ items, title, description, isLoading, className, isError }: Props) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [ isFullscreen, setIsFullscreen ] = useState(false); const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
...@@ -111,10 +113,53 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro ...@@ -111,10 +113,53 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro
return <ChartWidgetSkeleton hasDescription={ Boolean(description) }/>; return <ChartWidgetSkeleton hasDescription={ Boolean(description) }/>;
} }
const hasItems = items && items.length > 2;
const content = (() => {
if (isError) {
return (
<Flex
alignItems="center"
justifyContent="center"
flexGrow={ 1 }
py={ 4 }
>
<Text
variant="secondary"
fontSize="sm"
textAlign="center"
>
{ `The data didn${ apos }t load. Please, ` }
<Link href={ window.document.location.href }>try to reload the page.</Link>
</Text>
</Flex>
);
}
if (!hasItems) {
return (
<Center flexGrow={ 1 }>
<Text variant="secondary" fontSize="sm">No data</Text>
</Center>
);
}
return (
<Box h="100%" maxW="100%">
<ChartWidgetGraph
items={ items }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title={ title }
/>
</Box>
);
})();
return ( return (
<> <>
<Box <Box
height={ chartHeight } height="100%"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
ref={ ref } ref={ ref }
...@@ -122,6 +167,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro ...@@ -122,6 +167,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro
borderRadius="md" borderRadius="md"
border="1px" border="1px"
borderColor={ borderColor } borderColor={ borderColor }
className={ className }
> >
<Grid <Grid
gridTemplateColumns="auto auto 36px" gridTemplateColumns="auto auto 36px"
...@@ -167,7 +213,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro ...@@ -167,7 +213,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro
/> />
</Tooltip> </Tooltip>
{ !isError && ( { hasItems && (
<Menu> <Menu>
<MenuButton <MenuButton
gridColumn={ 3 } gridColumn={ 3 }
...@@ -216,36 +262,10 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro ...@@ -216,36 +262,10 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro
) } ) }
</Grid> </Grid>
{ items ? ( { content }
<Box h={ chartHeight || 'auto' } maxW="100%">
<ChartWidgetGraph
margin={{ bottom: 20 }}
items={ items }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title={ title }
/>
</Box>
) : (
<Flex
alignItems="center"
justifyContent="center"
flexGrow={ 1 }
py={ 4 }
>
<Text
variant="secondary"
fontSize="sm"
textAlign="center"
>
{ `The data didn${ apos }t load. Please, ` }
<Link href={ window.document.location.href }>try to reload the page.</Link>
</Text>
</Flex>
) }
</Box> </Box>
{ items && ( { hasItems && (
<FullscreenChartModal <FullscreenChartModal
isOpen={ isFullscreen } isOpen={ isFullscreen }
items={ items } items={ items }
...@@ -258,4 +278,4 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro ...@@ -258,4 +278,4 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight, isErro
); );
}; };
export default React.memo(ChartWidget); export default React.memo(chakra(ChartWidget));
...@@ -5,6 +5,7 @@ import React, { useEffect, useMemo } from 'react'; ...@@ -5,6 +5,7 @@ import React, { useEffect, useMemo } from 'react';
import type { ChartMargin, TimeChartItem } from 'ui/shared/chart/types'; import type { ChartMargin, TimeChartItem } from 'ui/shared/chart/types';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useClientRect from 'lib/hooks/useClientRect';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import ChartArea from 'ui/shared/chart/ChartArea'; import ChartArea from 'ui/shared/chart/ChartArea';
import ChartAxis from 'ui/shared/chart/ChartAxis'; import ChartAxis from 'ui/shared/chart/ChartAxis';
...@@ -13,8 +14,8 @@ import ChartLine from 'ui/shared/chart/ChartLine'; ...@@ -13,8 +14,8 @@ import ChartLine from 'ui/shared/chart/ChartLine';
import ChartOverlay from 'ui/shared/chart/ChartOverlay'; import ChartOverlay from 'ui/shared/chart/ChartOverlay';
import ChartSelectionX from 'ui/shared/chart/ChartSelectionX'; import ChartSelectionX from 'ui/shared/chart/ChartSelectionX';
import ChartTooltip from 'ui/shared/chart/ChartTooltip'; import ChartTooltip from 'ui/shared/chart/ChartTooltip';
import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController'; import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import calculateInnerSize from 'ui/shared/chart/utils/calculateInnerSize';
interface Props { interface Props {
isEnlarged?: boolean; isEnlarged?: boolean;
...@@ -22,7 +23,7 @@ interface Props { ...@@ -22,7 +23,7 @@ interface Props {
items: Array<TimeChartItem>; items: Array<TimeChartItem>;
onZoom: () => void; onZoom: () => void;
isZoomResetInitial: boolean; isZoomResetInitial: boolean;
margin: ChartMargin; margin?: ChartMargin;
} }
const MAX_SHOW_ITEMS = 100; const MAX_SHOW_ITEMS = 100;
...@@ -31,10 +32,12 @@ const DEFAULT_CHART_MARGIN = { bottom: 20, left: 40, right: 20, top: 10 }; ...@@ -31,10 +32,12 @@ const DEFAULT_CHART_MARGIN = { bottom: 20, left: 40, right: 20, top: 10 };
const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin }: Props) => { const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const color = useToken('colors', 'blue.200'); const color = useToken('colors', 'blue.200');
const ref = React.useRef<SVGSVGElement>(null);
const overlayRef = React.useRef<SVGRectElement>(null); const overlayRef = React.useRef<SVGRectElement>(null);
const [ rect, ref ] = useClientRect<SVGSVGElement>();
const chartMargin = { ...DEFAULT_CHART_MARGIN, ...margin }; const chartMargin = { ...DEFAULT_CHART_MARGIN, ...margin };
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, chartMargin); const { innerWidth, innerHeight } = calculateInnerSize(rect, chartMargin);
const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`; const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`;
const [ range, setRange ] = React.useState<[ Date, Date ]>([ items[0].date, items[items.length - 1].date ]); const [ range, setRange ] = React.useState<[ Date, Date ]>([ items[0].date, items[items.length - 1].date ]);
...@@ -70,7 +73,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -70,7 +73,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
}, [ isZoomResetInitial, items ]); }, [ isZoomResetInitial, items ]);
return ( return (
<svg width="100%" height={ height || '100%' } ref={ ref } cursor="pointer" id={ chartId } opacity={ width ? 1 : 0 }> <svg width="100%" height="100%" ref={ ref } cursor="pointer" id={ chartId } opacity={ rect ? 1 : 0 }>
<g transform={ `translate(${ chartMargin?.left || 0 },${ chartMargin?.top || 0 })` }> <g transform={ `translate(${ chartMargin?.left || 0 },${ chartMargin?.top || 0 })` }>
<ChartGridLine <ChartGridLine
...@@ -149,6 +152,6 @@ function groupChartItemsByWeekNumber(items: Array<TimeChartItem>): Array<TimeCha ...@@ -149,6 +152,6 @@ function groupChartItemsByWeekNumber(items: Array<TimeChartItem>): Array<TimeCha
value: d3.sum(group, (d) => d.value), value: d3.sum(group, (d) => d.value),
dateLabel: `${ d3.timeFormat('%e %b %Y')(group[0].date) }${ d3.timeFormat('%e %b %Y')(group[group.length - 1].date) }`, dateLabel: `${ d3.timeFormat('%e %b %Y')(group[0].date) }${ d3.timeFormat('%e %b %Y')(group[group.length - 1].date) }`,
}), }),
(t) => dayjs(t.date).week(), (t) => `${ dayjs(t.date).week() } / ${ dayjs(t.date).year() }`,
).map(([ , v ]) => v); ).map(([ , v ]) => v);
} }
...@@ -3,10 +3,9 @@ import React from 'react'; ...@@ -3,10 +3,9 @@ import React from 'react';
interface Props { interface Props {
hasDescription: boolean; hasDescription: boolean;
chartHeight?: string;
} }
const ChartWidgetSkeleton = ({ hasDescription, chartHeight }: Props) => { const ChartWidgetSkeleton = ({ hasDescription }: Props) => {
return ( return (
<Box <Box
height="235px" height="235px"
...@@ -15,7 +14,7 @@ const ChartWidgetSkeleton = ({ hasDescription, chartHeight }: Props) => { ...@@ -15,7 +14,7 @@ const ChartWidgetSkeleton = ({ hasDescription, chartHeight }: Props) => {
<Skeleton w="75%" h="24px"/> <Skeleton w="75%" h="24px"/>
{ hasDescription && <Skeleton w="50%" h="18px" mt={ 1 }/> } { hasDescription && <Skeleton w="50%" h="18px" mt={ 1 }/> }
<Skeleton w="100%" h={ chartHeight || '150px' } mt={ 5 }/> <Skeleton w="100%" h="150px" mt={ 5 }/>
</Box> </Box>
); );
}; };
......
import _debounce from 'lodash/debounce';
import React from 'react';
import type { ChartMargin, ChartOffset } from 'ui/shared/chart/types';
export default function useChartSize(svgEl: SVGSVGElement | null, margin?: ChartMargin, offsets?: ChartOffset) {
const [ rect, setRect ] = React.useState<{ width: number; height: number}>({ width: 0, height: 0 });
const calculateRect = React.useCallback(() => {
const rect = svgEl?.getBoundingClientRect();
return { width: rect?.width || 0, height: rect?.height || 0 };
}, [ svgEl ]);
React.useEffect(() => {
setRect(calculateRect());
}, [ calculateRect ]);
React.useEffect(() => {
const content = window.document.querySelector('main');
if (!content) {
return;
}
let timeoutId: number;
const resizeHandler = _debounce(() => {
setRect({ width: 0, height: 0 });
timeoutId = window.setTimeout(() => {
setRect(calculateRect());
}, 100);
}, 100);
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(content);
resizeObserver.observe(window.document.body);
return function cleanup() {
resizeObserver.unobserve(content);
resizeObserver.unobserve(window.document.body);
window.clearTimeout(timeoutId);
};
}, [ calculateRect ]);
return React.useMemo(() => {
return {
width: Math.max(rect.width - (offsets?.x || 0), 0),
height: Math.max(rect.height - (offsets?.y || 0), 0),
innerWidth: Math.max(rect.width - (offsets?.x || 0) - (margin?.left || 0) - (margin?.right || 0), 0),
innerHeight: Math.max(rect.height - (offsets?.y || 0) - (margin?.bottom || 0) - (margin?.top || 0), 0),
};
}, [ margin?.bottom, margin?.left, margin?.right, margin?.top, offsets?.x, offsets?.y, rect.height, rect.width ]);
}
import type { ChartMargin, ChartOffset } from 'ui/shared/chart/types';
export default function calculateInnerSize(rect: DOMRect | null, margin?: ChartMargin, offsets?: ChartOffset) {
if (!rect) {
return { innerWidth: 0, innerHeight: 0 };
}
return {
innerWidth: Math.max(rect.width - (offsets?.x || 0) - (margin?.left || 0) - (margin?.right || 0), 0),
innerHeight: Math.max(rect.height - (offsets?.y || 0) - (margin?.bottom || 0) - (margin?.top || 0), 0),
};
}
import type { As } from '@chakra-ui/react';
import { Box, Button, Circle, Icon, useColorModeValue } from '@chakra-ui/react'; import { Box, Button, Circle, Icon, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -9,9 +10,10 @@ interface Props { ...@@ -9,9 +10,10 @@ interface Props {
isActive?: boolean; isActive?: boolean;
appliedFiltersNum?: number; appliedFiltersNum?: number;
onClick: () => void; onClick: () => void;
as?: As;
} }
const FilterButton = ({ isActive, appliedFiltersNum, onClick }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => { const FilterButton = ({ isActive, appliedFiltersNum, onClick, as }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const badgeColor = useColorModeValue('white', 'black'); const badgeColor = useColorModeValue('white', 'black');
const badgeBgColor = useColorModeValue('blue.700', 'gray.50'); const badgeBgColor = useColorModeValue('blue.700', 'gray.50');
...@@ -27,6 +29,7 @@ const FilterButton = ({ isActive, appliedFiltersNum, onClick }: Props, ref: Reac ...@@ -27,6 +29,7 @@ const FilterButton = ({ isActive, appliedFiltersNum, onClick }: Props, ref: Reac
isActive={ isActive } isActive={ isActive }
px={ 1.5 } px={ 1.5 }
flexShrink={ 0 } flexShrink={ 0 }
as={ as }
> >
{ FilterIcon } { FilterIcon }
<Box display={{ base: 'none', lg: 'block' }}>Filter</Box> <Box display={{ base: 'none', lg: 'block' }}>Filter</Box>
......
...@@ -35,6 +35,7 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialVal ...@@ -35,6 +35,7 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialVal
<InputGroup <InputGroup
size={ size } size={ size }
className={ className } className={ className }
minW="250px"
> >
<InputLeftElement <InputLeftElement
pointerEvents="none" pointerEvents="none"
......
...@@ -58,7 +58,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash ...@@ -58,7 +58,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash
<AddressLink <AddressLink
hash={ hasTxInfo ? txHash : address.hash } hash={ hasTxInfo ? txHash : address.hash }
alias={ hasTxInfo ? undefined : address.name } alias={ hasTxInfo ? undefined : address.name }
type={ type } type={ type === 'address' ? 'transaction' : 'address' }
/> />
</Address> </Address>
{ /* api doesn't have find topic feature yet */ } { /* api doesn't have find topic feature yet */ }
......
import { Box, Flex, Skeleton, SkeletonCircle } from '@chakra-ui/react'; import { Box, Flex, Skeleton, SkeletonCircle, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const SkeletonList = () => { const SkeletonList = ({ className }: {className?: string}) => {
return ( return (
<Box> <Box className={ className }>
{ Array.from(Array(2)).map((item, index) => ( { Array.from(Array(2)).map((item, index) => (
<Flex <Flex
key={ index } key={ index }
...@@ -35,4 +35,4 @@ const SkeletonList = () => { ...@@ -35,4 +35,4 @@ const SkeletonList = () => {
); );
}; };
export default SkeletonList; export default chakra(SkeletonList);
import { HStack, Skeleton } from '@chakra-ui/react'; import { Box, HStack, Skeleton, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
interface Props { interface Props {
columns: Array<string>; columns: Array<string>;
className?: string;
} }
const SkeletonTable = ({ columns }: Props) => { const SkeletonTable = ({ columns, className }: Props) => {
return ( return (
<div> <Box className={ className }>
<Skeleton height={ 10 } width="100%" borderBottomLeftRadius="none" borderBottomRightRadius="none"/> <Skeleton height={ 10 } width="100%" borderBottomLeftRadius="none" borderBottomRightRadius="none"/>
{ Array.from(Array(3)).map((item, index) => ( { Array.from(Array(3)).map((item, index) => (
<HStack key={ index } spacing={ 6 } marginTop={ 8 }> <HStack key={ index } spacing={ 6 } marginTop={ 8 }>
...@@ -22,8 +23,8 @@ const SkeletonTable = ({ columns }: Props) => { ...@@ -22,8 +23,8 @@ const SkeletonTable = ({ columns }: Props) => {
)) } )) }
</HStack> </HStack>
)) } )) }
</div> </Box>
); );
}; };
export default React.memo(SkeletonTable); export default React.memo(chakra(SkeletonTable));
import React, { useEffect } from 'react'; import React, { useEffect, useMemo } from 'react';
import type { StatsIntervalIds } from 'types/client/stats'; import type { StatsIntervalIds } from 'types/client/stats';
...@@ -33,9 +33,9 @@ const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError ...@@ -33,9 +33,9 @@ const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError
}, },
}); });
const items = data?.chart?.map((item) => { const items = useMemo(() => data?.chart?.map((item) => {
return { date: new Date(item.date), value: Number(item.value) }; return { date: new Date(item.date), value: Number(item.value) };
}); }), [ data ]);
useEffect(() => { useEffect(() => {
if (isError) { if (isError) {
...@@ -45,7 +45,6 @@ const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError ...@@ -45,7 +45,6 @@ const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError
return ( return (
<ChartWidget <ChartWidget
chartHeight="100%"
isError={ isError } isError={ isError }
items={ items } items={ items }
title={ title } title={ title }
......
...@@ -41,8 +41,8 @@ const StatsFilters = ({ ...@@ -41,8 +41,8 @@ const StatsFilters = ({
<Grid <Grid
gap={ 2 } gap={ 2 }
templateAreas={{ templateAreas={{
base: `"input input" base: `"section interval"
"section interval"`, "input input"`,
lg: `"section interval input"`, lg: `"section interval input"`,
}} }}
gridTemplateColumns={{ base: 'repeat(2, minmax(0, 1fr))', lg: 'auto auto 1fr' }} gridTemplateColumns={{ base: 'repeat(2, minmax(0, 1fr))', lg: 'auto auto 1fr' }}
......
...@@ -69,11 +69,6 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -69,11 +69,6 @@ const TokenDetails = ({ tokenQuery }: Props) => {
); );
} }
// we show error in parent component, this is only for TS
if (tokenQuery.isError) {
return null;
}
const { const {
exchange_rate: exchangeRate, exchange_rate: exchangeRate,
total_supply: totalSupply, total_supply: totalSupply,
......
...@@ -66,11 +66,11 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => { ...@@ -66,11 +66,11 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
if (isLoading) { if (isLoading) {
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<SkeletonTable columns={ [ '45%', '15%', '36px', '15%', '25%' ] } <SkeletonTable columns={ [ '45%', '15%', '36px', '15%', '25%' ] }
/> />
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
<SkeletonList/> <SkeletonList/>
</Show> </Show>
</> </>
...@@ -88,7 +88,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => { ...@@ -88,7 +88,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
const items = data.items.reduce(flattenTotal, []); const items = data.items.reduce(flattenTotal, []);
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<TokenTransferTable <TokenTransferTable
data={ items } data={ items }
top={ 80 } top={ 80 }
...@@ -100,7 +100,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => { ...@@ -100,7 +100,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
socketInfoNum={ newItemsCount } socketInfoNum={ newItemsCount }
/> />
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
{ pagination.page === 1 && ( { pagination.page === 1 && (
<SocketNewItemsNotice <SocketNewItemsNotice
url={ window.location.href } url={ window.location.href }
......
...@@ -69,19 +69,15 @@ const Tokens = () => { ...@@ -69,19 +69,15 @@ const Tokens = () => {
const bar = ( const bar = (
<> <>
<Show below="lg"> <HStack spacing={ 3 } mb={ 6 } display={{ base: 'flex', lg: 'none' }}>
<HStack spacing={ 3 } mb={ 6 }>
{ typeFilter } { typeFilter }
{ filterInput } { filterInput }
</HStack> </HStack>
</Show>
<ActionBar mt={ -6 }> <ActionBar mt={ -6 }>
<Hide below="lg"> <HStack spacing={ 3 } display={{ base: 'none', lg: 'flex' }}>
<HStack spacing={ 3 }>
{ typeFilter } { typeFilter }
{ filterInput } { filterInput }
</HStack> </HStack>
</Hide>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> } { isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar> </ActionBar>
</> </>
...@@ -91,10 +87,8 @@ const Tokens = () => { ...@@ -91,10 +87,8 @@ const Tokens = () => {
return ( return (
<> <>
{ bar } { bar }
<Show below="lg"><SkeletonList/></Show> <SkeletonList display={{ base: 'block', lg: 'none' }}/>
<Hide below="lg"> <SkeletonTable display={{ base: 'none', lg: 'block' }} columns={ [ '25px', '33%', '33%', '33%', '110px' ] }/>
<SkeletonTable columns={ [ '25px', '33%', '33%', '33%', '110px' ] }/>
</Hide>
</> </>
); );
} }
......
...@@ -99,7 +99,7 @@ const TxDetails = () => { ...@@ -99,7 +99,7 @@ const TxDetails = () => {
const executionFailedBadge = toAddress.is_contract && Boolean(data.status) && data.result !== 'success' ? ( const executionFailedBadge = toAddress.is_contract && Boolean(data.status) && data.result !== 'success' ? (
<Tooltip label="Error occurred during contract execution"> <Tooltip label="Error occurred during contract execution">
<chakra.span display="inline-flex" ml={ 2 } mr={ 1 }> <chakra.span display="inline-flex" ml={ 2 } mr={ 1 }>
<Icon as={ errorIcon } boxSize={ 4 } color="red.500" cursor="pointer"/> <Icon as={ errorIcon } boxSize={ 4 } color="error" cursor="pointer"/>
</chakra.span> </chakra.span>
</Tooltip> </Tooltip>
) : null; ) : null;
......
...@@ -101,8 +101,8 @@ const TxInternals = () => { ...@@ -101,8 +101,8 @@ const TxInternals = () => {
if (isLoading || txInfo.isLoading) { if (isLoading || txInfo.isLoading) {
return ( return (
<> <>
<Show below="lg"><SkeletonList/></Show> <Show below="lg" ssr={ false }><SkeletonList/></Show>
<Hide below="lg"><SkeletonTable columns={ [ '28%', '20%', '24px', '20%', '16%', '16%' ] }/></Hide> <Hide below="lg" ssr={ false }><SkeletonTable columns={ [ '28%', '20%', '24px', '20%', '16%', '16%' ] }/></Hide>
</> </>
); );
} }
......
...@@ -83,10 +83,10 @@ const TxTokenTransfer = () => { ...@@ -83,10 +83,10 @@ const TxTokenTransfer = () => {
const items = tokenTransferQuery.data.items.reduce(flattenTotal, []); const items = tokenTransferQuery.data.items.reduce(flattenTotal, []);
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg" ssr={ false }>
<TokenTransferTable data={ items } top={ 80 }/> <TokenTransferTable data={ items } top={ 80 }/>
</Hide> </Hide>
<Show below="lg"> <Show below="lg" ssr={ false }>
<TokenTransferList data={ items }/> <TokenTransferList data={ items }/>
</Show> </Show>
</> </>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment