1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Connected indicator info popup (#8293)

* Add popover for informing user about the connected status indicator

* Ensure user only sees connected status info popover once

* Default connectedStatusPopoverHasBeenShown to true and set it to false in a migration

* Add unit test for migration 42

* Initialize AppStateController if it does not exist in migration 42

* Update connect indicator popup locale text

* Code cleanup for connected-indicator-info-popup

* Code cleanup for connected-indicator-info-popup
This commit is contained in:
Dan J Miller 2020-04-22 14:41:36 -02:30 committed by GitHub
parent f2f70342e2
commit 01985b2cff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 277 additions and 5 deletions

View File

@ -22,6 +22,9 @@
"disconnectAccountConfirmationDescription": {
"message": "Are you sure you want to disconnect? You may lose site functionality."
},
"dismiss": {
"message": "Dismiss"
},
"migrateSai": {
"message": "A message from Maker: The new Multi-Collateral Dai token has been released. Your old tokens are now called Sai. Please upgrade your Sai tokens to the new Dai."
},
@ -867,6 +870,12 @@
"message": {
"message": "Message"
},
"metaMaskConnectStatusParagraphOne": {
"message": "This is the new MetaMask Connect status indicator. From here you can easily see and manage sites youve connected to with your MetaMask wallet."
},
"metaMaskConnectStatusParagraphTwo": {
"message": "Click the Connect status to see your connected sites and their permissions."
},
"metamaskDescription": {
"message": "Connecting you to Ethereum and the Decentralized Web."
},
@ -1627,6 +1636,9 @@
"welcome": {
"message": "Welcome to MetaMask"
},
"whatsThis": {
"message": "What's this?"
},
"writePhrase": {
"message": "Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations."
},

View File

@ -22,6 +22,7 @@ class AppStateController extends EventEmitter {
this.store = new ObservableStore(Object.assign({
timeoutMinutes: 0,
mkrMigrationReminderTimestamp: null,
connectedStatusPopoverHasBeenShown: true,
}, initState))
this.timer = null
@ -72,6 +73,15 @@ class AppStateController extends EventEmitter {
})
}
/**
* Record that the user has seen the connected status info popover
*/
setConnectedStatusPopoverHasBeenShown () {
this.store.updateState({
connectedStatusPopoverHasBeenShown: true,
})
}
/**
* Sets the last active time to the current time
* @returns {void}

View File

@ -511,6 +511,7 @@ export default class MetamaskController extends EventEmitter {
// AppStateController
setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController),
setMkrMigrationReminderTimestamp: nodeify(this.appStateController.setMkrMigrationReminderTimestamp, this.appStateController),
setConnectedStatusPopoverHasBeenShown: nodeify(this.appStateController.setConnectedStatusPopoverHasBeenShown, this.appStateController),
// EnsController
tryReverseResolveAddress: nodeify(this.ensController.reverseResolveAddress, this.ensController),

View File

@ -0,0 +1,27 @@
const version = 42
import { cloneDeep } from 'lodash'
/**
* PreferencesController.autoLogoutTimeLimit -> autoLockTimeLimit
*/
export default {
version,
migrate: async function (originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
versionedData.data = transformState(state)
return versionedData
},
}
function transformState (state) {
if (state.AppStateController) {
state.AppStateController.connectedStatusPopoverHasBeenShown = false
} else {
state.AppStateController = {
connectedStatusPopoverHasBeenShown: false,
}
}
return state
}

View File

@ -52,6 +52,7 @@ const migrations = [
require('./039').default,
require('./040').default,
require('./041').default,
require('./042').default,
]
export default migrations

View File

@ -0,0 +1,70 @@
import assert from 'assert'
import migration42 from '../../../app/scripts/migrations/042'
describe('migration #42', function () {
it('should update the version metadata', function (done) {
const oldStorage = {
'meta': {
'version': 41,
},
'data': {},
}
migration42.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.meta, {
'version': 42,
})
done()
})
.catch(done)
})
it('should set connectedStatusPopoverHasBeenShown to false', function (done) {
const oldStorage = {
meta: {},
data: {
AppStateController: {
connectedStatusPopoverHasBeenShown: true,
bar: 'baz',
},
foo: 'bar',
},
}
migration42.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.data, {
AppStateController: {
connectedStatusPopoverHasBeenShown: false,
bar: 'baz',
},
foo: 'bar',
})
done()
})
.catch(done)
})
it('should initialize AppStateController if it does not exist', function (done) {
const oldStorage = {
meta: {},
data: {
foo: 'bar',
},
}
migration42.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.data, {
foo: 'bar',
AppStateController: {
connectedStatusPopoverHasBeenShown: false,
},
})
done()
})
.catch(done)
})
})

View File

@ -25,6 +25,9 @@
display: flex;
padding: 24px;
flex-direction: column;
background: white;
position: relative;
border-radius: 10px;
&__title {
display: flex;
@ -107,4 +110,13 @@
margin: 0 auto;
}
}
&-arrow {
width: 22px;
height: 22px;
background: white;
position: absolute;
transform: rotate(45deg);
box-shadow: 0px 4px 30px rgba(0, 0, 0, 0.25);
}
}

View File

@ -4,12 +4,27 @@ import PropTypes from 'prop-types'
import classnames from 'classnames'
import { I18nContext } from '../../../contexts/i18n'
const Popover = ({ title, subtitle, children, footer, footerClassName, onBack, onClose }) => {
const Popover = ({
title,
subtitle = '',
children,
footer,
footerClassName,
onBack,
onClose,
className,
showArrow,
CustomBackground,
}) => {
const t = useContext(I18nContext)
return (
<div className="popover-container">
<div className="popover-bg" onClick={onClose} />
<section className="popover-wrap">
{ CustomBackground
? <CustomBackground onClose={onClose} />
: <div className="popover-bg" onClick={onClose} />
}
<section className={classnames('popover-wrap', className)}>
{ showArrow ? <div className="popover-arrow" /> : null}
<header className="popover-header">
<div className="popover-header__title">
<h2 title={title}>
@ -32,7 +47,7 @@ const Popover = ({ title, subtitle, children, footer, footerClassName, onBack, o
onClick={onClose}
/>
</div>
<p className="popover-header__subtitle">{subtitle}</p>
{ subtitle ? <p className="popover-header__subtitle">{subtitle}</p> : null }
</header>
{
children
@ -59,12 +74,15 @@ const Popover = ({ title, subtitle, children, footer, footerClassName, onBack, o
Popover.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string.isRequired,
subtitle: PropTypes.string,
children: PropTypes.node,
footer: PropTypes.node,
footerClassName: PropTypes.string,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
CustomBackground: PropTypes.func,
className: PropTypes.string,
showArrow: PropTypes.bool,
}
export default class PopoverPortal extends PureComponent {

View File

@ -11,6 +11,8 @@ import WalletView from '../../components/app/wallet-view'
import TransactionList from '../../components/app/transaction-list'
import TransactionViewBalance from '../../components/app/transaction-view-balance'
import MenuBar from '../../components/app/menu-bar'
import Popover from '../../components/ui/popover'
import Button from '../../components/ui/button'
import ConnectedSites from '../connected-sites'
import { Tabs, Tab } from '../../components/ui/tabs'
@ -51,6 +53,8 @@ export default class Home extends PureComponent {
hasDaiV1Token: PropTypes.bool,
firstPermissionsRequestId: PropTypes.string,
totalUnapprovedCount: PropTypes.number.isRequired,
setConnectedStatusPopoverHasBeenShown: PropTypes.func,
connectedStatusPopoverHasBeenShown: PropTypes.bool,
}
UNSAFE_componentWillMount () {
@ -172,11 +176,48 @@ export default class Home extends PureComponent {
</MultipleNotifications>
)
}
renderPopover = () => {
const { setConnectedStatusPopoverHasBeenShown } = this.props
const { t } = this.context
return (
<Popover
title={ t('whatsThis') }
onClose={setConnectedStatusPopoverHasBeenShown}
className="home__connected-status-popover"
showArrow
CustomBackground={({ onClose }) => {
return (
<div
className="home__connected-status-popover-bg-container"
onClick={onClose}
>
<div className="home__connected-status-popover-bg" />
</div>
)
}}
footer={(
<Button
type="primary"
onClick={setConnectedStatusPopoverHasBeenShown}
>
{ t('dismiss') }
</Button>
)}
>
<main className="home__connect-status-text">
<div>{ t('metaMaskConnectStatusParagraphOne') }</div>
<div>{ t('metaMaskConnectStatusParagraphTwo') }</div>
</main>
</Popover>
)
}
render () {
const {
forgottenPassword,
history,
connectedStatusPopoverHasBeenShown,
isPopup,
} = this.props
if (forgottenPassword) {
@ -191,6 +232,7 @@ export default class Home extends PureComponent {
<div className="main-container">
<Route path={CONNECTED_ROUTE} component={ConnectedSites} />
<div className="home__container">
{ isPopup && !connectedStatusPopoverHasBeenShown ? this.renderPopover() : null }
<Media
query="(min-width: 576px)"
>

View File

@ -16,6 +16,7 @@ import {
turnThreeBoxSyncingOn,
getThreeBoxLastUpdated,
setShowRestorePromptToFalse,
setConnectedStatusPopoverHasBeenShown,
} from '../../store/actions'
import { setThreeBoxLastUpdated } from '../../ducks/app/app'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
@ -33,6 +34,7 @@ const mapStateToProps = (state) => {
threeBoxSynced,
showRestorePrompt,
selectedAddress,
connectedStatusPopoverHasBeenShown,
} = metamask
const accountBalance = getCurrentEthBalance(state)
const { forgottenPassword, threeBoxLastUpdated } = appState
@ -61,6 +63,7 @@ const mapStateToProps = (state) => {
hasDaiV1Token: Boolean(getDaiV1Token(state)),
firstPermissionsRequestId,
totalUnapprovedCount,
connectedStatusPopoverHasBeenShown,
}
}
@ -79,6 +82,7 @@ const mapDispatchToProps = (dispatch) => ({
},
restoreFromThreeBox: (address) => dispatch(restoreFromThreeBox(address)),
setShowRestorePromptToFalse: () => dispatch(setShowRestorePromptToFalse()),
setConnectedStatusPopoverHasBeenShown: () => dispatch(setConnectedStatusPopoverHasBeenShown()),
})
export default compose(

View File

@ -36,4 +36,69 @@
&__tab {
flex-grow: 1;
}
&__connect-status-text {
display: flex;
flex-direction: column;
@extend %content-text;
padding-left: 24px;
padding-right: 24px;
div {
&:last-child {
margin-top: 26px;
}
}
}
&__connected-status-popover {
width: 329px;
height: 295px;
margin-top: -12px;
.popover-header {
padding-bottom: 4px;
}
.popover-content {
overflow-y: auto;
}
.popover-arrow {
top: -6px;
left: 24px;
}
.popover-footer {
justify-content: flex-end;
& :only-child {
margin: 0;
}
button {
height: 39px;
width: 133px;
border-radius: 39px;
padding: 0;
}
}
}
&__connected-status-popover-bg {
height: 34px;
width: 110px;
border-radius: 34px;
position: absolute;
top: 82px;
left: 12px;
opacity: 1;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.2);
background: none;
}
&__connected-status-popover-bg-container {
height: 100%;
width: 100%;
}
}

View File

@ -2208,6 +2208,16 @@ export function setMkrMigrationReminderTimestamp (timestamp) {
}
}
export function setConnectedStatusPopoverHasBeenShown () {
return () => {
background.setConnectedStatusPopoverHasBeenShown((err) => {
if (err) {
throw new Error(err.message)
}
})
}
}
export function loadingMethoDataStarted () {
return {
type: actionConstants.LOADING_METHOD_DATA_STARTED,