Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alert user upon switching to unconnected account #8312

Merged
merged 3 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,9 @@
"failed": {
"message": "Failed"
},
"failureMessage": {
"message": "Something went wrong, and we were unable to complete the action"
},
"fast": {
"message": "Fast"
},
Expand Down Expand Up @@ -1530,6 +1533,12 @@
"unapproved": {
"message": "Unapproved"
},
"unconnectedAccountAlertTitle": {
"message": "Not connected"
},
"unconnectedAccountAlertDescription": {
"message": "This account is not connected to this site"
},
"units": {
"message": "units"
},
Expand Down
3 changes: 3 additions & 0 deletions development/states/confirm-sig-requests.json
Original file line number Diff line number Diff line change
Expand Up @@ -521,5 +521,8 @@
],
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
"errors": {}
},
"unconnectedAccount": {
"state": "CLOSED"
}
}
3 changes: 3 additions & 0 deletions development/states/currency-localization.json
Original file line number Diff line number Diff line change
Expand Up @@ -472,5 +472,8 @@
],
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
"errors": {}
},
"unconnectedAccount": {
"state": "CLOSED"
}
}
5 changes: 4 additions & 1 deletion development/states/tx-list-items.json
Original file line number Diff line number Diff line change
Expand Up @@ -1403,5 +1403,8 @@
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
"errors": {}
},
"confirmTransaction": {}
"confirmTransaction": {},
"unconnectedAccount": {
"state": "CLOSED"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"@metamask/eth-token-tracker": "^2.0.0",
"@metamask/etherscan-link": "^1.1.0",
"@metamask/inpage-provider": "^5.0.0",
"@reduxjs/toolkit": "^1.3.2",
"@sentry/browser": "^5.11.1",
"@sentry/integrations": "^5.11.1",
"@zxing/library": "^0.8.0",
Expand Down
5 changes: 3 additions & 2 deletions test/unit/ui/app/actions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ describe('Actions', function () {

const expectedActions = [
'SHOW_LOADING_INDICATION',
'SELECTED_ADDRESS_CHANGED',
'UPDATE_METAMASK_STATE',
'HIDE_LOADING_INDICATION',
'SHOW_ACCOUNTS_PAGE',
Expand Down Expand Up @@ -979,7 +980,7 @@ describe('Actions', function () {
it('#showAccountDetail', async function () {
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
.callsArgWith(1, null)
const store = mockStore()
const store = mockStore({ metamask: { selectedAddress: '0x123' } })

await store.dispatch(actions.showAccountDetail())
assert(setSelectedAddressSpy.calledOnce)
Expand All @@ -988,7 +989,7 @@ describe('Actions', function () {
it('displays warning if setSelectedAddress throws', async function () {
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
.callsArgWith(1, new Error('error'))
const store = mockStore()
const store = mockStore({ metamask: { selectedAddress: '0x123' } })
const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
{ type: 'HIDE_LOADING_INDICATION' },
Expand Down
19 changes: 19 additions & 0 deletions ui/app/components/app/alerts/alerts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { useSelector } from 'react-redux'

import UnconnectedAccountAlert from './unconnected-account-alert'
import { alertIsOpen as unconnectedAccountAlertIsOpen } from '../../../ducks/alerts/unconnected-account'

const Alerts = () => {
const _unconnectedAccountAlertIsOpen = useSelector(unconnectedAccountAlertIsOpen)

if (_unconnectedAccountAlertIsOpen) {
return (
<UnconnectedAccountAlert />
)
}

return null
}

export default Alerts
1 change: 1 addition & 0 deletions ui/app/components/app/alerts/alerts.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import './unconnected-account-alert/unconnected-account-alert.scss';
1 change: 1 addition & 0 deletions ui/app/components/app/alerts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './alerts'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './unconnected-account-alert'
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useContext } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import {
ALERT_STATE,
connectAccount,
dismissAlert,
getAlertState,
} from '../../../../ducks/alerts/unconnected-account'
import { I18nContext } from '../../../../contexts/i18n'
import Popover from '../../../ui/popover'
import Button from '../../../ui/button'

const {
ERROR,
LOADING,
} = ALERT_STATE

const SwitchToUnconnectedAccountAlert = () => {
const t = useContext(I18nContext)
const dispatch = useDispatch()
const alertState = useSelector(getAlertState)

return (
<Popover
title={t('unconnectedAccountAlertTitle')}
subtitle={t('unconnectedAccountAlertDescription')}
onClose={() => dispatch(dismissAlert())}
footer={(
<>
{
alertState === ERROR
? (
<div className="unconnected-account-alert__error">
{ t('failureMessage') }
</div>
)
: null
}
<div className="unconnected-account-alert__footer-buttons">
<Button
disabled={alertState === LOADING}
onClick={() => dispatch(dismissAlert())}
type="secondary"
>
{ t('dismiss') }
</Button>
<Button
disabled={alertState === LOADING || alertState === ERROR}
onClick={() => dispatch(connectAccount())}
type="primary"
>
{ t('connect') }
</Button>
</div>
</>
)}
footerClassName="unconnected-account-alert__footer"
/>
)
}

export default SwitchToUnconnectedAccountAlert
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.unconnected-account-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;
}
}
2 changes: 2 additions & 0 deletions ui/app/components/app/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

@import 'add-token-button/index';

@import 'alerts/alerts.scss';

@import 'app-header/index';

@import 'asset-list/asset-list.scss';
Expand Down
5 changes: 5 additions & 0 deletions ui/app/ducks/alerts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import unconnectedAccount from './unconnected-account'

export default {
unconnectedAccount,
}
90 changes: 90 additions & 0 deletions ui/app/ducks/alerts/unconnected-account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { createSlice } from '@reduxjs/toolkit'
import { captureException } from '@sentry/browser'

import actionConstants from '../../store/actionConstants'
import { addPermittedAccount } from '../../store/actions'
import { getOriginOfCurrentTab, getSelectedAddress } from '../../selectors/selectors'

// Constants

export const ALERT_STATE = {
CLOSED: 'CLOSED',
ERROR: 'ERROR',
LOADING: 'LOADING',
OPEN: 'OPEN',
}

const name = 'unconnectedAccount'

const initialState = {
state: ALERT_STATE.CLOSED,
}

// Slice (reducer plus auto-generated actions and action creators)

const slice = createSlice({
name,
initialState,
reducers: {
connectAccountFailed: (state) => {
state.state = ALERT_STATE.ERROR
},
connectAccountRequested: (state) => {
state.state = ALERT_STATE.LOADING
},
connectAccountSucceeded: (state) => {
state.state = ALERT_STATE.CLOSED
},
dismissAlert: (state) => {
state.state = ALERT_STATE.CLOSED
},
switchedToUnconnectedAccount: (state) => {
state.state = ALERT_STATE.OPEN
},
},
extraReducers: {
Gudahtt marked this conversation as resolved.
Show resolved Hide resolved
[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

export const {
connectAccountFailed,
connectAccountRequested,
connectAccountSucceeded,
dismissAlert,
switchedToUnconnectedAccount,
} = actions

export const connectAccount = () => {
return async (dispatch, getState) => {
const state = getState()
const selectedAddress = getSelectedAddress(state)
const origin = getOriginOfCurrentTab(state)
try {
await dispatch(connectAccountRequested())
await dispatch(addPermittedAccount(origin, selectedAddress))
await dispatch(connectAccountSucceeded())
} catch (error) {
console.error(error)
captureException(error)
await dispatch(connectAccountFailed())
}
}
}
2 changes: 2 additions & 0 deletions ui/app/ducks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import sendReducer from './send/send.duck'
import appStateReducer from './app/app'
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'
import gasReducer from './gas/gas.duck'
import alerts from './alerts'

export default combineReducers({
...alerts,
activeTab: (s) => (s === undefined ? null : s),
metamask: metamaskReducer,
appState: appStateReducer,
Expand Down
2 changes: 2 additions & 0 deletions ui/app/pages/routes/routes.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Modal } from '../../components/app/modals'
import Alert from '../../components/ui/alert'
import AppHeader from '../../components/app/app-header'
import UnlockPage from '../unlock-page'
import Alerts from '../../components/app/alerts'

import {
ADD_TOKEN_ROUTE,
Expand Down Expand Up @@ -251,6 +252,7 @@ export default class Routes extends Component {
{ !isLoading && isLoadingNetwork && <LoadingNetwork /> }
{ this.renderRoutes() }
</div>
<Alerts />
</div>
)
}
Expand Down
1 change: 1 addition & 0 deletions ui/app/store/actionConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default {
NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE',
// remote state
UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE',
SELECTED_ADDRESS_CHANGED: 'SELECTED_ADDRESS_CHANGED',
FORGOT_PASSWORD: 'FORGOT_PASSWORD',
CLOSE_WELCOME_SCREEN: 'CLOSE_WELCOME_SCREEN',
// unlock screen
Expand Down
Loading