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

Merge pull request #2054 from blockscout/release/v1_32_0-fixes

Fixes for release v.1.32.0
parents 110076d4 b8fedb9a
...@@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps> ...@@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps>
containerStyle: { containerStyle: {
margin: 8, margin: 8,
}, },
variant: 'subtle',
}; };
export default function useToastModified() { export default function useToastModified() {
......
import type { TestFixture, Page } from '@playwright/test';
import _isEqual from 'lodash/isEqual';
import type { PublicRpcSchema } from 'viem';
import { getEnvValue } from 'configs/app/utils';
type Params = PublicRpcSchema[number];
export type MockRpcResponseFixture = (params: Params) => Promise<void>;
// WIP
const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ page }, use) => {
await use(async({ Method, ReturnType }) => {
const rpcUrl = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
if (!rpcUrl) {
return;
}
await page.route(rpcUrl, (route) => {
const method = route.request().method();
if (method !== 'POST') {
route.continue();
return;
}
const json = route.request().postDataJSON();
const id = json?.id;
const payload = {
id,
jsonrpc: '2.0',
method: Method,
// TODO: add params to match actual payload
};
if (_isEqual(json, payload) && id !== undefined) {
return route.fulfill({
status: 200,
body: JSON.stringify({
id,
jsonrpc: '2.0',
result: ReturnType,
}),
});
}
});
});
};
export default fixture;
...@@ -8,6 +8,7 @@ import * as mockConfigResponse from './fixtures/mockConfigResponse'; ...@@ -8,6 +8,7 @@ import * as mockConfigResponse from './fixtures/mockConfigResponse';
import * as mockContractReadResponse from './fixtures/mockContractReadResponse'; import * as mockContractReadResponse from './fixtures/mockContractReadResponse';
import * as mockEnvs from './fixtures/mockEnvs'; import * as mockEnvs from './fixtures/mockEnvs';
import * as mockFeatures from './fixtures/mockFeatures'; import * as mockFeatures from './fixtures/mockFeatures';
import * as mockRpcResponse from './fixtures/mockRpcResponse';
import * as mockTextAd from './fixtures/mockTextAd'; import * as mockTextAd from './fixtures/mockTextAd';
import * as render from './fixtures/render'; import * as render from './fixtures/render';
import * as socketServer from './fixtures/socketServer'; import * as socketServer from './fixtures/socketServer';
...@@ -20,6 +21,7 @@ interface Fixtures { ...@@ -20,6 +21,7 @@ interface Fixtures {
mockContractReadResponse: mockContractReadResponse.MockContractReadResponseFixture; mockContractReadResponse: mockContractReadResponse.MockContractReadResponseFixture;
mockEnvs: mockEnvs.MockEnvsFixture; mockEnvs: mockEnvs.MockEnvsFixture;
mockFeatures: mockFeatures.MockFeaturesFixture; mockFeatures: mockFeatures.MockFeaturesFixture;
mockRpcResponse: mockRpcResponse.MockRpcResponseFixture;
createSocket: socketServer.CreateSocketFixture; createSocket: socketServer.CreateSocketFixture;
injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider; injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider;
mockTextAd: mockTextAd.MockTextAdFixture; mockTextAd: mockTextAd.MockTextAdFixture;
...@@ -33,6 +35,7 @@ const test = base.extend<Fixtures>({ ...@@ -33,6 +35,7 @@ const test = base.extend<Fixtures>({
mockContractReadResponse: mockContractReadResponse.default, mockContractReadResponse: mockContractReadResponse.default,
mockEnvs: mockEnvs.default, mockEnvs: mockEnvs.default,
mockFeatures: mockFeatures.default, mockFeatures: mockFeatures.default,
mockRpcResponse: mockRpcResponse.default,
// FIXME: for some reason Playwright does not intercept requests to text ad provider when running multiple tests in parallel // FIXME: for some reason Playwright does not intercept requests to text ad provider when running multiple tests in parallel
// even if we have a global request interceptor (maybe it is related to service worker issue, maybe not) // even if we have a global request interceptor (maybe it is related to service worker issue, maybe not)
// so we have to inject mockTextAd fixture in each test and mock the response where it is needed // so we have to inject mockTextAd fixture in each test and mock the response where it is needed
......
...@@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => { ...@@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
</Skeleton> </Skeleton>
) : data.tx_count } ) : data.tx_count }
</Td> </Td>
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton>
<Flex mt={ 2 }>
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }>
<Box>
<Utilization
colorScheme="gray"
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
isLoading={ isLoading }
/>
</Box>
</Tooltip>
{ data.gas_target_percentage && (
<>
<TextSeparator color={ separatorColor } mx={ 1 }/>
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
</>
) }
</Flex>
</Td>
{ !isRollup && !config.UI.views.block.hiddenFields?.total_reward && ( { !isRollup && !config.UI.views.block.hiddenFields?.total_reward && (
<Td fontSize="sm"> <Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton> <Skeleton isLoaded={ !isLoading } display="inline-block">
<Flex mt={ 2 }> { totalReward.toFixed(8) }
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }> </Skeleton>
<Box>
<Utilization
colorScheme="gray"
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
isLoading={ isLoading }
/>
</Box>
</Tooltip>
{ data.gas_target_percentage && (
<>
<TextSeparator color={ separatorColor } mx={ 1 }/>
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
</>
) }
</Flex>
</Td> </Td>
) } ) }
<Td fontSize="sm">
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ totalReward.toFixed(8) }
</Skeleton>
</Td>
{ !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && ( { !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && (
<Td fontSize="sm"> <Td fontSize="sm">
<Flex alignItems="center" columnGap={ 2 }> <Flex alignItems="center" columnGap={ 2 }>
......
...@@ -179,7 +179,7 @@ const MarketplaceAppCard = ({ ...@@ -179,7 +179,7 @@ const MarketplaceAppCard = ({
showContractList={ showContractList } showContractList={ showContractList }
isLoading={ isLoading } isLoading={ isLoading }
source="Discovery view" source="Discovery view"
popoverPlacement={ isMobile ? 'bottom-end' : 'bottom-start' } popoverPlacement={ isMobile ? 'bottom-end' : 'left' }
position="absolute" position="absolute"
right={{ base: 3, md: 5 }} right={{ base: 3, md: 5 }}
top={{ base: '10px', md: 5 }} top={{ base: '10px', md: 5 }}
......
...@@ -53,7 +53,7 @@ const NameDomainsActionBar = ({ ...@@ -53,7 +53,7 @@ const NameDomainsActionBar = ({
minW={{ base: 'auto', lg: '250px' }} minW={{ base: 'auto', lg: '250px' }}
size="xs" size="xs"
onChange={ onSearchChange } onChange={ onSearchChange }
placeholder="Search by name" placeholder="Search by name or address"
initialValue={ searchTerm } initialValue={ searchTerm }
isLoading={ isInitialLoading } isLoading={ isInitialLoading }
/> />
......
...@@ -64,21 +64,12 @@ const GasTracker = () => { ...@@ -64,21 +64,12 @@ const GasTracker = () => {
</Flex> </Flex>
); );
const content = (() => { const snippets = (() => {
if (!isPlaceholderData && data?.gas_prices?.slow === null && data?.gas_prices.average === null && data.gas_prices.fast === null) { if (!isPlaceholderData && data?.gas_prices?.slow === null && data?.gas_prices.average === null && data.gas_prices.fast === null) {
return <Alert status="warning">No data available yet</Alert>; return <Alert status="warning">No recent data available</Alert>;
} }
return ( return data?.gas_prices ? <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> : null;
<>
{ data?.gas_prices && <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> }
{ config.features.stats.isEnabled && (
<Box mt={ 12 }>
<GasTrackerChart/>
</Box>
) }
</>
);
})(); })();
return ( return (
...@@ -88,7 +79,12 @@ const GasTracker = () => { ...@@ -88,7 +79,12 @@ const GasTracker = () => {
secondRow={ titleSecondRow } secondRow={ titleSecondRow }
withTextAd withTextAd
/> />
{ content } { snippets }
{ config.features.stats.isEnabled && (
<Box mt={ 12 }>
<GasTrackerChart/>
</Box>
) }
</> </>
); );
}; };
......
...@@ -33,9 +33,9 @@ const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, m ...@@ -33,9 +33,9 @@ const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, m
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}; };
test('base view +@dark-mode', testFn); test.fixme('base view +@dark-mode', testFn);
test.describe('mobile', () => { test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport }); test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', testFn); test.fixme('base view', testFn);
}); });
...@@ -20,7 +20,7 @@ function getBgColor(status?: AlertStatus) { ...@@ -20,7 +20,7 @@ function getBgColor(status?: AlertStatus) {
} }
} }
const Toast = ({ onClose, title, description, id, isClosable, status }: ToastProps) => { const Toast = ({ onClose, title, description, id, isClosable, status, icon }: ToastProps) => {
const ids = id ? const ids = id ?
{ {
...@@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro ...@@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro
maxWidth="400px" maxWidth="400px"
> >
<chakra.div flex="1" maxWidth="100%"> <chakra.div flex="1" maxWidth="100%">
{ title && <AlertTitle id={ ids?.title }>{ title }</AlertTitle> } { title && <AlertTitle id={ ids?.title } display="flex" alignItems="center">{ icon }{ title }</AlertTitle> }
{ description && ( { description && (
<AlertDescription id={ ids?.description } display="block"> <AlertDescription id={ ids?.description } display="block">
{ description } { description }
......
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import type { ToastId } from '@chakra-ui/react';
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Spinner } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import ReCaptcha from 'react-google-recaptcha'; import ReCaptcha from 'react-google-recaptcha';
...@@ -9,7 +10,7 @@ import type { TokenInstance } from 'types/api/token'; ...@@ -9,7 +10,7 @@ import type { TokenInstance } from 'types/api/token';
import config from 'configs/app'; import config from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import { MINUTE } from 'lib/consts'; import { MINUTE, SECOND } from 'lib/consts';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
...@@ -23,6 +24,7 @@ interface Props { ...@@ -23,6 +24,7 @@ interface Props {
const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
const timeoutId = React.useRef<number>(); const timeoutId = React.useRef<number>();
const toastId = React.useRef<ToastId>();
const { status, setStatus } = useMetadataUpdateContext() || {}; const { status, setStatus } = useMetadataUpdateContext() || {};
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
...@@ -31,12 +33,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -31,12 +33,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
const handleRefreshError = React.useCallback(() => { const handleRefreshError = React.useCallback(() => {
setStatus?.('ERROR'); setStatus?.('ERROR');
toast.closeAll(); toastId.current && toast.update(toastId.current, {
toast({
title: 'Error', title: 'Error',
description: 'The refreshing process has failed. Please try again.', description: 'The refreshing process has failed. Please try again.',
status: 'warning', status: 'warning',
variant: 'subtle', duration: 5 * SECOND,
isClosable: true,
}); });
}, [ setStatus, toast ]); }, [ setStatus, toast ]);
...@@ -49,13 +51,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -49,13 +51,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
}, },
}) })
.then(() => { .then(() => {
toast({ setStatus?.('WAITING_FOR_RESPONSE');
toastId.current = toast({
title: 'Please wait', title: 'Please wait',
description: 'Refetching metadata request sent', description: 'Refetching metadata request sent',
icon: <Spinner size="sm" mr={ 2 }/>,
status: 'warning', status: 'warning',
variant: 'subtle', duration: null,
isClosable: false,
}); });
setStatus?.('WAITING_FOR_RESPONSE');
timeoutId.current = window.setTimeout(handleRefreshError, 2 * MINUTE); timeoutId.current = window.setTimeout(handleRefreshError, 2 * MINUTE);
}) })
.catch(() => { .catch(() => {
...@@ -63,7 +67,6 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -63,7 +67,6 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
title: 'Error', title: 'Error',
description: 'Unable to initialize metadata update', description: 'Unable to initialize metadata update',
status: 'warning', status: 'warning',
variant: 'subtle',
}); });
setStatus?.('ERROR'); setStatus?.('ERROR');
}); });
...@@ -112,12 +115,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -112,12 +115,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
}; };
}); });
toast.closeAll(); toastId.current && toast.update(toastId.current, {
toast({
title: 'Success!', title: 'Success!',
description: 'Metadata has been refreshed', description: 'Metadata has been refreshed',
status: 'success', status: 'success',
variant: 'subtle', duration: 5 * SECOND,
isClosable: true,
}); });
setStatus?.('SUCCESS'); setStatus?.('SUCCESS');
...@@ -138,6 +141,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => { ...@@ -138,6 +141,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
handler: handleSocketMessage, handler: handleSocketMessage,
}); });
React.useEffect(() => {
return () => {
timeoutId.current && window.clearTimeout(timeoutId.current);
toastId.current && toast.close(toastId.current);
};
// run only on mount/unmount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ( return (
<Modal isOpen={ status === 'MODAL_OPENED' } onClose={ handleModalClose } size={{ base: 'full', lg: 'sm' }}> <Modal isOpen={ status === 'MODAL_OPENED' } onClose={ handleModalClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/> <ModalOverlay/>
......
...@@ -8605,10 +8605,10 @@ damerau-levenshtein@^1.0.8: ...@@ -8605,10 +8605,10 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
dappscout-iframe@0.2.1: dappscout-iframe@0.2.2:
version "0.2.1" version "0.2.2"
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.1.tgz#b4718515ee4f00022af3912fac6ca1a321c156f9" resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.2.tgz#de3df6abccad68a27c9304300b92d86ec0ab1c59"
integrity sha512-EsiAAEk2I6hN+/E8o45WUn4BFd7aN8UvBwsIcOH79WOly0GOOHkPEO/puPkBCV0EcdxBsZIfssx3X0fSWVz5Bw== integrity sha512-ASOimgBRG61pSYQLdYGWePdiO3IsfTEgWZ6CHpZ4XQjJRmj1+WiWF56vFTeLIo5aucp+2+6oRCJ8KgKHGVDj0A==
dependencies: dependencies:
react "^18.2.0" react "^18.2.0"
react-dom "^18.2.0" react-dom "^18.2.0"
......
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