diff options
Diffstat (limited to 'ui/app/components/pages/settings')
12 files changed, 798 insertions, 592 deletions
diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js index aee17e0e8..44a9ffa63 100644 --- a/ui/app/components/pages/settings/index.js +++ b/ui/app/components/pages/settings/index.js @@ -1,64 +1 @@ -const { Component } = require('react') -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 +export { default } from './settings.component' diff --git a/ui/app/components/pages/settings/index.scss b/ui/app/components/pages/settings/index.scss new file mode 100644 index 000000000..138ebcfc5 --- /dev/null +++ b/ui/app/components/pages/settings/index.scss @@ -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%; + } + } +} diff --git a/ui/app/components/pages/settings/info-tab/index.js b/ui/app/components/pages/settings/info-tab/index.js new file mode 100644 index 000000000..7556a258d --- /dev/null +++ b/ui/app/components/pages/settings/info-tab/index.js @@ -0,0 +1 @@ +export { default } from './info-tab.component' diff --git a/ui/app/components/pages/settings/info-tab/index.scss b/ui/app/components/pages/settings/info-tab/index.scss new file mode 100644 index 000000000..43ad6f652 --- /dev/null +++ b/ui/app/components/pages/settings/info-tab/index.scss @@ -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; + } +} diff --git a/ui/app/components/pages/settings/info-tab/info-tab.component.js b/ui/app/components/pages/settings/info-tab/info-tab.component.js new file mode 100644 index 000000000..72f7d835e --- /dev/null +++ b/ui/app/components/pages/settings/info-tab/info-tab.component.js @@ -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> + ) + } +} diff --git a/ui/app/components/pages/settings/info.js b/ui/app/components/pages/settings/info.js deleted file mode 100644 index bd9040499..000000000 --- a/ui/app/components/pages/settings/info.js +++ /dev/null @@ -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 diff --git a/ui/app/components/pages/settings/settings-tab/index.js b/ui/app/components/pages/settings/settings-tab/index.js new file mode 100644 index 000000000..9fdaafd3f --- /dev/null +++ b/ui/app/components/pages/settings/settings-tab/index.js @@ -0,0 +1 @@ +export { default } from './settings-tab.container' diff --git a/ui/app/components/pages/settings/settings-tab/index.scss b/ui/app/components/pages/settings/settings-tab/index.scss new file mode 100644 index 000000000..76a0cec6f --- /dev/null +++ b/ui/app/components/pages/settings/settings-tab/index.scss @@ -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; + } + } +} diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js new file mode 100644 index 000000000..53c4f16e0 --- /dev/null +++ b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js @@ -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> + ) + } +} diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js new file mode 100644 index 000000000..665b56f5c --- /dev/null +++ b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js @@ -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) diff --git a/ui/app/components/pages/settings/settings.component.js b/ui/app/components/pages/settings/settings.component.js new file mode 100644 index 000000000..94a97bba1 --- /dev/null +++ b/ui/app/components/pages/settings/settings.component.js @@ -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> + ) + } +} diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js deleted file mode 100644 index 423276cf3..000000000 --- a/ui/app/components/pages/settings/settings.js +++ /dev/null @@ -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) |