Add switch to connected account alert (#8532)
Add alert suggesting that the user switch to a connected account. This alert is displayed when the popup is opened over an active tab that is connected to some account, but not the current selected account. The user can choose to switch to a connected account, or dismiss the alert. This alert is only shown once per account switch. So if the user repeatedly opens the popup on a dapp without switching accounts, it'll only be shown the first time. The alert also won't be shown if the user has just dismissed an "Unconnected account" alert on this same dapp and account, as that would be redundant. The alert has a "Don't show me this again" checkbox that allows the user to disable the alert. It can be re-enabled again on the Alerts settings page.feature/default_network_editable
parent
6868688a03
commit
53ec42d95f
@ -1 +1,3 @@ |
||||
@import './unconnected-account-alert/unconnected-account-alert.scss'; |
||||
|
||||
@import './switch-to-connected-alert/switch-to-connected-alert.scss'; |
||||
|
@ -0,0 +1 @@ |
||||
export { default } from './switch-to-connected-alert' |
@ -0,0 +1,121 @@ |
||||
import React, { useContext, 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 { I18nContext } from '../../../../contexts/i18n' |
||||
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' |
||||
|
||||
const { |
||||
ERROR, |
||||
LOADING, |
||||
} = ALERT_STATE |
||||
|
||||
const SwitchToUnconnectedAccountAlert = () => { |
||||
const t = useContext(I18nContext) |
||||
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 |
@ -0,0 +1,66 @@ |
||||
.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; |
||||
} |
||||
} |
@ -1 +1,2 @@ |
||||
export { default as switchToConnected } from './switch-to-connected' |
||||
export { default as unconnectedAccount } from './unconnected-account' |
||||
|
@ -0,0 +1,111 @@ |
||||
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()) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue