Merge pull request #2054 from blockscout/release/v1_32_0-fixes
Fixes for release v.1.32.0
@@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps>
|
||||
containerStyle: {
|
||||
margin: 8,
|
||||
},
|
||||
variant: 'subtle',
|
||||
};
|
||||
|
||||
export default function useToastModified() {
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"chakra-react-select": "^4.4.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"d3": "^7.6.1",
|
||||
"dappscout-iframe": "0.2.1",
|
||||
"dappscout-iframe": "0.2.2",
|
||||
"dayjs": "^1.11.5",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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';
|
||||
import * as mockContractReadResponse from './fixtures/mockContractReadResponse';
|
||||
import * as mockEnvs from './fixtures/mockEnvs';
|
||||
import * as mockFeatures from './fixtures/mockFeatures';
|
||||
import * as mockRpcResponse from './fixtures/mockRpcResponse';
|
||||
import * as mockTextAd from './fixtures/mockTextAd';
|
||||
import * as render from './fixtures/render';
|
||||
import * as socketServer from './fixtures/socketServer';
|
||||
@@ -20,6 +21,7 @@ interface Fixtures {
|
||||
mockContractReadResponse: mockContractReadResponse.MockContractReadResponseFixture;
|
||||
mockEnvs: mockEnvs.MockEnvsFixture;
|
||||
mockFeatures: mockFeatures.MockFeaturesFixture;
|
||||
mockRpcResponse: mockRpcResponse.MockRpcResponseFixture;
|
||||
createSocket: socketServer.CreateSocketFixture;
|
||||
injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider;
|
||||
mockTextAd: mockTextAd.MockTextAdFixture;
|
||||
@@ -33,6 +35,7 @@ const test = base.extend<Fixtures>({
|
||||
mockContractReadResponse: mockContractReadResponse.default,
|
||||
mockEnvs: mockEnvs.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
|
||||
// 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
|
||||
|
||||
@@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
|
||||
</Skeleton>
|
||||
) : data.tx_count }
|
||||
</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 && (
|
||||
<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>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ totalReward.toFixed(8) }
|
||||
</Skeleton>
|
||||
</Td>
|
||||
) }
|
||||
<Td fontSize="sm">
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ totalReward.toFixed(8) }
|
||||
</Skeleton>
|
||||
</Td>
|
||||
{ !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && (
|
||||
<Td fontSize="sm">
|
||||
<Flex alignItems="center" columnGap={ 2 }>
|
||||
|
||||
@@ -179,7 +179,7 @@ const MarketplaceAppCard = ({
|
||||
showContractList={ showContractList }
|
||||
isLoading={ isLoading }
|
||||
source="Discovery view"
|
||||
popoverPlacement={ isMobile ? 'bottom-end' : 'bottom-start' }
|
||||
popoverPlacement={ isMobile ? 'bottom-end' : 'left' }
|
||||
position="absolute"
|
||||
right={{ base: 3, md: 5 }}
|
||||
top={{ base: '10px', md: 5 }}
|
||||
|
||||
@@ -53,7 +53,7 @@ const NameDomainsActionBar = ({
|
||||
minW={{ base: 'auto', lg: '250px' }}
|
||||
size="xs"
|
||||
onChange={ onSearchChange }
|
||||
placeholder="Search by name"
|
||||
placeholder="Search by name or address"
|
||||
initialValue={ searchTerm }
|
||||
isLoading={ isInitialLoading }
|
||||
/>
|
||||
|
||||
@@ -64,21 +64,12 @@ const GasTracker = () => {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const content = (() => {
|
||||
const snippets = (() => {
|
||||
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 (
|
||||
<>
|
||||
{ data?.gas_prices && <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> }
|
||||
{ config.features.stats.isEnabled && (
|
||||
<Box mt={ 12 }>
|
||||
<GasTrackerChart/>
|
||||
</Box>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
return data?.gas_prices ? <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> : null;
|
||||
})();
|
||||
|
||||
return (
|
||||
@@ -88,7 +79,12 @@ const GasTracker = () => {
|
||||
secondRow={ titleSecondRow }
|
||||
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
|
||||
await expect(component).toHaveScreenshot();
|
||||
};
|
||||
|
||||
test('base view +@dark-mode', testFn);
|
||||
test.fixme('base view +@dark-mode', testFn);
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
test('base view', testFn);
|
||||
test.fixme('base view', testFn);
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
@@ -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 ?
|
||||
{
|
||||
@@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro
|
||||
maxWidth="400px"
|
||||
>
|
||||
<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 && (
|
||||
<AlertDescription id={ ids?.description } display="block">
|
||||
{ description }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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 React from 'react';
|
||||
import ReCaptcha from 'react-google-recaptcha';
|
||||
@@ -9,7 +10,7 @@ import type { TokenInstance } from 'types/api/token';
|
||||
import config from 'configs/app';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import { MINUTE } from 'lib/consts';
|
||||
import { MINUTE, SECOND } from 'lib/consts';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import useSocketChannel from 'lib/socket/useSocketChannel';
|
||||
import useSocketMessage from 'lib/socket/useSocketMessage';
|
||||
@@ -23,6 +24,7 @@ interface Props {
|
||||
|
||||
const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
const timeoutId = React.useRef<number>();
|
||||
const toastId = React.useRef<ToastId>();
|
||||
|
||||
const { status, setStatus } = useMetadataUpdateContext() || {};
|
||||
const apiFetch = useApiFetch();
|
||||
@@ -31,12 +33,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
|
||||
const handleRefreshError = React.useCallback(() => {
|
||||
setStatus?.('ERROR');
|
||||
toast.closeAll();
|
||||
toast({
|
||||
toastId.current && toast.update(toastId.current, {
|
||||
title: 'Error',
|
||||
description: 'The refreshing process has failed. Please try again.',
|
||||
status: 'warning',
|
||||
variant: 'subtle',
|
||||
duration: 5 * SECOND,
|
||||
isClosable: true,
|
||||
});
|
||||
}, [ setStatus, toast ]);
|
||||
|
||||
@@ -49,13 +51,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
setStatus?.('WAITING_FOR_RESPONSE');
|
||||
toastId.current = toast({
|
||||
title: 'Please wait',
|
||||
description: 'Refetching metadata request sent',
|
||||
icon: <Spinner size="sm" mr={ 2 }/>,
|
||||
status: 'warning',
|
||||
variant: 'subtle',
|
||||
duration: null,
|
||||
isClosable: false,
|
||||
});
|
||||
setStatus?.('WAITING_FOR_RESPONSE');
|
||||
timeoutId.current = window.setTimeout(handleRefreshError, 2 * MINUTE);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -63,7 +67,6 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
title: 'Error',
|
||||
description: 'Unable to initialize metadata update',
|
||||
status: 'warning',
|
||||
variant: 'subtle',
|
||||
});
|
||||
setStatus?.('ERROR');
|
||||
});
|
||||
@@ -112,12 +115,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
};
|
||||
});
|
||||
|
||||
toast.closeAll();
|
||||
toast({
|
||||
toastId.current && toast.update(toastId.current, {
|
||||
title: 'Success!',
|
||||
description: 'Metadata has been refreshed',
|
||||
status: 'success',
|
||||
variant: 'subtle',
|
||||
duration: 5 * SECOND,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
setStatus?.('SUCCESS');
|
||||
@@ -138,6 +141,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
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 (
|
||||
<Modal isOpen={ status === 'MODAL_OPENED' } onClose={ handleModalClose } size={{ base: 'full', lg: 'sm' }}>
|
||||
<ModalOverlay/>
|
||||
|
||||
@@ -8605,10 +8605,10 @@ damerau-levenshtein@^1.0.8:
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
|
||||
|
||||
[email protected].1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.1.tgz#b4718515ee4f00022af3912fac6ca1a321c156f9"
|
||||
integrity sha512-EsiAAEk2I6hN+/E8o45WUn4BFd7aN8UvBwsIcOH79WOly0GOOHkPEO/puPkBCV0EcdxBsZIfssx3X0fSWVz5Bw==
|
||||
[email protected].2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.2.tgz#de3df6abccad68a27c9304300b92d86ec0ab1c59"
|
||||
integrity sha512-ASOimgBRG61pSYQLdYGWePdiO3IsfTEgWZ6CHpZ4XQjJRmj1+WiWF56vFTeLIo5aucp+2+6oRCJ8KgKHGVDj0A==
|
||||
dependencies:
|
||||
react "^18.2.0"
|
||||
react-dom "^18.2.0"
|
||||
|
||||