Consolidate connected account alerts (#8802)

* update connected accounts appearance

* consolidate account alerts

* UnconnectedAccountAlert: use ConnectedAccountsList

* move switch account action out of menu in all views

Co-authored-by: Mark Stacey <markjstacey@gmail.com>
feature/default_network_editable
Erik Marks 5 years ago committed by GitHub
parent 3f8fa161ca
commit 4dfe4e7463
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      app/_locales/en/messages.json
  2. 19
      app/scripts/controllers/alert.js
  3. 2
      app/scripts/metamask-controller.js
  4. 3
      development/states/confirm-sig-requests.json
  5. 3
      development/states/currency-localization.json
  6. 3
      development/states/tx-list-items.json
  7. 7
      ui/app/components/app/alerts/alerts.js
  8. 2
      ui/app/components/app/alerts/alerts.scss
  9. 1
      ui/app/components/app/alerts/switch-to-connected-alert/index.js
  10. 121
      ui/app/components/app/alerts/switch-to-connected-alert/switch-to-connected-alert.js
  11. 66
      ui/app/components/app/alerts/switch-to-connected-alert/switch-to-connected-alert.scss
  12. 81
      ui/app/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js
  13. 34
      ui/app/components/app/alerts/unconnected-account-alert/unconnected-account-alert.scss
  14. 11
      ui/app/components/app/connected-accounts-list/connected-accounts-list-item/connected-accounts-list-item.component.js
  15. 1
      ui/app/components/app/connected-accounts-list/connected-accounts-list-permissions/index.js
  16. 123
      ui/app/components/app/connected-accounts-list/connected-accounts-list.component.js
  17. 88
      ui/app/components/app/connected-accounts-list/index.scss
  18. 2
      ui/app/components/app/connected-accounts-permissions/connected-accounts-permissions.component.js
  19. 1
      ui/app/components/app/connected-accounts-permissions/index.js
  20. 64
      ui/app/components/app/connected-accounts-permissions/index.scss
  21. 2
      ui/app/components/app/index.scss
  22. 2
      ui/app/components/ui/popover/index.scss
  23. 1
      ui/app/ducks/alerts/index.js
  24. 111
      ui/app/ducks/alerts/switch-to-connected.js
  25. 40
      ui/app/ducks/alerts/unconnected-account.js
  26. 3
      ui/app/ducks/index.js
  27. 4
      ui/app/ducks/metamask/metamask.js
  28. 24
      ui/app/helpers/utils/util.js
  29. 15
      ui/app/pages/connected-accounts/connected-accounts.component.js
  30. 11
      ui/app/pages/connected-accounts/connected-accounts.container.js
  31. 4
      ui/app/pages/settings/alerts-tab/alerts-tab.js
  32. 10
      ui/app/store/actions.js
  33. 18
      ui/index.js

@ -28,11 +28,8 @@
"connectedAccountsEmptyDescription": { "connectedAccountsEmptyDescription": {
"message": "MetaMask is not connected this site. To connect to a Web3 site, find the connect button on their site." "message": "MetaMask is not connected this site. To connect to a Web3 site, find the connect button on their site."
}, },
"primary": { "currentAccountNotConnected": {
"message": "Primary" "message": "Your current account is not connected"
},
"lastActive": {
"message": "Last active"
}, },
"switchToThisAccount": { "switchToThisAccount": {
"message": "Switch to this account" "message": "Switch to this account"
@ -139,6 +136,9 @@
"accountSelectionRequired": { "accountSelectionRequired": {
"message": "You need to select an account!" "message": "You need to select an account!"
}, },
"active": {
"message": "Active"
},
"activity": { "activity": {
"message": "Activity" "message": "Activity"
}, },
@ -187,17 +187,11 @@
"alertsSettingsDescription": { "alertsSettingsDescription": {
"message": "Enable or disable each alert" "message": "Enable or disable each alert"
}, },
"alertSettingsSwitchToConnected": {
"message": "Opening popup with an unconnected account selected"
},
"alertSettingsSwitchToConnectedDescription": {
"message": "This alert is shown when you open the popup with an unconnected account selected."
},
"alertSettingsUnconnectedAccount": { "alertSettingsUnconnectedAccount": {
"message": "Switching to an unconnected account" "message": "Browsing a website with an unconnected account selected"
}, },
"alertSettingsUnconnectedAccountDescription": { "alertSettingsUnconnectedAccountDescription": {
"message": "This alert is shown in the popup when you switch from a connected account to an unconnected account." "message": "This alert is shown in the popup when you are browsing a connected Web3 site, but the currently selected account is not connected."
}, },
"allowOriginSpendToken": { "allowOriginSpendToken": {
"message": "Allow $1 to spend your $2?", "message": "Allow $1 to spend your $2?",
@ -991,9 +985,6 @@
"noAlreadyHaveSeed": { "noAlreadyHaveSeed": {
"message": "No, I already have a seed phrase" "message": "No, I already have a seed phrase"
}, },
"notConnected": {
"message": "Not connected"
},
"protectYourKeys": { "protectYourKeys": {
"message": "Protect Your Keys!" "message": "Protect Your Keys!"
}, },
@ -1478,16 +1469,6 @@
"supportCenter": { "supportCenter": {
"message": "Visit our Support Center" "message": "Visit our Support Center"
}, },
"switchAccounts": {
"message": "Switch accounts"
},
"switchToConnectedAlertMultipleAccountsDescription": {
"message": "This account is not connected. Switch to a connected account?"
},
"switchToConnectedAlertSingleAccountDescription": {
"message": "This account is not connected. Switch to a connected account ($1)?",
"description": "$1 will be replaced by the name of the connected account"
},
"symbol": { "symbol": {
"message": "Symbol" "message": "Symbol"
}, },
@ -1635,10 +1616,7 @@
"unapproved": { "unapproved": {
"message": "Unapproved" "message": "Unapproved"
}, },
"unconnectedAccountAlertDescription": { "alertDisableTooltip": {
"message": "$1 is not connected to $2."
},
"unconnectedAccountAlertDisableTooltip": {
"message": "This can be changed in \"Settings > Alerts\"" "message": "This can be changed in \"Settings > Alerts\""
}, },
"units": { "units": {
@ -1776,11 +1754,5 @@
"encryptionPublicKeyNotice": { "encryptionPublicKeyNotice": {
"message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.", "message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.",
"description": "$1 is website or dapp name" "description": "$1 is website or dapp name"
},
"thisSite": {
"message": "this site"
},
"thisAccount": {
"message": "This account"
} }
} }

@ -12,7 +12,6 @@ import ObservableStore from 'obs-store'
*/ */
export const ALERT_TYPES = { export const ALERT_TYPES = {
switchToConnected: 'switchToConnected',
unconnectedAccount: 'unconnectedAccount', unconnectedAccount: 'unconnectedAccount',
} }
@ -25,7 +24,7 @@ const defaultState = {
}, },
{} {}
), ),
switchToConnectedAlertShown: {}, unconnectedAccountAlertShownOrigins: {},
} }
/** /**
@ -44,7 +43,7 @@ export default class AlertController {
defaultState, defaultState,
initState, initState,
{ {
switchToConnectedAlertShown: {}, unconnectedAccountAlertShownOrigins: {},
} }
) )
this.store = new ObservableStore(state) this.store = new ObservableStore(state)
@ -54,9 +53,9 @@ export default class AlertController {
preferencesStore.subscribe(({ selectedAddress }) => { preferencesStore.subscribe(({ selectedAddress }) => {
const currentState = this.store.getState() const currentState = this.store.getState()
if (currentState.switchToConnectedAlertShown && this.selectedAddress !== selectedAddress) { if (currentState.unconnectedAccountAlertShownOrigins && this.selectedAddress !== selectedAddress) {
this.selectedAddress = selectedAddress this.selectedAddress = selectedAddress
this.store.updateState({ switchToConnectedAlertShown: {} }) this.store.updateState({ unconnectedAccountAlertShownOrigins: {} })
} }
}) })
} }
@ -72,10 +71,10 @@ export default class AlertController {
* Sets the "switch to connected" alert as shown for the given origin * Sets the "switch to connected" alert as shown for the given origin
* @param {string} origin - The origin the alert has been shown for * @param {string} origin - The origin the alert has been shown for
*/ */
setSwitchToConnectedAlertShown (origin) { setUnconnectedAccountAlertShown (origin) {
let { switchToConnectedAlertShown } = this.store.getState() let { unconnectedAccountAlertShownOrigins } = this.store.getState()
switchToConnectedAlertShown = { ...switchToConnectedAlertShown } unconnectedAccountAlertShownOrigins = { ...unconnectedAccountAlertShownOrigins }
switchToConnectedAlertShown[origin] = true unconnectedAccountAlertShownOrigins[origin] = true
this.store.updateState({ switchToConnectedAlertShown }) this.store.updateState({ unconnectedAccountAlertShownOrigins })
} }
} }

@ -545,7 +545,7 @@ export default class MetamaskController extends EventEmitter {
// alert controller // alert controller
setAlertEnabledness: nodeify(alertController.setAlertEnabledness, alertController), setAlertEnabledness: nodeify(alertController.setAlertEnabledness, alertController),
setSwitchToConnectedAlertShown: nodeify(this.alertController.setSwitchToConnectedAlertShown, this.alertController), setUnconnectedAccountAlertShown: nodeify(this.alertController.setUnconnectedAccountAlertShown, this.alertController),
// 3Box // 3Box
setThreeBoxSyncingPermission: nodeify(threeBoxController.setThreeBoxSyncingPermission, threeBoxController), setThreeBoxSyncingPermission: nodeify(threeBoxController.setThreeBoxSyncingPermission, threeBoxController),

@ -522,9 +522,6 @@
"priceAndTimeEstimatesLastRetrieved": 1541527901281, "priceAndTimeEstimatesLastRetrieved": 1541527901281,
"errors": {} "errors": {}
}, },
"switchToConnected": {
"state": "CLOSED"
},
"unconnectedAccount": { "unconnectedAccount": {
"state": "CLOSED" "state": "CLOSED"
}, },

@ -473,9 +473,6 @@
"priceAndTimeEstimatesLastRetrieved": 1541527901281, "priceAndTimeEstimatesLastRetrieved": 1541527901281,
"errors": {} "errors": {}
}, },
"switchToConnected": {
"state": "CLOSED"
},
"unconnectedAccount": { "unconnectedAccount": {
"state": "CLOSED" "state": "CLOSED"
}, },

@ -1295,9 +1295,6 @@
"errors": {} "errors": {}
}, },
"confirmTransaction": {}, "confirmTransaction": {},
"switchToConnected": {
"state": "CLOSED"
},
"unconnectedAccount": { "unconnectedAccount": {
"state": "CLOSED" "state": "CLOSED"
} }

@ -2,22 +2,15 @@ import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import UnconnectedAccountAlert from './unconnected-account-alert' import UnconnectedAccountAlert from './unconnected-account-alert'
import SwitchToConnectedAlert from './switch-to-connected-alert'
import { alertIsOpen as unconnectedAccountAlertIsOpen } from '../../../ducks/alerts/unconnected-account' import { alertIsOpen as unconnectedAccountAlertIsOpen } from '../../../ducks/alerts/unconnected-account'
import { alertIsOpen as switchToConnectedAlertIsOpen } from '../../../ducks/alerts/switch-to-connected'
const Alerts = () => { const Alerts = () => {
const _unconnectedAccountAlertIsOpen = useSelector(unconnectedAccountAlertIsOpen) const _unconnectedAccountAlertIsOpen = useSelector(unconnectedAccountAlertIsOpen)
const _switchToConnectedAlertIsOpen = useSelector(switchToConnectedAlertIsOpen)
if (_unconnectedAccountAlertIsOpen) { if (_unconnectedAccountAlertIsOpen) {
return ( return (
<UnconnectedAccountAlert /> <UnconnectedAccountAlert />
) )
} else if (_switchToConnectedAlertIsOpen) {
return (
<SwitchToConnectedAlert />
)
} }
return null return null

@ -1,3 +1 @@
@import './unconnected-account-alert/unconnected-account-alert'; @import './unconnected-account-alert/unconnected-account-alert';
@import './switch-to-connected-alert/switch-to-connected-alert';

@ -1 +0,0 @@
export { default } from './switch-to-connected-alert'

@ -1,121 +0,0 @@
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {
ALERT_STATE,
switchToAccount,
dismissAlert,
dismissAndDisableAlert,
getAlertState,
} from '../../../../ducks/alerts/switch-to-connected'
import { getPermittedIdentitiesForCurrentTab } from '../../../../selectors'
import Popover from '../../../ui/popover'
import Button from '../../../ui/button'
import Dropdown from '../../../ui/dropdown'
import Checkbox from '../../../ui/check-box'
import Tooltip from '../../../ui/tooltip-v2'
import { useI18nContext } from '../../../../hooks/useI18nContext'
const {
ERROR,
LOADING,
} = ALERT_STATE
const SwitchToUnconnectedAccountAlert = () => {
const t = useI18nContext()
const dispatch = useDispatch()
const alertState = useSelector(getAlertState)
const connectedAccounts = useSelector(getPermittedIdentitiesForCurrentTab)
const [accountToSwitchTo, setAccountToSwitchTo] = useState(connectedAccounts[0].address)
const [dontShowThisAgain, setDontShowThisAgain] = useState(false)
const onClose = async () => {
return dontShowThisAgain
? await dispatch(dismissAndDisableAlert())
: dispatch(dismissAlert())
}
const options = connectedAccounts.map((account) => {
return { name: account.name, value: account.address }
})
return (
<Popover
contentClassName="switch-to-connected-alert__content"
footer={(
<>
{
alertState === ERROR
? (
<div className="switch-to-connected-alert__error">
{ t('failureMessage') }
</div>
)
: null
}
<div className="switch-to-connected-alert__footer-buttons">
<Button
disabled={alertState === LOADING}
onClick={onClose}
type="secondary"
>
{ t('dismiss') }
</Button>
<Button
disabled={alertState === LOADING || alertState === ERROR || dontShowThisAgain}
onClick={() => dispatch(switchToAccount(accountToSwitchTo))}
type="primary"
>
{ t('switchAccounts') }
</Button>
</div>
</>
)}
footerClassName="switch-to-connected-alert__footer"
onClose={onClose}
subtitle={
connectedAccounts.length > 1
? t('switchToConnectedAlertMultipleAccountsDescription')
: t('switchToConnectedAlertSingleAccountDescription', [connectedAccounts[0].name])
}
title={t('notConnected')}
>
{
connectedAccounts.length > 1
? (
<Dropdown
className="switch-to-connected-alert__dropdown"
title="Switch to account"
onChange={(address) => setAccountToSwitchTo(address)}
options={options}
selectedOption={accountToSwitchTo}
/>
)
: null
}
<div className="switch-to-connected-alert__checkbox-wrapper">
<Checkbox
id="switchToConnected_dontShowThisAgain"
checked={dontShowThisAgain}
className="switch-to-connected-alert__checkbox"
onClick={() => setDontShowThisAgain((checked) => !checked)}
/>
<label
className="switch-to-connected-alert__checkbox-label"
htmlFor="switchToConnected_dontShowThisAgain"
>
{ t('dontShowThisAgain') }
<Tooltip
position="top"
title={t('unconnectedAccountAlertDisableTooltip')}
wrapperClassName="switch-to-connected-alert__checkbox-label-tooltip"
>
<i className="fa fa-info-circle" />
</Tooltip>
</label>
</div>
</Popover>
)
}
export default SwitchToUnconnectedAccountAlert

@ -1,66 +0,0 @@
.switch-to-connected-alert {
&__footer {
flex-direction: column;
:only-child {
margin: 0;
}
}
&__footer-buttons {
display: flex;
flex-direction: row;
button:first-child {
margin-right: 24px;
}
button {
font-size: 14px;
line-height: 20px;
padding: 8px;
}
}
&__error {
margin-bottom: 16px;
padding: 16px;
font-size: 14px;
border: 1px solid #D73A49;
background: #F8EAE8;
border-radius: 3px;
}
&__content {
align-items: center;
padding: 0 24px 24px 24px;
}
&__dropdown {
background-color: white;
width: 100%;
margin-bottom: 24px;
}
&__checkbox-wrapper {
display: flex;
flex-direction: row;
width: 100%;
}
&__checkbox {
margin-right: 8px;
}
&__checkbox-label {
font-size: 14px;
margin-top: auto;
margin-bottom: auto;
color: $Grey-500;
display: flex;
}
&__checkbox-label-tooltip {
margin-left: 8px;
}
}

@ -7,12 +7,20 @@ import {
dismissAlert, dismissAlert,
dismissAndDisableAlert, dismissAndDisableAlert,
getAlertState, getAlertState,
switchToAccount,
} from '../../../../ducks/alerts/unconnected-account' } from '../../../../ducks/alerts/unconnected-account'
import {
getOriginOfCurrentTab,
getPermittedIdentitiesForCurrentTab,
getSelectedAddress,
getSelectedIdentity,
} from '../../../../selectors'
import { isExtensionUrl } from '../../../../helpers/utils/util'
import Popover from '../../../ui/popover' import Popover from '../../../ui/popover'
import Button from '../../../ui/button' import Button from '../../../ui/button'
import Checkbox from '../../../ui/check-box' import Checkbox from '../../../ui/check-box'
import Tooltip from '../../../ui/tooltip-v2' import Tooltip from '../../../ui/tooltip-v2'
import { getSelectedIdentity, getOriginOfCurrentTab } from '../../../../selectors' import ConnectedAccountsList from '../../connected-accounts-list'
import { useI18nContext } from '../../../../hooks/useI18nContext' import { useI18nContext } from '../../../../hooks/useI18nContext'
const { const {
@ -20,12 +28,14 @@ const {
LOADING, LOADING,
} = ALERT_STATE } = ALERT_STATE
const SwitchToUnconnectedAccountAlert = () => { const UnconnectedAccountAlert = () => {
const t = useI18nContext() const t = useI18nContext()
const dispatch = useDispatch() const dispatch = useDispatch()
const alertState = useSelector(getAlertState) const alertState = useSelector(getAlertState)
const connectedAccounts = useSelector(getPermittedIdentitiesForCurrentTab)
const origin = useSelector(getOriginOfCurrentTab) const origin = useSelector(getOriginOfCurrentTab)
const selectedIdentity = useSelector(getSelectedIdentity) const selectedIdentity = useSelector(getSelectedIdentity)
const selectedAddress = useSelector(getSelectedAddress)
const [dontShowThisAgain, setDontShowThisAgain] = useState(false) const [dontShowThisAgain, setDontShowThisAgain] = useState(false)
const onClose = async () => { const onClose = async () => {
@ -34,16 +44,7 @@ const SwitchToUnconnectedAccountAlert = () => {
: dispatch(dismissAlert()) : dispatch(dismissAlert())
} }
const accountName = selectedIdentity?.name || t('thisAccount') const footer = (
const siteName = origin || t('thisSite')
return (
<Popover
contentClassName="unconnected-account-alert__content"
title={t('notConnected')}
subtitle={t('unconnectedAccountAlertDescription', [ accountName, siteName ])}
onClose={onClose}
footer={(
<> <>
{ {
alertState === ERROR alertState === ERROR
@ -54,26 +55,8 @@ const SwitchToUnconnectedAccountAlert = () => {
) )
: null : null
} }
<div className="unconnected-account-alert__footer-buttons"> <div className="unconnected-account-alert__footer-row">
<Button <div className="unconnected-account-alert__checkbox-wrapper">
disabled={alertState === LOADING}
onClick={onClose}
type="secondary"
>
{ t('dismiss') }
</Button>
<Button
disabled={alertState === LOADING || alertState === ERROR || dontShowThisAgain }
onClick={() => dispatch(connectAccount())}
type="primary"
>
{ t('connect') }
</Button>
</div>
</>
)}
footerClassName="unconnected-account-alert__footer"
>
<Checkbox <Checkbox
id="unconnectedAccount_dontShowThisAgain" id="unconnectedAccount_dontShowThisAgain"
checked={dontShowThisAgain} checked={dontShowThisAgain}
@ -87,14 +70,44 @@ const SwitchToUnconnectedAccountAlert = () => {
{ t('dontShowThisAgain') } { t('dontShowThisAgain') }
<Tooltip <Tooltip
position="top" position="top"
title={t('unconnectedAccountAlertDisableTooltip')} title={t('alertDisableTooltip')}
wrapperClassName="unconnected-account-alert__checkbox-label-tooltip" wrapperClassName="unconnected-account-alert__checkbox-label-tooltip"
> >
<i className="fa fa-info-circle" /> <i className="fa fa-info-circle" />
</Tooltip> </Tooltip>
</label> </label>
</div>
<Button
disabled={alertState === LOADING}
onClick={onClose}
type="secondary"
className="unconnected-account-alert__dismiss-button"
>
{ t('dismiss') }
</Button>
</div>
</>
)
return (
<Popover
title={isExtensionUrl(origin) ? t('currentExtension') : new URL(origin).host}
subtitle={t('currentAccountNotConnected')}
onClose={onClose}
contentClassName="unconnected-account-alert__content"
footerClassName="unconnected-account-alert__footer"
footer={footer}
>
<ConnectedAccountsList
accountToConnect={selectedIdentity}
connectAccount={() => dispatch(connectAccount(selectedAddress))}
connectedAccounts={connectedAccounts}
selectedAddress={selectedAddress}
setSelectedAddress={(address) => dispatch(switchToAccount(address))}
shouldRenderListOptions={false}
/>
</Popover> </Popover>
) )
} }
export default SwitchToUnconnectedAccountAlert export default UnconnectedAccountAlert

@ -1,25 +1,28 @@
.unconnected-account-alert { .unconnected-account-alert {
&__content {
border-radius: 0;
}
&__footer { &__footer {
flex-direction: column; flex-direction: column;
:only-child { > :only-child {
margin: 0; margin: 0;
} }
} }
&__footer-buttons { &__footer-row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
button:first-child {
margin-right: 24px;
} }
button { &__dismiss-button {
font-size: 14px; background: #037DD6;
line-height: 20px; color: white;
padding: 8px; height: 40px;
} width: 100px;
border: 0;
border-radius: 100px;
} }
&__error { &__error {
@ -31,21 +34,24 @@
border-radius: 3px; border-radius: 3px;
} }
&__content { &__checkbox-wrapper {
width: 100%;
display: flex;
flex-direction: row; flex-direction: row;
padding: 0 24px 24px 24px; align-items: center;
} }
&__checkbox { &__checkbox {
margin-right: 8px; margin-right: 8px;
padding-top: 1px; // better alignment with rest of content
} }
&__checkbox-label { &__checkbox-label {
font-size: 14px; display: flex;
font-size: 12px;
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
color: $Grey-500; color: $Grey-500;
display: flex;
} }
&__checkbox-label-tooltip { &__checkbox-label-tooltip {

@ -12,13 +12,15 @@ export default class ConnectedAccountsListItem extends PureComponent {
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
className: PropTypes.string, className: PropTypes.string,
name: PropTypes.node.isRequired, name: PropTypes.node.isRequired,
status: PropTypes.node.isRequired, status: PropTypes.string,
action: PropTypes.node,
options: PropTypes.node, options: PropTypes.node,
} }
static defaultProps = { static defaultProps = {
className: null, className: null,
options: null, options: null,
action: null,
} }
render () { render () {
@ -27,6 +29,7 @@ export default class ConnectedAccountsListItem extends PureComponent {
className, className,
name, name,
status, status,
action,
options, options,
} = this.props } = this.props
@ -39,18 +42,20 @@ export default class ConnectedAccountsListItem extends PureComponent {
diameter={32} diameter={32}
/> />
<div> <div>
<p> <p className="connected-accounts-list__account-name">
<strong className="connected-accounts-list__account-name">{name}</strong> <strong>{name}</strong>
</p> </p>
{ {
status status
? ( ? (
<p className="connected-accounts-list__account-status"> <p className="connected-accounts-list__account-status">
&nbsp;&nbsp;
{status} {status}
</p> </p>
) )
: null : null
} }
{action}
</div> </div>
</div> </div>
{options} {options}

@ -1 +0,0 @@
export { default } from './connected-accounts-list-permissions.component'

@ -1,7 +1,5 @@
import { DateTime } from 'luxon'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import ConnectedAccountsListPermissions from './connected-accounts-list-permissions'
import ConnectedAccountsListItem from './connected-accounts-list-item' import ConnectedAccountsListItem from './connected-accounts-list-item'
import ConnectedAccountsListOptions from './connected-accounts-list-options' import ConnectedAccountsListOptions from './connected-accounts-list-options'
import { MenuItem } from '../../ui/menu' import { MenuItem } from '../../ui/menu'
@ -13,7 +11,6 @@ export default class ConnectedAccountsList extends PureComponent {
static defaultProps = { static defaultProps = {
accountToConnect: null, accountToConnect: null,
permissions: undefined,
} }
static propTypes = { static propTypes = {
@ -26,31 +23,35 @@ export default class ConnectedAccountsList extends PureComponent {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
lastActive: PropTypes.number, lastActive: PropTypes.number,
})).isRequired, })).isRequired,
permissions: PropTypes.arrayOf(PropTypes.shape({ connectAccount: PropTypes.func.isRequired,
key: PropTypes.string.isRequired,
})),
selectedAddress: PropTypes.string.isRequired, selectedAddress: PropTypes.string.isRequired,
addPermittedAccount: PropTypes.func.isRequired, removePermittedAccount: PropTypes.func,
removePermittedAccount: PropTypes.func.isRequired,
setSelectedAddress: PropTypes.func.isRequired, setSelectedAddress: PropTypes.func.isRequired,
shouldRenderListOptions: (props, propName, componentName) => {
if (typeof props[propName] !== 'boolean') {
return new Error(
`Warning: Failed prop type: '${propName}' of component '${componentName}' must be a boolean. Received: ${typeof props[propName]}`
)
} else if (props[propName] && !props['removePermittedAccount']) {
return new Error(
`Warning: Failed prop type: '${propName}' of component '${componentName}' requires prop 'removePermittedAccount'.`
)
}
},
} }
state = { state = {
accountWithOptionsShown: null, accountWithOptionsShown: null,
} }
connectAccount = () => {
this.props.addPermittedAccount(this.props.accountToConnect?.address)
}
disconnectAccount = () => { disconnectAccount = () => {
this.hideAccountOptions() this.hideAccountOptions()
this.props.removePermittedAccount(this.state.accountWithOptionsShown) this.props.removePermittedAccount(this.state.accountWithOptionsShown)
} }
switchAccount = () => { switchAccount = (address) => {
this.hideAccountOptions() this.hideAccountOptions()
this.props.setSelectedAddress(this.state.accountWithOptionsShown) this.props.setSelectedAddress(address)
} }
hideAccountOptions = () => { hideAccountOptions = () => {
@ -62,7 +63,7 @@ export default class ConnectedAccountsList extends PureComponent {
} }
renderUnconnectedAccount () { renderUnconnectedAccount () {
const { accountToConnect } = this.props const { accountToConnect, connectAccount } = this.props
const { t } = this.context const { t } = this.context
if (!accountToConnect) { if (!accountToConnect) {
@ -75,59 +76,29 @@ export default class ConnectedAccountsList extends PureComponent {
className="connected-accounts-list__row--highlight" className="connected-accounts-list__row--highlight"
address={address} address={address}
name={`${name} (…${address.substr(-4, 4)})`} name={`${name} (…${address.substr(-4, 4)})`}
status={( status={t('statusNotConnected')}
<> action={(
{t('statusNotConnected')} <a
&nbsp;&middot;&nbsp; className="connected-accounts-list__account-status-link"
<a className="connected-accounts-list__account-status-link" onClick={this.connectAccount}> onClick={() => connectAccount(accountToConnect.address)}
>
{t('connect')} {t('connect')}
</a> </a>
</>
)} )}
/> />
) )
} }
render () { renderListItemOptions (address) {
const { connectedAccounts, permissions, selectedAddress } = this.props
const { accountWithOptionsShown } = this.state const { accountWithOptionsShown } = this.state
const { t } = this.context const { t } = this.context
return ( return (
<>
<main className="connected-accounts-list">
{this.renderUnconnectedAccount()}
{
connectedAccounts.map(({ address, name, lastActive }, index) => {
let status
if (index === 0) {
status = t('primary')
} else if (lastActive) {
status = `${t('lastActive')}: ${DateTime.fromMillis(lastActive).toISODate()}`
}
return (
<ConnectedAccountsListItem
key={address}
address={address}
name={`${name} (…${address.substr(-4, 4)})`}
status={status}
options={(
<ConnectedAccountsListOptions <ConnectedAccountsListOptions
onHideOptions={this.hideAccountOptions} onHideOptions={this.hideAccountOptions}
onShowOptions={this.showAccountOptions.bind(null, address)} onShowOptions={this.showAccountOptions.bind(null, address)}
show={accountWithOptionsShown === address} show={accountWithOptionsShown === address}
> >
{
address === selectedAddress ? null : (
<MenuItem
iconClassName="fas fa-random"
onClick={this.switchAccount}
>
{t('switchToThisAccount')}
</MenuItem>
)
}
<MenuItem <MenuItem
iconClassName="disconnect-icon" iconClassName="disconnect-icon"
onClick={this.disconnectAccount} onClick={this.disconnectAccount}
@ -135,13 +106,57 @@ export default class ConnectedAccountsList extends PureComponent {
{t('disconnectThisAccount')} {t('disconnectThisAccount')}
</MenuItem> </MenuItem>
</ConnectedAccountsListOptions> </ConnectedAccountsListOptions>
)} )
}
renderListItemAction (address) {
const { t } = this.context
return (
<a
className="connected-accounts-list__account-status-link"
onClick={() => this.switchAccount(address)}
>
{t('switchToThisAccount')}
</a>
)
}
render () {
const {
connectedAccounts,
selectedAddress,
shouldRenderListOptions,
} = this.props
const { t } = this.context
return (
<>
<main className="connected-accounts-list">
{this.renderUnconnectedAccount()}
{
connectedAccounts.map(({ address, name }, index) => {
return (
<ConnectedAccountsListItem
key={address}
address={address}
name={`${name} (…${address.substr(-4, 4)})`}
status={index === 0 ? t('active') : null}
options={
shouldRenderListOptions
? this.renderListItemOptions(address)
: null
}
action={
address !== selectedAddress
? this.renderListItemAction(address)
: null
}
/> />
) )
}) })
} }
</main> </main>
<ConnectedAccountsListPermissions permissions={permissions} />
</> </>
) )
} }

@ -8,28 +8,34 @@
} }
&__account-name { &__account-name {
display: inline;
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
line-height: 20px; line-height: 20px;
} }
%account-status-typography {
font-size: 12px;
line-height: 17px;
padding-top: 4px;
}
&__account-status { &__account-status {
@extend %account-status-typography;
display: inline;
color: $Grey-500; color: $Grey-500;
} }
&__account-status-link { &__account-status-link {
@extend %account-status-typography;
display: block;
&, &:hover { &, &:hover {
color: $curious-blue; color: $curious-blue;
cursor: pointer; cursor: pointer;
} }
} }
&__account-status {
font-size: 12px;
line-height: 17px;
padding-top: 4px;
}
&__row { &__row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -40,10 +46,6 @@
border-top: 1px solid $geyser; border-top: 1px solid $geyser;
&:last-of-type {
border-bottom: 1px solid $geyser;
}
&--highlight { &--highlight {
background-color: $warning-light-yellow; background-color: $warning-light-yellow;
border: 1px solid $warning-yellow; border: 1px solid $warning-yellow;
@ -70,72 +72,6 @@
} }
} }
.connected-accounts-permissions {
display: flex;
flex-direction: column;
padding: 24px;
font-size: 12px;
line-height: 17px;
color: $Grey-500;
strong {
font-weight: bold;
}
p + p {
padding-top: 8px;
}
&__header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
cursor: pointer;
font-size: 14px;
line-height: 20px;
color: #24292E;
button {
font-size: 16px;
line-height: 24px;
background: none;
padding: 0;
margin-left: 8px;
}
}
&__list {
padding-top: 8px;
}
&__list-item {
display: flex;
i {
display: block;
padding-right: 8px;
font-size: 18px;
color: $Grey-800;
}
}
&__list-container {
max-height: 0px;
overflow: hidden;
height: auto;
transition: max-height 0.8s cubic-bezier(0.4, 0.0, 0.2, 1);
&--expanded {
// arbitrarily set hard coded value for effect to work
max-height: 100px;
}
}
}
.tippy-tooltip.none-theme { .tippy-tooltip.none-theme {
background: none; background: none;
padding: 0; padding: 0;

@ -2,7 +2,7 @@ import classnames from 'classnames'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
export default class ConnectedAccountsListPermissions extends PureComponent { export default class ConnectedAccountsPermissions extends PureComponent {
static contextTypes = { static contextTypes = {
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
} }

@ -0,0 +1 @@
export { default } from './connected-accounts-permissions.component'

@ -0,0 +1,64 @@
.connected-accounts-permissions {
display: flex;
flex-direction: column;
font-size: 12px;
line-height: 17px;
color: $Grey-500;
strong {
font-weight: bold;
}
p + p {
padding-top: 8px;
}
&__header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
cursor: pointer;
font-size: 14px;
line-height: 20px;
color: #24292E;
button {
font-size: 16px;
line-height: 24px;
background: none;
padding: 0;
margin-left: 8px;
}
}
&__list {
padding-top: 8px;
}
&__list-item {
display: flex;
i {
display: block;
padding-right: 8px;
font-size: 18px;
color: $Grey-800;
}
}
&__list-container {
max-height: 0px;
overflow: hidden;
height: auto;
transition: max-height 0.8s cubic-bezier(0.4, 0.0, 0.2, 1);
&--expanded {
// arbitrarily set hard coded value for effect to work
max-height: 100px;
}
}
}

@ -88,6 +88,8 @@
@import 'connected-accounts-list/index'; @import 'connected-accounts-list/index';
@import 'connected-accounts-permissions/index';
@import '../ui/icon-with-fallback/index'; @import '../ui/icon-with-fallback/index';
@import '../ui/icon/index'; @import '../ui/icon/index';

@ -107,7 +107,7 @@
border-top: 1px solid #D2D8DD; border-top: 1px solid #D2D8DD;
padding: 16px 24px 24px; padding: 16px 24px 24px;
& :only-child { > :only-child {
margin: 0 auto; margin: 0 auto;
} }
} }

@ -1,2 +1 @@
export { default as switchToConnected } from './switch-to-connected'
export { default as unconnectedAccount } from './unconnected-account' export { default as unconnectedAccount } from './unconnected-account'

@ -1,111 +0,0 @@
import { createSlice } from '@reduxjs/toolkit'
import { captureException } from '@sentry/browser'
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert'
import * as actionConstants from '../../store/actionConstants'
import { setAlertEnabledness, setSelectedAddress } from '../../store/actions'
// Constants
export const ALERT_STATE = {
CLOSED: 'CLOSED',
ERROR: 'ERROR',
LOADING: 'LOADING',
OPEN: 'OPEN',
}
const name = ALERT_TYPES.switchToConnected
const initialState = {
state: ALERT_STATE.CLOSED,
}
// Slice (reducer plus auto-generated actions and action creators)
const slice = createSlice({
name,
initialState,
reducers: {
disableAlertFailed: (state) => {
state.state = ALERT_STATE.ERROR
},
disableAlertRequested: (state) => {
state.state = ALERT_STATE.LOADING
},
disableAlertSucceeded: (state) => {
state.state = ALERT_STATE.CLOSED
},
dismissAlert: (state) => {
state.state = ALERT_STATE.CLOSED
},
switchAccountFailed: (state) => {
state.state = ALERT_STATE.ERROR
},
switchAccountRequested: (state) => {
state.state = ALERT_STATE.LOADING
},
switchAccountSucceeded: (state) => {
state.state = ALERT_STATE.CLOSED
},
},
extraReducers: {
[actionConstants.SELECTED_ADDRESS_CHANGED]: (state) => {
// close the alert if the account is switched while it's open
if (state.state === ALERT_STATE.OPEN) {
state.state = ALERT_STATE.CLOSED
}
},
},
})
const { actions, reducer } = slice
export default reducer
// Selectors
export const getAlertState = (state) => state[name].state
export const alertIsOpen = (state) => state[name].state !== ALERT_STATE.CLOSED
// Actions / action-creators
const {
disableAlertFailed,
disableAlertRequested,
disableAlertSucceeded,
dismissAlert,
switchAccountFailed,
switchAccountRequested,
switchAccountSucceeded,
} = actions
export { dismissAlert }
export const dismissAndDisableAlert = () => {
return async (dispatch) => {
try {
await dispatch(disableAlertRequested())
await dispatch(setAlertEnabledness(name, false))
await dispatch(disableAlertSucceeded())
} catch (error) {
console.error(error)
captureException(error)
await dispatch(disableAlertFailed())
}
}
}
export const switchToAccount = (address) => {
return async (dispatch) => {
try {
await dispatch(switchAccountRequested())
await dispatch(setSelectedAddress(address))
await dispatch(switchAccountSucceeded())
} catch (error) {
console.error(error)
captureException(error)
await dispatch(switchAccountFailed())
}
}
}

@ -3,7 +3,11 @@ import { captureException } from '@sentry/browser'
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert' import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert'
import * as actionConstants from '../../store/actionConstants' import * as actionConstants from '../../store/actionConstants'
import { addPermittedAccount, setAlertEnabledness } from '../../store/actions' import {
addPermittedAccount,
setAlertEnabledness,
setSelectedAddress,
} from '../../store/actions'
import { import {
getOriginOfCurrentTab, getOriginOfCurrentTab,
getSelectedAddress, getSelectedAddress,
@ -39,9 +43,6 @@ const slice = createSlice({
connectAccountSucceeded: (state) => { connectAccountSucceeded: (state) => {
state.state = ALERT_STATE.CLOSED state.state = ALERT_STATE.CLOSED
}, },
dismissAlert: (state) => {
state.state = ALERT_STATE.CLOSED
},
disableAlertFailed: (state) => { disableAlertFailed: (state) => {
state.state = ALERT_STATE.ERROR state.state = ALERT_STATE.ERROR
}, },
@ -51,6 +52,18 @@ const slice = createSlice({
disableAlertSucceeded: (state) => { disableAlertSucceeded: (state) => {
state.state = ALERT_STATE.CLOSED state.state = ALERT_STATE.CLOSED
}, },
dismissAlert: (state) => {
state.state = ALERT_STATE.CLOSED
},
switchAccountFailed: (state) => {
state.state = ALERT_STATE.ERROR
},
switchAccountRequested: (state) => {
state.state = ALERT_STATE.LOADING
},
switchAccountSucceeded: (state) => {
state.state = ALERT_STATE.CLOSED
},
switchedToUnconnectedAccount: (state) => { switchedToUnconnectedAccount: (state) => {
state.state = ALERT_STATE.OPEN state.state = ALERT_STATE.OPEN
}, },
@ -81,10 +94,13 @@ const {
connectAccountFailed, connectAccountFailed,
connectAccountRequested, connectAccountRequested,
connectAccountSucceeded, connectAccountSucceeded,
dismissAlert,
disableAlertFailed, disableAlertFailed,
disableAlertRequested, disableAlertRequested,
disableAlertSucceeded, disableAlertSucceeded,
dismissAlert,
switchAccountFailed,
switchAccountRequested,
switchAccountSucceeded,
switchedToUnconnectedAccount, switchedToUnconnectedAccount,
} = actions } = actions
@ -104,6 +120,20 @@ export const dismissAndDisableAlert = () => {
} }
} }
export const switchToAccount = (address) => {
return async (dispatch) => {
try {
await dispatch(switchAccountRequested())
await dispatch(setSelectedAddress(address))
await dispatch(switchAccountSucceeded())
} catch (error) {
console.error(error)
captureException(error)
await dispatch(switchAccountFailed())
}
}
}
export const connectAccount = () => { export const connectAccount = () => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState() const state = getState()

@ -5,12 +5,11 @@ import sendReducer from './send/send.duck'
import appStateReducer from './app/app' import appStateReducer from './app/app'
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck' import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'
import gasReducer from './gas/gas.duck' import gasReducer from './gas/gas.duck'
import { switchToConnected, unconnectedAccount } from './alerts' import { unconnectedAccount } from './alerts'
import historyReducer from './history/history' import historyReducer from './history/history'
import { ALERT_TYPES } from '../../../app/scripts/controllers/alert' import { ALERT_TYPES } from '../../../app/scripts/controllers/alert'
export default combineReducers({ export default combineReducers({
[ALERT_TYPES.switchToConnected]: switchToConnected,
[ALERT_TYPES.unconnectedAccount]: unconnectedAccount, [ALERT_TYPES.unconnectedAccount]: unconnectedAccount,
activeTab: (s) => (s === undefined ? null : s), activeTab: (s) => (s === undefined ? null : s),
metamask: metamaskReducer, metamask: metamaskReducer,

@ -371,10 +371,8 @@ export const getCurrentLocale = (state) => state.metamask.currentLocale
export const getAlertEnabledness = (state) => state.metamask.alertEnabledness export const getAlertEnabledness = (state) => state.metamask.alertEnabledness
export const getSwitchToConnectedAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.switchToConnected]
export const getUnconnectedAccountAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount] export const getUnconnectedAccountAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount]
export const getSwitchToConnectedAlertShown = (state) => state.metamask.switchToConnectedAlertShown export const getUnconnectedAccountAlertShown = (state) => state.metamask.unconnectedAccountAlertShownOrigins
export const getTokens = (state) => state.metamask.tokens export const getTokens = (state) => state.metamask.tokens

@ -309,3 +309,27 @@ export function getAccountByAddress (accounts = [], targetAddress) {
export function stripHttpSchemes (urlString) { export function stripHttpSchemes (urlString) {
return urlString.replace(/^https?:\/\//u, '') return urlString.replace(/^https?:\/\//u, '')
} }
/**
* Checks whether a URL-like value (object or string) is an extension URL.
*
* @param {string | URL | object} urlLike - The URL-like value to test.
* @returns {boolean} Whether the URL-like value is an extension URL.
*/
export function isExtensionUrl (urlLike) {
const EXT_PROTOCOLS = ['chrome-extension:', 'moz-extension:']
if (typeof urlLike === 'string') {
for (const protocol of EXT_PROTOCOLS) {
if (urlLike.startsWith(protocol)) {
return true
}
}
}
if (urlLike?.protocol) {
return EXT_PROTOCOLS.includes(urlLike.protocol)
}
return false
}

@ -1,8 +1,8 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { CONNECTED_ROUTE } from '../../helpers/constants/routes'
import Popover from '../../components/ui/popover' import Popover from '../../components/ui/popover'
import ConnectedAccountsList from '../../components/app/connected-accounts-list' import ConnectedAccountsList from '../../components/app/connected-accounts-list'
import ConnectedAccountsPermissions from '../../components/app/connected-accounts-permissions'
export default class ConnectedAccounts extends PureComponent { export default class ConnectedAccounts extends PureComponent {
static contextTypes = { static contextTypes = {
@ -17,7 +17,7 @@ export default class ConnectedAccounts extends PureComponent {
static propTypes = { static propTypes = {
accountToConnect: PropTypes.object, accountToConnect: PropTypes.object,
activeTabOrigin: PropTypes.string.isRequired, activeTabOrigin: PropTypes.string.isRequired,
addPermittedAccount: PropTypes.func.isRequired, connectAccount: PropTypes.func.isRequired,
connectedAccounts: PropTypes.array.isRequired, connectedAccounts: PropTypes.array.isRequired,
mostRecentOverviewPage: PropTypes.string.isRequired, mostRecentOverviewPage: PropTypes.string.isRequired,
permissions: PropTypes.array, permissions: PropTypes.array,
@ -28,16 +28,12 @@ export default class ConnectedAccounts extends PureComponent {
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
} }
viewConnectedSites = () => {
this.props.history.push(CONNECTED_ROUTE)
}
render () { render () {
const { const {
accountToConnect, accountToConnect,
activeTabOrigin, activeTabOrigin,
isActiveTabExtension, isActiveTabExtension,
addPermittedAccount, connectAccount,
connectedAccounts, connectedAccounts,
history, history,
mostRecentOverviewPage, mostRecentOverviewPage,
@ -58,15 +54,16 @@ export default class ConnectedAccounts extends PureComponent {
subtitle={connectedAccounts.length ? connectedAccountsDescription : t('connectedAccountsEmptyDescription')} subtitle={connectedAccounts.length ? connectedAccountsDescription : t('connectedAccountsEmptyDescription')}
onClose={() => history.push(mostRecentOverviewPage)} onClose={() => history.push(mostRecentOverviewPage)}
footerClassName="connected-accounts__footer" footerClassName="connected-accounts__footer"
footer={<ConnectedAccountsPermissions permissions={permissions} />}
> >
<ConnectedAccountsList <ConnectedAccountsList
accountToConnect={accountToConnect} accountToConnect={accountToConnect}
addPermittedAccount={addPermittedAccount} connectAccount={connectAccount}
connectedAccounts={connectedAccounts} connectedAccounts={connectedAccounts}
permissions={permissions}
selectedAddress={selectedAddress} selectedAddress={selectedAddress}
removePermittedAccount={removePermittedAccount} removePermittedAccount={removePermittedAccount}
setSelectedAddress={setSelectedAddress} setSelectedAddress={setSelectedAddress}
shouldRenderListOptions
/> />
</Popover> </Popover>
) )

@ -6,11 +6,10 @@ import {
getPermissionsForActiveTab, getPermissionsForActiveTab,
getSelectedAddress, getSelectedAddress,
} from '../../selectors' } from '../../selectors'
import { isExtensionUrl } from '../../helpers/utils/util'
import { addPermittedAccount, removePermittedAccount, setSelectedAddress } from '../../store/actions' import { addPermittedAccount, removePermittedAccount, setSelectedAddress } from '../../store/actions'
import { getMostRecentOverviewPage } from '../../ducks/history/history' import { getMostRecentOverviewPage } from '../../ducks/history/history'
const EXT_PROTOCOLS = ['chrome-extension:', 'moz-extension:']
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { activeTab } = state const { activeTab } = state
const accountToConnect = getAccountToConnectToActiveTab(state) const accountToConnect = getAccountToConnectToActiveTab(state)
@ -18,7 +17,7 @@ const mapStateToProps = (state) => {
const permissions = getPermissionsForActiveTab(state) const permissions = getPermissionsForActiveTab(state)
const selectedAddress = getSelectedAddress(state) const selectedAddress = getSelectedAddress(state)
const isActiveTabExtension = EXT_PROTOCOLS.includes(activeTab.protocol) const isActiveTabExtension = isExtensionUrl(activeTab)
return { return {
accountToConnect, accountToConnect,
isActiveTabExtension, isActiveTabExtension,
@ -39,14 +38,14 @@ const mapDispatchToProps = (dispatch) => {
} }
const mergeProps = (stateProps, dispatchProps, ownProps) => { const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { activeTabOrigin: origin } = stateProps const { activeTabOrigin } = stateProps
return { return {
...ownProps, ...ownProps,
...stateProps, ...stateProps,
...dispatchProps, ...dispatchProps,
addPermittedAccount: (address) => dispatchProps.addPermittedAccount(origin, address), connectAccount: (address) => dispatchProps.addPermittedAccount(activeTabOrigin, address),
removePermittedAccount: (address) => dispatchProps.removePermittedAccount(origin, address), removePermittedAccount: (address) => dispatchProps.removePermittedAccount(activeTabOrigin, address),
} }
} }

@ -46,10 +46,6 @@ const AlertsTab = () => {
const t = useI18nContext() const t = useI18nContext()
const alertConfig = { const alertConfig = {
[ALERT_TYPES.switchToConnected]: {
title: t('alertSettingsSwitchToConnected'),
description: t('alertSettingsSwitchToConnectedDescription'),
},
[ALERT_TYPES.unconnectedAccount]: { [ALERT_TYPES.unconnectedAccount]: {
title: t('alertSettingsUnconnectedAccount'), title: t('alertSettingsUnconnectedAccount'),
description: t('alertSettingsUnconnectedAccountDescription'), description: t('alertSettingsUnconnectedAccountDescription'),

@ -1187,7 +1187,7 @@ export function showAccountDetail (address) {
log.debug(`background.setSelectedAddress`) log.debug(`background.setSelectedAddress`)
const state = getState() const state = getState()
const unconnectedAccountAlertIsEnabled = getUnconnectedAccountAlertEnabledness(state) const unconnectedAccountAccountAlertIsEnabled = getUnconnectedAccountAlertEnabledness(state)
const activeTabOrigin = state.activeTab.origin const activeTabOrigin = state.activeTab.origin
const selectedAddress = getSelectedAddress(state) const selectedAddress = getSelectedAddress(state)
const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(state) const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(state)
@ -1207,9 +1207,9 @@ export function showAccountDetail (address) {
type: actionConstants.SHOW_ACCOUNT_DETAIL, type: actionConstants.SHOW_ACCOUNT_DETAIL,
value: address, value: address,
}) })
if (unconnectedAccountAlertIsEnabled && switchingToUnconnectedAddress) { if (unconnectedAccountAccountAlertIsEnabled && switchingToUnconnectedAddress) {
dispatch(switchedToUnconnectedAccount()) dispatch(switchedToUnconnectedAccount())
await setSwitchToConnectedAlertShown(activeTabOrigin) await setUnconnectedAccountAlertShown(activeTabOrigin)
} }
} }
} }
@ -2139,8 +2139,8 @@ export function setAlertEnabledness (alertId, enabledness) {
} }
} }
export async function setSwitchToConnectedAlertShown (origin) { export async function setUnconnectedAccountAlertShown (origin) {
await promisifiedBackground.setSwitchToConnectedAlertShown(origin) await promisifiedBackground.setUnconnectedAccountAlertShown(origin)
} }
export function loadingMethodDataStarted () { export function loadingMethodDataStarted () {

@ -13,10 +13,10 @@ import { ENVIRONMENT_TYPE_POPUP } from '../app/scripts/lib/enums'
import { fetchLocale } from './app/helpers/utils/i18n-helper' import { fetchLocale } from './app/helpers/utils/i18n-helper'
import switchDirection from './app/helpers/utils/switch-direction' import switchDirection from './app/helpers/utils/switch-direction'
import { getPermittedAccountsForCurrentTab, getSelectedAddress } from './app/selectors' import { getPermittedAccountsForCurrentTab, getSelectedAddress } from './app/selectors'
import { ALERT_STATE } from './app/ducks/alerts/switch-to-connected' import { ALERT_STATE } from './app/ducks/alerts/unconnected-account'
import { import {
getSwitchToConnectedAlertEnabledness, getUnconnectedAccountAlertEnabledness,
getSwitchToConnectedAlertShown, getUnconnectedAccountAlertShown,
} from './app/ducks/metamask/metamask' } from './app/ducks/metamask/metamask'
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn') log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn')
@ -71,18 +71,18 @@ async function startApp (metamaskState, backgroundConnection, opts) {
const origin = draftInitialState.activeTab.origin const origin = draftInitialState.activeTab.origin
const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(draftInitialState) const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(draftInitialState)
const selectedAddress = getSelectedAddress(draftInitialState) const selectedAddress = getSelectedAddress(draftInitialState)
const switchToConnectedAlertShown = getSwitchToConnectedAlertShown(draftInitialState) const unconnectedAccountAlertShownOrigins = getUnconnectedAccountAlertShown(draftInitialState)
const switchToConnectedAlertIsEnabled = getSwitchToConnectedAlertEnabledness(draftInitialState) const unconnectedAccountAlertIsEnabled = getUnconnectedAccountAlertEnabledness(draftInitialState)
if ( if (
origin && origin &&
switchToConnectedAlertIsEnabled && unconnectedAccountAlertIsEnabled &&
!switchToConnectedAlertShown[origin] && !unconnectedAccountAlertShownOrigins[origin] &&
permittedAccountsForCurrentTab.length > 0 && permittedAccountsForCurrentTab.length > 0 &&
!permittedAccountsForCurrentTab.includes(selectedAddress) !permittedAccountsForCurrentTab.includes(selectedAddress)
) { ) {
draftInitialState[ALERT_TYPES.switchToConnected] = { state: ALERT_STATE.OPEN } draftInitialState[ALERT_TYPES.unconnectedAccount] = { state: ALERT_STATE.OPEN }
actions.setSwitchToConnectedAlertShown(origin) actions.setUnconnectedAccountAlertShown(origin)
} }
} }

Loading…
Cancel
Save