diff --git a/lib/hooks/useToast.tsx b/lib/hooks/useToast.tsx index 4110c513c1..599ac0e3e8 100644 --- a/lib/hooks/useToast.tsx +++ b/lib/hooks/useToast.tsx @@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC containerStyle: { margin: 8, }, + variant: 'subtle', }; export default function useToastModified() { diff --git a/package.json b/package.json index 2e6d782c4e..223f80e845 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,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", diff --git a/playwright/fixtures/mockRpcResponse.ts b/playwright/fixtures/mockRpcResponse.ts new file mode 100644 index 0000000000..f65bf81974 --- /dev/null +++ b/playwright/fixtures/mockRpcResponse.ts @@ -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; + +// WIP +const fixture: TestFixture = 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; diff --git a/playwright/lib.tsx b/playwright/lib.tsx index dffe7f3715..586f980bf0 100644 --- a/playwright/lib.tsx +++ b/playwright/lib.tsx @@ -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({ 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 diff --git a/ui/blocks/BlocksTableItem.tsx b/ui/blocks/BlocksTableItem.tsx index 1560bfa1f3..1e230e4788 100644 --- a/ui/blocks/BlocksTableItem.tsx +++ b/ui/blocks/BlocksTableItem.tsx @@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => { ) : data.tx_count } + + { BigNumber(data.gas_used || 0).toFormat() } + + + + + + + { data.gas_target_percentage && ( + <> + + + + ) } + + { !isRollup && !config.UI.views.block.hiddenFields?.total_reward && ( - { BigNumber(data.gas_used || 0).toFormat() } - - - - - - - { data.gas_target_percentage && ( - <> - - - - ) } - + + { totalReward.toFixed(8) } + ) } - - - { totalReward.toFixed(8) } - - { !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && ( diff --git a/ui/marketplace/MarketplaceAppCard.tsx b/ui/marketplace/MarketplaceAppCard.tsx index d35533d2d3..7db60d3ec4 100644 --- a/ui/marketplace/MarketplaceAppCard.tsx +++ b/ui/marketplace/MarketplaceAppCard.tsx @@ -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 }} diff --git a/ui/nameDomains/NameDomainsActionBar.tsx b/ui/nameDomains/NameDomainsActionBar.tsx index a4230f9086..8518abceb2 100644 --- a/ui/nameDomains/NameDomainsActionBar.tsx +++ b/ui/nameDomains/NameDomainsActionBar.tsx @@ -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 } /> diff --git a/ui/pages/GasTracker.tsx b/ui/pages/GasTracker.tsx index 84d8b581a5..69efae2104 100644 --- a/ui/pages/GasTracker.tsx +++ b/ui/pages/GasTracker.tsx @@ -64,21 +64,12 @@ const GasTracker = () => { ); - const content = (() => { + const snippets = (() => { if (!isPlaceholderData && data?.gas_prices?.slow === null && data?.gas_prices.average === null && data.gas_prices.fast === null) { - return No data available yet; + return No recent data available; } - return ( - <> - { data?.gas_prices && } - { config.features.stats.isEnabled && ( - - - - ) } - - ); + return data?.gas_prices ? : null; })(); return ( @@ -88,7 +79,12 @@ const GasTracker = () => { secondRow={ titleSecondRow } withTextAd /> - { content } + { snippets } + { config.features.stats.isEnabled && ( + + + + ) } ); }; diff --git a/ui/pages/MarketplaceApp.pw.tsx b/ui/pages/MarketplaceApp.pw.tsx index 08d1be9b6d..7f35c17f7b 100644 --- a/ui/pages/MarketplaceApp.pw.tsx +++ b/ui/pages/MarketplaceApp.pw.tsx @@ -33,9 +33,9 @@ const testFn: Parameters[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); }); diff --git a/ui/pages/__screenshots__/Blocks.pw.tsx_default_hidden-fields-1.png b/ui/pages/__screenshots__/Blocks.pw.tsx_default_hidden-fields-1.png index fa0ba6445e..96d1c2e66a 100644 Binary files a/ui/pages/__screenshots__/Blocks.pw.tsx_default_hidden-fields-1.png and b/ui/pages/__screenshots__/Blocks.pw.tsx_default_hidden-fields-1.png differ diff --git a/ui/pages/__screenshots__/NameDomains.pw.tsx_default_default-view-mobile-1.png b/ui/pages/__screenshots__/NameDomains.pw.tsx_default_default-view-mobile-1.png index 49dedc7bc7..4aa9eaba13 100644 Binary files a/ui/pages/__screenshots__/NameDomains.pw.tsx_default_default-view-mobile-1.png and b/ui/pages/__screenshots__/NameDomains.pw.tsx_default_default-view-mobile-1.png differ diff --git a/ui/pages/__screenshots__/NameDomains.pw.tsx_mobile_default-view-mobile-1.png b/ui/pages/__screenshots__/NameDomains.pw.tsx_mobile_default-view-mobile-1.png index 2af4ecf5ae..90abdecd57 100644 Binary files a/ui/pages/__screenshots__/NameDomains.pw.tsx_mobile_default-view-mobile-1.png and b/ui/pages/__screenshots__/NameDomains.pw.tsx_mobile_default-view-mobile-1.png differ diff --git a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-2.png b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-2.png index 3958a1ced0..857a2d1515 100644 Binary files a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-2.png and b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-2.png differ diff --git a/ui/shared/Toast.tsx b/ui/shared/Toast.tsx index 67451e02f6..c886a67438 100644 --- a/ui/shared/Toast.tsx +++ b/ui/shared/Toast.tsx @@ -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" > - { title && { title } } + { title && { icon }{ title } } { description && ( { description } diff --git a/ui/tokenInstance/TokenInstanceMetadataFetcher.tsx b/ui/tokenInstance/TokenInstanceMetadataFetcher.tsx index 5a8af64044..255ed645c4 100644 --- a/ui/tokenInstance/TokenInstanceMetadataFetcher.tsx +++ b/ui/tokenInstance/TokenInstanceMetadataFetcher.tsx @@ -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(); + const toastId = React.useRef(); 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: , 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 ( diff --git a/yarn.lock b/yarn.lock index 2e79f65273..8169089edc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8595,10 +8595,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== -dappscout-iframe@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.1.tgz#b4718515ee4f00022af3912fac6ca1a321c156f9" - integrity sha512-EsiAAEk2I6hN+/E8o45WUn4BFd7aN8UvBwsIcOH79WOly0GOOHkPEO/puPkBCV0EcdxBsZIfssx3X0fSWVz5Bw== +dappscout-iframe@0.2.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" @@ -14846,16 +14846,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14983,14 +14974,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16150,7 +16134,7 @@ word-wrap@^1.2.5, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -16168,15 +16152,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"