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. 66
      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. 101
      ui/app/pages/settings/settings.component.js

@ -44,6 +44,9 @@
"providerRequestInfo": {
"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": {
"message": "Accept"
},
@ -74,6 +77,12 @@
"address": {
"message": "Address"
},
"advanced": {
"message": "Advanced"
},
"advancedSettingsDescription": {
"message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC."
},
"advancedOptions": {
"message": "Advanced Options"
},
@ -92,9 +101,6 @@
"addAcquiredTokens": {
"message": "Add the tokens you've acquired using MetaMask"
},
"advanced": {
"message": "Advanced"
},
"agreeTermsOfService": {
"message": "I agree to the Terms of Service"
},
@ -236,6 +242,9 @@
"chromeRequiredForHardwareWallets": {
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
},
"company": {
"message": "Company"
},
"confirm": {
"message": "Confirm"
},
@ -619,6 +628,12 @@
"gasPriceRequired": {
"message": "Gas Price Required"
},
"general": {
"message": "General"
},
"generalSettingsDescription": {
"message": "Currency conversion, primary currency, language, blockies identicon"
},
"generatingTransaction": {
"message": "Generating transaction"
},
@ -790,6 +805,9 @@
"ledgerAccountRestriction": {
"message": "You need to make use your last account before you can add a new one."
},
"legal": {
"message": "Legal"
},
"lessThanMax": {
"message": "must be less than or equal to $1.",
"description": "helper for inputting hex as decimal input"
@ -1240,6 +1258,12 @@
"secretPhrase": {
"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": {
"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": {
"babelify": {
"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=",
"requires": {
"babel-core": "^6.0.14",
@ -9901,7 +9901,7 @@
}
},
"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",
"requires": {
"bn.js": "^4.11.8",
@ -10189,7 +10189,7 @@
}
},
"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",
"requires": {
"bn.js": "^4.11.8",
@ -10484,7 +10484,7 @@
}
},
"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",
"requires": {
"bn.js": "^4.11.8",
@ -24076,7 +24076,7 @@
"dependencies": {
"babelify": {
"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=",
"requires": {
"babel-core": "^6.0.14",
@ -24115,7 +24115,7 @@
},
"babelify": {
"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=",
"requires": {
"babel-core": "^6.0.14",
@ -26541,7 +26541,7 @@
"dependencies": {
"babelify": {
"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=",
"requires": {
"babel-core": "^6.0.14",

@ -233,7 +233,11 @@ describe('MetaMask', function () {
await customRpcButton.click()
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 delay(largeDelayMs * 2)
})
@ -472,15 +476,19 @@ describe('MetaMask', function () {
const settingsButton = await findElement(driver, By.xpath(`//div[contains(text(), 'Settings')]`))
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()
const advancedGasTitle = await findElement(driver, By.xpath(`//span[contains(text(), 'Advanced gas controls')]`))
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()
windowHandles = await driver.getAllWindowHandles()
extension = windowHandles[0]

@ -10,7 +10,7 @@ const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
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.
const notToggleElementClassnames = [
@ -233,7 +233,7 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem,
{
closeMenu: () => this.props.hideNetworkDropdown(),
onClick: () => this.props.history.push(SETTINGS_ROUTE),
onClick: () => this.props.history.push(ADVANCED_ROUTE),
style: dropdownMenuItemStyle,
},
[

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

@ -1,21 +1,73 @@
.tab-bar {
display: flex;
flex-direction: row;
flex-direction: column;
justify-content: flex-start;
align-items: flex-end;
}
.tab-bar__tab {
display: flex;
flex-flow: row nowrap;
align-items: flex-start;
min-width: 0;
flex: 0 0 auto;
padding: 15px 25px;
border-bottom: 1px solid $alto;
box-sizing: border-box;
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 {
border-color: $black;
&--active {
opacity: 1 !important;
}
}
.tab-bar__grow-tab {

@ -2,7 +2,12 @@ const DEFAULT_ROUTE = '/'
const UNLOCK_ROUTE = '/unlock'
const LOCK_ROUTE = '/lock'
const SETTINGS_ROUTE = '/settings'
const GENERAL_ROUTE = '/settings/general'
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 MOBILE_SYNC_ROUTE = '/mobile-sync'
const CONFIRM_SEED_ROUTE = '/confirm-seed'
@ -80,4 +85,9 @@ module.exports = {
CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH,
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,36 +9,81 @@
flex-flow: column nowrap;
&__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 {
content: '\00D7';
font-size: 40px;
color: $dusty-gray;
position: absolute;
top: 25px;
right: 30px;
cursor: pointer;
}
&__content {
padding: 25px;
display: flex;
flex-flow: row nowrap;
height: auto;
overflow: auto;
}
&__content-row {
&__tabs {
display: flex;
flex-direction: row;
padding: 10px 0 20px;
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) {
flex-direction: column;
padding: 10px 0;
display: none;
}
}
}
&__body {
padding: 12px 24px;
}
&__content-row {
display: flex;
flex-direction: column;
padding: 10px 0 20px;
}
&__content-item {
flex: 1;
min-width: 0;
@ -77,4 +122,22 @@
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
return (
<div className="settings-page__content">
<div className="settings-page__body">
<div className="settings-page__content-row">
<div className="settings-page__content-item settings-page__content-item--without-height">
<div className="info-tab__logo-wrapper">
@ -133,4 +133,8 @@ export default class InfoTab extends PureComponent {
</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 PropTypes from 'prop-types'
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 ToggleButton from 'react-toggle-button'
import { REVEAL_SEED_ROUTE, MOBILE_SYNC_ROUTE } from '../../../helpers/constants/routes'
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) => {
return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
@ -37,44 +32,19 @@ export default class SettingsTab extends PureComponent {
}
static propTypes = {
metamask: PropTypes.object,
setUseBlockie: PropTypes.func,
setHexDataFeatureFlag: PropTypes.func,
setPrivacyMode: PropTypes.func,
privacyMode: PropTypes.bool,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
delRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
showClearApprovalModal: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string,
useBlockie: PropTypes.bool,
sendHexData: PropTypes.bool,
currentCurrency: PropTypes.string,
conversionDate: PropTypes.number,
nativeCurrency: PropTypes.string,
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
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 () {
@ -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 () {
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 () {
const { t } = this.context
const {
@ -566,109 +180,21 @@ export default class SettingsTab extends PureComponent {
)
}
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>
)
}
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 () {
renderContent () {
const { warning } = this.props
return (
<div className="settings-page__content">
<div className="settings-page__body">
{ warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderCurrentConversion() }
{ this.renderUsePrimaryCurrencyOptions() }
{ this.renderShowConversionInTestnets() }
{ this.renderCurrentLocale() }
{ this.renderNewRpcUrl() }
{ this.renderStateLogs() }
{ this.renderSeedWords() }
{ this.renderResetAccount() }
{ this.renderClearApproval() }
{ this.renderPrivacyOptIn() }
{ this.renderHexDataOptIn() }
{ this.renderAdvancedGasInputInline() }
{ this.renderBlockieOptIn() }
{ this.renderMobileSync() }
{ this.renderMetaMetricsOptIn() }
</div>
)
}
render () {
return this.renderContent()
}
}

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

@ -1,10 +1,29 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
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 c from 'classnames'
import SettingsTab from './settings-tab'
import AdvancedTab from './advanced-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 {
static propTypes = {
@ -17,38 +36,102 @@ export default class SettingsPage extends PureComponent {
t: PropTypes.func,
}
isCurrentPath (pathname) {
return this.props.location.pathname === pathname
}
render () {
const { t } = this.context
const { history, location } = this.props
const pathnameI18nKey = ROUTES_TO_I18N_KEYS[location.pathname]
const isPopupView = getEnvironmentType(location.href) === ENVIRONMENT_TYPE_POPUP
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">
{
!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
className="settings-page__close-button"
onClick={() => history.push(DEFAULT_ROUTE)}
/>
</div>
<div className="settings-page__content">
<div className="settings-page__content__tabs">
{ this.renderTabs() }
</div>
<div className="settings-page__content__modules">
{ this.renderContent() }
</div>
</div>
</div>
)
}
renderTabs () {
const { history, location } = this.props
const { t } = this.context
return (
<TabBar
tabs={[
{ content: this.context.t('settings'), key: SETTINGS_ROUTE },
{ content: this.context.t('info'), key: INFO_ROUTE },
{ 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 => matchPath(location.pathname, { path: key, exact: true })}
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)}
/>
</div>
)
}
renderContent () {
return (
<Switch>
<Route
exact
path={INFO_ROUTE}
path={GENERAL_ROUTE}
component={SettingsTab}
/>
<Route
exact
path={ABOUT_US_ROUTE}
component={InfoTab}
/>
<Route
exact
path={SETTINGS_ROUTE}
path={ADVANCED_ROUTE}
component={AdvancedTab}
/>
<Route
exact
path={SECURITY_ROUTE}
component={SecurityTab}
/>
<Route
component={SettingsTab}
/>
</Switch>
</div>
)
}
}

Loading…
Cancel
Save