aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorVimal <vtiwari@coinswitch.co>2018-11-12 20:20:37 +0800
committerVimal <vtiwari@coinswitch.co>2018-11-12 20:20:37 +0800
commit6bb92a8672edf23465fb109c8966d2e56500ea86 (patch)
tree6f5c4e348fb58fcf23b27278ffae1c1b912ad08e /ui
parent82b11b69a5dea38a4a036096528354a5a291a447 (diff)
parentda1036f2c4b20748407b7fa89b60df5e321fcab7 (diff)
downloadtangerine-wallet-browser-6bb92a8672edf23465fb109c8966d2e56500ea86.tar
tangerine-wallet-browser-6bb92a8672edf23465fb109c8966d2e56500ea86.tar.gz
tangerine-wallet-browser-6bb92a8672edf23465fb109c8966d2e56500ea86.tar.bz2
tangerine-wallet-browser-6bb92a8672edf23465fb109c8966d2e56500ea86.tar.lz
tangerine-wallet-browser-6bb92a8672edf23465fb109c8966d2e56500ea86.tar.xz
tangerine-wallet-browser-6bb92a8672edf23465fb109c8966d2e56500ea86.tar.zst
tangerine-wallet-browser-6bb92a8672edf23465fb109c8966d2e56500ea86.zip
merging upstream branch
Diffstat (limited to 'ui')
-rw-r--r--ui/app/actions.js63
-rw-r--r--ui/app/app.js8
-rw-r--r--ui/app/components/account-dropdowns.js3
-rw-r--r--ui/app/components/account-menu/index.js13
-rw-r--r--ui/app/components/account-panel.js2
-rw-r--r--ui/app/components/add-token-button/add-token-button.component.js34
-rw-r--r--ui/app/components/add-token-button/index.js1
-rw-r--r--ui/app/components/add-token-button/index.scss26
-rw-r--r--ui/app/components/app-header/app-header.component.js2
-rw-r--r--ui/app/components/balance-component.js38
-rw-r--r--ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js66
-rw-r--r--ui/app/components/confirm-page-container/confirm-detail-row/index.scss12
-rw-r--r--ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js38
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js7
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js19
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container.component.js5
-rw-r--r--ui/app/components/currency-display/currency-display.component.js26
-rw-r--r--ui/app/components/currency-display/currency-display.container.js35
-rw-r--r--ui/app/components/currency-display/index.scss14
-rw-r--r--ui/app/components/currency-display/tests/currency-display.container.test.js52
-rw-r--r--ui/app/components/currency-input/currency-input.component.js121
-rw-r--r--ui/app/components/currency-input/currency-input.container.js28
-rw-r--r--ui/app/components/currency-input/index.js1
-rw-r--r--ui/app/components/currency-input/index.scss7
-rw-r--r--ui/app/components/currency-input/tests/currency-input.component.test.js251
-rw-r--r--ui/app/components/currency-input/tests/currency-input.container.test.js60
-rw-r--r--ui/app/components/dropdowns/components/account-dropdowns.js8
-rw-r--r--ui/app/components/dropdowns/network-dropdown.js35
-rw-r--r--ui/app/components/dropdowns/tests/network-dropdown.test.js4
-rw-r--r--ui/app/components/eth-balance.js12
-rw-r--r--ui/app/components/identicon.js124
-rw-r--r--ui/app/components/identicon/identicon.component.js99
-rw-r--r--ui/app/components/identicon/identicon.container.js12
-rw-r--r--ui/app/components/identicon/index.js1
-rw-r--r--ui/app/components/identicon/index.scss7
-rw-r--r--ui/app/components/identicon/tests/identicon.component.test.js51
-rw-r--r--ui/app/components/index.scss12
-rw-r--r--ui/app/components/jazzicon/index.js1
-rw-r--r--ui/app/components/jazzicon/jazzicon.component.js69
-rw-r--r--ui/app/components/modals/account-modal-container.js2
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js12
-rw-r--r--ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js9
-rw-r--r--ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js39
-rw-r--r--ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js16
-rw-r--r--ui/app/components/modals/clear-approved-origins/index.js1
-rw-r--r--ui/app/components/modals/hide-token-confirmation-modal.js2
-rw-r--r--ui/app/components/modals/modal.js28
-rw-r--r--ui/app/components/modals/transaction-details/index.js1
-rw-r--r--ui/app/components/modals/transaction-details/transaction-details.component.js54
-rw-r--r--ui/app/components/modals/transaction-details/transaction-details.container.js4
-rw-r--r--ui/app/components/network-display/index.scss5
-rw-r--r--ui/app/components/network-display/network-display.component.js19
-rw-r--r--ui/app/components/network.js5
-rw-r--r--ui/app/components/page-container/index.scss1
-rw-r--r--ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js59
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js89
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js8
-rw-r--r--ui/app/components/pages/home/home.component.js10
-rw-r--r--ui/app/components/pages/home/home.container.js2
-rw-r--r--ui/app/components/pages/provider-approval/index.js1
-rw-r--r--ui/app/components/pages/provider-approval/provider-approval.component.js28
-rw-r--r--ui/app/components/pages/provider-approval/provider-approval.container.js12
-rw-r--r--ui/app/components/pages/settings/settings-tab/index.scss30
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.component.js214
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.container.js19
-rw-r--r--ui/app/components/provider-page-container/index.js3
-rw-r--r--ui/app/components/provider-page-container/index.scss121
-rw-r--r--ui/app/components/provider-page-container/provider-page-container-content/index.js1
-rw-r--r--ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js77
-rw-r--r--ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js11
-rw-r--r--ui/app/components/provider-page-container/provider-page-container-header/index.js1
-rw-r--r--ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js12
-rw-r--r--ui/app/components/provider-page-container/provider-page-container.component.js50
-rw-r--r--ui/app/components/send/account-list-item/account-list-item.component.js29
-rw-r--r--ui/app/components/send/account-list-item/account-list-item.container.js2
-rw-r--r--ui/app/components/send/account-list-item/tests/account-list-item-component.test.js17
-rw-r--r--ui/app/components/send/account-list-item/tests/account-list-item-container.test.js2
-rw-r--r--ui/app/components/send/currency-display/currency-display.js186
-rw-r--r--ui/app/components/send/currency-display/index.js1
-rw-r--r--ui/app/components/send/currency-display/tests/currency-display.test.js91
-rw-r--r--ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js34
-rw-r--r--ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js9
-rw-r--r--ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js45
-rw-r--r--ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js26
-rw-r--r--ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js35
-rw-r--r--ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js12
-rw-r--r--ui/app/components/send/send.selectors.js5
-rw-r--r--ui/app/components/send/send.utils.js4
-rw-r--r--ui/app/components/send/tests/send-selectors-test-data.js1
-rw-r--r--ui/app/components/send/tests/send-selectors.test.js10
-rw-r--r--ui/app/components/shift-list-item.js5
-rw-r--r--ui/app/components/signature-request.js2
-rw-r--r--ui/app/components/token-cell.js2
-rw-r--r--ui/app/components/token-input/index.js1
-rw-r--r--ui/app/components/token-input/tests/token-input.component.test.js308
-rw-r--r--ui/app/components/token-input/tests/token-input.container.test.js129
-rw-r--r--ui/app/components/token-input/token-input.component.js136
-rw-r--r--ui/app/components/token-input/token-input.container.js27
-rw-r--r--ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js3
-rw-r--r--ui/app/components/transaction-activity-log/transaction-activity-log.component.js21
-rw-r--r--ui/app/components/transaction-activity-log/transaction-activity-log.container.js3
-rw-r--r--ui/app/components/transaction-activity-log/transaction-activity-log.util.js17
-rw-r--r--ui/app/components/transaction-breakdown/index.js2
-rw-r--r--ui/app/components/transaction-breakdown/transaction-breakdown.component.js40
-rw-r--r--ui/app/components/transaction-breakdown/transaction-breakdown.container.js11
-rw-r--r--ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js17
-rw-r--r--ui/app/components/transaction-list-item/index.scss2
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.component.js36
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.container.js10
-rw-r--r--ui/app/components/transaction-list/index.scss3
-rw-r--r--ui/app/components/transaction-status/index.scss7
-rw-r--r--ui/app/components/transaction-view-balance/index.scss9
-rw-r--r--ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js4
-rw-r--r--ui/app/components/transaction-view-balance/transaction-view-balance.component.js14
-rw-r--r--ui/app/components/transaction-view-balance/transaction-view-balance.container.js3
-rw-r--r--ui/app/components/transaction-view/index.scss1
-rw-r--r--ui/app/components/unit-input/index.js1
-rw-r--r--ui/app/components/unit-input/index.scss44
-rw-r--r--ui/app/components/unit-input/tests/unit-input.component.test.js146
-rw-r--r--ui/app/components/unit-input/unit-input.component.js104
-rw-r--r--ui/app/components/user-preferenced-currency-display/index.js1
-rw-r--r--ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js34
-rw-r--r--ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js115
-rw-r--r--ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js46
-rw-r--r--ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.container.js54
-rw-r--r--ui/app/components/user-preferenced-currency-input/index.js1
-rw-r--r--ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js32
-rw-r--r--ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js31
-rw-r--r--ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js20
-rw-r--r--ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js13
-rw-r--r--ui/app/components/user-preferenced-token-input/index.js1
-rw-r--r--ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js32
-rw-r--r--ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js31
-rw-r--r--ui/app/components/user-preferenced-token-input/user-preferenced-token-input.component.js20
-rw-r--r--ui/app/components/user-preferenced-token-input/user-preferenced-token-input.container.js13
-rw-r--r--ui/app/components/wallet-view.js33
-rw-r--r--ui/app/constants/common.js3
-rw-r--r--ui/app/constants/error-keys.js1
-rw-r--r--ui/app/css/itcss/components/buttons.scss5
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss12
-rw-r--r--ui/app/css/itcss/components/pages/index.scss2
-rw-r--r--ui/app/css/itcss/components/pages/provider-approval.scss11
-rw-r--r--ui/app/css/itcss/components/request-signature.scss2
-rw-r--r--ui/app/css/itcss/components/wallet-balance.scss5
-rw-r--r--ui/app/ducks/confirm-transaction.duck.js69
-rw-r--r--ui/app/ducks/tests/confirm-transaction.duck.test.js44
-rw-r--r--ui/app/helpers/confirm-transaction/util.js8
-rw-r--r--ui/app/helpers/conversions.util.js29
-rw-r--r--ui/app/helpers/tests/transactions.util.test.js35
-rw-r--r--ui/app/helpers/transactions.util.js33
-rw-r--r--ui/app/reducers.js26
-rw-r--r--ui/app/reducers/metamask.js18
-rw-r--r--ui/app/selectors.js10
-rw-r--r--ui/app/selectors/confirm-transaction.js1
-rw-r--r--ui/app/util.js8
-rw-r--r--ui/i18n-helper.js2
156 files changed, 3722 insertions, 1009 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index eea581d33..501fef76d 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -305,6 +305,12 @@ var actions = {
updateFeatureFlags,
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
+ // Preferences
+ setPreference,
+ updatePreferences,
+ UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
+ setUseNativeCurrencyAsPrimaryCurrencyPreference,
+
setMouseUserState,
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
@@ -319,6 +325,9 @@ var actions = {
clearPendingTokens,
createCancelTransaction,
+ approveProviderRequest,
+ rejectProviderRequest,
+ clearApprovedOrigins,
}
module.exports = actions
@@ -1868,10 +1877,10 @@ function updateProviderType (type) {
}
}
-function setRpcTarget (newRpc) {
+function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname = '') {
return (dispatch) => {
- log.debug(`background.setRpcTarget: ${newRpc}`)
- background.setCustomRpc(newRpc, (err, result) => {
+ log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
+ background.setCustomRpc(newRpc, chainId, ticker, nickname, (err, result) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -2298,6 +2307,36 @@ function updateFeatureFlags (updatedFeatureFlags) {
}
}
+function setPreference (preference, value) {
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.setPreference(preference, value, (err, updatedPreferences) => {
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.updatePreferences(updatedPreferences))
+ resolve(updatedPreferences)
+ })
+ })
+ }
+}
+
+function updatePreferences (value) {
+ return {
+ type: actions.UPDATE_PREFERENCES,
+ value,
+ }
+}
+
+function setUseNativeCurrencyAsPrimaryCurrencyPreference (value) {
+ return setPreference('useNativeCurrencyAsPrimaryCurrency', value)
+}
+
function setNetworkNonce (networkNonce) {
return {
type: actions.SET_NETWORK_NONCE,
@@ -2448,3 +2487,21 @@ function setPendingTokens (pendingTokens) {
payload: tokens,
}
}
+
+function approveProviderRequest (origin) {
+ return (dispatch) => {
+ background.approveProviderRequest(origin)
+ }
+}
+
+function rejectProviderRequest (origin) {
+ return (dispatch) => {
+ background.rejectProviderRequest(origin)
+ }
+}
+
+function clearApprovedOrigins () {
+ return (dispatch) => {
+ background.clearApprovedOrigins()
+ }
+}
diff --git a/ui/app/app.js b/ui/app/app.js
index aeb3d05ee..b3aff1f39 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -101,7 +101,7 @@ class App extends Component {
network,
isMouseUser,
provider,
- frequentRpcList,
+ frequentRpcListDetail,
currentView,
setMouseUserState,
sidebar,
@@ -147,7 +147,7 @@ class App extends Component {
// network dropdown
h(NetworkDropdown, {
provider,
- frequentRpcList,
+ frequentRpcListDetail,
}, []),
h(AccountMenu),
@@ -230,7 +230,7 @@ App.propTypes = {
alertMessage: PropTypes.string,
network: PropTypes.string,
provider: PropTypes.object,
- frequentRpcList: PropTypes.array,
+ frequentRpcListDetail: PropTypes.array,
currentView: PropTypes.object,
sidebar: PropTypes.object,
alertOpen: PropTypes.bool,
@@ -322,7 +322,7 @@ function mapStateToProps (state) {
forgottenPassword: state.appState.forgottenPassword,
nextUnreadNotice,
lostAccounts,
- frequentRpcList: state.metamask.frequentRpcList || [],
+ frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
betaUI: state.metamask.featureFlags.betaUI,
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js
index 043008a36..06376e48b 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/account-dropdowns.js
@@ -6,10 +6,11 @@ const genAccountLink = require('etherscan-link').createAccountLink
const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
-const Identicon = require('./identicon')
const copyToClipboard = require('copy-to-clipboard')
const { checksumAddress } = require('../util')
+import Identicon from './identicon'
+
class AccountDropdowns extends Component {
constructor (props) {
super(props)
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index bcada41e3..94eae8d07 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -7,12 +7,12 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
-const Identicon = require('../identicon')
-const { formatBalance } = require('../../util')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const Tooltip = require('../tooltip')
-
+import Identicon from '../identicon'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
+import { PRIMARY } from '../../constants/common'
const {
SETTINGS_ROUTE,
@@ -163,7 +163,6 @@ AccountMenu.prototype.renderAccounts = function () {
const isSelected = identity.address === selectedAddress
const balanceValue = accounts[address] ? accounts[address].balance : ''
- const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
@@ -189,7 +188,11 @@ AccountMenu.prototype.renderAccounts = function () {
h('div.account-menu__account-info', [
h('div.account-menu__name', identity.name || ''),
- h('div.account-menu__balance', formattedBalance),
+ h(UserPreferencedCurrencyDisplay, {
+ className: 'account-menu__balance',
+ value: balanceValue,
+ type: PRIMARY,
+ }),
]),
this.renderKeyringType(keyring),
diff --git a/ui/app/components/account-panel.js b/ui/app/components/account-panel.js
index abaaf8163..a379ed3ac 100644
--- a/ui/app/components/account-panel.js
+++ b/ui/app/components/account-panel.js
@@ -1,7 +1,7 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
-const Identicon = require('./identicon')
+import Identicon from './identicon'
const formatBalance = require('../util').formatBalance
const addressSummary = require('../util').addressSummary
diff --git a/ui/app/components/add-token-button/add-token-button.component.js b/ui/app/components/add-token-button/add-token-button.component.js
new file mode 100644
index 000000000..10887aed8
--- /dev/null
+++ b/ui/app/components/add-token-button/add-token-button.component.js
@@ -0,0 +1,34 @@
+import PropTypes from 'prop-types'
+import React, {PureComponent} from 'react'
+
+export default class AddTokenButton extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ }
+
+ static defaultProps = {
+ onClick: () => {},
+ }
+
+ static propTypes = {
+ onClick: PropTypes.func,
+ }
+
+ render () {
+ const { t } = this.context
+ const { onClick } = this.props
+
+ return (
+ <div className="add-token-button">
+ <h1 className="add-token-button__help-header">{t('missingYourTokens')}</h1>
+ <p className="add-token-button__help-desc">{t('clickToAdd', [t('addToken')])}</p>
+ <div
+ className="add-token-button__button"
+ onClick={onClick}
+ >
+ {t('addToken')}
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/add-token-button/index.js b/ui/app/components/add-token-button/index.js
new file mode 100644
index 000000000..15c4fe6ca
--- /dev/null
+++ b/ui/app/components/add-token-button/index.js
@@ -0,0 +1 @@
+export { default } from './add-token-button.component'
diff --git a/ui/app/components/add-token-button/index.scss b/ui/app/components/add-token-button/index.scss
new file mode 100644
index 000000000..39f404716
--- /dev/null
+++ b/ui/app/components/add-token-button/index.scss
@@ -0,0 +1,26 @@
+.add-token-button {
+ display: flex;
+ flex-direction: column;
+ color: lighten($scorpion, 25%);
+ width: 185px;
+ margin: 36px auto;
+ text-align: center;
+
+ &__help-header {
+ font-weight: bold;
+ font-size: 1rem;
+ }
+
+ &__help-desc {
+ font-size: 0.75rem;
+ margin-top: 1rem;
+ }
+
+ &__button {
+ font-size: 0.75rem;
+ margin: 1rem;
+ text-transform: uppercase;
+ color: $curious-blue;
+ cursor: pointer;
+ }
+}
diff --git a/ui/app/components/app-header/app-header.component.js b/ui/app/components/app-header/app-header.component.js
index b8b002dcc..c82dc1de9 100644
--- a/ui/app/components/app-header/app-header.component.js
+++ b/ui/app/components/app-header/app-header.component.js
@@ -2,13 +2,13 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { matchPath } from 'react-router-dom'
+import Identicon from '../identicon'
const {
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP,
} = require('../../../../app/scripts/lib/enums')
const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
-const Identicon = require('../identicon')
const NetworkIndicator = require('../network')
export default class AppHeader extends PureComponent {
diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js
index d63d78c9f..4e2769ee8 100644
--- a/ui/app/components/balance-component.js
+++ b/ui/app/components/balance-component.js
@@ -2,12 +2,13 @@ const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const inherits = require('util').inherits
-const TokenBalance = require('./token-balance')
-const Identicon = require('./identicon')
-import CurrencyDisplay from './currency-display'
-const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
+import TokenBalance from './token-balance'
+import Identicon from './identicon'
+import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../constants/common'
+const { getNativeCurrency, getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
-const { formatBalance, generateBalanceObject } = require('../util')
+const { formatBalance } = require('../util')
module.exports = connect(mapStateToProps)(BalanceComponent)
@@ -20,6 +21,7 @@ function mapStateToProps (state) {
return {
account,
network,
+ nativeCurrency: getNativeCurrency(state),
conversionRate: conversionRateSelector(state),
currentCurrency: getCurrentCurrency(state),
assetImages: getAssetImages(state),
@@ -65,10 +67,10 @@ BalanceComponent.prototype.renderTokenBalance = function () {
BalanceComponent.prototype.renderBalance = function () {
const props = this.props
- const { shorten, account } = props
+ const { account, nativeCurrency } = props
const balanceValue = account && account.balance
const needsParse = 'needsParse' in props ? props.needsParse : true
- const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
+ const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse, nativeCurrency) : '...'
const showFiat = 'showFiat' in props ? props.showFiat : true
if (formattedBalance === 'None' || formattedBalance === '...') {
@@ -80,25 +82,21 @@ BalanceComponent.prototype.renderBalance = function () {
}
return h('div.flex-column.balance-display', {}, [
- h('div.token-amount', {
- style: {},
- }, this.getTokenBalance(formattedBalance, shorten)),
+ h(UserPreferencedCurrencyDisplay, {
+ className: 'token-amount',
+ value: balanceValue,
+ type: PRIMARY,
+ ethNumberOfDecimals: 4,
+ }),
- showFiat && h(CurrencyDisplay, {
+ showFiat && h(UserPreferencedCurrencyDisplay, {
value: balanceValue,
+ type: SECONDARY,
+ ethNumberOfDecimals: 4,
}),
])
}
-BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
- const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3)
-
- const balanceValue = shorten ? balanceObj.shortBalance : balanceObj.balance
- const label = balanceObj.label
-
- return `${balanceValue} ${label}`
-}
-
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
if (formattedBalance === 'None') return formattedBalance
if (conversionRate === 0) return 'N/A'
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js b/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
index f0703dde2..c7262d2a9 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
@@ -1,16 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
+import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../constants/common'
const ConfirmDetailRow = props => {
const {
label,
- fiatText,
- ethText,
+ primaryText,
+ secondaryText,
onHeaderClick,
- fiatTextColor,
+ primaryValueTextColor,
headerText,
headerTextClassName,
+ value,
} = props
return (
@@ -25,28 +28,57 @@ const ConfirmDetailRow = props => {
>
{ headerText }
</div>
- <div
- className="confirm-detail-row__fiat"
- style={{ color: fiatTextColor }}
- >
- { fiatText }
- </div>
- <div className="confirm-detail-row__eth">
- { ethText }
- </div>
+ {
+ primaryText
+ ? (
+ <div
+ className="confirm-detail-row__primary"
+ style={{ color: primaryValueTextColor }}
+ >
+ { primaryText }
+ </div>
+ ) : (
+ <UserPreferencedCurrencyDisplay
+ className="confirm-detail-row__primary"
+ type={PRIMARY}
+ value={value}
+ showEthLogo
+ ethLogoHeight="18"
+ style={{ color: primaryValueTextColor }}
+ hideLabel
+ />
+ )
+ }
+ {
+ secondaryText
+ ? (
+ <div className="confirm-detail-row__secondary">
+ { secondaryText }
+ </div>
+ ) : (
+ <UserPreferencedCurrencyDisplay
+ className="confirm-detail-row__secondary"
+ type={SECONDARY}
+ value={value}
+ showEthLogo
+ hideLabel
+ />
+ )
+ }
</div>
</div>
)
}
ConfirmDetailRow.propTypes = {
- label: PropTypes.string,
- fiatText: PropTypes.string,
- ethText: PropTypes.string,
- fiatTextColor: PropTypes.string,
- onHeaderClick: PropTypes.func,
headerText: PropTypes.string,
headerTextClassName: PropTypes.string,
+ label: PropTypes.string,
+ onHeaderClick: PropTypes.func,
+ primaryValueTextColor: PropTypes.string,
+ primaryText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ secondaryText: PropTypes.string,
+ value: PropTypes.string,
}
export default ConfirmDetailRow
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/index.scss b/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
index dd6f87c17..580a41fde 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
@@ -18,18 +18,14 @@
min-width: 0;
}
- &__fiat {
+ &__primary {
font-size: 1.5rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ justify-content: flex-end;
}
- &__eth {
+ &__secondary {
color: $oslo-gray;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ justify-content: flex-end;
}
&__header-text {
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js
index 6f2489071..c8507985d 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js
@@ -12,17 +12,19 @@ describe('Confirm Detail Row Component', function () {
let wrapper
beforeEach(() => {
- wrapper = shallow(<ConfirmDetailRow
- errorType={'mockErrorType'}
- label={'mockLabel'}
- showError={false}
- fiatText = {'mockFiatText'}
- ethText = {'mockEthText'}
- fiatTextColor= {'mockColor'}
- onHeaderClick= {propsMethodSpies.onHeaderClick}
- headerText = {'mockHeaderText'}
- headerTextClassName = {'mockHeaderClass'}
- />)
+ wrapper = shallow(
+ <ConfirmDetailRow
+ errorType={'mockErrorType'}
+ label={'mockLabel'}
+ showError={false}
+ primaryText = {'mockFiatText'}
+ secondaryText = {'mockEthText'}
+ primaryValueTextColor= {'mockColor'}
+ onHeaderClick= {propsMethodSpies.onHeaderClick}
+ headerText = {'mockHeaderText'}
+ headerTextClassName = {'mockHeaderClass'}
+ />
+ )
})
describe('render', () => {
@@ -38,16 +40,16 @@ describe('Confirm Detail Row Component', function () {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText')
})
- it('should render the fiatText as a child of the confirm-detail-row__fiat', () => {
- assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatText')
+ it('should render the primaryText as a child of the confirm-detail-row__primary', () => {
+ assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText')
})
- it('should render the ethText as a child of the confirm-detail-row__eth', () => {
- assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), 'mockEthText')
+ it('should render the ethText as a child of the confirm-detail-row__secondary', () => {
+ assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText')
})
- it('should set the fiatTextColor on confirm-detail-row__fiat', () => {
- assert.equal(wrapper.find('.confirm-detail-row__fiat').props().style.color, 'mockColor')
+ it('should set the fiatTextColor on confirm-detail-row__primary', () => {
+ assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
})
it('should assure the confirm-detail-row__header-text classname is correct', () => {
@@ -58,7 +60,5 @@ describe('Confirm Detail Row Component', function () {
wrapper.find('.confirm-detail-row__header-text').props().onClick()
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
})
-
-
})
})
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
index 74e95ece6..1dca81560 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
@@ -17,9 +17,10 @@ export default class ConfirmPageContainerContent extends Component {
nonce: PropTypes.string,
assetImage: PropTypes.string,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ subtitleComponent: PropTypes.node,
summaryComponent: PropTypes.node,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- titleComponent: PropTypes.func,
+ titleComponent: PropTypes.node,
warning: PropTypes.string,
}
@@ -54,7 +55,9 @@ export default class ConfirmPageContainerContent extends Component {
errorKey,
errorMessage,
title,
+ titleComponent,
subtitle,
+ subtitleComponent,
hideSubtitle,
identiconAddress,
nonce,
@@ -80,7 +83,9 @@ export default class ConfirmPageContainerContent extends Component {
})}
action={action}
title={title}
+ titleComponent={titleComponent}
subtitle={subtitle}
+ subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
identiconAddress={identiconAddress}
nonce={nonce}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
index 38b158fd3..89ceb015f 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
@@ -4,7 +4,18 @@ import classnames from 'classnames'
import Identicon from '../../../identicon'
const ConfirmPageContainerSummary = props => {
- const { action, title, subtitle, hideSubtitle, className, identiconAddress, nonce, assetImage } = props
+ const {
+ action,
+ title,
+ titleComponent,
+ subtitle,
+ subtitleComponent,
+ hideSubtitle,
+ className,
+ identiconAddress,
+ nonce,
+ assetImage,
+ } = props
return (
<div className={classnames('confirm-page-container-summary', className)}>
@@ -32,12 +43,12 @@ const ConfirmPageContainerSummary = props => {
)
}
<div className="confirm-page-container-summary__title-text">
- { title }
+ { titleComponent || title }
</div>
</div>
{
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
- { subtitle }
+ { subtitleComponent || subtitle }
</div>
}
</div>
@@ -47,7 +58,9 @@ const ConfirmPageContainerSummary = props => {
ConfirmPageContainerSummary.propTypes = {
action: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ titleComponent: PropTypes.node,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ subtitleComponent: PropTypes.node,
hideSubtitle: PropTypes.bool,
className: PropTypes.string,
identiconAddress: PropTypes.string,
diff --git a/ui/app/components/confirm-page-container/confirm-page-container.component.js b/ui/app/components/confirm-page-container/confirm-page-container.component.js
index 36d5a1f58..8b2e47cbb 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container.component.js
+++ b/ui/app/components/confirm-page-container/confirm-page-container.component.js
@@ -16,8 +16,9 @@ export default class ConfirmPageContainer extends Component {
onEdit: PropTypes.func,
showEdit: PropTypes.bool,
subtitle: PropTypes.string,
+ subtitleComponent: PropTypes.node,
title: PropTypes.string,
- titleComponent: PropTypes.func,
+ titleComponent: PropTypes.node,
// Sender to Recipient
fromAddress: PropTypes.string,
fromName: PropTypes.string,
@@ -65,6 +66,7 @@ export default class ConfirmPageContainer extends Component {
title,
titleComponent,
subtitle,
+ subtitleComponent,
hideSubtitle,
summaryComponent,
detailsComponent,
@@ -101,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
title={title}
titleComponent={titleComponent}
subtitle={subtitle}
+ subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
summaryComponent={summaryComponent}
detailsComponent={detailsComponent}
diff --git a/ui/app/components/currency-display/currency-display.component.js b/ui/app/components/currency-display/currency-display.component.js
index e4eb58a2a..2d7413b57 100644
--- a/ui/app/components/currency-display/currency-display.component.js
+++ b/ui/app/components/currency-display/currency-display.component.js
@@ -1,14 +1,18 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import { ETH, GWEI } from '../../constants/common'
+import classnames from 'classnames'
+import { GWEI } from '../../constants/common'
export default class CurrencyDisplay extends PureComponent {
static propTypes = {
className: PropTypes.string,
displayValue: PropTypes.string,
prefix: PropTypes.string,
+ prefixComponent: PropTypes.node,
+ style: PropTypes.object,
+ suffix: PropTypes.string,
// Used in container
- currency: PropTypes.oneOf([ETH]),
+ currency: PropTypes.string,
denomination: PropTypes.oneOf([GWEI]),
value: PropTypes.string,
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -16,15 +20,25 @@ export default class CurrencyDisplay extends PureComponent {
}
render () {
- const { className, displayValue, prefix } = this.props
+ const { className, displayValue, prefix, prefixComponent, style, suffix } = this.props
const text = `${prefix || ''}${displayValue}`
+ const title = `${text} ${suffix}`
return (
<div
- className={className}
- title={text}
+ className={classnames('currency-display-component', className)}
+ style={style}
+ title={title}
>
- { text }
+ { prefixComponent}
+ <span className="currency-display-component__text">{ text }</span>
+ {
+ suffix && (
+ <span className="currency-display-component__suffix">
+ { suffix }
+ </span>
+ )
+ }
</div>
)
}
diff --git a/ui/app/components/currency-display/currency-display.container.js b/ui/app/components/currency-display/currency-display.container.js
index 6644a1099..6ddf07172 100644
--- a/ui/app/components/currency-display/currency-display.container.js
+++ b/ui/app/components/currency-display/currency-display.container.js
@@ -2,20 +2,41 @@ import { connect } from 'react-redux'
import CurrencyDisplay from './currency-display.component'
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
-const mapStateToProps = (state, ownProps) => {
- const { value, numberOfDecimals = 2, currency, denomination, hideLabel } = ownProps
- const { metamask: { currentCurrency, conversionRate } } = state
+const mapStateToProps = state => {
+ const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
+
+ return {
+ currentCurrency,
+ conversionRate,
+ nativeCurrency,
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { nativeCurrency, currentCurrency, conversionRate, ...restStateProps } = stateProps
+ const {
+ value,
+ numberOfDecimals = 2,
+ currency,
+ denomination,
+ hideLabel,
+ ...restOwnProps
+ } = ownProps
const toCurrency = currency || currentCurrency
const convertedValue = getValueFromWeiHex({
- value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
+ value, fromCurrency: nativeCurrency, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
})
- const formattedValue = formatCurrency(convertedValue, toCurrency)
- const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
+ const displayValue = formatCurrency(convertedValue, toCurrency)
+ const suffix = hideLabel ? undefined : toCurrency.toUpperCase()
return {
+ ...restStateProps,
+ ...dispatchProps,
+ ...restOwnProps,
displayValue,
+ suffix,
}
}
-export default connect(mapStateToProps)(CurrencyDisplay)
+export default connect(mapStateToProps, null, mergeProps)(CurrencyDisplay)
diff --git a/ui/app/components/currency-display/index.scss b/ui/app/components/currency-display/index.scss
new file mode 100644
index 000000000..313c932b8
--- /dev/null
+++ b/ui/app/components/currency-display/index.scss
@@ -0,0 +1,14 @@
+.currency-display-component {
+ display: flex;
+ align-items: center;
+
+ &__text {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__suffix {
+ padding-left: 4px;
+ }
+}
diff --git a/ui/app/components/currency-display/tests/currency-display.container.test.js b/ui/app/components/currency-display/tests/currency-display.container.test.js
index 5265bbb04..0c886af50 100644
--- a/ui/app/components/currency-display/tests/currency-display.container.test.js
+++ b/ui/app/components/currency-display/tests/currency-display.container.test.js
@@ -1,12 +1,13 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
-let mapStateToProps
+let mapStateToProps, mergeProps
proxyquire('../currency-display.container.js', {
'react-redux': {
- connect: ms => {
+ connect: (ms, md, mp) => {
mapStateToProps = ms
+ mergeProps = mp
return () => ({})
},
},
@@ -19,86 +20,125 @@ describe('CurrencyDisplay container', () => {
metamask: {
conversionRate: 280.45,
currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
},
}
+ assert.deepEqual(mapStateToProps(mockState), {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ })
+ })
+ })
+
+ describe('mergeProps()', () => {
+ it('should return the correct props', () => {
+ const mockStateProps = {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ }
+
const tests = [
{
props: {
value: '0x2386f26fc10000',
numberOfDecimals: 2,
currency: 'usd',
+ nativeCurrency: 'ETH',
},
result: {
- displayValue: '$2.80 USD',
+ displayValue: '$2.80',
+ suffix: 'USD',
+ nativeCurrency: 'ETH',
},
},
{
props: {
value: '0x2386f26fc10000',
+ currency: 'usd',
+ nativeCurrency: 'ETH',
},
result: {
- displayValue: '$2.80 USD',
+ displayValue: '$2.80',
+ suffix: 'USD',
+ nativeCurrency: 'ETH',
},
},
{
props: {
value: '0x1193461d01595930',
currency: 'ETH',
+ nativeCurrency: 'ETH',
numberOfDecimals: 3,
},
result: {
- displayValue: '1.266 ETH',
+ displayValue: '1.266',
+ suffix: 'ETH',
+ nativeCurrency: 'ETH',
},
},
{
props: {
value: '0x1193461d01595930',
currency: 'ETH',
+ nativeCurrency: 'ETH',
numberOfDecimals: 3,
hideLabel: true,
},
result: {
+ nativeCurrency: 'ETH',
displayValue: '1.266',
+ suffix: undefined,
},
},
{
props: {
value: '0x3b9aca00',
currency: 'ETH',
+ nativeCurrency: 'ETH',
denomination: 'GWEI',
hideLabel: true,
},
result: {
+ nativeCurrency: 'ETH',
displayValue: '1',
+ suffix: undefined,
},
},
{
props: {
value: '0x3b9aca00',
currency: 'ETH',
+ nativeCurrency: 'ETH',
denomination: 'WEI',
hideLabel: true,
},
result: {
+ nativeCurrency: 'ETH',
displayValue: '1000000000',
+ suffix: undefined,
},
},
{
props: {
value: '0x3b9aca00',
currency: 'ETH',
+ nativeCurrency: 'ETH',
numberOfDecimals: 100,
hideLabel: true,
},
result: {
+ nativeCurrency: 'ETH',
displayValue: '1e-9',
+ suffix: undefined,
},
},
]
tests.forEach(({ props, result }) => {
- assert.deepEqual(mapStateToProps(mockState, props), result)
+ assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result)
})
})
})
diff --git a/ui/app/components/currency-input/currency-input.component.js b/ui/app/components/currency-input/currency-input.component.js
new file mode 100644
index 000000000..0761a75c5
--- /dev/null
+++ b/ui/app/components/currency-input/currency-input.component.js
@@ -0,0 +1,121 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import UnitInput from '../unit-input'
+import CurrencyDisplay from '../currency-display'
+import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
+import { ETH } from '../../constants/common'
+
+/**
+ * Component that allows user to enter currency values as a number, and props receive a converted
+ * hex value in WEI. props.value, used as a default or forced value, should be a hex value, which
+ * gets converted into a decimal value depending on the currency (ETH or Fiat).
+ */
+export default class CurrencyInput extends PureComponent {
+ static propTypes = {
+ conversionRate: PropTypes.number,
+ currentCurrency: PropTypes.string,
+ nativeCurrency: PropTypes.string,
+ onChange: PropTypes.func,
+ onBlur: PropTypes.func,
+ suffix: PropTypes.string,
+ useFiat: PropTypes.bool,
+ value: PropTypes.string,
+ }
+
+ constructor (props) {
+ super(props)
+
+ const { value: hexValue } = props
+ const decimalValue = hexValue ? this.getDecimalValue(props) : 0
+
+ this.state = {
+ decimalValue,
+ hexValue,
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ const { value: prevPropsHexValue } = prevProps
+ const { value: propsHexValue } = this.props
+ const { hexValue: stateHexValue } = this.state
+
+ if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
+ const decimalValue = this.getDecimalValue(this.props)
+ this.setState({ hexValue: propsHexValue, decimalValue })
+ }
+ }
+
+ getDecimalValue (props) {
+ const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
+ const decimalValueString = useFiat
+ ? getValueFromWeiHex({
+ value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
+ })
+ : getValueFromWeiHex({
+ value: hexValue, toCurrency: ETH, numberOfDecimals: 6,
+ })
+
+ return Number(decimalValueString) || 0
+ }
+
+ handleChange = decimalValue => {
+ const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
+
+ const hexValue = useFiat
+ ? getWeiHexFromDecimalValue({
+ value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
+ })
+ : getWeiHexFromDecimalValue({
+ value: decimalValue, fromCurrency: ETH, fromDenomination: ETH, conversionRate,
+ })
+
+ this.setState({ hexValue, decimalValue })
+ onChange(hexValue)
+ }
+
+ handleBlur = () => {
+ this.props.onBlur(this.state.hexValue)
+ }
+
+ renderConversionComponent () {
+ const { useFiat, currentCurrency, nativeCurrency } = this.props
+ const { hexValue } = this.state
+ let currency, numberOfDecimals
+
+ if (useFiat) {
+ // Display ETH
+ currency = nativeCurrency || ETH
+ numberOfDecimals = 6
+ } else {
+ // Display Fiat
+ currency = currentCurrency
+ numberOfDecimals = 2
+ }
+
+ return (
+ <CurrencyDisplay
+ className="currency-input__conversion-component"
+ currency={currency}
+ value={hexValue}
+ numberOfDecimals={numberOfDecimals}
+ />
+ )
+ }
+
+ render () {
+ const { suffix, ...restProps } = this.props
+ const { decimalValue } = this.state
+
+ return (
+ <UnitInput
+ {...restProps}
+ suffix={suffix}
+ onChange={this.handleChange}
+ onBlur={this.handleBlur}
+ value={decimalValue}
+ >
+ { this.renderConversionComponent() }
+ </UnitInput>
+ )
+ }
+}
diff --git a/ui/app/components/currency-input/currency-input.container.js b/ui/app/components/currency-input/currency-input.container.js
new file mode 100644
index 000000000..1d1ed7b41
--- /dev/null
+++ b/ui/app/components/currency-input/currency-input.container.js
@@ -0,0 +1,28 @@
+import { connect } from 'react-redux'
+import CurrencyInput from './currency-input.component'
+import { ETH } from '../../constants/common'
+
+const mapStateToProps = state => {
+ const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
+
+ return {
+ nativeCurrency,
+ currentCurrency,
+ conversionRate,
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { nativeCurrency, currentCurrency } = stateProps
+ const { useFiat } = ownProps
+ const suffix = useFiat ? currentCurrency.toUpperCase() : nativeCurrency || ETH
+
+ return {
+ ...stateProps,
+ ...dispatchProps,
+ ...ownProps,
+ suffix,
+ }
+}
+
+export default connect(mapStateToProps, null, mergeProps)(CurrencyInput)
diff --git a/ui/app/components/currency-input/index.js b/ui/app/components/currency-input/index.js
new file mode 100644
index 000000000..d8069fb67
--- /dev/null
+++ b/ui/app/components/currency-input/index.js
@@ -0,0 +1 @@
+export { default } from './currency-input.container'
diff --git a/ui/app/components/currency-input/index.scss b/ui/app/components/currency-input/index.scss
new file mode 100644
index 000000000..fcb2db461
--- /dev/null
+++ b/ui/app/components/currency-input/index.scss
@@ -0,0 +1,7 @@
+.currency-input {
+ &__conversion-component {
+ font-size: 12px;
+ line-height: 12px;
+ padding-left: 1px;
+ }
+}
diff --git a/ui/app/components/currency-input/tests/currency-input.component.test.js b/ui/app/components/currency-input/tests/currency-input.component.test.js
new file mode 100644
index 000000000..a33889f94
--- /dev/null
+++ b/ui/app/components/currency-input/tests/currency-input.component.test.js
@@ -0,0 +1,251 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow, mount } from 'enzyme'
+import sinon from 'sinon'
+import { Provider } from 'react-redux'
+import configureMockStore from 'redux-mock-store'
+import CurrencyInput from '../currency-input.component'
+import UnitInput from '../../unit-input'
+import CurrencyDisplay from '../../currency-display'
+
+describe('CurrencyInput Component', () => {
+ describe('rendering', () => {
+ it('should render properly without a suffix', () => {
+ const wrapper = shallow(
+ <CurrencyInput />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(UnitInput).length, 1)
+ })
+
+ it('should render properly with a suffix', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ suffix="ETH"
+ nativeCurrency="ETH"
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
+ assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ })
+
+ it('should render properly with an ETH value', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ value="de0b6b3a7640000"
+ suffix="ETH"
+ nativeCurrency="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '1')
+ assert.equal(wrapper.find('.currency-display-component').text(), '$231.06USD')
+ })
+
+ it('should render properly with a fiat value', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ value="f602f2234d0ea"
+ suffix="USD"
+ useFiat
+ nativeCurrency="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'USD')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '1')
+ assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH')
+ })
+ })
+
+ describe('handling actions', () => {
+ const handleChangeSpy = sinon.spy()
+ const handleBlurSpy = sinon.spy()
+
+ afterEach(() => {
+ handleChangeSpy.resetHistory()
+ handleBlurSpy.resetHistory()
+ })
+
+ it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ suffix="ETH"
+ nativeCurrency="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 0)
+ assert.equal(currencyInputInstance.state.hexValue, undefined)
+ assert.equal(wrapper.find('.currency-display-component').text(), '$0.00USD')
+ const input = wrapper.find('input')
+ assert.equal(input.props().value, 0)
+
+ input.simulate('change', { target: { value: 1 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000'))
+ assert.equal(wrapper.find('.currency-display-component').text(), '$231.06USD')
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
+
+ assert.equal(handleBlurSpy.callCount, 0)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith('de0b6b3a7640000'))
+ })
+
+ it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ suffix="USD"
+ nativeCurrency="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ useFiat
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 0)
+ assert.equal(currencyInputInstance.state.hexValue, undefined)
+ assert.equal(wrapper.find('.currency-display-component').text(), '0ETH')
+ const input = wrapper.find('input')
+ assert.equal(input.props().value, 0)
+
+ input.simulate('change', { target: { value: 1 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith('f602f2234d0ea'))
+ assert.equal(wrapper.find('.currency-display-component').text(), '0.004328ETH')
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
+
+ assert.equal(handleBlurSpy.callCount, 0)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith('f602f2234d0ea'))
+ })
+
+ it('should change the state and pass in a new decimalValue when props.value changes', () => {
+ const mockStore = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = shallow(
+ <Provider store={store}>
+ <CurrencyInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ suffix="USD"
+ nativeCurrency="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ useFiat
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const currencyInputInstance = wrapper.find(CurrencyInput).dive()
+ assert.equal(currencyInputInstance.state('decimalValue'), 0)
+ assert.equal(currencyInputInstance.state('hexValue'), undefined)
+ assert.equal(currencyInputInstance.find(UnitInput).props().value, 0)
+
+ currencyInputInstance.setProps({ value: '1ec05e43e72400' })
+ currencyInputInstance.update()
+ assert.equal(currencyInputInstance.state('decimalValue'), 2)
+ assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
+ assert.equal(currencyInputInstance.find(UnitInput).props().value, 2)
+ })
+ })
+})
diff --git a/ui/app/components/currency-input/tests/currency-input.container.test.js b/ui/app/components/currency-input/tests/currency-input.container.test.js
new file mode 100644
index 000000000..5d72958e6
--- /dev/null
+++ b/ui/app/components/currency-input/tests/currency-input.container.test.js
@@ -0,0 +1,60 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps, mergeProps
+
+proxyquire('../currency-input.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+})
+
+describe('CurrencyInput container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props', () => {
+ const mockState = {
+ metamask: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ })
+ })
+ })
+
+ describe('mergeProps()', () => {
+ it('should return the correct props', () => {
+ const mockStateProps = {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ }
+ const mockDispatchProps = {}
+
+ assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ useFiat: true,
+ suffix: 'USD',
+ })
+
+ assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ nativeCurrency: 'ETH',
+ suffix: 'ETH',
+ })
+ })
+ })
+})
diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js
index b497f5c09..e6b3e0c0c 100644
--- a/ui/app/components/dropdowns/components/account-dropdowns.js
+++ b/ui/app/components/dropdowns/components/account-dropdowns.js
@@ -6,7 +6,7 @@ const genAccountLink = require('../../../../lib/account-link.js')
const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
-const Identicon = require('../../identicon')
+import Identicon from '../../identicon'
const { checksumAddress } = require('../../../util')
const copyToClipboard = require('copy-to-clipboard')
const { formatBalance } = require('../../../util')
@@ -26,14 +26,14 @@ class AccountDropdowns extends Component {
}
renderAccounts () {
- const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props
+ const { identities, accounts, selected, menuItemStyles, actions, keyrings, ticker } = this.props
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
const isSelected = identity.address === selected
const balanceValue = accounts[key].balance
- const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
+ const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, true, ticker) : '...'
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
@@ -421,6 +421,7 @@ AccountDropdowns.propTypes = {
network: PropTypes.number,
// actions.showExportPrivateKeyModal: ,
style: PropTypes.object,
+ ticker: PropTypes.string,
enableAccountsSelector: PropTypes.bool,
enableAccountOption: PropTypes.bool,
enableAccountOptions: PropTypes.bool,
@@ -458,6 +459,7 @@ const mapDispatchToProps = (dispatch) => {
function mapStateToProps (state) {
return {
+ ticker: state.metamask.ticker,
keyrings: state.metamask.keyrings,
sidebarOpen: state.appState.sidebar.isOpen,
}
diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js
index b252b25d9..d4cc695a6 100644
--- a/ui/app/components/dropdowns/network-dropdown.js
+++ b/ui/app/components/dropdowns/network-dropdown.js
@@ -24,8 +24,9 @@ const notToggleElementClassnames = [
function mapStateToProps (state) {
return {
provider: state.metamask.provider,
- frequentRpcList: state.metamask.frequentRpcList || [],
+ frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
networkDropdownOpen: state.appState.networkDropdownOpen,
+ network: state.metamask.network,
}
}
@@ -40,8 +41,8 @@ function mapDispatchToProps (dispatch) {
setDefaultRpcTarget: type => {
dispatch(actions.setDefaultRpcTarget(type))
},
- setRpcTarget: (target) => {
- dispatch(actions.setRpcTarget(target))
+ setRpcTarget: (target, network, ticker, nickname) => {
+ dispatch(actions.setRpcTarget(target, network, ticker, nickname))
},
delRpcTarget: (target) => {
dispatch(actions.delRpcTarget(target))
@@ -71,7 +72,7 @@ module.exports = compose(
NetworkDropdown.prototype.render = function () {
const props = this.props
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
- const rpcList = props.frequentRpcList
+ const rpcListDetail = props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = {
fontSize: '16px',
@@ -225,7 +226,7 @@ NetworkDropdown.prototype.render = function () {
),
this.renderCustomOption(props.provider),
- this.renderCommonRpc(rpcList, props.provider),
+ this.renderCommonRpc(rpcListDetail, props.provider),
h(
DropdownMenuItem,
@@ -267,28 +268,33 @@ NetworkDropdown.prototype.getNetworkName = function () {
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
} else {
- name = this.context.t('unknownNetwork')
+ name = provider.nickname || this.context.t('unknownNetwork')
}
return name
}
-NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
+NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) {
const props = this.props
- const reversedRpcList = rpcList.slice().reverse()
+ const reversedRpcListDetail = rpcListDetail.slice().reverse()
+ const network = props.network
- return reversedRpcList.map((rpc) => {
+ return reversedRpcListDetail.map((entry) => {
+ const rpc = entry.rpcUrl
+ const ticker = entry.ticker || 'ETH'
+ const nickname = entry.nickname || ''
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
if ((rpc === 'http://localhost:8545') || currentRpcTarget) {
return null
} else {
+ const chainId = entry.chainId || network
return h(
DropdownMenuItem,
{
key: `common${rpc}`,
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => props.setRpcTarget(rpc),
+ onClick: () => props.setRpcTarget(rpc, chainId, ticker, nickname),
style: {
fontSize: '16px',
lineHeight: '20px',
@@ -302,7 +308,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
style: {
color: currentRpcTarget ? '#ffffff' : '#9b9b9b',
},
- }, rpc),
+ }, nickname || rpc),
h('i.fa.fa-times.delete',
{
onClick: (e) => {
@@ -317,8 +323,9 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
}
NetworkDropdown.prototype.renderCustomOption = function (provider) {
- const { rpcTarget, type } = provider
+ const { rpcTarget, type, ticker, nickname } = provider
const props = this.props
+ const network = props.network
if (type !== 'rpc') return null
@@ -332,7 +339,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
DropdownMenuItem,
{
key: rpcTarget,
- onClick: () => props.setRpcTarget(rpcTarget),
+ onClick: () => props.setRpcTarget(rpcTarget, network, ticker, nickname),
closeMenu: () => this.props.hideNetworkDropdown(),
style: {
fontSize: '16px',
@@ -347,7 +354,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
style: {
color: '#ffffff',
},
- }, rpcTarget),
+ }, nickname || rpcTarget),
]
)
}
diff --git a/ui/app/components/dropdowns/tests/network-dropdown.test.js b/ui/app/components/dropdowns/tests/network-dropdown.test.js
index 699b54605..88ad56851 100644
--- a/ui/app/components/dropdowns/tests/network-dropdown.test.js
+++ b/ui/app/components/dropdowns/tests/network-dropdown.test.js
@@ -45,8 +45,8 @@ describe('Network Dropdown', () => {
provider: {
'type': 'test',
},
- frequentRpcList: [
- 'http://localhost:7545',
+ frequentRpcListDetail: [
+ { rpcUrl: 'http://localhost:7545' },
],
},
appState: {
diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js
index c3d084bdc..2f6395a2d 100644
--- a/ui/app/components/eth-balance.js
+++ b/ui/app/components/eth-balance.js
@@ -1,5 +1,6 @@
const { Component } = require('react')
const h = require('react-hyperscript')
+const connect = require('react-redux').connect
const { inherits } = require('util')
const {
formatBalance,
@@ -8,7 +9,12 @@ const {
const Tooltip = require('./tooltip.js')
const FiatValue = require('./fiat-value.js')
-module.exports = EthBalanceComponent
+module.exports = connect(mapStateToProps)(EthBalanceComponent)
+function mapStateToProps (state) {
+ return {
+ ticker: state.metamask.ticker,
+ }
+}
inherits(EthBalanceComponent, Component)
function EthBalanceComponent () {
@@ -17,9 +23,9 @@ function EthBalanceComponent () {
EthBalanceComponent.prototype.render = function () {
const props = this.props
- const { value, style, width, needsParse = true } = props
+ const { ticker, value, style, width, needsParse = true } = props
- const formattedValue = value ? formatBalance(value, 6, needsParse) : '...'
+ const formattedValue = value ? formatBalance(value, 6, needsParse, ticker) : '...'
return (
diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js
deleted file mode 100644
index 7bd921892..000000000
--- a/ui/app/components/identicon.js
+++ /dev/null
@@ -1,124 +0,0 @@
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const connect = require('react-redux').connect
-const isNode = require('detect-node')
-const findDOMNode = require('react-dom').findDOMNode
-const jazzicon = require('jazzicon')
-const iconFactoryGen = require('../../lib/icon-factory')
-const iconFactory = iconFactoryGen(jazzicon)
-const { toDataUrl } = require('../../lib/blockies')
-
-module.exports = connect(mapStateToProps)(IdenticonComponent)
-
-inherits(IdenticonComponent, Component)
-function IdenticonComponent () {
- Component.call(this)
-
- this.defaultDiameter = 46
-}
-
-function mapStateToProps (state) {
- return {
- useBlockie: state.metamask.useBlockie,
- }
-}
-
-IdenticonComponent.prototype.render = function () {
- var props = this.props
- const { className = '', address, image } = props
- var diameter = props.diameter || this.defaultDiameter
- const style = {
- height: diameter,
- width: diameter,
- borderRadius: diameter / 2,
- }
- if (image) {
- return h('img', {
- className: `${className} identicon`,
- src: image,
- style: {
- ...style,
- },
- })
- } else if (address) {
- return h('div', {
- className: `${className} identicon`,
- key: 'identicon-' + address,
- style: {
- display: 'flex',
- flexShrink: 0,
- alignItems: 'center',
- justifyContent: 'center',
- ...style,
- overflow: 'hidden',
- },
- })
- } else {
- return h('img.balance-icon', {
- className,
- src: './images/eth_logo.svg',
- style: {
- ...style,
- },
- })
- }
-}
-
-IdenticonComponent.prototype.componentDidMount = function () {
- var props = this.props
- const { address, useBlockie } = props
-
- if (!address) return
-
- if (!isNode) {
- // eslint-disable-next-line react/no-find-dom-node
- var container = findDOMNode(this)
-
- const diameter = props.diameter || this.defaultDiameter
-
- if (useBlockie) {
- _generateBlockie(container, address, diameter)
- } else {
- _generateJazzicon(container, address, diameter)
- }
- }
-}
-
-IdenticonComponent.prototype.componentDidUpdate = function () {
- var props = this.props
- const { address, useBlockie } = props
-
- if (!address) return
-
- if (!isNode) {
- // eslint-disable-next-line react/no-find-dom-node
- var container = findDOMNode(this)
-
- var children = container.children
- for (var i = 0; i < children.length; i++) {
- container.removeChild(children[i])
- }
-
- const diameter = props.diameter || this.defaultDiameter
-
- if (useBlockie) {
- _generateBlockie(container, address, diameter)
- } else {
- _generateJazzicon(container, address, diameter)
- }
- }
-}
-
-function _generateBlockie (container, address, diameter) {
- const img = new Image()
- img.src = toDataUrl(address)
- img.height = diameter
- img.width = diameter
- container.appendChild(img)
-}
-
-function _generateJazzicon (container, address, diameter) {
- const img = iconFactory.iconForAddress(address, diameter)
- container.appendChild(img)
-}
diff --git a/ui/app/components/identicon/identicon.component.js b/ui/app/components/identicon/identicon.component.js
new file mode 100644
index 000000000..b892e5ae5
--- /dev/null
+++ b/ui/app/components/identicon/identicon.component.js
@@ -0,0 +1,99 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { toDataUrl } from '../../../lib/blockies'
+import contractMap from 'eth-contract-metadata'
+import { checksumAddress } from '../../../app/util'
+import Jazzicon from '../jazzicon'
+
+const getStyles = diameter => (
+ {
+ height: diameter,
+ width: diameter,
+ borderRadius: diameter / 2,
+ }
+)
+
+export default class Identicon extends PureComponent {
+ static propTypes = {
+ address: PropTypes.string,
+ className: PropTypes.string,
+ diameter: PropTypes.number,
+ image: PropTypes.string,
+ useBlockie: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ diameter: 46,
+ }
+
+ renderImage () {
+ const { className, diameter, image } = this.props
+
+ return (
+ <img
+ className={classnames('identicon', className)}
+ src={image}
+ style={getStyles(diameter)}
+ />
+ )
+ }
+
+ renderJazzicon () {
+ const { address, className, diameter } = this.props
+
+ return (
+ <Jazzicon
+ address={address}
+ diameter={diameter}
+ className={classnames('identicon', className)}
+ style={getStyles(diameter)}
+ />
+ )
+ }
+
+ renderBlockie () {
+ const { address, className, diameter } = this.props
+
+ return (
+ <div
+ className={classnames('identicon', className)}
+ style={getStyles(diameter)}
+ >
+ <img
+ src={toDataUrl(address)}
+ height={diameter}
+ width={diameter}
+ />
+ </div>
+ )
+ }
+
+ render () {
+ const { className, address, image, diameter, useBlockie } = this.props
+
+ if (image) {
+ return this.renderImage()
+ }
+
+ if (address) {
+ const checksummedAddress = checksumAddress(address)
+
+ if (contractMap[checksummedAddress] && contractMap[checksummedAddress].logo) {
+ return this.renderJazzicon()
+ }
+
+ return useBlockie
+ ? this.renderBlockie()
+ : this.renderJazzicon()
+ }
+
+ return (
+ <img
+ className={classnames('balance-icon', className)}
+ src="./images/eth_logo.svg"
+ style={getStyles(diameter)}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/identicon/identicon.container.js b/ui/app/components/identicon/identicon.container.js
new file mode 100644
index 000000000..bc49bc18e
--- /dev/null
+++ b/ui/app/components/identicon/identicon.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import Identicon from './identicon.component'
+
+const mapStateToProps = state => {
+ const { metamask: { useBlockie } } = state
+
+ return {
+ useBlockie,
+ }
+}
+
+export default connect(mapStateToProps)(Identicon)
diff --git a/ui/app/components/identicon/index.js b/ui/app/components/identicon/index.js
new file mode 100644
index 000000000..799c886f2
--- /dev/null
+++ b/ui/app/components/identicon/index.js
@@ -0,0 +1 @@
+export { default } from './identicon.container'
diff --git a/ui/app/components/identicon/index.scss b/ui/app/components/identicon/index.scss
new file mode 100644
index 000000000..657afc48f
--- /dev/null
+++ b/ui/app/components/identicon/index.scss
@@ -0,0 +1,7 @@
+.identicon {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+}
diff --git a/ui/app/components/identicon/tests/identicon.component.test.js b/ui/app/components/identicon/tests/identicon.component.test.js
new file mode 100644
index 000000000..2944818f5
--- /dev/null
+++ b/ui/app/components/identicon/tests/identicon.component.test.js
@@ -0,0 +1,51 @@
+import React from 'react'
+import assert from 'assert'
+import thunk from 'redux-thunk'
+import configureMockStore from 'redux-mock-store'
+import { mount } from 'enzyme'
+import Identicon from '../identicon.component'
+
+describe('Identicon', () => {
+ const state = {
+ metamask: {
+ useBlockie: false,
+ },
+ }
+
+ const middlewares = [thunk]
+ const mockStore = configureMockStore(middlewares)
+ const store = mockStore(state)
+
+ it('renders default eth_logo identicon with no props', () => {
+ const wrapper = mount(
+ <Identicon store={store}/>
+ )
+
+ assert.equal(wrapper.find('img.balance-icon').prop('src'), './images/eth_logo.svg')
+ })
+
+ it('renders custom image and add className props', () => {
+ const wrapper = mount(
+ <Identicon
+ store={store}
+ className="test-image"
+ image="test-image"
+ />
+ )
+
+ assert.equal(wrapper.find('img.test-image').prop('className'), 'identicon test-image')
+ assert.equal(wrapper.find('img.test-image').prop('src'), 'test-image')
+ })
+
+ it('renders div with address prop', () => {
+ const wrapper = mount(
+ <Identicon
+ store={store}
+ className="test-address"
+ address="0xTest"
+ />
+ )
+
+ assert.equal(wrapper.find('div.test-address').prop('className'), 'identicon test-address')
+ })
+})
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index 21b65bf55..e27b0f182 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -1,15 +1,23 @@
@import './app-header/index';
+@import './add-token-button/index';
+
@import './button-group/index';
@import './card/index';
@import './confirm-page-container/index';
+@import './currency-input/index';
+
+@import './currency-display/index';
+
@import './error-message/index';
@import './export-text-container/index';
+@import './identicon/index';
+
@import './info-box/index';
@import './menu-bar/index';
@@ -24,6 +32,8 @@
@import './pages/index';
+@import './provider-page-container/index';
+
@import './selected-account/index';
@import './sender-to-recipient/index';
@@ -49,3 +59,5 @@
@import './app-header/index';
@import './sidebars/index';
+
+@import './unit-input/index';
diff --git a/ui/app/components/jazzicon/index.js b/ui/app/components/jazzicon/index.js
new file mode 100644
index 000000000..bea900ab9
--- /dev/null
+++ b/ui/app/components/jazzicon/index.js
@@ -0,0 +1 @@
+export { default } from './jazzicon.component'
diff --git a/ui/app/components/jazzicon/jazzicon.component.js b/ui/app/components/jazzicon/jazzicon.component.js
new file mode 100644
index 000000000..fcb1c59b1
--- /dev/null
+++ b/ui/app/components/jazzicon/jazzicon.component.js
@@ -0,0 +1,69 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import isNode from 'detect-node'
+import { findDOMNode } from 'react-dom'
+import jazzicon from 'jazzicon'
+import iconFactoryGenerator from '../../../lib/icon-factory'
+const iconFactory = iconFactoryGenerator(jazzicon)
+
+/**
+ * Wrapper around the jazzicon library to return a React component, as the library returns an
+ * HTMLDivElement which needs to be appended.
+ */
+export default class Jazzicon extends PureComponent {
+ static propTypes = {
+ address: PropTypes.string.isRequired,
+ className: PropTypes.string,
+ diameter: PropTypes.number,
+ style: PropTypes.object,
+ }
+
+ static defaultProps = {
+ diameter: 46,
+ }
+
+ componentDidMount () {
+ if (!isNode) {
+ this.appendJazzicon()
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ const { address: prevAddress } = prevProps
+ const { address } = this.props
+
+ if (!isNode && address !== prevAddress) {
+ this.removeExistingChildren()
+ this.appendJazzicon()
+ }
+ }
+
+ removeExistingChildren () {
+ // eslint-disable-next-line react/no-find-dom-node
+ const container = findDOMNode(this)
+ const { children } = container
+
+ for (let i = 0; i < children.length; i++) {
+ container.removeChild(children[i])
+ }
+ }
+
+ appendJazzicon () {
+ // eslint-disable-next-line react/no-find-dom-node
+ const container = findDOMNode(this)
+ const { address, diameter } = this.props
+ const image = iconFactory.iconForAddress(address, diameter)
+ container.appendChild(image)
+ }
+
+ render () {
+ const { className, style } = this.props
+
+ return (
+ <div
+ className={className}
+ style={style}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/modals/account-modal-container.js b/ui/app/components/modals/account-modal-container.js
index aa0593df8..2a6c655e1 100644
--- a/ui/app/components/modals/account-modal-container.js
+++ b/ui/app/components/modals/account-modal-container.js
@@ -5,7 +5,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const { getSelectedIdentity } = require('../../selectors')
-const Identicon = require('../identicon')
+import Identicon from '../identicon'
function mapStateToProps (state, ownProps) {
return {
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
index b082db1d0..b973f221c 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import CurrencyDisplay from '../../../currency-display'
-import { ETH } from '../../../../constants/common'
+import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../../constants/common'
export default class CancelTransaction extends PureComponent {
static propTypes = {
@@ -13,15 +13,15 @@ export default class CancelTransaction extends PureComponent {
return (
<div className="cancel-transaction-gas-fee">
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="cancel-transaction-gas-fee__eth"
- currency={ETH}
value={value}
- numberOfDecimals={6}
+ type={PRIMARY}
/>
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="cancel-transaction-gas-fee__fiat"
value={value}
+ type={SECONDARY}
/>
</div>
)
diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
index 994c2a577..014815503 100644
--- a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
+++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component'
-import CurrencyDisplay from '../../../../currency-display'
+import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
describe('CancelTransactionGasFee Component', () => {
it('should render', () => {
@@ -13,12 +13,11 @@ describe('CancelTransactionGasFee Component', () => {
)
assert.ok(wrapper)
- assert.equal(wrapper.find(CurrencyDisplay).length, 2)
- const ethDisplay = wrapper.find(CurrencyDisplay).at(0)
- const fiatDisplay = wrapper.find(CurrencyDisplay).at(1)
+ assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
+ const ethDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(0)
+ const fiatDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(1)
assert.equal(ethDisplay.props().value, '0x3b9aca00')
- assert.equal(ethDisplay.props().currency, 'ETH')
assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth')
assert.equal(fiatDisplay.props().value, '0x3b9aca00')
diff --git a/ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js b/ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js
new file mode 100644
index 000000000..ceaa20a95
--- /dev/null
+++ b/ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js
@@ -0,0 +1,39 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Modal, { ModalContent } from '../../modal'
+
+export default class ClearApprovedOrigins extends PureComponent {
+ static propTypes = {
+ hideModal: PropTypes.func.isRequired,
+ clearApprovedOrigins: PropTypes.func.isRequired,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ handleClear = () => {
+ const { clearApprovedOrigins, hideModal } = this.props
+ clearApprovedOrigins()
+ hideModal()
+ }
+
+ render () {
+ const { t } = this.context
+
+ return (
+ <Modal
+ onSubmit={this.handleClear}
+ onCancel={() => this.props.hideModal()}
+ submitText={t('ok')}
+ cancelText={t('nevermind')}
+ submitType="secondary"
+ >
+ <ModalContent
+ title={t('clearApprovalData')}
+ description={t('confirmClear')}
+ />
+ </Modal>
+ )
+ }
+}
diff --git a/ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js b/ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js
new file mode 100644
index 000000000..3a801a062
--- /dev/null
+++ b/ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js
@@ -0,0 +1,16 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import withModalProps from '../../../higher-order-components/with-modal-props'
+import ClearApprovedOriginsComponent from './clear-approved-origins.component'
+import { clearApprovedOrigins } from '../../../actions'
+
+const mapDispatchToProps = dispatch => {
+ return {
+ clearApprovedOrigins: () => dispatch(clearApprovedOrigins()),
+ }
+}
+
+export default compose(
+ withModalProps,
+ connect(null, mapDispatchToProps)
+)(ClearApprovedOriginsComponent)
diff --git a/ui/app/components/modals/clear-approved-origins/index.js b/ui/app/components/modals/clear-approved-origins/index.js
new file mode 100644
index 000000000..b3e321995
--- /dev/null
+++ b/ui/app/components/modals/clear-approved-origins/index.js
@@ -0,0 +1 @@
+export { default } from './clear-approved-origins.container'
diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js
index fb38516d3..43f3009a5 100644
--- a/ui/app/components/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/modals/hide-token-confirmation-modal.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
-const Identicon = require('../identicon')
+import Identicon from '../identicon'
function mapStateToProps (state) {
return {
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 15ca9deaa..5aff4f5e1 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -27,8 +27,8 @@ import TransactionConfirmed from './transaction-confirmed'
import ConfirmCustomizeGasModal from './customize-gas'
import CancelTransaction from './cancel-transaction'
import WelcomeBeta from './welcome-beta'
-import TransactionDetails from './transaction-details'
import RejectTransactions from './reject-transactions'
+import ClearApprovedOrigins from './clear-approved-origins'
const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)',
@@ -213,6 +213,19 @@ const MODALS = {
},
},
+ CLEAR_APPROVED_ORIGINS: {
+ contents: h(ClearApprovedOrigins),
+ mobileModalStyle: {
+ ...modalContainerMobileStyle,
+ },
+ laptopModalStyle: {
+ ...modalContainerLaptopStyle,
+ },
+ contentStyle: {
+ borderRadius: '8px',
+ },
+ },
+
OLD_UI_NOTIFICATION_MODAL: {
contents: [
h(NotifcationModal, {
@@ -366,19 +379,6 @@ const MODALS = {
},
},
- TRANSACTION_DETAILS: {
- contents: h(TransactionDetails),
- mobileModalStyle: {
- ...modalContainerMobileStyle,
- },
- laptopModalStyle: {
- ...modalContainerLaptopStyle,
- },
- contentStyle: {
- borderRadius: '8px',
- },
- },
-
REJECT_TRANSACTIONS: {
contents: h(RejectTransactions),
mobileModalStyle: {
diff --git a/ui/app/components/modals/transaction-details/index.js b/ui/app/components/modals/transaction-details/index.js
deleted file mode 100644
index 1fc42c662..000000000
--- a/ui/app/components/modals/transaction-details/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './transaction-details.container'
diff --git a/ui/app/components/modals/transaction-details/transaction-details.component.js b/ui/app/components/modals/transaction-details/transaction-details.component.js
deleted file mode 100644
index f2fec3409..000000000
--- a/ui/app/components/modals/transaction-details/transaction-details.component.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import Modal from '../../modal'
-import TransactionListItemDetails from '../../transaction-list-item-details'
-import { hexToDecimal } from '../../../helpers/conversions.util'
-
-export default class TransactionConfirmed extends PureComponent {
- static contextTypes = {
- t: PropTypes.func,
- }
-
- static propTypes = {
- hideModal: PropTypes.func,
- transaction: PropTypes.object,
- onRetry: PropTypes.func,
- showRetry: PropTypes.bool,
- onCancel: PropTypes.func,
- showCancel: PropTypes.bool,
- }
-
- handleSubmit = () => {
- this.props.hideModal()
- }
-
- handleRetry = () => {
- const { onRetry, hideModal } = this.props
-
- Promise.resolve(onRetry()).then(() => hideModal())
- }
-
- render () {
- const { t } = this.context
- const { transaction, showRetry, onCancel, showCancel } = this.props
- const { txParams: { nonce } = {} } = transaction
- const decimalNonce = nonce && hexToDecimal(nonce)
-
- return (
- <Modal
- onSubmit={this.handleSubmit}
- onClose={this.handleSubmit}
- submitText={t('ok')}
- headerText={t('transactionWithNonce', [`#${decimalNonce}`])}
- >
- <TransactionListItemDetails
- transaction={transaction}
- onRetry={this.handleRetry}
- showRetry={showRetry}
- onCancel={() => onCancel()}
- showCancel={showCancel}
- />
- </Modal>
- )
- }
-}
diff --git a/ui/app/components/modals/transaction-details/transaction-details.container.js b/ui/app/components/modals/transaction-details/transaction-details.container.js
deleted file mode 100644
index f212920bb..000000000
--- a/ui/app/components/modals/transaction-details/transaction-details.container.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import TransactionDetails from './transaction-details.component'
-import withModalProps from '../../../higher-order-components/with-modal-props'
-
-export default withModalProps(TransactionDetails)
diff --git a/ui/app/components/network-display/index.scss b/ui/app/components/network-display/index.scss
index 2085cff67..e9f2f2057 100644
--- a/ui/app/components/network-display/index.scss
+++ b/ui/app/components/network-display/index.scss
@@ -3,11 +3,14 @@
display: flex;
align-items: center;
justify-content: flex-start;
- background-color: lighten(rgb(125, 128, 130), 45%);
padding: 0 10px;
border-radius: 4px;
height: 25px;
+ &--colored {
+ background-color: lighten(rgb(125, 128, 130), 45%);
+ }
+
&--mainnet {
background-color: lighten($blue-lagoon, 68%);
}
diff --git a/ui/app/components/network-display/network-display.component.js b/ui/app/components/network-display/network-display.component.js
index 38626af20..22d617099 100644
--- a/ui/app/components/network-display/network-display.component.js
+++ b/ui/app/components/network-display/network-display.component.js
@@ -16,7 +16,12 @@ const networkToClassHash = {
}
export default class NetworkDisplay extends Component {
+ static defaultProps = {
+ colored: true,
+ }
+
static propTypes = {
+ colored: PropTypes.bool,
network: PropTypes.string,
provider: PropTypes.object,
}
@@ -41,14 +46,16 @@ export default class NetworkDisplay extends Component {
}
render () {
- const { network, provider: { type } } = this.props
+ const { colored, network, provider: { type, nickname } } = this.props
const networkClass = networkToClassHash[network]
return (
- <div className={classnames(
- 'network-display__container',
- networkClass && ('network-display__container--' + networkClass)
- )}>
+ <div
+ className={classnames('network-display__container', {
+ 'network-display__container--colored': colored,
+ ['network-display__container--' + networkClass]: colored && networkClass,
+ })}
+ >
{
networkClass
? <div className={`network-display__icon network-display__icon--${networkClass}`} />
@@ -61,7 +68,7 @@ export default class NetworkDisplay extends Component {
/>
}
<div className="network-display__name">
- { this.context.t(type) }
+ { type === 'rpc' && nickname ? nickname : this.context.t(type) }
</div>
</div>
)
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index 83297c4f2..611aadb7b 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -23,9 +23,10 @@ Network.prototype.render = function () {
const props = this.props
const context = this.context
const networkNumber = props.network
- let providerName
+ let providerName, providerNick
try {
providerName = props.provider.type
+ providerNick = props.provider.nickname || ''
} catch (e) {
providerName = null
}
@@ -131,7 +132,7 @@ Network.prototype.render = function () {
},
}),
- h('.network-name', context.t('privateNetwork')),
+ h('.network-name', providerNick || context.t('privateNetwork')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
}
diff --git a/ui/app/components/page-container/index.scss b/ui/app/components/page-container/index.scss
index 6742e3082..ba1215e84 100644
--- a/ui/app/components/page-container/index.scss
+++ b/ui/app/components/page-container/index.scss
@@ -101,6 +101,7 @@
font-size: 2rem;
font-weight: 500;
line-height: 2rem;
+ margin-right: 1.5rem;
}
&__subtitle {
diff --git a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
index acaed383a..7f1fb4e49 100644
--- a/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
+++ b/ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
@@ -1,12 +1,15 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmTransactionBase from '../confirm-transaction-base'
+import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import {
formatCurrency,
convertTokenToFiat,
addFiat,
roundExponential,
} from '../../../helpers/confirm-transaction/util'
+import { getWeiHexFromDecimalValue } from '../../../helpers/conversions.util'
+import { ETH, PRIMARY } from '../../../constants/common'
export default class ConfirmTokenTransactionBase extends Component {
static contextTypes = {
@@ -36,19 +39,48 @@ export default class ConfirmTokenTransactionBase extends Component {
})
}
- getSubtitle () {
- const { currentCurrency, contractExchangeRate } = this.props
+ renderSubtitleComponent () {
+ const { contractExchangeRate, tokenAmount } = this.props
- if (typeof contractExchangeRate === 'undefined') {
- return this.context.t('noConversionRateAvailable')
- } else {
- const fiatTransactionAmount = this.getFiatTransactionAmount()
- const roundedFiatTransactionAmount = roundExponential(fiatTransactionAmount)
- return formatCurrency(roundedFiatTransactionAmount, currentCurrency)
- }
+ const decimalEthValue = (tokenAmount * contractExchangeRate) || 0
+ const hexWeiValue = getWeiHexFromDecimalValue({
+ value: decimalEthValue,
+ fromCurrency: ETH,
+ fromDenomination: ETH,
+ })
+
+ return typeof contractExchangeRate === 'undefined'
+ ? (
+ <span>
+ { this.context.t('noConversionRateAvailable') }
+ </span>
+ ) : (
+ <UserPreferencedCurrencyDisplay
+ value={hexWeiValue}
+ type={PRIMARY}
+ showEthLogo
+ hideLabel
+ />
+ )
+ }
+
+ renderPrimaryTotalTextOverride () {
+ const { tokenAmount, tokenSymbol, ethTransactionTotal } = this.props
+ const tokensText = `${tokenAmount} ${tokenSymbol}`
+
+ return (
+ <div>
+ <span>{ `${tokensText} + ` }</span>
+ <img
+ src="/images/eth.svg"
+ height="18"
+ />
+ <span>{ ethTransactionTotal }</span>
+ </div>
+ )
}
- getFiatTotalTextOverride () {
+ getSecondaryTotalTextOverride () {
const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
if (typeof contractExchangeRate === 'undefined') {
@@ -67,7 +99,6 @@ export default class ConfirmTokenTransactionBase extends Component {
tokenAddress,
tokenSymbol,
tokenAmount,
- ethTransactionTotal,
...restProps
} = this.props
@@ -78,9 +109,9 @@ export default class ConfirmTokenTransactionBase extends Component {
toAddress={toAddress}
identiconAddress={tokenAddress}
title={tokensText}
- subtitle={this.getSubtitle()}
- ethTotalTextOverride={`${tokensText} + \u2666 ${ethTransactionTotal}`}
- fiatTotalTextOverride={this.getFiatTotalTextOverride()}
+ subtitleComponent={this.renderSubtitleComponent()}
+ primaryTotalTextOverride={this.renderPrimaryTotalTextOverride()}
+ secondaryTotalTextOverride={this.getSecondaryTotalTextOverride()}
{...restProps}
/>
)
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 707dad62d..7d01aaffb 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -1,7 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
-import { formatCurrency } from '../../../helpers/confirm-transaction/util'
import { isBalanceSufficient } from '../../send/send.utils'
import { DEFAULT_ROUTE } from '../../../routes'
import {
@@ -9,6 +8,8 @@ import {
TRANSACTION_ERROR_KEY,
} from '../../../constants/error-keys'
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
+import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../constants/common'
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
@@ -36,7 +37,9 @@ export default class ConfirmTransactionBase extends Component {
fiatTransactionTotal: PropTypes.string,
fromAddress: PropTypes.string,
fromName: PropTypes.string,
- hexGasTotal: PropTypes.string,
+ hexTransactionAmount: PropTypes.string,
+ hexTransactionFee: PropTypes.string,
+ hexTransactionTotal: PropTypes.string,
isTxReprice: PropTypes.bool,
methodData: PropTypes.object,
nonce: PropTypes.string,
@@ -59,8 +62,8 @@ export default class ConfirmTransactionBase extends Component {
detailsComponent: PropTypes.node,
errorKey: PropTypes.string,
errorMessage: PropTypes.string,
- ethTotalTextOverride: PropTypes.string,
- fiatTotalTextOverride: PropTypes.string,
+ primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ secondaryTotalTextOverride: PropTypes.string,
hideData: PropTypes.bool,
hideDetails: PropTypes.bool,
hideSubtitle: PropTypes.bool,
@@ -70,8 +73,10 @@ export default class ConfirmTransactionBase extends Component {
onEditGas: PropTypes.func,
onSubmit: PropTypes.func,
subtitle: PropTypes.string,
+ subtitleComponent: PropTypes.node,
summaryComponent: PropTypes.node,
title: PropTypes.string,
+ titleComponent: PropTypes.node,
valid: PropTypes.bool,
warning: PropTypes.string,
}
@@ -105,7 +110,7 @@ export default class ConfirmTransactionBase extends Component {
const {
balance,
conversionRate,
- hexGasTotal,
+ hexTransactionFee,
txData: {
simulationFails,
txParams: {
@@ -116,7 +121,7 @@ export default class ConfirmTransactionBase extends Component {
const insufficientBalance = balance && !isBalanceSufficient({
amount,
- gasTotal: hexGasTotal || '0x0',
+ gasTotal: hexTransactionFee || '0x0',
balance,
conversionRate,
})
@@ -131,7 +136,7 @@ export default class ConfirmTransactionBase extends Component {
if (simulationFails) {
return {
valid: true,
- errorKey: TRANSACTION_ERROR_KEY,
+ errorKey: simulationFails.errorKey ? simulationFails.errorKey : TRANSACTION_ERROR_KEY,
}
}
@@ -153,13 +158,10 @@ export default class ConfirmTransactionBase extends Component {
renderDetails () {
const {
detailsComponent,
- fiatTransactionFee,
- ethTransactionFee,
- currentCurrency,
- fiatTransactionTotal,
- ethTransactionTotal,
- fiatTotalTextOverride,
- ethTotalTextOverride,
+ primaryTotalTextOverride,
+ secondaryTotalTextOverride,
+ hexTransactionFee,
+ hexTransactionTotal,
hideDetails,
} = this.props
@@ -167,16 +169,13 @@ export default class ConfirmTransactionBase extends Component {
return null
}
- const formattedCurrency = formatCurrency(fiatTransactionTotal, currentCurrency)
-
return (
detailsComponent || (
<div className="confirm-page-container-content__details">
<div className="confirm-page-container-content__gas-fee">
<ConfirmDetailRow
label="Gas Fee"
- fiatText={formatCurrency(fiatTransactionFee, currentCurrency)}
- ethText={`\u2666 ${ethTransactionFee}`}
+ value={hexTransactionFee}
headerText="Edit"
headerTextClassName="confirm-detail-row__header-text--edit"
onHeaderClick={() => this.handleEditGas()}
@@ -185,11 +184,12 @@ export default class ConfirmTransactionBase extends Component {
<div>
<ConfirmDetailRow
label="Total"
- fiatText={fiatTotalTextOverride || formattedCurrency}
- ethText={ethTotalTextOverride || `\u2666 ${ethTransactionTotal}`}
+ value={hexTransactionTotal}
+ primaryText={primaryTotalTextOverride}
+ secondaryText={secondaryTotalTextOverride}
headerText="Amount + Gas Fee"
headerTextClassName="confirm-detail-row__header-text--total"
- fiatTextColor="#2f9ae0"
+ primaryValueTextColor="#2f9ae0"
/>
</div>
</div>
@@ -311,6 +311,43 @@ export default class ConfirmTransactionBase extends Component {
}
}
+ renderTitleComponent () {
+ const { title, titleComponent, hexTransactionAmount } = this.props
+
+ // Title string passed in by props takes priority
+ if (title) {
+ return null
+ }
+
+ return titleComponent || (
+ <UserPreferencedCurrencyDisplay
+ value={hexTransactionAmount}
+ type={PRIMARY}
+ showEthLogo
+ ethLogoHeight="26"
+ hideLabel
+ />
+ )
+ }
+
+ renderSubtitleComponent () {
+ const { subtitle, subtitleComponent, hexTransactionAmount } = this.props
+
+ // Subtitle string passed in by props takes priority
+ if (subtitle) {
+ return null
+ }
+
+ return subtitleComponent || (
+ <UserPreferencedCurrencyDisplay
+ value={hexTransactionAmount}
+ type={SECONDARY}
+ showEthLogo
+ hideLabel
+ />
+ )
+ }
+
render () {
const {
isTxReprice,
@@ -319,12 +356,9 @@ export default class ConfirmTransactionBase extends Component {
toName,
toAddress,
methodData,
- ethTransactionAmount,
- fiatTransactionAmount,
valid: propsValid = true,
errorMessage,
errorKey: propsErrorKey,
- currentCurrency,
action,
title,
subtitle,
@@ -341,7 +375,6 @@ export default class ConfirmTransactionBase extends Component {
const { submitting, submitError } = this.state
const { name } = methodData
- const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
const { valid, errorKey } = this.getErrorKey()
return (
@@ -352,8 +385,10 @@ export default class ConfirmTransactionBase extends Component {
toAddress={toAddress}
showEdit={onEdit && !isTxReprice}
action={action || name || this.context.t('unknownFunction')}
- title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`}
- subtitle={subtitle || `\u2666 ${ethTransactionAmount}`}
+ title={title}
+ titleComponent={this.renderTitleComponent()}
+ subtitle={subtitle}
+ subtitleComponent={this.renderSubtitleComponent()}
hideSubtitle={hideSubtitle}
summaryComponent={summaryComponent}
detailsComponent={this.renderDetails()}
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
index b34067686..c366d5137 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -36,7 +36,9 @@ const mapStateToProps = (state, props) => {
fiatTransactionAmount,
fiatTransactionFee,
fiatTransactionTotal,
- hexGasTotal,
+ hexTransactionAmount,
+ hexTransactionFee,
+ hexTransactionTotal,
tokenData,
methodData,
txData,
@@ -87,7 +89,9 @@ const mapStateToProps = (state, props) => {
fiatTransactionAmount,
fiatTransactionFee,
fiatTransactionTotal,
- hexGasTotal,
+ hexTransactionAmount,
+ hexTransactionFee,
+ hexTransactionTotal,
txData,
tokenData,
methodData,
diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js
index d3c71c4f6..b9ec3c258 100644
--- a/ui/app/components/pages/home/home.component.js
+++ b/ui/app/components/pages/home/home.component.js
@@ -4,6 +4,8 @@ import Media from 'react-media'
import { Redirect } from 'react-router-dom'
import WalletView from '../../wallet-view'
import TransactionView from '../../transaction-view'
+import ProviderApproval from '../provider-approval'
+
import {
INITIALIZE_BACKUP_PHRASE_ROUTE,
RESTORE_VAULT_ROUTE,
@@ -21,6 +23,7 @@ export default class Home extends PureComponent {
seedWords: PropTypes.string,
suggestedTokens: PropTypes.object,
unconfirmedTransactionsCount: PropTypes.number,
+ providerRequests: PropTypes.array,
}
componentDidMount () {
@@ -46,6 +49,7 @@ export default class Home extends PureComponent {
lostAccounts,
forgottenPassword,
seedWords,
+ providerRequests,
} = this.props
// notices
@@ -62,6 +66,12 @@ export default class Home extends PureComponent {
return <Redirect to={{ pathname: RESTORE_VAULT_ROUTE }} />
}
+ if (providerRequests && providerRequests.length > 0) {
+ return (
+ <ProviderApproval providerRequest={providerRequests[0]} />
+ )
+ }
+
return (
<div className="main-container">
<div className="account-and-transaction-details">
diff --git a/ui/app/components/pages/home/home.container.js b/ui/app/components/pages/home/home.container.js
index 58001df6b..bb8cf5e81 100644
--- a/ui/app/components/pages/home/home.container.js
+++ b/ui/app/components/pages/home/home.container.js
@@ -11,6 +11,7 @@ const mapStateToProps = state => {
lostAccounts,
seedWords,
suggestedTokens,
+ providerRequests,
} = metamask
const { forgottenPassword } = appState
@@ -21,6 +22,7 @@ const mapStateToProps = state => {
seedWords,
suggestedTokens,
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
+ providerRequests,
}
}
diff --git a/ui/app/components/pages/provider-approval/index.js b/ui/app/components/pages/provider-approval/index.js
new file mode 100644
index 000000000..4162f3155
--- /dev/null
+++ b/ui/app/components/pages/provider-approval/index.js
@@ -0,0 +1 @@
+export { default } from './provider-approval.container'
diff --git a/ui/app/components/pages/provider-approval/provider-approval.component.js b/ui/app/components/pages/provider-approval/provider-approval.component.js
new file mode 100644
index 000000000..da98bc3fc
--- /dev/null
+++ b/ui/app/components/pages/provider-approval/provider-approval.component.js
@@ -0,0 +1,28 @@
+import PropTypes from 'prop-types'
+import React, { Component } from 'react'
+import ProviderPageContainer from '../../provider-page-container'
+
+export default class ProviderApproval extends Component {
+ static propTypes = {
+ approveProviderRequest: PropTypes.func.isRequired,
+ providerRequest: PropTypes.object.isRequired,
+ rejectProviderRequest: PropTypes.func.isRequired,
+ };
+
+ static contextTypes = {
+ t: PropTypes.func,
+ };
+
+ render () {
+ const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props
+ return (
+ <ProviderPageContainer
+ approveProviderRequest={approveProviderRequest}
+ origin={providerRequest.origin}
+ rejectProviderRequest={rejectProviderRequest}
+ siteImage={providerRequest.siteImage}
+ siteTitle={providerRequest.siteTitle}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/pages/provider-approval/provider-approval.container.js b/ui/app/components/pages/provider-approval/provider-approval.container.js
new file mode 100644
index 000000000..b223244a1
--- /dev/null
+++ b/ui/app/components/pages/provider-approval/provider-approval.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import ProviderApproval from './provider-approval.component'
+import { approveProviderRequest, rejectProviderRequest } from '../../../actions'
+
+function mapDispatchToProps (dispatch) {
+ return {
+ approveProviderRequest: origin => dispatch(approveProviderRequest(origin)),
+ rejectProviderRequest: origin => dispatch(rejectProviderRequest(origin)),
+ }
+}
+
+export default connect(null, mapDispatchToProps)(ProviderApproval)
diff --git a/ui/app/components/pages/settings/settings-tab/index.scss b/ui/app/components/pages/settings/settings-tab/index.scss
index 76a0cec6f..ef32b0e4c 100644
--- a/ui/app/components/pages/settings/settings-tab/index.scss
+++ b/ui/app/components/pages/settings/settings-tab/index.scss
@@ -5,12 +5,9 @@
color: $crimson;
}
- &__rpc-save-button {
- align-self: flex-end;
- padding: 5px;
- text-transform: uppercase;
- color: $dusty-gray;
- cursor: pointer;
+ &__advanced-link {
+ color: $curious-blue;
+ padding-left: 5px;
}
&__rpc-save-button {
@@ -19,6 +16,9 @@
text-transform: uppercase;
color: $dusty-gray;
cursor: pointer;
+ width: 25%;
+ min-width: 80px;
+ height: 33px;
}
&__button--red {
@@ -48,4 +48,22 @@
border-color: $ecstasy;
}
}
+
+ &__radio-buttons {
+ display: flex;
+ align-items: center;
+ }
+
+ &__radio-button {
+ display: flex;
+ align-items: center;
+
+ &:not(:last-child) {
+ margin-right: 16px;
+ }
+ }
+
+ &__radio-label {
+ padding-left: 4px;
+ }
}
diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
index 9da624f56..a0a8ed47e 100644
--- a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
+++ b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
@@ -39,12 +39,15 @@ export default class SettingsTab extends PureComponent {
metamask: PropTypes.object,
setUseBlockie: PropTypes.func,
setHexDataFeatureFlag: PropTypes.func,
+ setPrivacyMode: PropTypes.func,
+ privacyMode: PropTypes.bool,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
delRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
+ showClearApprovalModal: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
history: PropTypes.object,
@@ -55,10 +58,17 @@ export default class SettingsTab extends PureComponent {
sendHexData: PropTypes.bool,
currentCurrency: PropTypes.string,
conversionDate: PropTypes.number,
+ nativeCurrency: PropTypes.string,
+ useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
+ setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func,
}
state = {
newRpc: '',
+ chainId: '',
+ showOptions: false,
+ ticker: '',
+ nickname: '',
}
renderCurrentConversion () {
@@ -119,37 +129,98 @@ export default class SettingsTab extends PureComponent {
renderNewRpcUrl () {
const { t } = this.context
- const { newRpc } = this.state
+ const { newRpc, chainId, ticker, nickname } = this.state
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
- <span>{ t('newRPC') }</span>
+ <span>{ t('newNetwork') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
id="new-rpc"
- placeholder={t('newRPC')}
+ placeholder={t('rpcURL')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
- this.validateRpc(newRpc)
+ this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
fullWidth
- margin="none"
+ margin="dense"
/>
- <div
- className="settings-tab__rpc-save-button"
- onClick={e => {
- e.preventDefault()
- this.validateRpc(newRpc)
+ <TextField
+ type="text"
+ id="chainid"
+ placeholder={t('optionalChainId')}
+ value={chainId}
+ onChange={e => this.setState({ chainId: e.target.value })}
+ onKeyPress={e => {
+ if (e.key === 'Enter') {
+ this.validateRpc(newRpc, chainId, ticker, nickname)
+ }
}}
- >
- { t('save') }
+ style={{
+ display: this.state.showOptions ? null : 'none',
+ }}
+ fullWidth
+ margin="dense"
+ />
+ <TextField
+ type="text"
+ id="ticker"
+ placeholder={t('optionalSymbol')}
+ value={ticker}
+ onChange={e => this.setState({ ticker: e.target.value })}
+ onKeyPress={e => {
+ if (e.key === 'Enter') {
+ this.validateRpc(newRpc, chainId, ticker, nickname)
+ }
+ }}
+ style={{
+ display: this.state.showOptions ? null : 'none',
+ }}
+ fullWidth
+ margin="dense"
+ />
+ <TextField
+ type="text"
+ id="nickname"
+ placeholder={t('optionalNickname')}
+ value={nickname}
+ onChange={e => this.setState({ nickname: e.target.value })}
+ onKeyPress={e => {
+ if (e.key === 'Enter') {
+ this.validateRpc(newRpc, chainId, ticker, nickname)
+ }
+ }}
+ style={{
+ display: this.state.showOptions ? null : 'none',
+ }}
+ fullWidth
+ margin="dense"
+ />
+ <div className="flex-row flex-align-center space-between">
+ <span className="settings-tab__advanced-link"
+ onClick={e => {
+ e.preventDefault()
+ this.setState({ showOptions: !this.state.showOptions })
+ }}
+ >
+ { t(this.state.showOptions ? 'hideAdvancedOptions' : 'showAdvancedOptions') }
+ </span>
+ <button
+ className="button btn-primary settings-tab__rpc-save-button"
+ onClick={e => {
+ e.preventDefault()
+ this.validateRpc(newRpc, chainId, ticker, nickname)
+ }}
+ >
+ { t('save') }
+ </button>
</div>
</div>
</div>
@@ -157,11 +228,11 @@ export default class SettingsTab extends PureComponent {
)
}
- validateRpc (newRpc) {
+ validateRpc (newRpc, chainId, ticker = 'ETH', nickname) {
const { setRpcTarget, displayWarning } = this.props
if (validUrl.isWebUri(newRpc)) {
- setRpcTarget(newRpc)
+ setRpcTarget(newRpc, chainId, ticker, nickname)
} else {
const appendedRpc = `http://${newRpc}`
@@ -208,6 +279,36 @@ export default class SettingsTab extends PureComponent {
)
}
+ renderClearApproval () {
+ const { t } = this.context
+ const { showClearApprovalModal } = this.props
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('approvalData') }</span>
+ <span className="settings-page__content-description">
+ { t('approvalDataDescription') }
+ </span>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <Button
+ type="secondary"
+ large
+ className="settings-tab__button--orange"
+ onClick={event => {
+ event.preventDefault()
+ showClearApprovalModal()
+ }}
+ >
+ { t('clearApprovalData') }
+ </Button>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
renderSeedWords () {
const { t } = this.context
const { history } = this.props
@@ -339,6 +440,86 @@ export default class SettingsTab extends PureComponent {
)
}
+ renderUsePrimaryCurrencyOptions () {
+ const { t } = this.context
+ const {
+ nativeCurrency,
+ setUseNativeCurrencyAsPrimaryCurrencyPreference,
+ useNativeCurrencyAsPrimaryCurrency,
+ } = this.props
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('primaryCurrencySetting') }</span>
+ <div className="settings-page__content-description">
+ { t('primaryCurrencySettingDescription') }
+ </div>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <div className="settings-tab__radio-buttons">
+ <div className="settings-tab__radio-button">
+ <input
+ type="radio"
+ id="native-primary-currency"
+ onChange={() => setUseNativeCurrencyAsPrimaryCurrencyPreference(true)}
+ checked={Boolean(useNativeCurrencyAsPrimaryCurrency)}
+ />
+ <label
+ htmlFor="native-primary-currency"
+ className="settings-tab__radio-label"
+ >
+ { nativeCurrency }
+ </label>
+ </div>
+ <div className="settings-tab__radio-button">
+ <input
+ type="radio"
+ id="fiat-primary-currency"
+ onChange={() => setUseNativeCurrencyAsPrimaryCurrencyPreference(false)}
+ checked={!useNativeCurrencyAsPrimaryCurrency}
+ />
+ <label
+ htmlFor="fiat-primary-currency"
+ className="settings-tab__radio-label"
+ >
+ { t('fiat') }
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ renderPrivacyOptIn () {
+ const { t } = this.context
+ const { privacyMode, setPrivacyMode } = this.props
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('privacyMode') }</span>
+ <div className="settings-page__content-description">
+ { t('privacyModeDescription') }
+ </div>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <ToggleButton
+ value={privacyMode}
+ onToggle={value => setPrivacyMode(!value)}
+ activeLabel=""
+ inactiveLabel=""
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+
render () {
const { warning, isMascara } = this.props
@@ -346,14 +527,17 @@ export default class SettingsTab extends PureComponent {
<div className="settings-page__content">
{ warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderCurrentConversion() }
+ { this.renderUsePrimaryCurrencyOptions() }
{ this.renderCurrentLocale() }
{ this.renderNewRpcUrl() }
{ this.renderStateLogs() }
{ this.renderSeedWords() }
{ !isMascara && this.renderOldUI() }
{ this.renderResetAccount() }
- { this.renderBlockieOptIn() }
+ { this.renderClearApproval() }
+ { this.renderPrivacyOptIn() }
{ this.renderHexDataOptIn() }
+ { this.renderBlockieOptIn() }
</div>
)
}
diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
index 665b56f5c..b6c33a5b2 100644
--- a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
+++ b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
@@ -11,19 +11,26 @@ import {
updateCurrentLocale,
setFeatureFlag,
showModal,
+ setUseNativeCurrencyAsPrimaryCurrencyPreference,
} from '../../../../actions'
+import { preferencesSelector } from '../../../../selectors'
const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
const {
currentCurrency,
conversionDate,
+ nativeCurrency,
useBlockie,
- featureFlags: { sendHexData } = {},
+ featureFlags: {
+ sendHexData,
+ privacyMode,
+ } = {},
provider = {},
isMascara,
currentLocale,
} = metamask
+ const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
return {
warning,
@@ -31,16 +38,19 @@ const mapStateToProps = state => {
currentLocale,
currentCurrency,
conversionDate,
+ nativeCurrency,
useBlockie,
sendHexData,
+ privacyMode,
provider,
+ useNativeCurrencyAsPrimaryCurrency,
}
}
const mapDispatchToProps = dispatch => {
return {
setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)),
- setRpcTarget: newRpc => dispatch(setRpcTarget(newRpc)),
+ setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(setRpcTarget(newRpc, chainId, ticker, nickname)),
displayWarning: warning => dispatch(displayWarning(warning)),
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
setUseBlockie: value => dispatch(setUseBlockie(value)),
@@ -49,7 +59,12 @@ const mapDispatchToProps = dispatch => {
return dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
},
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
+ setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
+ setUseNativeCurrencyAsPrimaryCurrencyPreference: value => {
+ return dispatch(setUseNativeCurrencyAsPrimaryCurrencyPreference(value))
+ },
+ showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })),
}
}
diff --git a/ui/app/components/provider-page-container/index.js b/ui/app/components/provider-page-container/index.js
new file mode 100644
index 000000000..927c35940
--- /dev/null
+++ b/ui/app/components/provider-page-container/index.js
@@ -0,0 +1,3 @@
+export {default} from './provider-page-container.component'
+export {default as ProviderPageContainerContent} from './provider-page-container-content'
+export {default as ProviderPageContainerHeader} from './provider-page-container-header'
diff --git a/ui/app/components/provider-page-container/index.scss b/ui/app/components/provider-page-container/index.scss
new file mode 100644
index 000000000..8d35ac179
--- /dev/null
+++ b/ui/app/components/provider-page-container/index.scss
@@ -0,0 +1,121 @@
+.provider-approval-container {
+ display: flex;
+
+ &__header {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ border-bottom: 1px solid $geyser;
+ padding: 9px;
+ }
+
+ &__content {
+ display: flex;
+ overflow-y: auto;
+ flex: 1;
+ flex-direction: column;
+ justify-content: space-between;
+ color: #7C808E;
+
+ h1, h2 {
+ color: #4A4A4A;
+ display: flex;
+ justify-content: center;
+ text-align: center;
+ }
+
+ h2 {
+ font-size: 16px;
+ line-height: 18px;
+ padding: 20px;
+ }
+
+ h1 {
+ font-size: 22px;
+ line-height: 26px;
+ padding: 20px;
+ }
+
+ p {
+ padding: 0 40px;
+ text-align: center;
+ font-size: 12px;
+ line-height: 18px;
+ }
+
+ a, a:hover {
+ color: $dodger-blue;
+ }
+
+ .provider-approval-visual {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ position: relative;
+ margin: 0 32px;
+
+ section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ }
+
+ h1 {
+ font-size: 14px;
+ line-height: 18px;
+ padding: 8px 0 0;
+ }
+
+ h2 {
+ font-size: 10px;
+ line-height: 14px;
+ padding: 0;
+ color: #A2A4AC;
+ }
+
+ &__check {
+ width: 40px;
+ height: 40px;
+ background: white url("/images/provider-approval-check.svg") no-repeat;
+ margin-top: 14px;
+ }
+
+ &__identicon {
+ width: 64px;
+ height: 64px;
+
+ &--default {
+ background-color: #777A87;
+ color: white;
+ width: 64px;
+ height: 64px;
+ border-radius: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+ }
+ }
+
+ &:before {
+ border-top: 2px dashed #CDD1E4;
+ content: "";
+ margin: 0 auto;
+ position: absolute;
+ top: 32px;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ width: 65%;
+ z-index: -1;
+ }
+ }
+
+ .secure-badge {
+ display: flex;
+ justify-content: center;
+ padding: 25px;
+ }
+ }
+}
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/index.js b/ui/app/components/provider-page-container/provider-page-container-content/index.js
new file mode 100644
index 000000000..73e491adc
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-content/index.js
@@ -0,0 +1 @@
+export {default} from './provider-page-container-content.container'
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js
new file mode 100644
index 000000000..268db613f
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js
@@ -0,0 +1,77 @@
+import PropTypes from 'prop-types'
+import React, {PureComponent} from 'react'
+import Identicon from '../../identicon'
+
+export default class ProviderPageContainerContent extends PureComponent {
+ static propTypes = {
+ origin: PropTypes.string.isRequired,
+ selectedIdentity: PropTypes.string.isRequired,
+ siteImage: PropTypes.string,
+ siteTitle: PropTypes.string.isRequired,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ };
+
+ renderConnectVisual = () => {
+ const { origin, selectedIdentity, siteImage, siteTitle } = this.props
+
+ return (
+ <div className="provider-approval-visual">
+ <section>
+ {siteImage ? (
+ <img
+ className="provider-approval-visual__identicon"
+ src={siteImage}
+ />
+ ) : (
+ <i className="provider-approval-visual__identicon--default">
+ {siteTitle.charAt(0).toUpperCase()}
+ </i>
+ )}
+ <h1>{siteTitle}</h1>
+ <h2>{origin}</h2>
+ </section>
+ <span className="provider-approval-visual__check" />
+ <section>
+ <Identicon
+ className="provider-approval-visual__identicon"
+ address={selectedIdentity.address}
+ diameter={64}
+ />
+ <h1>{selectedIdentity.name}</h1>
+ </section>
+ </div>
+ )
+ }
+
+ render () {
+ const { siteTitle } = this.props
+ const { t } = this.context
+
+ return (
+ <div className="provider-approval-container__content">
+ <section>
+ <h2>{t('connectRequest')}</h2>
+ {this.renderConnectVisual()}
+ <h1>{t('providerRequest', [siteTitle])}</h1>
+ <p>
+ {t('providerRequestInfo')}
+ <br/>
+ <a
+ href="https://medium.com/metamask/introducing-privacy-mode-42549d4870fa"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {t('learnMore')}.
+ </a>
+ </p>
+ </section>
+ <section className="secure-badge">
+ <img src="/images/mm-secure.svg" />
+ </section>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js
new file mode 100644
index 000000000..3ea1ce20e
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux'
+import ProviderPageContainerContent from './provider-page-container-content.component'
+import { getSelectedIdentity } from '../../../selectors'
+
+const mapStateToProps = (state) => {
+ return {
+ selectedIdentity: getSelectedIdentity(state),
+ }
+}
+
+export default connect(mapStateToProps)(ProviderPageContainerContent)
diff --git a/ui/app/components/provider-page-container/provider-page-container-header/index.js b/ui/app/components/provider-page-container/provider-page-container-header/index.js
new file mode 100644
index 000000000..430627d3a
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-header/index.js
@@ -0,0 +1 @@
+export {default} from './provider-page-container-header.component'
diff --git a/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js b/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js
new file mode 100644
index 000000000..41bf6c3dd
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js
@@ -0,0 +1,12 @@
+import React, {PureComponent} from 'react'
+import NetworkDisplay from '../../network-display'
+
+export default class ProviderPageContainerHeader extends PureComponent {
+ render () {
+ return (
+ <div className="provider-approval-container__header">
+ <NetworkDisplay colored={false} />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/provider-page-container/provider-page-container.component.js b/ui/app/components/provider-page-container/provider-page-container.component.js
new file mode 100644
index 000000000..902733616
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container.component.js
@@ -0,0 +1,50 @@
+import PropTypes from 'prop-types'
+import React, {PureComponent} from 'react'
+import { ProviderPageContainerContent, ProviderPageContainerHeader } from './'
+import { PageContainerFooter } from '../page-container'
+
+export default class ProviderPageContainer extends PureComponent {
+ static propTypes = {
+ approveProviderRequest: PropTypes.func.isRequired,
+ origin: PropTypes.string.isRequired,
+ rejectProviderRequest: PropTypes.func.isRequired,
+ siteImage: PropTypes.string,
+ siteTitle: PropTypes.string.isRequired,
+ };
+
+ static contextTypes = {
+ t: PropTypes.func,
+ };
+
+ onCancel = () => {
+ const { origin, rejectProviderRequest } = this.props
+ rejectProviderRequest(origin)
+ }
+
+ onSubmit = () => {
+ const { approveProviderRequest, origin } = this.props
+ approveProviderRequest(origin)
+ }
+
+ render () {
+ const {origin, siteImage, siteTitle} = this.props
+
+ return (
+ <div className="page-container provider-approval-container">
+ <ProviderPageContainerHeader />
+ <ProviderPageContainerContent
+ origin={origin}
+ siteImage={siteImage}
+ siteTitle={siteTitle}
+ />
+ <PageContainerFooter
+ onCancel={() => this.onCancel()}
+ cancelText={this.context.t('cancel')}
+ onSubmit={() => this.onSubmit()}
+ submitText={this.context.t('connect')}
+ submitButtonType="confirm"
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/send/account-list-item/account-list-item.component.js b/ui/app/components/send/account-list-item/account-list-item.component.js
index 9f4a96e61..14bb7471f 100644
--- a/ui/app/components/send/account-list-item/account-list-item.component.js
+++ b/ui/app/components/send/account-list-item/account-list-item.component.js
@@ -2,7 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { checksumAddress } from '../../../util'
import Identicon from '../../identicon'
-import CurrencyDisplay from '../currency-display'
+import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../constants/common'
export default class AccountListItem extends Component {
@@ -25,8 +26,6 @@ export default class AccountListItem extends Component {
const {
account,
className,
- conversionRate,
- currentCurrency,
displayAddress = false,
displayBalance = true,
handleClick,
@@ -57,16 +56,20 @@ export default class AccountListItem extends Component {
{ checksumAddress(address) }
</div>}
- {displayBalance && <CurrencyDisplay
- className="account-list-item__account-balances"
- conversionRate={conversionRate}
- convertedBalanceClassName="account-list-item__account-secondary-balance"
- convertedCurrency={currentCurrency}
- primaryBalanceClassName="account-list-item__account-primary-balance"
- primaryCurrency="ETH"
- readOnly={true}
- value={balance}
- />}
+ {
+ displayBalance && (
+ <div className="account-list-item__account-balances">
+ <UserPreferencedCurrencyDisplay
+ type={PRIMARY}
+ value={balance}
+ />
+ <UserPreferencedCurrencyDisplay
+ type={SECONDARY}
+ value={balance}
+ />
+ </div>
+ )
+ }
</div>)
}
diff --git a/ui/app/components/send/account-list-item/account-list-item.container.js b/ui/app/components/send/account-list-item/account-list-item.container.js
index 4b4519288..f8e73d923 100644
--- a/ui/app/components/send/account-list-item/account-list-item.container.js
+++ b/ui/app/components/send/account-list-item/account-list-item.container.js
@@ -2,6 +2,7 @@ import { connect } from 'react-redux'
import {
getConversionRate,
getCurrentCurrency,
+ getNativeCurrency,
} from '../send.selectors.js'
import AccountListItem from './account-list-item.component'
@@ -11,5 +12,6 @@ function mapStateToProps (state) {
return {
conversionRate: getConversionRate(state),
currentCurrency: getCurrentCurrency(state),
+ nativeCurrency: getNativeCurrency(state),
}
}
diff --git a/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js
index ef152d2e7..6ffc0b1c6 100644
--- a/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js
+++ b/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js
@@ -4,7 +4,7 @@ import { shallow } from 'enzyme'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
import Identicon from '../../../identicon'
-import CurrencyDisplay from '../../currency-display'
+import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
const utilsMethodStubs = {
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
@@ -28,6 +28,7 @@ describe('AccountListItem Component', function () {
className={'mockClassName'}
conversionRate={4}
currentCurrency={'mockCurrentyCurrency'}
+ nativeCurrency={'ETH'}
displayAddress={false}
displayBalance={false}
handleClick={propsMethodSpies.handleClick}
@@ -114,17 +115,11 @@ describe('AccountListItem Component', function () {
it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => {
wrapper.setProps({ displayBalance: true })
- assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
assert.deepEqual(
- wrapper.find(CurrencyDisplay).props(),
+ wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(),
{
- className: 'account-list-item__account-balances',
- conversionRate: 4,
- convertedBalanceClassName: 'account-list-item__account-secondary-balance',
- convertedCurrency: 'mockCurrentyCurrency',
- primaryBalanceClassName: 'account-list-item__account-primary-balance',
- primaryCurrency: 'ETH',
- readOnly: true,
+ type: 'PRIMARY',
value: 'mockBalance',
}
)
@@ -132,7 +127,7 @@ describe('AccountListItem Component', function () {
it('should not render a CurrencyDisplay if displayBalance is false', () => {
wrapper.setProps({ displayBalance: false })
- assert.equal(wrapper.find(CurrencyDisplay).length, 0)
+ assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0)
})
})
})
diff --git a/ui/app/components/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/components/send/account-list-item/tests/account-list-item-container.test.js
index af0859117..7c2f5fcb2 100644
--- a/ui/app/components/send/account-list-item/tests/account-list-item-container.test.js
+++ b/ui/app/components/send/account-list-item/tests/account-list-item-container.test.js
@@ -13,6 +13,7 @@ proxyquire('../account-list-item.container.js', {
'../send.selectors.js': {
getConversionRate: (s) => `mockConversionRate:${s}`,
getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`,
+ getNativeCurrency: (s) => `mockNativeCurrency:${s}`,
},
})
@@ -24,6 +25,7 @@ describe('account-list-item container', () => {
assert.deepEqual(mapStateToProps('mockState'), {
conversionRate: 'mockConversionRate:mockState',
currentCurrency: 'mockCurrentCurrency:mockState',
+ nativeCurrency: 'mockNativeCurrency:mockState',
})
})
diff --git a/ui/app/components/send/currency-display/currency-display.js b/ui/app/components/send/currency-display/currency-display.js
deleted file mode 100644
index 2b8eaa41f..000000000
--- a/ui/app/components/send/currency-display/currency-display.js
+++ /dev/null
@@ -1,186 +0,0 @@
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const { conversionUtil, multiplyCurrencies } = require('../../../conversion-util')
-const { removeLeadingZeroes } = require('../send.utils')
-const currencyFormatter = require('currency-formatter')
-const currencies = require('currency-formatter/currencies')
-const ethUtil = require('ethereumjs-util')
-const PropTypes = require('prop-types')
-
-CurrencyDisplay.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = CurrencyDisplay
-
-inherits(CurrencyDisplay, Component)
-function CurrencyDisplay () {
- Component.call(this)
-}
-
-function toHexWei (value) {
- return conversionUtil(value, {
- fromNumericBase: 'dec',
- toNumericBase: 'hex',
- toDenomination: 'WEI',
- })
-}
-
-CurrencyDisplay.prototype.componentWillMount = function () {
- this.setState({
- valueToRender: this.getValueToRender(this.props),
- })
-}
-
-CurrencyDisplay.prototype.componentWillReceiveProps = function (nextProps) {
- const currentValueToRender = this.getValueToRender(this.props)
- const newValueToRender = this.getValueToRender(nextProps)
- if (currentValueToRender !== newValueToRender) {
- this.setState({
- valueToRender: newValueToRender,
- })
- }
-}
-
-CurrencyDisplay.prototype.getAmount = function (value) {
- const { selectedToken } = this.props
- const { decimals } = selectedToken || {}
- const multiplier = Math.pow(10, Number(decimals || 0))
-
- const sendAmount = multiplyCurrencies(value || '0', multiplier, {toNumericBase: 'hex'})
-
- return selectedToken
- ? sendAmount
- : toHexWei(value)
-}
-
-CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value, readOnly }) {
- if (value === '0x0') return readOnly ? '0' : ''
- const { decimals, symbol } = selectedToken || {}
- const multiplier = Math.pow(10, Number(decimals || 0))
-
- return selectedToken
- ? conversionUtil(ethUtil.addHexPrefix(value), {
- fromNumericBase: 'hex',
- toNumericBase: 'dec',
- toCurrency: symbol,
- conversionRate: multiplier,
- invertConversionRate: true,
- })
- : conversionUtil(ethUtil.addHexPrefix(value), {
- fromNumericBase: 'hex',
- toNumericBase: 'dec',
- fromDenomination: 'WEI',
- numberOfDecimals: 9,
- conversionRate,
- })
-}
-
-CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValue) {
- const { primaryCurrency, convertedCurrency, conversionRate } = this.props
-
- if (conversionRate === 0 || conversionRate === null || conversionRate === undefined) {
- if (nonFormattedValue !== 0) {
- return null
- }
- }
-
- let convertedValue = conversionUtil(nonFormattedValue, {
- fromNumericBase: 'dec',
- fromCurrency: primaryCurrency,
- toCurrency: convertedCurrency,
- numberOfDecimals: 2,
- conversionRate,
- })
-
- convertedValue = Number(convertedValue).toFixed(2)
- const upperCaseCurrencyCode = convertedCurrency.toUpperCase()
- return currencies.find(currency => currency.code === upperCaseCurrencyCode)
- ? currencyFormatter.format(Number(convertedValue), {
- code: upperCaseCurrencyCode,
- })
- : convertedValue
- }
-
-CurrencyDisplay.prototype.handleChange = function (newVal) {
- this.setState({ valueToRender: removeLeadingZeroes(newVal) })
- this.props.onChange(this.getAmount(newVal))
-}
-
-CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) {
- const valueString = String(valueToRender)
- const valueLength = valueString.length || 1
- const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
- return (valueLength + decimalPointDeficit + 0.75) + 'ch'
-}
-
-CurrencyDisplay.prototype.onlyRenderConversions = function (convertedValueToRender) {
- const {
- convertedBalanceClassName = 'currency-display__converted-value',
- convertedCurrency,
- } = this.props
- return h('div', {
- className: convertedBalanceClassName,
- }, convertedValueToRender == null
- ? this.context.t('noConversionRateAvailable')
- : `${convertedValueToRender} ${convertedCurrency.toUpperCase()}`
-)
- }
-
-CurrencyDisplay.prototype.render = function () {
- const {
- className = 'currency-display',
- primaryBalanceClassName = 'currency-display__input',
- primaryCurrency,
- readOnly = false,
- inError = false,
- onBlur,
- step,
- } = this.props
- const { valueToRender } = this.state
-
- const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
-
- return h('div', {
- className,
- style: {
- borderColor: inError ? 'red' : null,
- },
- onClick: () => {
- this.currencyInput && this.currencyInput.focus()
- },
- }, [
-
- h('div.currency-display__primary-row', [
-
- h('div.currency-display__input-wrapper', [
-
- h('input', {
- className: primaryBalanceClassName,
- value: `${valueToRender}`,
- placeholder: '0',
- type: 'number',
- readOnly,
- ...(!readOnly ? {
- onChange: e => this.handleChange(e.target.value),
- onBlur: () => onBlur(this.getAmount(valueToRender)),
- } : {}),
- ref: input => { this.currencyInput = input },
- style: {
- width: this.getInputWidth(valueToRender, readOnly),
- },
- min: 0,
- step,
- }),
-
- h('span.currency-display__currency-symbol', primaryCurrency),
-
- ]),
-
- ]), this.onlyRenderConversions(convertedValueToRender),
-
- ])
-
-}
-
diff --git a/ui/app/components/send/currency-display/index.js b/ui/app/components/send/currency-display/index.js
deleted file mode 100644
index 5dc269c5a..000000000
--- a/ui/app/components/send/currency-display/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './currency-display.js'
diff --git a/ui/app/components/send/currency-display/tests/currency-display.test.js b/ui/app/components/send/currency-display/tests/currency-display.test.js
deleted file mode 100644
index c9560b81c..000000000
--- a/ui/app/components/send/currency-display/tests/currency-display.test.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import React from 'react'
-import assert from 'assert'
-import sinon from 'sinon'
-import { shallow, mount } from 'enzyme'
-import CurrencyDisplay from '../currency-display'
-
-describe('', () => {
-
- const token = {
- address: '0xTest',
- symbol: 'TST',
- decimals: '13',
- }
-
- it('retuns ETH value for wei value', () => {
- const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
-
- const value = wrapper.instance().getValueToRender({
- // 1000000000000000000
- value: 'DE0B6B3A7640000',
- })
-
- assert.equal(value, 1)
- })
-
- it('returns value of token based on token decimals', () => {
- const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
-
- const value = wrapper.instance().getValueToRender({
- selectedToken: token,
- // 1000000000000000000
- value: 'DE0B6B3A7640000',
- })
-
- assert.equal(value, 100000)
- })
-
- it('returns hex value with decimal adjustment', () => {
-
- const wrapper = mount(
- <CurrencyDisplay
- selectedToken={token}
- />, {context: {t: str => str + '_t'}})
-
- const value = wrapper.instance().getAmount(1)
- // 10000000000000
- assert.equal(value, '9184e72a000')
- })
-
- it('#getConvertedValueToRender converts input value based on conversionRate', () => {
-
- const wrapper = mount(
- <CurrencyDisplay
- primaryCurrency={'usd'}
- convertedCurrency={'ja'}
- conversionRate={2}
- />, {context: {t: str => str + '_t'}})
-
- const value = wrapper.instance().getConvertedValueToRender(32)
-
- assert.equal(value, 64)
- })
-
- it('#onlyRenderConversions renders single element for converted currency and value', () => {
- const wrapper = mount(
- <CurrencyDisplay
- convertedCurrency={'test'}
- />, {context: {t: str => str + '_t'}})
-
- const value = wrapper.instance().onlyRenderConversions(10)
- assert.equal(value.props.className, 'currency-display__converted-value')
- assert.equal(value.props.children, '10 TEST')
- })
-
- it('simulates change value in input', () => {
- const handleChangeSpy = sinon.spy()
-
- const wrapper = shallow(
- <CurrencyDisplay
- onChange={handleChangeSpy}
- />, {context: {t: str => str + '_t'}})
-
- const input = wrapper.find('input')
- input.simulate('focus')
- input.simulate('change', { target: { value: '100' } })
-
- assert.equal(wrapper.state().valueToRender, '100')
- assert.equal(wrapper.find('input').prop('value'), '100')
- })
-
-})
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
index 4d0d36ab4..ceb620941 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
+++ b/ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
@@ -34,21 +34,27 @@ export default class AmountMaxButton extends Component {
})
}
+ onMaxClick = (event) => {
+ const { setMaxModeTo } = this.props
+
+ event.preventDefault()
+ setMaxModeTo(true)
+ this.setMaxAmount()
+ }
+
render () {
- const { setMaxModeTo, maxModeOn } = this.props
-
- return (
- <div
- className="send-v2__amount-max"
- onClick={(event) => {
- event.preventDefault()
- setMaxModeTo(true)
- this.setMaxAmount()
- }}
- >
- {!maxModeOn ? this.context.t('max') : ''}
- </div>
- )
+ return this.props.maxModeOn
+ ? null
+ : (
+ <div>
+ <span
+ className="send-v2__amount-max"
+ onClick={this.onMaxClick}
+ >
+ {this.context.t('max')}
+ </span>
+ </div>
+ )
}
}
diff --git a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
index 86a05ff21..b04d3897f 100644
--- a/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
+++ b/ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
@@ -56,9 +56,8 @@ describe('AmountMaxButton Component', function () {
})
describe('render', () => {
- it('should render a div with a send-v2__amount-max class', () => {
- assert.equal(wrapper.find('.send-v2__amount-max').length, 1)
- assert(wrapper.find('.send-v2__amount-max').is('div'))
+ it('should render an element with a send-v2__amount-max class', () => {
+ assert(wrapper.exists('.send-v2__amount-max'))
})
it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => {
@@ -77,9 +76,9 @@ describe('AmountMaxButton Component', function () {
)
})
- it('should not render text when maxModeOn is true', () => {
+ it('should not render anything when maxModeOn is true', () => {
wrapper.setProps({ maxModeOn: true })
- assert.equal(wrapper.find('.send-v2__amount-max').text(), '')
+ assert.ok(!wrapper.exists('.send-v2__amount-max'))
})
it('should render the expected text when maxModeOn is false', () => {
diff --git a/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js
index c548a5695..0268376bf 100644
--- a/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js
+++ b/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js
@@ -2,7 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper/'
import AmountMaxButton from './amount-max-button/'
-import CurrencyDisplay from '../../currency-display'
+import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input'
+import UserPreferencedTokenInput from '../../../user-preferenced-token-input'
export default class SendAmountRow extends Component {
@@ -84,16 +85,25 @@ export default class SendAmountRow extends Component {
}
}
+ renderInput () {
+ const { amount, inError, selectedToken } = this.props
+ const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput
+
+ return (
+ <Component
+ onChange={newAmount => this.validateAmount(newAmount)}
+ onBlur={newAmount => {
+ this.updateGas(newAmount)
+ this.updateAmount(newAmount)
+ }}
+ error={inError}
+ value={amount}
+ />
+ )
+ }
+
render () {
- const {
- amount,
- amountConversionRate,
- convertedCurrency,
- gasTotal,
- inError,
- primaryCurrency,
- selectedToken,
- } = this.props
+ const { gasTotal, inError } = this.props
return (
<SendRowWrapper
@@ -102,20 +112,7 @@ export default class SendAmountRow extends Component {
errorType={'amount'}
>
{!inError && gasTotal && <AmountMaxButton />}
- <CurrencyDisplay
- conversionRate={amountConversionRate}
- convertedCurrency={convertedCurrency}
- onBlur={newAmount => {
- this.updateGas(newAmount)
- this.updateAmount(newAmount)
- }}
- onChange={newAmount => this.validateAmount(newAmount)}
- inError={inError}
- primaryCurrency={primaryCurrency || 'ETH'}
- selectedToken={selectedToken}
- value={amount}
- step="any"
- />
+ { this.renderInput() }
</SendRowWrapper>
)
}
diff --git a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
index 8425e076e..56e80cb83 100644
--- a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
+++ b/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
@@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
-import CurrencyDisplay from '../../../currency-display'
+import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
const propsMethodSpies = {
setMaxModeTo: sinon.spy(),
@@ -150,26 +150,18 @@ describe('SendAmountRow Component', function () {
assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton))
})
- it('should render a CurrencyDisplay as the second child of the SendRowWrapper', () => {
- assert(wrapper.find(SendRowWrapper).childAt(1).is(CurrencyDisplay))
+ it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => {
+ assert(wrapper.find(SendRowWrapper).childAt(1).is(UserPreferencedTokenInput))
})
- it('should render the CurrencyDisplay with the correct props', () => {
+ it('should render the UserPreferencedTokenInput with the correct props', () => {
const {
- conversionRate,
- convertedCurrency,
onBlur,
onChange,
- inError,
- primaryCurrency,
- selectedToken,
+ error,
value,
} = wrapper.find(SendRowWrapper).childAt(1).props()
- assert.equal(conversionRate, 'mockAmountConversionRate')
- assert.equal(convertedCurrency, 'mockConvertedCurrency')
- assert.equal(inError, false)
- assert.equal(primaryCurrency, 'mockPrimaryCurrency')
- assert.deepEqual(selectedToken, { address: 'mockTokenAddress' })
+ assert.equal(error, false)
assert.equal(value, 'mockAmount')
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
@@ -192,11 +184,5 @@ describe('SendAmountRow Component', function () {
['mockNewAmount']
)
})
-
- it('should pass the default primaryCurrency to the CurrencyDisplay if primaryCurrency is falsy', () => {
- wrapper.setProps({ primaryCurrency: null })
- const { primaryCurrency } = wrapper.find(SendRowWrapper).childAt(1).props()
- assert.equal(primaryCurrency, 'ETH')
- })
})
})
diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
index bb9a94428..9bbb67506 100644
--- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
+++ b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
-import CurrencyDisplay from '../../../../send/currency-display'
-
+import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../../../constants/common'
export default class GasFeeDisplay extends Component {
@@ -19,27 +19,24 @@ export default class GasFeeDisplay extends Component {
};
render () {
- const {
- conversionRate,
- gasTotal,
- onClick,
- primaryCurrency = 'ETH',
- convertedCurrency,
- gasLoadingError,
- } = this.props
+ const { gasTotal, onClick, gasLoadingError } = this.props
return (
<div className="send-v2__gas-fee-display">
{gasTotal
- ? <CurrencyDisplay
- primaryCurrency={primaryCurrency}
- convertedCurrency={convertedCurrency}
- value={gasTotal}
- conversionRate={conversionRate}
- gasLoadingError={gasLoadingError}
- convertedPrefix={'$'}
- readOnly
- />
+ ? (
+ <div className="currency-display">
+ <UserPreferencedCurrencyDisplay
+ value={gasTotal}
+ type={PRIMARY}
+ />
+ <UserPreferencedCurrencyDisplay
+ className="currency-display__converted-value"
+ value={gasTotal}
+ type={SECONDARY}
+ />
+ </div>
+ )
: gasLoadingError
? <div className="currency-display.currency-display--message">
{this.context.t('setGasPrice')}
diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
index 7cbe8d0df..9ff01493a 100644
--- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
+++ b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import {shallow} from 'enzyme'
import GasFeeDisplay from '../gas-fee-display.component'
-import CurrencyDisplay from '../../../../../send/currency-display'
+import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
import sinon from 'sinon'
@@ -29,17 +29,15 @@ describe('SendGasRow Component', function () {
describe('render', () => {
it('should render a CurrencyDisplay component', () => {
- assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
})
it('should render the CurrencyDisplay with the correct props', () => {
const {
- conversionRate,
- convertedCurrency,
+ type,
value,
- } = wrapper.find(CurrencyDisplay).props()
- assert.equal(conversionRate, 20)
- assert.equal(convertedCurrency, 'mockConvertedCurrency')
+ } = wrapper.find(UserPreferencedCurrencyDisplay).at(0).props()
+ assert.equal(type, 'PRIMARY')
assert.equal(value, 'mockGasTotal')
})
diff --git a/ui/app/components/send/send.selectors.js b/ui/app/components/send/send.selectors.js
index 22e379693..eb22a08b7 100644
--- a/ui/app/components/send/send.selectors.js
+++ b/ui/app/components/send/send.selectors.js
@@ -19,6 +19,7 @@ const selectors = {
getCurrentNetwork,
getCurrentViewContext,
getForceGasMin,
+ getNativeCurrency,
getGasLimit,
getGasPrice,
getGasPriceFromRecentBlocks,
@@ -111,6 +112,10 @@ function getCurrentCurrency (state) {
return state.metamask.currentCurrency
}
+function getNativeCurrency (state) {
+ return state.metamask.nativeCurrency
+}
+
function getCurrentNetwork (state) {
return state.metamask.network
}
diff --git a/ui/app/components/send/send.utils.js b/ui/app/components/send/send.utils.js
index a18a9e4b3..eb1667c63 100644
--- a/ui/app/components/send/send.utils.js
+++ b/ui/app/components/send/send.utils.js
@@ -215,7 +215,9 @@ async function estimateGas ({
// if recipient has no code, gas is 21k max:
if (!selectedToken && !data) {
const code = Boolean(to) && await global.eth.getCode(to)
- if (!code || code === '0x') {
+ // Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
+ const codeIsEmpty = !code || code === '0x' || code === '0x0'
+ if (codeIsEmpty) {
return SIMPLE_GAS_COST
}
} else if (selectedToken && !to) {
diff --git a/ui/app/components/send/tests/send-selectors-test-data.js b/ui/app/components/send/tests/send-selectors-test-data.js
index 8b939dadb..30a2666cf 100644
--- a/ui/app/components/send/tests/send-selectors-test-data.js
+++ b/ui/app/components/send/tests/send-selectors-test-data.js
@@ -26,6 +26,7 @@ module.exports = {
'currentCurrency': 'USD',
'conversionRate': 1200.88200327,
'conversionDate': 1489013762,
+ 'nativeCurrency': 'ETH',
'noActiveNotices': true,
'frequentRpcList': [],
'network': '3',
diff --git a/ui/app/components/send/tests/send-selectors.test.js b/ui/app/components/send/tests/send-selectors.test.js
index 1a47cd209..e7e901f0d 100644
--- a/ui/app/components/send/tests/send-selectors.test.js
+++ b/ui/app/components/send/tests/send-selectors.test.js
@@ -12,6 +12,7 @@ const {
getCurrentCurrency,
getCurrentNetwork,
getCurrentViewContext,
+ getNativeCurrency,
getForceGasMin,
getGasLimit,
getGasPrice,
@@ -178,6 +179,15 @@ describe('send selectors', () => {
})
})
+ describe('getNativeCurrency()', () => {
+ it('should return the ticker symbol of the selected network', () => {
+ assert.equal(
+ getNativeCurrency(mockState),
+ 'ETH'
+ )
+ })
+ })
+
describe('getCurrentNetwork()', () => {
it('should return the id of the currently selected network', () => {
assert.equal(
diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js
index b87bf959e..0461b615a 100644
--- a/ui/app/components/shift-list-item.js
+++ b/ui/app/components/shift-list-item.js
@@ -52,12 +52,12 @@ ShiftListItem.prototype.render = function () {
},
}, [
h('img', {
- src: 'https://info.shapeshift.io/sites/default/files/logo.png',
+ src: 'https://shapeshift.io/logo.png',
style: {
height: '35px',
width: '132px',
position: 'absolute',
- clip: 'rect(0px,23px,34px,0px)',
+ clip: 'rect(0px,30px,34px,0px)',
},
}),
]),
@@ -132,7 +132,6 @@ ShiftListItem.prototype.renderInfo = function () {
case 'no_deposits':
return h('.flex-column', {
style: {
- width: '200px',
overflow: 'hidden',
},
}, [
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
index d76eb5ef8..85af3b00b 100644
--- a/ui/app/components/signature-request.js
+++ b/ui/app/components/signature-request.js
@@ -2,7 +2,7 @@ const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
-const Identicon = require('./identicon')
+import Identicon from './identicon'
const connect = require('react-redux').connect
const ethUtil = require('ethereumjs-util')
const classnames = require('classnames')
diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js
index 477d97597..75ba347fa 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/token-cell.js
@@ -2,7 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const Identicon = require('./identicon')
+import Identicon from './identicon'
const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
const selectors = require('../selectors')
const actions = require('../actions')
diff --git a/ui/app/components/token-input/index.js b/ui/app/components/token-input/index.js
new file mode 100644
index 000000000..22c06111e
--- /dev/null
+++ b/ui/app/components/token-input/index.js
@@ -0,0 +1 @@
+export { default } from './token-input.container'
diff --git a/ui/app/components/token-input/tests/token-input.component.test.js b/ui/app/components/token-input/tests/token-input.component.test.js
new file mode 100644
index 000000000..2dacb9bc4
--- /dev/null
+++ b/ui/app/components/token-input/tests/token-input.component.test.js
@@ -0,0 +1,308 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import assert from 'assert'
+import { shallow, mount } from 'enzyme'
+import sinon from 'sinon'
+import { Provider } from 'react-redux'
+import configureMockStore from 'redux-mock-store'
+import TokenInput from '../token-input.component'
+import UnitInput from '../../unit-input'
+import CurrencyDisplay from '../../currency-display'
+
+describe('TokenInput Component', () => {
+ const t = key => `translate ${key}`
+
+ describe('rendering', () => {
+ it('should render properly without a token', () => {
+ const wrapper = shallow(
+ <TokenInput />,
+ { context: { t } }
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(UnitInput).length, 1)
+ })
+
+ it('should render properly with a token', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <TokenInput
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ />
+ </Provider>,
+ { context: { t },
+ childContextTypes: {
+ t: PropTypes.func,
+ },
+ },
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
+ assert.equal(wrapper.find('.currency-input__conversion-component').length, 1)
+ assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'translate noConversionRateAvailable')
+ })
+
+ it('should render properly with a token and selectedTokenExchangeRate', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <TokenInput
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ selectedTokenExchangeRate={2}
+ />
+ </Provider>,
+ { context: { t },
+ childContextTypes: {
+ t: PropTypes.func,
+ },
+ },
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
+ assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ })
+
+ it('should render properly with a token value for ETH', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <TokenInput
+ value="2710"
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ selectedTokenExchangeRate={2}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
+ assert.equal(tokenInputInstance.state.decimalValue, 1)
+ assert.equal(tokenInputInstance.state.hexValue, '2710')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '1')
+ assert.equal(wrapper.find('.currency-display-component').text(), '2ETH')
+ })
+
+ it('should render properly with a token value for fiat', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <TokenInput
+ value="2710"
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ selectedTokenExchangeRate={2}
+ showFiat
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
+ assert.equal(tokenInputInstance.state.decimalValue, 1)
+ assert.equal(tokenInputInstance.state.hexValue, '2710')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '1')
+ assert.equal(wrapper.find('.currency-display-component').text(), '$462.12USD')
+ })
+ })
+
+ describe('handling actions', () => {
+ const handleChangeSpy = sinon.spy()
+ const handleBlurSpy = sinon.spy()
+
+ afterEach(() => {
+ handleChangeSpy.resetHistory()
+ handleBlurSpy.resetHistory()
+ })
+
+ it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = mount(
+ <Provider store={store}>
+ <TokenInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ selectedTokenExchangeRate={2}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+
+ const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
+ assert.equal(tokenInputInstance.state.decimalValue, 0)
+ assert.equal(tokenInputInstance.state.hexValue, undefined)
+ assert.equal(wrapper.find('.currency-display-component').text(), '0ETH')
+ const input = wrapper.find('input')
+ assert.equal(input.props().value, 0)
+
+ input.simulate('change', { target: { value: 1 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith('2710'))
+ assert.equal(wrapper.find('.currency-display-component').text(), '2ETH')
+ assert.equal(tokenInputInstance.state.decimalValue, 1)
+ assert.equal(tokenInputInstance.state.hexValue, '2710')
+
+ assert.equal(handleBlurSpy.callCount, 0)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith('2710'))
+ })
+
+ it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = mount(
+ <Provider store={store}>
+ <TokenInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ selectedTokenExchangeRate={2}
+ showFiat
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+
+ const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
+ assert.equal(tokenInputInstance.state.decimalValue, 0)
+ assert.equal(tokenInputInstance.state.hexValue, undefined)
+ assert.equal(wrapper.find('.currency-display-component').text(), '$0.00USD')
+ const input = wrapper.find('input')
+ assert.equal(input.props().value, 0)
+
+ input.simulate('change', { target: { value: 1 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith('2710'))
+ assert.equal(wrapper.find('.currency-display-component').text(), '$462.12USD')
+ assert.equal(tokenInputInstance.state.decimalValue, 1)
+ assert.equal(tokenInputInstance.state.hexValue, '2710')
+
+ assert.equal(handleBlurSpy.callCount, 0)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith('2710'))
+ })
+
+ it('should change the state and pass in a new decimalValue when props.value changes', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = shallow(
+ <Provider store={store}>
+ <TokenInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ selectedToken={{
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ }}
+ suffix="ABC"
+ selectedTokenExchangeRate={2}
+ showFiat
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const tokenInputInstance = wrapper.find(TokenInput).dive()
+ assert.equal(tokenInputInstance.state('decimalValue'), 0)
+ assert.equal(tokenInputInstance.state('hexValue'), undefined)
+ assert.equal(tokenInputInstance.find(UnitInput).props().value, 0)
+
+ tokenInputInstance.setProps({ value: '2710' })
+ tokenInputInstance.update()
+ assert.equal(tokenInputInstance.state('decimalValue'), 1)
+ assert.equal(tokenInputInstance.state('hexValue'), '2710')
+ assert.equal(tokenInputInstance.find(UnitInput).props().value, 1)
+ })
+ })
+})
diff --git a/ui/app/components/token-input/tests/token-input.container.test.js b/ui/app/components/token-input/tests/token-input.container.test.js
new file mode 100644
index 000000000..d73bc9a94
--- /dev/null
+++ b/ui/app/components/token-input/tests/token-input.container.test.js
@@ -0,0 +1,129 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps, mergeProps
+
+proxyquire('../token-input.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+})
+
+describe('TokenInput container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props when send is empty', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x1',
+ contractExchangeRates: {},
+ send: {},
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 0,
+ })
+ })
+
+ it('should return the correct props when selectedTokenAddress is not found and send is populated', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x2',
+ contractExchangeRates: {},
+ send: {
+ token: { address: 'test' },
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: 'test',
+ },
+ selectedTokenExchangeRate: 0,
+ })
+ })
+
+ it('should return the correct props when contractExchangeRates is populated', () => {
+ const mockState = {
+ metamask: {
+ currentCurrency: 'usd',
+ tokens: [
+ {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ ],
+ selectedTokenAddress: '0x1',
+ contractExchangeRates: {
+ '0x1': 5,
+ },
+ send: {},
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 5,
+ })
+ })
+ })
+
+ describe('mergeProps()', () => {
+ it('should return the correct props', () => {
+ const mockStateProps = {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 5,
+ }
+
+ assert.deepEqual(mergeProps(mockStateProps, {}, {}), {
+ currentCurrency: 'usd',
+ selectedToken: {
+ address: '0x1',
+ decimals: '4',
+ symbol: 'ABC',
+ },
+ selectedTokenExchangeRate: 5,
+ suffix: 'ABC',
+ })
+ })
+ })
+})
diff --git a/ui/app/components/token-input/token-input.component.js b/ui/app/components/token-input/token-input.component.js
new file mode 100644
index 000000000..d1388945b
--- /dev/null
+++ b/ui/app/components/token-input/token-input.component.js
@@ -0,0 +1,136 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import UnitInput from '../unit-input'
+import CurrencyDisplay from '../currency-display'
+import { getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
+import ethUtil from 'ethereumjs-util'
+import { conversionUtil, multiplyCurrencies } from '../../conversion-util'
+import { ETH } from '../../constants/common'
+
+/**
+ * Component that allows user to enter token values as a number, and props receive a converted
+ * hex value. props.value, used as a default or forced value, should be a hex value, which
+ * gets converted into a decimal value.
+ */
+export default class TokenInput extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ currentCurrency: PropTypes.string,
+ onChange: PropTypes.func,
+ onBlur: PropTypes.func,
+ value: PropTypes.string,
+ suffix: PropTypes.string,
+ showFiat: PropTypes.bool,
+ selectedToken: PropTypes.object,
+ selectedTokenExchangeRate: PropTypes.number,
+ }
+
+ constructor (props) {
+ super(props)
+
+ const { value: hexValue } = props
+ const decimalValue = hexValue ? this.getDecimalValue(props) : 0
+
+ this.state = {
+ decimalValue,
+ hexValue,
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ const { value: prevPropsHexValue } = prevProps
+ const { value: propsHexValue } = this.props
+ const { hexValue: stateHexValue } = this.state
+
+ if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
+ const decimalValue = this.getDecimalValue(this.props)
+ this.setState({ hexValue: propsHexValue, decimalValue })
+ }
+ }
+
+ getDecimalValue (props) {
+ const { value: hexValue, selectedToken: { decimals, symbol } = {} } = props
+
+ const multiplier = Math.pow(10, Number(decimals || 0))
+ const decimalValueString = conversionUtil(ethUtil.addHexPrefix(hexValue), {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ toCurrency: symbol,
+ conversionRate: multiplier,
+ invertConversionRate: true,
+ })
+
+ return Number(decimalValueString) || 0
+ }
+
+ handleChange = decimalValue => {
+ const { selectedToken: { decimals } = {}, onChange } = this.props
+
+ const multiplier = Math.pow(10, Number(decimals || 0))
+ const hexValue = multiplyCurrencies(decimalValue || 0, multiplier, { toNumericBase: 'hex' })
+
+ this.setState({ hexValue, decimalValue })
+ onChange(hexValue)
+ }
+
+ handleBlur = () => {
+ this.props.onBlur(this.state.hexValue)
+ }
+
+ renderConversionComponent () {
+ const { selectedTokenExchangeRate, showFiat, currentCurrency } = this.props
+ const { decimalValue } = this.state
+ let currency, numberOfDecimals
+
+ if (showFiat) {
+ // Display Fiat
+ currency = currentCurrency
+ numberOfDecimals = 2
+ } else {
+ // Display ETH
+ currency = ETH
+ numberOfDecimals = 6
+ }
+
+ const decimalEthValue = (decimalValue * selectedTokenExchangeRate) || 0
+ const hexWeiValue = getWeiHexFromDecimalValue({
+ value: decimalEthValue,
+ fromCurrency: ETH,
+ fromDenomination: ETH,
+ })
+
+ return selectedTokenExchangeRate
+ ? (
+ <CurrencyDisplay
+ className="currency-input__conversion-component"
+ currency={currency}
+ value={hexWeiValue}
+ numberOfDecimals={numberOfDecimals}
+ />
+ ) : (
+ <div className="currency-input__conversion-component">
+ { this.context.t('noConversionRateAvailable') }
+ </div>
+ )
+ }
+
+ render () {
+ const { suffix, ...restProps } = this.props
+ const { decimalValue } = this.state
+
+ return (
+ <UnitInput
+ {...restProps}
+ suffix={suffix}
+ onChange={this.handleChange}
+ onBlur={this.handleBlur}
+ value={decimalValue}
+ >
+ { this.renderConversionComponent() }
+ </UnitInput>
+ )
+ }
+}
diff --git a/ui/app/components/token-input/token-input.container.js b/ui/app/components/token-input/token-input.container.js
new file mode 100644
index 000000000..ec233b1b8
--- /dev/null
+++ b/ui/app/components/token-input/token-input.container.js
@@ -0,0 +1,27 @@
+import { connect } from 'react-redux'
+import TokenInput from './token-input.component'
+import { getSelectedToken, getSelectedTokenExchangeRate } from '../../selectors'
+
+const mapStateToProps = state => {
+ const { metamask: { currentCurrency } } = state
+
+ return {
+ currentCurrency,
+ selectedToken: getSelectedToken(state),
+ selectedTokenExchangeRate: getSelectedTokenExchangeRate(state),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { selectedToken } = stateProps
+ const suffix = selectedToken && selectedToken.symbol
+
+ return {
+ ...stateProps,
+ ...dispatchProps,
+ ...ownProps,
+ suffix,
+ }
+}
+
+export default connect(mapStateToProps, null, mergeProps)(TokenInput)
diff --git a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js b/ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js
index 85d56a6a2..a7c35f51e 100644
--- a/ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js
+++ b/ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js
@@ -18,10 +18,11 @@ describe('TransactionActivityLog container', () => {
const mockState = {
metamask: {
conversionRate: 280.45,
+ nativeCurrency: 'ETH',
},
}
- assert.deepEqual(mapStateToProps(mockState), { conversionRate: 280.45 })
+ assert.deepEqual(mapStateToProps(mockState), { conversionRate: 280.45, nativeCurrency: 'ETH' })
})
})
})
diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.component.js b/ui/app/components/transaction-activity-log/transaction-activity-log.component.js
index c4cf57d14..58d932a0f 100644
--- a/ui/app/components/transaction-activity-log/transaction-activity-log.component.js
+++ b/ui/app/components/transaction-activity-log/transaction-activity-log.component.js
@@ -4,7 +4,6 @@ import classnames from 'classnames'
import { getActivities } from './transaction-activity-log.util'
import Card from '../card'
import { getEthConversionFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
-import { ETH } from '../../constants/common'
import { formatDate } from '../../util'
export default class TransactionActivityLog extends PureComponent {
@@ -16,6 +15,7 @@ export default class TransactionActivityLog extends PureComponent {
transaction: PropTypes.object,
className: PropTypes.string,
conversionRate: PropTypes.number,
+ nativeCurrency: PropTypes.string,
}
state = {
@@ -27,10 +27,14 @@ export default class TransactionActivityLog extends PureComponent {
}
componentDidUpdate (prevProps) {
- const { transaction: { history: prevHistory = [] } = {} } = prevProps
- const { transaction: { history = [] } = {} } = this.props
+ const {
+ transaction: { history: prevHistory = [], txReceipt: { status: prevStatus } = {} } = {},
+ } = prevProps
+ const {
+ transaction: { history = [], txReceipt: { status } = {} } = {},
+ } = this.props
- if (prevHistory.length !== history.length) {
+ if (prevHistory.length !== history.length || prevStatus !== status) {
this.setActivites()
}
}
@@ -41,16 +45,17 @@ export default class TransactionActivityLog extends PureComponent {
}
renderActivity (activity, index) {
- const { conversionRate } = this.props
+ const { conversionRate, nativeCurrency } = this.props
const { eventKey, value, timestamp } = activity
const ethValue = index === 0
? `${getValueFromWeiHex({
value,
- toCurrency: ETH,
+ fromCurrency: nativeCurrency,
+ toCurrency: nativeCurrency,
conversionRate,
numberOfDecimals: 6,
- })} ${ETH}`
- : getEthConversionFromWeiHex({ value, toCurrency: ETH, conversionRate })
+ })} ${nativeCurrency}`
+ : getEthConversionFromWeiHex({ value, fromCurrency: nativeCurrency, conversionRate })
const formattedTimestamp = formatDate(timestamp)
const activityText = this.context.t(eventKey, [ethValue, formattedTimestamp])
diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.container.js b/ui/app/components/transaction-activity-log/transaction-activity-log.container.js
index 4c8b6d971..622f77df1 100644
--- a/ui/app/components/transaction-activity-log/transaction-activity-log.container.js
+++ b/ui/app/components/transaction-activity-log/transaction-activity-log.container.js
@@ -1,10 +1,11 @@
import { connect } from 'react-redux'
import TransactionActivityLog from './transaction-activity-log.component'
-import { conversionRateSelector } from '../../selectors'
+import { conversionRateSelector, getNativeCurrency } from '../../selectors'
const mapStateToProps = state => {
return {
conversionRate: conversionRateSelector(state),
+ nativeCurrency: getNativeCurrency(state),
}
}
diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.util.js b/ui/app/components/transaction-activity-log/transaction-activity-log.util.js
index 32834ff47..16597ae1a 100644
--- a/ui/app/components/transaction-activity-log/transaction-activity-log.util.js
+++ b/ui/app/components/transaction-activity-log/transaction-activity-log.util.js
@@ -18,6 +18,7 @@ const TRANSACTION_SUBMITTED_EVENT = 'transactionSubmitted'
const TRANSACTION_CONFIRMED_EVENT = 'transactionConfirmed'
const TRANSACTION_DROPPED_EVENT = 'transactionDropped'
const TRANSACTION_UPDATED_EVENT = 'transactionUpdated'
+const TRANSACTION_ERRORED_EVENT = 'transactionErrored'
const eventPathsHash = {
[STATUS_PATH]: true,
@@ -39,18 +40,22 @@ function eventCreator (eventKey, timestamp, value) {
}
export function getActivities (transaction) {
- const { history = [] } = transaction
+ const { history = [], txReceipt: { status } = {} } = transaction
- return history.reduce((acc, base) => {
+ const historyActivities = history.reduce((acc, base) => {
// First history item should be transaction creation
if (!Array.isArray(base) && base.status === UNAPPROVED_STATUS && base.txParams) {
const { time, txParams: { value } = {} } = base
return acc.concat(eventCreator(TRANSACTION_CREATED_EVENT, time, value))
+ // An entry in the history may be an array of more sub-entries.
} else if (Array.isArray(base)) {
const events = []
base.forEach(entry => {
- const { op, path, value, timestamp } = entry
+ const { op, path, value, timestamp: entryTimestamp } = entry
+ // Not all sub-entries in a history entry have a timestamp. If the sub-entry does not have a
+ // timestamp, the first sub-entry in a history entry should.
+ const timestamp = entryTimestamp || base[0] && base[0].timestamp
if (path in eventPathsHash && op === REPLACE_OP) {
switch (path) {
@@ -79,4 +84,10 @@ export function getActivities (transaction) {
return acc
}, [])
+
+ // If txReceipt.status is '0x0', that means that an on-chain error occured for the transaction,
+ // so we add an error entry to the Activity Log.
+ return status === '0x0'
+ ? historyActivities.concat(eventCreator(TRANSACTION_ERRORED_EVENT))
+ : historyActivities
}
diff --git a/ui/app/components/transaction-breakdown/index.js b/ui/app/components/transaction-breakdown/index.js
index c887f504f..4a5b52663 100644
--- a/ui/app/components/transaction-breakdown/index.js
+++ b/ui/app/components/transaction-breakdown/index.js
@@ -1 +1 @@
-export { default } from './transaction-breakdown.component'
+export { default } from './transaction-breakdown.container'
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/transaction-breakdown/transaction-breakdown.component.js
index bb6075e9f..3a7647873 100644
--- a/ui/app/components/transaction-breakdown/transaction-breakdown.component.js
+++ b/ui/app/components/transaction-breakdown/transaction-breakdown.component.js
@@ -4,8 +4,9 @@ import classnames from 'classnames'
import TransactionBreakdownRow from './transaction-breakdown-row'
import Card from '../card'
import CurrencyDisplay from '../currency-display'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import HexToDecimal from '../hex-to-decimal'
-import { ETH, GWEI } from '../../constants/common'
+import { GWEI, PRIMARY, SECONDARY } from '../../constants/common'
import { getHexGasTotal } from '../../helpers/confirm-transaction/util'
import { sumHexes } from '../../helpers/transactions.util'
@@ -17,6 +18,7 @@ export default class TransactionBreakdown extends PureComponent {
static propTypes = {
transaction: PropTypes.object,
className: PropTypes.string,
+ nativeCurrency: PropTypes.string.isRequired,
}
static defaultProps = {
@@ -25,9 +27,12 @@ export default class TransactionBreakdown extends PureComponent {
render () {
const { t } = this.context
- const { transaction, className } = this.props
- const { txParams: { gas, gasPrice, value } = {} } = transaction
- const hexGasTotal = getHexGasTotal({ gasLimit: gas, gasPrice })
+ const { transaction, className, nativeCurrency } = this.props
+ const { txParams: { gas, gasPrice, value } = {}, txReceipt: { gasUsed } = {} } = transaction
+
+ const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas
+
+ const hexGasTotal = getHexGasTotal({ gasLimit, gasPrice })
const totalInHex = sumHexes(hexGasTotal, value)
return (
@@ -37,9 +42,9 @@ export default class TransactionBreakdown extends PureComponent {
className="transaction-breakdown__card"
>
<TransactionBreakdownRow title={t('amount')}>
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="transaction-breakdown__value"
- currency={ETH}
+ type={PRIMARY}
value={value}
/>
</TransactionBreakdownRow>
@@ -52,10 +57,23 @@ export default class TransactionBreakdown extends PureComponent {
value={gas}
/>
</TransactionBreakdownRow>
+ {
+ typeof gasUsed === 'string' && (
+ <TransactionBreakdownRow
+ title={`${t('gasUsed')} (${t('units')})`}
+ className="transaction-breakdown__row-title"
+ >
+ <HexToDecimal
+ className="transaction-breakdown__value"
+ value={gasUsed}
+ />
+ </TransactionBreakdownRow>
+ )
+ }
<TransactionBreakdownRow title={t('gasPrice')}>
<CurrencyDisplay
className="transaction-breakdown__value"
- currency={ETH}
+ currency={nativeCurrency}
denomination={GWEI}
value={gasPrice}
hideLabel
@@ -63,14 +81,14 @@ export default class TransactionBreakdown extends PureComponent {
</TransactionBreakdownRow>
<TransactionBreakdownRow title={t('total')}>
<div>
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="transaction-breakdown__value transaction-breakdown__value--eth-total"
- currency={ETH}
+ type={PRIMARY}
value={totalInHex}
- numberOfDecimals={6}
/>
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="transaction-breakdown__value"
+ type={SECONDARY}
value={totalInHex}
/>
</div>
diff --git a/ui/app/components/transaction-breakdown/transaction-breakdown.container.js b/ui/app/components/transaction-breakdown/transaction-breakdown.container.js
new file mode 100644
index 000000000..ed2708e03
--- /dev/null
+++ b/ui/app/components/transaction-breakdown/transaction-breakdown.container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux'
+import TransactionBreakdown from './transaction-breakdown.component'
+import { getNativeCurrency } from '../../selectors'
+
+const mapStateToProps = (state) => {
+ return {
+ nativeCurrency: getNativeCurrency(state),
+ }
+}
+
+export default connect(mapStateToProps)(TransactionBreakdown)
diff --git a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js
index 13cb51349..a4f28fd63 100644
--- a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js
+++ b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js
@@ -5,6 +5,7 @@ import { CARDS_VARIANT } from '../sender-to-recipient/sender-to-recipient.consta
import TransactionActivityLog from '../transaction-activity-log'
import TransactionBreakdown from '../transaction-breakdown'
import Button from '../button'
+import Tooltip from '../tooltip'
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
export default class TransactionListItemDetails extends PureComponent {
@@ -75,13 +76,15 @@ export default class TransactionListItemDetails extends PureComponent {
</Button>
)
}
- <Button
- type="raised"
- onClick={this.handleEtherscanClick}
- className="transaction-list-item-details__header-button"
- >
- <img src="/images/arrow-popout.svg" />
- </Button>
+ <Tooltip title={t('viewOnEtherscan')}>
+ <Button
+ type="raised"
+ onClick={this.handleEtherscanClick}
+ className="transaction-list-item-details__header-button"
+ >
+ <img src="/images/arrow-popout.svg" />
+ </Button>
+ </Tooltip>
</div>
</div>
<div className="transaction-list-item-details__sender-to-recipient-container">
diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss
index 9d694546b..ac0e7beeb 100644
--- a/ui/app/components/transaction-list-item/index.scss
+++ b/ui/app/components/transaction-list-item/index.scss
@@ -85,6 +85,7 @@
text-align: end;
grid-area: primary-amount;
align-self: end;
+ justify-self: end;
@media screen and (max-width: $break-small) {
padding-bottom: 2px;
@@ -97,6 +98,7 @@
color: #5e6064;
grid-area: secondary-amount;
align-self: start;
+ justify-self: end;
}
}
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js
index c1c69f59b..696634fe0 100644
--- a/ui/app/components/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js
@@ -4,13 +4,13 @@ import classnames from 'classnames'
import Identicon from '../identicon'
import TransactionStatus from '../transaction-status'
import TransactionAction from '../transaction-action'
-import CurrencyDisplay from '../currency-display'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import TokenCurrencyDisplay from '../token-currency-display'
import TransactionListItemDetails from '../transaction-list-item-details'
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
-import { ETH } from '../../constants/common'
-import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
+import { PRIMARY, SECONDARY } from '../../constants/common'
+import { getStatusKey } from '../../helpers/transactions.util'
export default class TransactionListItem extends PureComponent {
static propTypes = {
@@ -23,7 +23,6 @@ export default class TransactionListItem extends PureComponent {
showCancelModal: PropTypes.func,
showCancel: PropTypes.bool,
showRetry: PropTypes.bool,
- showTransactionDetailsModal: PropTypes.func,
token: PropTypes.object,
tokenData: PropTypes.object,
transaction: PropTypes.object,
@@ -38,31 +37,16 @@ export default class TransactionListItem extends PureComponent {
const {
transaction,
history,
- showTransactionDetailsModal,
- methodData,
- showCancel,
- showRetry,
} = this.props
const { id, status } = transaction
const { showTransactionDetails } = this.state
- const windowType = window.METAMASK_UI_TYPE
if (status === UNAPPROVED_STATUS) {
history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)
return
}
- if (windowType === ENVIRONMENT_TYPE_FULLSCREEN) {
- this.setState({ showTransactionDetails: !showTransactionDetails })
- } else {
- showTransactionDetailsModal({
- transaction,
- onRetry: this.handleRetry,
- showRetry: showRetry && methodData.done,
- onCancel: this.handleCancel,
- showCancel,
- })
- }
+ this.setState({ showTransactionDetails: !showTransactionDetails })
}
handleCancel = () => {
@@ -102,12 +86,11 @@ export default class TransactionListItem extends PureComponent {
prefix="-"
/>
) : (
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="transaction-list-item__amount transaction-list-item__amount--primary"
value={value}
+ type={PRIMARY}
prefix="-"
- numberOfDecimals={2}
- currency={ETH}
/>
)
}
@@ -118,10 +101,11 @@ export default class TransactionListItem extends PureComponent {
return token
? null
: (
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="transaction-list-item__amount transaction-list-item__amount--secondary"
- prefix="-"
value={value}
+ prefix="-"
+ type={SECONDARY}
/>
)
}
@@ -167,7 +151,7 @@ export default class TransactionListItem extends PureComponent {
</div>
<TransactionStatus
className="transaction-list-item__status"
- statusKey={transaction.status}
+ statusKey={getStatusKey(transaction)}
title={(
(transaction.err && transaction.err.rpc)
? transaction.err.rpc.message
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js
index 72f5f5d61..62ed7a73f 100644
--- a/ui/app/components/transaction-list-item/transaction-list-item.container.js
+++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js
@@ -28,16 +28,6 @@ const mapDispatchToProps = dispatch => {
showCancelModal: (transactionId, originalGasPrice) => {
return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice }))
},
- showTransactionDetailsModal: ({ transaction, onRetry, showRetry, onCancel, showCancel }) => {
- return dispatch(showModal({
- name: 'TRANSACTION_DETAILS',
- transaction,
- onRetry,
- showRetry,
- onCancel,
- showCancel,
- }))
- },
}
}
diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/transaction-list/index.scss
index 777f701f9..ba7ffd87b 100644
--- a/ui/app/components/transaction-list/index.scss
+++ b/ui/app/components/transaction-list/index.scss
@@ -2,9 +2,7 @@
display: flex;
flex-direction: column;
flex: 1;
- overflow-y: hidden;
margin-top: 8px;
- border-top: 1px solid $geyser;
&__completed-transactions {
display: flex;
@@ -26,7 +24,6 @@
&__transactions {
flex: 1;
- overflow-y: auto;
}
&__pending-transactions {
diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss
index 35be550f7..26a1f5d38 100644
--- a/ui/app/components/transaction-status/index.scss
+++ b/ui/app/components/transaction-status/index.scss
@@ -25,4 +25,9 @@
background-color: #FFF2DB;
color: #CA810A;
}
-} \ No newline at end of file
+
+ &--failed {
+ background: lighten($monzo, 56%);
+ color: $monzo;
+ }
+}
diff --git a/ui/app/components/transaction-view-balance/index.scss b/ui/app/components/transaction-view-balance/index.scss
index 12045ab6d..659f896ff 100644
--- a/ui/app/components/transaction-view-balance/index.scss
+++ b/ui/app/components/transaction-view-balance/index.scss
@@ -4,11 +4,13 @@
align-items: center;
flex: 1;
height: 54px;
+ min-width: 0;
&__balance {
- margin-left: 12px;
+ margin: 0 12px;
display: flex;
flex-direction: column;
+ min-width: 0;
@media screen and (max-width: $break-small) {
align-items: center;
@@ -21,7 +23,8 @@
font-size: 1.5rem;
@media screen and (max-width: $break-small) {
- margin-bottom: 12px;
+ margin: 12px 0;
+ margin-left: 0;
font-size: 1.75rem;
}
}
@@ -30,7 +33,6 @@
font-size: 1.5rem;
@media screen and (max-width: $break-small) {
- margin-bottom: 12px;
font-size: 1.75rem;
}
}
@@ -45,6 +47,7 @@
display: flex;
flex-direction: row;
align-items: center;
+ min-width: 0;
@media screen and (max-width: $break-small) {
flex-direction: column;
diff --git a/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js
index bb95cb27e..513a8aac9 100644
--- a/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js
+++ b/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js
@@ -3,7 +3,7 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import TokenBalance from '../../token-balance'
-import CurrencyDisplay from '../../currency-display'
+import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { SEND_ROUTE } from '../../../routes'
import TransactionViewBalance from '../transaction-view-balance.component'
@@ -35,7 +35,7 @@ describe('TransactionViewBalance Component', () => {
assert.equal(wrapper.find('.transaction-view-balance').length, 1)
assert.equal(wrapper.find('.transaction-view-balance__button').length, 2)
- assert.equal(wrapper.find(CurrencyDisplay).length, 2)
+ assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
const buttons = wrapper.find('.transaction-view-balance__buttons')
assert.equal(propsMethodSpies.showDepositModal.callCount, 0)
diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js
index 1b7a29c87..402b0f486 100644
--- a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js
+++ b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
import Button from '../button'
import Identicon from '../identicon'
import TokenBalance from '../token-balance'
-import CurrencyDisplay from '../currency-display'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { SEND_ROUTE } from '../../routes'
-import { ETH } from '../../constants/common'
+import { PRIMARY, SECONDARY } from '../../constants/common'
export default class TransactionViewBalance extends PureComponent {
static contextTypes = {
@@ -33,15 +33,17 @@ export default class TransactionViewBalance extends PureComponent {
/>
) : (
<div className="transaction-view-balance__balance">
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="transaction-view-balance__primary-balance"
value={balance}
- currency={ETH}
- numberOfDecimals={3}
+ type={PRIMARY}
+ ethNumberOfDecimals={4}
/>
- <CurrencyDisplay
+ <UserPreferencedCurrencyDisplay
className="transaction-view-balance__secondary-balance"
value={balance}
+ type={SECONDARY}
+ ethNumberOfDecimals={4}
/>
</div>
)
diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.container.js b/ui/app/components/transaction-view-balance/transaction-view-balance.container.js
index 30c5cab16..cb8078ec1 100644
--- a/ui/app/components/transaction-view-balance/transaction-view-balance.container.js
+++ b/ui/app/components/transaction-view-balance/transaction-view-balance.container.js
@@ -2,7 +2,7 @@ import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import TransactionViewBalance from './transaction-view-balance.component'
-import { getSelectedToken, getSelectedAddress, getSelectedTokenAssetImage } from '../../selectors'
+import { getSelectedToken, getSelectedAddress, getNativeCurrency, getSelectedTokenAssetImage } from '../../selectors'
import { showModal } from '../../actions'
const mapStateToProps = state => {
@@ -15,6 +15,7 @@ const mapStateToProps = state => {
selectedToken: getSelectedToken(state),
network,
balance,
+ nativeCurrency: getNativeCurrency(state),
assetImage: getSelectedTokenAssetImage(state),
}
}
diff --git a/ui/app/components/transaction-view/index.scss b/ui/app/components/transaction-view/index.scss
index af9771ce0..13187f0e5 100644
--- a/ui/app/components/transaction-view/index.scss
+++ b/ui/app/components/transaction-view/index.scss
@@ -4,6 +4,7 @@
min-width: 0;
display: flex;
flex-direction: column;
+ overflow-y: auto;
&__balance-wrapper {
@media screen and (max-width: $break-small) {
diff --git a/ui/app/components/unit-input/index.js b/ui/app/components/unit-input/index.js
new file mode 100644
index 000000000..7c33c9e5c
--- /dev/null
+++ b/ui/app/components/unit-input/index.js
@@ -0,0 +1 @@
+export { default } from './unit-input.component'
diff --git a/ui/app/components/unit-input/index.scss b/ui/app/components/unit-input/index.scss
new file mode 100644
index 000000000..28c5bf6f0
--- /dev/null
+++ b/ui/app/components/unit-input/index.scss
@@ -0,0 +1,44 @@
+.unit-input {
+ min-height: 54px;
+ border: 1px solid #dedede;
+ border-radius: 4px;
+ background-color: #fff;
+ color: #4d4d4d;
+ font-size: 1rem;
+ padding: 8px 10px;
+ position: relative;
+
+ input[type="number"] {
+ -moz-appearance: textfield;
+ }
+
+ input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ input[type="number"]:hover::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ &__input {
+ color: #4d4d4d;
+ font-size: 1rem;
+ font-family: Roboto;
+ border: none;
+ outline: 0 !important;
+ max-width: 22ch;
+ }
+
+ &__input-container {
+ display: flex;
+ align-items: center;
+ }
+
+ &--error {
+ border-color: $red;
+ }
+}
diff --git a/ui/app/components/unit-input/tests/unit-input.component.test.js b/ui/app/components/unit-input/tests/unit-input.component.test.js
new file mode 100644
index 000000000..97d987bc7
--- /dev/null
+++ b/ui/app/components/unit-input/tests/unit-input.component.test.js
@@ -0,0 +1,146 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow, mount } from 'enzyme'
+import sinon from 'sinon'
+import UnitInput from '../unit-input.component'
+
+describe('UnitInput Component', () => {
+ describe('rendering', () => {
+ it('should render properly without a suffix', () => {
+ const wrapper = shallow(
+ <UnitInput />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.unit-input__suffix').length, 0)
+ })
+
+ it('should render properly with a suffix', () => {
+ const wrapper = shallow(
+ <UnitInput
+ suffix="ETH"
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
+ })
+
+ it('should render properly with a child omponent', () => {
+ const wrapper = shallow(
+ <UnitInput>
+ <div className="testing">
+ TESTCOMPONENT
+ </div>
+ </UnitInput>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.testing').length, 1)
+ assert.equal(wrapper.find('.testing').text(), 'TESTCOMPONENT')
+ })
+
+ it('should render with an error class when props.error === true', () => {
+ const wrapper = shallow(
+ <UnitInput
+ error
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.unit-input--error').length, 1)
+ })
+ })
+
+ describe('handling actions', () => {
+ const handleChangeSpy = sinon.spy()
+ const handleBlurSpy = sinon.spy()
+
+ afterEach(() => {
+ handleChangeSpy.resetHistory()
+ handleBlurSpy.resetHistory()
+ })
+
+ it('should focus the input on component click', () => {
+ const wrapper = mount(
+ <UnitInput />
+ )
+
+ assert.ok(wrapper)
+ const handleFocusSpy = sinon.spy(wrapper.instance(), 'handleFocus')
+ wrapper.instance().forceUpdate()
+ wrapper.update()
+ assert.equal(handleFocusSpy.callCount, 0)
+ wrapper.find('.unit-input').simulate('click')
+ assert.equal(handleFocusSpy.callCount, 1)
+ })
+
+ it('should call onChange on input changes with the value', () => {
+ const wrapper = mount(
+ <UnitInput
+ onChange={handleChangeSpy}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ const input = wrapper.find('input')
+ input.simulate('change', { target: { value: 123 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith(123))
+ assert.equal(wrapper.state('value'), 123)
+ })
+
+ it('should call onBlur on blur with the value', () => {
+ const wrapper = mount(
+ <UnitInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+ const input = wrapper.find('input')
+ input.simulate('change', { target: { value: 123 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith(123))
+ assert.equal(wrapper.state('value'), 123)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith(123))
+ })
+
+ it('should set the component state value with props.value', () => {
+ const wrapper = mount(
+ <UnitInput
+ value={123}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.state('value'), 123)
+ })
+
+ it('should update the component state value with props.value', () => {
+ const wrapper = mount(
+ <UnitInput
+ onChange={handleChangeSpy}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ const input = wrapper.find('input')
+ input.simulate('change', { target: { value: 123 } })
+ assert.equal(wrapper.state('value'), 123)
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith(123))
+ wrapper.setProps({ value: 456 })
+ assert.equal(wrapper.state('value'), 456)
+ assert.equal(handleChangeSpy.callCount, 1)
+ })
+ })
+})
diff --git a/ui/app/components/unit-input/unit-input.component.js b/ui/app/components/unit-input/unit-input.component.js
new file mode 100644
index 000000000..f1ebf4d77
--- /dev/null
+++ b/ui/app/components/unit-input/unit-input.component.js
@@ -0,0 +1,104 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { removeLeadingZeroes } from '../send/send.utils'
+
+/**
+ * Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also
+ * allows rendering a child component underneath the input to, for example, display conversions of
+ * the shown suffix.
+ */
+export default class UnitInput extends PureComponent {
+ static propTypes = {
+ children: PropTypes.node,
+ error: PropTypes.bool,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ placeholder: PropTypes.string,
+ suffix: PropTypes.string,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ }
+
+ static defaultProps = {
+ placeholder: '0',
+ }
+
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ value: props.value || '',
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ const { value: prevPropsValue } = prevProps
+ const { value: propsValue } = this.props
+ const { value: stateValue } = this.state
+
+ if (prevPropsValue !== propsValue && propsValue !== stateValue) {
+ this.setState({ value: propsValue })
+ }
+ }
+
+ handleFocus = () => {
+ this.unitInput.focus()
+ }
+
+ handleChange = event => {
+ const { value: userInput } = event.target
+ let value = userInput
+
+ if (userInput.length && userInput.length > 1) {
+ value = removeLeadingZeroes(userInput)
+ }
+
+ this.setState({ value })
+ this.props.onChange(value)
+ }
+
+ handleBlur = event => {
+ const { onBlur } = this.props
+ typeof onBlur === 'function' && onBlur(this.state.value)
+ }
+
+ getInputWidth (value) {
+ const valueString = String(value)
+ const valueLength = valueString.length || 1
+ const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
+ return (valueLength + decimalPointDeficit + 0.75) + 'ch'
+ }
+
+ render () {
+ const { error, placeholder, suffix, children } = this.props
+ const { value } = this.state
+
+ return (
+ <div
+ className={classnames('unit-input', { 'unit-input--error': error })}
+ onClick={this.handleFocus}
+ >
+ <div className="unit-input__input-container">
+ <input
+ type="number"
+ className="unit-input__input"
+ value={value}
+ placeholder={placeholder}
+ onChange={this.handleChange}
+ onBlur={this.handleBlur}
+ style={{ width: this.getInputWidth(value) }}
+ ref={ref => { this.unitInput = ref }}
+ />
+ {
+ suffix && (
+ <div className="unit-input__suffix">
+ { suffix }
+ </div>
+ )
+ }
+ </div>
+ { children }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/user-preferenced-currency-display/index.js b/ui/app/components/user-preferenced-currency-display/index.js
new file mode 100644
index 000000000..0deddaecf
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-display/index.js
@@ -0,0 +1 @@
+export { default } from './user-preferenced-currency-display.container'
diff --git a/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js b/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js
new file mode 100644
index 000000000..ead584c26
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js
@@ -0,0 +1,34 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display.component'
+import CurrencyDisplay from '../../currency-display'
+
+describe('UserPreferencedCurrencyDisplay Component', () => {
+ describe('rendering', () => {
+ it('should render properly', () => {
+ const wrapper = shallow(
+ <UserPreferencedCurrencyDisplay />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ })
+
+ it('should pass all props to the CurrencyDisplay child component', () => {
+ const wrapper = shallow(
+ <UserPreferencedCurrencyDisplay
+ prop1={true}
+ prop2="test"
+ prop3={1}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ assert.equal(wrapper.find(CurrencyDisplay).props().prop1, true)
+ assert.equal(wrapper.find(CurrencyDisplay).props().prop2, 'test')
+ assert.equal(wrapper.find(CurrencyDisplay).props().prop3, 1)
+ })
+ })
+})
diff --git a/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js b/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
new file mode 100644
index 000000000..ba1c23d83
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
@@ -0,0 +1,115 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps, mergeProps
+
+proxyquire('../user-preferenced-currency-display.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+})
+
+describe('UserPreferencedCurrencyDisplay container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props', () => {
+ const mockState = {
+ metamask: {
+ nativeCurrency: 'ETH',
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ nativeCurrency: 'ETH',
+ useNativeCurrencyAsPrimaryCurrency: true,
+ })
+ })
+ })
+
+ describe('mergeProps()', () => {
+ it('should return the correct props', () => {
+ const mockDispatchProps = {}
+
+ const tests = [
+ {
+ stateProps: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ nativeCurrency: 'ETH',
+ },
+ ownProps: {
+ type: 'PRIMARY',
+ },
+ result: {
+ currency: 'ETH',
+ nativeCurrency: 'ETH',
+ numberOfDecimals: 6,
+ prefix: undefined,
+ },
+ },
+ {
+ stateProps: {
+ useNativeCurrencyAsPrimaryCurrency: false,
+ nativeCurrency: 'ETH',
+ },
+ ownProps: {
+ type: 'PRIMARY',
+ },
+ result: {
+ currency: undefined,
+ nativeCurrency: 'ETH',
+ numberOfDecimals: 2,
+ prefix: undefined,
+ },
+ },
+ {
+ stateProps: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ nativeCurrency: 'ETH',
+ },
+ ownProps: {
+ type: 'SECONDARY',
+ fiatNumberOfDecimals: 4,
+ fiatPrefix: '-',
+ },
+ result: {
+ nativeCurrency: 'ETH',
+ currency: undefined,
+ numberOfDecimals: 4,
+ prefix: '-',
+ },
+ },
+ {
+ stateProps: {
+ useNativeCurrencyAsPrimaryCurrency: false,
+ nativeCurrency: 'ETH',
+ },
+ ownProps: {
+ type: 'SECONDARY',
+ fiatNumberOfDecimals: 4,
+ numberOfDecimals: 3,
+ fiatPrefix: 'a',
+ prefix: 'b',
+ },
+ result: {
+ currency: 'ETH',
+ nativeCurrency: 'ETH',
+ numberOfDecimals: 3,
+ prefix: 'b',
+ },
+ },
+ ]
+
+ tests.forEach(({ stateProps, ownProps, result }) => {
+ assert.deepEqual(mergeProps({ ...stateProps }, mockDispatchProps, { ...ownProps }), {
+ ...result,
+ })
+ })
+ })
+ })
+})
diff --git a/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js b/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js
new file mode 100644
index 000000000..f2a834ea7
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js
@@ -0,0 +1,46 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { PRIMARY, SECONDARY, ETH } from '../../constants/common'
+import CurrencyDisplay from '../currency-display'
+
+export default class UserPreferencedCurrencyDisplay extends PureComponent {
+ static propTypes = {
+ className: PropTypes.string,
+ prefix: PropTypes.string,
+ value: PropTypes.string,
+ numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ hideLabel: PropTypes.bool,
+ style: PropTypes.object,
+ showEthLogo: PropTypes.bool,
+ ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ // Used in container
+ type: PropTypes.oneOf([PRIMARY, SECONDARY]),
+ ethNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ fiatNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ ethPrefix: PropTypes.string,
+ fiatPrefix: PropTypes.string,
+ // From container
+ currency: PropTypes.string,
+ nativeCurrency: PropTypes.string,
+ }
+
+ renderEthLogo () {
+ const { currency, showEthLogo, ethLogoHeight = 12 } = this.props
+
+ return currency === ETH && showEthLogo && (
+ <img
+ src="/images/eth.svg"
+ height={ethLogoHeight}
+ />
+ )
+ }
+
+ render () {
+ return (
+ <CurrencyDisplay
+ {...this.props}
+ prefixComponent={this.renderEthLogo()}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.container.js b/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.container.js
new file mode 100644
index 000000000..7999301ad
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.container.js
@@ -0,0 +1,54 @@
+import { connect } from 'react-redux'
+import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display.component'
+import { preferencesSelector } from '../../selectors'
+import { ETH, PRIMARY, SECONDARY } from '../../constants/common'
+
+const mapStateToProps = (state, ownProps) => {
+ const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
+
+ return {
+ useNativeCurrencyAsPrimaryCurrency,
+ nativeCurrency: state.metamask.nativeCurrency,
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { useNativeCurrencyAsPrimaryCurrency, nativeCurrency, ...restStateProps } = stateProps
+ const {
+ type,
+ numberOfDecimals: propsNumberOfDecimals,
+ ethNumberOfDecimals,
+ fiatNumberOfDecimals,
+ ethPrefix,
+ fiatPrefix,
+ prefix: propsPrefix,
+ ...restOwnProps
+ } = ownProps
+
+ let currency, numberOfDecimals, prefix
+
+ if (type === PRIMARY && useNativeCurrencyAsPrimaryCurrency ||
+ type === SECONDARY && !useNativeCurrencyAsPrimaryCurrency) {
+ // Display ETH
+ currency = nativeCurrency || ETH
+ numberOfDecimals = propsNumberOfDecimals || ethNumberOfDecimals || 6
+ prefix = propsPrefix || ethPrefix
+ } else if (type === SECONDARY && useNativeCurrencyAsPrimaryCurrency ||
+ type === PRIMARY && !useNativeCurrencyAsPrimaryCurrency) {
+ // Display Fiat
+ numberOfDecimals = propsNumberOfDecimals || fiatNumberOfDecimals || 2
+ prefix = propsPrefix || fiatPrefix
+ }
+
+ return {
+ ...restStateProps,
+ ...dispatchProps,
+ ...restOwnProps,
+ nativeCurrency,
+ currency,
+ numberOfDecimals,
+ prefix,
+ }
+}
+
+export default connect(mapStateToProps, null, mergeProps)(UserPreferencedCurrencyDisplay)
diff --git a/ui/app/components/user-preferenced-currency-input/index.js b/ui/app/components/user-preferenced-currency-input/index.js
new file mode 100644
index 000000000..4dc70db3d
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-input/index.js
@@ -0,0 +1 @@
+export { default } from './user-preferenced-currency-input.container'
diff --git a/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js b/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js
new file mode 100644
index 000000000..710b5d519
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import UserPreferencedCurrencyInput from '../user-preferenced-currency-input.component'
+import CurrencyInput from '../../currency-input'
+
+describe('UserPreferencedCurrencyInput Component', () => {
+ describe('rendering', () => {
+ it('should render properly', () => {
+ const wrapper = shallow(
+ <UserPreferencedCurrencyInput />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(CurrencyInput).length, 1)
+ })
+
+ it('should render useFiat for CurrencyInput based on preferences.useNativeCurrencyAsPrimaryCurrency', () => {
+ const wrapper = shallow(
+ <UserPreferencedCurrencyInput
+ useNativeCurrencyAsPrimaryCurrency
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(CurrencyInput).length, 1)
+ assert.equal(wrapper.find(CurrencyInput).props().useFiat, false)
+ wrapper.setProps({ useNativeCurrencyAsPrimaryCurrency: false })
+ assert.equal(wrapper.find(CurrencyInput).props().useFiat, true)
+ })
+ })
+})
diff --git a/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js b/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js
new file mode 100644
index 000000000..959726443
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js
@@ -0,0 +1,31 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps
+
+proxyquire('../user-preferenced-currency-input.container.js', {
+ 'react-redux': {
+ connect: ms => {
+ mapStateToProps = ms
+ return () => ({})
+ },
+ },
+})
+
+describe('UserPreferencedCurrencyInput container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props', () => {
+ const mockState = {
+ metamask: {
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ })
+ })
+ })
+})
diff --git a/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js b/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js
new file mode 100644
index 000000000..463e66b80
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js
@@ -0,0 +1,20 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import CurrencyInput from '../currency-input'
+
+export default class UserPreferencedCurrencyInput extends PureComponent {
+ static propTypes = {
+ useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
+ }
+
+ render () {
+ const { useNativeCurrencyAsPrimaryCurrency, ...restProps } = this.props
+
+ return (
+ <CurrencyInput
+ {...restProps}
+ useFiat={!useNativeCurrencyAsPrimaryCurrency}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js b/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js
new file mode 100644
index 000000000..0b88eb5a7
--- /dev/null
+++ b/ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import UserPreferencedCurrencyInput from './user-preferenced-currency-input.component'
+import { preferencesSelector } from '../../selectors'
+
+const mapStateToProps = state => {
+ const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
+
+ return {
+ useNativeCurrencyAsPrimaryCurrency,
+ }
+}
+
+export default connect(mapStateToProps)(UserPreferencedCurrencyInput)
diff --git a/ui/app/components/user-preferenced-token-input/index.js b/ui/app/components/user-preferenced-token-input/index.js
new file mode 100644
index 000000000..54167e633
--- /dev/null
+++ b/ui/app/components/user-preferenced-token-input/index.js
@@ -0,0 +1 @@
+export { default } from './user-preferenced-token-input.container'
diff --git a/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js b/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js
new file mode 100644
index 000000000..d85bddeeb
--- /dev/null
+++ b/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.component.test.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import UserPreferencedTokenInput from '../user-preferenced-token-input.component'
+import TokenInput from '../../token-input'
+
+describe('UserPreferencedCurrencyInput Component', () => {
+ describe('rendering', () => {
+ it('should render properly', () => {
+ const wrapper = shallow(
+ <UserPreferencedTokenInput />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(TokenInput).length, 1)
+ })
+
+ it('should render showFiat for TokenInput based on preferences.useNativeCurrencyAsPrimaryCurrency', () => {
+ const wrapper = shallow(
+ <UserPreferencedTokenInput
+ useNativeCurrencyAsPrimaryCurrency
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(TokenInput).length, 1)
+ assert.equal(wrapper.find(TokenInput).props().showFiat, false)
+ wrapper.setProps({ useNativeCurrencyAsPrimaryCurrency: false })
+ assert.equal(wrapper.find(TokenInput).props().showFiat, true)
+ })
+ })
+})
diff --git a/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js b/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js
new file mode 100644
index 000000000..2f89fba90
--- /dev/null
+++ b/ui/app/components/user-preferenced-token-input/tests/user-preferenced-token-input.container.test.js
@@ -0,0 +1,31 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps
+
+proxyquire('../user-preferenced-token-input.container.js', {
+ 'react-redux': {
+ connect: ms => {
+ mapStateToProps = ms
+ return () => ({})
+ },
+ },
+})
+
+describe('UserPreferencedTokenInput container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props', () => {
+ const mockState = {
+ metamask: {
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ })
+ })
+ })
+})
diff --git a/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.component.js b/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.component.js
new file mode 100644
index 000000000..8f14231ac
--- /dev/null
+++ b/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.component.js
@@ -0,0 +1,20 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import TokenInput from '../token-input'
+
+export default class UserPreferencedTokenInput extends PureComponent {
+ static propTypes = {
+ useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
+ }
+
+ render () {
+ const { useNativeCurrencyAsPrimaryCurrency, ...restProps } = this.props
+
+ return (
+ <TokenInput
+ {...restProps}
+ showFiat={!useNativeCurrencyAsPrimaryCurrency}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.container.js b/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.container.js
new file mode 100644
index 000000000..3305d4e29
--- /dev/null
+++ b/ui/app/components/user-preferenced-token-input/user-preferenced-token-input.container.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import UserPreferencedTokenInput from './user-preferenced-token-input.component'
+import { preferencesSelector } from '../../selectors'
+
+const mapStateToProps = state => {
+ const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
+
+ return {
+ useNativeCurrencyAsPrimaryCurrency,
+ }
+}
+
+export default connect(mapStateToProps)(UserPreferencedTokenInput)
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index 064a6ab55..e050e0ee6 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -7,7 +7,7 @@ const { compose } = require('recompose')
const inherits = require('util').inherits
const classnames = require('classnames')
const { checksumAddress } = require('../util')
-const Identicon = require('./identicon')
+import Identicon from './identicon'
// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
const Tooltip = require('./tooltip-v2.js').default
const copyToClipboard = require('copy-to-clipboard')
@@ -17,7 +17,7 @@ const TokenList = require('./token-list')
const selectors = require('../selectors')
const { ADD_TOKEN_ROUTE } = require('../routes')
-import Button from './button'
+import AddTokenButton from './add-token-button'
module.exports = compose(
withRouter,
@@ -100,19 +100,33 @@ WalletView.prototype.renderWalletBalance = function () {
])
}
+WalletView.prototype.renderAddToken = function () {
+ const {
+ sidebarOpen,
+ hideSidebar,
+ history,
+ } = this.props
+
+ return h(AddTokenButton, {
+ onClick () {
+ history.push(ADD_TOKEN_ROUTE)
+ if (sidebarOpen) {
+ hideSidebar()
+ }
+ },
+ })
+}
+
WalletView.prototype.render = function () {
const {
responsiveDisplayClassname,
selectedAddress,
keyrings,
showAccountDetailModal,
- sidebarOpen,
hideSidebar,
- history,
identities,
} = this.props
// temporary logs + fake extra wallets
- // console.log('walletview, selectedAccount:', selectedAccount)
const checksummedAddress = checksumAddress(selectedAddress)
@@ -201,14 +215,7 @@ WalletView.prototype.render = function () {
h(TokenList),
- h(Button, {
- type: 'primary',
- className: 'wallet-view__add-token-button',
- onClick: () => {
- history.push(ADD_TOKEN_ROUTE)
- sidebarOpen && hideSidebar()
- },
- }, this.context.t('addToken')),
+ this.renderAddToken(),
])
}
diff --git a/ui/app/constants/common.js b/ui/app/constants/common.js
index a20f6cc02..4ff4dc837 100644
--- a/ui/app/constants/common.js
+++ b/ui/app/constants/common.js
@@ -1,3 +1,6 @@
export const ETH = 'ETH'
export const GWEI = 'GWEI'
export const WEI = 'WEI'
+
+export const PRIMARY = 'PRIMARY'
+export const SECONDARY = 'SECONDARY'
diff --git a/ui/app/constants/error-keys.js b/ui/app/constants/error-keys.js
index f70ed3b19..704064c96 100644
--- a/ui/app/constants/error-keys.js
+++ b/ui/app/constants/error-keys.js
@@ -1,3 +1,4 @@
export const INSUFFICIENT_FUNDS_ERROR_KEY = 'insufficientFunds'
export const GAS_LIMIT_TOO_LOW_ERROR_KEY = 'gasLimitTooLow'
export const TRANSACTION_ERROR_KEY = 'transactionError'
+export const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract'
diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss
index 655188a3e..5826a8b49 100644
--- a/ui/app/css/itcss/components/buttons.scss
+++ b/ui/app/css/itcss/components/buttons.scss
@@ -3,7 +3,7 @@
*/
.button {
- height: 44px;
+ min-height: 44px;
background: $white;
display: flex;
justify-content: center;
@@ -82,12 +82,13 @@
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
padding: 6px;
height: initial;
+ min-height: initial;
width: initial;
min-width: initial;
}
.btn--large {
- height: 54px;
+ min-height: 54px;
}
.btn-green {
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index 8e963d495..233e781ef 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -120,18 +120,6 @@ $wallet-view-bg: $alabaster;
}
}
}
-
- &__add-token-button {
- flex: 0 0 auto;
- margin: 36px auto;
- background: none;
- transition: border-color .3s ease;
- width: 150px;
-
- &:hover {
- border-color: $curious-blue;
- }
- }
}
@media screen and (min-width: 576px) {
diff --git a/ui/app/css/itcss/components/pages/index.scss b/ui/app/css/itcss/components/pages/index.scss
index 709f8baf6..45e47f447 100644
--- a/ui/app/css/itcss/components/pages/index.scss
+++ b/ui/app/css/itcss/components/pages/index.scss
@@ -1 +1,3 @@
@import './reveal-seed.scss';
+
+@import './provider-approval.scss';
diff --git a/ui/app/css/itcss/components/pages/provider-approval.scss b/ui/app/css/itcss/components/pages/provider-approval.scss
new file mode 100644
index 000000000..f172165ab
--- /dev/null
+++ b/ui/app/css/itcss/components/pages/provider-approval.scss
@@ -0,0 +1,11 @@
+.provider_approval_content {
+ height: auto;
+ overflow: auto;
+ padding: 16px;
+}
+
+.provider_approval_origin {
+ font-weight: 999;
+ margin-top: 16px;
+ word-wrap: break-word;
+}
diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss
index 445b9ebf5..6c950d846 100644
--- a/ui/app/css/itcss/components/request-signature.scss
+++ b/ui/app/css/itcss/components/request-signature.scss
@@ -19,7 +19,7 @@
}
@media screen and (min-width: $break-large) {
- max-height: 620px;
+ height: 620px;
}
}
diff --git a/ui/app/css/itcss/components/wallet-balance.scss b/ui/app/css/itcss/components/wallet-balance.scss
index 293771550..3c3349ae0 100644
--- a/ui/app/css/itcss/components/wallet-balance.scss
+++ b/ui/app/css/itcss/components/wallet-balance.scss
@@ -29,7 +29,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
align-items: center;
margin: 20px 24px;
flex-direction: row;
- flex-grow: 3;
+ min-width: 0;
@media #{$wallet-balance-breakpoint-range} {
margin: 10% 4%;
@@ -38,8 +38,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
.balance-display {
margin-left: 15px;
- justify-content: flex-start;
- align-items: flex-start;
+ min-width: 0;
.token-amount {
font-size: 1.5rem;
diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js
index 30c32f2bf..275eb1551 100644
--- a/ui/app/ducks/confirm-transaction.duck.js
+++ b/ui/app/ducks/confirm-transaction.duck.js
@@ -2,6 +2,7 @@ import {
conversionRateSelector,
currentCurrencySelector,
unconfirmedTransactionsHashSelector,
+ getNativeCurrency,
} from '../selectors/confirm-transaction'
import {
@@ -14,7 +15,13 @@ import {
hexGreaterThan,
} from '../helpers/confirm-transaction/util'
-import { getTokenData, getMethodData, isSmartContractAddress } from '../helpers/transactions.util'
+import {
+ getTokenData,
+ getMethodData,
+ isSmartContractAddress,
+ sumHexes,
+} from '../helpers/transactions.util'
+
import { getSymbolAndDecimals } from '../token-util'
import { conversionUtil } from '../conversion-util'
@@ -31,7 +38,6 @@ const CLEAR_CONFIRM_TRANSACTION = createActionType('CLEAR_CONFIRM_TRANSACTION')
const UPDATE_TRANSACTION_AMOUNTS = createActionType('UPDATE_TRANSACTION_AMOUNTS')
const UPDATE_TRANSACTION_FEES = createActionType('UPDATE_TRANSACTION_FEES')
const UPDATE_TRANSACTION_TOTALS = createActionType('UPDATE_TRANSACTION_TOTALS')
-const UPDATE_HEX_GAS_TOTAL = createActionType('UPDATE_HEX_GAS_TOTAL')
const UPDATE_TOKEN_PROPS = createActionType('UPDATE_TOKEN_PROPS')
const UPDATE_NONCE = createActionType('UPDATE_NONCE')
const UPDATE_TO_SMART_CONTRACT = createActionType('UPDATE_TO_SMART_CONTRACT')
@@ -53,7 +59,9 @@ const initState = {
ethTransactionAmount: '',
ethTransactionFee: '',
ethTransactionTotal: '',
- hexGasTotal: '',
+ hexTransactionAmount: '',
+ hexTransactionFee: '',
+ hexTransactionTotal: '',
nonce: '',
toSmartContract: false,
fetchingData: false,
@@ -99,30 +107,28 @@ export default function reducer ({ confirmTransaction: confirmState = initState
methodData: {},
}
case UPDATE_TRANSACTION_AMOUNTS:
- const { fiatTransactionAmount, ethTransactionAmount } = action.payload
+ const { fiatTransactionAmount, ethTransactionAmount, hexTransactionAmount } = action.payload
return {
...confirmState,
fiatTransactionAmount: fiatTransactionAmount || confirmState.fiatTransactionAmount,
ethTransactionAmount: ethTransactionAmount || confirmState.ethTransactionAmount,
+ hexTransactionAmount: hexTransactionAmount || confirmState.hexTransactionAmount,
}
case UPDATE_TRANSACTION_FEES:
- const { fiatTransactionFee, ethTransactionFee } = action.payload
+ const { fiatTransactionFee, ethTransactionFee, hexTransactionFee } = action.payload
return {
...confirmState,
fiatTransactionFee: fiatTransactionFee || confirmState.fiatTransactionFee,
ethTransactionFee: ethTransactionFee || confirmState.ethTransactionFee,
+ hexTransactionFee: hexTransactionFee || confirmState.hexTransactionFee,
}
case UPDATE_TRANSACTION_TOTALS:
- const { fiatTransactionTotal, ethTransactionTotal } = action.payload
+ const { fiatTransactionTotal, ethTransactionTotal, hexTransactionTotal } = action.payload
return {
...confirmState,
fiatTransactionTotal: fiatTransactionTotal || confirmState.fiatTransactionTotal,
ethTransactionTotal: ethTransactionTotal || confirmState.ethTransactionTotal,
- }
- case UPDATE_HEX_GAS_TOTAL:
- return {
- ...confirmState,
- hexGasTotal: action.payload,
+ hexTransactionTotal: hexTransactionTotal || confirmState.hexTransactionTotal,
}
case UPDATE_TOKEN_PROPS:
const { tokenSymbol = '', tokenDecimals = '' } = action.payload
@@ -222,13 +228,6 @@ export function updateTransactionTotals (totals) {
}
}
-export function updateHexGasTotal (hexGasTotal) {
- return {
- type: UPDATE_HEX_GAS_TOTAL,
- payload: hexGasTotal,
- }
-}
-
export function updateTokenProps (tokenProps) {
return {
type: UPDATE_TOKEN_PROPS,
@@ -294,43 +293,53 @@ export function updateTxDataAndCalculate (txData) {
const state = getState()
const currentCurrency = currentCurrencySelector(state)
const conversionRate = conversionRateSelector(state)
+ const nativeCurrency = getNativeCurrency(state)
dispatch(updateTxData(txData))
- const { txParams: { value, gas: gasLimit = '0x0', gasPrice = '0x0' } = {} } = txData
+ const { txParams: { value = '0x0', gas: gasLimit = '0x0', gasPrice = '0x0' } = {} } = txData
const fiatTransactionAmount = getValueFromWeiHex({
- value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
+ value, fromCurrency: nativeCurrency, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
const ethTransactionAmount = getValueFromWeiHex({
- value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6,
+ value, fromCurrency: nativeCurrency, toCurrency: nativeCurrency, conversionRate, numberOfDecimals: 6,
})
- dispatch(updateTransactionAmounts({ fiatTransactionAmount, ethTransactionAmount }))
-
- const hexGasTotal = getHexGasTotal({ gasLimit, gasPrice })
+ dispatch(updateTransactionAmounts({
+ fiatTransactionAmount,
+ ethTransactionAmount,
+ hexTransactionAmount: value,
+ }))
- dispatch(updateHexGasTotal(hexGasTotal))
+ const hexTransactionFee = getHexGasTotal({ gasLimit, gasPrice })
const fiatTransactionFee = getTransactionFee({
- value: hexGasTotal,
+ value: hexTransactionFee,
+ fromCurrency: nativeCurrency,
toCurrency: currentCurrency,
numberOfDecimals: 2,
conversionRate,
})
const ethTransactionFee = getTransactionFee({
- value: hexGasTotal,
- toCurrency: 'ETH',
+ value: hexTransactionFee,
+ fromCurrency: nativeCurrency,
+ toCurrency: nativeCurrency,
numberOfDecimals: 6,
conversionRate,
})
- dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee }))
+ dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee, hexTransactionFee }))
const fiatTransactionTotal = addFiat(fiatTransactionFee, fiatTransactionAmount)
const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount)
+ const hexTransactionTotal = sumHexes(value, hexTransactionFee)
- dispatch(updateTransactionTotals({ fiatTransactionTotal, ethTransactionTotal }))
+ dispatch(updateTransactionTotals({
+ fiatTransactionTotal,
+ ethTransactionTotal,
+ hexTransactionTotal,
+ }))
}
}
diff --git a/ui/app/ducks/tests/confirm-transaction.duck.test.js b/ui/app/ducks/tests/confirm-transaction.duck.test.js
index 1bab0add0..eceacd0bd 100644
--- a/ui/app/ducks/tests/confirm-transaction.duck.test.js
+++ b/ui/app/ducks/tests/confirm-transaction.duck.test.js
@@ -19,7 +19,9 @@ const initialState = {
ethTransactionAmount: '',
ethTransactionFee: '',
ethTransactionTotal: '',
- hexGasTotal: '',
+ hexTransactionAmount: '',
+ hexTransactionFee: '',
+ hexTransactionTotal: '',
nonce: '',
toSmartContract: false,
fetchingData: false,
@@ -34,7 +36,6 @@ const CLEAR_METHOD_DATA = 'metamask/confirm-transaction/CLEAR_METHOD_DATA'
const UPDATE_TRANSACTION_AMOUNTS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS'
const UPDATE_TRANSACTION_FEES = 'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES'
const UPDATE_TRANSACTION_TOTALS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS'
-const UPDATE_HEX_GAS_TOTAL = 'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL'
const UPDATE_TOKEN_PROPS = 'metamask/confirm-transaction/UPDATE_TOKEN_PROPS'
const UPDATE_NONCE = 'metamask/confirm-transaction/UPDATE_NONCE'
const UPDATE_TO_SMART_CONTRACT = 'metamask/confirm-transaction/UPDATE_TO_SMART_CONTRACT'
@@ -65,7 +66,9 @@ describe('Confirm Transaction Duck', () => {
ethTransactionAmount: '1',
ethTransactionFee: '0.000021',
ethTransactionTotal: '469.27',
- hexGasTotal: '0x1319718a5000',
+ hexTransactionAmount: '',
+ hexTransactionFee: '0x1319718a5000',
+ hexTransactionTotal: '',
nonce: '0x0',
toSmartContract: false,
fetchingData: false,
@@ -186,12 +189,14 @@ describe('Confirm Transaction Duck', () => {
payload: {
fiatTransactionAmount: '123.45',
ethTransactionAmount: '.5',
+ hexTransactionAmount: '0x1',
},
}),
{
...mockState.confirmTransaction,
fiatTransactionAmount: '123.45',
ethTransactionAmount: '.5',
+ hexTransactionAmount: '0x1',
}
)
})
@@ -203,12 +208,14 @@ describe('Confirm Transaction Duck', () => {
payload: {
fiatTransactionFee: '123.45',
ethTransactionFee: '.5',
+ hexTransactionFee: '0x1',
},
}),
{
...mockState.confirmTransaction,
fiatTransactionFee: '123.45',
ethTransactionFee: '.5',
+ hexTransactionFee: '0x1',
}
)
})
@@ -220,25 +227,14 @@ describe('Confirm Transaction Duck', () => {
payload: {
fiatTransactionTotal: '123.45',
ethTransactionTotal: '.5',
+ hexTransactionTotal: '0x1',
},
}),
{
...mockState.confirmTransaction,
fiatTransactionTotal: '123.45',
ethTransactionTotal: '.5',
- }
- )
- })
-
- it('should update hexGasTotal when receiving an UPDATE_HEX_GAS_TOTAL action', () => {
- assert.deepEqual(
- ConfirmTransactionReducer(mockState, {
- type: UPDATE_HEX_GAS_TOTAL,
- payload: '0x0',
- }),
- {
- ...mockState.confirmTransaction,
- hexGasTotal: '0x0',
+ hexTransactionTotal: '0x1',
}
)
})
@@ -435,19 +431,6 @@ describe('Confirm Transaction Duck', () => {
)
})
- it('should create an action to update hexGasTotal', () => {
- const hexGasTotal = '0x0'
- const expectedAction = {
- type: UPDATE_HEX_GAS_TOTAL,
- payload: hexGasTotal,
- }
-
- assert.deepEqual(
- actions.updateHexGasTotal(hexGasTotal),
- expectedAction
- )
- })
-
it('should create an action to update tokenProps', () => {
const tokenProps = {
tokenDecimals: '1',
@@ -568,7 +551,6 @@ describe('Confirm Transaction Duck', () => {
const expectedActions = [
'metamask/confirm-transaction/UPDATE_TX_DATA',
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
- 'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
]
@@ -637,7 +619,6 @@ describe('Confirm Transaction Duck', () => {
const expectedActions = [
'metamask/confirm-transaction/UPDATE_TX_DATA',
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
- 'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
]
@@ -687,7 +668,6 @@ describe('Confirm Transaction Duck', () => {
const expectedActions = [
'metamask/confirm-transaction/UPDATE_TX_DATA',
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
- 'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
]
diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js
index bcac22500..eb334a4b8 100644
--- a/ui/app/helpers/confirm-transaction/util.js
+++ b/ui/app/helpers/confirm-transaction/util.js
@@ -55,6 +55,7 @@ export function addFiat (...args) {
export function getValueFromWeiHex ({
value,
+ fromCurrency = 'ETH',
toCurrency,
conversionRate,
numberOfDecimals,
@@ -63,7 +64,7 @@ export function getValueFromWeiHex ({
return conversionUtil(value, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
- fromCurrency: 'ETH',
+ fromCurrency,
toCurrency,
numberOfDecimals,
fromDenomination: 'WEI',
@@ -74,6 +75,7 @@ export function getValueFromWeiHex ({
export function getTransactionFee ({
value,
+ fromCurrency = 'ETH',
toCurrency,
conversionRate,
numberOfDecimals,
@@ -82,7 +84,7 @@ export function getTransactionFee ({
fromNumericBase: 'BN',
toNumericBase: 'dec',
fromDenomination: 'WEI',
- fromCurrency: 'ETH',
+ fromCurrency,
toCurrency,
numberOfDecimals,
conversionRate,
@@ -99,6 +101,7 @@ export function formatCurrency (value, currencyCode) {
export function convertTokenToFiat ({
value,
+ fromCurrency = 'ETH',
toCurrency,
conversionRate,
contractExchangeRate,
@@ -108,6 +111,7 @@ export function convertTokenToFiat ({
return conversionUtil(value, {
fromNumericBase: 'dec',
toNumericBase: 'dec',
+ fromCurrency,
toCurrency,
numberOfDecimals: 2,
conversionRate: totalExchangeRate,
diff --git a/ui/app/helpers/conversions.util.js b/ui/app/helpers/conversions.util.js
index 20ef9e35b..cb5e1b90b 100644
--- a/ui/app/helpers/conversions.util.js
+++ b/ui/app/helpers/conversions.util.js
@@ -20,8 +20,8 @@ export function decimalToHex (decimal) {
})
}
-export function getEthConversionFromWeiHex ({ value, conversionRate, numberOfDecimals = 6 }) {
- const denominations = [ETH, GWEI, WEI]
+export function getEthConversionFromWeiHex ({ value, fromCurrency = ETH, conversionRate, numberOfDecimals = 6 }) {
+ const denominations = [fromCurrency, GWEI, WEI]
let nonZeroDenomination
@@ -29,7 +29,8 @@ export function getEthConversionFromWeiHex ({ value, conversionRate, numberOfDec
const convertedValue = getValueFromWeiHex({
value,
conversionRate,
- toCurrency: ETH,
+ fromCurrency,
+ toCurrency: fromCurrency,
numberOfDecimals,
toDenomination: denominations[i],
})
@@ -45,6 +46,7 @@ export function getEthConversionFromWeiHex ({ value, conversionRate, numberOfDec
export function getValueFromWeiHex ({
value,
+ fromCurrency = ETH,
toCurrency,
conversionRate,
numberOfDecimals,
@@ -53,7 +55,7 @@ export function getValueFromWeiHex ({
return conversionUtil(value, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
- fromCurrency: ETH,
+ fromCurrency,
toCurrency,
numberOfDecimals,
fromDenomination: WEI,
@@ -61,3 +63,22 @@ export function getValueFromWeiHex ({
conversionRate,
})
}
+
+export function getWeiHexFromDecimalValue ({
+ value,
+ fromCurrency,
+ conversionRate,
+ fromDenomination,
+ invertConversionRate,
+}) {
+ return conversionUtil(value, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ toCurrency: ETH,
+ fromCurrency,
+ conversionRate,
+ invertConversionRate,
+ fromDenomination,
+ toDenomination: WEI,
+ })
+}
diff --git a/ui/app/helpers/tests/transactions.util.test.js b/ui/app/helpers/tests/transactions.util.test.js
index 103a84a8c..838522e35 100644
--- a/ui/app/helpers/tests/transactions.util.test.js
+++ b/ui/app/helpers/tests/transactions.util.test.js
@@ -19,4 +19,39 @@ describe('Transactions utils', () => {
assert.doesNotThrow(() => utils.getTokenData())
})
})
+
+ describe('getStatusKey', () => {
+ it('should return the correct status', () => {
+ const tests = [
+ {
+ transaction: {
+ status: 'confirmed',
+ txReceipt: {
+ status: '0x0',
+ },
+ },
+ expected: 'failed',
+ },
+ {
+ transaction: {
+ status: 'confirmed',
+ txReceipt: {
+ status: '0x1',
+ },
+ },
+ expected: 'confirmed',
+ },
+ {
+ transaction: {
+ status: 'pending',
+ },
+ expected: 'pending',
+ },
+ ]
+
+ tests.forEach(({ transaction, expected }) => {
+ assert.equal(utils.getStatusKey(transaction), expected)
+ })
+ })
+ })
})
diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js
index f7d249e63..2f4b1d095 100644
--- a/ui/app/helpers/transactions.util.js
+++ b/ui/app/helpers/transactions.util.js
@@ -27,10 +27,21 @@ export function getTokenData (data = '') {
const registry = new MethodRegistry({ provider: global.ethereumProvider })
+/**
+ * Attempts to return the method data from the MethodRegistry library, if the method exists in the
+ * registry. Otherwise, returns an empty object.
+ * @param {string} data - The hex data (@code txParams.data) of a transaction
+ * @returns {Object}
+ */
export async function getMethodData (data = '') {
const prefixedData = ethUtil.addHexPrefix(data)
const fourBytePrefix = prefixedData.slice(0, 10)
const sig = await registry.lookup(fourBytePrefix)
+
+ if (!sig) {
+ return {}
+ }
+
const parsedResult = registry.parse(sig)
return {
@@ -114,7 +125,9 @@ export function getLatestSubmittedTxWithNonce (transactions = [], nonce = '0x0')
export async function isSmartContractAddress (address) {
const code = await global.eth.getCode(address)
- return code && code !== '0x'
+ // Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
+ const codeIsEmpty = !code || code === '0x' || code === '0x0'
+ return !codeIsEmpty
}
export function sumHexes (...args) {
@@ -126,3 +139,21 @@ export function sumHexes (...args) {
return ethUtil.addHexPrefix(total)
}
+
+/**
+ * Returns a status key for a transaction. Requires parsing the txMeta.txReceipt on top of
+ * txMeta.status because txMeta.status does not reflect on-chain errors.
+ * @param {Object} transaction - The txMeta object of a transaction.
+ * @param {Object} transaction.txReceipt - The transaction receipt.
+ * @returns {string}
+ */
+export function getStatusKey (transaction) {
+ const { txReceipt: { status } = {} } = transaction
+
+ // There was an on-chain failure
+ if (status === '0x0') {
+ return 'failed'
+ }
+
+ return transaction.status
+}
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index 80e76d570..e1a982f93 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -1,3 +1,4 @@
+const clone = require('clone')
const extend = require('xtend')
const copyToClipboard = require('copy-to-clipboard')
@@ -52,19 +53,24 @@ function rootReducer (state, action) {
return state
}
+window.getCleanAppState = function () {
+ const state = clone(window.METAMASK_CACHED_LOG_STATE)
+ // append additional information
+ state.version = global.platform.getVersion()
+ state.browser = window.navigator.userAgent
+ // ensure seedWords are not included
+ if (state.metamask) delete state.metamask.seedWords
+ if (state.appState.currentView) delete state.appState.currentView.seedWords
+ return state
+}
+
window.logStateString = function (cb) {
- const state = window.METAMASK_CACHED_LOG_STATE
- const version = global.platform.getVersion()
- const browser = window.navigator.userAgent
- return global.platform.getPlatformInfo((err, platform) => {
- if (err) {
- return cb(err)
- }
- state.version = version
+ const state = window.getCleanAppState()
+ global.platform.getPlatformInfo((err, platform) => {
+ if (err) return cb(err)
state.platform = platform
- state.browser = browser
const stateString = JSON.stringify(state, removeSeedWords, 2)
- return cb(null, stateString)
+ cb(null, stateString)
})
}
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 3f1d3394f..dfa2f3656 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -51,6 +51,9 @@ function reduceMetamask (state, action) {
isRevealingSeedWords: false,
welcomeScreenSeen: false,
currentLocale: '',
+ preferences: {
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
}, state.metamask)
switch (action.type) {
@@ -71,6 +74,7 @@ function reduceMetamask (state, action) {
case actions.CLEAR_NOTICES:
return extend(metamaskState, {
noActiveNotices: true,
+ nextUnreadNotice: undefined,
})
case actions.UPDATE_METAMASK_STATE:
@@ -291,8 +295,10 @@ function reduceMetamask (state, action) {
amount: '0x0',
memo: '',
errors: {},
+ maxModeOn: false,
editingTransactionId: null,
forceGasMin: null,
+ toNickname: '',
},
})
@@ -330,9 +336,9 @@ function reduceMetamask (state, action) {
})
case actions.SET_USE_BLOCKIE:
- return extend(metamaskState, {
- useBlockie: action.value,
- })
+ return extend(metamaskState, {
+ useBlockie: action.value,
+ })
case actions.UPDATE_FEATURE_FLAGS:
return extend(metamaskState, {
@@ -365,6 +371,12 @@ function reduceMetamask (state, action) {
})
}
+ case actions.UPDATE_PREFERENCES: {
+ return extend(metamaskState, {
+ preferences: { ...action.payload },
+ })
+ }
+
default:
return metamaskState
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index fb4517628..7209f19d1 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -26,6 +26,7 @@ const selectors = {
getAddressBook,
getSendFrom,
getCurrentCurrency,
+ getNativeCurrency,
getSendAmount,
getSelectedTokenToFiatRate,
getSelectedTokenContract,
@@ -33,6 +34,7 @@ const selectors = {
getSendMaxModeState,
getCurrentViewContext,
getTotalUnapprovedCount,
+ preferencesSelector,
}
module.exports = selectors
@@ -142,6 +144,10 @@ function getCurrentCurrency (state) {
return state.metamask.currentCurrency
}
+function getNativeCurrency (state) {
+ return state.metamask.nativeCurrency
+}
+
function getSelectedTokenToFiatRate (state) {
const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
const conversionRate = conversionRateSelector(state)
@@ -195,3 +201,7 @@ function getTotalUnapprovedCount ({ metamask }) {
return Object.keys(unapprovedTxs).length + unapprovedMsgCount + unapprovedPersonalMsgCount +
unapprovedTypedMessagesCount
}
+
+function preferencesSelector ({ metamask }) {
+ return metamask.preferences
+}
diff --git a/ui/app/selectors/confirm-transaction.js b/ui/app/selectors/confirm-transaction.js
index 86b10bac3..90924c036 100644
--- a/ui/app/selectors/confirm-transaction.js
+++ b/ui/app/selectors/confirm-transaction.js
@@ -93,6 +93,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
export const currentCurrencySelector = state => state.metamask.currentCurrency
export const conversionRateSelector = state => state.metamask.conversionRate
+export const getNativeCurrency = state => state.metamask.nativeCurrency
const txDataSelector = state => state.confirmTransaction.txData
const tokenDataSelector = state => state.confirmTransaction.tokenData
diff --git a/ui/app/util.js b/ui/app/util.js
index 37c0fb698..b19a028cc 100644
--- a/ui/app/util.js
+++ b/ui/app/util.js
@@ -128,7 +128,7 @@ function parseBalance (balance) {
// Takes wei hex, returns an object with three properties.
// Its "formatted" property is what we generally use to render values.
-function formatBalance (balance, decimalsToKeep, needsParse = true) {
+function formatBalance (balance, decimalsToKeep, needsParse = true, ticker = 'ETH') {
var parsed = needsParse ? parseBalance(balance) : balance.split('.')
var beforeDecimal = parsed[0]
var afterDecimal = parsed[1]
@@ -138,14 +138,14 @@ function formatBalance (balance, decimalsToKeep, needsParse = true) {
if (afterDecimal !== '0') {
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
if (sigFigs) { afterDecimal = sigFigs[0] }
- formatted = '0.' + afterDecimal + ' ETH'
+ formatted = '0.' + afterDecimal + ` ${ticker}`
}
} else {
- formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ' ETH'
+ formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${ticker}`
}
} else {
afterDecimal += Array(decimalsToKeep).join('0')
- formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ' ETH'
+ formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${ticker}`
}
return formatted
}
diff --git a/ui/i18n-helper.js b/ui/i18n-helper.js
index c6a7d0bf1..db07049e1 100644
--- a/ui/i18n-helper.js
+++ b/ui/i18n-helper.js
@@ -13,7 +13,7 @@ const getMessage = (locale, key, substitutions) => {
return null
}
if (!locale[key]) {
- log.error(`Translator - Unable to find value for key "${key}"`)
+ log.warn(`Translator - Unable to find value for key "${key}"`)
return null
}
const entry = locale[key]