Merge pull request #6082 from whymarrh/migrate-to-new-ui

Migrate all users to the new UI
feature/default_network_editable
Whymarrh Whitby 6 years ago committed by GitHub
commit 697d5adfc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/_locales/en/messages.json
  2. 14
      app/scripts/controllers/preferences.js
  3. 1
      app/scripts/metamask-controller.js
  4. 29
      app/scripts/migrations/032.js
  5. 22
      app/scripts/ui.js
  6. 8
      test/unit/ui/app/reducers/metamask.spec.js
  7. 30
      ui/app/actions.js
  8. 4
      ui/app/app.js
  9. 2
      ui/app/components/index.scss
  10. 14
      ui/app/components/modals/modal.js
  11. 1
      ui/app/components/modals/welcome-beta/index.js
  12. 30
      ui/app/components/modals/welcome-beta/welcome-beta.component.js
  13. 4
      ui/app/components/modals/welcome-beta/welcome-beta.container.js
  14. 33
      ui/app/components/pages/settings/settings-tab/settings-tab.component.js
  15. 5
      ui/app/components/pages/settings/settings-tab/settings-tab.container.js
  16. 18
      ui/app/components/send/send.selectors.js
  17. 2
      ui/app/components/send/tests/send-selectors-test-data.js
  18. 1
      ui/app/components/ui-migration-annoucement/index.js
  19. 22
      ui/app/components/ui-migration-annoucement/index.scss
  20. 33
      ui/app/components/ui-migration-annoucement/ui-migration-annoucement.component.js
  21. 21
      ui/app/components/ui-migration-annoucement/ui-migration-announcement.container.js
  22. 6
      ui/app/reducers/metamask.js
  23. 12
      ui/app/root.js
  24. 65
      ui/app/select-app.js
  25. 26
      ui/app/selectors.js

@ -1437,6 +1437,9 @@
"typePassword": {
"message": "Type your MetaMask password"
},
"uiMigrationAnnouncement": {
"message": "Welcome to the new MetaMask UI. If you have feedback about the UI or feature requests, please reach out to our support team or on GitHub."
},
"uiWelcome": {
"message": "Welcome to the New UI (Beta)"
},

@ -33,10 +33,7 @@ class PreferencesController {
tokens: [],
suggestedTokens: {},
useBlockie: false,
featureFlags: {
betaUI: true,
skipAnnounceBetaUI: true,
},
featureFlags: {},
knownMethodData: {},
currentLocale: opts.initLangCode,
identities: {},
@ -47,6 +44,7 @@ class PreferencesController {
useNativeCurrencyAsPrimaryCurrency: true,
},
completedOnboarding: false,
completedUiMigration: true,
}, opts.initState)
this.diagnostics = opts.diagnostics
@ -552,6 +550,14 @@ class PreferencesController {
return Promise.resolve(true)
}
/**
* Sets the {@code completedUiMigration} state to {@code true}, indicating that the user has completed the UI switch.
*/
completeUiMigration () {
this.store.updateState({ completedUiMigration: true })
return Promise.resolve(true)
}
//
// PRIVATE METHODS
//

@ -426,6 +426,7 @@ module.exports = class MetamaskController extends EventEmitter {
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
setPreference: nodeify(preferencesController.setPreference, preferencesController),
completeUiMigration: nodeify(preferencesController.completeUiMigration, preferencesController),
completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController),
addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController),

@ -0,0 +1,29 @@
const version = 32
const clone = require('clone')
/**
* The purpose of this migration is to set the {@code completedUiMigration} flag based on the user's UI preferences
*/
module.exports = {
version,
migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
versionedData.data = transformState(state)
return versionedData
},
}
function transformState (state) {
const { PreferencesController } = state
if (PreferencesController) {
const { betaUI } = PreferencesController.featureFlags || {}
// Users who have been using the "beta" UI are considered to have completed the migration
// as they'll see no difference in this version
PreferencesController.completedUiMigration = betaUI
}
return state
}

@ -1,7 +1,5 @@
const injectCss = require('inject-css')
const OldMetaMaskUiCss = require('../../old-ui/css')
const NewMetaMaskUiCss = require('../../ui/css')
const {getShouldUseNewUi} = require('../../ui/app/selectors')
const startPopup = require('./popup-core')
const PortStream = require('extension-port-stream')
const { getEnvironmentType } = require('./lib/util')
@ -49,30 +47,14 @@ async function start () {
if (err) return displayCriticalError(err)
const state = store.getState()
const { metamask: { completedOnboarding, featureFlags } = {} } = state
const { metamask: { completedOnboarding } = {} } = state
if (!completedOnboarding && windowType !== ENVIRONMENT_TYPE_FULLSCREEN) {
global.platform.openExtensionInBrowser()
return
}
let betaUIState = Boolean(featureFlags && featureFlags.betaUI)
const useBetaCss = getShouldUseNewUi(state)
let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
let deleteInjectedCss = injectCss(css)
let newBetaUIState
store.subscribe(() => {
const state = store.getState()
newBetaUIState = state.metamask.featureFlags.betaUI
if (newBetaUIState !== betaUIState) {
deleteInjectedCss()
betaUIState = newBetaUIState
css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
deleteInjectedCss = injectCss(css)
}
})
injectCss(NewMetaMaskUiCss())
})

@ -502,17 +502,15 @@ describe('MetaMask Reducers', () => {
assert.equal(state.useBlockie, true)
})
it('updates feature flag', () => {
it('updates an arbitrary feature flag', () => {
const state = reduceMetamask({}, {
type: actions.UPDATE_FEATURE_FLAGS,
value: {
betaUI: true,
skipAnnounceBetaUI: true,
foo: true,
},
})
assert.equal(state.featureFlags.betaUI, true)
assert.equal(state.featureFlags.skipAnnounceBetaUI, true)
assert.equal(state.featureFlags.foo, true)
})
it('updates network endpoint type', () => {

@ -317,6 +317,11 @@ var actions = {
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
// Migration of users to new UI
setCompletedUiMigration,
completeUiMigration,
COMPLETE_UI_MIGRATION: 'COMPLETE_UI_MIGRATION',
// Onboarding
setCompletedOnboarding,
completeOnboarding,
@ -2474,6 +2479,31 @@ function completeOnboarding () {
}
}
function setCompletedUiMigration () {
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.completeUiMigration(err => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completeUiMigration())
resolve()
})
})
}
}
function completeUiMigration () {
return {
type: actions.COMPLETE_UI_MIGRATION,
}
}
function setNetworkNonce (networkNonce) {
return {
type: actions.SET_NETWORK_NONCE,

@ -22,6 +22,7 @@ import Settings from './components/pages/settings'
import Authenticated from './higher-order-components/authenticated'
import Initialized from './higher-order-components/initialized'
import Lock from './components/pages/lock'
import UiMigrationAnnouncement from './components/ui-migration-annoucement'
const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token')
@ -173,6 +174,7 @@ class App extends Component {
}
}}
>
<UiMigrationAnnouncement />
<Modal />
<Alert
visible={this.props.alertOpen}
@ -303,7 +305,6 @@ App.propTypes = {
unapprovedTypedMessagesCount: PropTypes.number,
welcomeScreenSeen: PropTypes.bool,
isPopup: PropTypes.bool,
betaUI: PropTypes.bool,
isMouseUser: PropTypes.bool,
setMouseUserState: PropTypes.func,
t: PropTypes.func,
@ -375,7 +376,6 @@ function mapStateToProps (state) {
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
betaUI: state.metamask.featureFlags.betaUI,
isRevealingSeedWords: state.metamask.isRevealingSeedWords,
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,

@ -77,3 +77,5 @@
@import './gas-customization/index';
@import './gas-customization/gas-price-button-group/index';
@import './ui-migration-annoucement/index';

@ -25,7 +25,6 @@ import ConfirmRemoveAccount from './confirm-remove-account'
import ConfirmResetAccount from './confirm-reset-account'
import TransactionConfirmed from './transaction-confirmed'
import CancelTransaction from './cancel-transaction'
import WelcomeBeta from './welcome-beta'
import RejectTransactions from './reject-transactions'
import ClearApprovedOrigins from './clear-approved-origins'
import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container'
@ -201,19 +200,6 @@ const MODALS = {
},
},
BETA_UI_NOTIFICATION_MODAL: {
contents: h(WelcomeBeta),
mobileModalStyle: {
...modalContainerMobileStyle,
},
laptopModalStyle: {
...modalContainerLaptopStyle,
},
contentStyle: {
borderRadius: '8px',
},
},
CLEAR_APPROVED_ORIGINS: {
contents: h(ClearApprovedOrigins),
mobileModalStyle: {

@ -1 +0,0 @@
export { default } from './welcome-beta.container'

@ -1,30 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Modal, { ModalContent } from '../../modal'
const TransactionConfirmed = (props, context) => {
const { t } = context
const { hideModal } = props
return (
<Modal
onSubmit={() => hideModal()}
submitText={t('ok')}
>
<ModalContent
title={t('uiWelcome')}
description={t('uiWelcomeMessage')}
/>
</Modal>
)
}
TransactionConfirmed.contextTypes = {
t: PropTypes.func,
}
TransactionConfirmed.propTypes = {
hideModal: PropTypes.func,
}
export default TransactionConfirmed

@ -1,4 +0,0 @@
import WelcomeBeta from './welcome-beta.component'
import withModalProps from '../../../higher-order-components/with-modal-props'
export default withModalProps(WelcomeBeta)

@ -46,12 +46,10 @@ export default class SettingsTab extends PureComponent {
delRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
showClearApprovalModal: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
isMascara: PropTypes.bool,
updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string,
useBlockie: PropTypes.bool,
@ -338,34 +336,6 @@ export default class SettingsTab extends PureComponent {
)
}
renderOldUI () {
const { t } = this.context
const { setFeatureFlagToBeta } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('useOldUI') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="secondary"
large
className="settings-tab__button--orange"
onClick={event => {
event.preventDefault()
setFeatureFlagToBeta()
}}
>
{ t('useOldUI') }
</Button>
</div>
</div>
</div>
)
}
renderResetAccount () {
const { t } = this.context
const { showResetAccountConfirmationModal } = this.props
@ -523,7 +493,7 @@ export default class SettingsTab extends PureComponent {
}
render () {
const { warning, isMascara } = this.props
const { warning } = this.props
return (
<div className="settings-page__content">
@ -534,7 +504,6 @@ export default class SettingsTab extends PureComponent {
{ this.renderNewRpcUrl() }
{ this.renderStateLogs() }
{ this.renderSeedWords() }
{ !isMascara && this.renderOldUI() }
{ this.renderResetAccount() }
{ this.renderClearApproval() }
{ this.renderPrivacyOptIn() }

@ -27,14 +27,12 @@ const mapStateToProps = state => {
privacyMode,
} = {},
provider = {},
isMascara,
currentLocale,
} = metamask
const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
return {
warning,
isMascara,
currentLocale,
currentCurrency,
conversionDate,
@ -55,9 +53,6 @@ const mapDispatchToProps = dispatch => {
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
setUseBlockie: value => dispatch(setUseBlockie(value)),
updateCurrentLocale: key => dispatch(updateCurrentLocale(key)),
setFeatureFlagToBeta: () => {
return dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
},
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),

@ -16,7 +16,6 @@ import {
const selectors = {
accountsWithSendEtherInfoSelector,
// autoAddToBetaUI,
getAddressBook,
getAmountConversionRate,
getBlockGasLimit,
@ -72,23 +71,6 @@ function accountsWithSendEtherInfoSelector (state) {
return accountsWithSendEtherInfo
}
// function autoAddToBetaUI (state) {
// const autoAddTransactionThreshold = 12
// const autoAddAccountsThreshold = 2
// const autoAddTokensThreshold = 1
// const numberOfTransactions = state.metamask.selectedAddressTxList.length
// const numberOfAccounts = Object.keys(getMetaMaskAccounts(state)).length
// const numberOfTokensAdded = state.metamask.tokens.length
// const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
// (numberOfAccounts > autoAddAccountsThreshold) &&
// (numberOfTokensAdded > autoAddTokensThreshold)
// const userIsNotInBeta = !state.metamask.featureFlags.betaUI
// return userIsNotInBeta && userPassesThreshold
// }
function getAddressBook (state) {
return state.metamask.addressBook
}

@ -2,7 +2,7 @@ module.exports = {
'metamask': {
'isInitialized': true,
'isUnlocked': true,
'featureFlags': {'betaUI': true, 'sendHexData': true},
'featureFlags': {'sendHexData': true},
'rpcTarget': 'https://rawtestrpc.metamask.io/',
'identities': {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {

@ -0,0 +1 @@
export {default} from './ui-migration-announcement.container'

@ -0,0 +1,22 @@
.ui-migration-announcement {
position: absolute;
z-index: 9999;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
background: $white;
p {
box-sizing: border-box;
padding: 1em;
font-size: 12pt;
}
p:last-of-type {
cursor: pointer;
text-decoration: underline;
font-weight: bold;
}
}

@ -0,0 +1,33 @@
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
export default class UiMigrationAnnouncement extends PureComponent {
static contextTypes = {
t: PropTypes.func.isRequired,
}
static defaultProps = {
shouldShowAnnouncement: true,
};
static propTypes = {
onClose: PropTypes.func.isRequired,
shouldShowAnnouncement: PropTypes.bool,
}
render () {
const { t } = this.context
const { onClose, shouldShowAnnouncement } = this.props
if (!shouldShowAnnouncement) {
return null
}
return (
<div className="ui-migration-announcement">
<p>{t('uiMigrationAnnouncement')}</p>
<p onClick={onClose}>{t('close')}</p>
</div>
)
}
}

@ -0,0 +1,21 @@
import { connect } from 'react-redux'
import UiMigrationAnnouncement from './ui-migration-annoucement.component'
import { setCompletedUiMigration } from '../../actions'
const mapStateToProps = (state) => {
const shouldShowAnnouncement = !state.metamask.completedUiMigration
return {
shouldShowAnnouncement,
}
}
const mapDispatchToProps = dispatch => {
return {
onClose () {
dispatch(setCompletedUiMigration())
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UiMigrationAnnouncement)

@ -385,6 +385,12 @@ function reduceMetamask (state, action) {
})
}
case actions.COMPLETE_UI_MIGRATION: {
return extend(metamaskState, {
completedUiMigration: true,
})
}
default:
return metamaskState

@ -2,7 +2,9 @@ const { Component } = require('react')
const PropTypes = require('prop-types')
const { Provider } = require('react-redux')
const h = require('react-hyperscript')
const SelectedApp = require('./select-app')
const { HashRouter } = require('react-router-dom')
const App = require('./app')
const I18nProvider = require('./i18n-provider')
class Root extends Component {
render () {
@ -10,7 +12,13 @@ class Root extends Component {
return (
h(Provider, { store }, [
h(SelectedApp),
h(HashRouter, {
hashType: 'noslash',
}, [
h(I18nProvider, [
h(App),
]),
]),
])
)
}

@ -1,65 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const { HashRouter } = require('react-router-dom')
const App = require('./app')
const OldApp = require('../../old-ui/app/app')
const { getShouldUseNewUi } = require('./selectors')
const { setFeatureFlag } = require('./actions')
const I18nProvider = require('./i18n-provider')
function mapStateToProps (state) {
return {
isMascara: state.metamask.isMascara,
shouldUseNewUi: getShouldUseNewUi(state),
}
}
function mapDispatchToProps (dispatch) {
return {
setFeatureFlagWithModal: () => {
return dispatch(setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
},
setFeatureFlagWithoutModal: () => {
return dispatch(setFeatureFlag('betaUI', true))
},
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(SelectedApp)
inherits(SelectedApp, Component)
function SelectedApp () {
Component.call(this)
}
SelectedApp.prototype.componentWillReceiveProps = function (nextProps) {
// Code commented out until we begin auto adding users to NewUI
const {
// isUnlocked,
// setFeatureFlagWithModal,
setFeatureFlagWithoutModal,
isMascara,
// firstTime,
} = this.props
// if (isMascara || firstTime) {
if (isMascara) {
setFeatureFlagWithoutModal()
}
// } else if (!isUnlocked && nextProps.isUnlocked && (nextProps.autoAdd)) {
// setFeatureFlagWithModal()
// }
}
SelectedApp.prototype.render = function () {
const { shouldUseNewUi } = this.props
const newUi = h(HashRouter, {
hashType: 'noslash',
}, [
h(I18nProvider, [
h(App),
]),
])
return shouldUseNewUi ? newUi : h(OldApp)
}

@ -28,8 +28,6 @@ const selectors = {
getSendAmount,
getSelectedTokenToFiatRate,
getSelectedTokenContract,
autoAddToBetaUI,
getShouldUseNewUi,
getSendMaxModeState,
getCurrentViewContext,
getTotalUnapprovedCount,
@ -212,30 +210,6 @@ function getSelectedTokenContract (state) {
: null
}
function autoAddToBetaUI (state) {
const autoAddTransactionThreshold = 12
const autoAddAccountsThreshold = 2
const autoAddTokensThreshold = 1
const numberOfTransactions = state.metamask.selectedAddressTxList.length
const numberOfAccounts = Object.keys(getMetaMaskAccounts(state)).length
const numberOfTokensAdded = state.metamask.tokens.length
const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
(numberOfAccounts > autoAddAccountsThreshold) &&
(numberOfTokensAdded > autoAddTokensThreshold)
const userIsNotInBeta = !state.metamask.featureFlags.betaUI
return userIsNotInBeta && userPassesThreshold
}
function getShouldUseNewUi (state) {
const isAlreadyUsingBetaUi = state.metamask.featureFlags.betaUI
const isMascara = state.metamask.isMascara
const isFreshInstall = Object.keys(state.metamask.identities).length === 0
return isAlreadyUsingBetaUi || isMascara || isFreshInstall
}
function getCurrentViewContext (state) {
const { currentView = {} } = state.appState
return currentView.context

Loading…
Cancel
Save