diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js index f3eb1a950a40..4f488701bad5 100644 --- a/ui/components/app/modals/modal.js +++ b/ui/components/app/modals/modal.js @@ -117,7 +117,7 @@ const custodyConfirmModalStyle = { const MODALS = { ONBOARDING_ADD_NETWORK: { - contents: , + contents: , testId: 'add-network-modal', ...accountModalStyle, }, 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..6f1c6dab3233 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,156 @@ 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)} + + {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')} + - ) : null} - - - { - if (isFullScreen) { - if (completedOnboarding) { - history.push(ADD_POPULAR_CUSTOM_NETWORK); - } else { - dispatch(showModal({ name: 'ONBOARDING_ADD_NETWORK' })); + {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/pages/onboarding-flow/add-network-modal/add-network-modal.test.js b/ui/pages/onboarding-flow/add-network-modal/add-network-modal.test.js index 69f0570a0252..5a098ceed47e 100644 --- a/ui/pages/onboarding-flow/add-network-modal/add-network-modal.test.js +++ b/ui/pages/onboarding-flow/add-network-modal/add-network-modal.test.js @@ -12,12 +12,25 @@ jest.mock('../../../store/actions', () => ({ })); describe('Add Network Modal', () => { + // Set the environment variable before tests run + beforeEach(() => { + process.env.ENABLE_NETWORK_UI_REDESIGN = ''; + }); + + // Reset the environment variable after tests complete + afterEach(() => { + delete process.env.ENABLE_NETWORK_UI_REDESIGN; + }); + it('should render', async () => { const mockStore = configureMockStore([])({ metamask: { useSafeChainsListValidation: true }, }); - const { container } = renderWithProvider(, mockStore); + const { container } = renderWithProvider( + , + mockStore, + ); await waitFor(() => { expect(container).toMatchSnapshot(); @@ -29,7 +42,10 @@ describe('Add Network Modal', () => { metamask: { useSafeChainsListValidation: true }, }); - const { queryByText } = renderWithProvider(, mockStore); + const { queryByText } = renderWithProvider( + , + mockStore, + ); const cancelButton = queryByText('Cancel'); fireEvent.click(cancelButton); @@ -38,4 +54,20 @@ describe('Add Network Modal', () => { expect(mockHideModal).toHaveBeenCalledTimes(1); }); }); + + it('should not render the new network flow modal', async () => { + const mockStore = configureMockStore([thunk])({ + metamask: { useSafeChainsListValidation: true }, + }); + + const { queryByText } = renderWithProvider( + , + mockStore, + ); + + await waitFor(() => { + expect(queryByText('Cancel')).not.toBeInTheDocument(); + expect(queryByText('Save')).toBeInTheDocument(); + }); + }); }); diff --git a/ui/pages/onboarding-flow/add-network-modal/index.js b/ui/pages/onboarding-flow/add-network-modal/index.js index e3f813d04054..2f2b820d9791 100644 --- a/ui/pages/onboarding-flow/add-network-modal/index.js +++ b/ui/pages/onboarding-flow/add-network-modal/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { hideModal } from '../../../store/actions'; @@ -14,7 +15,9 @@ import { import NetworksForm from '../../settings/networks-tab/networks-form/networks-form'; -export default function AddNetworkModal() { +export default function AddNetworkModal({ + newNetworkMenuDesignActive = false, +}) { const dispatch = useDispatch(); const t = useI18nContext(); @@ -23,15 +26,17 @@ export default function AddNetworkModal() { return ( <> - - - {t('onboardingMetametricsModalTitle')} - - + {newNetworkMenuDesignActive ? null : ( + + + {t('onboardingMetametricsModalTitle')} + + + )} ); } + +AddNetworkModal.propTypes = { + newNetworkMenuDesignActive: PropTypes.bool, +}; + +AddNetworkModal.defaultProps = { + newNetworkMenuDesignActive: false, +}; diff --git a/ui/pages/settings/networks-tab/index.scss b/ui/pages/settings/networks-tab/index.scss index b8d1458a77ed..bbe958c3c6e0 100644 --- a/ui/pages/settings/networks-tab/index.scss +++ b/ui/pages/settings/networks-tab/index.scss @@ -220,8 +220,14 @@ } } + &__new-network-form-footer { + position: sticky; + bottom: 0; + } + &__add-network-form { - padding: 16px 24px; + margin-left: 16px; + margin-right: 16px; grid-column: span 2; // spread both columns of grid layout @include design-system.screen-sm-min { diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.js b/ui/pages/settings/networks-tab/networks-form/networks-form.js index b5a072ac3312..7b9e87097c5e 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.js @@ -44,17 +44,25 @@ import { setSelectedNetworkConfigurationId, showDeprecatedNetworkModal, showModal, + toggleNetworkMenu, upsertNetworkConfiguration, } from '../../../../store/actions'; import { + Box, ButtonLink, + ButtonPrimary, + ButtonPrimarySize, HelpText, HelpTextSeverity, Text, } from '../../../../components/component-library'; import { FormTextField } from '../../../../components/component-library/form-text-field/deprecated'; import { + AlignItems, + BackgroundColor, + BlockSize, FontWeight, + TextAlign, TextColor, TextVariant, } from '../../../../helpers/constants/design-system'; @@ -104,6 +112,7 @@ const NetworksForm = ({ selectedNetwork, cancelCallback, submitCallback, + newNetworkMenuDesignActive, }) => { const t = useI18nContext(); const dispatch = useDispatch(); @@ -860,36 +869,61 @@ const NetworksForm = ({ dataTestId="network-form-block-explorer-url" /> -
- {!viewOnly && ( - <> - {deletable && ( - + )} + + - )} - - - - )} -
+ + + + )} + + )} ); }; @@ -903,10 +937,12 @@ NetworksForm.propTypes = { submitCallback: PropTypes.func, restrictHeight: PropTypes.bool, setActiveOnSubmit: PropTypes.bool, + newNetworkMenuDesignActive: PropTypes.bool, }; NetworksForm.defaultProps = { selectedNetwork: {}, + newNetworkMenuDesignActive: false, }; export default NetworksForm;