aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorDan Finlay <542863+danfinlay@users.noreply.github.com>2019-05-15 04:22:54 +0800
committerGitHub <noreply@github.com>2019-05-15 04:22:54 +0800
commitbf5b7f8e77684ce4a66d7e0301ed19d048a545f9 (patch)
tree9c68af7d6cb4b10410d5deeb33b9c9b61996cd02 /ui/app
parent7dcc707a4329d69e9d4d7c6befbfd228ed7d714b (diff)
parent985f51a764560311e8aa5bb149e34b3c25b78c92 (diff)
downloadtangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar.gz
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar.zst
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.zip
Merge pull request #6599 from MetaMask/develop
Master Version Bump (v6.5.0)
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/components/app/account-panel.js17
-rw-r--r--ui/app/components/app/bn-as-decimal-input.js2
-rw-r--r--ui/app/components/app/dropdowns/account-details-dropdown.js17
-rw-r--r--ui/app/components/app/dropdowns/network-dropdown.js10
-rw-r--r--ui/app/components/app/ens-input.js2
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js2
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js4
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js3
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js6
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js2
-rw-r--r--ui/app/components/app/modals/account-details-modal.js12
-rw-r--r--ui/app/components/app/modals/deposit-ether-modal.js2
-rw-r--r--ui/app/components/app/modals/export-private-key-modal.js4
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js2
-rw-r--r--ui/app/components/app/modals/qr-scanner/qr-scanner.component.js2
-rw-r--r--ui/app/components/app/modals/reject-transactions/reject-transactions.container.js2
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container.component.js13
-rw-r--r--ui/app/components/app/token-cell.js2
-rw-r--r--ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js15
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.component.js3
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.container.js5
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js2
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js2
-rw-r--r--ui/app/components/ui/alert/index.js4
-rw-r--r--ui/app/components/ui/currency-display/currency-display.component.js2
-rw-r--r--ui/app/components/ui/currency-display/tests/currency-display.container.test.js2
-rw-r--r--ui/app/components/ui/currency-input/tests/currency-input.container.test.js2
-rw-r--r--ui/app/components/ui/text-field/text-field.component.js6
-rw-r--r--ui/app/components/ui/token-input/tests/token-input.container.test.js2
-rw-r--r--ui/app/components/ui/unit-input/unit-input.component.js2
-rw-r--r--ui/app/ducks/app/app.js12
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.js2
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js2
-rw-r--r--ui/app/ducks/gas/gas-duck.test.js4
-rw-r--r--ui/app/helpers/constants/routes.js2
-rw-r--r--ui/app/helpers/higher-order-components/i18n-provider.js12
-rw-r--r--ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js2
-rw-r--r--ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js2
-rw-r--r--ui/app/helpers/utils/conversion-util.js2
-rw-r--r--ui/app/helpers/utils/metametrics.util.js6
-rw-r--r--ui/app/helpers/utils/transactions.util.js16
-rw-r--r--ui/app/helpers/utils/util.js9
-rw-r--r--ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js11
-rw-r--r--ui/app/pages/create-account/connect-hardware/account-list.js4
-rw-r--r--ui/app/pages/create-account/connect-hardware/connect-screen.js4
-rw-r--r--ui/app/pages/create-account/connect-hardware/index.js4
-rw-r--r--ui/app/pages/create-account/import-account/seed.js2
-rw-r--r--ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js2
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js191
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js41
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js126
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss93
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js76
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js169
-rw-r--r--ui/app/pages/home/home.component.js19
-rw-r--r--ui/app/pages/provider-approval/provider-approval.component.js10
-rw-r--r--ui/app/pages/provider-approval/provider-approval.container.js6
-rw-r--r--ui/app/pages/routes/index.js24
-rw-r--r--ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js2
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js13
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js7
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js2
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js5
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js10
-rw-r--r--ui/app/pages/send/tests/send-container.test.js2
-rw-r--r--ui/app/pages/send/tests/send-utils.test.js4
-rw-r--r--ui/app/pages/send/to-autocomplete/to-autocomplete.js2
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.component.js47
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.container.js11
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js44
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js46
-rw-r--r--ui/app/pages/settings/index.scss60
-rw-r--r--ui/app/pages/settings/networks-tab/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/index.scss200
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/network-form.component.js225
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.component.js214
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.constants.js50
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.container.js77
-rw-r--r--ui/app/pages/settings/settings.component.js28
-rw-r--r--ui/app/selectors/selectors.js23
-rw-r--r--ui/app/store/actions.js122
85 files changed, 1895 insertions, 311 deletions
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/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/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 + '</table>' + "<div class='tooltip-arrow'></div>"
},
- 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/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/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/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/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/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 {
/>
</Button>
</Tooltip>
- <Tooltip title={t('viewOnEtherscan')}>
+ <Tooltip title={blockExplorerUrl ? t('viewOnCustomBlockExplorer', [blockExplorerUrl]) : t('viewOnEtherscan')}>
<Button
type="raised"
onClick={this.handleEtherscanClick}
diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
index c7d9dd7c7..0d4127b4f 100644
--- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
@@ -33,6 +33,7 @@ export default class TransactionListItem extends PureComponent {
value: PropTypes.string,
fetchBasicGasAndTimeEstimates: PropTypes.func,
fetchGasEstimates: PropTypes.func,
+ rpcPrefs: PropTypes.object,
}
static defaultProps = {
@@ -161,6 +162,7 @@ export default class TransactionListItem extends PureComponent {
showRetry,
tokenData,
transactionGroup,
+ rpcPrefs,
} = this.props
const { txParams = {} } = transaction
const { showTransactionDetails } = this.state
@@ -216,6 +218,7 @@ export default class TransactionListItem extends PureComponent {
onCancel={this.handleCancel}
showCancel={showCancel}
cancelDisabled={!hasEnoughCancelGas}
+ rpcPrefs={rpcPrefs}
/>
</div>
)
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/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/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 (
<div
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/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/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/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/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/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/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/higher-order-components/i18n-provider.js b/ui/app/helpers/higher-order-components/i18n-provider.js
index 0e34e17e0..5a6650147 100644
--- a/ui/app/helpers/higher-order-components/i18n-provider.js
+++ b/ui/app/helpers/higher-order-components/i18n-provider.js
@@ -15,11 +15,21 @@ 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,
- tOrKey (key, ...args) {
+ tOrKey: (key, ...args) => {
return this.tOrDefault(key, key, ...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 = () => (
<div className="test">Testing</div>
)
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 5ae3e8937..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 = '',
@@ -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/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/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/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 1cbe5951d..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,
}
@@ -99,15 +100,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()
@@ -543,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}
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 f3bfc3171..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
@@ -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) {
+ 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')
}
@@ -64,24 +108,35 @@ export default class ConfirmSeedPhrase extends PureComponent {
}
}
- handleSelectSeedWord = (word, shuffledIndex) => {
- this.setState(selectSeedWord(word, shuffledIndex))
+ handleSelectSeedWord = (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 (
<div className="confirm-seed-phrase">
@@ -102,41 +157,39 @@ export default class ConfirmSeedPhrase extends PureComponent {
<div className="first-time-flow__text-block">
{ t('selectEachPhrase') }
</div>
- <div className="confirm-seed-phrase__selected-seed-words">
- {
- selectedSeedWords.map((word, index) => (
- <div
- key={index}
- className="confirm-seed-phrase__seed-word"
- >
- { word }
- </div>
- ))
- }
+ <div
+ className={classnames('confirm-seed-phrase__selected-seed-words', {
+ 'confirm-seed-phrase__selected-seed-words--dragging': draggingSeedIndex > -1,
+ })}
+ >
+ { this.renderPendingSeeds() }
+ { this.renderSelectedSeeds() }
</div>
<div className="confirm-seed-phrase__shuffled-seed-words">
{
shuffledSeedWords.map((word, index) => {
- const isSelected = index in selectedSeedWordsHash
+ const isSelected = selectedSeedIndices.includes(index)
return (
- <div
+ <DraggableSeed
key={index}
- className={classnames(
- 'confirm-seed-phrase__seed-word',
- 'confirm-seed-phrase__seed-word--shuffled',
- { 'confirm-seed-phrase__seed-word--selected': isSelected }
- )}
+ seedIndex={index}
+ index={index}
+ draggingSeedIndex={this.state.draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ className="confirm-seed-phrase__seed-word--shuffled"
+ selected={isSelected}
onClick={() => {
if (!isSelected) {
- this.handleSelectSeedWord(word, index)
+ this.handleSelectSeedWord(index)
} else {
this.handleDeselectSeedWord(index)
}
}}
- >
- { word }
- </div>
+ word={word}
+ />
)
})
}
@@ -152,4 +205,80 @@ export default class ConfirmSeedPhrase extends PureComponent {
</div>
)
}
+
+ renderSelectedSeeds () {
+ const { shuffledSeedWords, selectedSeedIndices, draggingSeedIndex } = this.state
+ return EMPTY_SEEDS.map((_, index) => {
+ const seedIndex = selectedSeedIndices[index]
+ const word = shuffledSeedWords[seedIndex]
+
+ return (
+ <DraggableSeed
+ key={`selected-${seedIndex}-${index}`}
+ className="confirm-seed-phrase__selected-seed-words__selected-seed"
+ index={index}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ draggable
+ />
+ )
+ })
+ }
+
+ 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 (
+ <DraggableSeed
+ key={`pending-${seedIndex}-${index}`}
+ index={index}
+ className={classnames('confirm-seed-phrase__selected-seed-words__pending-seed', {
+ 'confirm-seed-phrase__seed-word--hidden': draggingSeedIndex === seedIndex && index !== hoveringIndex,
+ })}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ droppable={!!word}
+ />
+ )
+ })
+ }
+}
+
+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..cdb881921
--- /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) {
+ 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(
+ <div
+ key={index}
+ className={classnames('btn-secondary confirm-seed-phrase__seed-word', className, {
+ 'confirm-seed-phrase__seed-word--selected btn-primary': selected,
+ 'confirm-seed-phrase__seed-word--dragging': isDragging,
+ 'confirm-seed-phrase__seed-word--empty': !word,
+ 'confirm-seed-phrase__seed-word--active-drop': !isOver && canDrop,
+ 'confirm-seed-phrase__seed-word--drop-hover': isOver && canDrop,
+ })}
+ onClick={onClick}
+ >
+ { word }
+ </div>
+ ))
+ }
+}
+
+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 (
- <div className="first-time-flow__wrapper">
- <div className="app-header__logo-container">
- <img
- className="app-header__metafox-logo app-header__metafox-logo--horizontal"
- src="/images/logo/metamask-logo-horizontal.svg"
- height={30}
- />
- <img
- className="app-header__metafox-logo app-header__metafox-logo--icon"
- src="/images/logo/metamask-fox.svg"
- height={42}
- width={42}
- />
+ <DragDropContextProvider backend={HTML5Backend}>
+ <div className="first-time-flow__wrapper">
+ <div className="app-header__logo-container">
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--horizontal"
+ src="/images/logo/metamask-logo-horizontal.svg"
+ height={30}
+ />
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--icon"
+ src="/images/logo/metamask-fox.svg"
+ height={42}
+ width={42}
+ />
+ </div>
+ <Switch>
+ <Route
+ exact
+ path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ConfirmSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_SEED_PHRASE_ROUTE}
+ render={props => (
+ <RevealSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ </Switch>
</div>
- <Switch>
- <Route
- exact
- path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
- render={props => (
- <ConfirmSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- <Route
- exact
- path={INITIALIZE_SEED_PHRASE_ROUTE}
- render={props => (
- <RevealSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- </Switch>
- </div>
+ </DragDropContextProvider>
)
}
}
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(
+ <ConfirmSeedPhrase {...props} />,
+ {
+ 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')
+ })
+})
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={() => <WalletView />}
/>
- <TransactionView />
+ { !history.location.pathname.match(/^\/confirm-transaction/) ? <TransactionView /> : null }
</div>
</div>
)
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 (
<ProviderPageContainer
- approveProviderRequest={approveProviderRequest}
+ approveProviderRequestByOrigin={approveProviderRequestByOrigin}
+ rejectProviderRequestByOrigin={rejectProviderRequestByOrigin}
origin={providerRequest.origin}
tabID={providerRequest.tabID}
- rejectProviderRequest={rejectProviderRequest}
siteImage={providerRequest.siteImage}
siteTitle={providerRequest.siteTitle}
/>
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/pages/routes/index.js b/ui/app/pages/routes/index.js
index e38a6d6ce..9eeac2da2 100644
--- a/ui/app/pages/routes/index.js
+++ b/ui/app/pages/routes/index.js
@@ -5,7 +5,8 @@ import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
import { compose } from 'recompose'
import actions 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, setLastActiveTime } = this.props
+
+ const routes = (
<Switch>
<Route path={LOCK_ROUTE} component={Lock} exact />
<Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
@@ -116,6 +119,16 @@ class Routes extends Component {
<Authenticated path={DEFAULT_ROUTE} component={Home} exact />
</Switch>
)
+
+ if (autoLogoutTimeLimit > 0) {
+ return (
+ <IdleTimer onAction={setLastActiveTime} throttle={1000}>
+ {routes}
+ </IdleTimer>
+ )
+ }
+
+ return routes
}
onInitializationUnlockPage () {
@@ -322,6 +335,7 @@ Routes.propTypes = {
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
+ setLastActiveTime: PropTypes.func,
history: PropTypes.object,
location: PropTypes.object,
dispatch: PropTypes.func,
@@ -344,6 +358,7 @@ Routes.propTypes = {
t: PropTypes.func,
providerId: PropTypes.string,
providerRequests: PropTypes.array,
+ autoLogoutTimeLimit: PropTypes.number,
}
function mapStateToProps (state) {
@@ -358,6 +373,7 @@ function mapStateToProps (state) {
} = appState
const accounts = getMetaMaskAccounts(state)
+ const { autoLogoutTimeLimit = 0 } = preferencesSelector(state)
const {
identities,
@@ -409,6 +425,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,
@@ -418,7 +435,7 @@ function mapStateToProps (state) {
}
}
-function mapDispatchToProps (dispatch, ownProps) {
+function mapDispatchToProps (dispatch) {
return {
dispatch,
hideSidebar: () => dispatch(actions.hideSidebar()),
@@ -427,6 +444,7 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
+ setLastActiveTime: () => dispatch(actions.setLastActiveTime()),
}
}
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-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()
})
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 d0a43f086..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,16 +10,15 @@ 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
}
} 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,
})
})
})
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/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
index 14b9daae6..3d27fe349 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,
}
@@ -49,7 +51,7 @@ export default class AdvancedTab extends PureComponent {
<TextField
type="text"
id="new-rpc"
- placeholder={t('rpcURL')}
+ placeholder={t('rpcUrl')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
@@ -355,6 +357,48 @@ export default class AdvancedTab extends PureComponent {
)
}
+ renderAutoLogoutTimeLimit () {
+ const { t } = this.context
+ const {
+ autoLogoutTimeLimit,
+ setAutoLogoutTimeLimit,
+ } = this.props
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('autoLogoutTimeLimit') }</span>
+ <div className="settings-page__content-description">
+ { t('autoLogoutTimeLimitDescription') }
+ </div>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <TextField
+ type="number"
+ id="autoTimeout"
+ placeholder="5"
+ value={this.state.autoLogoutTimeLimit}
+ defaultValue={autoLogoutTimeLimit}
+ onChange={e => this.setState({ autoLogoutTimeLimit: Math.max(Number(e.target.value), 0) })}
+ fullWidth
+ margin="dense"
+ min={0}
+ />
+ <button
+ className="button btn-primary settings-tab__rpc-save-button"
+ onClick={() => {
+ setAutoLogoutTimeLimit(this.state.autoLogoutTimeLimit)
+ }}
+ >
+ { t('save') }
+ </button>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
renderContent () {
const { warning } = this.props
@@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent {
{ this.renderAdvancedGasInputInline() }
{ this.renderHexDataOptIn() }
{ this.renderShowConversionInTestnets() }
+ { this.renderAutoLogoutTimeLimit() }
</div>
)
}
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(
+ <AdvancedTab />,
+ {
+ 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(
+ <AdvancedTab
+ setAutoLogoutTimeLimit={setAutoLogoutTimeLimitSpy}
+ />,
+ {
+ 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/pages/settings/index.scss b/ui/app/pages/settings/index.scss
index 52208dc85..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 {
@@ -22,6 +23,45 @@
}
}
+ &__subheader {
+ padding: 16px 4px;
+ font-size: 20px;
+ border-bottom: 1px solid $alto;
+ margin-right: 24px;
+
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__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;
@@ -49,8 +89,9 @@
&__content {
display: flex;
flex-flow: row nowrap;
- height: auto;
+ height: 100%;
overflow: auto;
+ border-top: 1px solid #D8D8D8;
&__tabs {
display: flex;
@@ -58,9 +99,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 +123,10 @@
&__body {
padding: 12px 24px;
+
+ @media screen and (min-width: 576px) {
+ padding: 12px;
+ }
}
&__content-row {
@@ -89,7 +140,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/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 (
+ <div className="networks-tab__network-form-row">
+ <div className="networks-tab__network-form-label">{this.context.t(optionalTextFieldKey || fieldKey)}</div>
+ <TextField
+ type="text"
+ id={textFieldId}
+ onChange={onChange}
+ fullWidth
+ margin="dense"
+ value={value}
+ disabled={viewOnly}
+ error={errors[fieldKey]}
+ />
+ </div>
+ )
+ }
+
+ 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 (
+ <div className="networks-tab__network-form">
+ {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',
+ )}
+ <PageContainerFooter
+ cancelText={this.context.t('cancel')}
+ hideCancel={true}
+ onSubmit={() => {
+ 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}
+ />
+ </div>
+ )
+ }
+
+}
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 (
+ <div className="settings-page__sub-header">
+ <div
+ className="networks-tab__back-button"
+ onClick={(networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode
+ ? () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }
+ : () => this.props.history.push(SETTINGS_ROUTE)
+ }
+ />
+ <span className="settings-page__sub-header-text">{ this.context.t('networks') }</span>
+ <div className="networks-tab__add-network-header-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+
+ 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 (
+ <div
+ key={'settings-network-list-item:' + rpcUrl}
+ className="networks-tab__networks-list-item"
+ onClick={ () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(rpcUrl)
+ }}
+ >
+ <NetworkDropdownIcon
+ backgroundColor={iconColor || 'white'}
+ innerBorder={border}
+ />
+ <div className={ classnames('networks-tab__networks-list-name', {
+ 'networks-tab__networks-list-name--selected': displayNetworkListItemAsSelected,
+ }) }>
+ { label || this.context.t(labelKey) }
+ </div>
+ <div className="networks-tab__networks-list-arrow" />
+ </div>
+ )
+ }
+
+ renderNetworksList () {
+ const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props
+
+ return (
+ <div className={classnames('networks-tab__networks-list', {
+ 'networks-tab__networks-list--selection': (networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode,
+ })}>
+ { networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) }
+ </div>
+ )
+ }
+
+ 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 (
+ <div className="networks-tab__content">
+ {this.renderNetworksList()}
+ {networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider)
+ ? <NetworkForm
+ setRpcTarget={setRpcTarget}
+ editRpc={editRpc}
+ networkName={label || labelKey && this.context.t(labelKey) || ''}
+ rpcUrl={rpcUrl}
+ chainId={chainId}
+ ticker={ticker}
+ onClear={() => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }}
+ viewOnly={viewOnly}
+ networksTabIsInAddMode={networksTabIsInAddMode}
+ rpcPrefs={rpcPrefs}
+ blockExplorerUrl={blockExplorerUrl}
+ />
+ : null
+ }
+ </div>
+ )
+ }
+
+ renderContent () {
+ const { setNetworksTabAddMode, setSelectedSettingsRpcUrl, networkIsSelected, networksTabIsInAddMode } = this.props
+
+ return (
+ <div className="networks-tab__body">
+ {this.renderSubHeader()}
+ {this.renderNetworksTabContent()}
+ {!networkIsSelected && !networksTabIsInAddMode
+ ? <div className="networks-tab__add-network-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ : null
+ }
+ </div>
+ )
+ }
+
+ 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 061e65060..a2f137264 100644
--- a/ui/app/pages/settings/settings.component.js
+++ b/ui/app/pages/settings/settings.component.js
@@ -1,11 +1,12 @@
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'
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 = {
@@ -25,7 +27,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,
@@ -55,7 +57,7 @@ export default class SettingsPage extends PureComponent {
>
<div className="settings-page__header">
{
- !this.isCurrentPath(SETTINGS_ROUTE) && (
+ !this.isCurrentPath(SETTINGS_ROUTE) && !this.isCurrentPath(NETWORKS_ROUTE) && (
<div
className="settings-page__back-button"
onClick={() => history.push(SETTINGS_ROUTE)}
@@ -75,6 +77,7 @@ export default class SettingsPage extends PureComponent {
{ this.renderTabs() }
</div>
<div className="settings-page__content__modules">
+ { this.renderSubHeader() }
{ this.renderContent() }
</div>
</div>
@@ -82,6 +85,17 @@ export default class SettingsPage extends PureComponent {
)
}
+ renderSubHeader () {
+ const { t } = this.context
+ const { location: { pathname } } = this.props
+
+ return (
+ <div className="settings-page__subheader">
+ {t(ROUTES_TO_I18N_KEYS[pathname] || 'general')}
+ </div>
+ )
+ }
+
renderTabs () {
const { history, location } = this.props
const { t } = this.context
@@ -92,6 +106,7 @@ export default 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 => {
@@ -125,6 +140,11 @@ export default class SettingsPage extends PureComponent {
/>
<Route
exact
+ path={NETWORKS_ROUTE}
+ component={NetworksTab}
+ />
+ <Route
+ exact
path={SECURITY_ROUTE}
component={SecurityTab}
/>
@@ -135,3 +155,5 @@ export default class SettingsPage extends PureComponent {
)
}
}
+
+export default withRouter(SettingsPage)
diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js
index 2d25aa156..c7cb80024 100644
--- a/ui/app/selectors/selectors.js
+++ b/ui/app/selectors/selectors.js
@@ -48,6 +48,8 @@ const selectors = {
getNumberOfAccounts,
getNumberOfTokens,
isEthereumNetwork,
+ getMetaMetricState,
+ getRpcPrefsForCurrentProvider,
}
module.exports = selectors
@@ -165,7 +167,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 +316,22 @@ 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,
+ }
+}
+
+function getRpcPrefsForCurrentProvider (state) {
+ const { frequentRpcListDetail, provider } = state.metamask
+ const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
+ const { rpcPrefs = {} } = selectRpcInfo || {}
+ return rpcPrefs
+}
diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js
index f594d9002..7f6cbea1f 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,
@@ -316,6 +317,7 @@ var actions = {
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
+ setAutoLogoutTimeLimit,
// Migration of users to new UI
setCompletedUiMigration,
@@ -343,12 +345,21 @@ var actions = {
createCancelTransaction,
createSpeedUpTransaction,
- approveProviderRequest,
- rejectProviderRequest,
+ approveProviderRequestByOrigin,
+ rejectProviderRequestByOrigin,
clearApprovedOrigins,
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,
+
+ // AppStateController-related actions
+ SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME',
+ setLastActiveTime,
}
module.exports = actions
@@ -761,7 +772,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) => {
@@ -782,10 +793,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))
@@ -803,7 +814,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) => {
@@ -824,10 +835,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))
@@ -848,7 +859,7 @@ function showInfoPage () {
}
function showQrScanner (ROUTE) {
- return (dispatch, getState) => {
+ return (dispatch) => {
return WebcamUtils.checkStatus()
.then(status => {
if (!status.environmentReady) {
@@ -987,7 +998,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))
}
@@ -1020,7 +1031,6 @@ function setGasTotal (gasTotal) {
function updateGasData ({
gasPrice,
blockGasLimit,
- recentBlocks,
selectedAddress,
selectedToken,
to,
@@ -1402,7 +1412,7 @@ function cancelTx (txData) {
* @return {function(*): Promise<void>}
*/
function cancelTxs (txDataList) {
- return async (dispatch, getState) => {
+ return async (dispatch) => {
window.onbeforeunload = null
dispatch(actions.showLoadingIndication())
const txIds = txDataList.map(({id}) => id)
@@ -1807,7 +1817,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) {
@@ -1826,7 +1836,7 @@ function removeSuggestedTokens () {
}
function addKnownMethodData (fourBytePrefix, methodData) {
- return (dispatch) => {
+ return () => {
background.addKnownMethodData(fourBytePrefix, methodData)
}
}
@@ -1931,7 +1941,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!'))
@@ -1958,10 +1968,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, result) => {
+ background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1974,10 +1984,33 @@ 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}`)
- 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!'))
@@ -1990,7 +2023,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!'))
@@ -2000,11 +2033,12 @@ function delRpcTarget (oldRpc) {
}
}
+
// Calls the addressBookController to add a new address.
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'))
@@ -2273,7 +2307,7 @@ function pairUpdate (coin) {
}
}
-function shapeShiftSubview (network) {
+function shapeShiftSubview () {
var pair = 'btc_eth'
return (dispatch) => {
dispatch(actions.showSubLoadingIndication())
@@ -2309,7 +2343,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)
@@ -2356,7 +2390,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
@@ -2439,6 +2473,10 @@ function setShowFiatConversionOnTestnetsPreference (value) {
return setPreference('showFiatInTestnets', value)
}
+function setAutoLogoutTimeLimit (value) {
+ return setPreference('autoLogoutTimeLimit', value)
+}
+
function setCompletedOnboarding () {
return async dispatch => {
dispatch(actions.showLoadingIndication())
@@ -2680,20 +2718,20 @@ function setPendingTokens (pendingTokens) {
}
}
-function approveProviderRequest (tabID) {
- return (dispatch) => {
- background.approveProviderRequest(tabID)
+function approveProviderRequestByOrigin (origin) {
+ return () => {
+ background.approveProviderRequestByOrigin(origin)
}
}
-function rejectProviderRequest (tabID) {
- return (dispatch) => {
- background.rejectProviderRequest(tabID)
+function rejectProviderRequestByOrigin (origin) {
+ return () => {
+ background.rejectProviderRequestByOrigin(origin)
}
}
function clearApprovedOrigins () {
- return (dispatch) => {
+ return () => {
background.clearApprovedOrigins()
}
}
@@ -2712,3 +2750,27 @@ 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,
+ }
+}
+
+function setLastActiveTime () {
+ return (dispatch) => {
+ background.setLastActiveTime((err) => {
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ })
+ }
+}