Merge pull request #5241 from MetaMask/refactor-settings

Refactor settings page to use JSX and follow component file folder st…
feature/default_network_editable
Alexander Tseung 6 years ago committed by GitHub
commit 04988eca5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      app/_locales/en/messages.json
  2. 2
      test/e2e/beta/metamask-beta-ui.spec.js
  3. 2
      ui/app/app.js
  4. 2
      ui/app/components/pages/index.scss
  5. 65
      ui/app/components/pages/settings/index.js
  6. 80
      ui/app/components/pages/settings/index.scss
  7. 1
      ui/app/components/pages/settings/info-tab/index.js
  8. 56
      ui/app/components/pages/settings/info-tab/index.scss
  9. 136
      ui/app/components/pages/settings/info-tab/info-tab.component.js
  10. 120
      ui/app/components/pages/settings/info.js
  11. 1
      ui/app/components/pages/settings/settings-tab/index.js
  12. 51
      ui/app/components/pages/settings/settings-tab/index.scss
  13. 359
      ui/app/components/pages/settings/settings-tab/settings-tab.component.js
  14. 59
      ui/app/components/pages/settings/settings-tab/settings-tab.container.js
  15. 54
      ui/app/components/pages/settings/settings.component.js
  16. 408
      ui/app/components/pages/settings/settings.js
  17. 2
      ui/app/css/itcss/components/index.scss
  18. 214
      ui/app/css/itcss/components/settings.scss

@ -216,6 +216,9 @@
"currentConversion": { "currentConversion": {
"message": "Current Conversion" "message": "Current Conversion"
}, },
"currentLanguage": {
"message": "Current Language"
},
"currentNetwork": { "currentNetwork": {
"message": "Current Network" "message": "Current Network"
}, },
@ -596,6 +599,9 @@
"metamaskSeedWords": { "metamaskSeedWords": {
"message": "MetaMask Seed Words" "message": "MetaMask Seed Words"
}, },
"metamaskVersion": {
"message": "MetaMask Version"
},
"min": { "min": {
"message": "Minimum" "message": "Minimum"
}, },
@ -906,6 +912,9 @@
"selectCurrency": { "selectCurrency": {
"message": "Select Currency" "message": "Select Currency"
}, },
"selectLocale": {
"message": "Select Locale"
},
"selectService": { "selectService": {
"message": "Select Service" "message": "Select Service"
}, },
@ -1188,6 +1197,9 @@
"unlockMessage": { "unlockMessage": {
"message": "The decentralized web awaits" "message": "The decentralized web awaits"
}, },
"updatedWithDate": {
"message": "Updated $1"
},
"uriErrorMsg": { "uriErrorMsg": {
"message": "URIs require the appropriate HTTP/HTTPS prefix." "message": "URIs require the appropriate HTTP/HTTPS prefix."
}, },

@ -1043,7 +1043,7 @@ describe('MetaMask', function () {
await customRpcInput.clear() await customRpcInput.clear()
await customRpcInput.sendKeys(customRpcUrl) await customRpcInput.sendKeys(customRpcUrl)
const customRpcSave = await findElement(driver, By.css('.settings__rpc-save-button')) const customRpcSave = await findElement(driver, By.css('.settings-tab__rpc-save-button'))
await customRpcSave.click() await customRpcSave.click()
await delay(largeDelayMs * 2) await delay(largeDelayMs * 2)
}) })

@ -19,9 +19,9 @@ const Sidebar = require('./components/sidebars').default
// other views // other views
import Home from './components/pages/home' import Home from './components/pages/home'
import Settings from './components/pages/settings'
const Authenticated = require('./components/pages/authenticated') const Authenticated = require('./components/pages/authenticated')
const Initialized = require('./components/pages/initialized') const Initialized = require('./components/pages/initialized')
const Settings = require('./components/pages/settings')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed') const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token') const AddTokenPage = require('./components/pages/add-token')

@ -3,3 +3,5 @@
@import './add-token/index'; @import './add-token/index';
@import './confirm-add-token/index'; @import './confirm-add-token/index';
@import './settings/index';

@ -1,64 +1 @@
const { Component } = require('react') export { default } from './settings.component'
const { Switch, Route, matchPath } = require('react-router-dom')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const TabBar = require('../../tab-bar')
const Settings = require('./settings')
const Info = require('./info')
const { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes')
class Config extends Component {
renderTabs () {
const { history, location } = this.props
return h('div.settings__tabs', [
h(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),
}),
])
}
render () {
const { history } = this.props
return (
h('.main-container.settings', {}, [
h('.settings__header', [
h('div.settings__close-button', {
onClick: () => history.push(DEFAULT_ROUTE),
}),
this.renderTabs(),
]),
h(Switch, [
h(Route, {
exact: true,
path: INFO_ROUTE,
component: Info,
}),
h(Route, {
exact: true,
path: SETTINGS_ROUTE,
component: Settings,
}),
]),
])
)
}
}
Config.propTypes = {
location: PropTypes.object,
history: PropTypes.object,
t: PropTypes.func,
}
Config.contextTypes = {
t: PropTypes.func,
}
module.exports = Config

@ -0,0 +1,80 @@
@import './info-tab/index';
@import './settings-tab/index';
.settings-page {
position: relative;
background: $white;
display: flex;
flex-flow: column nowrap;
&__header {
padding: 25px 25px 0;
}
&__close-button::after {
content: '\00D7';
font-size: 40px;
color: $dusty-gray;
position: absolute;
top: 25px;
right: 30px;
cursor: pointer;
}
&__content {
padding: 25px;
height: auto;
overflow: auto;
}
&__content-row {
display: flex;
flex-direction: row;
padding: 10px 0 20px;
@media screen and (max-width: 575px) {
flex-direction: column;
padding: 10px 0;
}
}
&__content-item {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
padding: 0 5px;
min-height: 71px;
@media screen and (max-width: 575px) {
height: initial;
padding: 5px 0;
}
&--without-height {
height: initial;
}
}
&__content-label {
text-transform: capitalize;
}
&__content-description {
font-size: 14px;
color: $dusty-gray;
padding-top: 5px;
}
&__content-item-col {
max-width: 300px;
display: flex;
flex-direction: column;
@media screen and (max-width: 575px) {
max-width: 100%;
width: 100%;
}
}
}

@ -0,0 +1 @@
export { default } from './info-tab.component'

@ -0,0 +1,56 @@
.info-tab {
&__logo-wrapper {
height: 80px;
margin-bottom: 20px;
}
&__logo {
max-height: 100%;
max-width: 100%;
}
&__item {
padding: 10px 0;
}
&__link-header {
padding-bottom: 15px;
@media screen and (max-width: 575px) {
padding-bottom: 5px;
}
}
&__link-item {
padding: 15px 0;
@media screen and (max-width: 575px) {
padding: 5px 0;
}
}
&__link-text {
color: $curious-blue;
}
&__version-number {
padding-top: 5px;
font-size: 13px;
color: $dusty-gray;
}
&__separator {
margin: 15px 0;
width: 80px;
border-color: $alto;
border: none;
height: 1px;
background-color: $alto;
color: $alto;
}
&__about {
color: $dusty-gray;
margin-bottom: 15px;
}
}

@ -0,0 +1,136 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
export default class InfoTab extends PureComponent {
state = {
version: global.platform.getVersion(),
}
static propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
warning: PropTypes.string,
location: PropTypes.object,
history: PropTypes.object,
}
static contextTypes = {
t: PropTypes.func,
}
renderInfoLinks () {
const { t } = this.context
return (
<div className="settings-page__content-item settings-page__content-item--without-height">
<div className="info-tab__link-header">
{ t('links') }
</div>
<div className="info-tab__link-item">
<a
href="https://metamask.io/privacy.html"
target="_blank"
rel="noopener noreferrer"
>
<span className="info-tab__link-text">
{ t('privacyMsg') }
</span>
</a>
</div>
<div className="info-tab__link-item">
<a
href="https://metamask.io/terms.html"
target="_blank"
rel="noopener noreferrer"
>
<span className="info-tab__link-text">
{ t('terms') }
</span>
</a>
</div>
<div className="info-tab__link-item">
<a
href="https://metamask.io/attributions.html"
target="_blank"
rel="noopener noreferrer"
>
<span className="info-tab__link-text">
{ t('attributions') }
</span>
</a>
</div>
<hr className="info-tab__separator" />
<div className="info-tab__link-item">
<a
href="https://support.metamask.io"
target="_blank"
rel="noopener noreferrer"
>
<span className="info-tab__link-text">
{ t('supportCenter') }
</span>
</a>
</div>
<div className="info-tab__link-item">
<a
href="https://metamask.io/"
target="_blank"
rel="noopener noreferrer"
>
<span className="info-tab__link-text">
{ t('visitWebSite') }
</span>
</a>
</div>
<div className="info-tab__link-item">
<a
href="mailto:help@metamask.io?subject=Feedback"
target="_blank"
rel="noopener noreferrer"
>
<span className="info-tab__link-text">
{ t('emailUs') }
</span>
</a>
</div>
</div>
)
}
render () {
const { t } = this.context
return (
<div className="settings-page__content">
<div className="settings-page__content-row">
<div className="settings-page__content-item settings-page__content-item--without-height">
<div className="info-tab__logo-wrapper">
<img
src="images/info-logo.png"
className="info-tab__logo"
/>
</div>
<div className="info-tab__item">
<div className="info-tab__version-header">
{ t('metamaskVersion') }
</div>
<div className="info-tab__version-number">
{ this.state.version }
</div>
</div>
<div className="info-tab__item">
<div className="info-tab__about">
{ t('builtInCalifornia') }
</div>
</div>
</div>
{ this.renderInfoLinks() }
</div>
</div>
)
}
}

@ -1,120 +0,0 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
class Info extends Component {
constructor (props) {
super(props)
this.state = {
version: global.platform.getVersion(),
}
}
renderLogo () {
return (
h('div.settings__info-logo-wrapper', [
h('img.settings__info-logo', { src: 'images/info-logo.png' }),
])
)
}
renderInfoLinks () {
return (
h('div.settings__content-item.settings__content-item--without-height', [
h('div.settings__info-link-header', this.context.t('links')),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/privacy.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('privacyMsg')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/terms.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('terms')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/attributions.html',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('attributions')),
]),
]),
h('hr.settings__info-separator'),
h('div.settings__info-link-item', [
h('a', {
href: 'https://support.metamask.io',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('supportCenter')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/',
target: '_blank',
}, [
h('span.settings__info-link', this.context.t('visitWebSite')),
]),
]),
h('div.settings__info-link-item', [
h('a', {
target: '_blank',
href: 'mailto:help@metamask.io?subject=Feedback',
}, [
h('span.settings__info-link', this.context.t('emailUs')),
]),
]),
])
)
}
render () {
return (
h('div.settings__content', [
h('div.settings__content-row', [
h('div.settings__content-item.settings__content-item--without-height', [
this.renderLogo(),
h('div.settings__info-item', [
h('div.settings__info-version-header', 'MetaMask Version'),
h('div.settings__info-version-number', this.state.version),
]),
h('div.settings__info-item', [
h(
'div.settings__info-about',
this.context.t('builtInCalifornia')
),
]),
]),
this.renderInfoLinks(),
]),
])
)
}
}
Info.propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
warning: PropTypes.string,
location: PropTypes.object,
history: PropTypes.object,
t: PropTypes.func,
}
Info.contextTypes = {
t: PropTypes.func,
}
module.exports = Info

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

@ -0,0 +1,51 @@
.settings-tab {
&__error {
padding-bottom: 20px;
text-align: center;
color: $crimson;
}
&__rpc-save-button {
align-self: flex-end;
padding: 5px;
text-transform: uppercase;
color: $dusty-gray;
cursor: pointer;
}
&__rpc-save-button {
align-self: flex-end;
padding: 5px;
text-transform: uppercase;
color: $dusty-gray;
cursor: pointer;
}
&__button--red {
border-color: lighten($monzo, 10%);
color: $monzo;
&:active {
background: lighten($monzo, 55%);
border-color: $monzo;
}
&:hover {
border-color: $monzo;
}
}
&__button--orange {
border-color: lighten($ecstasy, 20%);
color: $ecstasy;
&:active {
background: lighten($ecstasy, 40%);
border-color: $ecstasy;
}
&:hover {
border-color: $ecstasy;
}
}
}

@ -0,0 +1,359 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import infuraCurrencies from '../../../../infura-conversion.json'
import validUrl from 'valid-url'
import { exportAsFile } from '../../../../util'
import SimpleDropdown from '../../../dropdowns/simple-dropdown'
import ToggleButton from 'react-toggle-button'
import { REVEAL_SEED_ROUTE } from '../../../../routes'
import locales from '../../../../../../app/_locales/index.json'
import TextField from '../../../text-field'
import Button from '../../../button'
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
})
const infuraCurrencyOptions = sortedCurrencies.map(({ quote: { code, name } }) => {
return {
displayValue: `${code.toUpperCase()} - ${name}`,
key: code,
value: code,
}
})
const localeOptions = locales.map(locale => {
return {
displayValue: `${locale.name}`,
key: locale.code,
value: locale.code,
}
})
export default class SettingsTab extends PureComponent {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
metamask: PropTypes.object,
setUseBlockie: PropTypes.func,
setHexDataFeatureFlag: PropTypes.func,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
isMascara: PropTypes.bool,
updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string,
useBlockie: PropTypes.bool,
sendHexData: PropTypes.bool,
currentCurrency: PropTypes.string,
conversionDate: PropTypes.number,
}
state = {
newRpc: '',
}
renderCurrentConversion () {
const { t } = this.context
const { currentCurrency, conversionDate, setCurrentCurrency } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('currentConversion') }</span>
<span className="settings-page__content-description">
{ t('updatedWithDate', [Date(conversionDate)]) }
</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<SimpleDropdown
placeholder={t('selectCurrency')}
options={infuraCurrencyOptions}
selectedOption={currentCurrency}
onSelect={newCurrency => setCurrentCurrency(newCurrency)}
/>
</div>
</div>
</div>
)
}
renderCurrentLocale () {
const { t } = this.context
const { updateCurrentLocale, currentLocale } = this.props
const currentLocaleMeta = locales.find(locale => locale.code === currentLocale)
const currentLocaleName = currentLocaleMeta ? currentLocaleMeta.name : ''
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span className="settings-page__content-label">
{ t('currentLanguage') }
</span>
<span className="settings-page__content-description">
{ currentLocaleName }
</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<SimpleDropdown
placeholder={t('selectLocale')}
options={localeOptions}
selectedOption={currentLocale}
onSelect={async newLocale => updateCurrentLocale(newLocale)}
/>
</div>
</div>
</div>
)
}
renderNewRpcUrl () {
const { t } = this.context
const { newRpc } = this.state
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('newRPC') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
id="new-rpc"
placeholder={t('newRPC')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc)
}
}}
fullWidth
margin="none"
/>
<div
className="settings-tab__rpc-save-button"
onClick={e => {
e.preventDefault()
this.validateRpc(newRpc)
}}
>
{ t('save') }
</div>
</div>
</div>
</div>
)
}
validateRpc (newRpc) {
const { setRpcTarget, displayWarning } = this.props
if (validUrl.isWebUri(newRpc)) {
setRpcTarget(newRpc)
} else {
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>
)
}
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()
history.push(REVEAL_SEED_ROUTE)
}}
>
{ t('revealSeedWords') }
</Button>
</div>
</div>
</div>
)
}
renderOldUI () {
const { t } = this.context
const { setFeatureFlagToBeta } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('useOldUI') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="secondary"
large
className="settings-tab__button--orange"
onClick={event => {
event.preventDefault()
setFeatureFlagToBeta()
}}
>
{ t('useOldUI') }
</Button>
</div>
</div>
</div>
)
}
renderResetAccount () {
const { t } = this.context
const { showResetAccountConfirmationModal } = this.props
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()
showResetAccountConfirmationModal()
}}
>
{ t('resetAccount') }
</Button>
</div>
</div>
</div>
)
}
renderBlockieOptIn () {
const { useBlockie, setUseBlockie } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ this.context.t('blockiesIdenticon') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={useBlockie}
onToggle={value => setUseBlockie(!value)}
activeLabel=""
inactiveLabel=""
/>
</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>
)
}
render () {
const { warning, isMascara } = this.props
return (
<div className="settings-page__content">
{ warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderCurrentConversion() }
{ this.renderCurrentLocale() }
{ this.renderNewRpcUrl() }
{ this.renderStateLogs() }
{ this.renderSeedWords() }
{ !isMascara && this.renderOldUI() }
{ this.renderResetAccount() }
{ this.renderBlockieOptIn() }
{ this.renderHexDataOptIn() }
</div>
)
}
}

@ -0,0 +1,59 @@
import SettingsTab from './settings-tab.component'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import {
setCurrentCurrency,
setRpcTarget,
displayWarning,
revealSeedConfirmation,
setUseBlockie,
updateCurrentLocale,
setFeatureFlag,
showModal,
} from '../../../../actions'
const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
const {
currentCurrency,
conversionDate,
useBlockie,
featureFlags: { sendHexData } = {},
provider = {},
isMascara,
currentLocale,
} = metamask
return {
warning,
isMascara,
currentLocale,
currentCurrency,
conversionDate,
useBlockie,
sendHexData,
provider,
}
}
const mapDispatchToProps = dispatch => {
return {
setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)),
setRpcTarget: newRpc => dispatch(setRpcTarget(newRpc)),
displayWarning: warning => dispatch(displayWarning(warning)),
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
setUseBlockie: value => dispatch(setUseBlockie(value)),
updateCurrentLocale: key => dispatch(updateCurrentLocale(key)),
setFeatureFlagToBeta: () => {
return dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
},
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SettingsTab)

@ -0,0 +1,54 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Switch, Route, matchPath } from 'react-router-dom'
import TabBar from '../../tab-bar'
import SettingsTab from './settings-tab'
import InfoTab from './info-tab'
import { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } from '../../../routes'
export default class SettingsPage extends PureComponent {
static propTypes = {
location: PropTypes.object,
history: PropTypes.object,
t: PropTypes.func,
}
static contextTypes = {
t: PropTypes.func,
}
render () {
const { history, location } = this.props
return (
<div className="main-container settings-page">
<div className="settings-page__header">
<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>
)
}
}

@ -1,408 +0,0 @@
const { Component } = require('react')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../../actions')
const infuraCurrencies = require('../../../infura-conversion.json')
const validUrl = require('valid-url')
const { exportAsFile } = require('../../../util')
const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button')
const { REVEAL_SEED_ROUTE } = require('../../../routes')
const locales = require('../../../../../app/_locales/index.json')
import Button from '../../button'
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
})
return sortedCurrencies.map(({ quote: { code, name } }) => {
return {
displayValue: `${code.toUpperCase()} - ${name}`,
key: code,
value: code,
}
})
}
const getLocaleOptions = () => {
return locales.map((locale) => {
return {
displayValue: `${locale.name}`,
key: locale.code,
value: locale.code,
}
})
}
class Settings extends Component {
constructor (props) {
super(props)
this.state = {
newRpc: '',
}
}
renderBlockieOptIn () {
const { metamask: { useBlockie }, setUseBlockie } = this.props
return h('div.settings__content-row', [
h('div.settings__content-item', [
h('span', this.context.t('blockiesIdenticon')),
]),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(ToggleButton, {
value: useBlockie,
onToggle: (value) => setUseBlockie(!value),
activeLabel: '',
inactiveLabel: '',
}),
]),
]),
])
}
renderHexDataOptIn () {
const { metamask: { featureFlags: { sendHexData } }, setHexDataFeatureFlag } = this.props
return h('div.settings__content-row', [
h('div.settings__content-item', [
h('span', this.context.t('showHexData')),
h(
'div.settings__content-description',
this.context.t('showHexDataDescription')
),
]),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(ToggleButton, {
value: sendHexData,
onToggle: (value) => setHexDataFeatureFlag(!value),
activeLabel: '',
inactiveLabel: '',
}),
]),
]),
])
}
renderCurrentConversion () {
const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props
return h('div.settings__content-row', [
h('div.settings__content-item', [
h('span', this.context.t('currentConversion')),
h('span.settings__content-description', `Updated ${Date(conversionDate)}`),
]),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(SimpleDropdown, {
placeholder: this.context.t('selectCurrency'),
options: getInfuraCurrencyOptions(),
selectedOption: currentCurrency,
onSelect: newCurrency => setCurrentCurrency(newCurrency),
}),
]),
]),
])
}
renderCurrentLocale () {
const { updateCurrentLocale, currentLocale } = this.props
const currentLocaleMeta = locales.find(locale => locale.code === currentLocale)
const currentLocaleName = currentLocaleMeta ? currentLocaleMeta.name : ''
return h('div.settings__content-row', [
h('div.settings__content-item', [
h('span', 'Current Language'),
h('span.settings__content-description', `${currentLocaleName}`),
]),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(SimpleDropdown, {
placeholder: 'Select Locale',
options: getLocaleOptions(),
selectedOption: currentLocale,
onSelect: async (newLocale) => {
updateCurrentLocale(newLocale)
},
}),
]),
]),
])
}
renderCurrentProvider () {
const { metamask: { provider = {} } } = this.props
let title, value, color
switch (provider.type) {
case 'mainnet':
title = this.context.t('currentNetwork')
value = this.context.t('mainnet')
color = '#038789'
break
case 'ropsten':
title = this.context.t('currentNetwork')
value = this.context.t('ropsten')
color = '#e91550'
break
case 'kovan':
title = this.context.t('currentNetwork')
value = this.context.t('kovan')
color = '#690496'
break
case 'rinkeby':
title = this.context.t('currentNetwork')
value = this.context.t('rinkeby')
color = '#ebb33f'
break
default:
title = this.context.t('currentRpc')
value = provider.rpcTarget
}
return h('div.settings__content-row', [
h('div.settings__content-item', title),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h('div.settings__provider-wrapper', [
h('div.settings__provider-icon', { style: { background: color } }),
h('div', value),
]),
]),
]),
])
}
renderNewRpcUrl () {
return (
h('div.settings__content-row', [
h('div.settings__content-item', [
h('span', this.context.t('newRPC')),
]),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h('input.settings__input', {
placeholder: this.context.t('newRPC'),
onChange: event => this.setState({ newRpc: event.target.value }),
onKeyPress: event => {
if (event.key === 'Enter') {
this.validateRpc(this.state.newRpc)
}
},
}),
h('div.settings__rpc-save-button', {
onClick: event => {
event.preventDefault()
this.validateRpc(this.state.newRpc)
},
}, this.context.t('save')),
]),
]),
])
)
}
validateRpc (newRpc) {
const { setRpcTarget, displayWarning } = this.props
if (validUrl.isWebUri(newRpc)) {
setRpcTarget(newRpc)
} else {
const appendedRpc = `http://${newRpc}`
if (validUrl.isWebUri(appendedRpc)) {
displayWarning(this.context.t('uriErrorMsg'))
} else {
displayWarning(this.context.t('invalidRPC'))
}
}
}
renderStateLogs () {
return (
h('div.settings__content-row', [
h('div.settings__content-item', [
h('div', this.context.t('stateLogs')),
h(
'div.settings__content-description',
this.context.t('stateLogsDescription')
),
]),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(Button, {
type: 'primary',
large: true,
className: 'settings__button',
onClick (event) {
window.logStateString((err, result) => {
if (err) {
this.state.dispatch(actions.displayWarning(this.context.t('stateLogError')))
} else {
exportAsFile('MetaMask State Logs.json', result)
}
})
},
}, this.context.t('downloadStateLogs')),
]),
]),
])
)
}
renderSeedWords () {
const { history } = this.props
return (
h('div.settings__content-row', [
h('div.settings__content-item', this.context.t('revealSeedWords')),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(Button, {
type: 'primary',
large: true,
className: 'settings__button--red',
onClick: event => {
event.preventDefault()
history.push(REVEAL_SEED_ROUTE)
},
}, this.context.t('revealSeedWords')),
]),
]),
])
)
}
renderOldUI () {
const { setFeatureFlagToBeta } = this.props
return (
h('div.settings__content-row', [
h('div.settings__content-item', this.context.t('useOldUI')),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(Button, {
type: 'primary',
large: true,
className: 'settings__button--orange',
onClick (event) {
event.preventDefault()
setFeatureFlagToBeta()
},
}, this.context.t('useOldUI')),
]),
]),
])
)
}
renderResetAccount () {
const { showResetAccountConfirmationModal } = this.props
return h('div.settings__content-row', [
h('div.settings__content-item', this.context.t('resetAccount')),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h(Button, {
type: 'primary',
large: true,
className: 'settings__button--orange',
onClick (event) {
event.preventDefault()
showResetAccountConfirmationModal()
},
}, this.context.t('resetAccount')),
]),
]),
])
}
render () {
const { warning, isMascara } = this.props
return (
h('div.settings__content', [
warning && h('div.settings__error', warning),
this.renderCurrentConversion(),
this.renderCurrentLocale(),
// this.renderCurrentProvider(),
this.renderNewRpcUrl(),
this.renderStateLogs(),
this.renderSeedWords(),
!isMascara && this.renderOldUI(),
this.renderResetAccount(),
this.renderBlockieOptIn(),
this.renderHexDataOptIn(),
])
)
}
}
Settings.propTypes = {
metamask: PropTypes.object,
setUseBlockie: PropTypes.func,
setHexDataFeatureFlag: PropTypes.func,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
isMascara: PropTypes.bool,
updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string,
t: PropTypes.func,
}
const mapStateToProps = state => {
return {
metamask: state.metamask,
warning: state.appState.warning,
isMascara: state.metamask.isMascara,
currentLocale: state.metamask.currentLocale,
}
}
const mapDispatchToProps = dispatch => {
return {
setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
displayWarning: warning => dispatch(actions.displayWarning(warning)),
revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
setUseBlockie: value => dispatch(actions.setUseBlockie(value)),
updateCurrentLocale: key => dispatch(actions.updateCurrentLocale(key)),
setFeatureFlagToBeta: () => {
return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
},
setHexDataFeatureFlag: (featureFlagShowState) => {
return dispatch(actions.setFeatureFlag('sendHexData', featureFlagShowState))
},
showResetAccountConfirmationModal: () => {
return dispatch(actions.showModal({ name: 'CONFIRM_RESET_ACCOUNT' }))
},
}
}
Settings.contextTypes = {
t: PropTypes.func,
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(Settings)

@ -36,8 +36,6 @@
@import './gas-slider.scss'; @import './gas-slider.scss';
@import './settings.scss';
@import './tab-bar.scss'; @import './tab-bar.scss';
@import './simple-dropdown.scss'; @import './simple-dropdown.scss';

@ -1,214 +0,0 @@
.settings {
position: relative;
background: $white;
display: flex;
flex-flow: column nowrap;
}
.settings__header {
padding: 25px;
}
.settings__close-button::after {
content: '\00D7';
font-size: 40px;
color: $dusty-gray;
position: absolute;
top: 25px;
right: 30px;
cursor: pointer;
}
.settings__error {
padding-bottom: 20px;
text-align: center;
color: $crimson;
}
.settings__content {
padding: 0 25px;
height: auto;
overflow: auto;
}
.settings__content-row {
display: flex;
flex-direction: row;
padding: 10px 0 20px;
@media screen and (max-width: 575px) {
flex-direction: column;
padding: 10px 0;
}
}
.settings__content-item {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
padding: 0 5px;
height: 71px;
@media screen and (max-width: 575px) {
height: initial;
padding: 5px 0;
}
&--without-height {
height: initial;
}
}
.settings__content-item-col {
max-width: 300px;
display: flex;
flex-direction: column;
@media screen and (max-width: 575px) {
max-width: 100%;
width: 100%;
}
}
.settings__content-description {
font-size: 14px;
color: $dusty-gray;
padding-top: 5px;
}
.settings__input {
padding-left: 10px;
font-size: 14px;
height: 40px;
border: 1px solid $alto;
}
.settings__input::-webkit-input-placeholder {
font-weight: 100;
color: $dusty-gray;
}
.settings__input::-moz-placeholder {
font-weight: 100;
color: $dusty-gray;
}
.settings__input:-ms-input-placeholder {
font-weight: 100;
color: $dusty-gray;
}
.settings__input:-moz-placeholder {
font-weight: 100;
color: $dusty-gray;
}
.settings__provider-wrapper {
font-size: 16px;
border: 1px solid $alto;
border-radius: 2px;
padding: 15px;
background-color: $white;
display: flex;
align-items: center;
justify-content: flex-start;
}
.settings__provider-icon {
height: 10px;
width: 10px;
margin-right: 10px;
border-radius: 10px;
}
.settings__rpc-save-button {
align-self: flex-end;
padding: 5px;
text-transform: uppercase;
color: $dusty-gray;
cursor: pointer;
}
.settings__button--red {
border-color: lighten($monzo, 10%);
color: $monzo;
&:active {
background: lighten($monzo, 55%);
border-color: $monzo;
}
&:hover {
border-color: $monzo;
}
}
.settings__button--orange {
border-color: lighten($ecstasy, 20%);
color: $ecstasy;
&:active {
background: lighten($ecstasy, 40%);
border-color: $ecstasy;
}
&:hover {
border-color: $ecstasy;
}
}
.settings__info-logo-wrapper {
height: 80px;
margin-bottom: 20px;
}
.settings__info-logo {
max-height: 100%;
max-width: 100%;
}
.settings__info-item {
padding: 10px 0;
}
.settings__info-link-header {
padding-bottom: 15px;
@media screen and (max-width: 575px) {
padding-bottom: 5px;
}
}
.settings__info-link-item {
padding: 15px 0;
@media screen and (max-width: 575px) {
padding: 5px 0;
}
}
.settings__info-version-number {
padding-top: 5px;
font-size: 13px;
color: $dusty-gray;
}
.settings__info-about {
color: $dusty-gray;
margin-bottom: 15px;
}
.settings__info-link {
color: $curious-blue;
}
.settings__info-separator {
margin: 15px 0;
width: 80px;
border-color: $alto;
border: none;
height: 1px;
background-color: $alto;
color: $alto;
}
Loading…
Cancel
Save