New settings page rebased (#6333)

* New setting tab

* Add InfoTab

* Add Advanced tab

* Add Security Tab

* Finish mobile view

* Make new setting page responsive

* Fix linter

* Fix y scrolling

* Update link in network dropdown

* Fix e2e tests

* Remove duplicate translation key

* Resolve merge conflict

* Only change settings header in popup view.

* Place mobile-sync button in advanced-tab of settings
feature/default_network_editable
Dan J Miller 6 years ago committed by GitHub
parent 4ff9126ff2
commit 961ad267df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      app/_locales/en/messages.json
  2. 18
      app/images/caret-left-black.svg
  3. 14
      package-lock.json
  4. 16
      test/e2e/beta/metamask-beta-ui.spec.js
  5. 4
      ui/app/components/app/dropdowns/network-dropdown.js
  6. 30
      ui/app/components/app/tab-bar.js
  7. 68
      ui/app/css/itcss/components/tab-bar.scss
  8. 10
      ui/app/helpers/constants/routes.js
  9. 378
      ui/app/pages/settings/advanced-tab/advanced-tab.component.js
  10. 48
      ui/app/pages/settings/advanced-tab/advanced-tab.container.js
  11. 1
      ui/app/pages/settings/advanced-tab/index.js
  12. 85
      ui/app/pages/settings/index.scss
  13. 8
      ui/app/pages/settings/info-tab/info-tab.component.js
  14. 1
      ui/app/pages/settings/security-tab/index.js
  15. 195
      ui/app/pages/settings/security-tab/security-tab.component.js
  16. 42
      ui/app/pages/settings/security-tab/security-tab.container.js
  17. 486
      ui/app/pages/settings/settings-tab/settings-tab.component.js
  18. 30
      ui/app/pages/settings/settings-tab/settings-tab.container.js
  19. 127
      ui/app/pages/settings/settings.component.js

@ -44,6 +44,9 @@
"providerRequestInfo": { "providerRequestInfo": {
"message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with." "message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with."
}, },
"aboutUs": {
"message": "About Us"
},
"accept": { "accept": {
"message": "Accept" "message": "Accept"
}, },
@ -74,6 +77,12 @@
"address": { "address": {
"message": "Address" "message": "Address"
}, },
"advanced": {
"message": "Advanced"
},
"advancedSettingsDescription": {
"message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC."
},
"advancedOptions": { "advancedOptions": {
"message": "Advanced Options" "message": "Advanced Options"
}, },
@ -92,9 +101,6 @@
"addAcquiredTokens": { "addAcquiredTokens": {
"message": "Add the tokens you've acquired using MetaMask" "message": "Add the tokens you've acquired using MetaMask"
}, },
"advanced": {
"message": "Advanced"
},
"agreeTermsOfService": { "agreeTermsOfService": {
"message": "I agree to the Terms of Service" "message": "I agree to the Terms of Service"
}, },
@ -236,6 +242,9 @@
"chromeRequiredForHardwareWallets": { "chromeRequiredForHardwareWallets": {
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet." "message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
}, },
"company": {
"message": "Company"
},
"confirm": { "confirm": {
"message": "Confirm" "message": "Confirm"
}, },
@ -619,6 +628,12 @@
"gasPriceRequired": { "gasPriceRequired": {
"message": "Gas Price Required" "message": "Gas Price Required"
}, },
"general": {
"message": "General"
},
"generalSettingsDescription": {
"message": "Currency conversion, primary currency, language, blockies identicon"
},
"generatingTransaction": { "generatingTransaction": {
"message": "Generating transaction" "message": "Generating transaction"
}, },
@ -790,6 +805,9 @@
"ledgerAccountRestriction": { "ledgerAccountRestriction": {
"message": "You need to make use your last account before you can add a new one." "message": "You need to make use your last account before you can add a new one."
}, },
"legal": {
"message": "Legal"
},
"lessThanMax": { "lessThanMax": {
"message": "must be less than or equal to $1.", "message": "must be less than or equal to $1.",
"description": "helper for inputting hex as decimal input" "description": "helper for inputting hex as decimal input"
@ -1240,6 +1258,12 @@
"secretPhrase": { "secretPhrase": {
"message": "Enter your secret twelve word phrase here to restore your vault." "message": "Enter your secret twelve word phrase here to restore your vault."
}, },
"securityAndPrivacy": {
"message": "Security & Privacy"
},
"securitySettingsDescription": {
"message": "Privacy settings and wallet seed phrase"
},
"secondsShorthand": { "secondsShorthand": {
"message": "Sec" "message": "Sec"
}, },

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="9px" height="15px" viewBox="0 0 9 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>8439120D-5704-4273-B416-FEE134322584</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Approve---insufficient-amount" transform="translate(-75.000000, -69.000000)" stroke="#000000" stroke-width="2">
<g id="Group-7" transform="translate(53.000000, 51.000000)">
<g id="cancel" transform="translate(24.000000, 14.000000)">
<g id="Group">
<polyline id="Path-8" points="6.1263881 18.0633906 0 11.6306831 6.31493631 5"></polyline>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 913 B

14
package-lock.json generated

@ -9849,7 +9849,7 @@
"dependencies": { "dependencies": {
"babelify": { "babelify": {
"version": "7.3.0", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": { "requires": {
"babel-core": "^6.0.14", "babel-core": "^6.0.14",
@ -9901,7 +9901,7 @@
} }
}, },
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8",
@ -10189,7 +10189,7 @@
} }
}, },
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8",
@ -10484,7 +10484,7 @@
} }
}, },
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8",
@ -24076,7 +24076,7 @@
"dependencies": { "dependencies": {
"babelify": { "babelify": {
"version": "7.3.0", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": { "requires": {
"babel-core": "^6.0.14", "babel-core": "^6.0.14",
@ -24115,7 +24115,7 @@
}, },
"babelify": { "babelify": {
"version": "7.3.0", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": { "requires": {
"babel-core": "^6.0.14", "babel-core": "^6.0.14",
@ -26541,7 +26541,7 @@
"dependencies": { "dependencies": {
"babelify": { "babelify": {
"version": "7.3.0", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": { "requires": {
"babel-core": "^6.0.14", "babel-core": "^6.0.14",

@ -233,7 +233,11 @@ describe('MetaMask', function () {
await customRpcButton.click() await customRpcButton.click()
await delay(regularDelayMs) await delay(regularDelayMs)
const privacyToggle = await findElement(driver, By.css('.settings-page__content-row:nth-of-type(10) .settings-page__content-item-col > div')) const securityTab = await findElement(driver, By.xpath(`//div[contains(text(), 'Security & Privacy')]`))
await securityTab.click()
await delay(regularDelayMs)
const privacyToggle = await findElement(driver, By.css('.settings-page__content-row:nth-of-type(1) .settings-page__content-item-col > div'))
await privacyToggle.click() await privacyToggle.click()
await delay(largeDelayMs * 2) await delay(largeDelayMs * 2)
}) })
@ -472,15 +476,19 @@ describe('MetaMask', function () {
const settingsButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Settings')]`)) const settingsButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Settings')]`))
settingsButton.click() settingsButton.click()
await findElement(driver, By.css('.tab-bar')) // await findElement(driver, By.css('.tab-bar'))
const advancedTab = await findElement(driver, By.xpath(`//div[contains(text(), 'Advanced')]`))
await advancedTab.click()
await delay(regularDelayMs)
const showConversionToggle = await findElement(driver, By.css('.settings-page__content-row:nth-of-type(3) .settings-page__content-item-col > div')) const showConversionToggle = await findElement(driver, By.css('.settings-page__content-row:nth-of-type(7) .settings-page__content-item-col > div'))
await showConversionToggle.click() await showConversionToggle.click()
const advancedGasTitle = await findElement(driver, By.xpath(`//span[contains(text(), 'Advanced gas controls')]`)) const advancedGasTitle = await findElement(driver, By.xpath(`//span[contains(text(), 'Advanced gas controls')]`))
await driver.executeScript('arguments[0].scrollIntoView(true)', advancedGasTitle) await driver.executeScript('arguments[0].scrollIntoView(true)', advancedGasTitle)
const advancedGasToggle = await findElement(driver, By.css('.settings-page__content-row:nth-of-type(12) .settings-page__content-item-col > div')) const advancedGasToggle = await findElement(driver, By.css('.settings-page__content-row:nth-of-type(5) .settings-page__content-item-col > div'))
await advancedGasToggle.click() await advancedGasToggle.click()
windowHandles = await driver.getAllWindowHandles() windowHandles = await driver.getAllWindowHandles()
extension = windowHandles[0] extension = windowHandles[0]

@ -10,7 +10,7 @@ const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon') const NetworkDropdownIcon = require('./components/network-dropdown-icon')
const R = require('ramda') const R = require('ramda')
const { SETTINGS_ROUTE } = require('../../../helpers/constants/routes') const { ADVANCED_ROUTE } = require('../../../helpers/constants/routes')
// classes from nodes of the toggle element. // classes from nodes of the toggle element.
const notToggleElementClassnames = [ const notToggleElementClassnames = [
@ -233,7 +233,7 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem, DropdownMenuItem,
{ {
closeMenu: () => this.props.hideNetworkDropdown(), closeMenu: () => this.props.hideNetworkDropdown(),
onClick: () => this.props.history.push(SETTINGS_ROUTE), onClick: () => this.props.history.push(ADVANCED_ROUTE),
style: dropdownMenuItemStyle, style: dropdownMenuItemStyle,
}, },
[ [

@ -1,5 +1,4 @@
const { Component } = require('react') import React, { Component } from 'react'
const h = require('react-hyperscript')
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const classnames = require('classnames') const classnames = require('classnames')
@ -8,18 +7,23 @@ class TabBar extends Component {
const { tabs = [], onSelect, isActive } = this.props const { tabs = [], onSelect, isActive } = this.props
return ( return (
h('.tab-bar', {}, [ <div className="tab-bar">
tabs.map(({ key, content }) => { {tabs.map(({ key, content, description }) => (
return h('div', { <div
className: classnames('tab-bar__tab pointer', { key={key}
className={classnames('tab-bar__tab pointer', {
'tab-bar__tab--active': isActive(key, content), 'tab-bar__tab--active': isActive(key, content),
}), })}
onClick: () => onSelect(key), onClick={() => onSelect(key)}
key, >
}, content) <div className="tab-bar__tab__content">
}), <div className="tab-bar__tab__content__title">{content}</div>
h('div.tab-bar__tab.tab-bar__grow-tab'), <div className="tab-bar__tab__content__description">{description}</div>
]) </div>
<div className="tab-bar__tab__caret" />
</div>
))}
</div>
) )
} }
} }

@ -1,21 +1,73 @@
.tab-bar { .tab-bar {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: flex-end;
} }
.tab-bar__tab { .tab-bar__tab {
display: flex;
flex-flow: row nowrap;
align-items: flex-start;
min-width: 0; min-width: 0;
flex: 0 0 auto; flex: 0 0 auto;
padding: 15px 25px;
border-bottom: 1px solid $alto;
box-sizing: border-box; box-sizing: border-box;
font-size: 18px; font-size: 16px;
} padding: 16px 24px;
opacity: .5;
transition: opacity 200ms ease-in-out;
@media screen and (min-width: 576px) {
&:hover {
opacity: .4;
}
&:active {
opacity: .6;
}
}
@media screen and (max-width: 575px) {
font-size: 18px;
padding: 24px;
border-bottom: 1px solid $alto;
opacity: 1;
}
&__content {
flex: 1 1 auto;
width: 0;
&__description {
display: none;
@media screen and (max-width: 575px) {
display: block;
font-size: 14px;
font-weight: 300;
margin-top: 8px;
min-height: 14px;
}
}
}
&__caret {
display: none;
@media screen and (max-width: 575px) {
display: block;
background-image: url('/images/caret-right.svg');
width: 36px;
height: 36px;
opacity: .5;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
}
.tab-bar__tab--active { &--active {
border-color: $black; opacity: 1 !important;
}
} }
.tab-bar__grow-tab { .tab-bar__grow-tab {

@ -2,7 +2,12 @@ const DEFAULT_ROUTE = '/'
const UNLOCK_ROUTE = '/unlock' const UNLOCK_ROUTE = '/unlock'
const LOCK_ROUTE = '/lock' const LOCK_ROUTE = '/lock'
const SETTINGS_ROUTE = '/settings' const SETTINGS_ROUTE = '/settings'
const GENERAL_ROUTE = '/settings/general'
const INFO_ROUTE = '/settings/info' const INFO_ROUTE = '/settings/info'
const ADVANCED_ROUTE = '/settings/advanced'
const SECURITY_ROUTE = '/settings/security'
const COMPANY_ROUTE = '/settings/company'
const ABOUT_US_ROUTE = '/settings/about-us'
const REVEAL_SEED_ROUTE = '/seed' const REVEAL_SEED_ROUTE = '/seed'
const MOBILE_SYNC_ROUTE = '/mobile-sync' const MOBILE_SYNC_ROUTE = '/mobile-sync'
const CONFIRM_SEED_ROUTE = '/confirm-seed' const CONFIRM_SEED_ROUTE = '/confirm-seed'
@ -80,4 +85,9 @@ module.exports = {
CONFIRM_TOKEN_METHOD_PATH, CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH, SIGNATURE_REQUEST_PATH,
INITIALIZE_METAMETRICS_OPT_IN_ROUTE, INITIALIZE_METAMETRICS_OPT_IN_ROUTE,
ADVANCED_ROUTE,
SECURITY_ROUTE,
COMPANY_ROUTE,
GENERAL_ROUTE,
ABOUT_US_ROUTE,
} }

@ -0,0 +1,378 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import validUrl from 'valid-url'
import { exportAsFile } from '../../../helpers/utils/util'
import ToggleButton from 'react-toggle-button'
import TextField from '../../../components/ui/text-field'
import Button from '../../../components/ui/button'
import { MOBILE_SYNC_ROUTE } from '../../../helpers/constants/routes'
export default class AdvancedTab extends PureComponent {
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
static propTypes = {
setHexDataFeatureFlag: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
sendHexData: PropTypes.bool,
setAdvancedInlineGasFeatureFlag: PropTypes.func,
advancedInlineGas: PropTypes.bool,
showFiatInTestnets: PropTypes.bool,
setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired,
}
state = {
newRpc: '',
chainId: '',
showOptions: false,
ticker: '',
nickname: '',
}
renderNewRpcUrl () {
const { t } = this.context
const { newRpc, chainId, ticker, nickname } = this.state
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('newNetwork') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
id="new-rpc"
placeholder={t('rpcURL')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="chainid"
placeholder={t('optionalChainId')}
value={chainId}
onChange={e => this.setState({ chainId: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="ticker"
placeholder={t('optionalSymbol')}
value={ticker}
onChange={e => this.setState({ ticker: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="nickname"
placeholder={t('optionalNickname')}
value={nickname}
onChange={e => this.setState({ nickname: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<div className="flex-row flex-align-center space-between">
<span className="settings-tab__advanced-link"
onClick={e => {
e.preventDefault()
this.setState({ showOptions: !this.state.showOptions })
}}
>
{ t(this.state.showOptions ? 'hideAdvancedOptions' : 'showAdvancedOptions') }
</span>
<button
className="button btn-primary settings-tab__rpc-save-button"
onClick={e => {
e.preventDefault()
this.validateRpc(newRpc, chainId, ticker, nickname)
}}
>
{ t('save') }
</button>
</div>
</div>
</div>
</div>
)
}
validateRpc (newRpc, chainId, ticker = 'ETH', nickname) {
const { setRpcTarget, displayWarning } = this.props
if (validUrl.isWebUri(newRpc)) {
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Custom RPC',
name: 'Success',
},
customVariables: {
networkId: newRpc,
chainId,
},
})
if (!!chainId && Number.isNaN(parseInt(chainId))) {
return displayWarning(`${this.context.t('invalidInput')} chainId`)
}
setRpcTarget(newRpc, chainId, ticker, nickname)
} else {
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Custom RPC',
name: 'Error',
},
customVariables: {
networkId: newRpc,
chainId,
},
})
const appendedRpc = `http://${newRpc}`
if (validUrl.isWebUri(appendedRpc)) {
displayWarning(this.context.t('uriErrorMsg'))
} else {
displayWarning(this.context.t('invalidRPC'))
}
}
}
renderMobileSync () {
const { t } = this.context
const { history } = this.props
//
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('syncWithMobile') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="primary"
large
onClick={event => {
event.preventDefault()
history.push(MOBILE_SYNC_ROUTE)
}}
>
{ t('syncWithMobile') }
</Button>
</div>
</div>
</div>
)
}
renderStateLogs () {
const { t } = this.context
const { displayWarning } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('stateLogs') }</span>
<span className="settings-page__content-description">
{ t('stateLogsDescription') }
</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="primary"
large
onClick={() => {
window.logStateString((err, result) => {
if (err) {
displayWarning(t('stateLogError'))
} else {
exportAsFile('MetaMask State Logs.json', result)
}
})
}}
>
{ t('downloadStateLogs') }
</Button>
</div>
</div>
</div>
)
}
renderResetAccount () {
const { t } = this.context
const { showResetAccountConfirmationModal } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('resetAccount') }</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()
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Reset Account',
name: 'Reset Account',
},
})
showResetAccountConfirmationModal()
}}
>
{ t('resetAccount') }
</Button>
</div>
</div>
</div>
)
}
renderHexDataOptIn () {
const { t } = this.context
const { sendHexData, setHexDataFeatureFlag } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('showHexData') }</span>
<div className="settings-page__content-description">
{ t('showHexDataDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={sendHexData}
onToggle={value => setHexDataFeatureFlag(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderAdvancedGasInputInline () {
const { t } = this.context
const { advancedInlineGas, setAdvancedInlineGasFeatureFlag } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('showAdvancedGasInline') }</span>
<div className="settings-page__content-description">
{ t('showAdvancedGasInlineDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={advancedInlineGas}
onToggle={value => setAdvancedInlineGasFeatureFlag(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderShowConversionInTestnets () {
const { t } = this.context
const {
showFiatInTestnets,
setShowFiatConversionOnTestnetsPreference,
} = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('showFiatConversionInTestnets') }</span>
<div className="settings-page__content-description">
{ t('showFiatConversionInTestnetsDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={showFiatInTestnets}
onToggle={value => setShowFiatConversionOnTestnetsPreference(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderContent () {
const { warning } = this.props
return (
<div className="settings-page__body">
{ warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderStateLogs() }
{ this.renderMobileSync() }
{ this.renderNewRpcUrl() }
{ this.renderResetAccount() }
{ this.renderAdvancedGasInputInline() }
{ this.renderHexDataOptIn() }
{ this.renderShowConversionInTestnets() }
</div>
)
}
render () {
return this.renderContent()
}
}

@ -0,0 +1,48 @@
import AdvancedTab from './advanced-tab.component'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import {
updateAndSetCustomRpc,
displayWarning,
setFeatureFlag,
showModal,
setShowFiatConversionOnTestnetsPreference,
} from '../../../store/actions'
import {preferencesSelector} from '../../../selectors/selectors'
const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
const {
featureFlags: {
sendHexData,
advancedInlineGas,
} = {},
} = metamask
const { showFiatInTestnets } = preferencesSelector(state)
return {
warning,
sendHexData,
advancedInlineGas,
showFiatInTestnets,
}
}
const mapDispatchToProps = dispatch => {
return {
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
displayWarning: warning => dispatch(displayWarning(warning)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
setAdvancedInlineGasFeatureFlag: shouldShow => dispatch(setFeatureFlag('advancedInlineGas', shouldShow)),
setShowFiatConversionOnTestnetsPreference: value => {
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
},
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(AdvancedTab)

@ -0,0 +1 @@
export { default } from './advanced-tab.container'

@ -9,34 +9,79 @@
flex-flow: column nowrap; flex-flow: column nowrap;
&__header { &__header {
padding: 25px 25px 0; display: flex;
flex-flow: row nowrap;
padding: 12px 24px;
align-items: center;
border-bottom: 1px solid $alto;
flex: 0 0 auto;
&__title {
flex: 1 0 auto;
font-size: 24px;
}
}
&__back-button {
display: none;
@media screen and (max-width: 575px) {
display: block;
background-image: url('/images/caret-left-black.svg');
width: 18px;
height: 18px;
opacity: .5;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
margin-right: 16px;
cursor: pointer;
}
} }
&__close-button::after { &__close-button::after {
content: '\00D7'; content: '\00D7';
font-size: 40px; font-size: 40px;
color: $dusty-gray; color: $dusty-gray;
position: absolute;
top: 25px;
right: 30px;
cursor: pointer; cursor: pointer;
} }
&__content { &__content {
padding: 25px; display: flex;
flex-flow: row nowrap;
height: auto; height: auto;
overflow: auto; overflow: auto;
&__tabs {
display: flex;
flex-direction: column;
flex: 1 1 auto;
@media screen and (min-width: 576px) {
flex: 0 0 32%;
max-width: 210px;
border-right: 1px solid $alto;
}
}
&__modules {
overflow-y: auto;
flex: 1 1 auto;
@media screen and (max-width: 575px) {
display: none;
}
}
}
&__body {
padding: 12px 24px;
} }
&__content-row { &__content-row {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
padding: 10px 0 20px; padding: 10px 0 20px;
@media screen and (max-width: 575px) {
flex-direction: column;
padding: 10px 0;
}
} }
&__content-item { &__content-item {
@ -77,4 +122,22 @@
width: 100%; width: 100%;
} }
} }
&--selected {
.settings-page {
&__content {
&__tabs {
@media screen and (max-width: 575px) {
display: none;
}
}
&__modules {
@media screen and (max-width: 575px) {
display: block;
}
}
}
}
}
} }

@ -101,11 +101,11 @@ export default class InfoTab extends PureComponent {
) )
} }
render () { renderContent () {
const { t } = this.context const { t } = this.context
return ( return (
<div className="settings-page__content"> <div className="settings-page__body">
<div className="settings-page__content-row"> <div className="settings-page__content-row">
<div className="settings-page__content-item settings-page__content-item--without-height"> <div className="settings-page__content-item settings-page__content-item--without-height">
<div className="info-tab__logo-wrapper"> <div className="info-tab__logo-wrapper">
@ -133,4 +133,8 @@ export default class InfoTab extends PureComponent {
</div> </div>
) )
} }
render () {
return this.renderContent()
}
} }

@ -0,0 +1 @@
export { default } from './security-tab.container'

@ -0,0 +1,195 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { exportAsFile } from '../../../helpers/utils/util'
import ToggleButton from 'react-toggle-button'
import { REVEAL_SEED_ROUTE } from '../../../helpers/constants/routes'
import Button from '../../../components/ui/button'
export default class SecurityTab extends PureComponent {
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
static propTypes = {
setPrivacyMode: PropTypes.func,
privacyMode: PropTypes.bool,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
showClearApprovalModal: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
mobileSync: PropTypes.bool,
participateInMetaMetrics: PropTypes.bool,
setParticipateInMetaMetrics: PropTypes.func,
}
renderStateLogs () {
const { t } = this.context
const { displayWarning } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('stateLogs') }</span>
<span className="settings-page__content-description">
{ t('stateLogsDescription') }
</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="primary"
large
onClick={() => {
window.logStateString((err, result) => {
if (err) {
displayWarning(t('stateLogError'))
} else {
exportAsFile('MetaMask State Logs.json', result)
}
})
}}
>
{ t('downloadStateLogs') }
</Button>
</div>
</div>
</div>
)
}
renderClearApproval () {
const { t } = this.context
const { showClearApprovalModal } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('approvalData') }</span>
<span className="settings-page__content-description">
{ t('approvalDataDescription') }
</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()
showClearApprovalModal()
}}
>
{ t('clearApprovalData') }
</Button>
</div>
</div>
</div>
)
}
renderSeedWords () {
const { t } = this.context
const { history } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('revealSeedWords') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="secondary"
large
onClick={event => {
event.preventDefault()
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Reveal Seed Phrase',
name: 'Reveal Seed Phrase',
},
})
history.push(REVEAL_SEED_ROUTE)
}}
>
{ t('revealSeedWords') }
</Button>
</div>
</div>
</div>
)
}
renderPrivacyOptIn () {
const { t } = this.context
const { privacyMode, setPrivacyMode } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('privacyMode') }</span>
<div className="settings-page__content-description">
{ t('privacyModeDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={privacyMode}
onToggle={value => setPrivacyMode(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderMetaMetricsOptIn () {
const { t } = this.context
const { participateInMetaMetrics, setParticipateInMetaMetrics } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('participateInMetaMetrics') }</span>
<div className="settings-page__content-description">
<span>{ t('participateInMetaMetricsDescription') }</span>
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={participateInMetaMetrics}
onToggle={value => setParticipateInMetaMetrics(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderContent () {
const { warning } = this.props
return (
<div className="settings-page__body">
{ warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderPrivacyOptIn() }
{ this.renderClearApproval() }
{ this.renderSeedWords() }
{ this.renderMetaMetricsOptIn() }
</div>
)
}
render () {
return this.renderContent()
}
}

@ -0,0 +1,42 @@
import SecurityTab from './security-tab.component'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import {
displayWarning,
revealSeedConfirmation,
setFeatureFlag,
showModal,
setParticipateInMetaMetrics,
} from '../../../store/actions'
const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
const {
featureFlags: {
privacyMode,
} = {},
participateInMetaMetrics,
} = metamask
return {
warning,
privacyMode,
participateInMetaMetrics,
}
}
const mapDispatchToProps = dispatch => {
return {
displayWarning: warning => dispatch(displayWarning(warning)),
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })),
setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)),
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SecurityTab)

@ -1,14 +1,9 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import infuraCurrencies from '../../../helpers/constants/infura-conversion.json' import infuraCurrencies from '../../../helpers/constants/infura-conversion.json'
import validUrl from 'valid-url'
import { exportAsFile } from '../../../helpers/utils/util'
import SimpleDropdown from '../../../components/app/dropdowns/simple-dropdown' import SimpleDropdown from '../../../components/app/dropdowns/simple-dropdown'
import ToggleButton from 'react-toggle-button' import ToggleButton from 'react-toggle-button'
import { REVEAL_SEED_ROUTE, MOBILE_SYNC_ROUTE } from '../../../helpers/constants/routes'
import locales from '../../../../../app/_locales/index.json' import locales from '../../../../../app/_locales/index.json'
import TextField from '../../../components/ui/text-field'
import Button from '../../../components/ui/button'
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase()) return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
@ -37,44 +32,19 @@ export default class SettingsTab extends PureComponent {
} }
static propTypes = { static propTypes = {
metamask: PropTypes.object,
setUseBlockie: PropTypes.func, setUseBlockie: PropTypes.func,
setHexDataFeatureFlag: PropTypes.func,
setPrivacyMode: PropTypes.func,
privacyMode: PropTypes.bool,
setCurrentCurrency: PropTypes.func, setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
delRpcTarget: PropTypes.func,
displayWarning: PropTypes.func, displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
showClearApprovalModal: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string, warning: PropTypes.string,
history: PropTypes.object, history: PropTypes.object,
updateCurrentLocale: PropTypes.func, updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string, currentLocale: PropTypes.string,
useBlockie: PropTypes.bool, useBlockie: PropTypes.bool,
sendHexData: PropTypes.bool,
currentCurrency: PropTypes.string, currentCurrency: PropTypes.string,
conversionDate: PropTypes.number, conversionDate: PropTypes.number,
nativeCurrency: PropTypes.string, nativeCurrency: PropTypes.string,
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func, setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func,
setAdvancedInlineGasFeatureFlag: PropTypes.func,
advancedInlineGas: PropTypes.bool,
showFiatInTestnets: PropTypes.bool,
setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired,
participateInMetaMetrics: PropTypes.bool,
setParticipateInMetaMetrics: PropTypes.func,
}
state = {
newRpc: '',
chainId: '',
showOptions: false,
ticker: '',
nickname: '',
} }
renderCurrentConversion () { renderCurrentConversion () {
@ -133,310 +103,6 @@ export default class SettingsTab extends PureComponent {
) )
} }
renderNewRpcUrl () {
const { t } = this.context
const { newRpc, chainId, ticker, nickname } = this.state
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('newNetwork') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
id="new-rpc"
placeholder={t('rpcURL')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="chainid"
placeholder={t('optionalChainId')}
value={chainId}
onChange={e => this.setState({ chainId: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="ticker"
placeholder={t('optionalSymbol')}
value={ticker}
onChange={e => this.setState({ ticker: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="nickname"
placeholder={t('optionalNickname')}
value={nickname}
onChange={e => this.setState({ nickname: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<div className="flex-row flex-align-center space-between">
<span className="settings-tab__advanced-link"
onClick={e => {
e.preventDefault()
this.setState({ showOptions: !this.state.showOptions })
}}
>
{ t(this.state.showOptions ? 'hideAdvancedOptions' : 'showAdvancedOptions') }
</span>
<button
className="button btn-primary settings-tab__rpc-save-button"
onClick={e => {
e.preventDefault()
this.validateRpc(newRpc, chainId, ticker, nickname)
}}
>
{ t('save') }
</button>
</div>
</div>
</div>
</div>
)
}
validateRpc (newRpc, chainId, ticker = 'ETH', nickname) {
const { setRpcTarget, displayWarning } = this.props
if (validUrl.isWebUri(newRpc)) {
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Custom RPC',
name: 'Success',
},
customVariables: {
networkId: newRpc,
chainId,
},
})
if (!!chainId && Number.isNaN(parseInt(chainId))) {
return displayWarning(`${this.context.t('invalidInput')} chainId`)
}
setRpcTarget(newRpc, chainId, ticker, nickname)
} else {
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Custom RPC',
name: 'Error',
},
customVariables: {
networkId: newRpc,
chainId,
},
})
const appendedRpc = `http://${newRpc}`
if (validUrl.isWebUri(appendedRpc)) {
displayWarning(this.context.t('uriErrorMsg'))
} else {
displayWarning(this.context.t('invalidRPC'))
}
}
}
renderStateLogs () {
const { t } = this.context
const { displayWarning } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('stateLogs') }</span>
<span className="settings-page__content-description">
{ t('stateLogsDescription') }
</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="primary"
large
onClick={() => {
window.logStateString((err, result) => {
if (err) {
displayWarning(t('stateLogError'))
} else {
exportAsFile('MetaMask State Logs.json', result)
}
})
}}
>
{ t('downloadStateLogs') }
</Button>
</div>
</div>
</div>
)
}
renderClearApproval () {
const { t } = this.context
const { showClearApprovalModal } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('approvalData') }</span>
<span className="settings-page__content-description">
{ t('approvalDataDescription') }
</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()
showClearApprovalModal()
}}
>
{ t('clearApprovalData') }
</Button>
</div>
</div>
</div>
)
}
renderSeedWords () {
const { t } = this.context
const { history } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('revealSeedWords') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="secondary"
large
onClick={event => {
event.preventDefault()
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Reveal Seed Phrase',
name: 'Reveal Seed Phrase',
},
})
history.push(REVEAL_SEED_ROUTE)
}}
>
{ t('revealSeedWords') }
</Button>
</div>
</div>
</div>
)
}
renderMobileSync () {
const { t } = this.context
const { history } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('syncWithMobile') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="primary"
large
onClick={event => {
event.preventDefault()
history.push(MOBILE_SYNC_ROUTE)
}}
>
{ t('syncWithMobile') }
</Button>
</div>
</div>
</div>
)
}
renderResetAccount () {
const { t } = this.context
const { showResetAccountConfirmationModal } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('resetAccount') }</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()
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Reset Account',
name: 'Reset Account',
},
})
showResetAccountConfirmationModal()
}}
>
{ t('resetAccount') }
</Button>
</div>
</div>
</div>
)
}
renderBlockieOptIn () { renderBlockieOptIn () {
const { useBlockie, setUseBlockie } = this.props const { useBlockie, setUseBlockie } = this.props
@ -460,58 +126,6 @@ export default class SettingsTab extends PureComponent {
) )
} }
renderHexDataOptIn () {
const { t } = this.context
const { sendHexData, setHexDataFeatureFlag } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('showHexData') }</span>
<div className="settings-page__content-description">
{ t('showHexDataDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={sendHexData}
onToggle={value => setHexDataFeatureFlag(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderAdvancedGasInputInline () {
const { t } = this.context
const { advancedInlineGas, setAdvancedInlineGasFeatureFlag } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('showAdvancedGasInline') }</span>
<div className="settings-page__content-description">
{ t('showAdvancedGasInlineDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={advancedInlineGas}
onToggle={value => setAdvancedInlineGasFeatureFlag(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderUsePrimaryCurrencyOptions () { renderUsePrimaryCurrencyOptions () {
const { t } = this.context const { t } = this.context
const { const {
@ -566,109 +180,21 @@ export default class SettingsTab extends PureComponent {
) )
} }
renderShowConversionInTestnets () { renderContent () {
const { t } = this.context
const {
showFiatInTestnets,
setShowFiatConversionOnTestnetsPreference,
} = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('showFiatConversionInTestnets') }</span>
<div className="settings-page__content-description">
{ t('showFiatConversionInTestnetsDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={showFiatInTestnets}
onToggle={value => setShowFiatConversionOnTestnetsPreference(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderPrivacyOptIn () {
const { t } = this.context
const { privacyMode, setPrivacyMode } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('privacyMode') }</span>
<div className="settings-page__content-description">
{ t('privacyModeDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={privacyMode}
onToggle={value => setPrivacyMode(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
renderMetaMetricsOptIn () {
const { t } = this.context
const { participateInMetaMetrics, setParticipateInMetaMetrics } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('participateInMetaMetrics') }</span>
<div className="settings-page__content-description">
<span>{ t('participateInMetaMetricsDescription') }</span>
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={participateInMetaMetrics}
onToggle={value => setParticipateInMetaMetrics(!value)}
activeLabel=""
inactiveLabel=""
/>
</div>
</div>
</div>
)
}
render () {
const { warning } = this.props const { warning } = this.props
return ( return (
<div className="settings-page__content"> <div className="settings-page__body">
{ warning && <div className="settings-tab__error">{ warning }</div> } { warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderCurrentConversion() } { this.renderCurrentConversion() }
{ this.renderUsePrimaryCurrencyOptions() } { this.renderUsePrimaryCurrencyOptions() }
{ this.renderShowConversionInTestnets() }
{ this.renderCurrentLocale() } { this.renderCurrentLocale() }
{ this.renderNewRpcUrl() }
{ this.renderStateLogs() }
{ this.renderSeedWords() }
{ this.renderResetAccount() }
{ this.renderClearApproval() }
{ this.renderPrivacyOptIn() }
{ this.renderHexDataOptIn() }
{ this.renderAdvancedGasInputInline() }
{ this.renderBlockieOptIn() } { this.renderBlockieOptIn() }
{ this.renderMobileSync() }
{ this.renderMetaMetricsOptIn() }
</div> </div>
) )
} }
render () {
return this.renderContent()
}
} }

@ -4,15 +4,10 @@ import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { import {
setCurrentCurrency, setCurrentCurrency,
updateAndSetCustomRpc,
displayWarning, displayWarning,
revealSeedConfirmation,
setUseBlockie, setUseBlockie,
updateCurrentLocale, updateCurrentLocale,
setFeatureFlag,
showModal,
setUseNativeCurrencyAsPrimaryCurrencyPreference, setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
setParticipateInMetaMetrics, setParticipateInMetaMetrics,
} from '../../../store/actions' } from '../../../store/actions'
import { preferencesSelector } from '../../../selectors/selectors' import { preferencesSelector } from '../../../selectors/selectors'
@ -24,16 +19,9 @@ const mapStateToProps = state => {
conversionDate, conversionDate,
nativeCurrency, nativeCurrency,
useBlockie, useBlockie,
featureFlags: {
sendHexData,
privacyMode,
advancedInlineGas,
} = {},
provider = {},
currentLocale, currentLocale,
participateInMetaMetrics,
} = metamask } = metamask
const { useNativeCurrencyAsPrimaryCurrency, showFiatInTestnets } = preferencesSelector(state) const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
return { return {
warning, warning,
@ -42,35 +30,19 @@ const mapStateToProps = state => {
conversionDate, conversionDate,
nativeCurrency, nativeCurrency,
useBlockie, useBlockie,
sendHexData,
advancedInlineGas,
privacyMode,
provider,
useNativeCurrencyAsPrimaryCurrency, useNativeCurrencyAsPrimaryCurrency,
showFiatInTestnets,
participateInMetaMetrics,
} }
} }
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)), setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)),
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
displayWarning: warning => dispatch(displayWarning(warning)), displayWarning: warning => dispatch(displayWarning(warning)),
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
setUseBlockie: value => dispatch(setUseBlockie(value)), setUseBlockie: value => dispatch(setUseBlockie(value)),
updateCurrentLocale: key => dispatch(updateCurrentLocale(key)), updateCurrentLocale: key => dispatch(updateCurrentLocale(key)),
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
setAdvancedInlineGasFeatureFlag: shouldShow => dispatch(setFeatureFlag('advancedInlineGas', shouldShow)),
setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
setUseNativeCurrencyAsPrimaryCurrencyPreference: value => { setUseNativeCurrencyAsPrimaryCurrencyPreference: value => {
return dispatch(setUseNativeCurrencyAsPrimaryCurrencyPreference(value)) return dispatch(setUseNativeCurrencyAsPrimaryCurrencyPreference(value))
}, },
setShowFiatConversionOnTestnetsPreference: value => {
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
},
showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })),
setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)),
} }
} }

@ -1,10 +1,29 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Switch, Route, matchPath } from 'react-router-dom' import { Switch, Route, matchPath } from 'react-router-dom'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import TabBar from '../../components/app/tab-bar' import TabBar from '../../components/app/tab-bar'
import c from 'classnames'
import SettingsTab from './settings-tab' import SettingsTab from './settings-tab'
import AdvancedTab from './advanced-tab'
import InfoTab from './info-tab' import InfoTab from './info-tab'
import { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } from '../../helpers/constants/routes' import SecurityTab from './security-tab'
import {
DEFAULT_ROUTE,
ADVANCED_ROUTE,
SECURITY_ROUTE,
GENERAL_ROUTE,
ABOUT_US_ROUTE,
SETTINGS_ROUTE,
} from '../../helpers/constants/routes'
const ROUTES_TO_I18N_KEYS = {
[GENERAL_ROUTE]: 'general',
[ADVANCED_ROUTE]: 'advanced',
[SECURITY_ROUTE]: 'securityAndPrivacy',
[ABOUT_US_ROUTE]: 'aboutUs',
}
export default class SettingsPage extends PureComponent { export default class SettingsPage extends PureComponent {
static propTypes = { static propTypes = {
@ -17,38 +36,102 @@ export default class SettingsPage extends PureComponent {
t: PropTypes.func, t: PropTypes.func,
} }
isCurrentPath (pathname) {
return this.props.location.pathname === pathname
}
render () { render () {
const { t } = this.context
const { history, location } = this.props const { history, location } = this.props
const pathnameI18nKey = ROUTES_TO_I18N_KEYS[location.pathname]
const isPopupView = getEnvironmentType(location.href) === ENVIRONMENT_TYPE_POPUP
return ( return (
<div className="main-container settings-page"> <div
className={c('main-container settings-page', {
'settings-page--selected': !this.isCurrentPath(SETTINGS_ROUTE),
})}
>
<div className="settings-page__header"> <div className="settings-page__header">
{
!this.isCurrentPath(SETTINGS_ROUTE) && (
<div
className="settings-page__back-button"
onClick={() => history.push(SETTINGS_ROUTE)}
/>
)
}
<div className="settings-page__header__title">
{t(pathnameI18nKey && isPopupView ? pathnameI18nKey : 'settings')}
</div>
<div <div
className="settings-page__close-button" className="settings-page__close-button"
onClick={() => history.push(DEFAULT_ROUTE)} onClick={() => history.push(DEFAULT_ROUTE)}
/> />
<TabBar
tabs={[
{ content: this.context.t('settings'), key: SETTINGS_ROUTE },
{ content: this.context.t('info'), key: INFO_ROUTE },
]}
isActive={key => matchPath(location.pathname, { path: key, exact: true })}
onSelect={key => history.push(key)}
/>
</div> </div>
<Switch> <div className="settings-page__content">
<Route <div className="settings-page__content__tabs">
exact { this.renderTabs() }
path={INFO_ROUTE} </div>
component={InfoTab} <div className="settings-page__content__modules">
/> { this.renderContent() }
<Route </div>
exact </div>
path={SETTINGS_ROUTE}
component={SettingsTab}
/>
</Switch>
</div> </div>
) )
} }
renderTabs () {
const { history, location } = this.props
const { t } = this.context
return (
<TabBar
tabs={[
{ content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE },
{ content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE },
{ content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE },
{ content: t('aboutUs'), key: ABOUT_US_ROUTE },
]}
isActive={key => {
if (key === GENERAL_ROUTE && this.isCurrentPath(SETTINGS_ROUTE)) {
return true
}
return matchPath(location.pathname, { path: key, exact: true })
}}
onSelect={key => history.push(key)}
/>
)
}
renderContent () {
return (
<Switch>
<Route
exact
path={GENERAL_ROUTE}
component={SettingsTab}
/>
<Route
exact
path={ABOUT_US_ROUTE}
component={InfoTab}
/>
<Route
exact
path={ADVANCED_ROUTE}
component={AdvancedTab}
/>
<Route
exact
path={SECURITY_ROUTE}
component={SecurityTab}
/>
<Route
component={SettingsTab}
/>
</Switch>
)
}
} }

Loading…
Cancel
Save