diff options
author | Dan J Miller <danjm.com@gmail.com> | 2019-03-26 00:13:23 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-26 00:13:23 +0800 |
commit | 961ad267df93cbb3fc61d0a999bd78f132c877b1 (patch) | |
tree | 6186b2c88a343d5df98db3c2f6ea381c80df4ef5 | |
parent | 4ff9126ff2fddba40d3f210c757796458528ef42 (diff) | |
download | tangerine-wallet-browser-961ad267df93cbb3fc61d0a999bd78f132c877b1.tar.gz tangerine-wallet-browser-961ad267df93cbb3fc61d0a999bd78f132c877b1.tar.zst tangerine-wallet-browser-961ad267df93cbb3fc61d0a999bd78f132c877b1.zip |
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
19 files changed, 1010 insertions, 581 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index cb785023e..ca6761183 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -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" }, diff --git a/app/images/caret-left-black.svg b/app/images/caret-left-black.svg new file mode 100644 index 000000000..872135ece --- /dev/null +++ b/app/images/caret-left-black.svg @@ -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> diff --git a/package-lock.json b/package-lock.json index 1d2b3b724..d753a1e9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index 06f67ad95..e25b7edf1 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -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] diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index fcc7a8681..3d9037a06 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -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, }, [ diff --git a/ui/app/components/app/tab-bar.js b/ui/app/components/app/tab-bar.js index 0016a09c1..43923989a 100644 --- a/ui/app/components/app/tab-bar.js +++ b/ui/app/components/app/tab-bar.js @@ -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> ) } } diff --git a/ui/app/css/itcss/components/tab-bar.scss b/ui/app/css/itcss/components/tab-bar.scss index 4f3077974..bb9f8f261 100644 --- a/ui/app/css/itcss/components/tab-bar.scss +++ b/ui/app/css/itcss/components/tab-bar.scss @@ -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: 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 { - border-color: $black; + &--active { + opacity: 1 !important; + } } .tab-bar__grow-tab { diff --git a/ui/app/helpers/constants/routes.js b/ui/app/helpers/constants/routes.js index 932dfa7df..c15027ff4 100644 --- a/ui/app/helpers/constants/routes.js +++ b/ui/app/helpers/constants/routes.js @@ -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, } diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js new file mode 100644 index 000000000..d1cad1746 --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js @@ -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() + } +} diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js new file mode 100644 index 000000000..69d7e07e6 --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js @@ -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) diff --git a/ui/app/pages/settings/advanced-tab/index.js b/ui/app/pages/settings/advanced-tab/index.js new file mode 100644 index 000000000..85955174e --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/index.js @@ -0,0 +1 @@ +export { default } from './advanced-tab.container' diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss index 0e8482c63..52208dc85 100644 --- a/ui/app/pages/settings/index.scss +++ b/ui/app/pages/settings/index.scss @@ -9,34 +9,79 @@ 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; + + &__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 { display: flex; - flex-direction: row; + flex-direction: column; padding: 10px 0 20px; - - @media screen and (max-width: 575px) { - flex-direction: column; - padding: 10px 0; - } } &__content-item { @@ -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; + } + } + } + } + } } diff --git a/ui/app/pages/settings/info-tab/info-tab.component.js b/ui/app/pages/settings/info-tab/info-tab.component.js index 72f7d835e..552dd156e 100644 --- a/ui/app/pages/settings/info-tab/info-tab.component.js +++ b/ui/app/pages/settings/info-tab/info-tab.component.js @@ -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() + } } diff --git a/ui/app/pages/settings/security-tab/index.js b/ui/app/pages/settings/security-tab/index.js new file mode 100644 index 000000000..7ffc291a2 --- /dev/null +++ b/ui/app/pages/settings/security-tab/index.js @@ -0,0 +1 @@ +export { default } from './security-tab.container' diff --git a/ui/app/pages/settings/security-tab/security-tab.component.js b/ui/app/pages/settings/security-tab/security-tab.component.js new file mode 100644 index 000000000..233561115 --- /dev/null +++ b/ui/app/pages/settings/security-tab/security-tab.component.js @@ -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() + } +} diff --git a/ui/app/pages/settings/security-tab/security-tab.container.js b/ui/app/pages/settings/security-tab/security-tab.container.js new file mode 100644 index 000000000..6036f4eda --- /dev/null +++ b/ui/app/pages/settings/security-tab/security-tab.container.js @@ -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) diff --git a/ui/app/pages/settings/settings-tab/settings-tab.component.js b/ui/app/pages/settings/settings-tab/settings-tab.component.js index f69c21e82..57e80be0d 100644 --- a/ui/app/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/app/pages/settings/settings-tab/settings-tab.component.js @@ -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() + } } diff --git a/ui/app/pages/settings/settings-tab/settings-tab.container.js b/ui/app/pages/settings/settings-tab/settings-tab.container.js index 3ae4985d7..d3d8457f0 100644 --- a/ui/app/pages/settings/settings-tab/settings-tab.container.js +++ b/ui/app/pages/settings/settings-tab/settings-tab.container.js @@ -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)), } } diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js index d67d3fcfe..3d415c6b8 100644 --- a/ui/app/pages/settings/settings.component.js +++ b/ui/app/pages/settings/settings.component.js @@ -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)} /> - <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> - <Switch> - <Route - exact - path={INFO_ROUTE} - component={InfoTab} - /> - <Route - exact - path={SETTINGS_ROUTE} - component={SettingsTab} - /> - </Switch> + <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: 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> + ) + } } |