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 settingsfeature/default_network_editable
parent
4ff9126ff2
commit
961ad267df
After Width: | Height: | Size: 913 B |
@ -0,0 +1,378 @@ |
||||
import React, { PureComponent } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import validUrl from 'valid-url' |
||||
import { exportAsFile } from '../../../helpers/utils/util' |
||||
import ToggleButton from 'react-toggle-button' |
||||
import TextField from '../../../components/ui/text-field' |
||||
import Button from '../../../components/ui/button' |
||||
import { MOBILE_SYNC_ROUTE } from '../../../helpers/constants/routes' |
||||
|
||||
export default class AdvancedTab extends PureComponent { |
||||
static contextTypes = { |
||||
t: PropTypes.func, |
||||
metricsEvent: PropTypes.func, |
||||
} |
||||
|
||||
static propTypes = { |
||||
setHexDataFeatureFlag: PropTypes.func, |
||||
setRpcTarget: PropTypes.func, |
||||
displayWarning: PropTypes.func, |
||||
showResetAccountConfirmationModal: PropTypes.func, |
||||
warning: PropTypes.string, |
||||
history: PropTypes.object, |
||||
sendHexData: PropTypes.bool, |
||||
setAdvancedInlineGasFeatureFlag: PropTypes.func, |
||||
advancedInlineGas: PropTypes.bool, |
||||
showFiatInTestnets: PropTypes.bool, |
||||
setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired, |
||||
} |
||||
|
||||
state = { |
||||
newRpc: '', |
||||
chainId: '', |
||||
showOptions: false, |
||||
ticker: '', |
||||
nickname: '', |
||||
} |
||||
|
||||
renderNewRpcUrl () { |
||||
const { t } = this.context |
||||
const { newRpc, chainId, ticker, nickname } = this.state |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('newNetwork') }</span> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<TextField |
||||
type="text" |
||||
id="new-rpc" |
||||
placeholder={t('rpcURL')} |
||||
value={newRpc} |
||||
onChange={e => this.setState({ newRpc: e.target.value })} |
||||
onKeyPress={e => { |
||||
if (e.key === 'Enter') { |
||||
this.validateRpc(newRpc, chainId, ticker, nickname) |
||||
} |
||||
}} |
||||
fullWidth |
||||
margin="dense" |
||||
/> |
||||
<TextField |
||||
type="text" |
||||
id="chainid" |
||||
placeholder={t('optionalChainId')} |
||||
value={chainId} |
||||
onChange={e => this.setState({ chainId: e.target.value })} |
||||
onKeyPress={e => { |
||||
if (e.key === 'Enter') { |
||||
this.validateRpc(newRpc, chainId, ticker, nickname) |
||||
} |
||||
}} |
||||
style={{ |
||||
display: this.state.showOptions ? null : 'none', |
||||
}} |
||||
fullWidth |
||||
margin="dense" |
||||
/> |
||||
<TextField |
||||
type="text" |
||||
id="ticker" |
||||
placeholder={t('optionalSymbol')} |
||||
value={ticker} |
||||
onChange={e => this.setState({ ticker: e.target.value })} |
||||
onKeyPress={e => { |
||||
if (e.key === 'Enter') { |
||||
this.validateRpc(newRpc, chainId, ticker, nickname) |
||||
} |
||||
}} |
||||
style={{ |
||||
display: this.state.showOptions ? null : 'none', |
||||
}} |
||||
fullWidth |
||||
margin="dense" |
||||
/> |
||||
<TextField |
||||
type="text" |
||||
id="nickname" |
||||
placeholder={t('optionalNickname')} |
||||
value={nickname} |
||||
onChange={e => this.setState({ nickname: e.target.value })} |
||||
onKeyPress={e => { |
||||
if (e.key === 'Enter') { |
||||
this.validateRpc(newRpc, chainId, ticker, nickname) |
||||
} |
||||
}} |
||||
style={{ |
||||
display: this.state.showOptions ? null : 'none', |
||||
}} |
||||
fullWidth |
||||
margin="dense" |
||||
/> |
||||
<div className="flex-row flex-align-center space-between"> |
||||
<span className="settings-tab__advanced-link" |
||||
onClick={e => { |
||||
e.preventDefault() |
||||
this.setState({ showOptions: !this.state.showOptions }) |
||||
}} |
||||
> |
||||
{ t(this.state.showOptions ? 'hideAdvancedOptions' : 'showAdvancedOptions') } |
||||
</span> |
||||
<button |
||||
className="button btn-primary settings-tab__rpc-save-button" |
||||
onClick={e => { |
||||
e.preventDefault() |
||||
this.validateRpc(newRpc, chainId, ticker, nickname) |
||||
}} |
||||
> |
||||
{ t('save') } |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
validateRpc (newRpc, chainId, ticker = 'ETH', nickname) { |
||||
const { setRpcTarget, displayWarning } = this.props |
||||
if (validUrl.isWebUri(newRpc)) { |
||||
this.context.metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Settings', |
||||
action: 'Custom RPC', |
||||
name: 'Success', |
||||
}, |
||||
customVariables: { |
||||
networkId: newRpc, |
||||
chainId, |
||||
}, |
||||
}) |
||||
if (!!chainId && Number.isNaN(parseInt(chainId))) { |
||||
return displayWarning(`${this.context.t('invalidInput')} chainId`) |
||||
} |
||||
|
||||
setRpcTarget(newRpc, chainId, ticker, nickname) |
||||
} else { |
||||
this.context.metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Settings', |
||||
action: 'Custom RPC', |
||||
name: 'Error', |
||||
}, |
||||
customVariables: { |
||||
networkId: newRpc, |
||||
chainId, |
||||
}, |
||||
}) |
||||
const appendedRpc = `http://${newRpc}` |
||||
|
||||
if (validUrl.isWebUri(appendedRpc)) { |
||||
displayWarning(this.context.t('uriErrorMsg')) |
||||
} else { |
||||
displayWarning(this.context.t('invalidRPC')) |
||||
} |
||||
} |
||||
} |
||||
|
||||
renderMobileSync () { |
||||
const { t } = this.context |
||||
const { history } = this.props |
||||
//
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('syncWithMobile') }</span> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<Button |
||||
type="primary" |
||||
large |
||||
onClick={event => { |
||||
event.preventDefault() |
||||
history.push(MOBILE_SYNC_ROUTE) |
||||
}} |
||||
> |
||||
{ t('syncWithMobile') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderStateLogs () { |
||||
const { t } = this.context |
||||
const { displayWarning } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('stateLogs') }</span> |
||||
<span className="settings-page__content-description"> |
||||
{ t('stateLogsDescription') } |
||||
</span> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<Button |
||||
type="primary" |
||||
large |
||||
onClick={() => { |
||||
window.logStateString((err, result) => { |
||||
if (err) { |
||||
displayWarning(t('stateLogError')) |
||||
} else { |
||||
exportAsFile('MetaMask State Logs.json', result) |
||||
} |
||||
}) |
||||
}} |
||||
> |
||||
{ t('downloadStateLogs') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderResetAccount () { |
||||
const { t } = this.context |
||||
const { showResetAccountConfirmationModal } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('resetAccount') }</span> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<Button |
||||
type="secondary" |
||||
large |
||||
className="settings-tab__button--orange" |
||||
onClick={event => { |
||||
event.preventDefault() |
||||
this.context.metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Settings', |
||||
action: 'Reset Account', |
||||
name: 'Reset Account', |
||||
}, |
||||
}) |
||||
showResetAccountConfirmationModal() |
||||
}} |
||||
> |
||||
{ t('resetAccount') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderHexDataOptIn () { |
||||
const { t } = this.context |
||||
const { sendHexData, setHexDataFeatureFlag } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('showHexData') }</span> |
||||
<div className="settings-page__content-description"> |
||||
{ t('showHexDataDescription') } |
||||
</div> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<ToggleButton |
||||
value={sendHexData} |
||||
onToggle={value => setHexDataFeatureFlag(!value)} |
||||
activeLabel="" |
||||
inactiveLabel="" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderAdvancedGasInputInline () { |
||||
const { t } = this.context |
||||
const { advancedInlineGas, setAdvancedInlineGasFeatureFlag } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('showAdvancedGasInline') }</span> |
||||
<div className="settings-page__content-description"> |
||||
{ t('showAdvancedGasInlineDescription') } |
||||
</div> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<ToggleButton |
||||
value={advancedInlineGas} |
||||
onToggle={value => setAdvancedInlineGasFeatureFlag(!value)} |
||||
activeLabel="" |
||||
inactiveLabel="" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderShowConversionInTestnets () { |
||||
const { t } = this.context |
||||
const { |
||||
showFiatInTestnets, |
||||
setShowFiatConversionOnTestnetsPreference, |
||||
} = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('showFiatConversionInTestnets') }</span> |
||||
<div className="settings-page__content-description"> |
||||
{ t('showFiatConversionInTestnetsDescription') } |
||||
</div> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<ToggleButton |
||||
value={showFiatInTestnets} |
||||
onToggle={value => setShowFiatConversionOnTestnetsPreference(!value)} |
||||
activeLabel="" |
||||
inactiveLabel="" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderContent () { |
||||
const { warning } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__body"> |
||||
{ warning && <div className="settings-tab__error">{ warning }</div> } |
||||
{ this.renderStateLogs() } |
||||
{ this.renderMobileSync() } |
||||
{ this.renderNewRpcUrl() } |
||||
{ this.renderResetAccount() } |
||||
{ this.renderAdvancedGasInputInline() } |
||||
{ this.renderHexDataOptIn() } |
||||
{ this.renderShowConversionInTestnets() } |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
render () { |
||||
return this.renderContent() |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
import AdvancedTab from './advanced-tab.component' |
||||
import { compose } from 'recompose' |
||||
import { connect } from 'react-redux' |
||||
import { withRouter } from 'react-router-dom' |
||||
import { |
||||
updateAndSetCustomRpc, |
||||
displayWarning, |
||||
setFeatureFlag, |
||||
showModal, |
||||
setShowFiatConversionOnTestnetsPreference, |
||||
} from '../../../store/actions' |
||||
import {preferencesSelector} from '../../../selectors/selectors' |
||||
|
||||
const mapStateToProps = state => { |
||||
const { appState: { warning }, metamask } = state |
||||
const { |
||||
featureFlags: { |
||||
sendHexData, |
||||
advancedInlineGas, |
||||
} = {}, |
||||
} = metamask |
||||
const { showFiatInTestnets } = preferencesSelector(state) |
||||
|
||||
return { |
||||
warning, |
||||
sendHexData, |
||||
advancedInlineGas, |
||||
showFiatInTestnets, |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)), |
||||
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)), |
||||
displayWarning: warning => dispatch(displayWarning(warning)), |
||||
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })), |
||||
setAdvancedInlineGasFeatureFlag: shouldShow => dispatch(setFeatureFlag('advancedInlineGas', shouldShow)), |
||||
setShowFiatConversionOnTestnetsPreference: value => { |
||||
return dispatch(setShowFiatConversionOnTestnetsPreference(value)) |
||||
}, |
||||
} |
||||
} |
||||
|
||||
export default compose( |
||||
withRouter, |
||||
connect(mapStateToProps, mapDispatchToProps) |
||||
)(AdvancedTab) |
@ -0,0 +1 @@ |
||||
export { default } from './advanced-tab.container' |
@ -0,0 +1 @@ |
||||
export { default } from './security-tab.container' |
@ -0,0 +1,195 @@ |
||||
import React, { PureComponent } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import { exportAsFile } from '../../../helpers/utils/util' |
||||
import ToggleButton from 'react-toggle-button' |
||||
import { REVEAL_SEED_ROUTE } from '../../../helpers/constants/routes' |
||||
import Button from '../../../components/ui/button' |
||||
|
||||
export default class SecurityTab extends PureComponent { |
||||
static contextTypes = { |
||||
t: PropTypes.func, |
||||
metricsEvent: PropTypes.func, |
||||
} |
||||
|
||||
static propTypes = { |
||||
setPrivacyMode: PropTypes.func, |
||||
privacyMode: PropTypes.bool, |
||||
displayWarning: PropTypes.func, |
||||
revealSeedConfirmation: PropTypes.func, |
||||
showClearApprovalModal: PropTypes.func, |
||||
warning: PropTypes.string, |
||||
history: PropTypes.object, |
||||
mobileSync: PropTypes.bool, |
||||
participateInMetaMetrics: PropTypes.bool, |
||||
setParticipateInMetaMetrics: PropTypes.func, |
||||
} |
||||
|
||||
renderStateLogs () { |
||||
const { t } = this.context |
||||
const { displayWarning } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('stateLogs') }</span> |
||||
<span className="settings-page__content-description"> |
||||
{ t('stateLogsDescription') } |
||||
</span> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<Button |
||||
type="primary" |
||||
large |
||||
onClick={() => { |
||||
window.logStateString((err, result) => { |
||||
if (err) { |
||||
displayWarning(t('stateLogError')) |
||||
} else { |
||||
exportAsFile('MetaMask State Logs.json', result) |
||||
} |
||||
}) |
||||
}} |
||||
> |
||||
{ t('downloadStateLogs') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderClearApproval () { |
||||
const { t } = this.context |
||||
const { showClearApprovalModal } = this.props |
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('approvalData') }</span> |
||||
<span className="settings-page__content-description"> |
||||
{ t('approvalDataDescription') } |
||||
</span> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<Button |
||||
type="secondary" |
||||
large |
||||
className="settings-tab__button--orange" |
||||
onClick={event => { |
||||
event.preventDefault() |
||||
showClearApprovalModal() |
||||
}} |
||||
> |
||||
{ t('clearApprovalData') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderSeedWords () { |
||||
const { t } = this.context |
||||
const { history } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('revealSeedWords') }</span> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<Button |
||||
type="secondary" |
||||
large |
||||
onClick={event => { |
||||
event.preventDefault() |
||||
this.context.metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Settings', |
||||
action: 'Reveal Seed Phrase', |
||||
name: 'Reveal Seed Phrase', |
||||
}, |
||||
}) |
||||
history.push(REVEAL_SEED_ROUTE) |
||||
}} |
||||
> |
||||
{ t('revealSeedWords') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderPrivacyOptIn () { |
||||
const { t } = this.context |
||||
const { privacyMode, setPrivacyMode } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('privacyMode') }</span> |
||||
<div className="settings-page__content-description"> |
||||
{ t('privacyModeDescription') } |
||||
</div> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<ToggleButton |
||||
value={privacyMode} |
||||
onToggle={value => setPrivacyMode(!value)} |
||||
activeLabel="" |
||||
inactiveLabel="" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderMetaMetricsOptIn () { |
||||
const { t } = this.context |
||||
const { participateInMetaMetrics, setParticipateInMetaMetrics } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__content-row"> |
||||
<div className="settings-page__content-item"> |
||||
<span>{ t('participateInMetaMetrics') }</span> |
||||
<div className="settings-page__content-description"> |
||||
<span>{ t('participateInMetaMetricsDescription') }</span> |
||||
</div> |
||||
</div> |
||||
<div className="settings-page__content-item"> |
||||
<div className="settings-page__content-item-col"> |
||||
<ToggleButton |
||||
value={participateInMetaMetrics} |
||||
onToggle={value => setParticipateInMetaMetrics(!value)} |
||||
activeLabel="" |
||||
inactiveLabel="" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderContent () { |
||||
const { warning } = this.props |
||||
|
||||
return ( |
||||
<div className="settings-page__body"> |
||||
{ warning && <div className="settings-tab__error">{ warning }</div> } |
||||
{ this.renderPrivacyOptIn() } |
||||
{ this.renderClearApproval() } |
||||
{ this.renderSeedWords() } |
||||
{ this.renderMetaMetricsOptIn() } |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
render () { |
||||
return this.renderContent() |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
import SecurityTab from './security-tab.component' |
||||
import { compose } from 'recompose' |
||||
import { connect } from 'react-redux' |
||||
import { withRouter } from 'react-router-dom' |
||||
import { |
||||
displayWarning, |
||||
revealSeedConfirmation, |
||||
setFeatureFlag, |
||||
showModal, |
||||
setParticipateInMetaMetrics, |
||||
} from '../../../store/actions' |
||||
|
||||
const mapStateToProps = state => { |
||||
const { appState: { warning }, metamask } = state |
||||
const { |
||||
featureFlags: { |
||||
privacyMode, |
||||
} = {}, |
||||
participateInMetaMetrics, |
||||
} = metamask |
||||
|
||||
return { |
||||
warning, |
||||
privacyMode, |
||||
participateInMetaMetrics, |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
displayWarning: warning => dispatch(displayWarning(warning)), |
||||
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()), |
||||
setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)), |
||||
showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })), |
||||
setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), |
||||
} |
||||
} |
||||
|
||||
export default compose( |
||||
withRouter, |
||||
connect(mapStateToProps, mapDispatchToProps) |
||||
)(SecurityTab) |
Loading…
Reference in new issue