mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Adds toggle for primary currency (#5421)
* Add UnitInput component * Add CurrencyInput component * Add UserPreferencedCurrencyInput component * Add UserPreferencedCurrencyDisplay component * Add updatePreferences action * Add styles for CurrencyInput, CurrencyDisplay, and UnitInput * Update SettingsTab page with Primary Currency toggle * Refactor currency displays and inputs to use UserPreferenced displays and inputs * Add TokenInput component * Add UserPreferencedTokenInput component * Use TokenInput in the send screen * Fix unit tests * Fix e2e and integration tests * Remove send/CurrencyDisplay component * Replace diamond unicode character with Eth logo. Fix typos
This commit is contained in:
parent
8bccb88132
commit
badebe017f
@ -361,6 +361,9 @@
|
|||||||
"enterPasswordContinue": {
|
"enterPasswordContinue": {
|
||||||
"message": "Enter password to continue"
|
"message": "Enter password to continue"
|
||||||
},
|
},
|
||||||
|
"eth": {
|
||||||
|
"message": "ETH"
|
||||||
|
},
|
||||||
"etherscanView": {
|
"etherscanView": {
|
||||||
"message": "View account on Etherscan"
|
"message": "View account on Etherscan"
|
||||||
},
|
},
|
||||||
@ -380,7 +383,7 @@
|
|||||||
"message": "Failed"
|
"message": "Failed"
|
||||||
},
|
},
|
||||||
"fiat": {
|
"fiat": {
|
||||||
"message": "FIAT",
|
"message": "Fiat",
|
||||||
"description": "Exchange type"
|
"description": "Exchange type"
|
||||||
},
|
},
|
||||||
"fileImportFail": {
|
"fileImportFail": {
|
||||||
@ -790,6 +793,12 @@
|
|||||||
"prev": {
|
"prev": {
|
||||||
"message": "Prev"
|
"message": "Prev"
|
||||||
},
|
},
|
||||||
|
"primaryCurrencySetting": {
|
||||||
|
"message": "Primary Currency"
|
||||||
|
},
|
||||||
|
"primaryCurrencySettingDescription": {
|
||||||
|
"message": "Select ETH to prioritize displaying values in ETH. Select Fiat to prioritize displaying values in your selected currency."
|
||||||
|
},
|
||||||
"privacyMsg": {
|
"privacyMsg": {
|
||||||
"message": "Privacy Policy"
|
"message": "Privacy Policy"
|
||||||
},
|
},
|
||||||
|
14
app/images/eth.svg
Normal file
14
app/images/eth.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#38393A;}
|
||||||
|
</style>
|
||||||
|
<title>deposit-eth</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="deposit-eth" transform="translate(0.000000, 14.000000)">
|
||||||
|
<path id="Shape" class="st0" d="M19.9,16L7.5,8.7L19.9,26L32.3,8.7L19.9,16L19.9,16z M20.1-14L7.7,6.4l12.4,7.3l12.4-7.2L20.1-14z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 670 B |
@ -38,6 +38,9 @@ class PreferencesController {
|
|||||||
lostIdentities: {},
|
lostIdentities: {},
|
||||||
seedWords: null,
|
seedWords: null,
|
||||||
forgottenPassword: false,
|
forgottenPassword: false,
|
||||||
|
preferences: {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
|
|
||||||
this.diagnostics = opts.diagnostics
|
this.diagnostics = opts.diagnostics
|
||||||
@ -463,6 +466,33 @@ class PreferencesController {
|
|||||||
getFeatureFlags () {
|
getFeatureFlags () {
|
||||||
return this.store.getState().featureFlags
|
return this.store.getState().featureFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the `preferences` property, which is an object. These are user-controlled features
|
||||||
|
* found in the settings page.
|
||||||
|
* @param {string} preference The preference to enable or disable.
|
||||||
|
* @param {boolean} value Indicates whether or not the preference should be enabled or disabled.
|
||||||
|
* @returns {Promise<object>} Promises a new object; the updated preferences object.
|
||||||
|
*/
|
||||||
|
setPreference (preference, value) {
|
||||||
|
const currentPreferences = this.getPreferences()
|
||||||
|
const updatedPreferences = {
|
||||||
|
...currentPreferences,
|
||||||
|
[preference]: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.updateState({ preferences: updatedPreferences })
|
||||||
|
return Promise.resolve(updatedPreferences)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A getter for the `preferences` property
|
||||||
|
* @returns {object} A key-boolean map of user-selected preferences.
|
||||||
|
*/
|
||||||
|
getPreferences () {
|
||||||
|
return this.store.getState().preferences
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
|
@ -387,6 +387,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||||
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
||||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||||
|
setPreference: nodeify(preferencesController.setPreference, preferencesController),
|
||||||
|
|
||||||
// BlacklistController
|
// BlacklistController
|
||||||
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
|
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
|
||||||
|
@ -107,7 +107,10 @@
|
|||||||
"maxModeOn": false,
|
"maxModeOn": false,
|
||||||
"editingTransactionId": null
|
"editingTransactionId": null
|
||||||
},
|
},
|
||||||
"currentLocale": "en"
|
"currentLocale": "en",
|
||||||
|
"preferences": {
|
||||||
|
"useETHAsPrimaryCurrency": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -150,7 +150,10 @@
|
|||||||
"maxModeOn": false,
|
"maxModeOn": false,
|
||||||
"editingTransactionId": null
|
"editingTransactionId": null
|
||||||
},
|
},
|
||||||
"currentLocale": "en"
|
"currentLocale": "en",
|
||||||
|
"preferences": {
|
||||||
|
"useETHAsPrimaryCurrency": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -108,7 +108,10 @@
|
|||||||
"maxModeOn": false,
|
"maxModeOn": false,
|
||||||
"editingTransactionId": null
|
"editingTransactionId": null
|
||||||
},
|
},
|
||||||
"currentLocale": "en"
|
"currentLocale": "en",
|
||||||
|
"preferences": {
|
||||||
|
"useETHAsPrimaryCurrency": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -37,7 +37,10 @@
|
|||||||
"shapeShiftTxList": [],
|
"shapeShiftTxList": [],
|
||||||
"lostAccounts": [],
|
"lostAccounts": [],
|
||||||
"tokens": [],
|
"tokens": [],
|
||||||
"currentLocale": "en"
|
"currentLocale": "en",
|
||||||
|
"preferences": {
|
||||||
|
"useETHAsPrimaryCurrency": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -109,7 +109,10 @@
|
|||||||
"maxModeOn": false,
|
"maxModeOn": false,
|
||||||
"editingTransactionId": null
|
"editingTransactionId": null
|
||||||
},
|
},
|
||||||
"currentLocale": "en"
|
"currentLocale": "en",
|
||||||
|
"preferences": {
|
||||||
|
"useETHAsPrimaryCurrency": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -102,7 +102,10 @@
|
|||||||
"shapeShiftTxList": [{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522377459106}],
|
"shapeShiftTxList": [{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522377459106}],
|
||||||
"lostAccounts": [],
|
"lostAccounts": [],
|
||||||
"send": {},
|
"send": {},
|
||||||
"currentLocale": "en"
|
"currentLocale": "en",
|
||||||
|
"preferences": {
|
||||||
|
"useETHAsPrimaryCurrency": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -286,7 +286,7 @@ describe('Using MetaMask with an existing account', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||||
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
|
||||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
await inputAmount.sendKeys('1')
|
await inputAmount.sendKeys('1')
|
||||||
|
|
||||||
|
@ -383,7 +383,7 @@ describe('MetaMask', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||||
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
|
||||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
await inputAmount.sendKeys('1')
|
await inputAmount.sendKeys('1')
|
||||||
|
|
||||||
@ -702,7 +702,7 @@ describe('MetaMask', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||||
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
|
||||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
await inputAmount.sendKeys('50')
|
await inputAmount.sendKeys('50')
|
||||||
|
|
||||||
@ -834,8 +834,8 @@ describe('MetaMask', function () {
|
|||||||
await save.click()
|
await save.click()
|
||||||
await driver.wait(until.stalenessOf(gasModal))
|
await driver.wait(until.stalenessOf(gasModal))
|
||||||
|
|
||||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
|
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
|
||||||
assert.equal(await gasFeeInputs[0].getText(), '♦ 0.0006')
|
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('submits the transaction', async function () {
|
it('submits the transaction', async function () {
|
||||||
@ -957,8 +957,8 @@ describe('MetaMask', function () {
|
|||||||
await save.click()
|
await save.click()
|
||||||
await driver.wait(until.stalenessOf(gasModal))
|
await driver.wait(until.stalenessOf(gasModal))
|
||||||
|
|
||||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
|
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
|
||||||
assert.equal(await gasFeeInputs[0].getText(), '♦ 0.0006')
|
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('submits the transaction', async function () {
|
it('submits the transaction', async function () {
|
||||||
|
@ -40,7 +40,7 @@ async function customizeGas (assert, price, limit, ethFee, usdFee) {
|
|||||||
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
(await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(),
|
(await findAsync(sendGasField, '.currency-display-component'))[0].textContent,
|
||||||
ethFee,
|
ethFee,
|
||||||
'send gas field should show customized gas total'
|
'send gas field should show customized gas total'
|
||||||
)
|
)
|
||||||
@ -97,9 +97,9 @@ async function runSendFlowTest (assert, done) {
|
|||||||
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
|
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
|
||||||
|
|
||||||
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
|
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
|
||||||
sendAmountField.find('.currency-display')[0].click()
|
sendAmountField.find('.unit-input')[0].click()
|
||||||
|
|
||||||
const sendAmountFieldInput = await findAsync(sendAmountField, '.currency-display__input')
|
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
|
||||||
sendAmountFieldInput.val('5.1')
|
sendAmountFieldInput.val('5.1')
|
||||||
reactTriggerChange(sendAmountField.find('input')[0])
|
reactTriggerChange(sendAmountField.find('input')[0])
|
||||||
|
|
||||||
@ -112,9 +112,9 @@ async function runSendFlowTest (assert, done) {
|
|||||||
errorMessage = $('.send-v2__error')
|
errorMessage = $('.send-v2__error')
|
||||||
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
|
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
|
||||||
|
|
||||||
await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
|
await customizeGas(assert, 0, 21000, '0 ETH', '$0.00 USD')
|
||||||
await customizeGas(assert, 1, 21000, '0.000021', '$0.03 USD')
|
await customizeGas(assert, 1, 21000, '0.000021 ETH', '$0.03 USD')
|
||||||
await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD')
|
await customizeGas(assert, 500, 60000, '0.03 ETH', '$36.03 USD')
|
||||||
|
|
||||||
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
|
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
|
||||||
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
|
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
|
||||||
@ -130,11 +130,11 @@ async function runSendFlowTest (assert, done) {
|
|||||||
const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last()
|
const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last()
|
||||||
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
|
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
|
||||||
|
|
||||||
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__fiat')
|
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__secondary')
|
||||||
const confirmScreenGas = confirmScreenRowFiats[0]
|
const confirmScreenGas = confirmScreenRowFiats[0]
|
||||||
assert.equal(confirmScreenGas.textContent, '$3.60', 'confirm screen should show correct gas')
|
assert.equal(confirmScreenGas.textContent, '$3.60', 'confirm screen should show correct gas')
|
||||||
const confirmScreenTotal = confirmScreenRowFiats[1]
|
const confirmScreenTotal = confirmScreenRowFiats[1]
|
||||||
assert.equal(confirmScreenTotal.textContent, '$2,405.36', 'confirm screen should show correct total')
|
assert.equal(confirmScreenTotal.textContent, '$2,405.37', 'confirm screen should show correct total')
|
||||||
|
|
||||||
const confirmScreenBackButton = await queryAsync($, '.confirm-page-container-header__back-button')
|
const confirmScreenBackButton = await queryAsync($, '.confirm-page-container-header__back-button')
|
||||||
confirmScreenBackButton[0].click()
|
confirmScreenBackButton[0].click()
|
||||||
@ -150,9 +150,9 @@ async function runSendFlowTest (assert, done) {
|
|||||||
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
|
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
|
||||||
|
|
||||||
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
|
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
|
||||||
sendAmountFieldInEdit.find('.currency-display')[0].click()
|
sendAmountFieldInEdit.find('.unit-input')[0].click()
|
||||||
|
|
||||||
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.currency-display__input')
|
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')
|
||||||
sendAmountFieldInputInEdit.val('1.0')
|
sendAmountFieldInputInEdit.val('1.0')
|
||||||
reactTriggerChange(sendAmountFieldInputInEdit[0])
|
reactTriggerChange(sendAmountFieldInputInEdit[0])
|
||||||
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
const assert = require('assert')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const { createMockStore } = require('redux-test-utils')
|
|
||||||
const { shallowWithStore } = require('../../lib/render-helpers')
|
|
||||||
const BalanceComponent = require('../../../ui/app/components/balance-component')
|
|
||||||
const mockState = {
|
|
||||||
metamask: {
|
|
||||||
accounts: { abc: {} },
|
|
||||||
network: 1,
|
|
||||||
selectedAddress: 'abc',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('BalanceComponent', function () {
|
|
||||||
let balanceComponent
|
|
||||||
let store
|
|
||||||
let component
|
|
||||||
beforeEach(function () {
|
|
||||||
store = createMockStore(mockState)
|
|
||||||
component = shallowWithStore(h(BalanceComponent), store)
|
|
||||||
balanceComponent = component.dive()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows token balance and convert to fiat value based on conversion rate', function () {
|
|
||||||
const formattedBalance = '1.23 ETH'
|
|
||||||
|
|
||||||
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
|
|
||||||
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 2)
|
|
||||||
|
|
||||||
assert.equal('1.23 ETH', tokenBalance)
|
|
||||||
assert.equal(2.46, fiatDisplayNumber)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows only the token balance when conversion rate is not available', function () {
|
|
||||||
const formattedBalance = '1.23 ETH'
|
|
||||||
|
|
||||||
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
|
|
||||||
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 0)
|
|
||||||
|
|
||||||
assert.equal('1.23 ETH', tokenBalance)
|
|
||||||
assert.equal('N/A', fiatDisplayNumber)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
@ -305,6 +305,12 @@ var actions = {
|
|||||||
updateFeatureFlags,
|
updateFeatureFlags,
|
||||||
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
|
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
setPreference,
|
||||||
|
updatePreferences,
|
||||||
|
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
|
||||||
|
setUseETHAsPrimaryCurrencyPreference,
|
||||||
|
|
||||||
setMouseUserState,
|
setMouseUserState,
|
||||||
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
|
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
|
||||||
|
|
||||||
@ -2298,6 +2304,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 setUseETHAsPrimaryCurrencyPreference (value) {
|
||||||
|
return setPreference('useETHAsPrimaryCurrency', value)
|
||||||
|
}
|
||||||
|
|
||||||
function setNetworkNonce (networkNonce) {
|
function setNetworkNonce (networkNonce) {
|
||||||
return {
|
return {
|
||||||
type: actions.SET_NETWORK_NONCE,
|
type: actions.SET_NETWORK_NONCE,
|
||||||
|
@ -8,11 +8,11 @@ const h = require('react-hyperscript')
|
|||||||
const actions = require('../../actions')
|
const actions = require('../../actions')
|
||||||
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
|
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
|
||||||
const Identicon = require('../identicon')
|
const Identicon = require('../identicon')
|
||||||
const { formatBalance } = require('../../util')
|
|
||||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
|
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
|
||||||
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
|
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
|
||||||
const Tooltip = require('../tooltip')
|
const Tooltip = require('../tooltip')
|
||||||
|
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||||
|
import { PRIMARY } from '../../constants/common'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
SETTINGS_ROUTE,
|
SETTINGS_ROUTE,
|
||||||
@ -163,7 +163,6 @@ AccountMenu.prototype.renderAccounts = function () {
|
|||||||
const isSelected = identity.address === selectedAddress
|
const isSelected = identity.address === selectedAddress
|
||||||
|
|
||||||
const balanceValue = accounts[address] ? accounts[address].balance : ''
|
const balanceValue = accounts[address] ? accounts[address].balance : ''
|
||||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
|
||||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||||
|
|
||||||
const keyring = keyrings.find((kr) => {
|
const keyring = keyrings.find((kr) => {
|
||||||
@ -189,7 +188,11 @@ AccountMenu.prototype.renderAccounts = function () {
|
|||||||
|
|
||||||
h('div.account-menu__account-info', [
|
h('div.account-menu__account-info', [
|
||||||
h('div.account-menu__name', identity.name || ''),
|
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),
|
this.renderKeyringType(keyring),
|
||||||
|
@ -4,10 +4,11 @@ const h = require('react-hyperscript')
|
|||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const TokenBalance = require('./token-balance')
|
const TokenBalance = require('./token-balance')
|
||||||
const Identicon = require('./identicon')
|
const Identicon = require('./identicon')
|
||||||
import CurrencyDisplay from './currency-display'
|
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
|
||||||
|
import { PRIMARY, SECONDARY } from '../constants/common'
|
||||||
const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
|
const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
|
||||||
|
|
||||||
const { formatBalance, generateBalanceObject } = require('../util')
|
const { formatBalance } = require('../util')
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(BalanceComponent)
|
module.exports = connect(mapStateToProps)(BalanceComponent)
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ BalanceComponent.prototype.renderTokenBalance = function () {
|
|||||||
|
|
||||||
BalanceComponent.prototype.renderBalance = function () {
|
BalanceComponent.prototype.renderBalance = function () {
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const { shorten, account } = props
|
const { account } = props
|
||||||
const balanceValue = account && account.balance
|
const balanceValue = account && account.balance
|
||||||
const needsParse = 'needsParse' in props ? props.needsParse : true
|
const needsParse = 'needsParse' in props ? props.needsParse : true
|
||||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
||||||
@ -80,25 +81,20 @@ BalanceComponent.prototype.renderBalance = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return h('div.flex-column.balance-display', {}, [
|
return h('div.flex-column.balance-display', {}, [
|
||||||
h('div.token-amount', {
|
h('div.token-amount', {}, h(UserPreferencedCurrencyDisplay, {
|
||||||
style: {},
|
|
||||||
}, this.getTokenBalance(formattedBalance, shorten)),
|
|
||||||
|
|
||||||
showFiat && h(CurrencyDisplay, {
|
|
||||||
value: balanceValue,
|
value: balanceValue,
|
||||||
|
type: PRIMARY,
|
||||||
|
ethNumberOfDecimals: 3,
|
||||||
|
})),
|
||||||
|
|
||||||
|
showFiat && h(UserPreferencedCurrencyDisplay, {
|
||||||
|
value: balanceValue,
|
||||||
|
type: SECONDARY,
|
||||||
|
ethNumberOfDecimals: 3,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
|
||||||
if (formattedBalance === 'None') return formattedBalance
|
if (formattedBalance === 'None') return formattedBalance
|
||||||
if (conversionRate === 0) return 'N/A'
|
if (conversionRate === 0) return 'N/A'
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||||
|
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||||
|
|
||||||
const ConfirmDetailRow = props => {
|
const ConfirmDetailRow = props => {
|
||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
fiatText,
|
primaryText,
|
||||||
ethText,
|
secondaryText,
|
||||||
onHeaderClick,
|
onHeaderClick,
|
||||||
fiatTextColor,
|
primaryValueTextColor,
|
||||||
headerText,
|
headerText,
|
||||||
headerTextClassName,
|
headerTextClassName,
|
||||||
|
value,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -25,28 +28,57 @@ const ConfirmDetailRow = props => {
|
|||||||
>
|
>
|
||||||
{ headerText }
|
{ headerText }
|
||||||
</div>
|
</div>
|
||||||
<div
|
{
|
||||||
className="confirm-detail-row__fiat"
|
primaryText
|
||||||
style={{ color: fiatTextColor }}
|
? (
|
||||||
>
|
<div
|
||||||
{ fiatText }
|
className="confirm-detail-row__primary"
|
||||||
</div>
|
style={{ color: primaryValueTextColor }}
|
||||||
<div className="confirm-detail-row__eth">
|
>
|
||||||
{ ethText }
|
{ primaryText }
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfirmDetailRow.propTypes = {
|
ConfirmDetailRow.propTypes = {
|
||||||
label: PropTypes.string,
|
|
||||||
fiatText: PropTypes.string,
|
|
||||||
ethText: PropTypes.string,
|
|
||||||
fiatTextColor: PropTypes.string,
|
|
||||||
onHeaderClick: PropTypes.func,
|
|
||||||
headerText: PropTypes.string,
|
headerText: PropTypes.string,
|
||||||
headerTextClassName: 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
|
export default ConfirmDetailRow
|
||||||
|
@ -18,18 +18,14 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__fiat {
|
&__primary {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
white-space: nowrap;
|
justify-content: flex-end;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__eth {
|
&__secondary {
|
||||||
color: $oslo-gray;
|
color: $oslo-gray;
|
||||||
white-space: nowrap;
|
justify-content: flex-end;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header-text {
|
&__header-text {
|
||||||
|
@ -12,17 +12,19 @@ describe('Confirm Detail Row Component', function () {
|
|||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = shallow(<ConfirmDetailRow
|
wrapper = shallow(
|
||||||
errorType={'mockErrorType'}
|
<ConfirmDetailRow
|
||||||
label={'mockLabel'}
|
errorType={'mockErrorType'}
|
||||||
showError={false}
|
label={'mockLabel'}
|
||||||
fiatText = {'mockFiatText'}
|
showError={false}
|
||||||
ethText = {'mockEthText'}
|
primaryText = {'mockFiatText'}
|
||||||
fiatTextColor= {'mockColor'}
|
secondaryText = {'mockEthText'}
|
||||||
onHeaderClick= {propsMethodSpies.onHeaderClick}
|
primaryValueTextColor= {'mockColor'}
|
||||||
headerText = {'mockHeaderText'}
|
onHeaderClick= {propsMethodSpies.onHeaderClick}
|
||||||
headerTextClassName = {'mockHeaderClass'}
|
headerText = {'mockHeaderText'}
|
||||||
/>)
|
headerTextClassName = {'mockHeaderClass'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('render', () => {
|
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')
|
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', () => {
|
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__fiat').childAt(0).text(), 'mockFiatText')
|
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', () => {
|
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__eth').childAt(0).text(), 'mockEthText')
|
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', () => {
|
it('should set the fiatTextColor on confirm-detail-row__primary', () => {
|
||||||
assert.equal(wrapper.find('.confirm-detail-row__fiat').props().style.color, 'mockColor')
|
assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should assure the confirm-detail-row__header-text classname is correct', () => {
|
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()
|
wrapper.find('.confirm-detail-row__header-text').props().onClick()
|
||||||
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
|
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -17,9 +17,10 @@ export default class ConfirmPageContainerContent extends Component {
|
|||||||
nonce: PropTypes.string,
|
nonce: PropTypes.string,
|
||||||
assetImage: PropTypes.string,
|
assetImage: PropTypes.string,
|
||||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
subtitleComponent: PropTypes.node,
|
||||||
summaryComponent: PropTypes.node,
|
summaryComponent: PropTypes.node,
|
||||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
titleComponent: PropTypes.func,
|
titleComponent: PropTypes.node,
|
||||||
warning: PropTypes.string,
|
warning: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +55,9 @@ export default class ConfirmPageContainerContent extends Component {
|
|||||||
errorKey,
|
errorKey,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
title,
|
title,
|
||||||
|
titleComponent,
|
||||||
subtitle,
|
subtitle,
|
||||||
|
subtitleComponent,
|
||||||
hideSubtitle,
|
hideSubtitle,
|
||||||
identiconAddress,
|
identiconAddress,
|
||||||
nonce,
|
nonce,
|
||||||
@ -80,7 +83,9 @@ export default class ConfirmPageContainerContent extends Component {
|
|||||||
})}
|
})}
|
||||||
action={action}
|
action={action}
|
||||||
title={title}
|
title={title}
|
||||||
|
titleComponent={titleComponent}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
subtitleComponent={subtitleComponent}
|
||||||
hideSubtitle={hideSubtitle}
|
hideSubtitle={hideSubtitle}
|
||||||
identiconAddress={identiconAddress}
|
identiconAddress={identiconAddress}
|
||||||
nonce={nonce}
|
nonce={nonce}
|
||||||
|
@ -4,7 +4,18 @@ import classnames from 'classnames'
|
|||||||
import Identicon from '../../../identicon'
|
import Identicon from '../../../identicon'
|
||||||
|
|
||||||
const ConfirmPageContainerSummary = props => {
|
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 (
|
return (
|
||||||
<div className={classnames('confirm-page-container-summary', className)}>
|
<div className={classnames('confirm-page-container-summary', className)}>
|
||||||
@ -32,12 +43,12 @@ const ConfirmPageContainerSummary = props => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div className="confirm-page-container-summary__title-text">
|
<div className="confirm-page-container-summary__title-text">
|
||||||
{ title }
|
{ titleComponent || title }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
|
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
|
||||||
{ subtitle }
|
{ subtitleComponent || subtitle }
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +58,9 @@ const ConfirmPageContainerSummary = props => {
|
|||||||
ConfirmPageContainerSummary.propTypes = {
|
ConfirmPageContainerSummary.propTypes = {
|
||||||
action: PropTypes.string,
|
action: PropTypes.string,
|
||||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
titleComponent: PropTypes.node,
|
||||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
subtitleComponent: PropTypes.node,
|
||||||
hideSubtitle: PropTypes.bool,
|
hideSubtitle: PropTypes.bool,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
identiconAddress: PropTypes.string,
|
identiconAddress: PropTypes.string,
|
||||||
|
@ -16,8 +16,9 @@ export default class ConfirmPageContainer extends Component {
|
|||||||
onEdit: PropTypes.func,
|
onEdit: PropTypes.func,
|
||||||
showEdit: PropTypes.bool,
|
showEdit: PropTypes.bool,
|
||||||
subtitle: PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
|
subtitleComponent: PropTypes.node,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
titleComponent: PropTypes.func,
|
titleComponent: PropTypes.node,
|
||||||
// Sender to Recipient
|
// Sender to Recipient
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromName: PropTypes.string,
|
fromName: PropTypes.string,
|
||||||
@ -65,6 +66,7 @@ export default class ConfirmPageContainer extends Component {
|
|||||||
title,
|
title,
|
||||||
titleComponent,
|
titleComponent,
|
||||||
subtitle,
|
subtitle,
|
||||||
|
subtitleComponent,
|
||||||
hideSubtitle,
|
hideSubtitle,
|
||||||
summaryComponent,
|
summaryComponent,
|
||||||
detailsComponent,
|
detailsComponent,
|
||||||
@ -101,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
|
|||||||
title={title}
|
title={title}
|
||||||
titleComponent={titleComponent}
|
titleComponent={titleComponent}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
subtitleComponent={subtitleComponent}
|
||||||
hideSubtitle={hideSubtitle}
|
hideSubtitle={hideSubtitle}
|
||||||
summaryComponent={summaryComponent}
|
summaryComponent={summaryComponent}
|
||||||
detailsComponent={detailsComponent}
|
detailsComponent={detailsComponent}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import classnames from 'classnames'
|
||||||
import { ETH, GWEI } from '../../constants/common'
|
import { ETH, GWEI } from '../../constants/common'
|
||||||
|
|
||||||
export default class CurrencyDisplay extends PureComponent {
|
export default class CurrencyDisplay extends PureComponent {
|
||||||
@ -7,6 +8,8 @@ export default class CurrencyDisplay extends PureComponent {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
displayValue: PropTypes.string,
|
displayValue: PropTypes.string,
|
||||||
prefix: PropTypes.string,
|
prefix: PropTypes.string,
|
||||||
|
prefixComponent: PropTypes.node,
|
||||||
|
style: PropTypes.object,
|
||||||
// Used in container
|
// Used in container
|
||||||
currency: PropTypes.oneOf([ETH]),
|
currency: PropTypes.oneOf([ETH]),
|
||||||
denomination: PropTypes.oneOf([GWEI]),
|
denomination: PropTypes.oneOf([GWEI]),
|
||||||
@ -16,15 +19,17 @@ export default class CurrencyDisplay extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, displayValue, prefix } = this.props
|
const { className, displayValue, prefix, prefixComponent, style } = this.props
|
||||||
const text = `${prefix || ''}${displayValue}`
|
const text = `${prefix || ''}${displayValue}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={classnames('currency-display-component', className)}
|
||||||
|
style={style}
|
||||||
title={text}
|
title={text}
|
||||||
>
|
>
|
||||||
{ text }
|
{ prefixComponent}
|
||||||
|
<span className="currency-display-component__text">{ text }</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,26 @@ import { connect } from 'react-redux'
|
|||||||
import CurrencyDisplay from './currency-display.component'
|
import CurrencyDisplay from './currency-display.component'
|
||||||
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
|
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = state => {
|
||||||
const { value, numberOfDecimals = 2, currency, denomination, hideLabel } = ownProps
|
|
||||||
const { metamask: { currentCurrency, conversionRate } } = state
|
const { metamask: { currentCurrency, conversionRate } } = state
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentCurrency,
|
||||||
|
conversionRate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||||
|
const { currentCurrency, conversionRate, ...restStateProps } = stateProps
|
||||||
|
const {
|
||||||
|
value,
|
||||||
|
numberOfDecimals = 2,
|
||||||
|
currency,
|
||||||
|
denomination,
|
||||||
|
hideLabel,
|
||||||
|
...restOwnProps
|
||||||
|
} = ownProps
|
||||||
|
|
||||||
const toCurrency = currency || currentCurrency
|
const toCurrency = currency || currentCurrency
|
||||||
const convertedValue = getValueFromWeiHex({
|
const convertedValue = getValueFromWeiHex({
|
||||||
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
||||||
@ -14,8 +30,11 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
|
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...restStateProps,
|
||||||
|
...dispatchProps,
|
||||||
|
...restOwnProps,
|
||||||
displayValue,
|
displayValue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(CurrencyDisplay)
|
export default connect(mapStateToProps, null, mergeProps)(CurrencyDisplay)
|
||||||
|
10
ui/app/components/currency-display/index.scss
Normal file
10
ui/app/components/currency-display/index.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.currency-display-component {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,13 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import proxyquire from 'proxyquire'
|
import proxyquire from 'proxyquire'
|
||||||
|
|
||||||
let mapStateToProps
|
let mapStateToProps, mergeProps
|
||||||
|
|
||||||
proxyquire('../currency-display.container.js', {
|
proxyquire('../currency-display.container.js', {
|
||||||
'react-redux': {
|
'react-redux': {
|
||||||
connect: ms => {
|
connect: (ms, md, mp) => {
|
||||||
mapStateToProps = ms
|
mapStateToProps = ms
|
||||||
|
mergeProps = mp
|
||||||
return () => ({})
|
return () => ({})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -22,6 +23,20 @@ describe('CurrencyDisplay container', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(mapStateToProps(mockState), {
|
||||||
|
conversionRate: 280.45,
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mergeProps()', () => {
|
||||||
|
it('should return the correct props', () => {
|
||||||
|
const mockStateProps = {
|
||||||
|
conversionRate: 280.45,
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
}
|
||||||
|
|
||||||
const tests = [
|
const tests = [
|
||||||
{
|
{
|
||||||
props: {
|
props: {
|
||||||
@ -98,7 +113,7 @@ describe('CurrencyDisplay container', () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
tests.forEach(({ props, result }) => {
|
tests.forEach(({ props, result }) => {
|
||||||
assert.deepEqual(mapStateToProps(mockState, props), result)
|
assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
120
ui/app/components/currency-input/currency-input.component.js
Normal file
120
ui/app/components/currency-input/currency-input.component.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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,
|
||||||
|
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 } = this.props
|
||||||
|
const { hexValue } = this.state
|
||||||
|
let currency, numberOfDecimals
|
||||||
|
|
||||||
|
if (useFiat) {
|
||||||
|
// Display ETH
|
||||||
|
currency = 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
27
ui/app/components/currency-input/currency-input.container.js
Normal file
27
ui/app/components/currency-input/currency-input.container.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import CurrencyInput from './currency-input.component'
|
||||||
|
import { ETH } from '../../constants/common'
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const { metamask: { currentCurrency, conversionRate } } = state
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentCurrency,
|
||||||
|
conversionRate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||||
|
const { currentCurrency } = stateProps
|
||||||
|
const { useFiat } = ownProps
|
||||||
|
const suffix = useFiat ? currentCurrency.toUpperCase() : ETH
|
||||||
|
|
||||||
|
return {
|
||||||
|
...stateProps,
|
||||||
|
...dispatchProps,
|
||||||
|
...ownProps,
|
||||||
|
suffix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null, mergeProps)(CurrencyInput)
|
1
ui/app/components/currency-input/index.js
Normal file
1
ui/app/components/currency-input/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './currency-input.container'
|
7
ui/app/components/currency-input/index.scss
Normal file
7
ui/app/components/currency-input/index.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.currency-input {
|
||||||
|
&__conversion-component {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
padding-left: 1px;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
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: {
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
conversionRate: 231.06,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const store = configureMockStore()(mockStore)
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<Provider store={store}>
|
||||||
|
<CurrencyInput
|
||||||
|
suffix="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: {
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
conversionRate: 231.06,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const store = configureMockStore()(mockStore)
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<Provider store={store}>
|
||||||
|
<CurrencyInput
|
||||||
|
value="de0b6b3a7640000"
|
||||||
|
suffix="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.06 USD')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render properly with a fiat value', () => {
|
||||||
|
const mockStore = {
|
||||||
|
metamask: {
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
conversionRate: 231.06,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const store = configureMockStore()(mockStore)
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<Provider store={store}>
|
||||||
|
<CurrencyInput
|
||||||
|
value="f602f2234d0ea"
|
||||||
|
suffix="USD"
|
||||||
|
useFiat
|
||||||
|
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.004328 ETH')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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}>
|
||||||
|
<CurrencyInput
|
||||||
|
onChange={handleChangeSpy}
|
||||||
|
onBlur={handleBlurSpy}
|
||||||
|
suffix="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.00 USD')
|
||||||
|
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.06 USD')
|
||||||
|
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: {
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
conversionRate: 231.06,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const store = configureMockStore()(mockStore)
|
||||||
|
const wrapper = mount(
|
||||||
|
<Provider store={store}>
|
||||||
|
<CurrencyInput
|
||||||
|
onChange={handleChangeSpy}
|
||||||
|
onBlur={handleBlurSpy}
|
||||||
|
suffix="USD"
|
||||||
|
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(), '0 ETH')
|
||||||
|
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.004328 ETH')
|
||||||
|
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: {
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
conversionRate: 231.06,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const store = configureMockStore()(mockStore)
|
||||||
|
const wrapper = shallow(
|
||||||
|
<Provider store={store}>
|
||||||
|
<CurrencyInput
|
||||||
|
onChange={handleChangeSpy}
|
||||||
|
onBlur={handleBlurSpy}
|
||||||
|
suffix="USD"
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,55 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(mapStateToProps(mockState), {
|
||||||
|
conversionRate: 280.45,
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mergeProps()', () => {
|
||||||
|
it('should return the correct props', () => {
|
||||||
|
const mockStateProps = {
|
||||||
|
conversionRate: 280.45,
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
}
|
||||||
|
const mockDispatchProps = {}
|
||||||
|
|
||||||
|
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
|
||||||
|
conversionRate: 280.45,
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
useFiat: true,
|
||||||
|
suffix: 'USD',
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
|
||||||
|
conversionRate: 280.45,
|
||||||
|
currentCurrency: 'usd',
|
||||||
|
suffix: 'ETH',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
@import './confirm-page-container/index';
|
@import './confirm-page-container/index';
|
||||||
|
|
||||||
|
@import './currency-input/index';
|
||||||
|
|
||||||
|
@import './currency-display/index';
|
||||||
|
|
||||||
@import './error-message/index';
|
@import './error-message/index';
|
||||||
|
|
||||||
@import './export-text-container/index';
|
@import './export-text-container/index';
|
||||||
@ -49,3 +53,5 @@
|
|||||||
@import './app-header/index';
|
@import './app-header/index';
|
||||||
|
|
||||||
@import './sidebars/index';
|
@import './sidebars/index';
|
||||||
|
|
||||||
|
@import './unit-input/index';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import CurrencyDisplay from '../../../currency-display'
|
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
|
||||||
import { ETH } from '../../../../constants/common'
|
import { PRIMARY, SECONDARY } from '../../../../constants/common'
|
||||||
|
|
||||||
export default class CancelTransaction extends PureComponent {
|
export default class CancelTransaction extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -13,15 +13,15 @@ export default class CancelTransaction extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cancel-transaction-gas-fee">
|
<div className="cancel-transaction-gas-fee">
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="cancel-transaction-gas-fee__eth"
|
className="cancel-transaction-gas-fee__eth"
|
||||||
currency={ETH}
|
|
||||||
value={value}
|
value={value}
|
||||||
numberOfDecimals={6}
|
type={PRIMARY}
|
||||||
/>
|
/>
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="cancel-transaction-gas-fee__fiat"
|
className="cancel-transaction-gas-fee__fiat"
|
||||||
value={value}
|
value={value}
|
||||||
|
type={SECONDARY}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import { shallow } from 'enzyme'
|
import { shallow } from 'enzyme'
|
||||||
import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component'
|
import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component'
|
||||||
import CurrencyDisplay from '../../../../currency-display'
|
import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
|
||||||
|
|
||||||
describe('CancelTransactionGasFee Component', () => {
|
describe('CancelTransactionGasFee Component', () => {
|
||||||
it('should render', () => {
|
it('should render', () => {
|
||||||
@ -13,12 +13,11 @@ describe('CancelTransactionGasFee Component', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert.ok(wrapper)
|
assert.ok(wrapper)
|
||||||
assert.equal(wrapper.find(CurrencyDisplay).length, 2)
|
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||||
const ethDisplay = wrapper.find(CurrencyDisplay).at(0)
|
const ethDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(0)
|
||||||
const fiatDisplay = wrapper.find(CurrencyDisplay).at(1)
|
const fiatDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(1)
|
||||||
|
|
||||||
assert.equal(ethDisplay.props().value, '0x3b9aca00')
|
assert.equal(ethDisplay.props().value, '0x3b9aca00')
|
||||||
assert.equal(ethDisplay.props().currency, 'ETH')
|
|
||||||
assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth')
|
assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth')
|
||||||
|
|
||||||
assert.equal(fiatDisplay.props().value, '0x3b9aca00')
|
assert.equal(fiatDisplay.props().value, '0x3b9aca00')
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||||
|
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||||
import {
|
import {
|
||||||
formatCurrency,
|
formatCurrency,
|
||||||
convertTokenToFiat,
|
convertTokenToFiat,
|
||||||
addFiat,
|
addFiat,
|
||||||
roundExponential,
|
roundExponential,
|
||||||
} from '../../../helpers/confirm-transaction/util'
|
} from '../../../helpers/confirm-transaction/util'
|
||||||
|
import { getWeiHexFromDecimalValue } from '../../../helpers/conversions.util'
|
||||||
|
import { ETH, PRIMARY } from '../../../constants/common'
|
||||||
|
|
||||||
export default class ConfirmTokenTransactionBase extends Component {
|
export default class ConfirmTokenTransactionBase extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -36,19 +39,48 @@ export default class ConfirmTokenTransactionBase extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubtitle () {
|
renderSubtitleComponent () {
|
||||||
const { currentCurrency, contractExchangeRate } = this.props
|
const { contractExchangeRate, tokenAmount } = this.props
|
||||||
|
|
||||||
if (typeof contractExchangeRate === 'undefined') {
|
const decimalEthValue = (tokenAmount * contractExchangeRate) || 0
|
||||||
return this.context.t('noConversionRateAvailable')
|
const hexWeiValue = getWeiHexFromDecimalValue({
|
||||||
} else {
|
value: decimalEthValue,
|
||||||
const fiatTransactionAmount = this.getFiatTransactionAmount()
|
fromCurrency: ETH,
|
||||||
const roundedFiatTransactionAmount = roundExponential(fiatTransactionAmount)
|
fromDenomination: ETH,
|
||||||
return formatCurrency(roundedFiatTransactionAmount, currentCurrency)
|
})
|
||||||
}
|
|
||||||
|
return typeof contractExchangeRate === 'undefined'
|
||||||
|
? (
|
||||||
|
<span>
|
||||||
|
{ this.context.t('noConversionRateAvailable') }
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<UserPreferencedCurrencyDisplay
|
||||||
|
value={hexWeiValue}
|
||||||
|
type={PRIMARY}
|
||||||
|
showEthLogo
|
||||||
|
hideLabel
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getFiatTotalTextOverride () {
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSecondaryTotalTextOverride () {
|
||||||
const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
|
const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
|
||||||
|
|
||||||
if (typeof contractExchangeRate === 'undefined') {
|
if (typeof contractExchangeRate === 'undefined') {
|
||||||
@ -67,7 +99,6 @@ export default class ConfirmTokenTransactionBase extends Component {
|
|||||||
tokenAddress,
|
tokenAddress,
|
||||||
tokenSymbol,
|
tokenSymbol,
|
||||||
tokenAmount,
|
tokenAmount,
|
||||||
ethTransactionTotal,
|
|
||||||
...restProps
|
...restProps
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
@ -78,9 +109,9 @@ export default class ConfirmTokenTransactionBase extends Component {
|
|||||||
toAddress={toAddress}
|
toAddress={toAddress}
|
||||||
identiconAddress={tokenAddress}
|
identiconAddress={tokenAddress}
|
||||||
title={tokensText}
|
title={tokensText}
|
||||||
subtitle={this.getSubtitle()}
|
subtitleComponent={this.renderSubtitleComponent()}
|
||||||
ethTotalTextOverride={`${tokensText} + \u2666 ${ethTransactionTotal}`}
|
primaryTotalTextOverride={this.renderPrimaryTotalTextOverride()}
|
||||||
fiatTotalTextOverride={this.getFiatTotalTextOverride()}
|
secondaryTotalTextOverride={this.getSecondaryTotalTextOverride()}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
|
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
|
||||||
import { formatCurrency } from '../../../helpers/confirm-transaction/util'
|
|
||||||
import { isBalanceSufficient } from '../../send/send.utils'
|
import { isBalanceSufficient } from '../../send/send.utils'
|
||||||
import { DEFAULT_ROUTE } from '../../../routes'
|
import { DEFAULT_ROUTE } from '../../../routes'
|
||||||
import {
|
import {
|
||||||
@ -9,6 +8,8 @@ import {
|
|||||||
TRANSACTION_ERROR_KEY,
|
TRANSACTION_ERROR_KEY,
|
||||||
} from '../../../constants/error-keys'
|
} from '../../../constants/error-keys'
|
||||||
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
|
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 {
|
export default class ConfirmTransactionBase extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -36,7 +37,9 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
fiatTransactionTotal: PropTypes.string,
|
fiatTransactionTotal: PropTypes.string,
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromName: PropTypes.string,
|
fromName: PropTypes.string,
|
||||||
hexGasTotal: PropTypes.string,
|
hexTransactionAmount: PropTypes.string,
|
||||||
|
hexTransactionFee: PropTypes.string,
|
||||||
|
hexTransactionTotal: PropTypes.string,
|
||||||
isTxReprice: PropTypes.bool,
|
isTxReprice: PropTypes.bool,
|
||||||
methodData: PropTypes.object,
|
methodData: PropTypes.object,
|
||||||
nonce: PropTypes.string,
|
nonce: PropTypes.string,
|
||||||
@ -59,8 +62,8 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
detailsComponent: PropTypes.node,
|
detailsComponent: PropTypes.node,
|
||||||
errorKey: PropTypes.string,
|
errorKey: PropTypes.string,
|
||||||
errorMessage: PropTypes.string,
|
errorMessage: PropTypes.string,
|
||||||
ethTotalTextOverride: PropTypes.string,
|
primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||||
fiatTotalTextOverride: PropTypes.string,
|
secondaryTotalTextOverride: PropTypes.string,
|
||||||
hideData: PropTypes.bool,
|
hideData: PropTypes.bool,
|
||||||
hideDetails: PropTypes.bool,
|
hideDetails: PropTypes.bool,
|
||||||
hideSubtitle: PropTypes.bool,
|
hideSubtitle: PropTypes.bool,
|
||||||
@ -70,8 +73,10 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
onEditGas: PropTypes.func,
|
onEditGas: PropTypes.func,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
subtitle: PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
|
subtitleComponent: PropTypes.node,
|
||||||
summaryComponent: PropTypes.node,
|
summaryComponent: PropTypes.node,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
|
titleComponent: PropTypes.node,
|
||||||
valid: PropTypes.bool,
|
valid: PropTypes.bool,
|
||||||
warning: PropTypes.string,
|
warning: PropTypes.string,
|
||||||
}
|
}
|
||||||
@ -105,7 +110,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
const {
|
const {
|
||||||
balance,
|
balance,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
hexGasTotal,
|
hexTransactionFee,
|
||||||
txData: {
|
txData: {
|
||||||
simulationFails,
|
simulationFails,
|
||||||
txParams: {
|
txParams: {
|
||||||
@ -116,7 +121,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
|
|
||||||
const insufficientBalance = balance && !isBalanceSufficient({
|
const insufficientBalance = balance && !isBalanceSufficient({
|
||||||
amount,
|
amount,
|
||||||
gasTotal: hexGasTotal || '0x0',
|
gasTotal: hexTransactionFee || '0x0',
|
||||||
balance,
|
balance,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
})
|
})
|
||||||
@ -153,13 +158,10 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
renderDetails () {
|
renderDetails () {
|
||||||
const {
|
const {
|
||||||
detailsComponent,
|
detailsComponent,
|
||||||
fiatTransactionFee,
|
primaryTotalTextOverride,
|
||||||
ethTransactionFee,
|
secondaryTotalTextOverride,
|
||||||
currentCurrency,
|
hexTransactionFee,
|
||||||
fiatTransactionTotal,
|
hexTransactionTotal,
|
||||||
ethTransactionTotal,
|
|
||||||
fiatTotalTextOverride,
|
|
||||||
ethTotalTextOverride,
|
|
||||||
hideDetails,
|
hideDetails,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
@ -167,16 +169,13 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedCurrency = formatCurrency(fiatTransactionTotal, currentCurrency)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
detailsComponent || (
|
detailsComponent || (
|
||||||
<div className="confirm-page-container-content__details">
|
<div className="confirm-page-container-content__details">
|
||||||
<div className="confirm-page-container-content__gas-fee">
|
<div className="confirm-page-container-content__gas-fee">
|
||||||
<ConfirmDetailRow
|
<ConfirmDetailRow
|
||||||
label="Gas Fee"
|
label="Gas Fee"
|
||||||
fiatText={formatCurrency(fiatTransactionFee, currentCurrency)}
|
value={hexTransactionFee}
|
||||||
ethText={`\u2666 ${ethTransactionFee}`}
|
|
||||||
headerText="Edit"
|
headerText="Edit"
|
||||||
headerTextClassName="confirm-detail-row__header-text--edit"
|
headerTextClassName="confirm-detail-row__header-text--edit"
|
||||||
onHeaderClick={() => this.handleEditGas()}
|
onHeaderClick={() => this.handleEditGas()}
|
||||||
@ -185,11 +184,12 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
<div>
|
<div>
|
||||||
<ConfirmDetailRow
|
<ConfirmDetailRow
|
||||||
label="Total"
|
label="Total"
|
||||||
fiatText={fiatTotalTextOverride || formattedCurrency}
|
value={hexTransactionTotal}
|
||||||
ethText={ethTotalTextOverride || `\u2666 ${ethTransactionTotal}`}
|
primaryText={primaryTotalTextOverride}
|
||||||
|
secondaryText={secondaryTotalTextOverride}
|
||||||
headerText="Amount + Gas Fee"
|
headerText="Amount + Gas Fee"
|
||||||
headerTextClassName="confirm-detail-row__header-text--total"
|
headerTextClassName="confirm-detail-row__header-text--total"
|
||||||
fiatTextColor="#2f9ae0"
|
primaryValueTextColor="#2f9ae0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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 () {
|
render () {
|
||||||
const {
|
const {
|
||||||
isTxReprice,
|
isTxReprice,
|
||||||
@ -319,12 +356,9 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
toName,
|
toName,
|
||||||
toAddress,
|
toAddress,
|
||||||
methodData,
|
methodData,
|
||||||
ethTransactionAmount,
|
|
||||||
fiatTransactionAmount,
|
|
||||||
valid: propsValid = true,
|
valid: propsValid = true,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
errorKey: propsErrorKey,
|
errorKey: propsErrorKey,
|
||||||
currentCurrency,
|
|
||||||
action,
|
action,
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
@ -341,7 +375,6 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
const { submitting, submitError } = this.state
|
const { submitting, submitError } = this.state
|
||||||
|
|
||||||
const { name } = methodData
|
const { name } = methodData
|
||||||
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
|
|
||||||
const { valid, errorKey } = this.getErrorKey()
|
const { valid, errorKey } = this.getErrorKey()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -352,8 +385,10 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
toAddress={toAddress}
|
toAddress={toAddress}
|
||||||
showEdit={onEdit && !isTxReprice}
|
showEdit={onEdit && !isTxReprice}
|
||||||
action={action || name || this.context.t('unknownFunction')}
|
action={action || name || this.context.t('unknownFunction')}
|
||||||
title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`}
|
title={title}
|
||||||
subtitle={subtitle || `\u2666 ${ethTransactionAmount}`}
|
titleComponent={this.renderTitleComponent()}
|
||||||
|
subtitle={subtitle}
|
||||||
|
subtitleComponent={this.renderSubtitleComponent()}
|
||||||
hideSubtitle={hideSubtitle}
|
hideSubtitle={hideSubtitle}
|
||||||
summaryComponent={summaryComponent}
|
summaryComponent={summaryComponent}
|
||||||
detailsComponent={this.renderDetails()}
|
detailsComponent={this.renderDetails()}
|
||||||
|
@ -36,7 +36,9 @@ const mapStateToProps = (state, props) => {
|
|||||||
fiatTransactionAmount,
|
fiatTransactionAmount,
|
||||||
fiatTransactionFee,
|
fiatTransactionFee,
|
||||||
fiatTransactionTotal,
|
fiatTransactionTotal,
|
||||||
hexGasTotal,
|
hexTransactionAmount,
|
||||||
|
hexTransactionFee,
|
||||||
|
hexTransactionTotal,
|
||||||
tokenData,
|
tokenData,
|
||||||
methodData,
|
methodData,
|
||||||
txData,
|
txData,
|
||||||
@ -87,7 +89,9 @@ const mapStateToProps = (state, props) => {
|
|||||||
fiatTransactionAmount,
|
fiatTransactionAmount,
|
||||||
fiatTransactionFee,
|
fiatTransactionFee,
|
||||||
fiatTransactionTotal,
|
fiatTransactionTotal,
|
||||||
hexGasTotal,
|
hexTransactionAmount,
|
||||||
|
hexTransactionFee,
|
||||||
|
hexTransactionTotal,
|
||||||
txData,
|
txData,
|
||||||
tokenData,
|
tokenData,
|
||||||
methodData,
|
methodData,
|
||||||
|
@ -48,4 +48,22 @@
|
|||||||
border-color: $ecstasy;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,8 @@ export default class SettingsTab extends PureComponent {
|
|||||||
sendHexData: PropTypes.bool,
|
sendHexData: PropTypes.bool,
|
||||||
currentCurrency: PropTypes.string,
|
currentCurrency: PropTypes.string,
|
||||||
conversionDate: PropTypes.number,
|
conversionDate: PropTypes.number,
|
||||||
|
useETHAsPrimaryCurrency: PropTypes.bool,
|
||||||
|
setUseETHAsPrimaryCurrencyPreference: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -339,6 +341,56 @@ export default class SettingsTab extends PureComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderUseEthAsPrimaryCurrency () {
|
||||||
|
const { t } = this.context
|
||||||
|
const { useETHAsPrimaryCurrency, setUseETHAsPrimaryCurrencyPreference } = 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="eth-primary-currency"
|
||||||
|
onChange={() => setUseETHAsPrimaryCurrencyPreference(true)}
|
||||||
|
checked={Boolean(useETHAsPrimaryCurrency)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="eth-primary-currency"
|
||||||
|
className="settings-tab__radio-label"
|
||||||
|
>
|
||||||
|
{ t('eth') }
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="settings-tab__radio-button">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="fiat-primary-currency"
|
||||||
|
onChange={() => setUseETHAsPrimaryCurrencyPreference(false)}
|
||||||
|
checked={!useETHAsPrimaryCurrency}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="fiat-primary-currency"
|
||||||
|
className="settings-tab__radio-label"
|
||||||
|
>
|
||||||
|
{ t('fiat') }
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { warning, isMascara } = this.props
|
const { warning, isMascara } = this.props
|
||||||
|
|
||||||
@ -346,6 +398,7 @@ export default class SettingsTab extends PureComponent {
|
|||||||
<div className="settings-page__content">
|
<div className="settings-page__content">
|
||||||
{ warning && <div className="settings-tab__error">{ warning }</div> }
|
{ warning && <div className="settings-tab__error">{ warning }</div> }
|
||||||
{ this.renderCurrentConversion() }
|
{ this.renderCurrentConversion() }
|
||||||
|
{ this.renderUseEthAsPrimaryCurrency() }
|
||||||
{ this.renderCurrentLocale() }
|
{ this.renderCurrentLocale() }
|
||||||
{ this.renderNewRpcUrl() }
|
{ this.renderNewRpcUrl() }
|
||||||
{ this.renderStateLogs() }
|
{ this.renderStateLogs() }
|
||||||
|
@ -11,7 +11,9 @@ import {
|
|||||||
updateCurrentLocale,
|
updateCurrentLocale,
|
||||||
setFeatureFlag,
|
setFeatureFlag,
|
||||||
showModal,
|
showModal,
|
||||||
|
setUseETHAsPrimaryCurrencyPreference,
|
||||||
} from '../../../../actions'
|
} from '../../../../actions'
|
||||||
|
import { preferencesSelector } from '../../../../selectors'
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { appState: { warning }, metamask } = state
|
const { appState: { warning }, metamask } = state
|
||||||
@ -24,6 +26,7 @@ const mapStateToProps = state => {
|
|||||||
isMascara,
|
isMascara,
|
||||||
currentLocale,
|
currentLocale,
|
||||||
} = metamask
|
} = metamask
|
||||||
|
const { useETHAsPrimaryCurrency } = preferencesSelector(state)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
warning,
|
warning,
|
||||||
@ -34,6 +37,7 @@ const mapStateToProps = state => {
|
|||||||
useBlockie,
|
useBlockie,
|
||||||
sendHexData,
|
sendHexData,
|
||||||
provider,
|
provider,
|
||||||
|
useETHAsPrimaryCurrency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +54,9 @@ const mapDispatchToProps = dispatch => {
|
|||||||
},
|
},
|
||||||
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
|
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
|
||||||
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
|
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
|
||||||
|
setUseETHAsPrimaryCurrencyPreference: value => {
|
||||||
|
return dispatch(setUseETHAsPrimaryCurrencyPreference(value))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { checksumAddress } from '../../../util'
|
import { checksumAddress } from '../../../util'
|
||||||
import Identicon from '../../identicon'
|
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 {
|
export default class AccountListItem extends Component {
|
||||||
|
|
||||||
@ -25,8 +26,6 @@ export default class AccountListItem extends Component {
|
|||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
className,
|
className,
|
||||||
conversionRate,
|
|
||||||
currentCurrency,
|
|
||||||
displayAddress = false,
|
displayAddress = false,
|
||||||
displayBalance = true,
|
displayBalance = true,
|
||||||
handleClick,
|
handleClick,
|
||||||
@ -57,16 +56,20 @@ export default class AccountListItem extends Component {
|
|||||||
{ checksumAddress(address) }
|
{ checksumAddress(address) }
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
{displayBalance && <CurrencyDisplay
|
{
|
||||||
className="account-list-item__account-balances"
|
displayBalance && (
|
||||||
conversionRate={conversionRate}
|
<div className="account-list-item__account-balances">
|
||||||
convertedBalanceClassName="account-list-item__account-secondary-balance"
|
<UserPreferencedCurrencyDisplay
|
||||||
convertedCurrency={currentCurrency}
|
type={PRIMARY}
|
||||||
primaryBalanceClassName="account-list-item__account-primary-balance"
|
value={balance}
|
||||||
primaryCurrency="ETH"
|
/>
|
||||||
readOnly={true}
|
<UserPreferencedCurrencyDisplay
|
||||||
value={balance}
|
type={SECONDARY}
|
||||||
/>}
|
value={balance}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { shallow } from 'enzyme'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import proxyquire from 'proxyquire'
|
import proxyquire from 'proxyquire'
|
||||||
import Identicon from '../../../identicon'
|
import Identicon from '../../../identicon'
|
||||||
import CurrencyDisplay from '../../currency-display'
|
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
|
||||||
|
|
||||||
const utilsMethodStubs = {
|
const utilsMethodStubs = {
|
||||||
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
|
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
|
||||||
@ -114,17 +114,11 @@ describe('AccountListItem Component', function () {
|
|||||||
|
|
||||||
it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => {
|
it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => {
|
||||||
wrapper.setProps({ displayBalance: true })
|
wrapper.setProps({ displayBalance: true })
|
||||||
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
|
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
wrapper.find(CurrencyDisplay).props(),
|
wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(),
|
||||||
{
|
{
|
||||||
className: 'account-list-item__account-balances',
|
type: 'PRIMARY',
|
||||||
conversionRate: 4,
|
|
||||||
convertedBalanceClassName: 'account-list-item__account-secondary-balance',
|
|
||||||
convertedCurrency: 'mockCurrentyCurrency',
|
|
||||||
primaryBalanceClassName: 'account-list-item__account-primary-balance',
|
|
||||||
primaryCurrency: 'ETH',
|
|
||||||
readOnly: true,
|
|
||||||
value: 'mockBalance',
|
value: 'mockBalance',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -132,7 +126,7 @@ describe('AccountListItem Component', function () {
|
|||||||
|
|
||||||
it('should not render a CurrencyDisplay if displayBalance is false', () => {
|
it('should not render a CurrencyDisplay if displayBalance is false', () => {
|
||||||
wrapper.setProps({ displayBalance: false })
|
wrapper.setProps({ displayBalance: false })
|
||||||
assert.equal(wrapper.find(CurrencyDisplay).length, 0)
|
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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),
|
|
||||||
|
|
||||||
])
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './currency-display.js'
|
|
@ -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')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
@ -2,7 +2,8 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import SendRowWrapper from '../send-row-wrapper/'
|
import SendRowWrapper from '../send-row-wrapper/'
|
||||||
import AmountMaxButton from './amount-max-button/'
|
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 {
|
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 () {
|
render () {
|
||||||
const {
|
const { gasTotal, inError } = this.props
|
||||||
amount,
|
|
||||||
amountConversionRate,
|
|
||||||
convertedCurrency,
|
|
||||||
gasTotal,
|
|
||||||
inError,
|
|
||||||
primaryCurrency,
|
|
||||||
selectedToken,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SendRowWrapper
|
<SendRowWrapper
|
||||||
@ -102,20 +112,7 @@ export default class SendAmountRow extends Component {
|
|||||||
errorType={'amount'}
|
errorType={'amount'}
|
||||||
>
|
>
|
||||||
{!inError && gasTotal && <AmountMaxButton />}
|
{!inError && gasTotal && <AmountMaxButton />}
|
||||||
<CurrencyDisplay
|
{ this.renderInput() }
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</SendRowWrapper>
|
</SendRowWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
|
|||||||
|
|
||||||
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
|
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
|
||||||
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
|
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
|
||||||
import CurrencyDisplay from '../../../currency-display'
|
import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
|
||||||
|
|
||||||
const propsMethodSpies = {
|
const propsMethodSpies = {
|
||||||
setMaxModeTo: sinon.spy(),
|
setMaxModeTo: sinon.spy(),
|
||||||
@ -150,26 +150,19 @@ describe('SendAmountRow Component', function () {
|
|||||||
assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton))
|
assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render a CurrencyDisplay as the second child of the SendRowWrapper', () => {
|
it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => {
|
||||||
assert(wrapper.find(SendRowWrapper).childAt(1).is(CurrencyDisplay))
|
console.log('HI', wrapper.find(SendRowWrapper).childAt(1))
|
||||||
|
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 {
|
const {
|
||||||
conversionRate,
|
|
||||||
convertedCurrency,
|
|
||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
inError,
|
error,
|
||||||
primaryCurrency,
|
|
||||||
selectedToken,
|
|
||||||
value,
|
value,
|
||||||
} = wrapper.find(SendRowWrapper).childAt(1).props()
|
} = wrapper.find(SendRowWrapper).childAt(1).props()
|
||||||
assert.equal(conversionRate, 'mockAmountConversionRate')
|
assert.equal(error, false)
|
||||||
assert.equal(convertedCurrency, 'mockConvertedCurrency')
|
|
||||||
assert.equal(inError, false)
|
|
||||||
assert.equal(primaryCurrency, 'mockPrimaryCurrency')
|
|
||||||
assert.deepEqual(selectedToken, { address: 'mockTokenAddress' })
|
|
||||||
assert.equal(value, 'mockAmount')
|
assert.equal(value, 'mockAmount')
|
||||||
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
|
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
|
||||||
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
|
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
|
||||||
@ -192,11 +185,5 @@ describe('SendAmountRow Component', function () {
|
|||||||
['mockNewAmount']
|
['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')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, {Component} from 'react'
|
import React, {Component} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 {
|
export default class GasFeeDisplay extends Component {
|
||||||
|
|
||||||
@ -19,27 +19,24 @@ export default class GasFeeDisplay extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const { gasTotal, onClick, gasLoadingError } = this.props
|
||||||
conversionRate,
|
|
||||||
gasTotal,
|
|
||||||
onClick,
|
|
||||||
primaryCurrency = 'ETH',
|
|
||||||
convertedCurrency,
|
|
||||||
gasLoadingError,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="send-v2__gas-fee-display">
|
<div className="send-v2__gas-fee-display">
|
||||||
{gasTotal
|
{gasTotal
|
||||||
? <CurrencyDisplay
|
? (
|
||||||
primaryCurrency={primaryCurrency}
|
<div className="currency-display">
|
||||||
convertedCurrency={convertedCurrency}
|
<UserPreferencedCurrencyDisplay
|
||||||
value={gasTotal}
|
value={gasTotal}
|
||||||
conversionRate={conversionRate}
|
type={PRIMARY}
|
||||||
gasLoadingError={gasLoadingError}
|
/>
|
||||||
convertedPrefix={'$'}
|
<UserPreferencedCurrencyDisplay
|
||||||
readOnly
|
className="currency-display__converted-value"
|
||||||
/>
|
value={gasTotal}
|
||||||
|
type={SECONDARY}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
: gasLoadingError
|
: gasLoadingError
|
||||||
? <div className="currency-display.currency-display--message">
|
? <div className="currency-display.currency-display--message">
|
||||||
{this.context.t('setGasPrice')}
|
{this.context.t('setGasPrice')}
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import {shallow} from 'enzyme'
|
import {shallow} from 'enzyme'
|
||||||
import GasFeeDisplay from '../gas-fee-display.component'
|
import GasFeeDisplay from '../gas-fee-display.component'
|
||||||
import CurrencyDisplay from '../../../../../send/currency-display'
|
import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
|
||||||
@ -29,17 +29,15 @@ describe('SendGasRow Component', function () {
|
|||||||
|
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
it('should render a CurrencyDisplay component', () => {
|
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', () => {
|
it('should render the CurrencyDisplay with the correct props', () => {
|
||||||
const {
|
const {
|
||||||
conversionRate,
|
type,
|
||||||
convertedCurrency,
|
|
||||||
value,
|
value,
|
||||||
} = wrapper.find(CurrencyDisplay).props()
|
} = wrapper.find(UserPreferencedCurrencyDisplay).at(0).props()
|
||||||
assert.equal(conversionRate, 20)
|
assert.equal(type, 'PRIMARY')
|
||||||
assert.equal(convertedCurrency, 'mockConvertedCurrency')
|
|
||||||
assert.equal(value, 'mockGasTotal')
|
assert.equal(value, 'mockGasTotal')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
1
ui/app/components/token-input/index.js
Normal file
1
ui/app/components/token-input/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './token-input.container'
|
@ -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(), '2 ETH')
|
||||||
|
})
|
||||||
|
|
||||||
|
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.12 USD')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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(), '0 ETH')
|
||||||
|
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(), '2 ETH')
|
||||||
|
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.00 USD')
|
||||||
|
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.12 USD')
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -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',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
136
ui/app/components/token-input/token-input.component.js
Normal file
136
ui/app/components/token-input/token-input.component.js
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
27
ui/app/components/token-input/token-input.container.js
Normal file
27
ui/app/components/token-input/token-input.container.js
Normal file
@ -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)
|
@ -4,8 +4,9 @@ import classnames from 'classnames'
|
|||||||
import TransactionBreakdownRow from './transaction-breakdown-row'
|
import TransactionBreakdownRow from './transaction-breakdown-row'
|
||||||
import Card from '../card'
|
import Card from '../card'
|
||||||
import CurrencyDisplay from '../currency-display'
|
import CurrencyDisplay from '../currency-display'
|
||||||
|
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||||
import HexToDecimal from '../hex-to-decimal'
|
import HexToDecimal from '../hex-to-decimal'
|
||||||
import { ETH, GWEI } from '../../constants/common'
|
import { ETH, GWEI, PRIMARY, SECONDARY } from '../../constants/common'
|
||||||
import { getHexGasTotal } from '../../helpers/confirm-transaction/util'
|
import { getHexGasTotal } from '../../helpers/confirm-transaction/util'
|
||||||
import { sumHexes } from '../../helpers/transactions.util'
|
import { sumHexes } from '../../helpers/transactions.util'
|
||||||
|
|
||||||
@ -40,9 +41,9 @@ export default class TransactionBreakdown extends PureComponent {
|
|||||||
className="transaction-breakdown__card"
|
className="transaction-breakdown__card"
|
||||||
>
|
>
|
||||||
<TransactionBreakdownRow title={t('amount')}>
|
<TransactionBreakdownRow title={t('amount')}>
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="transaction-breakdown__value"
|
className="transaction-breakdown__value"
|
||||||
currency={ETH}
|
type={PRIMARY}
|
||||||
value={value}
|
value={value}
|
||||||
/>
|
/>
|
||||||
</TransactionBreakdownRow>
|
</TransactionBreakdownRow>
|
||||||
@ -79,14 +80,14 @@ export default class TransactionBreakdown extends PureComponent {
|
|||||||
</TransactionBreakdownRow>
|
</TransactionBreakdownRow>
|
||||||
<TransactionBreakdownRow title={t('total')}>
|
<TransactionBreakdownRow title={t('total')}>
|
||||||
<div>
|
<div>
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="transaction-breakdown__value transaction-breakdown__value--eth-total"
|
className="transaction-breakdown__value transaction-breakdown__value--eth-total"
|
||||||
currency={ETH}
|
type={PRIMARY}
|
||||||
value={totalInHex}
|
value={totalInHex}
|
||||||
numberOfDecimals={6}
|
|
||||||
/>
|
/>
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="transaction-breakdown__value"
|
className="transaction-breakdown__value"
|
||||||
|
type={SECONDARY}
|
||||||
value={totalInHex}
|
value={totalInHex}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,12 +4,12 @@ import classnames from 'classnames'
|
|||||||
import Identicon from '../identicon'
|
import Identicon from '../identicon'
|
||||||
import TransactionStatus from '../transaction-status'
|
import TransactionStatus from '../transaction-status'
|
||||||
import TransactionAction from '../transaction-action'
|
import TransactionAction from '../transaction-action'
|
||||||
import CurrencyDisplay from '../currency-display'
|
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||||
import TokenCurrencyDisplay from '../token-currency-display'
|
import TokenCurrencyDisplay from '../token-currency-display'
|
||||||
import TransactionListItemDetails from '../transaction-list-item-details'
|
import TransactionListItemDetails from '../transaction-list-item-details'
|
||||||
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
|
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
|
||||||
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
|
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
|
||||||
import { ETH } from '../../constants/common'
|
import { PRIMARY, SECONDARY } from '../../constants/common'
|
||||||
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
|
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
|
||||||
import { getStatusKey } from '../../helpers/transactions.util'
|
import { getStatusKey } from '../../helpers/transactions.util'
|
||||||
|
|
||||||
@ -103,12 +103,11 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
prefix="-"
|
prefix="-"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="transaction-list-item__amount transaction-list-item__amount--primary"
|
className="transaction-list-item__amount transaction-list-item__amount--primary"
|
||||||
value={value}
|
value={value}
|
||||||
|
type={PRIMARY}
|
||||||
prefix="-"
|
prefix="-"
|
||||||
numberOfDecimals={2}
|
|
||||||
currency={ETH}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -119,10 +118,11 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
return token
|
return token
|
||||||
? null
|
? null
|
||||||
: (
|
: (
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="transaction-list-item__amount transaction-list-item__amount--secondary"
|
className="transaction-list-item__amount transaction-list-item__amount--secondary"
|
||||||
prefix="-"
|
|
||||||
value={value}
|
value={value}
|
||||||
|
prefix="-"
|
||||||
|
type={SECONDARY}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import assert from 'assert'
|
|||||||
import { shallow } from 'enzyme'
|
import { shallow } from 'enzyme'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import TokenBalance from '../../token-balance'
|
import TokenBalance from '../../token-balance'
|
||||||
import CurrencyDisplay from '../../currency-display'
|
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||||
import { SEND_ROUTE } from '../../../routes'
|
import { SEND_ROUTE } from '../../../routes'
|
||||||
import TransactionViewBalance from '../transaction-view-balance.component'
|
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').length, 1)
|
||||||
assert.equal(wrapper.find('.transaction-view-balance__button').length, 2)
|
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')
|
const buttons = wrapper.find('.transaction-view-balance__buttons')
|
||||||
assert.equal(propsMethodSpies.showDepositModal.callCount, 0)
|
assert.equal(propsMethodSpies.showDepositModal.callCount, 0)
|
||||||
|
@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
|
|||||||
import Button from '../button'
|
import Button from '../button'
|
||||||
import Identicon from '../identicon'
|
import Identicon from '../identicon'
|
||||||
import TokenBalance from '../token-balance'
|
import TokenBalance from '../token-balance'
|
||||||
import CurrencyDisplay from '../currency-display'
|
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||||
import { SEND_ROUTE } from '../../routes'
|
import { SEND_ROUTE } from '../../routes'
|
||||||
import { ETH } from '../../constants/common'
|
import { PRIMARY, SECONDARY } from '../../constants/common'
|
||||||
|
|
||||||
export default class TransactionViewBalance extends PureComponent {
|
export default class TransactionViewBalance extends PureComponent {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -33,15 +33,17 @@ export default class TransactionViewBalance extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="transaction-view-balance__balance">
|
<div className="transaction-view-balance__balance">
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="transaction-view-balance__primary-balance"
|
className="transaction-view-balance__primary-balance"
|
||||||
value={balance}
|
value={balance}
|
||||||
currency={ETH}
|
type={PRIMARY}
|
||||||
numberOfDecimals={3}
|
ethNumberOfDecimals={3}
|
||||||
/>
|
/>
|
||||||
<CurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
className="transaction-view-balance__secondary-balance"
|
className="transaction-view-balance__secondary-balance"
|
||||||
value={balance}
|
value={balance}
|
||||||
|
type={SECONDARY}
|
||||||
|
ethNumberOfDecimals={3}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
1
ui/app/components/unit-input/index.js
Normal file
1
ui/app/components/unit-input/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './unit-input.component'
|
44
ui/app/components/unit-input/index.scss
Normal file
44
ui/app/components/unit-input/index.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
146
ui/app/components/unit-input/tests/unit-input.component.test.js
Normal file
146
ui/app/components/unit-input/tests/unit-input.component.test.js
Normal file
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
104
ui/app/components/unit-input/unit-input.component.js
Normal file
104
ui/app/components/unit-input/unit-input.component.js
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './user-preferenced-currency-display.container'
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,105 @@
|
|||||||
|
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: {
|
||||||
|
preferences: {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(mapStateToProps(mockState), {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mergeProps()', () => {
|
||||||
|
it('should return the correct props', () => {
|
||||||
|
const mockDispatchProps = {}
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
stateProps: {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
|
ownProps: {
|
||||||
|
type: 'PRIMARY',
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
currency: 'ETH',
|
||||||
|
numberOfDecimals: 6,
|
||||||
|
prefix: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stateProps: {
|
||||||
|
useETHAsPrimaryCurrency: false,
|
||||||
|
},
|
||||||
|
ownProps: {
|
||||||
|
type: 'PRIMARY',
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
currency: undefined,
|
||||||
|
numberOfDecimals: 2,
|
||||||
|
prefix: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stateProps: {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
|
ownProps: {
|
||||||
|
type: 'SECONDARY',
|
||||||
|
fiatNumberOfDecimals: 4,
|
||||||
|
fiatPrefix: '-',
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
currency: undefined,
|
||||||
|
numberOfDecimals: 4,
|
||||||
|
prefix: '-',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stateProps: {
|
||||||
|
useETHAsPrimaryCurrency: false,
|
||||||
|
},
|
||||||
|
ownProps: {
|
||||||
|
type: 'SECONDARY',
|
||||||
|
fiatNumberOfDecimals: 4,
|
||||||
|
numberOfDecimals: 3,
|
||||||
|
fiatPrefix: 'a',
|
||||||
|
prefix: 'b',
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
currency: 'ETH',
|
||||||
|
numberOfDecimals: 3,
|
||||||
|
prefix: 'b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
tests.forEach(({ stateProps, ownProps, result }) => {
|
||||||
|
assert.deepEqual(mergeProps({ ...stateProps }, mockDispatchProps, { ...ownProps }), {
|
||||||
|
...result,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,45 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
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 { useETHAsPrimaryCurrency } = preferencesSelector(state)
|
||||||
|
|
||||||
|
return {
|
||||||
|
useETHAsPrimaryCurrency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||||
|
const { useETHAsPrimaryCurrency, ...restStateProps } = stateProps
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
numberOfDecimals: propsNumberOfDecimals,
|
||||||
|
ethNumberOfDecimals,
|
||||||
|
fiatNumberOfDecimals,
|
||||||
|
ethPrefix,
|
||||||
|
fiatPrefix,
|
||||||
|
prefix: propsPrefix,
|
||||||
|
...restOwnProps
|
||||||
|
} = ownProps
|
||||||
|
|
||||||
|
let currency, numberOfDecimals, prefix
|
||||||
|
|
||||||
|
if (type === PRIMARY && useETHAsPrimaryCurrency ||
|
||||||
|
type === SECONDARY && !useETHAsPrimaryCurrency) {
|
||||||
|
// Display ETH
|
||||||
|
currency = ETH
|
||||||
|
numberOfDecimals = propsNumberOfDecimals || ethNumberOfDecimals || 6
|
||||||
|
prefix = propsPrefix || ethPrefix
|
||||||
|
} else if (type === SECONDARY && useETHAsPrimaryCurrency ||
|
||||||
|
type === PRIMARY && !useETHAsPrimaryCurrency) {
|
||||||
|
// Display Fiat
|
||||||
|
numberOfDecimals = propsNumberOfDecimals || fiatNumberOfDecimals || 2
|
||||||
|
prefix = propsPrefix || fiatPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...restStateProps,
|
||||||
|
...dispatchProps,
|
||||||
|
...restOwnProps,
|
||||||
|
currency,
|
||||||
|
numberOfDecimals,
|
||||||
|
prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null, mergeProps)(UserPreferencedCurrencyDisplay)
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './user-preferenced-currency-input.container'
|
@ -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.useETHAsPrimaryCurrency', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<UserPreferencedCurrencyInput
|
||||||
|
useETHAsPrimaryCurrency
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.ok(wrapper)
|
||||||
|
assert.equal(wrapper.find(CurrencyInput).length, 1)
|
||||||
|
assert.equal(wrapper.find(CurrencyInput).props().useFiat, false)
|
||||||
|
wrapper.setProps({ useETHAsPrimaryCurrency: false })
|
||||||
|
assert.equal(wrapper.find(CurrencyInput).props().useFiat, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -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: {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(mapStateToProps(mockState), {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -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 = {
|
||||||
|
useETHAsPrimaryCurrency: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { useETHAsPrimaryCurrency, ...restProps } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CurrencyInput
|
||||||
|
{...restProps}
|
||||||
|
useFiat={!useETHAsPrimaryCurrency}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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 { useETHAsPrimaryCurrency } = preferencesSelector(state)
|
||||||
|
|
||||||
|
return {
|
||||||
|
useETHAsPrimaryCurrency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(UserPreferencedCurrencyInput)
|
1
ui/app/components/user-preferenced-token-input/index.js
Normal file
1
ui/app/components/user-preferenced-token-input/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './user-preferenced-token-input.container'
|
@ -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.useETHAsPrimaryCurrency', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<UserPreferencedTokenInput
|
||||||
|
useETHAsPrimaryCurrency
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.ok(wrapper)
|
||||||
|
assert.equal(wrapper.find(TokenInput).length, 1)
|
||||||
|
assert.equal(wrapper.find(TokenInput).props().showFiat, false)
|
||||||
|
wrapper.setProps({ useETHAsPrimaryCurrency: false })
|
||||||
|
assert.equal(wrapper.find(TokenInput).props().showFiat, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -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: {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(mapStateToProps(mockState), {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -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 = {
|
||||||
|
useETHAsPrimaryCurrency: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { useETHAsPrimaryCurrency, ...restProps } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TokenInput
|
||||||
|
{...restProps}
|
||||||
|
showFiat={!useETHAsPrimaryCurrency}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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 { useETHAsPrimaryCurrency } = preferencesSelector(state)
|
||||||
|
|
||||||
|
return {
|
||||||
|
useETHAsPrimaryCurrency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(UserPreferencedTokenInput)
|
@ -1,3 +1,6 @@
|
|||||||
export const ETH = 'ETH'
|
export const ETH = 'ETH'
|
||||||
export const GWEI = 'GWEI'
|
export const GWEI = 'GWEI'
|
||||||
export const WEI = 'WEI'
|
export const WEI = 'WEI'
|
||||||
|
|
||||||
|
export const PRIMARY = 'PRIMARY'
|
||||||
|
export const SECONDARY = 'SECONDARY'
|
||||||
|
@ -14,7 +14,13 @@ import {
|
|||||||
hexGreaterThan,
|
hexGreaterThan,
|
||||||
} from '../helpers/confirm-transaction/util'
|
} 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 { getSymbolAndDecimals } from '../token-util'
|
||||||
import { conversionUtil } from '../conversion-util'
|
import { conversionUtil } from '../conversion-util'
|
||||||
|
|
||||||
@ -31,7 +37,6 @@ const CLEAR_CONFIRM_TRANSACTION = createActionType('CLEAR_CONFIRM_TRANSACTION')
|
|||||||
const UPDATE_TRANSACTION_AMOUNTS = createActionType('UPDATE_TRANSACTION_AMOUNTS')
|
const UPDATE_TRANSACTION_AMOUNTS = createActionType('UPDATE_TRANSACTION_AMOUNTS')
|
||||||
const UPDATE_TRANSACTION_FEES = createActionType('UPDATE_TRANSACTION_FEES')
|
const UPDATE_TRANSACTION_FEES = createActionType('UPDATE_TRANSACTION_FEES')
|
||||||
const UPDATE_TRANSACTION_TOTALS = createActionType('UPDATE_TRANSACTION_TOTALS')
|
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_TOKEN_PROPS = createActionType('UPDATE_TOKEN_PROPS')
|
||||||
const UPDATE_NONCE = createActionType('UPDATE_NONCE')
|
const UPDATE_NONCE = createActionType('UPDATE_NONCE')
|
||||||
const UPDATE_TO_SMART_CONTRACT = createActionType('UPDATE_TO_SMART_CONTRACT')
|
const UPDATE_TO_SMART_CONTRACT = createActionType('UPDATE_TO_SMART_CONTRACT')
|
||||||
@ -53,7 +58,9 @@ const initState = {
|
|||||||
ethTransactionAmount: '',
|
ethTransactionAmount: '',
|
||||||
ethTransactionFee: '',
|
ethTransactionFee: '',
|
||||||
ethTransactionTotal: '',
|
ethTransactionTotal: '',
|
||||||
hexGasTotal: '',
|
hexTransactionAmount: '',
|
||||||
|
hexTransactionFee: '',
|
||||||
|
hexTransactionTotal: '',
|
||||||
nonce: '',
|
nonce: '',
|
||||||
toSmartContract: false,
|
toSmartContract: false,
|
||||||
fetchingData: false,
|
fetchingData: false,
|
||||||
@ -99,30 +106,28 @@ export default function reducer ({ confirmTransaction: confirmState = initState
|
|||||||
methodData: {},
|
methodData: {},
|
||||||
}
|
}
|
||||||
case UPDATE_TRANSACTION_AMOUNTS:
|
case UPDATE_TRANSACTION_AMOUNTS:
|
||||||
const { fiatTransactionAmount, ethTransactionAmount } = action.payload
|
const { fiatTransactionAmount, ethTransactionAmount, hexTransactionAmount } = action.payload
|
||||||
return {
|
return {
|
||||||
...confirmState,
|
...confirmState,
|
||||||
fiatTransactionAmount: fiatTransactionAmount || confirmState.fiatTransactionAmount,
|
fiatTransactionAmount: fiatTransactionAmount || confirmState.fiatTransactionAmount,
|
||||||
ethTransactionAmount: ethTransactionAmount || confirmState.ethTransactionAmount,
|
ethTransactionAmount: ethTransactionAmount || confirmState.ethTransactionAmount,
|
||||||
|
hexTransactionAmount: hexTransactionAmount || confirmState.hexTransactionAmount,
|
||||||
}
|
}
|
||||||
case UPDATE_TRANSACTION_FEES:
|
case UPDATE_TRANSACTION_FEES:
|
||||||
const { fiatTransactionFee, ethTransactionFee } = action.payload
|
const { fiatTransactionFee, ethTransactionFee, hexTransactionFee } = action.payload
|
||||||
return {
|
return {
|
||||||
...confirmState,
|
...confirmState,
|
||||||
fiatTransactionFee: fiatTransactionFee || confirmState.fiatTransactionFee,
|
fiatTransactionFee: fiatTransactionFee || confirmState.fiatTransactionFee,
|
||||||
ethTransactionFee: ethTransactionFee || confirmState.ethTransactionFee,
|
ethTransactionFee: ethTransactionFee || confirmState.ethTransactionFee,
|
||||||
|
hexTransactionFee: hexTransactionFee || confirmState.hexTransactionFee,
|
||||||
}
|
}
|
||||||
case UPDATE_TRANSACTION_TOTALS:
|
case UPDATE_TRANSACTION_TOTALS:
|
||||||
const { fiatTransactionTotal, ethTransactionTotal } = action.payload
|
const { fiatTransactionTotal, ethTransactionTotal, hexTransactionTotal } = action.payload
|
||||||
return {
|
return {
|
||||||
...confirmState,
|
...confirmState,
|
||||||
fiatTransactionTotal: fiatTransactionTotal || confirmState.fiatTransactionTotal,
|
fiatTransactionTotal: fiatTransactionTotal || confirmState.fiatTransactionTotal,
|
||||||
ethTransactionTotal: ethTransactionTotal || confirmState.ethTransactionTotal,
|
ethTransactionTotal: ethTransactionTotal || confirmState.ethTransactionTotal,
|
||||||
}
|
hexTransactionTotal: hexTransactionTotal || confirmState.hexTransactionTotal,
|
||||||
case UPDATE_HEX_GAS_TOTAL:
|
|
||||||
return {
|
|
||||||
...confirmState,
|
|
||||||
hexGasTotal: action.payload,
|
|
||||||
}
|
}
|
||||||
case UPDATE_TOKEN_PROPS:
|
case UPDATE_TOKEN_PROPS:
|
||||||
const { tokenSymbol = '', tokenDecimals = '' } = action.payload
|
const { tokenSymbol = '', tokenDecimals = '' } = action.payload
|
||||||
@ -222,13 +227,6 @@ export function updateTransactionTotals (totals) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateHexGasTotal (hexGasTotal) {
|
|
||||||
return {
|
|
||||||
type: UPDATE_HEX_GAS_TOTAL,
|
|
||||||
payload: hexGasTotal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTokenProps (tokenProps) {
|
export function updateTokenProps (tokenProps) {
|
||||||
return {
|
return {
|
||||||
type: UPDATE_TOKEN_PROPS,
|
type: UPDATE_TOKEN_PROPS,
|
||||||
@ -297,7 +295,7 @@ export function updateTxDataAndCalculate (txData) {
|
|||||||
|
|
||||||
dispatch(updateTxData(txData))
|
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({
|
const fiatTransactionAmount = getValueFromWeiHex({
|
||||||
value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
|
value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
|
||||||
@ -306,31 +304,39 @@ export function updateTxDataAndCalculate (txData) {
|
|||||||
value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6,
|
value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6,
|
||||||
})
|
})
|
||||||
|
|
||||||
dispatch(updateTransactionAmounts({ fiatTransactionAmount, ethTransactionAmount }))
|
dispatch(updateTransactionAmounts({
|
||||||
|
fiatTransactionAmount,
|
||||||
|
ethTransactionAmount,
|
||||||
|
hexTransactionAmount: value,
|
||||||
|
}))
|
||||||
|
|
||||||
const hexGasTotal = getHexGasTotal({ gasLimit, gasPrice })
|
const hexTransactionFee = getHexGasTotal({ gasLimit, gasPrice })
|
||||||
|
|
||||||
dispatch(updateHexGasTotal(hexGasTotal))
|
|
||||||
|
|
||||||
const fiatTransactionFee = getTransactionFee({
|
const fiatTransactionFee = getTransactionFee({
|
||||||
value: hexGasTotal,
|
value: hexTransactionFee,
|
||||||
toCurrency: currentCurrency,
|
toCurrency: currentCurrency,
|
||||||
numberOfDecimals: 2,
|
numberOfDecimals: 2,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
})
|
})
|
||||||
const ethTransactionFee = getTransactionFee({
|
const ethTransactionFee = getTransactionFee({
|
||||||
value: hexGasTotal,
|
value: hexTransactionFee,
|
||||||
toCurrency: 'ETH',
|
toCurrency: 'ETH',
|
||||||
numberOfDecimals: 6,
|
numberOfDecimals: 6,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
})
|
})
|
||||||
|
|
||||||
dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee }))
|
dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee, hexTransactionFee }))
|
||||||
|
|
||||||
const fiatTransactionTotal = addFiat(fiatTransactionFee, fiatTransactionAmount)
|
const fiatTransactionTotal = addFiat(fiatTransactionFee, fiatTransactionAmount)
|
||||||
const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount)
|
const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount)
|
||||||
|
console.log('HIHIH', value, hexTransactionFee)
|
||||||
|
const hexTransactionTotal = sumHexes(value, hexTransactionFee)
|
||||||
|
|
||||||
dispatch(updateTransactionTotals({ fiatTransactionTotal, ethTransactionTotal }))
|
dispatch(updateTransactionTotals({
|
||||||
|
fiatTransactionTotal,
|
||||||
|
ethTransactionTotal,
|
||||||
|
hexTransactionTotal,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,9 @@ const initialState = {
|
|||||||
ethTransactionAmount: '',
|
ethTransactionAmount: '',
|
||||||
ethTransactionFee: '',
|
ethTransactionFee: '',
|
||||||
ethTransactionTotal: '',
|
ethTransactionTotal: '',
|
||||||
hexGasTotal: '',
|
hexTransactionAmount: '',
|
||||||
|
hexTransactionFee: '',
|
||||||
|
hexTransactionTotal: '',
|
||||||
nonce: '',
|
nonce: '',
|
||||||
toSmartContract: false,
|
toSmartContract: false,
|
||||||
fetchingData: 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_AMOUNTS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS'
|
||||||
const UPDATE_TRANSACTION_FEES = 'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES'
|
const UPDATE_TRANSACTION_FEES = 'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES'
|
||||||
const UPDATE_TRANSACTION_TOTALS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS'
|
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_TOKEN_PROPS = 'metamask/confirm-transaction/UPDATE_TOKEN_PROPS'
|
||||||
const UPDATE_NONCE = 'metamask/confirm-transaction/UPDATE_NONCE'
|
const UPDATE_NONCE = 'metamask/confirm-transaction/UPDATE_NONCE'
|
||||||
const UPDATE_TO_SMART_CONTRACT = 'metamask/confirm-transaction/UPDATE_TO_SMART_CONTRACT'
|
const UPDATE_TO_SMART_CONTRACT = 'metamask/confirm-transaction/UPDATE_TO_SMART_CONTRACT'
|
||||||
@ -65,7 +66,9 @@ describe('Confirm Transaction Duck', () => {
|
|||||||
ethTransactionAmount: '1',
|
ethTransactionAmount: '1',
|
||||||
ethTransactionFee: '0.000021',
|
ethTransactionFee: '0.000021',
|
||||||
ethTransactionTotal: '469.27',
|
ethTransactionTotal: '469.27',
|
||||||
hexGasTotal: '0x1319718a5000',
|
hexTransactionAmount: '',
|
||||||
|
hexTransactionFee: '0x1319718a5000',
|
||||||
|
hexTransactionTotal: '',
|
||||||
nonce: '0x0',
|
nonce: '0x0',
|
||||||
toSmartContract: false,
|
toSmartContract: false,
|
||||||
fetchingData: false,
|
fetchingData: false,
|
||||||
@ -186,12 +189,14 @@ describe('Confirm Transaction Duck', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
fiatTransactionAmount: '123.45',
|
fiatTransactionAmount: '123.45',
|
||||||
ethTransactionAmount: '.5',
|
ethTransactionAmount: '.5',
|
||||||
|
hexTransactionAmount: '0x1',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
...mockState.confirmTransaction,
|
...mockState.confirmTransaction,
|
||||||
fiatTransactionAmount: '123.45',
|
fiatTransactionAmount: '123.45',
|
||||||
ethTransactionAmount: '.5',
|
ethTransactionAmount: '.5',
|
||||||
|
hexTransactionAmount: '0x1',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -203,12 +208,14 @@ describe('Confirm Transaction Duck', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
fiatTransactionFee: '123.45',
|
fiatTransactionFee: '123.45',
|
||||||
ethTransactionFee: '.5',
|
ethTransactionFee: '.5',
|
||||||
|
hexTransactionFee: '0x1',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
...mockState.confirmTransaction,
|
...mockState.confirmTransaction,
|
||||||
fiatTransactionFee: '123.45',
|
fiatTransactionFee: '123.45',
|
||||||
ethTransactionFee: '.5',
|
ethTransactionFee: '.5',
|
||||||
|
hexTransactionFee: '0x1',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -220,25 +227,14 @@ describe('Confirm Transaction Duck', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
fiatTransactionTotal: '123.45',
|
fiatTransactionTotal: '123.45',
|
||||||
ethTransactionTotal: '.5',
|
ethTransactionTotal: '.5',
|
||||||
|
hexTransactionTotal: '0x1',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
...mockState.confirmTransaction,
|
...mockState.confirmTransaction,
|
||||||
fiatTransactionTotal: '123.45',
|
fiatTransactionTotal: '123.45',
|
||||||
ethTransactionTotal: '.5',
|
ethTransactionTotal: '.5',
|
||||||
}
|
hexTransactionTotal: '0x1',
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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',
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -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', () => {
|
it('should create an action to update tokenProps', () => {
|
||||||
const tokenProps = {
|
const tokenProps = {
|
||||||
tokenDecimals: '1',
|
tokenDecimals: '1',
|
||||||
@ -568,7 +551,6 @@ describe('Confirm Transaction Duck', () => {
|
|||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
||||||
'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
|
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
||||||
]
|
]
|
||||||
@ -637,7 +619,6 @@ describe('Confirm Transaction Duck', () => {
|
|||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
||||||
'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
|
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
||||||
]
|
]
|
||||||
@ -687,7 +668,6 @@ describe('Confirm Transaction Duck', () => {
|
|||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
||||||
'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
|
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
||||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
||||||
]
|
]
|
||||||
|
@ -61,3 +61,22 @@ export function getValueFromWeiHex ({
|
|||||||
conversionRate,
|
conversionRate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getWeiHexFromDecimalValue ({
|
||||||
|
value,
|
||||||
|
fromCurrency,
|
||||||
|
conversionRate,
|
||||||
|
fromDenomination,
|
||||||
|
invertConversionRate,
|
||||||
|
}) {
|
||||||
|
return conversionUtil(value, {
|
||||||
|
fromNumericBase: 'dec',
|
||||||
|
toNumericBase: 'hex',
|
||||||
|
toCurrency: ETH,
|
||||||
|
fromCurrency,
|
||||||
|
conversionRate,
|
||||||
|
invertConversionRate,
|
||||||
|
fromDenomination,
|
||||||
|
toDenomination: WEI,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -51,6 +51,9 @@ function reduceMetamask (state, action) {
|
|||||||
isRevealingSeedWords: false,
|
isRevealingSeedWords: false,
|
||||||
welcomeScreenSeen: false,
|
welcomeScreenSeen: false,
|
||||||
currentLocale: '',
|
currentLocale: '',
|
||||||
|
preferences: {
|
||||||
|
useETHAsPrimaryCurrency: true,
|
||||||
|
},
|
||||||
}, state.metamask)
|
}, state.metamask)
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -365,6 +368,12 @@ function reduceMetamask (state, action) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case actions.UPDATE_PREFERENCES: {
|
||||||
|
return extend(metamaskState, {
|
||||||
|
preferences: { ...action.payload },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return metamaskState
|
return metamaskState
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ const selectors = {
|
|||||||
getSendMaxModeState,
|
getSendMaxModeState,
|
||||||
getCurrentViewContext,
|
getCurrentViewContext,
|
||||||
getTotalUnapprovedCount,
|
getTotalUnapprovedCount,
|
||||||
|
preferencesSelector,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = selectors
|
module.exports = selectors
|
||||||
@ -195,3 +196,7 @@ function getTotalUnapprovedCount ({ metamask }) {
|
|||||||
return Object.keys(unapprovedTxs).length + unapprovedMsgCount + unapprovedPersonalMsgCount +
|
return Object.keys(unapprovedTxs).length + unapprovedMsgCount + unapprovedPersonalMsgCount +
|
||||||
unapprovedTypedMessagesCount
|
unapprovedTypedMessagesCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preferencesSelector ({ metamask }) {
|
||||||
|
return metamask.preferences
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user