diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2363bc51dc2d..8c9b810bdfa1 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -289,6 +289,9 @@ "addNfts": { "message": "Add NFTs" }, + "addRpcUrl": { + "message": "Add RPC URL" + }, "addSnapAccountToggle": { "message": "Enable \"Add account Snap (Beta)\"" }, @@ -1354,6 +1357,9 @@ "decryptRequest": { "message": "Decrypt request" }, + "defaultRpcUrl": { + "message": "Default RPC URL" + }, "delete": { "message": "Delete" }, @@ -1629,6 +1635,9 @@ "enabled": { "message": "Enabled" }, + "enabledNetworks": { + "message": "Enabled networks" + }, "encryptionPublicKeyNotice": { "message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.", "description": "$1 is the web3 site name" @@ -3871,6 +3880,10 @@ "popularCustomNetworks": { "message": "Popular custom networks" }, + "popularNetworkAddToolTip": { + "message": "Some of these networks rely on third parties. The connections may be less reliable or enable third-parties to track activity. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index dfa04ab73236..5dc85389f449 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -629,6 +629,13 @@ export default function setupSentry({ release, getState }) { tracesSampleRate: 0.01, beforeSend: (report) => rewriteReport(report, getState), beforeBreadcrumb: beforeBreadcrumb(getState), + // Client reports are automatically sent when a page's visibility changes to + // "hidden", but cancelled (with an Error) that gets logged to the console. + // Our test infra sometimes reports these errors as unexpected failures, + // which results in test flakiness. We don't use these client reports, so + // we can safely turn them off by setting the `sendClientReports` option to + // `false`. + sendClientReports: false, }); /** diff --git a/test/e2e/default-fixture.js b/test/e2e/default-fixture.js index 98413671caca..b3075f3319ca 100644 --- a/test/e2e/default-fixture.js +++ b/test/e2e/default-fixture.js @@ -107,6 +107,8 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { '__FIXTURE_SUBSTITUTION__currentDateInMilliseconds', showTestnetMessageInDropdown: true, trezorModel: null, + newPrivacyPolicyToastClickedOrClosed: true, + newPrivacyPolicyToastShownDate: Date.now(), usedNetworks: { [CHAIN_IDS.MAINNET]: true, [CHAIN_IDS.LINEA_MAINNET]: true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 8ab0a3b93cd0..dddbe2b554f4 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -39,8 +39,8 @@ "showAccountBanner": true, "trezorModel": null, "onboardingDate": "object", - "newPrivacyPolicyToastClickedOrClosed": "object", - "newPrivacyPolicyToastShownDate": "object", + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index 93650970b9a9..96dc9ca07e48 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -75,8 +75,8 @@ "showAccountBanner": true, "trezorModel": null, "onboardingDate": "object", - "newPrivacyPolicyToastClickedOrClosed": "object", - "newPrivacyPolicyToastShownDate": "object", + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index 60379a1d0811..dd90d1071776 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -46,6 +46,8 @@ "0x5": true, "0x539": true }, + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "snapsInstallPrivacyWarningShown": true }, "CurrencyController": { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 48392c6b4db3..fc4bb1d65eed 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -46,6 +46,8 @@ "0x5": true, "0x539": true }, + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "snapsInstallPrivacyWarningShown": true }, "CurrencyController": { diff --git a/ui/components/multichain/network-list-menu/network-list-menu.js b/ui/components/multichain/network-list-menu/network-list-menu.js index 022ad7f055b0..6c276e326062 100644 --- a/ui/components/multichain/network-list-menu/network-list-menu.js +++ b/ui/components/multichain/network-list-menu/network-list-menu.js @@ -67,12 +67,23 @@ import { getIsUnlocked, } from '../../../ducks/metamask/metamask'; import { getLocalNetworkMenuRedesignFeatureFlag } from '../../../helpers/utils/feature-flags'; +import AddNetworkModal from '../../../pages/onboarding-flow/add-network-modal'; import PopularNetworkList from './popular-network-list/popular-network-list'; import NetworkListSearch from './network-list-search/network-list-search'; +const ACTION_MODES = { + // Displays the search box and network list + LIST: 'list', + // Displays the Add form + ADD: 'add', + // Displays the Edit form + EDIT: 'edit', +}; + export const NetworkListMenu = ({ onClose }) => { const t = useI18nContext(); + const [actionMode, setActionMode] = useState(ACTION_MODES.LIST); const nonTestNetworks = useSelector(getNonTestNetworks); const testNetworks = useSelector(getTestNetworks); const showTestNetworks = useSelector(getShowTestNetworks); @@ -99,6 +110,13 @@ export const NetworkListMenu = ({ onClose }) => { const isUnlocked = useSelector(getIsUnlocked); + let title = t('networkMenuHeading'); + if (actionMode === ACTION_MODES.ADD) { + title = t('addCustomNetwork'); + } else if (actionMode === ACTION_MODES.EDIT) { + title = currentNetwork.nickname; + } + const orderedNetworksList = useSelector(getOrderedNetworksList); const networkConfigurationChainIds = Object.values(networkConfigurations).map( @@ -315,6 +333,11 @@ export const NetworkListMenu = ({ onClose }) => { } }; + const headerAdditionalProps = + actionMode === ACTION_MODES.LIST + ? {} + : { onBack: () => setActionMode(ACTION_MODES.LIST) }; + return ( @@ -332,145 +355,165 @@ export const NetworkListMenu = ({ onClose }) => { paddingRight={4} paddingBottom={6} onClose={onClose} + {...headerAdditionalProps} > - {t('networkMenuHeading')} + {title} - <> - - {showBanner ? ( - - drag-and-drop - - } - onClose={() => hideNetworkBanner()} - description={t('dragAndDropBanner')} + {actionMode === ACTION_MODES.LIST ? ( + <> + - ) : null} - - {searchResults.length === 0 && focusSearch ? ( - - {t('noNetworksFound')} - - ) : ( - - - {(provided) => ( - - {searchResults.map((network, index) => { - const isCurrentNetwork = - currentNetwork.id === network.id; - - const canDeleteNetwork = - isUnlocked && !isCurrentNetwork && network.removable; - - const networkListItem = generateNetworkListItem({ - network, - isCurrentNetwork, - canDeleteNetwork, - }); - - return ( - - {(providedDrag) => ( - - {networkListItem} - - )} - - ); - })} - {provided.placeholder} - - )} - - - )} - {networkMenuRedesign ? ( - + drag-and-drop + + } + onClose={() => hideNetworkBanner()} + description={t('dragAndDropBanner')} /> ) : null} - - {t('showTestnetNetworks')} - - - {showTestNetworks || currentlyOnTestNetwork ? ( - - {generateMenuItems(searchTestNetworkResults)} + + + {t('enabledNetworks')} - ) : null} - - - { - if (isFullScreen) { - if (completedOnboarding) { - history.push(ADD_POPULAR_CUSTOM_NETWORK); - } else { - dispatch(showModal({ name: 'ONBOARDING_ADD_NETWORK' })); + {searchResults.length === 0 && focusSearch ? ( + + {t('noNetworksFound')} + + ) : ( + + + {(provided) => ( + + {searchResults.map((network, index) => { + const isCurrentNetwork = + currentNetwork.id === network.id; + + const canDeleteNetwork = + isUnlocked && + !isCurrentNetwork && + network.removable; + + const networkListItem = generateNetworkListItem({ + network, + isCurrentNetwork, + canDeleteNetwork, + }); + + return ( + + {(providedDrag) => ( + + {networkListItem} + + )} + + ); + })} + {provided.placeholder} + + )} + + + )} + {networkMenuRedesign ? ( + + ) : null} + + {t('showTestnetNetworks')} + + + {showTestNetworks || currentlyOnTestNetwork ? ( + + {generateMenuItems(searchTestNetworkResults)} + + ) : null} + + + { + if (!networkMenuRedesign) { + if (isFullScreen) { + if (completedOnboarding) { + history.push(ADD_POPULAR_CUSTOM_NETWORK); + } else { + dispatch(showModal({ name: 'ONBOARDING_ADD_NETWORK' })); + } + } else { + global.platform.openExtensionInBrowser( + ADD_POPULAR_CUSTOM_NETWORK, + ); + } + dispatch(toggleNetworkMenu()); + return; } - } else { - global.platform.openExtensionInBrowser( - ADD_POPULAR_CUSTOM_NETWORK, - ); - } - dispatch(toggleNetworkMenu()); - trackEvent({ - event: MetaMetricsEventName.AddNetworkButtonClick, - category: MetaMetricsEventCategory.Network, - }); - }} - > - {t('addNetwork')} - - - + trackEvent({ + event: MetaMetricsEventName.AddNetworkButtonClick, + category: MetaMetricsEventCategory.Network, + }); + setActionMode(ACTION_MODES.ADD); + }} + > + {t('addNetwork')} + + + + ) : ( + + )} ); diff --git a/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx index 6d2cd02f5170..cbe1a99fd69f 100644 --- a/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx +++ b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx @@ -9,6 +9,11 @@ import { Button, AvatarNetworkSize, ButtonVariant, + IconName, + Icon, + IconSize, + ButtonLinkSize, + ButtonLink, } from '../../../component-library'; import { MetaMetricsNetworkEventSource } from '../../../../../shared/constants/metametrics'; import { @@ -26,8 +31,11 @@ import { Display, JustifyContent, TextColor, + IconColor, } from '../../../../helpers/constants/design-system'; import { RPCDefinition } from '../../../../../shared/constants/network'; +import Tooltip from '../../../ui/tooltip'; +import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; const PopularNetworkList = ({ searchAddNetworkResults, @@ -53,7 +61,45 @@ const PopularNetworkList = ({ display={Display.Flex} justifyContent={JustifyContent.spaceBetween} > - {t('additionalNetworks')} + + {t('additionalNetworks')} + + {t('popularNetworkAddToolTip', [ + + { + global.platform.openTab({ + url: ZENDESK_URLS.UNKNOWN_NETWORK, + }); + }} + > + {t('learnMoreUpperCase')} + + , + , + ])} + + } + trigger="mouseenter" + > + + + + + )} diff --git a/ui/helpers/utils/feature-flags.js b/ui/helpers/utils/feature-flags.js index 765bbcf532f9..1956ee75c4bc 100644 --- a/ui/helpers/utils/feature-flags.js +++ b/ui/helpers/utils/feature-flags.js @@ -1,3 +1,3 @@ export function getLocalNetworkMenuRedesignFeatureFlag() { - return window.metamaskFeatureFlags.networkMenuRedesign; + return window.metamaskFeatureFlags?.networkMenuRedesign; } diff --git a/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap b/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap index ace866477762..e422b5d8216b 100644 --- a/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap +++ b/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap @@ -223,15 +223,10 @@ exports[`Add Network Modal should render 1`] = ` -
- {!viewOnly && ( - <> - {deletable && ( - + )} + + - )} - - - - )} -
+ + + + )} + + )} ); }; diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.test.js b/ui/pages/settings/networks-tab/networks-form/networks-form.test.js index a491dee64184..e4b338a8a580 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.test.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.test.js @@ -43,6 +43,11 @@ const propNetworkDisplay = { addNewNetwork: false, }; +jest.mock('../../../../helpers/utils/feature-flags', () => ({ + ...jest.requireActual('../../../../helpers/utils/feature-flags'), + getLocalNetworkMenuRedesignFeatureFlag: () => false, +})); + describe('NetworkForm Component', () => { beforeAll(() => { nock.disableNetConnect(); diff --git a/ui/pages/settings/networks-tab/networks-form/rpc-url-editor.tsx b/ui/pages/settings/networks-tab/networks-form/rpc-url-editor.tsx new file mode 100644 index 000000000000..99cd98776c07 --- /dev/null +++ b/ui/pages/settings/networks-tab/networks-form/rpc-url-editor.tsx @@ -0,0 +1,144 @@ +import React, { useRef, useState } from 'react'; +import classnames from 'classnames'; +import { + Box, + ButtonIcon, + ButtonIconSize, + Icon, + IconName, + IconSize, + Popover, + PopoverPosition, + Text, +} from '../../../../components/component-library'; +import { + AlignItems, + BackgroundColor, + BorderColor, + BorderRadius, + Display, + IconColor, + JustifyContent, + TextColor, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +export const RpcUrlEditor = ({ currentRpcUrl }: { currentRpcUrl: string }) => { + // TODO: real endpoints + const dummyRpcUrls = [ + currentRpcUrl, + 'https://dummy.mainnet.public.blastapi.io', + 'https://dummy.io/v3/blockchain/node/dummy', + ]; + + const t = useI18nContext(); + const rpcDropdown = useRef(null); + const [isOpen, setIsOpen] = useState(false); + const [currentRpcEndpoint, setCurrentRpcEndpoint] = useState(currentRpcUrl); + + return ( + <> + + {t('defaultRpcUrl')} + + setIsOpen(!isOpen)} + className="networks-tab__rpc-dropdown" + display={Display.Flex} + justifyContent={JustifyContent.spaceBetween} + borderRadius={BorderRadius.MD} + borderColor={BorderColor.borderDefault} + borderWidth={1} + padding={2} + ref={rpcDropdown} + > + {currentRpcEndpoint} + + + + {dummyRpcUrls.map((rpcEndpoint) => ( + setCurrentRpcEndpoint(rpcEndpoint)} + className={classnames('networks-tab__rpc-item', { + 'networks-tab__rpc-item--selected': + rpcEndpoint === currentRpcEndpoint, + })} + > + {rpcEndpoint === currentRpcEndpoint && ( + + )} + + {rpcEndpoint} + + alert('TODO: delete confirmation modal')} + /> + + ))} + alert('TODO: add RPC modal')} + padding={4} + display={Display.Flex} + alignItems={AlignItems.center} + className="networks-tab__rpc-item" + > + + + {t('addRpcUrl')} + + + + + ); +}; + +export default RpcUrlEditor; diff --git a/ui/pages/settings/networks-tab/networks-tab.test.js b/ui/pages/settings/networks-tab/networks-tab.test.js index fe985de9ebe4..cead29634a70 100644 --- a/ui/pages/settings/networks-tab/networks-tab.test.js +++ b/ui/pages/settings/networks-tab/networks-tab.test.js @@ -20,6 +20,11 @@ const mockState = { }, }; +jest.mock('../../../helpers/utils/feature-flags', () => ({ + ...jest.requireActual('../../../helpers/utils/feature-flags'), + getLocalNetworkMenuRedesignFeatureFlag: () => false, +})); + const renderComponent = (props) => { const store = configureMockStore([])(mockState); return renderWithProvider(, store);