diff --git a/configs/envs/.env.garnet b/configs/envs/.env.garnet index a6763934bc..44720ed11c 100644 --- a/configs/envs/.env.garnet +++ b/configs/envs/.env.garnet @@ -1,50 +1,53 @@ -# Set of ENVs for Garnet (dev only) -# https://https://explorer.garnetchain.com// +# Set of ENVs for Garnet Testnet network explorer +# https://explorer.garnetchain.com +# This is an auto-generated file. To update all values, run "yarn preset:sync --name=garnet" -# app configuration +# Local ENVs NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME="Garnet Testnet" -NEXT_PUBLIC_NETWORK_ID=17069 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_RPC_URL=https://partner-rpc.garnetchain.com/tireless-strand-dreamt-overcome - -# api configuration -NEXT_PUBLIC_API_HOST=explorer.garnetchain.com +# Instance ENVs +NEXT_PUBLIC_AD_BANNER_PROVIDER=none +NEXT_PUBLIC_AD_TEXT_PROVIDER=none +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -## views +NEXT_PUBLIC_API_HOST=explorer.garnetchain.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] -# app features -NEXT_PUBLIC_APP_INSTANCE=local -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d -NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login -NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/redstone-testnet.json NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/redstone.json -NEXT_PUBLIC_AD_BANNER_PROVIDER=none -## sidebar +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x5b0ba69f2cf5fbc6da96b6cf475c5521f7a385efd9d68673f69c1fc54f737a52 +NEXT_PUBLIC_HAS_MUD_FRAMEWORK=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgb(169, 31, 47)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://redstone-lattice.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/garnet.svg -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/garnet.svg NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/garnet-dark.svg +NEXT_PUBLIC_NETWORK_ID=17069 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/garnet.svg NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/garnet-dark.svg -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgb(169, 31, 47) -NEXT_PUBLIC_OG_DESCRIPTION="Redstone is the home for onchain games, worlds, and other MUD applications" -# rollup -NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_NETWORK_NAME=Garnet Testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://partner-rpc.garnetchain.com/tireless-strand-dreamt-overcome +NEXT_PUBLIC_NETWORK_SHORT_NAME=Garnet Testnet +NEXT_PUBLIC_OG_DESCRIPTION=Redstone is the home for onchain games, worlds, and other MUD applications +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/garnet.png NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-holesky.blockscout.com/ NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://garnet.qry.live/withdraw -NEXT_PUBLIC_HAS_MUD_FRAMEWORK=true \ No newline at end of file +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_STATS_API_HOST=https://stats-redstone-garnet.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/theme/components/Badge.ts b/theme/components/Badge.ts index b673b528f6..01cb9fb948 100644 --- a/theme/components/Badge.ts +++ b/theme/components/Badge.ts @@ -24,6 +24,20 @@ const variantSubtle = defineStyle((props) => { }; } + if (c === 'black-blue') { + return { + bg: mode('blue.50', 'blue.800')(props), + color: mode('blackAlpha.800', 'whiteAlpha.800')(props), + }; + } + + if (c === 'black-purple') { + return { + bg: mode('purple.100', 'purple.800')(props), + color: mode('blackAlpha.800', 'whiteAlpha.800')(props), + }; + } + return { bg: mode(`${ c }.50`, `${ c }.800`)(props), color: mode(`${ c }.500`, `${ c }.100`)(props), diff --git a/theme/components/Button/Button.ts b/theme/components/Button/Button.ts index b43731816e..7f6ea7605f 100644 --- a/theme/components/Button/Button.ts +++ b/theme/components/Button/Button.ts @@ -112,6 +112,9 @@ const variantRadioGroup = defineStyle((props) => { _active: { bgColor: 'none', }, + _notFirst: { + borderLeftWidth: 0, + }, // We have a special state for this button variant that serves as a popover trigger. // When any items (filters) are selected in the popover, the button should change its background and text color. // The last CSS selector is for redefining styles for the TabList component. diff --git a/theme/components/Tabs.ts b/theme/components/Tabs.ts index b07ade5e11..f64bec2886 100644 --- a/theme/components/Tabs.ts +++ b/theme/components/Tabs.ts @@ -51,9 +51,6 @@ const variantRadioGroup = definePartsStyle((props) => { &[data-selected=true][aria-selected=true] `], borderRadius: 'none', - _notFirst: { - borderLeftWidth: 0, - }, '&[role="tab"]': { _first: { borderTopLeftRadius: 'base', diff --git a/tools/preset-sync/index.ts b/tools/preset-sync/index.ts index acf8ba84c2..ab19ee3469 100755 --- a/tools/preset-sync/index.ts +++ b/tools/preset-sync/index.ts @@ -10,6 +10,7 @@ const PRESETS = { eth: 'https://eth.blockscout.com', eth_goerli: 'https://eth-goerli.blockscout.com', eth_sepolia: 'https://eth-sepolia.blockscout.com', + garnet: 'https://explorer.garnetchain.com', gnosis: 'https://gnosis.blockscout.com', optimism: 'https://optimism.blockscout.com', optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com', diff --git a/types/api/account.ts b/types/api/account.ts index 50af396ea7..4e755fe3db 100644 --- a/types/api/account.ts +++ b/types/api/account.ts @@ -1,3 +1,5 @@ +import type { Abi } from 'viem'; + import type { AddressParam } from './addressParams'; export interface AddressTag { address_hash: string; @@ -111,22 +113,7 @@ export interface CustomAbi { id: number; contract_address_hash: string; contract_address: AddressParam; - abi: Array; -} - -export interface AbiItem { - type: 'function'; - stateMutability: 'nonpayable' | 'view'; - payable: boolean; - outputs: Array; - name: string; - inputs: Array; - constant: boolean; -} - -interface AbiInputOutput { - type: 'uint256' | 'address'; - name: string; + abi: Abi; } export type WatchlistErrors = { diff --git a/ui/address/AddressContract.pw.tsx b/ui/address/AddressContract.pw.tsx index 5374c4ec21..dcdad6c3c4 100644 --- a/ui/address/AddressContract.pw.tsx +++ b/ui/address/AddressContract.pw.tsx @@ -64,12 +64,12 @@ test.describe('ABI functionality', () => { await expect(component.getByRole('button', { name: 'Connect wallet' })).toBeVisible(); await component.getByText('setReserveInterestRateStrategyAddress').click(); - await expect(component.getByLabel('2.').getByRole('button', { name: 'Simulate' })).toBeEnabled(); - await expect(component.getByLabel('2.').getByRole('button', { name: 'Write' })).toBeEnabled(); + await expect(component.getByLabel('4.').getByRole('button', { name: 'Simulate' })).toBeEnabled(); + await expect(component.getByLabel('4.').getByRole('button', { name: 'Write' })).toBeEnabled(); await component.getByText('pause').click(); - await expect(component.getByLabel('5.').getByRole('button', { name: 'Simulate' })).toBeHidden(); - await expect(component.getByLabel('5.').getByRole('button', { name: 'Write' })).toBeEnabled(); + await expect(component.getByLabel('7.').getByRole('button', { name: 'Simulate' })).toBeHidden(); + await expect(component.getByLabel('7.').getByRole('button', { name: 'Write' })).toBeEnabled(); }); test('write, no wallet client', async({ render, createSocket, mockEnvs }) => { @@ -86,11 +86,11 @@ test.describe('ABI functionality', () => { await expect(component.getByRole('button', { name: 'Connect wallet' })).toBeHidden(); await component.getByText('setReserveInterestRateStrategyAddress').click(); - await expect(component.getByLabel('2.').getByRole('button', { name: 'Simulate' })).toBeEnabled(); - await expect(component.getByLabel('2.').getByRole('button', { name: 'Write' })).toBeDisabled(); + await expect(component.getByLabel('4.').getByRole('button', { name: 'Simulate' })).toBeEnabled(); + await expect(component.getByLabel('4.').getByRole('button', { name: 'Write' })).toBeDisabled(); await component.getByText('pause').click(); - await expect(component.getByLabel('5.').getByRole('button', { name: 'Simulate' })).toBeHidden(); - await expect(component.getByLabel('5.').getByRole('button', { name: 'Write' })).toBeDisabled(); + await expect(component.getByLabel('7.').getByRole('button', { name: 'Simulate' })).toBeHidden(); + await expect(component.getByLabel('7.').getByRole('button', { name: 'Write' })).toBeDisabled(); }); }); diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png index 743da2862d..2eed2112c5 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png index 2490632952..0f75d307f1 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png index 2c9f8d372c..b757aaac2f 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png index f2bce20aea..1d900a78c9 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png index 50963d545a..d85120fe5d 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png index 013acf9a15..3bd4e9dd2f 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png differ diff --git a/ui/address/contract/methods/ContractAbi.tsx b/ui/address/contract/methods/ContractAbi.tsx index c3502ccf1e..72f7fe6295 100644 --- a/ui/address/contract/methods/ContractAbi.tsx +++ b/ui/address/contract/methods/ContractAbi.tsx @@ -4,18 +4,24 @@ import React from 'react'; import type { SmartContractMethod } from './types'; +import { route } from 'nextjs-routes'; + +import { apos } from 'lib/html-entities'; +import LinkInternal from 'ui/shared/links/LinkInternal'; + import ContractAbiItem from './ContractAbiItem'; import useFormSubmit from './useFormSubmit'; import useScrollToMethod from './useScrollToMethod'; interface Props { abi: Array; + visibleItems?: Array; addressHash: string; tab: string; sourceAddress?: string; } -const ContractAbi = ({ abi, addressHash, sourceAddress, tab }: Props) => { +const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Props) => { const [ expandedSections, setExpandedSections ] = React.useState>(abi.length === 1 ? [ 0 ] : []); const [ id, setId ] = React.useState(0); @@ -43,8 +49,10 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab }: Props) => { setId((id) => id + 1); }, []); + const hasVisibleItems = !visibleItems || visibleItems.length > 0; + return ( - <> +
Contract information { abi.length > 1 && ( @@ -58,9 +66,10 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab }: Props) => { { abi.map((item, index) => ( { /> )) } - + { !hasVisibleItems && ( +
+
Couldn{ apos }t find any method that matches your query.
+
+ You can use custom ABI for this contract without verifying the contract in the{ ' ' } + + Custom ABI + + { ' ' }tab. +
+
+ ) } +
); }; diff --git a/ui/address/contract/methods/ContractAbiItem.tsx b/ui/address/contract/methods/ContractAbiItem.tsx index 2e97d3d385..1b3bb6621d 100644 --- a/ui/address/contract/methods/ContractAbiItem.tsx +++ b/ui/address/contract/methods/ContractAbiItem.tsx @@ -1,4 +1,4 @@ -import { AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Alert, Box } from '@chakra-ui/react'; +import { AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Alert, Box, Tag } from '@chakra-ui/react'; import React from 'react'; import { Element } from 'react-scroll'; @@ -7,12 +7,12 @@ import type { FormSubmitHandler, SmartContractMethod } from './types'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import Tag from 'ui/shared/chakra/Tag'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import Hint from 'ui/shared/Hint'; import ContractMethodForm from './form/ContractMethodForm'; -import { getElementName } from './useScrollToMethod'; +import { getElementId, getElementName } from './useScrollToMethod'; +import { isReadMethod } from './utils'; interface Props { data: SmartContractMethod; @@ -22,16 +22,13 @@ interface Props { sourceAddress?: string; tab: string; onSubmit: FormSubmitHandler; + isVisible?: boolean; } -const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onSubmit }: Props) => { +const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onSubmit, isVisible = true }: Props) => { const [ attempt, setAttempt ] = React.useState(0); const url = React.useMemo(() => { - if (!('method_id' in data)) { - return ''; - } - return config.app.baseUrl + route({ pathname: '/address/[hash]', query: { @@ -39,7 +36,7 @@ const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onS tab, ...(sourceAddress ? { source_address: sourceAddress } : {}), }, - hash: data.method_id, + hash: getElementId(data), }); }, [ addressHash, data, tab, sourceAddress ]); @@ -55,39 +52,57 @@ const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onS setAttempt((prev) => prev + 1); }, []); + const isRead = isReadMethod(data); + return ( - + { ({ isExpanded }) => ( <> - - - { 'method_id' in data && } - + + + + { index + 1 }. { data.type === 'fallback' || data.type === 'receive' ? data.type : data.name } - - { data.type === 'fallback' && ( - - ) } - { data.type === 'receive' && ( - + ) } + { data.type === 'receive' && ( + - ) } + } + ml={ 1 } + /> + ) } + + { isRead ? 'read' : 'write' } { 'method_id' in data && ( - <> - { data.method_id } - - + + { data.method_id } + + ) } diff --git a/ui/address/contract/methods/ContractConnectWallet.tsx b/ui/address/contract/methods/ContractConnectWallet.tsx index cd81cc0619..0ddfa6ae87 100644 --- a/ui/address/contract/methods/ContractConnectWallet.tsx +++ b/ui/address/contract/methods/ContractConnectWallet.tsx @@ -50,7 +50,7 @@ const ContractConnectWallet = ({ isLoading }: Props) => { })(); return ( - + { content } diff --git a/ui/address/contract/methods/ContractCustomAbiAlert.tsx b/ui/address/contract/methods/ContractCustomAbiAlert.tsx index d381d87fd3..633aa43cb4 100644 --- a/ui/address/contract/methods/ContractCustomAbiAlert.tsx +++ b/ui/address/contract/methods/ContractCustomAbiAlert.tsx @@ -8,7 +8,7 @@ interface Props { const ContractCustomAbiAlert = ({ isLoading }: Props) => { return ( - + Note: Contract with custom ABI is only meant for debugging purpose and it is the user’s responsibility to ensure that the provided ABI matches the contract, otherwise errors may occur or results returned may be incorrect. Blockscout is not responsible for any losses that arise from the use of Read & Write contract. diff --git a/ui/address/contract/methods/ContractMethods.tsx b/ui/address/contract/methods/ContractMethods.tsx deleted file mode 100644 index 4c13085d98..0000000000 --- a/ui/address/contract/methods/ContractMethods.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { MethodType, SmartContractMethod } from './types'; - -import getQueryParamString from 'lib/router/getQueryParamString'; -import ContentLoader from 'ui/shared/ContentLoader'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; - -import ContractAbi from './ContractAbi'; - -interface Props { - abi: Array; - isLoading?: boolean; - isError?: boolean; - type: MethodType; - sourceAddress?: string; -} - -const ContractMethods = ({ abi, isLoading, isError, type, sourceAddress }: Props) => { - - const router = useRouter(); - - const tab = getQueryParamString(router.query.tab); - const addressHash = getQueryParamString(router.query.hash); - - if (isLoading) { - return ; - } - - if (isError) { - return ; - } - - if (abi.length === 0) { - const typeText = type === 'all' ? '' : type; - return No public { typeText } functions were found for this contract.; - } - - return ; -}; - -export default React.memo(ContractMethods); diff --git a/ui/address/contract/methods/ContractMethodsContainer.tsx b/ui/address/contract/methods/ContractMethodsContainer.tsx new file mode 100644 index 0000000000..5ed7e54623 --- /dev/null +++ b/ui/address/contract/methods/ContractMethodsContainer.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import type { MethodType } from './types'; + +import ContentLoader from 'ui/shared/ContentLoader'; +import DataFetchAlert from 'ui/shared/DataFetchAlert'; + +interface Props { + isLoading?: boolean; + isError?: boolean; + isEmpty?: boolean; + type: MethodType; + children: JSX.Element; +} + +const ContractMethodsContainer = ({ isLoading, isError, isEmpty, type, children }: Props) => { + + if (isLoading) { + return ; + } + + if (isError) { + return ; + } + + if (isEmpty) { + const typeText = type === 'all' ? '' : type; + return No public { typeText } functions were found for this contract.; + } + + return children; +}; + +export default React.memo(ContractMethodsContainer); diff --git a/ui/address/contract/methods/ContractMethodsCustom.pw.tsx b/ui/address/contract/methods/ContractMethodsCustom.pw.tsx new file mode 100644 index 0000000000..7a7c05f063 --- /dev/null +++ b/ui/address/contract/methods/ContractMethodsCustom.pw.tsx @@ -0,0 +1,46 @@ +import type { BrowserContext } from '@playwright/test'; +import React from 'react'; +import type { AbiItem } from 'viem'; + +import * as addressMock from 'mocks/address/address'; +import * as methodsMock from 'mocks/contract/methods'; +import { contextWithAuth } from 'playwright/fixtures/auth'; +import { test, expect } from 'playwright/lib'; + +import ContractMethodsCustom from './ContractMethodsCustom'; + +const addressHash = addressMock.hash; + +const authTest = test.extend<{ context: BrowserContext }>({ + context: contextWithAuth, +}); + +authTest('without data', async({ render }) => { + const hooksConfig = { + router: { + query: { hash: addressHash, tab: 'read_write_custom_methods' }, + }, + }; + + const component = await render(, { hooksConfig }); + await expect(component).toHaveScreenshot(); +}); + +authTest('with data', async({ render, mockApiResponse }) => { + const abi: Array = [ ...methodsMock.read, ...methodsMock.write ]; + await mockApiResponse('custom_abi', [ { + abi, + contract_address_hash: addressHash, + contract_address: addressMock.withName, + id: 1, + name: 'Test', + } ]); + const hooksConfig = { + router: { + query: { hash: addressHash, tab: 'read_write_custom_methods' }, + }, + }; + + const component = await render(, { hooksConfig }); + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/address/contract/methods/ContractMethodsCustom.tsx b/ui/address/contract/methods/ContractMethodsCustom.tsx index abb86494ec..eb9b677513 100644 --- a/ui/address/contract/methods/ContractMethodsCustom.tsx +++ b/ui/address/contract/methods/ContractMethodsCustom.tsx @@ -1,25 +1,126 @@ +import { Button, Flex, Skeleton, useDisclosure } from '@chakra-ui/react'; +import { useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; import React from 'react'; -import type { MethodType, SmartContractMethod } from './types'; +import type { SmartContract } from 'types/api/contract'; +import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal'; +import RawDataSnippet from 'ui/shared/RawDataSnippet'; +import AuthGuard from 'ui/snippets/auth/AuthGuard'; +import useIsAuth from 'ui/snippets/auth/useIsAuth'; + +import ContractAbi from './ContractAbi'; import ContractConnectWallet from './ContractConnectWallet'; import ContractCustomAbiAlert from './ContractCustomAbiAlert'; -import ContractMethods from './ContractMethods'; +import ContractMethodsContainer from './ContractMethodsContainer'; +import ContractMethodsFilters from './ContractMethodsFilters'; +import useMethodsFilters from './useMethodsFilters'; +import { enrichWithMethodId, isMethod } from './utils'; interface Props { - abi: Array; isLoading?: boolean; - type: MethodType; } -const ContractMethodsCustom = ({ abi, isLoading, type }: Props) => { +const ContractMethodsCustom = ({ isLoading: isLoadingProp }: Props) => { + + const modal = useDisclosure(); + const router = useRouter(); + const queryClient = useQueryClient(); + + const tab = getQueryParamString(router.query.tab); + const addressHash = getQueryParamString(router.query.hash); + + const isAuth = useIsAuth(); + + const customAbiQuery = useApiQuery('custom_abi', { + queryOptions: { + enabled: !isLoadingProp && isAuth, + refetchOnMount: false, + }, + }); + + const contractQueryData = queryClient.getQueryData(getResourceKey('contract', { pathParams: { hash: addressHash } })); + + const isLoading = isLoadingProp || (isAuth && customAbiQuery.isLoading); + + const currentInfo = customAbiQuery.data?.find((item) => item.contract_address_hash.toLowerCase() === addressHash.toLowerCase()); + const modalData = currentInfo ?? (contractQueryData ? { + name: contractQueryData.name || '', + contract_address_hash: addressHash, + } : undefined); + + const abi = React.useMemo(() => { + return currentInfo?.abi + .filter(isMethod) + .map(enrichWithMethodId) ?? []; + }, [ currentInfo ]); + + const filters = useMethodsFilters({ abi }); + + const updateButton = React.useMemo(() => { + return ( + + + + ); + }, [ isLoading, modal.onOpen ]); return ( - <> - - - - + + { currentInfo ? ( + <> + + + + + + + + + + + ) : ( + <> + + Add custom ABIs for this contract and access when logged into your account. Helpful for debugging, + functional testing and contract interaction. + + + { ({ onClick }) => ( + + + + ) } + + + ) } + + ); }; diff --git a/ui/address/contract/methods/ContractMethodsFilters.tsx b/ui/address/contract/methods/ContractMethodsFilters.tsx new file mode 100644 index 0000000000..be06abe20c --- /dev/null +++ b/ui/address/contract/methods/ContractMethodsFilters.tsx @@ -0,0 +1,51 @@ +import { Flex } from '@chakra-ui/react'; +import React from 'react'; + +import type { MethodType } from './types'; + +import FilterInput from 'ui/shared/filters/FilterInput'; +import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup'; + +import type { MethodsFilters } from './useMethodsFilters'; +import { TYPE_FILTER_OPTIONS } from './utils'; + +interface Props { + defaultMethodType: MethodType; + defaultSearchTerm: string; + onChange: (filter: MethodsFilters) => void; + isLoading?: boolean; +} + +const ContractMethodsFilters = ({ defaultMethodType, defaultSearchTerm, onChange, isLoading }: Props) => { + + const handleTypeChange = React.useCallback((value: MethodType) => { + onChange({ type: 'method_type', value }); + }, [ onChange ]); + + const handleSearchTermChange = React.useCallback((value: string) => { + onChange({ type: 'method_name', value }); + }, [ onChange ]); + + return ( + + + name="contract-methods-filter" + defaultValue={ defaultMethodType } + options={ TYPE_FILTER_OPTIONS } + onChange={ handleTypeChange } + w={{ lg: 'fit-content' }} + isLoading={ isLoading } + /> + + + ); +}; + +export default React.memo(ContractMethodsFilters); diff --git a/ui/address/contract/methods/ContractMethodsMudSystem.tsx b/ui/address/contract/methods/ContractMethodsMudSystem.tsx index 87515bfa80..e695c82050 100644 --- a/ui/address/contract/methods/ContractMethodsMudSystem.tsx +++ b/ui/address/contract/methods/ContractMethodsMudSystem.tsx @@ -1,4 +1,4 @@ -import { Box } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; @@ -9,8 +9,11 @@ import getQueryParamString from 'lib/router/getQueryParamString'; import type { Item } from '../ContractSourceAddressSelector'; import ContractSourceAddressSelector from '../ContractSourceAddressSelector'; +import ContractAbi from './ContractAbi'; import ContractConnectWallet from './ContractConnectWallet'; -import ContractMethods from './ContractMethods'; +import ContractMethodsContainer from './ContractMethodsContainer'; +import ContractMethodsFilters from './ContractMethodsFilters'; +import useMethodsFilters from './useMethodsFilters'; import { enrichWithMethodId, isMethod } from './utils'; interface Props { @@ -23,6 +26,7 @@ const ContractMethodsMudSystem = ({ items }: Props) => { const addressHash = getQueryParamString(router.query.hash); const sourceAddress = getQueryParamString(router.query.source_address); + const tab = getQueryParamString(router.query.tab); const [ selectedItem, setSelectedItem ] = React.useState(items.find((item) => item.address === sourceAddress) || items[0]); @@ -38,31 +42,45 @@ const ContractMethodsMudSystem = ({ items }: Props) => { setSelectedItem(item as SmartContractMudSystemItem); }, []); - if (items.length === 0) { - return No MUD System found for this contract.; - } + const abi = React.useMemo(() => { + return systemInfoQuery.data?.abi?.filter(isMethod).map(enrichWithMethodId) || []; + }, [ systemInfoQuery.data?.abi ]); - const abi = systemInfoQuery.data?.abi?.filter(isMethod).map(enrichWithMethodId) || []; + const filters = useMethodsFilters({ abi }); return ( - + - - + + + + - + > + + + ); }; diff --git a/ui/address/contract/methods/ContractMethodsProxy.pw.tsx b/ui/address/contract/methods/ContractMethodsProxy.pw.tsx index 5d4d7516ec..d89e23b05b 100644 --- a/ui/address/contract/methods/ContractMethodsProxy.pw.tsx +++ b/ui/address/contract/methods/ContractMethodsProxy.pw.tsx @@ -20,7 +20,7 @@ test('with one implementation +@mobile', async({ render, mockApiResponse }) => { ]; await mockApiResponse('contract', { ...contractMock.verified, abi: methodsMock.read }, { pathParams: { hash: implementations[0].address } }); - const component = await render(, { hooksConfig }); + const component = await render(, { hooksConfig }); await expect(component).toHaveScreenshot(); }); @@ -36,6 +36,6 @@ test('with multiple implementations +@mobile', async({ render, mockApiResponse } ]; await mockApiResponse('contract', { ...contractMock.verified, abi: methodsMock.read }, { pathParams: { hash: implementations[0].address } }); - const component = await render(, { hooksConfig }); + const component = await render(, { hooksConfig }); await expect(component).toHaveScreenshot(); }); diff --git a/ui/address/contract/methods/ContractMethodsProxy.tsx b/ui/address/contract/methods/ContractMethodsProxy.tsx index 79d645d5c0..372668ecfb 100644 --- a/ui/address/contract/methods/ContractMethodsProxy.tsx +++ b/ui/address/contract/methods/ContractMethodsProxy.tsx @@ -1,29 +1,32 @@ -import { Box } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; -import type { MethodType } from './types'; import type { AddressImplementation } from 'types/api/addressParams'; import useApiQuery from 'lib/api/useApiQuery'; import getQueryParamString from 'lib/router/getQueryParamString'; import ContractSourceAddressSelector from '../ContractSourceAddressSelector'; +import ContractAbi from './ContractAbi'; import ContractConnectWallet from './ContractConnectWallet'; -import ContractMethods from './ContractMethods'; -import { enrichWithMethodId, isReadMethod, isWriteMethod } from './utils'; +import ContractMethodsContainer from './ContractMethodsContainer'; +import ContractMethodsFilters from './ContractMethodsFilters'; +import useMethodsFilters from './useMethodsFilters'; +import { enrichWithMethodId, isMethod } from './utils'; interface Props { - type: MethodType; implementations: Array; isLoading?: boolean; } -const ContractMethodsProxy = ({ type, implementations, isLoading: isInitialLoading }: Props) => { +const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }: Props) => { const router = useRouter(); - const contractAddress = getQueryParamString(router.query.source_address); + const sourceAddress = getQueryParamString(router.query.source_address); + const tab = getQueryParamString(router.query.tab); + const addressHash = getQueryParamString(router.query.hash); - const [ selectedItem, setSelectedItem ] = React.useState(implementations.find((item) => item.address === contractAddress) || implementations[0]); + const [ selectedItem, setSelectedItem ] = React.useState(implementations.find((item) => item.address === sourceAddress) || implementations[0]); const contractQuery = useApiQuery('contract', { pathParams: { hash: selectedItem.address }, @@ -33,28 +36,47 @@ const ContractMethodsProxy = ({ type, implementations, isLoading: isInitialLoadi }, }); - const abi = contractQuery.data?.abi?.filter(type === 'read' ? isReadMethod : isWriteMethod).map(enrichWithMethodId) || []; + const abi = React.useMemo(() => { + return contractQuery.data?.abi?.filter(isMethod).map(enrichWithMethodId) || []; + }, [ contractQuery.data?.abi ]); + + const filters = useMethodsFilters({ abi }); return ( - + - - + + + + - + > + + + ); }; diff --git a/ui/address/contract/methods/ContractMethodsRegular.pw.tsx b/ui/address/contract/methods/ContractMethodsRegular.pw.tsx index eb6b1fb4ee..7e8ae9be3b 100644 --- a/ui/address/contract/methods/ContractMethodsRegular.pw.tsx +++ b/ui/address/contract/methods/ContractMethodsRegular.pw.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import type { SmartContractMethod } from './types'; + import * as addressMock from 'mocks/address/address'; import * as methodsMock from 'mocks/contract/methods'; import { test, expect } from 'playwright/lib'; @@ -8,7 +10,7 @@ import ContractMethodsRegular from './ContractMethodsRegular'; const addressHash = addressMock.hash; -test('read methods', async({ render, mockContractReadResponse }) => { +test('can read method', async({ render, mockContractReadResponse }) => { // for some reason it takes a long time for "wagmi" library to parse response result in the test environment // so I had to increase the test timeout test.slow(); @@ -25,21 +27,21 @@ test('read methods', async({ render, mockContractReadResponse }) => { result: [ 'USDC' ], }); - const component = await render(, { hooksConfig }); + const component = await render(, { hooksConfig }); await component.getByText(/expand all/i).click(); await expect(component.getByText('USDC')).toBeVisible({ timeout: 20_000 }); - await expect(component).toHaveScreenshot(); }); -test('write methods +@dark-mode +@mobile', async({ render }) => { +test('all methods +@dark-mode +@mobile', async({ render }) => { const hooksConfig = { router: { - query: { hash: addressHash, tab: 'write_contract' }, + query: { hash: addressHash, tab: 'read_write_contract' }, }, }; - const component = await render(, { hooksConfig }); + const abi: Array = [ ...methodsMock.read, ...methodsMock.write ]; + const component = await render(, { hooksConfig }); await component.getByText(/expand all/i).click(); await expect(component).toHaveScreenshot(); diff --git a/ui/address/contract/methods/ContractMethodsRegular.tsx b/ui/address/contract/methods/ContractMethodsRegular.tsx index 1254d3c8a5..5b9956a5de 100644 --- a/ui/address/contract/methods/ContractMethodsRegular.tsx +++ b/ui/address/contract/methods/ContractMethodsRegular.tsx @@ -1,23 +1,44 @@ +import { Flex } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; import React from 'react'; -import type { MethodType, SmartContractMethod } from './types'; +import type { SmartContractMethod } from './types'; +import getQueryParamString from 'lib/router/getQueryParamString'; + +import ContractAbi from './ContractAbi'; import ContractConnectWallet from './ContractConnectWallet'; -import ContractMethods from './ContractMethods'; +import ContractMethodsContainer from './ContractMethodsContainer'; +import ContractMethodsFilters from './ContractMethodsFilters'; +import useMethodsFilters from './useMethodsFilters'; interface Props { abi: Array; isLoading?: boolean; - type: MethodType; } -const ContractMethodsRegular = ({ abi, isLoading, type }: Props) => { +const ContractMethodsRegular = ({ abi, isLoading }: Props) => { + + const router = useRouter(); + + const tab = getQueryParamString(router.query.tab); + const addressHash = getQueryParamString(router.query.hash); + + const filters = useMethodsFilters({ abi }); return ( - <> + - - + + + + + ); }; diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_with-data-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_with-data-1.png new file mode 100644 index 0000000000..760b7f0b9c Binary files /dev/null and b/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_with-data-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_without-data-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_without-data-1.png new file mode 100644 index 0000000000..92a40274e2 Binary files /dev/null and b/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_without-data-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-multiple-implementations-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-multiple-implementations-mobile-1.png index 45e9b13fd8..d83530b66e 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-multiple-implementations-mobile-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-multiple-implementations-mobile-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-one-implementation-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-one-implementation-mobile-1.png index 05b11f3caf..f5e39d47a8 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-one-implementation-mobile-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_default_with-one-implementation-mobile-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-multiple-implementations-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-multiple-implementations-mobile-1.png index dbe3321d59..aa9c7a7743 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-multiple-implementations-mobile-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-multiple-implementations-mobile-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-one-implementation-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-one-implementation-mobile-1.png index ed6c01ad6d..58a0af5688 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-one-implementation-mobile-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsProxy.pw.tsx_mobile_with-one-implementation-mobile-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_all-methods-dark-mode-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_all-methods-dark-mode-mobile-1.png new file mode 100644 index 0000000000..eeec224eba Binary files /dev/null and b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_all-methods-dark-mode-mobile-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_write-methods-dark-mode-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_write-methods-dark-mode-mobile-1.png deleted file mode 100644 index 584358167b..0000000000 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_write-methods-dark-mode-mobile-1.png and /dev/null differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-dark-mode-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-dark-mode-mobile-1.png new file mode 100644 index 0000000000..34a7666b31 Binary files /dev/null and b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-dark-mode-mobile-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_read-methods-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_read-methods-1.png deleted file mode 100644 index 25da934041..0000000000 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_read-methods-1.png and /dev/null differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_write-methods-dark-mode-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_write-methods-dark-mode-mobile-1.png deleted file mode 100644 index c0bda8cff7..0000000000 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_write-methods-dark-mode-mobile-1.png and /dev/null differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_mobile_all-methods-dark-mode-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_mobile_all-methods-dark-mode-mobile-1.png new file mode 100644 index 0000000000..217f7a6ef2 Binary files /dev/null and b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_mobile_all-methods-dark-mode-mobile-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_mobile_write-methods-dark-mode-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_mobile_write-methods-dark-mode-mobile-1.png deleted file mode 100644 index d1b45cab13..0000000000 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_mobile_write-methods-dark-mode-mobile-1.png and /dev/null differ diff --git a/ui/address/contract/methods/form/ContractMethodFieldInput.tsx b/ui/address/contract/methods/form/ContractMethodFieldInput.tsx index 0c30c36262..7869c1690b 100644 --- a/ui/address/contract/methods/form/ContractMethodFieldInput.tsx +++ b/ui/address/contract/methods/form/ContractMethodFieldInput.tsx @@ -121,7 +121,7 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi { field.value !== undefined && field.value !== '' && } { data.type === 'address' && } - { argTypeMatchInt && (hasTimestampButton ? ( + { argTypeMatchInt && !isNativeCoin && (hasTimestampButton ? ( diff --git a/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx b/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx index 7f79207e9b..8ad048d1f6 100644 --- a/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx +++ b/ui/customAbi/CustomAbiModal/CustomAbiModal.tsx @@ -4,23 +4,24 @@ import type { CustomAbi } from 'types/api/account'; import FormModal from 'ui/shared/FormModal'; -import CustomAbiForm from './CustomAbiForm'; +import CustomAbiForm, { type FormData } from './CustomAbiForm'; type Props = { isOpen: boolean; onClose: () => void; - data?: CustomAbi; + onSuccess?: () => Promise; + data: FormData; } -const CustomAbiModal: React.FC = ({ isOpen, onClose, data }) => { - const title = data ? 'Edit custom ABI' : 'New custom ABI'; - const text = !data ? 'Double check the ABI matches the contract to prevent errors or incorrect results.' : ''; +const CustomAbiModal: React.FC = ({ isOpen, onClose, data, onSuccess }) => { + const title = data && 'id' in data ? 'Edit custom ABI' : 'New custom ABI'; + const text = !(data && 'id' in data) ? 'Double check the ABI matches the contract to prevent errors or incorrect results.' : ''; const [ isAlertVisible, setAlertVisible ] = useState(false); const renderForm = useCallback(() => { - return ; - }, [ data, onClose ]); + return ; + }, [ data, onClose, onSuccess ]); return ( isOpen={ isOpen } diff --git a/ui/pages/Marketplace.tsx b/ui/pages/Marketplace.tsx index 1a3f251c84..f9ee624b36 100644 --- a/ui/pages/Marketplace.tsx +++ b/ui/pages/Marketplace.tsx @@ -116,7 +116,10 @@ const Marketplace = () => { const selectedApp = displayedApps.find(app => app.id === selectedAppId); const handleCategoryChange = React.useCallback((index: number) => { - onCategoryChange(categoryTabs[index].id); + const tabId = categoryTabs[index].id; + if (typeof tabId === 'string') { + onCategoryChange(tabId); + } }, [ categoryTabs, onCategoryChange ]); const handleAppClick = React.useCallback((event: MouseEvent, id: string) => { diff --git a/ui/shared/Tabs/AdaptiveTabsList.tsx b/ui/shared/Tabs/AdaptiveTabsList.tsx index 664648dd57..a249e2800a 100644 --- a/ui/shared/Tabs/AdaptiveTabsList.tsx +++ b/ui/shared/Tabs/AdaptiveTabsList.tsx @@ -110,7 +110,7 @@ const AdaptiveTabsList = (props: Props) => { return ( router.pathname.includes(`[${ key }]`)); + const tabId = Array.isArray(nextTab.id) ? nextTab.id[0] : nextTab.id; router.push( - { pathname: router.pathname, query: { ...queryForPathname, tab: nextTab.id } }, + { pathname: router.pathname, query: { ...queryForPathname, tab: tabId } }, undefined, { shallow: true }, ); diff --git a/ui/shared/Tabs/TabsMenu.tsx b/ui/shared/Tabs/TabsMenu.tsx index 3fd2706817..f125edbe02 100644 --- a/ui/shared/Tabs/TabsMenu.tsx +++ b/ui/shared/Tabs/TabsMenu.tsx @@ -58,7 +58,7 @@ const TabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRef, act { tabs.slice(tabsCut).map((tab, index) => (