diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 3c9e08eabc11..8fcd4dd849f4 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1170,7 +1170,7 @@ "message": "Connecting to Sepolia test network" }, "connectionDescription": { - "message": "This site wants to" + "message": "Connect this website with MetaMask." }, "connectionFailed": { "message": "Connection failed" @@ -4484,6 +4484,9 @@ "select": { "message": "Select" }, + "selectAccountToConnect": { + "message": "Select an account to connect" + }, "selectAccounts": { "message": "Select the account(s) to use on this site" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 3c9e08eabc11..8fcd4dd849f4 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -1170,7 +1170,7 @@ "message": "Connecting to Sepolia test network" }, "connectionDescription": { - "message": "This site wants to" + "message": "Connect this website with MetaMask." }, "connectionFailed": { "message": "Connection failed" @@ -4484,6 +4484,9 @@ "select": { "message": "Select" }, + "selectAccountToConnect": { + "message": "Select an account to connect" + }, "selectAccounts": { "message": "Select the account(s) to use on this site" }, diff --git a/test/e2e/json-rpc/switchEthereumChain.spec.js b/test/e2e/json-rpc/switchEthereumChain.spec.js index 7d7b37f44166..afbff962c416 100644 --- a/test/e2e/json-rpc/switchEthereumChain.spec.js +++ b/test/e2e/json-rpc/switchEthereumChain.spec.js @@ -140,6 +140,12 @@ describe('Switch Ethereum Chain for two dapps', function () { await driver.clickElement('#connectButton'); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const permissionsTab = await driver.findElement( + '[data-testid="permissions-tab"]', + ); + await permissionsTab.click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); await editButtons[1].click(); @@ -263,6 +269,12 @@ describe('Switch Ethereum Chain for two dapps', function () { await driver.clickElement('#connectButton'); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const permissionsTab = await driver.findElement( + '[data-testid="permissions-tab"]', + ); + await permissionsTab.click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); // Click the edit button for networks @@ -383,6 +395,11 @@ describe('Switch Ethereum Chain for two dapps', function () { await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const permissionsTab = await driver.findElement( + '[data-testid="permissions-tab"]', + ); + await permissionsTab.click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); // Click the edit button for networks diff --git a/test/e2e/page-objects/pages/confirmations/redesign/connect-account-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/connect-account-confirmation.ts index fac0ca685b95..dd1e61287cba 100644 --- a/test/e2e/page-objects/pages/confirmations/redesign/connect-account-confirmation.ts +++ b/test/e2e/page-objects/pages/confirmations/redesign/connect-account-confirmation.ts @@ -9,8 +9,8 @@ class ConnectAccountConfirmation { }; private readonly connectAccountConfirmationTitle = { - text: 'Connect with MetaMask', - tag: 'h2', + text: 'Connect this website with MetaMask.', + tag: 'p', }; constructor(driver: Driver) { diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts index c5153745848b..d5cefd3bab9f 100644 --- a/test/e2e/page-objects/pages/test-dapp.ts +++ b/test/e2e/page-objects/pages/test-dapp.ts @@ -36,8 +36,8 @@ class TestDapp { private readonly connectAccountButton = '#connectButton'; private readonly connectMetaMaskMessage = { - text: 'Connect with MetaMask', - tag: 'h2', + text: 'Connect this website with MetaMask.', + tag: 'p', }; private readonly connectedAccount = '#accounts'; diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js index 7489ebe842f6..03341b6693b3 100644 --- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js @@ -53,6 +53,11 @@ describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const permissionsTab = await driver.findElement( + '[data-testid="permissions-tab"]', + ); + await permissionsTab.click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); await editButtons[1].click(); @@ -208,6 +213,11 @@ describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { await driver.clickElement('#connectButton'); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const permissionsTab = await driver.findElement( + '[data-testid="permissions-tab"]', + ); + await permissionsTab.click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); await editButtons[1].click(); diff --git a/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js b/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js index 9cfae87add90..644140e490a9 100644 --- a/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js +++ b/test/e2e/tests/request-queuing/switchChain-watchAsset.spec.js @@ -49,6 +49,12 @@ describe('Request Queue SwitchChain -> WatchAsset', function () { await driver.clickElement('#connectButton'); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + const permissionsTab = await driver.findElement( + '[data-testid="permissions-tab"]', + ); + await permissionsTab.click(); + const editButtons = await driver.findElements('[data-testid="edit"]'); await editButtons[1].click(); diff --git a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx index 1c91489d0d1f..86c58385cd23 100644 --- a/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx +++ b/ui/components/multichain/pages/review-permissions-page/site-cell/site-cell.tsx @@ -100,7 +100,8 @@ export const SiteCell: React.FC = ({ category: MetaMetricsEventCategory.Navigation, event: MetaMetricsEventName.ViewPermissionedAccounts, properties: { - location: 'Connect view, Permissions toast, Permissions (dapp)', + location: + 'Connect view (permissions tab), Permissions toast, Permissions (dapp)', }, }); }; @@ -112,7 +113,8 @@ export const SiteCell: React.FC = ({ category: MetaMetricsEventCategory.Navigation, event: MetaMetricsEventName.ViewPermissionedNetworks, properties: { - location: 'Connect view, Permissions toast, Permissions (dapp)', + location: + 'Connect view (permissions tab), Permissions toast, Permissions (dapp)', }, }); }; diff --git a/ui/components/ui/tabs/tab/tab.component.js b/ui/components/ui/tabs/tab/tab.component.js index 2fa5f75f1134..118ff6f4dda2 100644 --- a/ui/components/ui/tabs/tab/tab.component.js +++ b/ui/components/ui/tabs/tab/tab.component.js @@ -77,6 +77,7 @@ Tab.propTypes = { tabIndex: PropTypes.number, // required, but added using React.cloneElement children: PropTypes.node, // required, but we are not rendering it explicitly textProps: PropTypes.object, // props to spread to the Text component + width: PropTypes.string, }; Tab.defaultProps = { diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js index ce2f126e102f..f96b74f0bdee 100644 --- a/ui/helpers/utils/util.js +++ b/ui/helpers/utils/util.js @@ -798,6 +798,49 @@ export const getAvatarFallbackLetter = (subjectName) => { return subjectName?.match(/[a-z0-9]/iu)?.[0] ?? '?'; }; +/** + * Check whether raw origin URL is an IP address. + * + * Note: IPv6 addresses are expected to be wrapped in brackets (e.g. [fe80::1]) + * because of how URL formatting works. + * + * @param {string} rawOriginUrl - Raw origin (URL) with protocol that is potentially an IP address + * @returns Boolean, true if the origin is an IP address, false otherwise. + */ +export const isIpAddress = (rawOriginUrl) => { + if (typeof rawOriginUrl === 'string') { + return Boolean( + rawOriginUrl.match(/^(\d{1,3}\.){3}\d{1,3}$|^\[[0-9a-f:]+\]$/iu), + ); + } + + return false; +}; + +/** + * Transforms full raw URLs to something that can be used as title. + * Basically, it removes subdomain and protocol prefixes. + * + * Note: For IP address origins, full IP address without protocol will be returned. + * + * @param {string} rawOrigin - Raw origin (URL) with protocol. + * @returns User friendly title extracted from raw URL. + */ +export const transformOriginToTitle = (rawOrigin) => { + try { + const url = new URL(rawOrigin); + + if (isIpAddress(url.hostname)) { + return url.hostname; + } + + const parts = url.hostname.split('.'); + return parts.slice(-2).join('.'); + } catch (e) { + return 'Unknown Origin'; + } +}; + /** * Get abstracted Snap permissions filtered by weight. * diff --git a/ui/helpers/utils/util.test.js b/ui/helpers/utils/util.test.js index 91718ca7fcce..9692da08efbc 100644 --- a/ui/helpers/utils/util.test.js +++ b/ui/helpers/utils/util.test.js @@ -1298,4 +1298,54 @@ describe('util', () => { expect(sortedAccount).toStrictEqual([]); }); }); + + describe('isIpAddress', () => { + it('should return true for the IPv4 address', () => { + expect(util.isIpAddress('127.0.0.1')).toBe(true); + }); + + it('should return true for the IPv6 address', () => { + expect(util.isIpAddress('[fe80::1]')).toBe(true); + }); + + it('should return true for the invalid IP address', () => { + expect(util.isIpAddress('metamask')).toBe(false); + }); + + it('should return true for the invalid type of argument', () => { + expect(util.isIpAddress(1024)).toBe(false); + }); + }); + + describe('transformOriginToTitle', () => { + it('should return the correct title for origin with domain', () => { + expect(util.transformOriginToTitle('https://metamask.io')).toBe( + 'metamask.io', + ); + }); + + it('should return the correct title for origin with subdomain', () => { + expect( + util.transformOriginToTitle('https://metamask.github.io/test-dapp/'), + ).toBe('github.io'); + }); + + it('should return the correct title for localhost', () => { + expect(util.transformOriginToTitle('http://localhost:3000')).toBe( + 'localhost', + ); + }); + + it('should return the correct title for IPv4 address', () => { + expect(util.transformOriginToTitle('http://127.0.0.1:3000')).toBe( + '127.0.0.1', + ); + }); + + it('should return the correct title for IPv6 address', () => { + expect(util.transformOriginToTitle('http://[fe80::1]:9011/')).toBe( + '[fe80::1]', + ); + }); + }); }); diff --git a/ui/pages/permissions-connect/connect-page/__snapshots__/connect-page.test.tsx.snap b/ui/pages/permissions-connect/connect-page/__snapshots__/connect-page.test.tsx.snap index 3c02fa78f20d..14e7c8bf3fc3 100644 --- a/ui/pages/permissions-connect/connect-page/__snapshots__/connect-page.test.tsx.snap +++ b/ui/pages/permissions-connect/connect-page/__snapshots__/connect-page.test.tsx.snap @@ -18,185 +18,322 @@ exports[`ConnectPage should render correctly 1`] = `

+

+
+
+ github.io logo +
+
+
+ github.io logo +
+

- Connect with MetaMask + github.io

-

- This site wants to - : -

+

+ Connect this website with MetaMask. +

+ +

-
-
- -
-
+ Accounts + + +
  • -

    - See your accounts and suggest transactions -

    + Permissions + +
  • + +
    +
    - - Requesting for Test Account - -
    -
    -
    - -
    -
    -
    - -
    -
    -

    - Use your enabled networks -

    -
    - - Requesting for - -
    Alerts"" - data-tooltipped="" - style="display: inline;" - >
    + +
    +
    +
    +
    +
    +

    + 0x0DCD5...3E7bc +

    +
    + E +
    +
    - Custom Mainnet RPC logo +
    + + 966.988 + + + ETH + +
    +
    + +
    -
    @@ -206,26 +343,6 @@ exports[`ConnectPage should render correctly 1`] = `
    -
    -

    - - - Only connect with sites you trust. - - - - -

    -
    @@ -268,175 +385,322 @@ exports[`ConnectPage should render with defaults from the requested permissions

    +

    +
    +
    + github.io logo +
    +
    +
    + github.io logo +
    +

    - Connect with MetaMask + github.io

    -

    - This site wants to - : -

    +

    + Connect this website with MetaMask. +

    + +

    -
    -
    - -
    -
    + Accounts + + +
  • -

    - See your accounts and suggest transactions -

    + Permissions + +
  • + +
    +
    - - Requesting for Test Account - -
    -
    -
    - -
    -
    -
    - -
    -
    -

    - Use your enabled networks -

    -
    - - Requesting for Custom Mainnet RPC - -
    Alerts"" - data-tooltipped="" - style="display: inline;" - >
    +
    + + +
    +
    +
    +

    + 0x0DCD5...3E7bc +

    +
    +
    +
    + +
    -
    @@ -446,26 +710,6 @@ exports[`ConnectPage should render with defaults from the requested permissions
    -
    -

    - - - Only connect with sites you trust. - - - - -

    -
    diff --git a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx index e720bb4781fc..089d80fa225a 100644 --- a/ui/pages/permissions-connect/connect-page/connect-page.test.tsx +++ b/ui/pages/permissions-connect/connect-page/connect-page.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { fireEvent } from '@testing-library/react'; import { Caip25CaveatType, Caip25EndowmentPermissionName, @@ -15,6 +16,14 @@ import { ConnectPage, ConnectPageProps } from './connect-page'; const mockTestDappUrl = 'https://test.dapp'; +const mockTargetSubjectMetadata = { + extensionId: null, + iconUrl: 'https://metamask.github.io/test-dapp/metamask-fox.svg', + name: 'E2E Test Dapp', + origin: 'https://metamask.github.io', + subjectType: 'website', +}; + const render = ( options: { props?: ConnectPageProps; @@ -31,6 +40,7 @@ const render = ( rejectPermissionsRequest: jest.fn(), approveConnection: jest.fn(), activeTabOrigin: mockTestDappUrl, + targetSubjectMetadata: mockTargetSubjectMetadata, }, state, } = options; @@ -62,13 +72,139 @@ describe('ConnectPage', () => { expect(container).toMatchSnapshot(); }); + it('should render image icon correctly', () => { + const { getAllByAltText } = render(); + + const images = getAllByAltText('github.io logo'); + expect(images.length).toBe(2); + expect(images[0]).toHaveAttribute( + 'src', + 'https://metamask.github.io/test-dapp/metamask-fox.svg', + ); + expect(images[1]).toHaveAttribute( + 'src', + 'https://metamask.github.io/test-dapp/metamask-fox.svg', + ); + }); + + it('should render fallback icon correctly', () => { + const { container } = render({ + props: { + request: { + id: '1', + origin: mockTestDappUrl, + }, + permissionsRequestId: '1', + rejectPermissionsRequest: jest.fn(), + approveConnection: jest.fn(), + activeTabOrigin: mockTestDappUrl, + targetSubjectMetadata: { + ...mockTargetSubjectMetadata, + iconUrl: null, + }, + }, + }); + + const divElement = container.querySelector('div.mm-avatar-base--size-lg'); + expect(divElement).toHaveTextContent('g'); + }); + + it('should render fallback icon correctly for IP address as an origin', () => { + const { container } = render({ + props: { + request: { + id: '1', + origin: 'http://127.0.0.1/test-dapp', + }, + permissionsRequestId: '1', + rejectPermissionsRequest: jest.fn(), + approveConnection: jest.fn(), + activeTabOrigin: mockTestDappUrl, + targetSubjectMetadata: { + ...mockTargetSubjectMetadata, + iconUrl: null, + origin: 'http://127.0.0.1/test-dapp', + }, + }, + }); + + const divElement = container.querySelector('div.mm-avatar-base--size-lg'); + expect(divElement).toHaveTextContent('?'); + }); + it('should render title correctly', () => { const { getByText } = render(); - expect(getByText('Connect with MetaMask')).toBeDefined(); + expect(getByText('github.io')).toBeDefined(); + }); + + it('should render subtitle correctly', () => { + const { getByText } = render(); + expect(getByText('Connect this website with MetaMask.')).toBeDefined(); + }); + + it('should render learn more link correctly', () => { + const { getByText } = render(); + expect(getByText('Learn more')).toBeDefined(); + }); + + it('should render accounts tab correctly', () => { + const { getByText, queryAllByText } = render(); + + expect(getByText('Accounts')).toBeDefined(); + expect(getByText('Test Account')).toBeDefined(); + expect(getByText('0x0DCD5...3E7bc')).toBeDefined(); + + const valueElements = queryAllByText('966.988'); + expect(valueElements[0]).toBeDefined(); + expect(getByText('Edit accounts')).toBeDefined(); + }); + + it('should render edit accounts modal', () => { + const { getByText, queryAllByText } = render(); + const editAccountsButton = getByText('Edit accounts'); + fireEvent.click(editAccountsButton); + + expect(getByText('Update')).toBeDefined(); + expect(getByText('Select all')).toBeDefined(); + expect(getByText('New account')).toBeDefined(); + + const accountElements = queryAllByText('Test Account'); + + expect(accountElements.length).toBe(2); + expect(accountElements[0].textContent).toBe('Test Account'); + expect(accountElements[1].textContent).toBe('Test Account'); + }); + + it('should render empty accounts state correctly', () => { + const { getByText, queryAllByText, getByTestId } = render(); + const editAccountsButton = getByText('Edit accounts'); + fireEvent.click(editAccountsButton); + + const accountElements = queryAllByText('Test Account'); + fireEvent.click(accountElements[1]); + + const disconnectButton = getByText('Disconnect'); + fireEvent.click(disconnectButton); + + expect(getByText('Select an account to connect')).toBeDefined(); + + const confirmButton = getByTestId('confirm-btn'); + expect(confirmButton).toBeDisabled(); + + const selectAnAccountToConnectButton = getByText( + 'Select an account to connect', + ); + fireEvent.click(selectAnAccountToConnectButton); + + expect(getByText('Select all')).toBeDefined(); + expect(getByText('New account')).toBeDefined(); }); it('should render account connectionListItem', () => { const { getByText } = render(); + const permissionsTab = getByText('Permissions'); + fireEvent.click(permissionsTab); + expect( getByText('See your accounts and suggest transactions'), ).toBeDefined(); @@ -76,6 +212,9 @@ describe('ConnectPage', () => { it('should render network connectionListItem', () => { const { getByText } = render(); + const permissionsTab = getByText('Permissions'); + fireEvent.click(permissionsTab); + expect(getByText('Use your enabled networks')).toBeDefined(); }); @@ -118,6 +257,7 @@ describe('ConnectPage', () => { rejectPermissionsRequest: jest.fn(), approveConnection: jest.fn(), activeTabOrigin: mockTestDappUrl, + targetSubjectMetadata: mockTargetSubjectMetadata, }, }); expect(container).toMatchSnapshot(); @@ -139,7 +279,7 @@ describe('ConnectPage', () => { const confirmButton = getByText('Connect'); const cancelButton = getByText('Cancel'); // The currently selected account is a Bitcoin account, the "connecting account list" would be - // empty by default and thus, we cannot confirm without explictly select an EVM account. + // empty by default and thus, we cannot confirm without explicitly select an EVM account. expect(confirmButton).toBeDisabled(); expect(cancelButton).toBeDefined(); }); diff --git a/ui/pages/permissions-connect/connect-page/connect-page.tsx b/ui/pages/permissions-connect/connect-page/connect-page.tsx index d39a95871189..8449104a6be0 100644 --- a/ui/pages/permissions-connect/connect-page/connect-page.tsx +++ b/ui/pages/permissions-connect/connect-page/connect-page.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useContext, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { InternalAccount } from '@metamask/keyring-internal-api'; import { isEvmAccountType } from '@metamask/keyring-api'; @@ -13,8 +13,14 @@ import { } from '../../../selectors'; import { getNetworkConfigurationsByChainId } from '../../../../shared/modules/selectors/networks'; import { + AvatarBase, + AvatarBaseSize, + AvatarFavicon, + AvatarFaviconSize, Box, Button, + ButtonLink, + ButtonLinkSize, ButtonSize, ButtonVariant, Text, @@ -27,18 +33,37 @@ import { } from '../../../components/multichain/pages/page'; import { SiteCell } from '../../../components/multichain/pages/review-permissions-page/site-cell/site-cell'; import { + AlignItems, BackgroundColor, BlockSize, + BorderRadius, Display, FlexDirection, + JustifyContent, + TextColor, TextVariant, } from '../../../helpers/constants/design-system'; import { TEST_CHAINS } from '../../../../shared/constants/network'; -import PermissionsConnectFooter from '../../../components/app/permissions-connect-footer'; import { getMultichainNetwork } from '../../../selectors/multichain'; +import { Tab, Tabs } from '../../../components/ui/tabs'; +import { + AccountListItem, + EditAccountsModal, +} from '../../../components/multichain'; +import { + getAvatarFallbackLetter, + isIpAddress, + transformOriginToTitle, +} from '../../../helpers/utils/util'; +import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import { - getRequestedSessionScopes, getCaip25PermissionsResponse, + getRequestedSessionScopes, PermissionsRequest, } from './utils'; @@ -54,6 +79,13 @@ export type ConnectPageProps = { rejectPermissionsRequest: (id: string) => void; approveConnection: (request: ConnectPageRequest) => void; activeTabOrigin: string; + targetSubjectMetadata: { + extensionId: string | null; + iconUrl: string | null; + name: string; + origin: string; + subjectType: string; + }; }; export const ConnectPage: React.FC = ({ @@ -61,8 +93,10 @@ export const ConnectPage: React.FC = ({ permissionsRequestId, rejectPermissionsRequest, approveConnection, + targetSubjectMetadata, }) => { const t = useI18nContext(); + const trackEvent = useContext(MetaMetricsContext); const requestedSessionsScopes = getRequestedSessionScopes( request.permissions, @@ -84,6 +118,8 @@ export const ConnectPage: React.FC = ({ [networkConfigurations], ); + const [showEditAccountsModal, setShowEditAccountsModal] = useState(false); + // By default, if a non test network is the globally selected network. We will only show non test networks as default selected. const currentlySelectedNetwork = useSelector(getMultichainNetwork); const currentlySelectedNetworkChainId = @@ -152,6 +188,26 @@ export const ConnectPage: React.FC = ({ approveConnection(_request); }; + const selectedAccounts = accounts.filter(({ address }) => + selectedAccountAddresses.some((selectedAccountAddress) => + isEqualCaseInsensitive(selectedAccountAddress, address), + ), + ); + + const title = transformOriginToTitle(targetSubjectMetadata.origin); + + const handleOpenAccountsModal = () => { + setShowEditAccountsModal(true); + trackEvent({ + category: MetaMetricsEventCategory.Navigation, + event: MetaMetricsEventName.ViewPermissionedAccounts, + properties: { + location: + 'Connect view (accounts tab), Permissions toast, Permissions (dapp)', + }, + }); + }; + return ( = ({ backgroundColor={BackgroundColor.backgroundAlternative} >
    - {t('connectWithMetaMask')} - {t('connectionDescription')}: + + {targetSubjectMetadata.iconUrl ? ( + <> + + + + + + ) : ( + + {isIpAddress(title) ? '?' : getAvatarFallbackLetter(title)} + + )} + + + {title} + + + {t('connectionDescription')} + { + global.platform.openTab({ + url: ZENDESK_URLS.USER_GUIDE_DAPPS, + }); + }} + > + {t('learnMoreUpperCase')} + +
    - - + + null} + backgroundColor={BackgroundColor.transparent} + justifyContent={JustifyContent.center} + defaultActiveTabKey="accounts" + tabListProps={{ + backgroundColor: BackgroundColor.transparent, + }} + > + + + + {selectedAccounts.map((account) => ( + + ))} + {selectedAccounts.length === 0 && ( + + handleOpenAccountsModal()} + data-testid="edit" + > + {t('selectAccountToConnect')} + + + )} + + {selectedAccounts.length > 0 && ( + + handleOpenAccountsModal()} + data-testid="edit" + > + {t('editAccounts')} + + + )} + {showEditAccountsModal && ( + setShowEditAccountsModal(false)} + onSubmit={setSelectedAccountAddresses} + /> + )} + + + + + + + +