diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index df3211fd2..be35dd241 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -16,10 +16,13 @@ "disconnect": { "message": "Disconnect" }, - "disconnectSite": { - "message": "Disconnect $1?" + "disconnectAllAccounts": { + "message": "Disconnect all accounts" }, - "disconnectAccountConfirmationDescription": { + "disconnectPrompt": { + "message": "Disconnect $1" + }, + "disconnectAllAccountsConfirmationDescription": { "message": "Are you sure you want to disconnect? You may lose site functionality." }, "dismiss": { diff --git a/test/e2e/permissions.spec.js b/test/e2e/permissions.spec.js index 3b79e603b..08d9b5e10 100644 --- a/test/e2e/permissions.spec.js +++ b/test/e2e/permissions.spec.js @@ -138,7 +138,7 @@ describe('MetaMask', function () { await driver.findElement(By.xpath(`//h2[contains(text(), 'Connected Sites')]`)) - const domains = await driver.findClickableElements(By.css('.connected-sites__domain-name')) + const domains = await driver.findClickableElements(By.css('.connected-sites-list__domain-name')) assert.equal(domains.length, 1) }) diff --git a/ui/app/components/app/account-menu/account-menu.container.js b/ui/app/components/app/account-menu/account-menu.container.js index b2825438b..f024cb4d5 100644 --- a/ui/app/components/app/account-menu/account-menu.container.js +++ b/ui/app/components/app/account-menu/account-menu.container.js @@ -15,7 +15,6 @@ import { getMetaMaskKeyrings, getOriginOfCurrentTab, getSelectedAddress, - // getPermittedAccounts, } from '../../../selectors/selectors' import AccountMenu from './account-menu.component' diff --git a/ui/app/components/app/connected-sites-list/connected-sites-list.component.js b/ui/app/components/app/connected-sites-list/connected-sites-list.component.js index 67d5b6a95..0a5db1b89 100644 --- a/ui/app/components/app/connected-sites-list/connected-sites-list.component.js +++ b/ui/app/components/app/connected-sites-list/connected-sites-list.component.js @@ -13,20 +13,20 @@ export default class ConnectedSitesList extends Component { icon: PropTypes.string, key: PropTypes.string, })).isRequired, - onDisconnectSite: PropTypes.func.isRequired, + onDisconnect: PropTypes.func.isRequired, } render () { - const { connectedDomains, onDisconnectSite } = this.props + const { connectedDomains, onDisconnect } = this.props const { t } = this.context return ( -
+
{ connectedDomains.map((domain) => ( -
-
+
+
- + { domain.extensionId ? t('externalExtension') @@ -35,9 +35,9 @@ export default class ConnectedSitesList extends Component {
onDisconnectSite(domain.key, domain.name)} + onClick={() => onDisconnect(domain.key, domain.name)} />
)) } diff --git a/ui/app/components/app/connected-sites-list/index.scss b/ui/app/components/app/connected-sites-list/index.scss index f2045db37..8666cfedf 100644 --- a/ui/app/components/app/connected-sites-list/index.scss +++ b/ui/app/components/app/connected-sites-list/index.scss @@ -1,4 +1,4 @@ -.connected-sites { +.connected-sites-list { &__content-rows { display: flex; flex-direction: column; diff --git a/ui/app/pages/connected-sites/connected-sites.component.js b/ui/app/pages/connected-sites/connected-sites.component.js index 650ac5f5c..df4a4bcf3 100644 --- a/ui/app/pages/connected-sites/connected-sites.component.js +++ b/ui/app/pages/connected-sites/connected-sites.component.js @@ -2,10 +2,9 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' import ConnectedSitesList from '../../components/app/connected-sites-list' import Popover from '../../components/ui/popover/popover.component' -import { DEFAULT_ROUTE } from '../../helpers/constants/routes' import Button from '../../components/ui/button' -export default class ConnectSites extends Component { +export default class ConnectedSites extends Component { static contextTypes = { t: PropTypes.func, } @@ -15,25 +14,29 @@ export default class ConnectSites extends Component { } static propTypes = { - connectedDomains: PropTypes.arrayOf(PropTypes.object).isRequired, accountLabel: PropTypes.string.isRequired, + closePopover: PropTypes.func.isRequired, + connectedDomains: PropTypes.arrayOf(PropTypes.object).isRequired, + disconnectAllAccounts: PropTypes.func.isRequired, disconnectAccount: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - tabToConnect: PropTypes.object, - legacyExposeAccount: PropTypes.func.isRequired, getOpenMetamaskTabsIds: PropTypes.func.isRequired, + legacyExposeAccount: PropTypes.func.isRequired, + permittedAccountsByOrigin: PropTypes.objectOf( + PropTypes.arrayOf(PropTypes.string), + ).isRequired, + tabToConnect: PropTypes.object, } state = { sitePendingDisconnect: null, } - UNSAFE_componentWillMount () { + componentDidMount () { const { getOpenMetamaskTabsIds } = this.props getOpenMetamaskTabsIds() } - setSitePendingDisconnect = (domainKey, domainName) => { + setPendingDisconnect = (domainKey, domainName) => { this.setState({ sitePendingDisconnect: { domainKey, @@ -42,73 +45,130 @@ export default class ConnectSites extends Component { }) } - clearSitePendingDisconnect = () => { + clearPendingDisconnect = () => { this.setState({ sitePendingDisconnect: null, }) } - disconnect = () => { + disconnectAccount = () => { const { disconnectAccount } = this.props const { sitePendingDisconnect } = this.state disconnectAccount(sitePendingDisconnect.domainKey) - this.clearSitePendingDisconnect() + this.clearPendingDisconnect() } - renderConnectedSites () { + disconnectAllAccounts = () => { + const { disconnectAllAccounts } = this.props + const { sitePendingDisconnect } = this.state + + disconnectAllAccounts(sitePendingDisconnect.domainKey) + this.clearPendingDisconnect() + } + + renderConnectedSitesList () { return ( + ) + } + + renderConnectedSitesPopover () { + + const { + accountLabel, + closePopover, + connectedDomains, + legacyExposeAccount, + tabToConnect, + } = this.props + const { t } = this.context + + return ( + + {t('connectManually')} + + ) + : null + } + footerClassName="connected-sites__add-site-manually" + > + {this.renderConnectedSitesList()} + + ) + } + + renderDisconnectPopover () { + + const { closePopover, permittedAccountsByOrigin } = this.props + const { t } = this.context + const { sitePendingDisconnect: { domainKey, domainName } } = this.state + + const numPermittedAccounts = permittedAccountsByOrigin[domainKey].length + + return ( + +
+ + +
+ { + numPermittedAccounts > 1 + ? ( + + ) + : null + } + + )} + footerClassName="connected-sites__confirmation" /> ) } render () { - const { accountLabel, history, legacyExposeAccount, tabToConnect, connectedDomains } = this.props - const { t } = this.context const { sitePendingDisconnect } = this.state return ( sitePendingDisconnect - ? ( - history.push(DEFAULT_ROUTE)} - footer={( - <> - - - - )} - footerClassName="connected-sites__confirmation" - /> - ) - : ( - history.push(DEFAULT_ROUTE)} - footer={ - tabToConnect - ? ( - { t('connectManually') } - ) - : null - } - footerClassName="connected-sites__add-site-manually" - > - {this.renderConnectedSites()} - - ) + ? this.renderDisconnectPopover() + : this.renderConnectedSitesPopover() ) } } diff --git a/ui/app/pages/connected-sites/connected-sites.container.js b/ui/app/pages/connected-sites/connected-sites.container.js index 61d5bdc8e..85dc40c57 100644 --- a/ui/app/pages/connected-sites/connected-sites.container.js +++ b/ui/app/pages/connected-sites/connected-sites.container.js @@ -1,23 +1,38 @@ import { connect } from 'react-redux' import ConnectedSites from './connected-sites.component' -import { getOpenMetamaskTabsIds, legacyExposeAccounts, removePermissionsFor } from '../../store/actions' +import { + getOpenMetamaskTabsIds, + legacyExposeAccounts, + removePermissionsFor, + removePermittedAccount, +} from '../../store/actions' import { getConnectedDomainsForSelectedAddress, getCurrentAccountWithSendEtherInfo, - getPermissionsDomains, - getPermittedAccountsForCurrentTab, + getOriginOfCurrentTab, getSelectedAddress, } from '../../selectors/selectors' +import { + getPermissionsDomains, + getPermittedAccountsByOrigin, +} from '../../selectors/permissions' +import { DEFAULT_ROUTE } from '../../helpers/constants/routes' import { getOriginFromUrl } from '../../helpers/utils/util' const mapStateToProps = (state) => { const { openMetaMaskTabs } = state.appState const { title, url, id } = state.activeTab - const permittedAccounts = getPermittedAccountsForCurrentTab(state) const connectedDomains = getConnectedDomainsForSelectedAddress(state) + const originOfCurrentTab = getOriginOfCurrentTab(state) + const permittedAccountsByOrigin = getPermittedAccountsByOrigin(state) + const selectedAddress = getSelectedAddress(state) + + const currentTabHasNoAccounts = !permittedAccountsByOrigin[ + originOfCurrentTab + ]?.length let tabToConnect - if (url && permittedAccounts.length === 0 && !openMetaMaskTabs[id]) { + if (url && currentTabHasNoAccounts && !openMetaMaskTabs[id]) { tabToConnect = { title, origin: getOriginFromUrl(url), @@ -28,7 +43,8 @@ const mapStateToProps = (state) => { accountLabel: getCurrentAccountWithSendEtherInfo(state).name, connectedDomains, domains: getPermissionsDomains(state), - selectedAddress: getSelectedAddress(state), + permittedAccountsByOrigin, + selectedAddress, tabToConnect, } } @@ -36,7 +52,10 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { getOpenMetamaskTabsIds: () => dispatch(getOpenMetamaskTabsIds()), - disconnectAccount: (domainKey, domain) => { + disconnectAccount: (domainKey, address) => { + dispatch(removePermittedAccount(domainKey, address)) + }, + disconnectAllAccounts: (domainKey, domain) => { const permissionMethodNames = domain.permissions.map(({ parentCapability }) => parentCapability) dispatch(removePermissionsFor({ [domainKey]: permissionMethodNames, @@ -47,17 +66,38 @@ const mapDispatchToProps = (dispatch) => { } const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { domains, selectedAddress, tabToConnect } = stateProps + const { + connectedDomains, + domains, + selectedAddress, + tabToConnect, + } = stateProps const { disconnectAccount, + disconnectAllAccounts, legacyExposeAccounts: dispatchLegacyExposeAccounts, } = dispatchProps + const { history } = ownProps + + const closePopover = () => history.push(DEFAULT_ROUTE) return { ...ownProps, ...stateProps, ...dispatchProps, - disconnectAccount: (domainKey) => disconnectAccount(domainKey, domains[domainKey]), + closePopover, + disconnectAccount: (domainKey) => { + disconnectAccount(domainKey, selectedAddress) + if (connectedDomains.length === 1) { + closePopover() + } + }, + disconnectAllAccounts: (domainKey) => { + disconnectAllAccounts(domainKey, domains[domainKey]) + if (connectedDomains.length === 1) { + closePopover() + } + }, legacyExposeAccount: () => dispatchLegacyExposeAccounts(tabToConnect.origin, selectedAddress), } } diff --git a/ui/app/pages/connected-sites/index.scss b/ui/app/pages/connected-sites/index.scss index 9c97fde2d..85de05700 100644 --- a/ui/app/pages/connected-sites/index.scss +++ b/ui/app/pages/connected-sites/index.scss @@ -1,22 +1,32 @@ .connected-sites { + h2 { + text-overflow: ellipsis; + margin-right: 10px; + } + &__confirmation { + flex-direction: column; button:first-child { margin-right: 24px; } } - &__add-site-manually { - margin-top: -1px; + &__footer-row { + display: flex; + width: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + &__footer-row + &__footer-row { + margin-top: 15px; + } + + a, a:hover { font-size: 14px; line-height: 20px; - - & :only-child { - margin: 0; - } - - a, a:hover { - cursor: pointer; - color: #037DD6; - } + color: #037DD6; + cursor: pointer; } } diff --git a/ui/app/pages/permissions-connect/permissions-connect.container.js b/ui/app/pages/permissions-connect/permissions-connect.container.js index 536a4b1ef..815d2e700 100644 --- a/ui/app/pages/permissions-connect/permissions-connect.container.js +++ b/ui/app/pages/permissions-connect/permissions-connect.container.js @@ -6,9 +6,11 @@ import { getNativeCurrency, getAccountsWithLabels, getLastConnectedInfo, - getPermissionsDomains, getTargetDomainMetadata, } from '../../selectors/selectors' +import { + getPermissionsDomains, +} from '../../selectors/permissions' import { formatDate } from '../../helpers/utils/util' import { approvePermissionsRequest, rejectPermissionsRequest, showModal, getCurrentWindowTab, getRequestAccountTabIds } from '../../store/actions' import { diff --git a/ui/app/selectors/permissions.js b/ui/app/selectors/permissions.js index afe874257..ac47d5a05 100644 --- a/ui/app/selectors/permissions.js +++ b/ui/app/selectors/permissions.js @@ -25,7 +25,7 @@ export function getPermittedAccounts (state, origin) { * @returns {Object} Permitted accounts by origin. */ export function getPermittedAccountsByOrigin (state) { - const domains = allDomainsSelector(state) + const domains = getPermissionsDomains(state) return Object.keys(domains).reduce((acc, domainKey) => { const accounts = getAccountsFromPermission( getAccountsPermissionFromDomain(domains[domainKey]) @@ -67,7 +67,7 @@ function getAccountsCaveatFromPermission (accountsPermission = {}) { ) } -function allDomainsSelector (state) { +export function getPermissionsDomains (state) { return state.metamask.domains || {} } diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 64447acd1..bfde89f5e 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -14,8 +14,6 @@ import { import { getPermittedAccountsByOrigin } from './permissions' -export { getPermittedAccounts } from './permissions' - export function getNetworkIdentifier (state) { const { metamask: { provider: { type, nickname, rpcTarget } } } = state @@ -431,10 +429,6 @@ export function hasPermissionRequests (state) { return Boolean(getFirstPermissionRequest(state)) } -export function getPermissionsDomains (state) { - return state.metamask.domains -} - export function getAddressConnectedDomainMap (state) { const { domainMetadata,