From 39b714542397277733134214c228403ae89d7d29 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Sat, 27 Apr 2019 06:59:55 -0230 Subject: Ensure home screen does not render if there are unapproved txs (#6501) * Ensure that the confirm screen renders before the home screen if there are unapproved txs. * Only render confirm screen before home screen on mount. --- .../confirm-transaction-base.component.js | 7 +++++-- ui/app/pages/home/home.component.js | 19 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) (limited to 'ui/app') diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js index 1cbe5951d..678f64844 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -99,15 +99,18 @@ export default class ConfirmTransactionBase extends Component { submitError: null, } - componentDidUpdate () { + componentDidUpdate (prevProps) { const { transactionStatus, showTransactionConfirmedModal, history, clearConfirmTransaction, } = this.props + const { transactionStatus: prevTxStatus } = prevProps + const statusUpdated = transactionStatus !== prevTxStatus + const txDroppedOrConfirmed = transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS - if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) { + if (statusUpdated && txDroppedOrConfirmed) { showTransactionConfirmedModal({ onSubmit: () => { clearConfirmTransaction() diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index 29d93a9fa..4d96c3131 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -23,21 +23,27 @@ export default class Home extends PureComponent { providerRequests: PropTypes.array, } + componentWillMount () { + const { + history, + unconfirmedTransactionsCount = 0, + } = this.props + + if (unconfirmedTransactionsCount > 0) { + history.push(CONFIRM_TRANSACTION_ROUTE) + } + } + componentDidMount () { const { history, suggestedTokens = {}, - unconfirmedTransactionsCount = 0, } = this.props // suggested new tokens if (Object.keys(suggestedTokens).length > 0) { history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE) } - - if (unconfirmedTransactionsCount > 0) { - history.push(CONFIRM_TRANSACTION_ROUTE) - } } render () { @@ -45,6 +51,7 @@ export default class Home extends PureComponent { forgottenPassword, seedWords, providerRequests, + history, } = this.props // seed words @@ -69,7 +76,7 @@ export default class Home extends PureComponent { query="(min-width: 576px)" render={() => } /> - + { !history.location.pathname.match(/^\/confirm-transaction/) ? : null } ) -- cgit From 00958894087a9af845f3746de6698130facd7654 Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Sun, 28 Apr 2019 23:18:03 -0700 Subject: Add subheader to all settings subviews (#6502) * Add subheader to all settings subviews * add margin right to subheader --- ui/app/pages/settings/index.scss | 26 +++++++++++++++++++++++--- ui/app/pages/settings/settings.component.js | 18 ++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) (limited to 'ui/app') diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss index 52208dc85..a19105bb4 100644 --- a/ui/app/pages/settings/index.scss +++ b/ui/app/pages/settings/index.scss @@ -22,6 +22,17 @@ } } + &__subheader { + padding: 16px 4px; + font-size: 20px; + border-bottom: 1px solid $alto; + margin-right: 24px; + + @media screen and (max-width: 575px) { + display: none; + } + } + &__back-button { display: none; @@ -58,9 +69,15 @@ flex: 1 1 auto; @media screen and (min-width: 576px) { - flex: 0 0 32%; + flex: 0 0 40%; max-width: 210px; - border-right: 1px solid $alto; + padding-top: 8px; + } + + .tab-bar__tab { + @media screen and (min-width: 576px) { + padding: 16px 24px 0; + } } } @@ -76,6 +93,10 @@ &__body { padding: 12px 24px; + + @media screen and (min-width: 576px) { + padding: 12px; + } } &__content-row { @@ -89,7 +110,6 @@ min-width: 0; display: flex; flex-direction: column; - padding: 0 5px; min-height: 71px; @media screen and (max-width: 575px) { diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js index 061e65060..fe799a6e8 100644 --- a/ui/app/pages/settings/settings.component.js +++ b/ui/app/pages/settings/settings.component.js @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import { Switch, Route, matchPath } from 'react-router-dom' +import { Switch, Route, matchPath, withRouter } from 'react-router-dom' import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' import { getEnvironmentType } from '../../../../app/scripts/lib/util' import TabBar from '../../components/app/tab-bar' @@ -25,7 +25,7 @@ const ROUTES_TO_I18N_KEYS = { [ABOUT_US_ROUTE]: 'about', } -export default class SettingsPage extends PureComponent { +class SettingsPage extends PureComponent { static propTypes = { location: PropTypes.object, history: PropTypes.object, @@ -75,6 +75,7 @@ export default class SettingsPage extends PureComponent { { this.renderTabs() }
+ { this.renderSubHeader() } { this.renderContent() }
@@ -82,6 +83,17 @@ export default class SettingsPage extends PureComponent { ) } + renderSubHeader () { + const { t } = this.context + const { location: { pathname } } = this.props + + return ( +
+ {t(ROUTES_TO_I18N_KEYS[pathname] || 'general')} +
+ ) + } + renderTabs () { const { history, location } = this.props const { t } = this.context @@ -135,3 +147,5 @@ export default class SettingsPage extends PureComponent { ) } } + +export default withRouter(SettingsPage) -- cgit From 4fea9d0cc2ec9c6914931d5e310665aca8e273b6 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 29 Apr 2019 03:48:40 -0230 Subject: Send metrics event from backend for on chain transaction failures (#6500) * Send metrics event from backend for on chain transaction failures * Passes state object to backEndMetaMetricsEvent, and adds getMetaMetricState selector --- .../ducks/confirm-transaction/confirm-transaction.duck.js | 2 +- ui/app/helpers/utils/metametrics.util.js | 4 ++-- ui/app/selectors/selectors.js | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) (limited to 'ui/app') diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js index 169c9d543..58b0ec8e8 100644 --- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js +++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js @@ -375,7 +375,7 @@ export function setTransactionToConfirm (transactionId) { dispatch(updateMethodData(methodData)) try { - const toSmartContract = await isSmartContractAddress(to) + const toSmartContract = await isSmartContractAddress(to || '') dispatch(updateToSmartContract(toSmartContract)) } catch (error) { log.error(error) diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index 5ae3e8937..62f5fd760 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -124,10 +124,10 @@ function composeUrl (config, permissionPreferences = {}) { numberOfTokens: customVariables && customVariables.numberOfTokens || numberOfTokens, numberOfAccounts: customVariables && customVariables.numberOfAccounts || numberOfAccounts, }) : '' - const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` + const url = configUrl || currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : '' const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' const rand = `&rand=${String(Math.random()).slice(2)}` - const pv_id = `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}` + const pv_id = (url || currentPath) && `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}` || '' const uid = metaMetricsId && !excludeMetaMetricsId ? `&uid=${metaMetricsId.slice(2, 18)}` : excludeMetaMetricsId diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 2d25aa156..ce02d067e 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -48,6 +48,7 @@ const selectors = { getNumberOfAccounts, getNumberOfTokens, isEthereumNetwork, + getMetaMetricState, } module.exports = selectors @@ -165,7 +166,7 @@ function getSelectedToken (state) { const tokens = state.metamask.tokens || [] const selectedTokenAddress = state.metamask.selectedTokenAddress const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0] - const sendToken = state.metamask.send.token + const sendToken = state.metamask.send && state.metamask.send.token return selectedToken || sendToken || null } @@ -314,3 +315,15 @@ function preferencesSelector ({ metamask }) { function getAdvancedInlineGasShown (state) { return Boolean(state.metamask.featureFlags.advancedInlineGas) } + +function getMetaMetricState (state) { + return { + network: getCurrentNetworkId(state), + activeCurrency: getSelectedAsset(state), + accountType: getAccountType(state), + metaMetricsId: state.metamask.metaMetricsId, + numberOfTokens: getNumberOfTokens(state), + numberOfAccounts: getNumberOfAccounts(state), + participateInMetaMetrics: state.metamask.participateInMetaMetrics, + } +} -- cgit From 12cfe8e543ebc0982f6dc087d28c7d56888ed666 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 1 May 2019 00:23:12 -0230 Subject: Fix binding of this for I18nProvider#tOrKey --- ui/app/helpers/higher-order-components/i18n-provider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui/app') diff --git a/ui/app/helpers/higher-order-components/i18n-provider.js b/ui/app/helpers/higher-order-components/i18n-provider.js index 0e34e17e0..298a12a28 100644 --- a/ui/app/helpers/higher-order-components/i18n-provider.js +++ b/ui/app/helpers/higher-order-components/i18n-provider.js @@ -19,7 +19,7 @@ class I18nProvider extends Component { return t(current, key, ...args) || t(en, key, ...args) || `[${key}]` }, tOrDefault: this.tOrDefault, - tOrKey (key, ...args) { + tOrKey: (key, ...args) => { return this.tOrDefault(key, key, ...args) }, } -- cgit From e6b9b5c5a9d25b70e9656a886b6d6053f160cd39 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 30 Apr 2019 23:46:50 -0230 Subject: Use tOrKey for actionKey in ConfirmTransactionBase --- .../confirm-transaction-base/confirm-transaction-base.component.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ui/app') diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js index 678f64844..3c4e6dcac 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -18,6 +18,7 @@ import AdvancedGasInputs from '../../components/app/gas-customization/advanced-g export default class ConfirmTransactionBase extends Component { static contextTypes = { t: PropTypes.func, + tOrKey: PropTypes.func.isRequired, metricsEvent: PropTypes.func, } @@ -546,7 +547,8 @@ export default class ConfirmTransactionBase extends Component { toName={toName} toAddress={toAddress} showEdit={onEdit && !isTxReprice} - action={this.context.t(actionKey) || getMethodName(name) || this.context.t('contractInteraction')} + // In the event that the key is falsy (and inherently invalid), use a fallback string + action={this.context.tOrKey(actionKey) || getMethodName(name) || this.context.t('contractInteraction')} title={title} titleComponent={this.renderTitleComponent()} subtitle={subtitle} -- cgit From 2845398c3d824e5da1830ba7905ffdbf8149cf9e Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 4 May 2019 01:32:05 +0800 Subject: Refactor ProviderApprovalController to use rpc and publicConfigStore (#6410) * Ensure home screen does not render if there are unapproved txs (#6501) * Ensure that the confirm screen renders before the home screen if there are unapproved txs. * Only render confirm screen before home screen on mount. * inpage - revert _metamask api to isEnabled isApproved isUnlocked --- .../provider-page-container.component.js | 13 ++++++------- .../pages/provider-approval/provider-approval.component.js | 10 +++++----- .../pages/provider-approval/provider-approval.container.js | 6 +++--- ui/app/store/actions.js | 12 ++++++------ 4 files changed, 20 insertions(+), 21 deletions(-) (limited to 'ui/app') diff --git a/ui/app/components/app/provider-page-container/provider-page-container.component.js b/ui/app/components/app/provider-page-container/provider-page-container.component.js index 910def2a3..1c655d404 100644 --- a/ui/app/components/app/provider-page-container/provider-page-container.component.js +++ b/ui/app/components/app/provider-page-container/provider-page-container.component.js @@ -5,12 +5,11 @@ import { PageContainerFooter } from '../../ui/page-container' export default class ProviderPageContainer extends PureComponent { static propTypes = { - approveProviderRequest: PropTypes.func.isRequired, + approveProviderRequestByOrigin: PropTypes.func.isRequired, + rejectProviderRequestByOrigin: PropTypes.func.isRequired, origin: PropTypes.string.isRequired, - rejectProviderRequest: PropTypes.func.isRequired, siteImage: PropTypes.string, siteTitle: PropTypes.string.isRequired, - tabID: PropTypes.string.isRequired, }; static contextTypes = { @@ -29,7 +28,7 @@ export default class ProviderPageContainer extends PureComponent { } onCancel = () => { - const { tabID, rejectProviderRequest } = this.props + const { origin, rejectProviderRequestByOrigin } = this.props this.context.metricsEvent({ eventOpts: { category: 'Auth', @@ -37,11 +36,11 @@ export default class ProviderPageContainer extends PureComponent { name: 'Canceled', }, }) - rejectProviderRequest(tabID) + rejectProviderRequestByOrigin(origin) } onSubmit = () => { - const { approveProviderRequest, tabID } = this.props + const { approveProviderRequestByOrigin, origin } = this.props this.context.metricsEvent({ eventOpts: { category: 'Auth', @@ -49,7 +48,7 @@ export default class ProviderPageContainer extends PureComponent { name: 'Confirmed', }, }) - approveProviderRequest(tabID) + approveProviderRequestByOrigin(origin) } render () { diff --git a/ui/app/pages/provider-approval/provider-approval.component.js b/ui/app/pages/provider-approval/provider-approval.component.js index 1f1d68da7..70d3d0007 100644 --- a/ui/app/pages/provider-approval/provider-approval.component.js +++ b/ui/app/pages/provider-approval/provider-approval.component.js @@ -4,9 +4,9 @@ import ProviderPageContainer from '../../components/app/provider-page-container' export default class ProviderApproval extends Component { static propTypes = { - approveProviderRequest: PropTypes.func.isRequired, + approveProviderRequestByOrigin: PropTypes.func.isRequired, + rejectProviderRequestByOrigin: PropTypes.func.isRequired, providerRequest: PropTypes.object.isRequired, - rejectProviderRequest: PropTypes.func.isRequired, }; static contextTypes = { @@ -14,13 +14,13 @@ export default class ProviderApproval extends Component { }; render () { - const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props + const { approveProviderRequestByOrigin, providerRequest, rejectProviderRequestByOrigin } = this.props return ( diff --git a/ui/app/pages/provider-approval/provider-approval.container.js b/ui/app/pages/provider-approval/provider-approval.container.js index d53c0ae4d..1e167ddb7 100644 --- a/ui/app/pages/provider-approval/provider-approval.container.js +++ b/ui/app/pages/provider-approval/provider-approval.container.js @@ -1,11 +1,11 @@ import { connect } from 'react-redux' import ProviderApproval from './provider-approval.component' -import { approveProviderRequest, rejectProviderRequest } from '../../store/actions' +import { approveProviderRequestByOrigin, rejectProviderRequestByOrigin } from '../../store/actions' function mapDispatchToProps (dispatch) { return { - approveProviderRequest: tabID => dispatch(approveProviderRequest(tabID)), - rejectProviderRequest: tabID => dispatch(rejectProviderRequest(tabID)), + approveProviderRequestByOrigin: origin => dispatch(approveProviderRequestByOrigin(origin)), + rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)), } } diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index f594d9002..e7b855119 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -343,8 +343,8 @@ var actions = { createCancelTransaction, createSpeedUpTransaction, - approveProviderRequest, - rejectProviderRequest, + approveProviderRequestByOrigin, + rejectProviderRequestByOrigin, clearApprovedOrigins, setFirstTimeFlowType, @@ -2680,15 +2680,15 @@ function setPendingTokens (pendingTokens) { } } -function approveProviderRequest (tabID) { +function approveProviderRequestByOrigin (origin) { return (dispatch) => { - background.approveProviderRequest(tabID) + background.approveProviderRequestByOrigin(origin) } } -function rejectProviderRequest (tabID) { +function rejectProviderRequestByOrigin (origin) { return (dispatch) => { - background.rejectProviderRequest(tabID) + background.rejectProviderRequestByOrigin(origin) } } -- cgit From 6aa889280dbbb5b01e3e1aa22ecaf54f9033d34f Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 6 May 2019 10:55:18 -0230 Subject: Add suffix to title text only when it exists (#6546) --- ui/app/components/ui/currency-display/currency-display.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui/app') diff --git a/ui/app/components/ui/currency-display/currency-display.component.js b/ui/app/components/ui/currency-display/currency-display.component.js index 04dd89892..c15668da3 100644 --- a/ui/app/components/ui/currency-display/currency-display.component.js +++ b/ui/app/components/ui/currency-display/currency-display.component.js @@ -23,7 +23,7 @@ export default class CurrencyDisplay extends PureComponent { render () { const { className, displayValue, prefix, prefixComponent, style, suffix, hideTitle } = this.props const text = `${prefix || ''}${displayValue}` - const title = `${text} ${suffix}` + const title = suffix ? `${text} ${suffix}` : text return (
Date: Mon, 6 May 2019 15:04:16 -0230 Subject: Skip null and undefined keys when translating via context (#6543) * i18n: Don't translate null or undefined keys * Add JSDoc for I18nProvider#t context fn --- ui/app/helpers/higher-order-components/i18n-provider.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'ui/app') diff --git a/ui/app/helpers/higher-order-components/i18n-provider.js b/ui/app/helpers/higher-order-components/i18n-provider.js index 298a12a28..5a6650147 100644 --- a/ui/app/helpers/higher-order-components/i18n-provider.js +++ b/ui/app/helpers/higher-order-components/i18n-provider.js @@ -15,7 +15,17 @@ class I18nProvider extends Component { const { localeMessages } = this.props const { current, en } = localeMessages return { + /** + * Returns a localized message for the given key + * @param {string} key The message key + * @param {string[]} args A list of message substitution replacements + * @return {string|undefined|null} The localized message if available + */ t (key, ...args) { + if (key === undefined || key === null) { + return key + } + return t(current, key, ...args) || t(en, key, ...args) || `[${key}]` }, tOrDefault: this.tOrDefault, -- cgit From 581128503c161bc3b569ca5d87e4eea8b0d15150 Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Tue, 7 May 2019 08:03:27 -0700 Subject: Allow dragging seed phrase during Confirm Seed Phrase (#6557) * Add basic drag and drop functionality * Refactor seed phrase data structure * Insert to list when drop * Save before refactor * Finish DND * Fix linter * update package-lock.json * Address styling feedbacks * Add box shadow on hover * Finish adding unit tests * Remove describe.only --- .../confirm-seed-phrase.component.js | 187 +++++++++++++++++---- .../confirm-seed-phrase.state.js | 41 ----- .../draggable-seed.component.js | 126 ++++++++++++++ .../seed-phrase/confirm-seed-phrase/index.scss | 93 +++++++--- .../seed-phrase/seed-phrase.component.js | 76 +++++---- .../tests/confirm-seed-phrase-component.test.js | 169 +++++++++++++++++++ 6 files changed, 565 insertions(+), 127 deletions(-) delete mode 100644 ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js create mode 100644 ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js create mode 100644 ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js (limited to 'ui/app') diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index f3bfc3171..04fe651e6 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -8,7 +8,9 @@ import { INITIALIZE_SEED_PHRASE_ROUTE, } from '../../../../helpers/constants/routes' import { exportAsFile } from '../../../../helpers/utils/util' -import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state' +import DraggableSeed from './draggable-seed.component' + +const EMPTY_SEEDS = Array(12).fill(null) export default class ConfirmSeedPhrase extends PureComponent { static contextTypes = { @@ -27,10 +29,32 @@ export default class ConfirmSeedPhrase extends PureComponent { } state = { - selectedSeedWords: [], + selectedSeedIndices: [], shuffledSeedWords: [], - // Hash of shuffledSeedWords index {Number} to selectedSeedWords index {Number} - selectedSeedWordsHash: {}, + pendingSeedIndices: [], + draggingSeedIndex: -1, + hoveringIndex: -1, + isDragging: false, + } + + shouldComponentUpdate (nextProps, nextState, nextContext) { + const { seedPhrase } = this.props + const { + selectedSeedIndices, + shuffledSeedWords, + pendingSeedIndices, + draggingSeedIndex, + hoveringIndex, + isDragging, + } = this.state + + return seedPhrase !== nextProps.seedPhrase || + draggingSeedIndex !== nextState.draggingSeedIndex || + isDragging !== nextState.isDragging || + hoveringIndex !== nextState.hoveringIndex || + selectedSeedIndices.join(' ') !== nextState.selectedSeedIndices.join(' ') || + shuffledSeedWords.join(' ') !== nextState.shuffledSeedWords.join(' ') || + pendingSeedIndices.join(' ') !== nextState.pendingSeedIndices.join(' ') } componentDidMount () { @@ -39,6 +63,26 @@ export default class ConfirmSeedPhrase extends PureComponent { this.setState({ shuffledSeedWords }) } + setDraggingSeedIndex = draggingSeedIndex => this.setState({ draggingSeedIndex }) + + setHoveringIndex = hoveringIndex => this.setState({ hoveringIndex }) + + onDrop = targetIndex => { + const { + selectedSeedIndices, + draggingSeedIndex, + } = this.state + + const indices = insert(selectedSeedIndices, draggingSeedIndex, targetIndex, true) + + this.setState({ + selectedSeedIndices: indices, + pendingSeedIndices: indices, + draggingSeedIndex: -1, + hoveringIndex: -1, + }) + } + handleExport = () => { exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain') } @@ -65,23 +109,34 @@ export default class ConfirmSeedPhrase extends PureComponent { } handleSelectSeedWord = (word, shuffledIndex) => { - this.setState(selectSeedWord(word, shuffledIndex)) + this.setState({ + selectedSeedIndices: [...this.state.selectedSeedIndices, shuffledIndex], + pendingSeedIndices: [...this.state.pendingSeedIndices, shuffledIndex], + }) } handleDeselectSeedWord = shuffledIndex => { - this.setState(deselectSeedWord(shuffledIndex)) + this.setState({ + selectedSeedIndices: this.state.selectedSeedIndices.filter(i => shuffledIndex !== i), + pendingSeedIndices: this.state.pendingSeedIndices.filter(i => shuffledIndex !== i), + }) } isValid () { const { seedPhrase } = this.props - const { selectedSeedWords } = this.state + const { selectedSeedIndices, shuffledSeedWords } = this.state + const selectedSeedWords = selectedSeedIndices.map(i => shuffledSeedWords[i]) return seedPhrase === selectedSeedWords.join(' ') } render () { const { t } = this.context const { history } = this.props - const { selectedSeedWords, shuffledSeedWords, selectedSeedWordsHash } = this.state + const { + selectedSeedIndices, + shuffledSeedWords, + draggingSeedIndex, + } = this.state return (
@@ -102,31 +157,30 @@ export default class ConfirmSeedPhrase extends PureComponent {
{ t('selectEachPhrase') }
-
- { - selectedSeedWords.map((word, index) => ( -
- { word } -
- )) - } +
-1, + })} + > + { this.renderPendingSeeds() } + { this.renderSelectedSeeds() }
{ shuffledSeedWords.map((word, index) => { - const isSelected = index in selectedSeedWordsHash + const isSelected = selectedSeedIndices.includes(index) return ( -
{ if (!isSelected) { this.handleSelectSeedWord(word, index) @@ -134,9 +188,8 @@ export default class ConfirmSeedPhrase extends PureComponent { this.handleDeselectSeedWord(index) } }} - > - { word } -
+ word={word} + /> ) }) } @@ -152,4 +205,80 @@ export default class ConfirmSeedPhrase extends PureComponent {
) } + + renderSelectedSeeds () { + const { shuffledSeedWords, selectedSeedIndices, draggingSeedIndex } = this.state + return EMPTY_SEEDS.map((_, index) => { + const seedIndex = selectedSeedIndices[index] + const word = shuffledSeedWords[seedIndex] + + return ( + + ) + }) + } + + renderPendingSeeds () { + const { + pendingSeedIndices, + shuffledSeedWords, + draggingSeedIndex, + hoveringIndex, + } = this.state + + const indices = insert(pendingSeedIndices, draggingSeedIndex, hoveringIndex) + + return EMPTY_SEEDS.map((_, index) => { + const seedIndex = indices[index] + const word = shuffledSeedWords[seedIndex] + + return ( + + ) + }) + } +} + +function insert (list, value, target, removeOld) { + let nextList = [...list] + + if (typeof list[target] === 'number') { + nextList = [...list.slice(0, target), value, ...list.slice(target)] + } + + if (removeOld) { + nextList = nextList.filter((seed, i) => { + return seed !== value || i === target + }) + } + + if (nextList.length > 12) { + nextList.pop() + } + + return nextList } diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js deleted file mode 100644 index f2476fc5c..000000000 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js +++ /dev/null @@ -1,41 +0,0 @@ -export function selectSeedWord (word, shuffledIndex) { - return function update (state) { - const { selectedSeedWords, selectedSeedWordsHash } = state - const nextSelectedIndex = selectedSeedWords.length - - return { - selectedSeedWords: [ ...selectedSeedWords, word ], - selectedSeedWordsHash: { ...selectedSeedWordsHash, [shuffledIndex]: nextSelectedIndex }, - } - } -} - -export function deselectSeedWord (shuffledIndex) { - return function update (state) { - const { - selectedSeedWords: prevSelectedSeedWords, - selectedSeedWordsHash: prevSelectedSeedWordsHash, - } = state - - const selectedSeedWords = [...prevSelectedSeedWords] - const indexToRemove = prevSelectedSeedWordsHash[shuffledIndex] - selectedSeedWords.splice(indexToRemove, 1) - const selectedSeedWordsHash = Object.keys(prevSelectedSeedWordsHash).reduce((acc, index) => { - const output = { ...acc } - const selectedSeedWordIndex = prevSelectedSeedWordsHash[index] - - if (selectedSeedWordIndex < indexToRemove) { - output[index] = selectedSeedWordIndex - } else if (selectedSeedWordIndex > indexToRemove) { - output[index] = selectedSeedWordIndex - 1 - } - - return output - }, {}) - - return { - selectedSeedWords, - selectedSeedWordsHash, - } - } -} diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js new file mode 100644 index 000000000..97dbd2a4b --- /dev/null +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js @@ -0,0 +1,126 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import { DragSource, DropTarget } from 'react-dnd' + +class DraggableSeed extends Component { + + static propTypes = { + // React DnD Props + connectDragSource: PropTypes.func.isRequired, + connectDropTarget: PropTypes.func.isRequired, + isDragging: PropTypes.bool, + isOver: PropTypes.bool, + canDrop: PropTypes.bool, + // Own Props + onClick: PropTypes.func.isRequired, + setHoveringIndex: PropTypes.func.isRequired, + index: PropTypes.number, + draggingSeedIndex: PropTypes.number, + word: PropTypes.string, + className: PropTypes.string, + selected: PropTypes.bool, + droppable: PropTypes.bool, + } + + static defaultProps = { + className: '', + onClick () {}, + } + + componentWillReceiveProps (nextProps, nextContext) { + const { isOver, setHoveringIndex } = this.props + if (isOver && !nextProps.isOver) { + setHoveringIndex(-1) + } + } + + render () { + const { + connectDragSource, + connectDropTarget, + isDragging, + index, + word, + selected, + className, + onClick, + isOver, + canDrop, + } = this.props + + return connectDropTarget(connectDragSource( +
+ { word } +
+ )) + } +} + +const SEEDWORD = 'SEEDWORD' + +const seedSource = { + beginDrag (props) { + setTimeout(() => props.setDraggingSeedIndex(props.seedIndex), 0) + return { + seedIndex: props.seedIndex, + word: props.word, + } + }, + canDrag (props) { + return props.draggable + }, + endDrag (props, monitor) { + const dropTarget = monitor.getDropResult() + + if (!dropTarget) { + setTimeout(() => props.setDraggingSeedIndex(-1), 0) + return + } + + props.onDrop(dropTarget.targetIndex) + }, +} + +const seedTarget = { + drop (props) { + return { + targetIndex: props.index, + } + }, + canDrop (props) { + return props.droppable + }, + hover (props) { + props.setHoveringIndex(props.index) + }, +} + +const collectDrag = (connect, monitor) => { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + } +} + +const collectDrop = (connect, monitor) => { + return { + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + } +} + +export default DropTarget(SEEDWORD, seedTarget, collectDrop)(DragSource(SEEDWORD, seedSource, collectDrag)(DraggableSeed)) + + diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss index 93137618c..f025a503f 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss @@ -3,37 +3,58 @@ margin-bottom: 12px; } - &__selected-seed-words { - min-height: 190px; - max-width: 496px; - border: 1px solid #CDCDCD; - border-radius: 6px; - background-color: $white; - margin: 24px 0 36px; - padding: 12px; - } - &__shuffled-seed-words { - max-width: 496px; + max-width: 575px; } &__seed-word { - display: inline-block; - color: #5B5D67; - background-color: #E7E7E7; + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + justify-content: center; padding: 8px 18px; - min-width: 64px; + width: 128px; + height: 41px; margin: 4px; text-align: center; + border-radius: 4px; + cursor: move; + + &--shuffled { + cursor: pointer; + margin: 6px; + } &--selected { - background-color: #85D1CC; color: $white; } - &--shuffled { - cursor: pointer; - margin: 6px; + &--dragging { + margin: 0; + } + + &--empty { + background-color: transparent; + border-color: transparent; + cursor: default; + + &:hover, + &:active { + background-color: transparent; + border-color: transparent; + cursor: default; + box-shadow: none !important; + } + } + + &--hidden { + display: none !important; + } + + &--drop-hover { + background-color: transparent; + border-color: transparent; + color: transparent; } @media screen and (max-width: 575px) { @@ -42,7 +63,37 @@ } } - button { - margin-top: 0xp; + &__selected-seed-words { + display: flex; + flex-flow: row wrap; + min-height: 161px; + max-width: 575px; + border: 1px solid #CDCDCD; + border-radius: 6px; + background-color: $white; + margin: 24px 0 36px; + padding: 12px; + + &__pending-seed { + display: none; + } + + &__selected-seed { + display: inline-flex; + + &:hover { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25); + } + } + + &--dragging { + .confirm-seed-phrase__selected-seed-words__pending-seed { + display: inline-flex; + } + + .confirm-seed-phrase__selected-seed-words__selected-seed { + display: none; + } + } } } diff --git a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js index 9a9f84049..0b19af18c 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js @@ -8,6 +8,8 @@ import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE, DEFAULT_ROUTE, } from '../../../helpers/constants/routes' +import HTML5Backend from 'react-dnd-html5-backend' +import {DragDropContextProvider} from 'react-dnd' export default class SeedPhrase extends PureComponent { static propTypes = { @@ -28,43 +30,45 @@ export default class SeedPhrase extends PureComponent { const { seedPhrase } = this.props return ( -
-
- - + +
+
+ + +
+ + ( + + )} + /> + ( + + )} + /> +
- - ( - - )} - /> - ( - - )} - /> - -
+ ) } } diff --git a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js new file mode 100644 index 000000000..8339a6f6f --- /dev/null +++ b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js @@ -0,0 +1,169 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import ConfirmSeedPhrase from '../confirm-seed-phrase/confirm-seed-phrase.component' + +function shallowRender (props = {}, context = {}) { + return shallow( + , + { + context: { + t: str => str + '_t', + ...context, + }, + } + ) +} + +describe('ConfirmSeedPhrase Component', () => { + it('should render correctly', () => { + const root = shallowRender({ + seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬', + }) + + assert.equal( + root.find('.confirm-seed-phrase__seed-word--shuffled').length, + 12, + 'should render 12 seed phrases' + ) + }) + + it('should add/remove selected on click', () => { + const metricsEventSpy = sinon.spy() + const pushSpy = sinon.spy() + const root = shallowRender( + { + seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬', + history: { push: pushSpy }, + }, + { + metricsEvent: metricsEventSpy, + } + ) + + const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled') + + // Click on 3 seeds to add to selected + seeds.at(0).simulate('click') + seeds.at(1).simulate('click') + seeds.at(2).simulate('click') + + assert.deepEqual( + root.state().selectedSeedIndices, + [0, 1, 2], + 'should add seed phrase to selected on click', + ) + + // Click on a selected seed to remove + root.state() + root.update() + root.state() + root.find('.confirm-seed-phrase__seed-word--shuffled').at(1).simulate('click') + assert.deepEqual( + root.state().selectedSeedIndices, + [0, 2], + 'should remove seed phrase from selected when click again', + ) + }) + + it('should render correctly on hover', () => { + const metricsEventSpy = sinon.spy() + const pushSpy = sinon.spy() + const root = shallowRender( + { + seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬', + history: { push: pushSpy }, + }, + { + metricsEvent: metricsEventSpy, + } + ) + + const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled') + + // Click on 3 seeds to add to selected + seeds.at(0).simulate('click') + seeds.at(1).simulate('click') + seeds.at(2).simulate('click') + + // Dragging Seed # 2 to 0 placeth + root.instance().setDraggingSeedIndex(2) + root.instance().setHoveringIndex(0) + + root.update() + + const pendingSeeds = root.find('.confirm-seed-phrase__selected-seed-words__pending-seed') + + assert.equal(pendingSeeds.at(0).props().seedIndex, 2) + assert.equal(pendingSeeds.at(1).props().seedIndex, 0) + assert.equal(pendingSeeds.at(2).props().seedIndex, 1) + }) + + it('should insert seed in place on drop', () => { + const metricsEventSpy = sinon.spy() + const pushSpy = sinon.spy() + const root = shallowRender( + { + seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬', + history: { push: pushSpy }, + }, + { + metricsEvent: metricsEventSpy, + } + ) + + const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled') + + // Click on 3 seeds to add to selected + seeds.at(0).simulate('click') + seeds.at(1).simulate('click') + seeds.at(2).simulate('click') + + // Drop Seed # 2 to 0 placeth + root.instance().setDraggingSeedIndex(2) + root.instance().setHoveringIndex(0) + root.instance().onDrop(0) + + root.update() + + assert.deepEqual(root.state().selectedSeedIndices, [2, 0, 1]) + assert.deepEqual(root.state().pendingSeedIndices, [2, 0, 1]) + }) + + it('should submit correctly', () => { + const originalSeed = ['鼠', '牛', '虎', '兔', '龍', '蛇', '馬', '羊', '猴', '雞', '狗', '豬'] + const metricsEventSpy = sinon.spy() + const pushSpy = sinon.spy() + const root = shallowRender( + { + seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬', + history: { push: pushSpy }, + }, + { + metricsEvent: metricsEventSpy, + } + ) + + const shuffled = root.state().shuffledSeedWords + const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled') + + + originalSeed.forEach(seed => { + const seedIndex = shuffled.findIndex(s => s === seed) + seeds.at(seedIndex).simulate('click') + }) + + root.update() + + root.find('.first-time-flow__button').simulate('click') + assert.deepEqual(metricsEventSpy.args[0][0], { + eventOpts: { + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Verify Complete', + }, + }) + assert.equal(pushSpy.args[0][0], '/initialize/end-of-flow') + }) +}) -- cgit From d730da8caadfd1e893c342577c079adbbc7f2164 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 7 May 2019 13:42:14 -0230 Subject: Use metricsEvent in AmountMaxButton component --- .../amount-max-button/amount-max-button.component.js | 13 +++++++++---- .../tests/amount-max-button-component.test.js | 7 ++++++- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'ui/app') diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index f17137c1e..e256d1442 100644 --- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -15,6 +15,7 @@ export default class AmountMaxButton extends Component { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } setMaxAmount () { @@ -35,11 +36,15 @@ export default class AmountMaxButton extends Component { } onMaxClick = (event) => { - const { setMaxModeTo, selectedToken } = this.props + const { setMaxModeTo } = this.props + const { metricsEvent } = this.context - fetch('https://chromeextensionmm.innocraft.cloud/piwik.php?idsite=1&rec=1&e_c=send&e_a=amountMax&e_n=' + (selectedToken ? 'token' : 'eth'), { - 'headers': {}, - 'method': 'GET', + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Edit Screen', + name: 'Clicked "Amount Max"', + }, }) event.preventDefault() diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js index b04d3897f..a6cb29d4c 100644 --- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js @@ -26,7 +26,12 @@ describe('AmountMaxButton Component', function () { setAmountToMax={propsMethodSpies.setAmountToMax} setMaxModeTo={propsMethodSpies.setMaxModeTo} tokenBalance={'mockTokenBalance'} - />, { context: { t: str => str + '_t' } }) + />, { + context: { + t: str => str + '_t', + metricsEvent: () => {}, + }, + }) instance = wrapper.instance() }) -- cgit From 0497d209b2adb4ac026a54159069fd44aaace9f7 Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Wed, 8 May 2019 10:34:56 -0700 Subject: Remove KNOWN_ADDRESS_ERROR from error objects (#6578) * Remove KNOWN_ADDRESS_ERROR from error objects * Update test comments --- .../pages/send/send-content/send-to-row/send-to-row.utils.js | 3 +-- .../send-content/send-to-row/tests/send-to-row-utils.test.js | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'ui/app') diff --git a/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js index d0a43f086..200a2e26a 100644 --- a/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js @@ -17,9 +17,8 @@ function getToErrorObject (to, toError = null, hasHexData = false, tokens = [], } } else if (!isValidAddress(to, network) && !toError) { toError = isEthNetwork(network) ? INVALID_RECIPIENT_ADDRESS_ERROR : INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR - } else if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) { - toError = KNOWN_RECIPIENT_ADDRESS_ERROR } + return { to: toError } } diff --git a/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js index f29f5efec..f8a6dd96f 100644 --- a/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js +++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js @@ -55,9 +55,9 @@ describe('send-to-row utils', () => { }) }) - it('should return a known address recipient if to is truthy but part of state tokens', () => { + it('should return null if to is truthy but part of state tokens', () => { assert.deepEqual(getToErrorObject('0xabc123', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), { - to: KNOWN_RECIPIENT_ADDRESS_ERROR, + to: null, }) }) @@ -67,14 +67,14 @@ describe('send-to-row utils', () => { }) }) - it('should return a known address recipient if to is truthy but part of contract metadata', () => { + it('should return null if to is truthy but part of contract metadata', () => { assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), { - to: KNOWN_RECIPIENT_ADDRESS_ERROR, + to: null, }) }) it('should null if to is truthy part of contract metadata but selectedToken falsy', () => { assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), { - to: KNOWN_RECIPIENT_ADDRESS_ERROR, + to: null, }) }) }) -- cgit From 56ed189aeb4ebc8e5dff7a6886e07791ce6569bb Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Wed, 8 May 2019 11:57:21 -0700 Subject: Auto logout after specific time (#6558) * Add i18n strings * Finish Auto timeout * Fix linter * Fix copies * Add unit test to Advanced Tab component * Add back actions and container * Add basic test to ensure container completeness * No zero, fix linters * restrict negative in input --- ui/app/pages/routes/index.js | 31 +++++++++++++-- .../advanced-tab/advanced-tab.component.js | 45 +++++++++++++++++++++ .../advanced-tab/advanced-tab.container.js | 11 ++++-- .../tests/advanced-tab-component.test.js | 44 +++++++++++++++++++++ .../tests/advanced-tab-container.test.js | 46 ++++++++++++++++++++++ ui/app/store/actions.js | 5 +++ 6 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js create mode 100644 ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js (limited to 'ui/app') diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index e38a6d6ce..7ff741405 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -3,9 +3,10 @@ import PropTypes from 'prop-types' import { connect } from 'react-redux' import { Route, Switch, withRouter, matchPath } from 'react-router-dom' import { compose } from 'recompose' -import actions from '../../store/actions' +import actions, {hideSidebar, hideWarning, lockMetamask} from '../../store/actions' import log from 'loglevel' -import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selectors' +import IdleTimer from 'react-idle-timer' +import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors' // init import FirstTimeFlow from '../first-time-flow' @@ -98,7 +99,9 @@ class Routes extends Component { } renderRoutes () { - return ( + const { autoLogoutTimeLimit, lockMetamask } = this.props + + const routes = ( @@ -116,6 +119,19 @@ class Routes extends Component { ) + + if (autoLogoutTimeLimit > 0) { + return ( + + {routes} + + ) + } + + return routes } onInitializationUnlockPage () { @@ -322,6 +338,7 @@ Routes.propTypes = { networkDropdownOpen: PropTypes.bool, showNetworkDropdown: PropTypes.func, hideNetworkDropdown: PropTypes.func, + lockMetamask: PropTypes.func, history: PropTypes.object, location: PropTypes.object, dispatch: PropTypes.func, @@ -344,6 +361,7 @@ Routes.propTypes = { t: PropTypes.func, providerId: PropTypes.string, providerRequests: PropTypes.array, + autoLogoutTimeLimit: PropTypes.number, } function mapStateToProps (state) { @@ -358,6 +376,7 @@ function mapStateToProps (state) { } = appState const accounts = getMetaMaskAccounts(state) + const { autoLogoutTimeLimit = 0 } = preferencesSelector(state) const { identities, @@ -409,6 +428,7 @@ function mapStateToProps (state) { Qr: state.appState.Qr, welcomeScreenSeen: state.metamask.welcomeScreenSeen, providerId: getNetworkIdentifier(state), + autoLogoutTimeLimit, // state needed to get account dropdown temporarily rendering from app bar identities, @@ -427,6 +447,11 @@ function mapDispatchToProps (dispatch, ownProps) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), + lockMetamask: () => { + dispatch(lockMetamask()) + dispatch(hideWarning()) + dispatch(hideSidebar()) + }, } } diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js index 14b9daae6..8d70cd2df 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js @@ -24,6 +24,8 @@ export default class AdvancedTab extends PureComponent { setAdvancedInlineGasFeatureFlag: PropTypes.func, advancedInlineGas: PropTypes.bool, showFiatInTestnets: PropTypes.bool, + autoLogoutTimeLimit: PropTypes.number, + setAutoLogoutTimeLimit: PropTypes.func.isRequired, setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired, } @@ -355,6 +357,48 @@ export default class AdvancedTab extends PureComponent { ) } + renderAutoLogoutTimeLimit () { + const { t } = this.context + const { + autoLogoutTimeLimit, + setAutoLogoutTimeLimit, + } = this.props + + return ( +
+
+ { t('autoLogoutTimeLimit') } +
+ { t('autoLogoutTimeLimitDescription') } +
+
+
+
+ this.setState({ autoLogoutTimeLimit: Math.max(Number(e.target.value), 0) })} + fullWidth + margin="dense" + min={0} + /> + +
+
+
+ ) + } + renderContent () { const { warning } = this.props @@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent { { this.renderAdvancedGasInputInline() } { this.renderHexDataOptIn() } { this.renderShowConversionInTestnets() } + { this.renderAutoLogoutTimeLimit() }
) } diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js index 69d7e07e6..bcac55f5e 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js @@ -8,10 +8,11 @@ import { setFeatureFlag, showModal, setShowFiatConversionOnTestnetsPreference, + setAutoLogoutTimeLimit, } from '../../../store/actions' import {preferencesSelector} from '../../../selectors/selectors' -const mapStateToProps = state => { +export const mapStateToProps = state => { const { appState: { warning }, metamask } = state const { featureFlags: { @@ -19,17 +20,18 @@ const mapStateToProps = state => { advancedInlineGas, } = {}, } = metamask - const { showFiatInTestnets } = preferencesSelector(state) + const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state) return { warning, sendHexData, advancedInlineGas, showFiatInTestnets, + autoLogoutTimeLimit, } } -const mapDispatchToProps = dispatch => { +export const mapDispatchToProps = dispatch => { return { setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)), setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)), @@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => { setShowFiatConversionOnTestnetsPreference: value => { return dispatch(setShowFiatConversionOnTestnetsPreference(value)) }, + setAutoLogoutTimeLimit: value => { + return dispatch(setAutoLogoutTimeLimit(value)) + }, } } diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js new file mode 100644 index 000000000..f81329533 --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js @@ -0,0 +1,44 @@ +import React from 'react' +import assert from 'assert' +import sinon from 'sinon' +import { shallow } from 'enzyme' +import AdvancedTab from '../advanced-tab.component' +import TextField from '../../../../components/ui/text-field' + +describe('AdvancedTab Component', () => { + it('should render correctly', () => { + const root = shallow( + , + { + context: { + t: s => `_${s}`, + }, + } + ) + + assert.equal(root.find('.settings-page__content-row').length, 8) + }) + + it('should update autoLogoutTimeLimit', () => { + const setAutoLogoutTimeLimitSpy = sinon.spy() + const root = shallow( + , + { + context: { + t: s => `_${s}`, + }, + } + ) + + const autoTimeout = root.find('.settings-page__content-row').last() + const textField = autoTimeout.find(TextField) + + textField.props().onChange({ target: { value: 1440 } }) + assert.equal(root.state().autoLogoutTimeLimit, 1440) + + autoTimeout.find('button').simulate('click') + assert.equal(setAutoLogoutTimeLimitSpy.args[0][0], 1440) + }) +}) diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js new file mode 100644 index 000000000..62122073d --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js @@ -0,0 +1,46 @@ +import assert from 'assert' +import { mapStateToProps, mapDispatchToProps } from '../advanced-tab.container' + +const defaultState = { + appState: { + warning: null, + }, + metamask: { + featureFlags: { + sendHexData: false, + advancedInlineGas: false, + }, + preferences: { + autoLogoutTimeLimit: 0, + showFiatInTestnets: false, + useNativeCurrencyAsPrimaryCurrency: true, + }, + }, +} + +describe('AdvancedTab Container', () => { + it('should map state to props correctly', () => { + const props = mapStateToProps(defaultState) + const expected = { + warning: null, + sendHexData: false, + advancedInlineGas: false, + showFiatInTestnets: false, + autoLogoutTimeLimit: 0, + } + + assert.deepEqual(props, expected) + }) + + it('should map dispatch to props correctly', () => { + const props = mapDispatchToProps(() => 'mockDispatch') + + assert.ok(typeof props.setHexDataFeatureFlag === 'function') + assert.ok(typeof props.setRpcTarget === 'function') + assert.ok(typeof props.displayWarning === 'function') + assert.ok(typeof props.showResetAccountConfirmationModal === 'function') + assert.ok(typeof props.setAdvancedInlineGasFeatureFlag === 'function') + assert.ok(typeof props.setShowFiatConversionOnTestnetsPreference === 'function') + assert.ok(typeof props.setAutoLogoutTimeLimit === 'function') + }) +}) diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index e7b855119..f2a9ed08f 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -316,6 +316,7 @@ var actions = { UPDATE_PREFERENCES: 'UPDATE_PREFERENCES', setUseNativeCurrencyAsPrimaryCurrencyPreference, setShowFiatConversionOnTestnetsPreference, + setAutoLogoutTimeLimit, // Migration of users to new UI setCompletedUiMigration, @@ -2439,6 +2440,10 @@ function setShowFiatConversionOnTestnetsPreference (value) { return setPreference('showFiatInTestnets', value) } +function setAutoLogoutTimeLimit (value) { + return setPreference('autoLogoutTimeLimit', value) +} + function setCompletedOnboarding () { return async dispatch => { dispatch(actions.showLoadingIndication()) -- cgit From 094e4cf555c698bfef50ca6679cd1e98f4ea9aa1 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 8 May 2019 17:21:33 -0230 Subject: Check for unused function arguments (#6583) * eslint: Check for unused function arguments * eslint: Ignore unused '_' in argument list Also allow any number of '_' e.g., '__' or '___' which is to be used sparingly * Remove and rename unused arguments --- ui/app/components/app/account-panel.js | 17 ++------ ui/app/components/app/bn-as-decimal-input.js | 2 +- ui/app/components/app/ens-input.js | 2 +- .../advanced-gas-inputs.component.js | 2 +- .../advanced-gas-inputs.container.js | 4 +- .../advanced-tab-content.component.js | 3 +- .../gas-modal-page-container.component.js | 2 - .../gas-price-button-group.component.js | 2 +- .../gas-price-chart/gas-price-chart.utils.js | 6 +-- .../tests/gas-price-chart.component.test.js | 2 +- .../components/app/modals/deposit-ether-modal.js | 2 +- .../app/modals/export-private-key-modal.js | 4 +- .../metametrics-opt-in-modal.container.js | 2 +- .../app/modals/qr-scanner/qr-scanner.component.js | 2 +- .../reject-transactions.container.js | 2 +- ui/app/components/app/token-cell.js | 2 +- ...-preferenced-currency-display.container.test.js | 2 +- .../user-preferenced-currency-display.container.js | 2 +- ui/app/components/ui/alert/index.js | 4 +- .../tests/currency-display.container.test.js | 2 +- .../tests/currency-input.container.test.js | 2 +- .../tests/token-input.container.test.js | 2 +- .../ui/unit-input/unit-input.component.js | 2 +- .../confirm-transaction.duck.test.js | 2 +- ui/app/ducks/gas/gas-duck.test.js | 4 +- .../metametrics/metametrics.provider.js | 2 +- .../tests/with-modal-props.test.js | 2 +- ui/app/helpers/utils/conversion-util.js | 2 +- ui/app/helpers/utils/metametrics.util.js | 2 +- ui/app/helpers/utils/util.js | 9 ++--- .../connect-hardware/account-list.js | 4 -- .../connect-hardware/connect-screen.js | 4 +- .../pages/create-account/connect-hardware/index.js | 4 +- ui/app/pages/create-account/import-account/seed.js | 2 +- .../metametrics-opt-in.component.js | 2 +- .../confirm-seed-phrase.component.js | 6 +-- .../draggable-seed.component.js | 2 +- ui/app/pages/routes/index.js | 2 +- .../tests/account-list-item-container.test.js | 2 +- .../tests/send-row-error-message-container.test.js | 2 +- .../send-row-warning-message-container.test.js | 2 +- .../send-content/send-to-row/send-to-row.utils.js | 2 +- ui/app/pages/send/tests/send-container.test.js | 2 +- ui/app/pages/send/tests/send-utils.test.js | 4 +- .../pages/send/to-autocomplete/to-autocomplete.js | 2 +- ui/app/store/actions.js | 45 +++++++++++----------- 46 files changed, 82 insertions(+), 100 deletions(-) (limited to 'ui/app') diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js index 79882f34a..e61cb8ad6 100644 --- a/ui/app/components/app/account-panel.js +++ b/ui/app/components/app/account-panel.js @@ -69,18 +69,9 @@ AccountPanel.prototype.render = function () { ) } -function balanceOrFaucetingIndication (account, isFauceting) { - // Temporarily deactivating isFauceting indication - // because it shows fauceting for empty restored accounts. - if (/* isFauceting*/ false) { - return { - key: 'Account is auto-funding.', - value: 'Please wait.', - } - } else { - return { - key: 'BALANCE', - value: formatBalance(account.balance), - } +function balanceOrFaucetingIndication (account) { + return { + key: 'BALANCE', + value: formatBalance(account.balance), } } diff --git a/ui/app/components/app/bn-as-decimal-input.js b/ui/app/components/app/bn-as-decimal-input.js index 9a033f893..834bab0a4 100644 --- a/ui/app/components/app/bn-as-decimal-input.js +++ b/ui/app/components/app/bn-as-decimal-input.js @@ -116,7 +116,7 @@ BnAsDecimalInput.prototype.render = function () { ) } -BnAsDecimalInput.prototype.setValid = function (message) { +BnAsDecimalInput.prototype.setValid = function () { this.setState({ invalid: null }) } diff --git a/ui/app/components/app/ens-input.js b/ui/app/components/app/ens-input.js index 424c5061e..5eea0dd90 100644 --- a/ui/app/components/app/ens-input.js +++ b/ui/app/components/app/ens-input.js @@ -144,7 +144,7 @@ EnsInput.prototype.ensIcon = function (recipient) { }, this.ensIconContents(recipient)) } -EnsInput.prototype.ensIconContents = function (recipient) { +EnsInput.prototype.ensIconContents = function () { const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS } if (toError) return diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js index 95894140c..d6c259033 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js @@ -58,7 +58,7 @@ export default class AdvancedTabContent extends Component { } } - gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) { + gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) { const { isInError, errorText, diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js index 90fef1a1b..73bc13481 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js @@ -17,8 +17,8 @@ function convertGasLimitForInputs (gasLimitInHexWEI) { const mapDispatchToProps = dispatch => { return { - showGasPriceInfoModal: modalName => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })), - showGasLimitInfoModal: modalName => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })), + showGasPriceInfoModal: () => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })), + showGasLimitInfoModal: () => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })), } } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js index ad8628621..eab3434df 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -67,7 +67,7 @@ export default class AdvancedTabContent extends Component { } } - gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) { + gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) { const { isInError, errorText, @@ -148,7 +148,6 @@ export default class AdvancedTabContent extends Component { customGasPrice, updateCustomGasPrice, customGasLimit, - updateCustomGasLimit, insufficientBalance, customPriceIsSafe, isSpeedUp, diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js index 8aaccafd5..e18c1067e 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -122,8 +122,6 @@ export default class GasModalPageContainer extends Component { } renderTabs ({ - originalTotalFiat, - originalTotalEth, newTotalFiat, newTotalEth, sendAmount, diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js index 0456f5262..14952a49a 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js @@ -49,7 +49,7 @@ export default class GasPriceButtonGroup extends Component { priceInHexWei, ...renderableGasInfo }, { - buttonDataLoading, + buttonDataLoading: _, handleGasPriceSelection, ...buttonContentPropsAndFlags }, index) { diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js index f19dafcc1..55512ce09 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js @@ -68,7 +68,7 @@ export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) { export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) { const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition) - const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => e > priceToPosition) + const closestHigherValueIndex = gasPrices.findIndex((e) => e > priceToPosition) return { closestLowerValueIndex, closestHigherValueIndex, @@ -133,7 +133,7 @@ export function setTickPosition (axis, n, newPosition, secondNewPosition) { d3.select('#chart') .select(`.c3-axis-${axis}`) .selectAll('.tick') - .filter((d, i) => i === n) + .filter((_, i) => i === n) .select('text') .attr(positionToShift, 0) .select('tspan') @@ -284,7 +284,7 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate }) return text + '' + "
" }, - position: function (data) { + position: function () { if (d3.select('#overlayed-circle').empty()) { return { top: -100, left: -100 } } diff --git a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js index 7dec7a85f..c960f49a7 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js @@ -6,7 +6,7 @@ import shallow from '../../../../../../lib/shallow-with-context' import * as d3 from 'd3' function timeout (time) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { setTimeout(resolve, time) }) } diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js index 8f7ef792c..f56069d65 100644 --- a/ui/app/components/app/modals/deposit-ether-modal.js +++ b/ui/app/components/app/modals/deposit-ether-modal.js @@ -48,7 +48,7 @@ function mapDispatchToProps (dispatch) { } inherits(DepositEtherModal, Component) -function DepositEtherModal (props, context) { +function DepositEtherModal (_, context) { Component.call(this) // need to set after i18n locale has loaded diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js index 70987330a..c3098a16c 100644 --- a/ui/app/components/app/modals/export-private-key-modal.js +++ b/ui/app/components/app/modals/export-private-key-modal.js @@ -98,7 +98,7 @@ ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) { }) } -ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) { +ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, hideModal) { return h('div.export-private-key-buttons', {}, [ !privateKey && h(Button, { type: 'default', @@ -171,7 +171,7 @@ ExportPrivateKeyModal.prototype.render = function () { h('div.private-key-password-warning', this.context.t('privateKeyWarning')), - this.renderButtons(privateKey, this.state.password, address, hideModal), + this.renderButtons(privateKey, address, hideModal), ]) } diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js index 83595281f..ea7d71a73 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js @@ -4,7 +4,7 @@ import MetaMetricsOptInModal from './metametrics-opt-in-modal.component' import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' import { setParticipateInMetaMetrics } from '../../../../store/actions' -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (_, ownProps) => { const { unapprovedTxCount } = ownProps return { diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js index 20915b5f9..a83ba8f8e 100644 --- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js @@ -71,7 +71,7 @@ export default class QrScanner extends Component { initCamera () { this.codeReader = new BrowserQRCodeReader() this.codeReader.getVideoInputDevices() - .then(videoInputDevices => { + .then(() => { clearTimeout(this.permissionChecker) this.checkPermisisions() this.codeReader.decodeFromInputVideoDevice(undefined, 'video') diff --git a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js index d2af05573..aa74fd800 100644 --- a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js +++ b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js @@ -3,7 +3,7 @@ import { compose } from 'recompose' import RejectTransactionsModal from './reject-transactions.component' import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (_, ownProps) => { const { unapprovedTxCount } = ownProps return { diff --git a/ui/app/components/app/token-cell.js b/ui/app/components/app/token-cell.js index cef809e8a..495b9502b 100644 --- a/ui/app/components/app/token-cell.js +++ b/ui/app/components/app/token-cell.js @@ -155,7 +155,7 @@ TokenCell.prototype.send = function (address, event) { } } -TokenCell.prototype.view = function (address, userAddress, network, event) { +TokenCell.prototype.view = function (address, userAddress, network) { const url = etherscanLinkFor(address, userAddress, network) if (url) { navigateTo(url) diff --git a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js index 88d63baae..4ecc0dabb 100644 --- a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js +++ b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js @@ -5,7 +5,7 @@ let mapStateToProps, mergeProps proxyquire('../user-preferenced-currency-display.container.js', { 'react-redux': { - connect: (ms, md, mp) => { + connect: (ms, _, mp) => { mapStateToProps = ms mergeProps = mp return () => ({}) diff --git a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js index 42d156f92..2a4635955 100644 --- a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js +++ b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js @@ -3,7 +3,7 @@ import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display. import { preferencesSelector, getIsMainnet } from '../../../selectors/selectors' import { ETH, PRIMARY, SECONDARY } from '../../../helpers/constants/common' -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state) => { const { useNativeCurrencyAsPrimaryCurrency, showFiatInTestnets, diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js index 5620d847a..b1229f502 100644 --- a/ui/app/components/ui/alert/index.js +++ b/ui/app/components/ui/alert/index.js @@ -18,7 +18,7 @@ class Alert extends Component { if (!this.props.visible && nextProps.visible) { this.animateIn(nextProps) } else if (this.props.visible && !nextProps.visible) { - this.animateOut(nextProps) + this.animateOut() } } @@ -30,7 +30,7 @@ class Alert extends Component { }) } - animateOut (props) { + animateOut () { this.setState({ msg: null, className: '.hidden', diff --git a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js index 9888c366e..182524e59 100644 --- a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js +++ b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js @@ -5,7 +5,7 @@ let mapStateToProps, mergeProps proxyquire('../currency-display.container.js', { 'react-redux': { - connect: (ms, md, mp) => { + connect: (ms, _, mp) => { mapStateToProps = ms mergeProps = mp return () => ({}) diff --git a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js index 6109d29b6..259fe594a 100644 --- a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js +++ b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js @@ -5,7 +5,7 @@ let mapStateToProps, mergeProps proxyquire('../currency-input.container.js', { 'react-redux': { - connect: (ms, md, mp) => { + connect: (ms, _, mp) => { mapStateToProps = ms mergeProps = mp return () => ({}) diff --git a/ui/app/components/ui/token-input/tests/token-input.container.test.js b/ui/app/components/ui/token-input/tests/token-input.container.test.js index 2b1c102c8..6f87e64a5 100644 --- a/ui/app/components/ui/token-input/tests/token-input.container.test.js +++ b/ui/app/components/ui/token-input/tests/token-input.container.test.js @@ -5,7 +5,7 @@ let mapStateToProps, mergeProps proxyquire('../token-input.container.js', { 'react-redux': { - connect: (ms, md, mp) => { + connect: (ms, _, mp) => { mapStateToProps = ms mergeProps = mp return () => ({}) diff --git a/ui/app/components/ui/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js index c5f8350a6..6a53f4c6f 100644 --- a/ui/app/components/ui/unit-input/unit-input.component.js +++ b/ui/app/components/ui/unit-input/unit-input.component.js @@ -58,7 +58,7 @@ export default class UnitInput extends PureComponent { this.props.onChange(value) } - handleBlur = event => { + handleBlur = () => { const { onBlur } = this.props typeof onBlur === 'function' && onBlur(this.state.value) } diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js index 483f2f56d..d2e344663 100644 --- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js +++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js @@ -494,7 +494,7 @@ describe('Confirm Transaction Duck', () => { }) }) - describe('Thunk actions', done => { + describe('Thunk actions', () => { beforeEach(() => { global.eth = { getCode: sinon.stub().callsFake( diff --git a/ui/app/ducks/gas/gas-duck.test.js b/ui/app/ducks/gas/gas-duck.test.js index c0152c74f..b7e83a81c 100644 --- a/ui/app/ducks/gas/gas-duck.test.js +++ b/ui/app/ducks/gas/gas-duck.test.js @@ -461,8 +461,8 @@ describe('Gas Duck', () => { assert.equal(thirdDispatchCallType, SET_PRICE_AND_TIME_ESTIMATES) assert(priceAndTimeEstimateResult.length < mockPredictTableResponse.length * 3 - 2) assert(!priceAndTimeEstimateResult.find(d => d.expectedTime > 100)) - assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime)) - assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice)) + assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime)) + assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice)) assert.deepEqual( mockDistpatch.getCall(3).args, diff --git a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js index 6086e03fb..6281ddcc6 100644 --- a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js +++ b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js @@ -42,7 +42,7 @@ class MetaMetricsProvider extends Component { currentPath: window.location.href, } - props.history.listen(locationObj => { + props.history.listen(() => { this.setState({ previousPath: this.state.currentPath, currentPath: window.location.href, diff --git a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js index 654e7062a..81a3512d1 100644 --- a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js +++ b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js @@ -21,7 +21,7 @@ const mockState = { describe('withModalProps', () => { it('should return a component wrapped with modal state props', () => { - const TestComponent = props => ( + const TestComponent = () => (
Testing
) const WrappedComponent = withModalProps(TestComponent) diff --git a/ui/app/helpers/utils/conversion-util.js b/ui/app/helpers/utils/conversion-util.js index 8cc531773..affddade7 100644 --- a/ui/app/helpers/utils/conversion-util.js +++ b/ui/app/helpers/utils/conversion-util.js @@ -42,7 +42,7 @@ const convert = R.invoker(1, 'times') const round = R.invoker(2, 'round')(R.__, BigNumber.ROUND_HALF_DOWN) const roundDown = R.invoker(2, 'round')(R.__, BigNumber.ROUND_DOWN) const invertConversionRate = conversionRate => () => new BigNumber(1.0).div(conversionRate) -const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec']) +const decToBigNumberViaString = () => R.pipe(String, toBigNumber['dec']) // Setter Maps const toBigNumber = { diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index 62f5fd760..cafbd5c07 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -84,7 +84,7 @@ function composeParamAddition (paramValue, paramName) { : `&${paramName}=${paramValue}` } -function composeUrl (config, permissionPreferences = {}) { +function composeUrl (config) { const { eventOpts = {}, customVariables = '', diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js index c50d7cbe5..94fa9ad42 100644 --- a/ui/app/helpers/utils/util.js +++ b/ui/app/helpers/utils/util.js @@ -92,7 +92,7 @@ function miniAddressSummary (address) { return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...' } -function isValidAddress (address, network) { +function isValidAddress (address) { var prefixed = ethUtil.addHexPrefix(address) if (address === '0x0000000000000000000000000000000000000000') return false return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) @@ -268,7 +268,7 @@ function bnMultiplyByFraction (targetBN, numerator, denominator) { return targetBN.mul(numBN).div(denomBN) } -function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimit) { +function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16)) { const gasBn = hexToBn(gas) const gasPriceBn = hexToBn(gasPrice) const txFeeBn = gasBn.mul(gasPriceBn) @@ -297,7 +297,7 @@ function exportAsFile (filename, data, type = 'text/csv') { } function allNull (obj) { - return Object.entries(obj).every(([key, value]) => value === null) + return Object.entries(obj).every(([_, value]) => value === null) } function getTokenAddressFromTokenObject (token) { @@ -308,11 +308,10 @@ function getTokenAddressFromTokenObject (token) { * Safely checksumms a potentially-null address * * @param {String} [address] - address to checksum - * @param {String} [network] - network id * @returns {String} - checksummed address * */ -function checksumAddress (address, network) { +function checksumAddress (address) { const checksummed = address ? ethUtil.toChecksumAddress(address) : '' return checksummed } diff --git a/ui/app/pages/create-account/connect-hardware/account-list.js b/ui/app/pages/create-account/connect-hardware/account-list.js index a521c7eaf..247c27a5d 100644 --- a/ui/app/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/pages/create-account/connect-hardware/account-list.js @@ -6,10 +6,6 @@ const Select = require('react-select').default import Button from '../../../components/ui/button' class AccountList extends Component { - constructor (props, context) { - super(props) - } - getHdPaths () { return [ { diff --git a/ui/app/pages/create-account/connect-hardware/connect-screen.js b/ui/app/pages/create-account/connect-hardware/connect-screen.js index f5a83e6cf..a3b8ad246 100644 --- a/ui/app/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/pages/create-account/connect-hardware/connect-screen.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') import Button from '../../../components/ui/button' class ConnectScreen extends Component { - constructor (props, context) { + constructor (props) { super(props) this.state = { selectedDevice: null, @@ -103,7 +103,7 @@ class ConnectScreen extends Component { } - scrollToTutorial = (e) => { + scrollToTutorial = () => { if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'}) } diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index 1398fa680..5a91a2725 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -12,7 +12,7 @@ const { getPlatform } = require('../../../../../app/scripts/lib/util') const { PLATFORM_FIREFOX } = require('../../../../../app/scripts/lib/enums') class ConnectHardwareForm extends Component { - constructor (props, context) { + constructor (props) { super(props) this.state = { error: null, @@ -101,7 +101,7 @@ class ConnectHardwareForm extends Component { const newState = { unlocked: true, device, error: null } // Default to the first account if (this.state.selectedAccount === null) { - accounts.forEach((a, i) => { + accounts.forEach((a) => { if (a.address.toLowerCase() === this.props.address) { newState.selectedAccount = a.index.toString() } diff --git a/ui/app/pages/create-account/import-account/seed.js b/ui/app/pages/create-account/import-account/seed.js index d98909baa..73332f926 100644 --- a/ui/app/pages/create-account/import-account/seed.js +++ b/ui/app/pages/create-account/import-account/seed.js @@ -11,7 +11,7 @@ SeedImportSubview.contextTypes = { module.exports = connect(mapStateToProps)(SeedImportSubview) -function mapStateToProps (state) { +function mapStateToProps () { return {} } diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index ffaff9acf..6b9d06cf9 100644 --- a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -119,7 +119,7 @@ export default class MetaMetricsOptIn extends Component { hideCancel={false} onSubmit={() => { setParticipateInMetaMetrics(true) - .then(([participateStatus, metaMetricsId]) => { + .then(([_, metaMetricsId]) => { const promise = participateInMetaMetrics !== true ? metricsEvent({ eventOpts: { diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index 04fe651e6..4cfc38fdf 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -37,7 +37,7 @@ export default class ConfirmSeedPhrase extends PureComponent { isDragging: false, } - shouldComponentUpdate (nextProps, nextState, nextContext) { + shouldComponentUpdate (nextProps, nextState) { const { seedPhrase } = this.props const { selectedSeedIndices, @@ -108,7 +108,7 @@ export default class ConfirmSeedPhrase extends PureComponent { } } - handleSelectSeedWord = (word, shuffledIndex) => { + handleSelectSeedWord = (shuffledIndex) => { this.setState({ selectedSeedIndices: [...this.state.selectedSeedIndices, shuffledIndex], pendingSeedIndices: [...this.state.pendingSeedIndices, shuffledIndex], @@ -183,7 +183,7 @@ export default class ConfirmSeedPhrase extends PureComponent { selected={isSelected} onClick={() => { if (!isSelected) { - this.handleSelectSeedWord(word, index) + this.handleSelectSeedWord(index) } else { this.handleDeselectSeedWord(index) } diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js index 97dbd2a4b..cdb881921 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js @@ -28,7 +28,7 @@ class DraggableSeed extends Component { onClick () {}, } - componentWillReceiveProps (nextProps, nextContext) { + componentWillReceiveProps (nextProps) { const { isOver, setHoveringIndex } = this.props if (isOver && !nextProps.isOver) { setHoveringIndex(-1) diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index 7ff741405..9c30da086 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -438,7 +438,7 @@ function mapStateToProps (state) { } } -function mapDispatchToProps (dispatch, ownProps) { +function mapDispatchToProps (dispatch) { return { dispatch, hideSidebar: () => dispatch(actions.hideSidebar()), diff --git a/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js index 33f932daf..1580fd497 100644 --- a/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js +++ b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js @@ -5,7 +5,7 @@ let mapStateToProps proxyquire('../account-list-item.container.js', { 'react-redux': { - connect: (ms, md) => { + connect: (ms) => { mapStateToProps = ms return () => ({}) }, diff --git a/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js index eecff165d..2013e3200 100644 --- a/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js +++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js @@ -5,7 +5,7 @@ let mapStateToProps proxyquire('../send-row-error-message.container.js', { 'react-redux': { - connect: (ms, md) => { + connect: (ms) => { mapStateToProps = ms return () => ({}) }, diff --git a/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js index 225bf056c..6c0739f0e 100644 --- a/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js +++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js @@ -5,7 +5,7 @@ let mapStateToProps proxyquire('../send-row-warning-message.container.js', { 'react-redux': { - connect: (ms, md) => { + connect: (ms) => { mapStateToProps = ms return () => ({}) }, diff --git a/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js index 200a2e26a..b3b0d2da3 100644 --- a/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js @@ -10,7 +10,7 @@ import { checkExistingAddresses } from '../../../add-token/util' const ethUtil = require('ethereumjs-util') const contractMap = require('eth-contract-metadata') -function getToErrorObject (to, toError = null, hasHexData = false, tokens = [], selectedToken = null, network) { +function getToErrorObject (to, toError = null, hasHexData = false, _, __, network) { if (!to) { if (!hasHexData) { toError = REQUIRED_ERROR diff --git a/ui/app/pages/send/tests/send-container.test.js b/ui/app/pages/send/tests/send-container.test.js index b3e202030..131c42f59 100644 --- a/ui/app/pages/send/tests/send-container.test.js +++ b/ui/app/pages/send/tests/send-container.test.js @@ -24,7 +24,7 @@ proxyquire('../send.container.js', { }, }, 'react-router-dom': { withRouter: () => {} }, - 'recompose': { compose: (arg1, arg2) => () => arg2() }, + 'recompose': { compose: (_, arg2) => () => arg2() }, './send.selectors': { getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`, getBlockGasLimit: (s) => `mockBlockGasLimit:${s}`, diff --git a/ui/app/pages/send/tests/send-utils.test.js b/ui/app/pages/send/tests/send-utils.test.js index b19535b9e..bf9cba14a 100644 --- a/ui/app/pages/send/tests/send-utils.test.js +++ b/ui/app/pages/send/tests/send-utils.test.js @@ -17,12 +17,12 @@ const { } = require('../send.constants') const stubs = { - addCurrencies: sinon.stub().callsFake((a, b, obj) => { + addCurrencies: sinon.stub().callsFake((a, b) => { if (String(a).match(/^0x.+/)) a = Number(String(a).slice(2)) if (String(b).match(/^0x.+/)) b = Number(String(b).slice(2)) return a + b }), - conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)), + conversionUtil: sinon.stub().callsFake((val) => parseInt(val, 16)), conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value), multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`), calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d), diff --git a/ui/app/pages/send/to-autocomplete/to-autocomplete.js b/ui/app/pages/send/to-autocomplete/to-autocomplete.js index b246413fb..328a5b62b 100644 --- a/ui/app/pages/send/to-autocomplete/to-autocomplete.js +++ b/ui/app/pages/send/to-autocomplete/to-autocomplete.js @@ -84,7 +84,7 @@ ToAutoComplete.prototype.handleInputEvent = function (event = {}, cb) { cb && cb(event.target.value) } -ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) { +ToAutoComplete.prototype.componentDidUpdate = function (nextProps) { if (this.props.to !== nextProps.to) { this.handleInputEvent() } diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index f2a9ed08f..95c6dbb77 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -762,7 +762,7 @@ function addNewAccount () { function checkHardwareStatus (deviceName, hdPath) { log.debug(`background.checkHardwareStatus`, deviceName, hdPath) - return (dispatch, getState) => { + return (dispatch) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => { @@ -783,10 +783,10 @@ function checkHardwareStatus (deviceName, hdPath) { function forgetDevice (deviceName) { log.debug(`background.forgetDevice`, deviceName) - return (dispatch, getState) => { + return (dispatch) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { - background.forgetDevice(deviceName, (err, response) => { + background.forgetDevice(deviceName, (err) => { if (err) { log.error(err) dispatch(actions.displayWarning(err.message)) @@ -804,7 +804,7 @@ function forgetDevice (deviceName) { function connectHardware (deviceName, page, hdPath) { log.debug(`background.connectHardware`, deviceName, page, hdPath) - return (dispatch, getState) => { + return (dispatch) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { background.connectHardware(deviceName, page, hdPath, (err, accounts) => { @@ -825,10 +825,10 @@ function connectHardware (deviceName, page, hdPath) { function unlockHardwareWalletAccount (index, deviceName, hdPath) { log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath) - return (dispatch, getState) => { + return (dispatch) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { - background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err, accounts) => { + background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err) => { if (err) { log.error(err) dispatch(actions.displayWarning(err.message)) @@ -849,7 +849,7 @@ function showInfoPage () { } function showQrScanner (ROUTE) { - return (dispatch, getState) => { + return (dispatch) => { return WebcamUtils.checkStatus() .then(status => { if (!status.environmentReady) { @@ -988,7 +988,7 @@ function signTypedMsg (msgData) { function signTx (txData) { return (dispatch) => { - global.ethQuery.sendTransaction(txData, (err, data) => { + global.ethQuery.sendTransaction(txData, (err) => { if (err) { return dispatch(actions.displayWarning(err.message)) } @@ -1021,7 +1021,6 @@ function setGasTotal (gasTotal) { function updateGasData ({ gasPrice, blockGasLimit, - recentBlocks, selectedAddress, selectedToken, to, @@ -1403,7 +1402,7 @@ function cancelTx (txData) { * @return {function(*): Promise} */ function cancelTxs (txDataList) { - return async (dispatch, getState) => { + return async (dispatch) => { window.onbeforeunload = null dispatch(actions.showLoadingIndication()) const txIds = txDataList.map(({id}) => id) @@ -1808,7 +1807,7 @@ function removeSuggestedTokens () { return (dispatch) => { dispatch(actions.showLoadingIndication()) window.onbeforeunload = null - return new Promise((resolve, reject) => { + return new Promise((resolve) => { background.removeSuggestedTokens((err, suggestedTokens) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -1827,7 +1826,7 @@ function removeSuggestedTokens () { } function addKnownMethodData (fourBytePrefix, methodData) { - return (dispatch) => { + return () => { background.addKnownMethodData(fourBytePrefix, methodData) } } @@ -1932,7 +1931,7 @@ function setProviderType (type) { return (dispatch, getState) => { const { type: currentProviderType } = getState().metamask.provider log.debug(`background.setProviderType`, type) - background.setProviderType(type, (err, result) => { + background.setProviderType(type, (err) => { if (err) { log.error(err) return dispatch(actions.displayWarning('Had a problem changing networks!')) @@ -1962,7 +1961,7 @@ function setPreviousProvider (type) { function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) { return (dispatch) => { log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`) - background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => { + background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => { if (err) { log.error(err) return dispatch(actions.displayWarning('Had a problem changing networks!')) @@ -1978,7 +1977,7 @@ function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) { function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) { return (dispatch) => { log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`) - background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => { + background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => { if (err) { log.error(err) return dispatch(actions.displayWarning('Had a problem changing networks!')) @@ -1991,7 +1990,7 @@ function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) { function delRpcTarget (oldRpc) { return (dispatch) => { log.debug(`background.delRpcTarget: ${oldRpc}`) - background.delCustomRpc(oldRpc, (err, result) => { + background.delCustomRpc(oldRpc, (err) => { if (err) { log.error(err) return dispatch(self.displayWarning('Had a problem removing network!')) @@ -2005,7 +2004,7 @@ function delRpcTarget (oldRpc) { function addToAddressBook (recipient, nickname = '') { log.debug(`background.addToAddressBook`) return (dispatch) => { - background.setAddressBook(recipient, nickname, (err, result) => { + background.setAddressBook(recipient, nickname, (err) => { if (err) { log.error(err) return dispatch(self.displayWarning('Address book failed to update')) @@ -2274,7 +2273,7 @@ function pairUpdate (coin) { } } -function shapeShiftSubview (network) { +function shapeShiftSubview () { var pair = 'btc_eth' return (dispatch) => { dispatch(actions.showSubLoadingIndication()) @@ -2310,7 +2309,7 @@ function coinShiftRquest (data, marketData) { } function buyWithShapeShift (data) { - return dispatch => new Promise((resolve, reject) => { + return () => new Promise((resolve, reject) => { shapeShiftRequest('shift', { method: 'POST', data}, (response) => { if (response.error) { return reject(response.error) @@ -2357,7 +2356,7 @@ function shapeShiftRequest (query, options, cb) { !options ? options = {} : null options.method ? method = options.method : method = 'GET' - var requestListner = function (request) { + var requestListner = function () { try { queryResponse = JSON.parse(this.responseText) cb ? cb(queryResponse) : null @@ -2686,19 +2685,19 @@ function setPendingTokens (pendingTokens) { } function approveProviderRequestByOrigin (origin) { - return (dispatch) => { + return () => { background.approveProviderRequestByOrigin(origin) } } function rejectProviderRequestByOrigin (origin) { - return (dispatch) => { + return () => { background.rejectProviderRequestByOrigin(origin) } } function clearApprovedOrigins () { - return (dispatch) => { + return () => { background.clearApprovedOrigins() } } -- cgit From 13be683701bc46d8f1bcbaa301e2b7f01a34e29c Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Thu, 9 May 2019 14:57:14 -0230 Subject: New settings custom rpc form (#6490) * Add networks tab to settings, with header. * Adds network list to settings network tab. * Adds form to settings networks tab and connects it to network list. * Network tab: form adding and editing working * Settings network form properly handles input errors * Add translations for settings network form * Clean up styles of settings network tab. * Add popup-view styles and behaviour to settings network tab. * Fix save button on settings network form * Adds 'Add Network' button and addMode to settings networks tab * Lint fix for settings networks tab addition * Fix navigation in settings networks tab. * Editing an rpcurl in networks tab does not create new network, just changes rpc of old * Fix layout of settings tabs other than network * Networks dropdown 'Custom Rpc' item links to networks tab in settings. * Update settings sidebar networks subheader. * Make networks tab buttons width consistent with input widths in extension view. * Fix settings screen subheader height in popup view * Fix height of add networks button in popup view * Add optional label to chainId and symbol form labels in networks setting tab * Style fixes for networks tab headers * Add ability to customize block explorer used by custom rpc * Stylistic improvements+fixes to custom rpc form. * Hide cancel button. * Highlight and show network form of provider by default. * Standardize network subheader name to 'Networks' * Update e2e tests for new settings network form * Update unit tests for new rpcPrefs prop * Extract blockexplorer url construction into method. * Fix broken styles on non-network tabs in popup mode * Fix block explorer url links for cases when provider in state has not been updated. * Fix vertical spacing of network form * Don't allow click of save button on network form if nothing has changed * Ensure add network button is shown in popup view * Lint fix for networks tab * Fix block explorer url preference setting. * Fix e2e tests for custom blockexplorer in account details modal changes. * Update integration test states to include frequentRpcList property * Fix some capitalizations in en/messages.json * Remove some console.logs added during custom rpc form work * Fix external account link text and url for modal and dropdown. * Documentation, url validation, proptype required additions and lint fixes on network tab and form. --- .../app/dropdowns/account-details-dropdown.js | 17 +- .../components/app/dropdowns/network-dropdown.js | 10 +- .../components/app/modals/account-details-modal.js | 12 +- .../transaction-list-item-details.component.js | 15 +- .../transaction-list-item.component.js | 3 + .../transaction-list-item.container.js | 5 +- .../ui/text-field/text-field.component.js | 6 +- ui/app/ducks/app/app.js | 12 ++ ui/app/helpers/constants/routes.js | 2 + ui/app/helpers/utils/transactions.util.js | 16 ++ ui/app/pages/settings/index.scss | 36 +++- ui/app/pages/settings/networks-tab/index.js | 1 + ui/app/pages/settings/networks-tab/index.scss | 200 ++++++++++++++++++ .../settings/networks-tab/network-form/index.js | 1 + .../network-form/network-form.component.js | 225 +++++++++++++++++++++ .../networks-tab/networks-tab.component.js | 214 ++++++++++++++++++++ .../networks-tab/networks-tab.constants.js | 50 +++++ .../networks-tab/networks-tab.container.js | 77 +++++++ ui/app/pages/settings/settings.component.js | 10 +- ui/app/selectors/selectors.js | 8 + ui/app/store/actions.js | 48 ++++- 21 files changed, 939 insertions(+), 29 deletions(-) create mode 100644 ui/app/pages/settings/networks-tab/index.js create mode 100644 ui/app/pages/settings/networks-tab/index.scss create mode 100644 ui/app/pages/settings/networks-tab/network-form/index.js create mode 100644 ui/app/pages/settings/networks-tab/network-form/network-form.component.js create mode 100644 ui/app/pages/settings/networks-tab/networks-tab.component.js create mode 100644 ui/app/pages/settings/networks-tab/networks-tab.constants.js create mode 100644 ui/app/pages/settings/networks-tab/networks-tab.container.js (limited to 'ui/app') diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js index 3d4598946..cbeccdd81 100644 --- a/ui/app/components/app/dropdowns/account-details-dropdown.js +++ b/ui/app/components/app/dropdowns/account-details-dropdown.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') -const { getSelectedIdentity } = require('../../../selectors/selectors') +const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors') const genAccountLink = require('../../../../lib/account-link.js') const { Menu, Item, CloseArea } = require('./components/menu') @@ -20,6 +20,7 @@ function mapStateToProps (state) { selectedIdentity: getSelectedIdentity(state), network: state.metamask.network, keyrings: state.metamask.keyrings, + rpcPrefs: getRpcPrefsForCurrentProvider(state), } } @@ -28,8 +29,8 @@ function mapDispatchToProps (dispatch) { showAccountDetailModal: () => { dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) }, - viewOnEtherscan: (address, network) => { - global.platform.openWindow({ url: genAccountLink(address, network) }) + viewOnEtherscan: (address, network, rpcPrefs) => { + global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) }) }, showRemoveAccountConfirmationModal: (identity) => { return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity })) @@ -56,7 +57,9 @@ AccountDetailsDropdown.prototype.render = function () { keyrings, showAccountDetailModal, viewOnEtherscan, - showRemoveAccountConfirmationModal } = this.props + showRemoveAccountConfirmationModal, + rpcPrefs, + } = this.props const address = selectedIdentity.address @@ -112,10 +115,12 @@ AccountDetailsDropdown.prototype.render = function () { name: 'Clicked View on Etherscan', }, }) - viewOnEtherscan(address, network) + viewOnEtherscan(address, network, rpcPrefs) this.props.onClose() }, - text: this.context.t('viewOnEtherscan'), + text: (rpcPrefs.blockExplorerUrl + ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]]) + : this.context.t('viewOnEtherscan')), icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }), }), isRemovable ? h(Item, { diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index dbe3f1bc8..378ad3ba6 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -10,7 +10,7 @@ const Dropdown = require('./components/dropdown').Dropdown const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem const NetworkDropdownIcon = require('./components/network-dropdown-icon') const R = require('ramda') -const { ADVANCED_ROUTE } = require('../../../helpers/constants/routes') +const { NETWORKS_ROUTE } = require('../../../helpers/constants/routes') // classes from nodes of the toggle element. const notToggleElementClassnames = [ @@ -49,6 +49,7 @@ function mapDispatchToProps (dispatch) { }, showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), + setNetworksTabAddMode: isInAddMode => dispatch(actions.setNetworksTabAddMode(isInAddMode)), } } @@ -72,7 +73,7 @@ module.exports = compose( // TODO: specify default props and proptypes NetworkDropdown.prototype.render = function () { const props = this.props - const { provider: { type: providerType, rpcTarget: activeNetwork } } = props + const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props const rpcListDetail = props.frequentRpcListDetail const isOpen = this.props.networkDropdownOpen const dropdownMenuItemStyle = { @@ -255,7 +256,10 @@ NetworkDropdown.prototype.render = function () { DropdownMenuItem, { closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => this.props.history.push(ADVANCED_ROUTE), + onClick: () => { + setNetworksTabAddMode(true) + this.props.history.push(NETWORKS_ROUTE) + }, style: dropdownMenuItemStyle, }, [ diff --git a/ui/app/components/app/modals/account-details-modal.js b/ui/app/components/app/modals/account-details-modal.js index 1b1ca6b8e..6cffc918b 100644 --- a/ui/app/components/app/modals/account-details-modal.js +++ b/ui/app/components/app/modals/account-details-modal.js @@ -5,7 +5,7 @@ const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') const AccountModalContainer = require('./account-modal-container') -const { getSelectedIdentity } = require('../../../selectors/selectors') +const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors') const genAccountLink = require('../../../../lib/account-link.js') const QrView = require('../../ui/qr-code') const EditableLabel = require('../../ui/editable-label') @@ -17,6 +17,7 @@ function mapStateToProps (state) { network: state.metamask.network, selectedIdentity: getSelectedIdentity(state), keyrings: state.metamask.keyrings, + rpcPrefs: getRpcPrefsForCurrentProvider(state), } } @@ -54,6 +55,7 @@ AccountDetailsModal.prototype.render = function () { showExportPrivateKeyModal, setAccountLabel, keyrings, + rpcPrefs, } = this.props const { name, address } = selectedIdentity @@ -86,8 +88,12 @@ AccountDetailsModal.prototype.render = function () { h(Button, { type: 'secondary', className: 'account-modal__button', - onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }), - }, this.context.t('etherscanView')), + onClick: () => { + global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) }) + }, + }, (rpcPrefs.blockExplorerUrl + ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]]) + : this.context.t('viewOnEtherscan'))), // Holding on redesign for Export Private Key functionality diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js index 4a3b04998..72ca784e2 100644 --- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -1,13 +1,15 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import copyToClipboard from 'copy-to-clipboard' +import { + getBlockExplorerUrlForTx, +} from '../../../helpers/utils/transactions.util' import SenderToRecipient from '../../ui/sender-to-recipient' import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants' import TransactionActivityLog from '../transaction-activity-log' import TransactionBreakdown from '../transaction-breakdown' import Button from '../../ui/button' import Tooltip from '../../ui/tooltip' -import prefixForNetwork from '../../../../lib/etherscan-prefix-for-network' export default class TransactionListItemDetails extends PureComponent { static contextTypes = { @@ -22,6 +24,7 @@ export default class TransactionListItemDetails extends PureComponent { showRetry: PropTypes.bool, cancelDisabled: PropTypes.bool, transactionGroup: PropTypes.object, + rpcPrefs: PropTypes.object, } state = { @@ -30,12 +33,9 @@ export default class TransactionListItemDetails extends PureComponent { } handleEtherscanClick = () => { - const { transactionGroup: { primaryTransaction } } = this.props + const { transactionGroup: { primaryTransaction }, rpcPrefs } = this.props const { hash, metamaskNetworkId } = primaryTransaction - const prefix = prefixForNetwork(metamaskNetworkId) - const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}` - this.context.metricsEvent({ eventOpts: { category: 'Navigation', @@ -44,7 +44,7 @@ export default class TransactionListItemDetails extends PureComponent { }, }) - global.platform.openWindow({ url: etherscanUrl }) + global.platform.openWindow({ url: getBlockExplorerUrlForTx(metamaskNetworkId, hash, rpcPrefs) }) } handleCancel = event => { @@ -125,6 +125,7 @@ export default class TransactionListItemDetails extends PureComponent { showRetry, onCancel, onRetry, + rpcPrefs: { blockExplorerUrl } = {}, } = this.props const { primaryTransaction: transaction } = transactionGroup const { txParams: { to, from } = {} } = transaction @@ -158,7 +159,7 @@ export default class TransactionListItemDetails extends PureComponent { /> - +
) diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js index a8fb8c246..5e88a2937 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js @@ -18,12 +18,14 @@ import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSe import { isBalanceSufficient } from '../../../pages/send/send.utils' const mapStateToProps = (state, ownProps) => { - const { metamask: { knownMethodData, accounts } } = state + const { metamask: { knownMethodData, accounts, provider, frequentRpcListDetail } } = state const { showFiatInTestnets } = preferencesSelector(state) const isMainnet = getIsMainnet(state) const { transactionGroup: { primaryTransaction } = {} } = ownProps const { txParams: { gas: gasLimit, gasPrice } = {} } = primaryTransaction const selectedAccountBalance = accounts[getSelectedAddress(state)].balance + const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget) + const { rpcPrefs } = selectRpcInfo || {} const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({ amount: '0x0', @@ -40,6 +42,7 @@ const mapStateToProps = (state, ownProps) => { showFiat: (isMainnet || !!showFiatInTestnets), selectedAccountBalance, hasEnoughCancelGas, + rpcPrefs, } } diff --git a/ui/app/components/ui/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js index 2c72d8124..1153a595b 100644 --- a/ui/app/components/ui/text-field/text-field.component.js +++ b/ui/app/components/ui/text-field/text-field.component.js @@ -41,11 +41,11 @@ const styles = { inputFocused: {}, inputRoot: { 'label + &': { - marginTop: '8px', + marginTop: '9px', }, - border: '1px solid #d2d8dd', + border: '2px solid #BBC0C5', height: '48px', - borderRadius: '4px', + borderRadius: '6px', padding: '0 16px', display: 'flex', alignItems: 'center', diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js index 295507d70..b181092c1 100644 --- a/ui/app/ducks/app/app.js +++ b/ui/app/ducks/app/app.js @@ -77,6 +77,8 @@ function reduceApp (state, action) { ledger: `m/44'/60'/0'/0/0`, }, lastSelectedProvider: null, + networksTabSelectedRpcUrl: '', + networksTabIsInAddMode: false, }, state.appState) switch (action.type) { @@ -751,6 +753,16 @@ function reduceApp (state, action) { lastSelectedProvider: action.value, }) + case actions.SET_SELECTED_SETTINGS_RPC_URL: + return extend(appState, { + networksTabSelectedRpcUrl: action.value, + }) + + case actions.SET_NETWORKS_TAB_ADD_MODE: + return extend(appState, { + networksTabIsInAddMode: action.value, + }) + default: return appState } diff --git a/ui/app/helpers/constants/routes.js b/ui/app/helpers/constants/routes.js index df35112d1..d906fc8e6 100644 --- a/ui/app/helpers/constants/routes.js +++ b/ui/app/helpers/constants/routes.js @@ -8,6 +8,7 @@ const ADVANCED_ROUTE = '/settings/advanced' const SECURITY_ROUTE = '/settings/security' const COMPANY_ROUTE = '/settings/company' const ABOUT_US_ROUTE = '/settings/about-us' +const NETWORKS_ROUTE = '/settings/networks' const REVEAL_SEED_ROUTE = '/seed' const MOBILE_SYNC_ROUTE = '/mobile-sync' const CONFIRM_SEED_ROUTE = '/confirm-seed' @@ -86,4 +87,5 @@ module.exports = { COMPANY_ROUTE, GENERAL_ROUTE, ABOUT_US_ROUTE, + NETWORKS_ROUTE, } diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js index cb6c9536c..99ccc3478 100644 --- a/ui/app/helpers/utils/transactions.util.js +++ b/ui/app/helpers/utils/transactions.util.js @@ -6,6 +6,8 @@ import { TRANSACTION_TYPE_CANCEL, TRANSACTION_STATUS_CONFIRMED, } from '../../../../app/scripts/controllers/transactions/enums' +import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' + import { TOKEN_METHOD_TRANSFER, @@ -188,3 +190,17 @@ export function getStatusKey (transaction) { return transaction.status } + +/** + * Returns an external block explorer URL at which a transaction can be viewed. + * @param {number} networkId + * @param {string} hash + * @param {Object} rpcPrefs + */ +export function getBlockExplorerUrlForTx (networkId, hash, rpcPrefs = {}) { + if (rpcPrefs.blockExplorerUrl) { + return `${rpcPrefs.blockExplorerUrl}/tx/${hash}` + } + const prefix = prefixForNetwork(networkId) + return `https://${prefix}etherscan.io/tx/${hash}` +} diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss index a19105bb4..66959ba93 100644 --- a/ui/app/pages/settings/index.scss +++ b/ui/app/pages/settings/index.scss @@ -1,5 +1,7 @@ @import 'info-tab/index'; +@import 'networks-tab/index'; + @import 'settings-tab/index'; .settings-page { @@ -13,7 +15,6 @@ flex-flow: row nowrap; padding: 12px 24px; align-items: center; - border-bottom: 1px solid $alto; flex: 0 0 auto; &__title { @@ -33,6 +34,34 @@ } } + &__sub-header { + height: 72px; + border-bottom: 1px solid #D8D8D8; + display: flex; + justify-content: space-between; + align-items: center; + + @media screen and (max-width: 575px) { + height: 69px; + position: relative; + text-align: center; + } + } + + &__sub-header-text { + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 24px; + line-height: 24px; + color: black; + + @media screen and (max-width: 575px) { + font-size: 16px; + width: 100%; + } + } + &__back-button { display: none; @@ -60,8 +89,9 @@ &__content { display: flex; flex-flow: row nowrap; - height: auto; + height: 100%; overflow: auto; + border-top: 1px solid #D8D8D8; &__tabs { display: flex; @@ -93,7 +123,7 @@ &__body { padding: 12px 24px; - + @media screen and (min-width: 576px) { padding: 12px; } diff --git a/ui/app/pages/settings/networks-tab/index.js b/ui/app/pages/settings/networks-tab/index.js new file mode 100644 index 000000000..362004498 --- /dev/null +++ b/ui/app/pages/settings/networks-tab/index.js @@ -0,0 +1 @@ +export { default } from './networks-tab.container' diff --git a/ui/app/pages/settings/networks-tab/index.scss b/ui/app/pages/settings/networks-tab/index.scss new file mode 100644 index 000000000..b0020437d --- /dev/null +++ b/ui/app/pages/settings/networks-tab/index.scss @@ -0,0 +1,200 @@ +.networks-tab { + &__content { + margin-top: 24px; + display: flex; + height: 100%; + max-width: 739px; + justify-content: space-between; + + @media screen and (max-width: 575px) { + margin-top: 0px; + } + } + + &__body { + padding: 12px 24px; + height: 100%; + display: flex; + flex-direction: column; + + @media screen and (max-width: 575px) { + padding: 0; + } + } + + &__back-button { + display: none; + + @media screen and (max-width: 575px) { + display: block; + background-image: url('/images/caret-left-black.svg'); + width: 18px; + height: 18px; + opacity: .5; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + margin-right: 16px; + cursor: pointer; + position: absolute; + margin-left: 10px; + } + } + + &__network-form { + flex: 0.5 0 auto; + max-width: 343px; + max-height: 465px; + display: flex; + flex-direction: column; + justify-content: space-between; + + .page-container__footer { + border-top: none; + + @media screen and (max-width: 575px) { + width: 93%; + } + + header { + padding: 10px 0px; + } + } + + @media screen and (max-width: 575px) { + display: flex; + flex: auto; + max-width: 100%; + max-height: 100%; + align-items: center; + width: 100%; + margin-top: 10px; + } + } + + &__network-form-row { + @media screen and (max-width: 575px) { + display: flex; + flex-direction: column; + width: 93%; + } + } + + &__network-form-label { + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 14px; + line-height: 20px; + color: #000000; + } + + &__networks-list { + flex: 0.5 0 auto; + max-width: 343px; + + @media screen and (max-width: 575px) { + max-width: 100vw; + width: 100vw; + overflow-y: scroll; + } + } + + &__add-network-button-wrapper { + display: none; + + @media screen and (max-width: 575px) { + display: flex; + padding-top: 19px; + padding-bottom: 23px; + justify-content: center; + align-items: center; + border-top: 1px solid #D8D8D8; + + .button { + width: 178px; + } + } + } + + &__add-network-header-button-wrapper { + padding-top: 15px; + padding-bottom: 21px; + justify-content: center; + + .button { + width: 178px; + } + + @media screen and (max-width: 575px) { + display: none; + } + } + + &__networks-list--selection { + @media screen and (max-width: 575px) { + display: none; + } + } + + &__networks-list-item { + display: flex; + padding: 13px 0px 13px 17px; + position: relative; + + .menu-icon-circle { + &:hover { + cursor: pointer; + } + } + + @media screen and (max-width: 575px) { + padding: 20px 23px 21px 17px; + border-bottom: 1px solid #D8D8D8; + } + } + + &__networks-list-item:last-of-type { + @media screen and (max-width: 575px) { + border-bottom: none; + } + } + + &__networks-list-name { + margin-left: 11px; + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 23px; + color: #6A737D; + + &:hover { + cursor: pointer; + } + } + + &__networks-list-arrow { + display: none; + + @media screen and (max-width: 575px) { + display: block; + background-image: url('/images/caret-right.svg'); + width: 24px; + height: 24px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + right: 10px; + cursor: pointer; + position: absolute; + width: 24px; + height: 24px; + } + } + + &__networks-list-name--selected { + font-weight: bold; + color: #000000; + } +} \ No newline at end of file diff --git a/ui/app/pages/settings/networks-tab/network-form/index.js b/ui/app/pages/settings/networks-tab/network-form/index.js new file mode 100644 index 000000000..89d9de42b --- /dev/null +++ b/ui/app/pages/settings/networks-tab/network-form/index.js @@ -0,0 +1 @@ +export { default } from './network-form.component' diff --git a/ui/app/pages/settings/networks-tab/network-form/network-form.component.js b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js new file mode 100644 index 000000000..5e455b65e --- /dev/null +++ b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js @@ -0,0 +1,225 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import validUrl from 'valid-url' +import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer' +import TextField from '../../../../components/ui/text-field' + +export default class NetworksTab extends PureComponent { + static contextTypes = { + t: PropTypes.func.isRequired, + metricsEvent: PropTypes.func.isRequired, + } + + static propTypes = { + editRpc: PropTypes.func.isRequired, + rpcUrl: PropTypes.string, + chainId: PropTypes.string, + ticker: PropTypes.string, + viewOnly: PropTypes.bool, + networkName: PropTypes.string, + onClear: PropTypes.func.isRequired, + setRpcTarget: PropTypes.func.isRequired, + networksTabIsInAddMode: PropTypes.bool, + blockExplorerUrl: PropTypes.string, + rpcPrefs: PropTypes.object, + } + + state = { + rpcUrl: this.props.rpcUrl, + chainId: this.props.chainId, + ticker: this.props.ticker, + networkName: this.props.networkName, + blockExplorerUrl: this.props.blockExplorerUrl, + errors: {}, + } + + componentDidUpdate (prevProps) { + const { rpcUrl: prevRpcUrl, networksTabIsInAddMode: prevAddMode } = prevProps + const { + rpcUrl, + chainId, + ticker, + networkName, + networksTabIsInAddMode, + blockExplorerUrl, + } = this.props + + if (!prevAddMode && networksTabIsInAddMode) { + this.setState({ + rpcUrl: '', + chainId: '', + ticker: '', + networkName: '', + blockExplorerUrl: '', + errors: {}, + }) + } else if (prevRpcUrl !== rpcUrl) { + this.setState({ rpcUrl, chainId, ticker, networkName, blockExplorerUrl, errors: {} }) + } + } + + componentWillUnmount () { + this.props.onClear() + this.setState({ + rpcUrl: '', + chainId: '', + ticker: '', + networkName: '', + blockExplorerUrl: '', + errors: {}, + }) + } + + stateIsUnchanged () { + const { + rpcUrl, + chainId, + ticker, + networkName, + blockExplorerUrl, + } = this.props + + const { + rpcUrl: stateRpcUrl, + chainId: stateChainId, + ticker: stateTicker, + networkName: stateNetworkName, + blockExplorerUrl: stateBlockExplorerUrl, + } = this.state + + return ( + stateRpcUrl === rpcUrl && + stateChainId === chainId && + stateTicker === ticker && + stateNetworkName === networkName && + stateBlockExplorerUrl === blockExplorerUrl + ) + } + + renderFormTextField (fieldKey, textFieldId, onChange, value, optionalTextFieldKey) { + const { errors } = this.state + const { viewOnly } = this.props + + return ( +
+
{this.context.t(optionalTextFieldKey || fieldKey)}
+ +
+ ) + } + + setStateWithValue = (stateKey, validator) => { + return (e) => { + validator && validator(e.target.value, stateKey) + this.setState({ [stateKey]: e.target.value }) + } + } + + setErrorTo = (errorKey, errorVal) => { + this.setState({ + errors: { + ...this.state.errors, + [errorKey]: errorVal, + }, + }) + } + + validateChainId = (chainId) => { + this.setErrorTo('chainId', !!chainId && Number.isNaN(parseInt(chainId)) + ? `${this.context.t('invalidInput')} chainId` + : '' + ) + } + + validateUrl = (url, stateKey) => { + if (validUrl.isWebUri(url)) { + this.setErrorTo(stateKey, '') + } else { + const appendedRpc = `http://${url}` + const validWhenAppended = validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/) + + this.setErrorTo(stateKey, this.context.t(validWhenAppended ? 'uriErrorMsg' : 'invalidRPC')) + } + } + + render () { + const { setRpcTarget, viewOnly, rpcUrl: propsRpcUrl, editRpc, rpcPrefs = {} } = this.props + const { + networkName, + rpcUrl, + chainId, + ticker, + blockExplorerUrl, + errors, + } = this.state + + + return ( +
+ {this.renderFormTextField( + 'networkName', + 'network-name', + this.setStateWithValue('networkName'), + networkName, + )} + {this.renderFormTextField( + 'rpcUrl', + 'rpc-url', + this.setStateWithValue('rpcUrl', this.validateUrl), + rpcUrl, + )} + {this.renderFormTextField( + 'chainId', + 'chainId', + this.setStateWithValue('chainId', this.validateChainId), + chainId, + 'optionalChainId', + )} + {this.renderFormTextField( + 'symbol', + 'network-ticker', + this.setStateWithValue('ticker'), + ticker, + 'optionalSymbol', + )} + {this.renderFormTextField( + 'blockExplorerUrl', + 'block-explorer-url', + this.setStateWithValue('blockExplorerUrl', this.validateUrl), + blockExplorerUrl, + 'optionalBlockExplorerUrl', + )} + { + if (propsRpcUrl && rpcUrl !== propsRpcUrl) { + editRpc(propsRpcUrl, rpcUrl, chainId, ticker, networkName, { + blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl, + ...rpcPrefs, + }) + } else { + setRpcTarget(rpcUrl, chainId, ticker, networkName, { + blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl, + ...rpcPrefs, + }) + } + }} + submitText={this.context.t('save')} + submitButtonType={'confirm'} + disabled={viewOnly || this.stateIsUnchanged() || Object.values(errors).some(x => x) || !rpcUrl} + /> +
+ ) + } + +} diff --git a/ui/app/pages/settings/networks-tab/networks-tab.component.js b/ui/app/pages/settings/networks-tab/networks-tab.component.js new file mode 100644 index 000000000..2f921a892 --- /dev/null +++ b/ui/app/pages/settings/networks-tab/networks-tab.component.js @@ -0,0 +1,214 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { SETTINGS_ROUTE } from '../../../helpers/constants/routes' +import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums' +import { getEnvironmentType } from '../../../../../app/scripts/lib/util' +import classnames from 'classnames' +import Button from '../../../components/ui/button' +import NetworkForm from './network-form' +import NetworkDropdownIcon from '../../../components/app/dropdowns/components/network-dropdown-icon' + +export default class NetworksTab extends PureComponent { + static contextTypes = { + t: PropTypes.func.isRequired, + metricsEvent: PropTypes.func.isRequired, + } + + static propTypes = { + editRpc: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + networkIsSelected: PropTypes.bool, + networksTabIsInAddMode: PropTypes.bool, + networksToRender: PropTypes.array.isRequired, + selectedNetwork: PropTypes.object, + setNetworksTabAddMode: PropTypes.func.isRequired, + setRpcTarget: PropTypes.func.isRequired, + setSelectedSettingsRpcUrl: PropTypes.func.isRequired, + providerUrl: PropTypes.string, + providerType: PropTypes.string, + networkDefaultedToProvider: PropTypes.bool, + } + + componentWillMount () { + this.props.setSelectedSettingsRpcUrl(null) + } + + isCurrentPath (pathname) { + return this.props.location.pathname === pathname + } + + renderSubHeader () { + const { + networkIsSelected, + setSelectedSettingsRpcUrl, + setNetworksTabAddMode, + networksTabIsInAddMode, + networkDefaultedToProvider, + } = this.props + + return ( +
+
{ + setNetworksTabAddMode(false) + setSelectedSettingsRpcUrl(null) + } + : () => this.props.history.push(SETTINGS_ROUTE) + } + /> + { this.context.t('networks') } +
+ +
+
+ ) + } + + renderNetworkListItem (network, selectRpcUrl) { + const { + setSelectedSettingsRpcUrl, + setNetworksTabAddMode, + networkIsSelected, + providerUrl, + providerType, + networksTabIsInAddMode, + } = this.props + const { + border, + iconColor, + label, + labelKey, + rpcUrl, + providerType: currentProviderType, + } = network + + const listItemNetworkIsSelected = selectRpcUrl && selectRpcUrl === rpcUrl + const listItemUrlIsProviderUrl = rpcUrl === providerUrl + const listItemTypeIsProviderNonRpcType = providerType !== 'rpc' && currentProviderType === providerType + const listItemNetworkIsCurrentProvider = !networkIsSelected && !networksTabIsInAddMode && (listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType) + const displayNetworkListItemAsSelected = listItemNetworkIsSelected || listItemNetworkIsCurrentProvider + + return ( +
{ + setNetworksTabAddMode(false) + setSelectedSettingsRpcUrl(rpcUrl) + }} + > + +
+ { label || this.context.t(labelKey) } +
+
+
+ ) + } + + renderNetworksList () { + const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props + + return ( +
+ { networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) } +
+ ) + } + + renderNetworksTabContent () { + const { + setRpcTarget, + setSelectedSettingsRpcUrl, + setNetworksTabAddMode, + selectedNetwork: { + labelKey, + label, + rpcUrl, + chainId, + ticker, + viewOnly, + rpcPrefs, + blockExplorerUrl, + }, + networksTabIsInAddMode, + editRpc, + networkDefaultedToProvider, + } = this.props + const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP + + return ( +
+ {this.renderNetworksList()} + {networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider) + ? { + setNetworksTabAddMode(false) + setSelectedSettingsRpcUrl(null) + }} + viewOnly={viewOnly} + networksTabIsInAddMode={networksTabIsInAddMode} + rpcPrefs={rpcPrefs} + blockExplorerUrl={blockExplorerUrl} + /> + : null + } +
+ ) + } + + renderContent () { + const { setNetworksTabAddMode, setSelectedSettingsRpcUrl, networkIsSelected, networksTabIsInAddMode } = this.props + + return ( +
+ {this.renderSubHeader()} + {this.renderNetworksTabContent()} + {!networkIsSelected && !networksTabIsInAddMode + ?
+ +
+ : null + } +
+ ) + } + + render () { + return this.renderContent() + } +} diff --git a/ui/app/pages/settings/networks-tab/networks-tab.constants.js b/ui/app/pages/settings/networks-tab/networks-tab.constants.js new file mode 100644 index 000000000..d3d1a01cc --- /dev/null +++ b/ui/app/pages/settings/networks-tab/networks-tab.constants.js @@ -0,0 +1,50 @@ +const defaultNetworksData = [ + { + labelKey: 'mainnet', + iconColor: '#29B6AF', + providerType: 'mainnet', + rpcUrl: 'https://api.infura.io/v1/jsonrpc/mainnet', + chainId: '1', + ticker: 'ETH', + blockExplorerUrl: 'https://etherscan.io', + }, + { + labelKey: 'ropsten', + iconColor: '#FF4A8D', + providerType: 'ropsten', + rpcUrl: 'https://api.infura.io/v1/jsonrpc/ropsten', + chainId: '3', + ticker: 'ETH', + blockExplorerUrl: 'https://ropsten.etherscan.io', + }, + { + labelKey: 'kovan', + iconColor: '#9064FF', + providerType: 'kovan', + rpcUrl: 'https://api.infura.io/v1/jsonrpc/kovan', + chainId: '4', + ticker: 'ETH', + blockExplorerUrl: 'https://etherscan.io', + }, + { + labelKey: 'rinkeby', + iconColor: '#F6C343', + providerType: 'rinkeby', + rpcUrl: 'https://api.infura.io/v1/jsonrpc/rinkeby', + chainId: '42', + ticker: 'ETH', + blockExplorerUrl: 'https://rinkeby.etherscan.io', + }, + { + labelKey: 'localhost', + iconColor: 'white', + border: '1px solid #6A737D', + providerType: 'localhost', + rpcUrl: 'http://localhost:8545/', + blockExplorerUrl: 'https://etherscan.io', + }, +] + +export { + defaultNetworksData, +} diff --git a/ui/app/pages/settings/networks-tab/networks-tab.container.js b/ui/app/pages/settings/networks-tab/networks-tab.container.js new file mode 100644 index 000000000..a5d71f714 --- /dev/null +++ b/ui/app/pages/settings/networks-tab/networks-tab.container.js @@ -0,0 +1,77 @@ +import NetworksTab from './networks-tab.component' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { + setSelectedSettingsRpcUrl, + updateAndSetCustomRpc, + displayWarning, + setNetworksTabAddMode, + editRpc, +} from '../../../store/actions' +import { defaultNetworksData } from './networks-tab.constants' +const defaultNetworks = defaultNetworksData.map(network => ({ ...network, viewOnly: true })) + +const mapStateToProps = state => { + const { + frequentRpcListDetail, + provider, + } = state.metamask + const { + networksTabSelectedRpcUrl, + networksTabIsInAddMode, + } = state.appState + + const frequentRpcNetworkListDetails = frequentRpcListDetail.map(rpc => { + return { + label: rpc.nickname, + iconColor: '#6A737D', + providerType: 'rpc', + rpcUrl: rpc.rpcUrl, + chainId: rpc.chainId, + ticker: rpc.ticker, + blockExplorerUrl: rpc.rpcPrefs && rpc.rpcPrefs.blockExplorerUrl || '', + } + }) + + const networksToRender = [ ...defaultNetworks, ...frequentRpcNetworkListDetails ] + let selectedNetwork = networksToRender.find(network => network.rpcUrl === networksTabSelectedRpcUrl) || {} + const networkIsSelected = Boolean(selectedNetwork.rpcUrl) + + let networkDefaultedToProvider = false + if (!networkIsSelected && !networksTabIsInAddMode) { + selectedNetwork = networksToRender.find(network => { + return network.rpcUrl === provider.rpcTarget || network.providerType !== 'rpc' && network.providerType === provider.type + }) || {} + networkDefaultedToProvider = true + } + + return { + selectedNetwork, + networksToRender, + networkIsSelected, + networksTabIsInAddMode, + providerType: provider.type, + providerUrl: provider.rpcTarget, + networkDefaultedToProvider, + } +} + +const mapDispatchToProps = dispatch => { + return { + setSelectedSettingsRpcUrl: newRpcUrl => dispatch(setSelectedSettingsRpcUrl(newRpcUrl)), + setRpcTarget: (newRpc, chainId, ticker, nickname, rpcPrefs) => { + dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname, rpcPrefs)) + }, + displayWarning: warning => dispatch(displayWarning(warning)), + setNetworksTabAddMode: isInAddMode => dispatch(setNetworksTabAddMode(isInAddMode)), + editRpc: (oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs) => { + dispatch(editRpc(oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs)) + }, + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(NetworksTab) diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js index fe799a6e8..a2f137264 100644 --- a/ui/app/pages/settings/settings.component.js +++ b/ui/app/pages/settings/settings.component.js @@ -6,6 +6,7 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util' import TabBar from '../../components/app/tab-bar' import c from 'classnames' import SettingsTab from './settings-tab' +import NetworksTab from './networks-tab' import AdvancedTab from './advanced-tab' import InfoTab from './info-tab' import SecurityTab from './security-tab' @@ -16,6 +17,7 @@ import { GENERAL_ROUTE, ABOUT_US_ROUTE, SETTINGS_ROUTE, + NETWORKS_ROUTE, } from '../../helpers/constants/routes' const ROUTES_TO_I18N_KEYS = { @@ -55,7 +57,7 @@ class SettingsPage extends PureComponent { >
{ - !this.isCurrentPath(SETTINGS_ROUTE) && ( + !this.isCurrentPath(SETTINGS_ROUTE) && !this.isCurrentPath(NETWORKS_ROUTE) && (
history.push(SETTINGS_ROUTE)} @@ -104,6 +106,7 @@ class SettingsPage extends PureComponent { { content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE }, { content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE }, { content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE }, + { content: t('networks'), description: t('networkSettingsDescription'), key: NETWORKS_ROUTE }, { content: t('about'), description: t('aboutSettingsDescription'), key: ABOUT_US_ROUTE }, ]} isActive={key => { @@ -135,6 +138,11 @@ class SettingsPage extends PureComponent { path={ADVANCED_ROUTE} component={AdvancedTab} /> + rpcInfo.rpcUrl === provider.rpcTarget) + const { rpcPrefs = {} } = selectRpcInfo || {} + return rpcPrefs +} diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 95c6dbb77..7d45f0932 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -239,6 +239,7 @@ var actions = { updateAndSetCustomRpc: updateAndSetCustomRpc, setRpcTarget: setRpcTarget, delRpcTarget: delRpcTarget, + editRpc: editRpc, setProviderType: setProviderType, SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH', setHardwareWalletDefaultHdPath, @@ -350,6 +351,11 @@ var actions = { setFirstTimeFlowType, SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE', + + SET_SELECTED_SETTINGS_RPC_URL: 'SET_SELECTED_SETTINGS_RPC_URL', + setSelectedSettingsRpcUrl, + SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE', + setNetworksTabAddMode, } module.exports = actions @@ -1958,10 +1964,10 @@ function setPreviousProvider (type) { } } -function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) { +function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) { return (dispatch) => { log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`) - background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => { + background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => { if (err) { log.error(err) return dispatch(actions.displayWarning('Had a problem changing networks!')) @@ -1974,6 +1980,29 @@ function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) { } } +function editRpc (oldRpc, newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) { + return (dispatch) => { + log.debug(`background.delRpcTarget: ${oldRpc}`) + background.delCustomRpc(oldRpc, (err) => { + if (err) { + log.error(err) + return dispatch(self.displayWarning('Had a problem removing network!')) + } + dispatch(actions.setSelectedToken()) + background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => { + if (err) { + log.error(err) + return dispatch(actions.displayWarning('Had a problem changing networks!')) + } + dispatch({ + type: actions.SET_RPC_TARGET, + value: newRpc, + }) + }) + }) + } +} + function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) { return (dispatch) => { log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`) @@ -2000,6 +2029,7 @@ function delRpcTarget (oldRpc) { } } + // Calls the addressBookController to add a new address. function addToAddressBook (recipient, nickname = '') { log.debug(`background.addToAddressBook`) @@ -2716,3 +2746,17 @@ function setFirstTimeFlowType (type) { }) } } + +function setSelectedSettingsRpcUrl (newRpcUrl) { + return { + type: actions.SET_SELECTED_SETTINGS_RPC_URL, + value: newRpcUrl, + } +} + +function setNetworksTabAddMode (isInAddMode) { + return { + type: actions.SET_NETWORKS_TAB_ADD_MODE, + value: isInAddMode, + } +} -- cgit From 0cdce533e2e9368b6aa9b37e85dd1f4cc33395aa Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 10 May 2019 11:54:53 -0230 Subject: Fix RPC URL message key casing (#6595) --- ui/app/pages/settings/advanced-tab/advanced-tab.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui/app') diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js index 8d70cd2df..3d27fe349 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js @@ -51,7 +51,7 @@ export default class AdvancedTab extends PureComponent { this.setState({ newRpc: e.target.value })} onKeyPress={e => { -- cgit From 28c4001f5269eb170f14bd2829698ee402b2cfbf Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 13 May 2019 13:46:09 -0230 Subject: Update auto-logout to recognize idle time in background (#6593) * Fix wording of autoLogoutTimeLimitDescription * AppStateController and update auto-logout functionality --- ui/app/pages/routes/index.js | 17 +++++------------ ui/app/store/actions.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) (limited to 'ui/app') diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index 9c30da086..9eeac2da2 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import { connect } from 'react-redux' import { Route, Switch, withRouter, matchPath } from 'react-router-dom' import { compose } from 'recompose' -import actions, {hideSidebar, hideWarning, lockMetamask} from '../../store/actions' +import actions from '../../store/actions' import log from 'loglevel' import IdleTimer from 'react-idle-timer' import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors' @@ -99,7 +99,7 @@ class Routes extends Component { } renderRoutes () { - const { autoLogoutTimeLimit, lockMetamask } = this.props + const { autoLogoutTimeLimit, setLastActiveTime } = this.props const routes = ( @@ -122,10 +122,7 @@ class Routes extends Component { if (autoLogoutTimeLimit > 0) { return ( - + {routes} ) @@ -338,7 +335,7 @@ Routes.propTypes = { networkDropdownOpen: PropTypes.bool, showNetworkDropdown: PropTypes.func, hideNetworkDropdown: PropTypes.func, - lockMetamask: PropTypes.func, + setLastActiveTime: PropTypes.func, history: PropTypes.object, location: PropTypes.object, dispatch: PropTypes.func, @@ -447,11 +444,7 @@ function mapDispatchToProps (dispatch) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), - lockMetamask: () => { - dispatch(lockMetamask()) - dispatch(hideWarning()) - dispatch(hideSidebar()) - }, + setLastActiveTime: () => dispatch(actions.setLastActiveTime()), } } diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 7d45f0932..7f6cbea1f 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -356,6 +356,10 @@ var actions = { setSelectedSettingsRpcUrl, SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE', setNetworksTabAddMode, + + // AppStateController-related actions + SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME', + setLastActiveTime, } module.exports = actions @@ -2760,3 +2764,13 @@ function setNetworksTabAddMode (isInAddMode) { value: isInAddMode, } } + +function setLastActiveTime () { + return (dispatch) => { + background.setLastActiveTime((err) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + }) + } +} -- cgit