mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
fix merge conflicts
This commit is contained in:
commit
9c1b2108f6
23
CHANGELOG.md
23
CHANGELOG.md
@ -2,6 +2,22 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
## 4.16.0 Wednesday October 17 2018
|
||||
|
||||
- Feature: Add toggle for primary currency (eth/fiat)
|
||||
- Feature: add tooltip for view etherscan tx
|
||||
- Feature: add Polish translations
|
||||
- Feature: improve Korean translations
|
||||
- Feature: improve Italian translations
|
||||
- Bug Fix: Fix bug with "pending" block reference
|
||||
- Bug Fix: Force AccountTracker to update balances on network change
|
||||
- Bug Fix: Fix document extension check when injecting web3
|
||||
- Bug Fix: Fix some support links
|
||||
|
||||
## 4.15.0 Thursday October 11 2018
|
||||
|
||||
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.14.0` is found to have bugs.
|
||||
|
||||
## 4.14.0 Thursday October 11 2018
|
||||
|
||||
- Update transaction statuses when switching networks.
|
||||
@ -9,13 +25,14 @@
|
||||
- Added rudimentary support for the subscription API to support web3 1.0 and Truffle's Drizzle.
|
||||
- [#5502](https://github.com/MetaMask/metamask-extension/pull/5502) Update Italian translation.
|
||||
|
||||
## 4.12.0 Thursday September 27 2018
|
||||
|
||||
- Reintroduces changes from 4.10.0
|
||||
## 4.13.0
|
||||
|
||||
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.12.0` is found to have bugs.
|
||||
|
||||
## 4.12.0 Thursday September 27 2018
|
||||
|
||||
- Reintroduces changes from 4.10.0
|
||||
|
||||
## 4.11.1 Tuesday September 25 2018
|
||||
|
||||
- Adds Ledger support.
|
||||
|
@ -140,6 +140,9 @@
|
||||
"clickCopy": {
|
||||
"message": "Click to Copy"
|
||||
},
|
||||
"clickToAdd": {
|
||||
"message": "Click on $1 to add them to your account"
|
||||
},
|
||||
"close": {
|
||||
"message": "Close"
|
||||
},
|
||||
@ -361,6 +364,9 @@
|
||||
"enterPasswordContinue": {
|
||||
"message": "Enter password to continue"
|
||||
},
|
||||
"eth": {
|
||||
"message": "ETH"
|
||||
},
|
||||
"etherscanView": {
|
||||
"message": "View account on Etherscan"
|
||||
},
|
||||
@ -380,7 +386,7 @@
|
||||
"message": "Failed"
|
||||
},
|
||||
"fiat": {
|
||||
"message": "FIAT",
|
||||
"message": "Fiat",
|
||||
"description": "Exchange type"
|
||||
},
|
||||
"fileImportFail": {
|
||||
@ -638,6 +644,9 @@
|
||||
"min": {
|
||||
"message": "Minimum"
|
||||
},
|
||||
"missingYourTokens": {
|
||||
"message": "Don't see your tokens?"
|
||||
},
|
||||
"myAccounts": {
|
||||
"message": "My Accounts"
|
||||
},
|
||||
@ -790,6 +799,12 @@
|
||||
"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": {
|
||||
"message": "Privacy Policy"
|
||||
},
|
||||
@ -798,7 +813,7 @@
|
||||
"description": "select this type of file to use to import an account"
|
||||
},
|
||||
"privateKeyWarning": {
|
||||
"message": "Warning: Never disclose this key. Anyone with your private keys can take steal any assets held in your account."
|
||||
"message": "Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account."
|
||||
},
|
||||
"privateNetwork": {
|
||||
"message": "Private Network"
|
||||
@ -1189,7 +1204,7 @@
|
||||
"message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret."
|
||||
},
|
||||
"typePassword": {
|
||||
"message": "Type Your Password"
|
||||
"message": "Type your MetaMask password"
|
||||
},
|
||||
"uiWelcome": {
|
||||
"message": "Welcome to the New UI (Beta)"
|
||||
|
@ -1,23 +1,24 @@
|
||||
[
|
||||
{ "code": "cs", "name": "Czech" },
|
||||
{ "code": "de", "name": "German" },
|
||||
{ "code": "cs", "name": "Čeština" },
|
||||
{ "code": "de", "name": "Deutsche" },
|
||||
{ "code": "en", "name": "English" },
|
||||
{ "code": "es", "name": "Spanish" },
|
||||
{ "code": "fr", "name": "French" },
|
||||
{ "code": "ht", "name": "Haitian Creole" },
|
||||
{ "code": "hn", "name": "Hindi" },
|
||||
{ "code": "it", "name": "Italian" },
|
||||
{ "code": "ja", "name": "Japanese" },
|
||||
{ "code": "ko", "name": "Korean" },
|
||||
{ "code": "nl", "name": "Dutch" },
|
||||
{ "code": "ph", "name": "Tagalog" },
|
||||
{ "code": "pt", "name": "Portuguese" },
|
||||
{ "code": "ru", "name": "Russian" },
|
||||
{ "code": "sl", "name": "Slovenian" },
|
||||
{ "code": "th", "name": "Thai" },
|
||||
{ "code": "tml", "name": "Tamil" },
|
||||
{ "code": "tr", "name": "Turkish" },
|
||||
{ "code": "vi", "name": "Vietnamese" },
|
||||
{ "code": "zh_CN", "name": "Chinese (Simplified)" },
|
||||
{ "code": "zh_TW", "name": "Chinese (Traditional)" }
|
||||
{ "code": "es", "name": "Español" },
|
||||
{ "code": "fr", "name": "Français" },
|
||||
{ "code": "ht", "name": "Kreyòl ayisyen" },
|
||||
{ "code": "hn", "name": "हिन्दी" },
|
||||
{ "code": "it", "name": "Italiano" },
|
||||
{ "code": "ja", "name": "日本語" },
|
||||
{ "code": "ko", "name": "한국어" },
|
||||
{ "code": "nl", "name": "Nederlands" },
|
||||
{ "code": "ph", "name": "Pilipino" },
|
||||
{ "code": "pl", "name": "Polskie" },
|
||||
{ "code": "pt", "name": "Português" },
|
||||
{ "code": "ru", "name": "Русский" },
|
||||
{ "code": "sl", "name": "Slovenščina" },
|
||||
{ "code": "th", "name": "ไทย" },
|
||||
{ "code": "tml", "name": "தமிழ்" },
|
||||
{ "code": "tr", "name": "Türkçe" },
|
||||
{ "code": "vi", "name": "Tiếng Việt" },
|
||||
{ "code": "zh_CN", "name": "中文(简体)" },
|
||||
{ "code": "zh_TW", "name": "中文(繁體)" }
|
||||
]
|
||||
|
1213
app/_locales/pl/messages.json
Normal file
1213
app/_locales/pl/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
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 |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "4.14.0",
|
||||
"version": "4.16.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
@ -85,4 +85,4 @@
|
||||
"*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,9 @@
|
||||
* @file The entry point for the web extension singleton process.
|
||||
*/
|
||||
|
||||
// this needs to run before anything else
|
||||
require('./lib/setupFetchDebugging')()
|
||||
|
||||
const urlUtil = require('url')
|
||||
const endOfStream = require('end-of-stream')
|
||||
const pump = require('pump')
|
||||
|
@ -38,6 +38,9 @@ class PreferencesController {
|
||||
lostIdentities: {},
|
||||
seedWords: null,
|
||||
forgottenPassword: false,
|
||||
preferences: {
|
||||
useETHAsPrimaryCurrency: true,
|
||||
},
|
||||
}, opts.initState)
|
||||
|
||||
this.diagnostics = opts.diagnostics
|
||||
@ -101,7 +104,7 @@ class PreferencesController {
|
||||
* @param {Function} - end
|
||||
*/
|
||||
async requestWatchAsset (req, res, next, end) {
|
||||
if (req.method === 'metamask_watchAsset') {
|
||||
if (req.method === 'metamask_watchAsset' || req.method === 'wallet_watchAsset') {
|
||||
const { type, options } = req.params
|
||||
switch (type) {
|
||||
case 'ERC20':
|
||||
@ -371,22 +374,6 @@ class PreferencesController {
|
||||
return Promise.resolve(label)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
|
||||
*
|
||||
* @param {string} _url The the new rpc url to add to the updated list
|
||||
* @param {bool} remove Remove selected url
|
||||
* @returns {Promise<void>} Promise resolves with undefined
|
||||
*
|
||||
*/
|
||||
updateFrequentRpcList (_url, remove = false) {
|
||||
return this.addToFrequentRpcList(_url, remove)
|
||||
.then((rpcList) => {
|
||||
this.store.updateState({ frequentRpcList: rpcList })
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `currentAccountTab` property
|
||||
*
|
||||
@ -402,24 +389,39 @@ class PreferencesController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an updated rpcList based on the passed url and the current list.
|
||||
* The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the
|
||||
* end of the list. The current list is modified and returned as a promise.
|
||||
* Adds custom RPC url to state.
|
||||
*
|
||||
* @param {string} _url The rpc url to add to the frequentRpcList.
|
||||
* @param {bool} remove Remove selected url
|
||||
* @returns {Promise<array>} The updated frequentRpcList.
|
||||
* @param {string} url The RPC url to add to frequentRpcList.
|
||||
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
addToFrequentRpcList (_url, remove = false) {
|
||||
addToFrequentRpcList (url) {
|
||||
const rpcList = this.getFrequentRpcList()
|
||||
const index = rpcList.findIndex((element) => { return element === _url })
|
||||
const index = rpcList.findIndex((element) => { return element === url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
if (!remove && _url !== 'http://localhost:8545') {
|
||||
rpcList.push(_url)
|
||||
if (url !== 'http://localhost:8545') {
|
||||
rpcList.push(url)
|
||||
}
|
||||
this.store.updateState({ frequentRpcList: rpcList })
|
||||
return Promise.resolve(rpcList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes custom RPC url from state.
|
||||
*
|
||||
* @param {string} url The RPC url to remove from frequentRpcList.
|
||||
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
removeFromFrequentRpcList (url) {
|
||||
const rpcList = this.getFrequentRpcList()
|
||||
const index = rpcList.findIndex((element) => { return element === url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
this.store.updateState({ frequentRpcList: rpcList })
|
||||
return Promise.resolve(rpcList)
|
||||
}
|
||||
|
||||
@ -463,6 +465,33 @@ class PreferencesController {
|
||||
getFeatureFlags () {
|
||||
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
|
||||
//
|
||||
|
@ -27,6 +27,8 @@ var metamaskStream = new LocalMessageDuplexStream({
|
||||
|
||||
// compose the inpage provider
|
||||
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||
// set a high max listener count to avoid unnecesary warnings
|
||||
inpageProvider.setMaxListeners(100)
|
||||
|
||||
// Augment the provider with its enable method
|
||||
inpageProvider.enable = function (options = {}) {
|
||||
|
34
app/scripts/lib/setupFetchDebugging.js
Normal file
34
app/scripts/lib/setupFetchDebugging.js
Normal file
@ -0,0 +1,34 @@
|
||||
module.exports = setupFetchDebugging
|
||||
|
||||
//
|
||||
// This is a utility to help resolve cases where `window.fetch` throws a
|
||||
// `TypeError: Failed to Fetch` without any stack or context for the request
|
||||
// https://github.com/getsentry/sentry-javascript/pull/1293
|
||||
//
|
||||
|
||||
function setupFetchDebugging() {
|
||||
if (!global.fetch) return
|
||||
const originalFetch = global.fetch
|
||||
|
||||
global.fetch = wrappedFetch
|
||||
|
||||
async function wrappedFetch(...args) {
|
||||
const initialStack = getCurrentStack()
|
||||
try {
|
||||
return await originalFetch.call(window, ...args)
|
||||
} catch (err) {
|
||||
console.warn('FetchDebugger - fetch encountered an Error', err)
|
||||
console.warn('FetchDebugger - overriding stack to point of original call')
|
||||
err.stack = initialStack
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentStack() {
|
||||
try {
|
||||
throw new Error('Fake error for generating stack trace')
|
||||
} catch (err) {
|
||||
return err.stack
|
||||
}
|
||||
}
|
@ -129,6 +129,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
})
|
||||
|
||||
// start and stop polling for balances based on activeControllerConnections
|
||||
this.on('controllerConnectionChanged', (activeControllerConnections) => {
|
||||
if (activeControllerConnections > 0) {
|
||||
@ -137,7 +138,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.accountTracker.stop()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
this.networkController.on('networkDidChange', () => {
|
||||
this.accountTracker._updateAccounts()
|
||||
})
|
||||
|
||||
// key mgmt
|
||||
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
|
||||
this.keyringController = new KeyringController({
|
||||
@ -387,6 +393,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||
setPreference: nodeify(preferencesController.setPreference, preferencesController),
|
||||
|
||||
// BlacklistController
|
||||
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
|
||||
@ -1451,7 +1458,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
async setCustomRpc (rpcTarget) {
|
||||
this.networkController.setRpcTarget(rpcTarget)
|
||||
await this.preferencesController.updateFrequentRpcList(rpcTarget)
|
||||
await this.preferencesController.addToFrequentRpcList(rpcTarget)
|
||||
return rpcTarget
|
||||
}
|
||||
|
||||
@ -1460,7 +1467,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {string} rpcTarget - A RPC URL to delete.
|
||||
*/
|
||||
async delCustomRpc (rpcTarget) {
|
||||
await this.preferencesController.updateFrequentRpcList(rpcTarget, true)
|
||||
await this.preferencesController.removeFromFrequentRpcList(rpcTarget)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,7 +107,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -150,7 +150,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -108,7 +108,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -37,7 +37,10 @@
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"tokens": [],
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -109,7 +109,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -102,7 +102,10 @@
|
||||
"shapeShiftTxList": [{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522377459106}],
|
||||
"lostAccounts": [],
|
||||
"send": {},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useETHAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -466,7 +466,9 @@ function generateBundler (opts, performBundle) {
|
||||
bundler.transform(envify({
|
||||
METAMASK_DEBUG: opts.devMode,
|
||||
NODE_ENV: opts.devMode ? 'development' : 'production',
|
||||
}))
|
||||
}), {
|
||||
global: true,
|
||||
})
|
||||
|
||||
if (opts.watch) {
|
||||
bundler = watchify(bundler)
|
||||
|
16585
package-lock.json
generated
16585
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -143,7 +143,7 @@
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"file-loader": "^1.1.11",
|
||||
"fuse.js": "^3.2.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp": "github:gulpjs/gulp#v4.0.0",
|
||||
"gulp-autoprefixer": "^5.0.0",
|
||||
"gulp-debug": "^3.2.0",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
|
@ -286,7 +286,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
await delay(regularDelayMs)
|
||||
|
||||
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 inputAmount.sendKeys('1')
|
||||
|
||||
|
@ -383,7 +383,7 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
|
||||
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 inputAmount.sendKeys('1')
|
||||
|
||||
@ -662,7 +662,7 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('clicks on the Add Token button', async () => {
|
||||
const addToken = await driver.findElement(By.css('.wallet-view__add-token-button'))
|
||||
const addToken = await driver.findElement(By.xpath(`//div[contains(text(), 'Add Token')]`))
|
||||
await addToken.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -702,7 +702,7 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
|
||||
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 inputAmount.sendKeys('50')
|
||||
|
||||
@ -834,8 +834,8 @@ describe('MetaMask', function () {
|
||||
await save.click()
|
||||
await driver.wait(until.stalenessOf(gasModal))
|
||||
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '♦ 0.0006')
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
|
||||
})
|
||||
|
||||
it('submits the transaction', async function () {
|
||||
@ -957,8 +957,8 @@ describe('MetaMask', function () {
|
||||
await save.click()
|
||||
await driver.wait(until.stalenessOf(gasModal))
|
||||
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '♦ 0.0006')
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
|
||||
})
|
||||
|
||||
it('submits the transaction', async function () {
|
||||
@ -1002,7 +1002,7 @@ describe('MetaMask', function () {
|
||||
|
||||
describe('Add existing token using search', () => {
|
||||
it('clicks on the Add Token button', async () => {
|
||||
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||
const addToken = await findElement(driver, By.xpath(`//div[contains(text(), 'Add Token')]`))
|
||||
await addToken.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
@ -1,140 +0,0 @@
|
||||
const reactTriggerChange = require('react-trigger-change')
|
||||
const {
|
||||
timeout,
|
||||
queryAsync,
|
||||
findAsync,
|
||||
} = require('../../lib/util')
|
||||
|
||||
QUnit.module('Add token flow')
|
||||
|
||||
QUnit.test('successful add token flow', (assert) => {
|
||||
const done = assert.async()
|
||||
runAddTokenFlowTest(assert)
|
||||
.then(done)
|
||||
.catch(err => {
|
||||
assert.notOk(err, `Error was thrown: ${err.stack}`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
async function runAddTokenFlowTest (assert, done) {
|
||||
const selectState = await queryAsync($, 'select')
|
||||
selectState.val('add token')
|
||||
reactTriggerChange(selectState[0])
|
||||
|
||||
// Used to set values on TextField input component
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype, 'value'
|
||||
).set
|
||||
|
||||
// Check that no tokens have been added
|
||||
assert.ok($('.token-list-item').length === 0, 'no tokens added')
|
||||
|
||||
// Go to Add Token screen
|
||||
let addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
|
||||
assert.ok(addTokenButton[0], 'add token button present')
|
||||
addTokenButton[0].click()
|
||||
|
||||
// Verify Add Token screen
|
||||
let addTokenWrapper = await queryAsync($, '.page-container')
|
||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
||||
|
||||
let addTokenTitle = await queryAsync($, '.page-container__title')
|
||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
||||
|
||||
// Cancel Add Token
|
||||
const cancelAddTokenButton = await queryAsync($, 'button.btn-default.btn--large.page-container__footer-button')
|
||||
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
|
||||
cancelAddTokenButton.click()
|
||||
|
||||
assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view')
|
||||
|
||||
// Return to Add Token Screen
|
||||
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
|
||||
assert.ok(addTokenButton[0], 'add token button present')
|
||||
addTokenButton[0].click()
|
||||
|
||||
// Verify Add Token Screen
|
||||
addTokenWrapper = await queryAsync($, '.page-container')
|
||||
addTokenTitle = await queryAsync($, '.page-container__title')
|
||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
||||
|
||||
// Search for token
|
||||
const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0]
|
||||
searchInput.focus()
|
||||
await timeout(1000)
|
||||
nativeInputValueSetter.call(searchInput, 'a')
|
||||
searchInput.dispatchEvent(new Event('input', { bubbles: true}))
|
||||
|
||||
// Click token to add
|
||||
const tokenWrapper = await queryAsync($, 'div.token-list__token')
|
||||
assert.ok(tokenWrapper[0], 'token found')
|
||||
const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image')
|
||||
const tokenImageUrl = tokenImageProp.slice(5, -2)
|
||||
tokenWrapper[0].click()
|
||||
|
||||
// Click Next button
|
||||
const nextButton = await queryAsync($, 'button.btn-primary.btn--large')
|
||||
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
|
||||
nextButton[0].click()
|
||||
|
||||
// Confirm Add token
|
||||
const confirmAddToken = await queryAsync($, '.confirm-add-token')
|
||||
assert.ok(confirmAddToken[0], 'confirm add token rendered')
|
||||
assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found')
|
||||
$('button.btn-primary.btn--large')[0].click()
|
||||
|
||||
// Verify added token image
|
||||
let heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
|
||||
assert.ok(heroBalance, 'rendered hero balance')
|
||||
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
|
||||
|
||||
// Return to Add Token Screen
|
||||
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
|
||||
assert.ok(addTokenButton[0], 'add token button present')
|
||||
addTokenButton[0].click()
|
||||
|
||||
addTokenWrapper = await queryAsync($, '.page-container')
|
||||
const addTokenTabs = await queryAsync($, '.page-container__tab')
|
||||
assert.equal(addTokenTabs.length, 2, 'expected number of tabs')
|
||||
assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present')
|
||||
assert.ok(addTokenTabs[1], 'add custom token tab present')
|
||||
addTokenTabs[1].click()
|
||||
await timeout(1000)
|
||||
|
||||
// Input token contract address
|
||||
const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0]
|
||||
customInput.focus()
|
||||
await timeout(1000)
|
||||
nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
|
||||
customInput.dispatchEvent(new Event('input', { bubbles: true}))
|
||||
|
||||
|
||||
// Click Next button
|
||||
// nextButton = await queryAsync($, 'button.btn-primary--lg')
|
||||
// assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
|
||||
// nextButton[0].click()
|
||||
|
||||
// // Verify symbol length error since contract address won't return symbol
|
||||
const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
|
||||
assert.ok(errorMessage[0], 'error rendered')
|
||||
|
||||
$('button.btn-default.btn--large')[0].click()
|
||||
|
||||
// await timeout(100000)
|
||||
|
||||
// Confirm Add token
|
||||
// assert.equal(
|
||||
// $('.page-container__subtitle')[0].textContent,
|
||||
// 'Would you like to add these tokens?',
|
||||
// 'confirm add token rendered'
|
||||
// )
|
||||
// assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
||||
// $('button.btn-primary--lg')[0].click()
|
||||
|
||||
// Verify added token image
|
||||
heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
|
||||
assert.ok(heroBalance, 'rendered hero balance')
|
||||
assert.ok(heroBalance.find('.identicon')[0], 'token added')
|
||||
}
|
@ -40,7 +40,7 @@ async function customizeGas (assert, price, limit, ethFee, usdFee) {
|
||||
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
||||
|
||||
assert.equal(
|
||||
(await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(),
|
||||
(await findAsync(sendGasField, '.currency-display-component'))[0].textContent,
|
||||
ethFee,
|
||||
'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')
|
||||
|
||||
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')
|
||||
reactTriggerChange(sendAmountField.find('input')[0])
|
||||
|
||||
@ -112,9 +112,9 @@ async function runSendFlowTest (assert, done) {
|
||||
errorMessage = $('.send-v2__error')
|
||||
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, 1, 21000, '0.000021', '$0.03 USD')
|
||||
await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD')
|
||||
await customizeGas(assert, 0, 21000, '0 ETH', '$0.00 USD')
|
||||
await customizeGas(assert, 1, 21000, '0.000021 ETH', '$0.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')
|
||||
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()
|
||||
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]
|
||||
assert.equal(confirmScreenGas.textContent, '$3.60', 'confirm screen should show correct gas')
|
||||
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')
|
||||
confirmScreenBackButton[0].click()
|
||||
@ -150,9 +150,9 @@ async function runSendFlowTest (assert, done) {
|
||||
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
|
||||
|
||||
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')
|
||||
reactTriggerChange(sendAmountFieldInputInEdit[0])
|
||||
|
||||
|
@ -375,6 +375,11 @@ describe('preferences controller', function () {
|
||||
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
|
||||
sandbox.assert.called(stubEnd)
|
||||
sandbox.assert.notCalled(stubNext)
|
||||
req.method = 'wallet_watchAsset'
|
||||
req.params.type = 'someasset'
|
||||
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
|
||||
sandbox.assert.calledTwice(stubEnd)
|
||||
sandbox.assert.notCalled(stubNext)
|
||||
})
|
||||
it('should through error if method is supported but asset type is not', async function () {
|
||||
req.method = 'metamask_watchAsset'
|
||||
@ -479,5 +484,24 @@ describe('preferences controller', function () {
|
||||
assert.equal(preferencesController.store.getState().seedWords, 'foo bar baz')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on updateFrequentRpcList', function () {
|
||||
it('should add custom RPC url to state', function () {
|
||||
preferencesController.addToFrequentRpcList('rpc_url')
|
||||
preferencesController.addToFrequentRpcList('http://localhost:8545')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, ['rpc_url'])
|
||||
preferencesController.addToFrequentRpcList('rpc_url')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, ['rpc_url'])
|
||||
})
|
||||
|
||||
it('should remove custom RPC url from state', function () {
|
||||
preferencesController.addToFrequentRpcList('rpc_url')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, ['rpc_url'])
|
||||
preferencesController.removeFromFrequentRpcList('other_rpc_url')
|
||||
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
|
||||
preferencesController.removeFromFrequentRpcList('rpc_url')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcList, [])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -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,
|
||||
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
|
||||
|
||||
// Preferences
|
||||
setPreference,
|
||||
updatePreferences,
|
||||
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
|
||||
setUseETHAsPrimaryCurrencyPreference,
|
||||
|
||||
setMouseUserState,
|
||||
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) {
|
||||
return {
|
||||
type: actions.SET_NETWORK_NONCE,
|
||||
|
@ -8,11 +8,11 @@ const h = require('react-hyperscript')
|
||||
const actions = require('../../actions')
|
||||
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
|
||||
const Identicon = require('../identicon')
|
||||
const { formatBalance } = require('../../util')
|
||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
|
||||
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
|
||||
const Tooltip = require('../tooltip')
|
||||
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import { PRIMARY } from '../../constants/common'
|
||||
|
||||
const {
|
||||
SETTINGS_ROUTE,
|
||||
@ -163,7 +163,6 @@ AccountMenu.prototype.renderAccounts = function () {
|
||||
const isSelected = identity.address === selectedAddress
|
||||
|
||||
const balanceValue = accounts[address] ? accounts[address].balance : ''
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
@ -189,7 +188,11 @@ AccountMenu.prototype.renderAccounts = function () {
|
||||
|
||||
h('div.account-menu__account-info', [
|
||||
h('div.account-menu__name', identity.name || ''),
|
||||
h('div.account-menu__balance', formattedBalance),
|
||||
h(UserPreferencedCurrencyDisplay, {
|
||||
className: 'account-menu__balance',
|
||||
value: balanceValue,
|
||||
type: PRIMARY,
|
||||
}),
|
||||
]),
|
||||
|
||||
this.renderKeyringType(keyring),
|
||||
|
@ -0,0 +1,34 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent} from 'react'
|
||||
|
||||
export default class AddTokenButton extends PureComponent {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onClick: () => {},
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { t } = this.context
|
||||
const { onClick } = this.props
|
||||
|
||||
return (
|
||||
<div className="add-token-button">
|
||||
<h1 className="add-token-button__help-header">{t('missingYourTokens')}</h1>
|
||||
<p className="add-token-button__help-desc">{t('clickToAdd', [t('addToken')])}</p>
|
||||
<div
|
||||
className="add-token-button__button"
|
||||
onClick={onClick}
|
||||
>
|
||||
{t('addToken')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
1
ui/app/components/add-token-button/index.js
Normal file
1
ui/app/components/add-token-button/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './add-token-button.component'
|
26
ui/app/components/add-token-button/index.scss
Normal file
26
ui/app/components/add-token-button/index.scss
Normal file
@ -0,0 +1,26 @@
|
||||
.add-token-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: lighten($scorpion, 25%);
|
||||
width: 185px;
|
||||
margin: 36px auto;
|
||||
text-align: center;
|
||||
|
||||
&__help-header {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&__help-desc {
|
||||
font-size: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
&__button {
|
||||
font-size: 0.75rem;
|
||||
margin: 1rem;
|
||||
text-transform: uppercase;
|
||||
color: $curious-blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
@ -4,10 +4,11 @@ const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const TokenBalance = require('./token-balance')
|
||||
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 { formatBalance, generateBalanceObject } = require('../util')
|
||||
const { formatBalance } = require('../util')
|
||||
|
||||
module.exports = connect(mapStateToProps)(BalanceComponent)
|
||||
|
||||
@ -65,7 +66,7 @@ BalanceComponent.prototype.renderTokenBalance = function () {
|
||||
|
||||
BalanceComponent.prototype.renderBalance = function () {
|
||||
const props = this.props
|
||||
const { shorten, account } = props
|
||||
const { account } = props
|
||||
const balanceValue = account && account.balance
|
||||
const needsParse = 'needsParse' in props ? props.needsParse : true
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
||||
@ -80,25 +81,20 @@ BalanceComponent.prototype.renderBalance = function () {
|
||||
}
|
||||
|
||||
return h('div.flex-column.balance-display', {}, [
|
||||
h('div.token-amount', {
|
||||
style: {},
|
||||
}, this.getTokenBalance(formattedBalance, shorten)),
|
||||
|
||||
showFiat && h(CurrencyDisplay, {
|
||||
h('div.token-amount', {}, h(UserPreferencedCurrencyDisplay, {
|
||||
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) {
|
||||
if (formattedBalance === 'None') return formattedBalance
|
||||
if (conversionRate === 0) return 'N/A'
|
||||
|
@ -1,16 +1,19 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
|
||||
const ConfirmDetailRow = props => {
|
||||
const {
|
||||
label,
|
||||
fiatText,
|
||||
ethText,
|
||||
primaryText,
|
||||
secondaryText,
|
||||
onHeaderClick,
|
||||
fiatTextColor,
|
||||
primaryValueTextColor,
|
||||
headerText,
|
||||
headerTextClassName,
|
||||
value,
|
||||
} = props
|
||||
|
||||
return (
|
||||
@ -25,28 +28,57 @@ const ConfirmDetailRow = props => {
|
||||
>
|
||||
{ headerText }
|
||||
</div>
|
||||
<div
|
||||
className="confirm-detail-row__fiat"
|
||||
style={{ color: fiatTextColor }}
|
||||
>
|
||||
{ fiatText }
|
||||
</div>
|
||||
<div className="confirm-detail-row__eth">
|
||||
{ ethText }
|
||||
</div>
|
||||
{
|
||||
primaryText
|
||||
? (
|
||||
<div
|
||||
className="confirm-detail-row__primary"
|
||||
style={{ color: primaryValueTextColor }}
|
||||
>
|
||||
{ primaryText }
|
||||
</div>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="confirm-detail-row__primary"
|
||||
type={PRIMARY}
|
||||
value={value}
|
||||
showEthLogo
|
||||
ethLogoHeight="18"
|
||||
style={{ color: primaryValueTextColor }}
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
secondaryText
|
||||
? (
|
||||
<div className="confirm-detail-row__secondary">
|
||||
{ secondaryText }
|
||||
</div>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="confirm-detail-row__secondary"
|
||||
type={SECONDARY}
|
||||
value={value}
|
||||
showEthLogo
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ConfirmDetailRow.propTypes = {
|
||||
label: PropTypes.string,
|
||||
fiatText: PropTypes.string,
|
||||
ethText: PropTypes.string,
|
||||
fiatTextColor: PropTypes.string,
|
||||
onHeaderClick: PropTypes.func,
|
||||
headerText: PropTypes.string,
|
||||
headerTextClassName: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onHeaderClick: PropTypes.func,
|
||||
primaryValueTextColor: PropTypes.string,
|
||||
primaryText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
secondaryText: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ConfirmDetailRow
|
||||
|
@ -18,18 +18,14 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__fiat {
|
||||
&__primary {
|
||||
font-size: 1.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__eth {
|
||||
&__secondary {
|
||||
color: $oslo-gray;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__header-text {
|
||||
|
@ -12,17 +12,19 @@ describe('Confirm Detail Row Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ConfirmDetailRow
|
||||
errorType={'mockErrorType'}
|
||||
label={'mockLabel'}
|
||||
showError={false}
|
||||
fiatText = {'mockFiatText'}
|
||||
ethText = {'mockEthText'}
|
||||
fiatTextColor= {'mockColor'}
|
||||
onHeaderClick= {propsMethodSpies.onHeaderClick}
|
||||
headerText = {'mockHeaderText'}
|
||||
headerTextClassName = {'mockHeaderClass'}
|
||||
/>)
|
||||
wrapper = shallow(
|
||||
<ConfirmDetailRow
|
||||
errorType={'mockErrorType'}
|
||||
label={'mockLabel'}
|
||||
showError={false}
|
||||
primaryText = {'mockFiatText'}
|
||||
secondaryText = {'mockEthText'}
|
||||
primaryValueTextColor= {'mockColor'}
|
||||
onHeaderClick= {propsMethodSpies.onHeaderClick}
|
||||
headerText = {'mockHeaderText'}
|
||||
headerTextClassName = {'mockHeaderClass'}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
@ -38,16 +40,16 @@ describe('Confirm Detail Row Component', function () {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText')
|
||||
})
|
||||
|
||||
it('should render the fiatText as a child of the confirm-detail-row__fiat', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatText')
|
||||
it('should render the primaryText as a child of the confirm-detail-row__primary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText')
|
||||
})
|
||||
|
||||
it('should render the ethText as a child of the confirm-detail-row__eth', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), 'mockEthText')
|
||||
it('should render the ethText as a child of the confirm-detail-row__secondary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText')
|
||||
})
|
||||
|
||||
it('should set the fiatTextColor on confirm-detail-row__fiat', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__fiat').props().style.color, 'mockColor')
|
||||
it('should set the fiatTextColor on confirm-detail-row__primary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
|
||||
})
|
||||
|
||||
it('should assure the confirm-detail-row__header-text classname is correct', () => {
|
||||
@ -58,7 +60,5 @@ describe('Confirm Detail Row Component', function () {
|
||||
wrapper.find('.confirm-detail-row__header-text').props().onClick()
|
||||
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
})
|
||||
|
@ -17,9 +17,10 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
nonce: PropTypes.string,
|
||||
assetImage: PropTypes.string,
|
||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
subtitleComponent: PropTypes.node,
|
||||
summaryComponent: PropTypes.node,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
titleComponent: PropTypes.func,
|
||||
titleComponent: PropTypes.node,
|
||||
warning: PropTypes.string,
|
||||
}
|
||||
|
||||
@ -54,7 +55,9 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
errorKey,
|
||||
errorMessage,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
identiconAddress,
|
||||
nonce,
|
||||
@ -80,7 +83,9 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
})}
|
||||
action={action}
|
||||
title={title}
|
||||
titleComponent={titleComponent}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={subtitleComponent}
|
||||
hideSubtitle={hideSubtitle}
|
||||
identiconAddress={identiconAddress}
|
||||
nonce={nonce}
|
||||
|
@ -4,7 +4,18 @@ import classnames from 'classnames'
|
||||
import Identicon from '../../../identicon'
|
||||
|
||||
const ConfirmPageContainerSummary = props => {
|
||||
const { action, title, subtitle, hideSubtitle, className, identiconAddress, nonce, assetImage } = props
|
||||
const {
|
||||
action,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
className,
|
||||
identiconAddress,
|
||||
nonce,
|
||||
assetImage,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className={classnames('confirm-page-container-summary', className)}>
|
||||
@ -32,12 +43,12 @@ const ConfirmPageContainerSummary = props => {
|
||||
)
|
||||
}
|
||||
<div className="confirm-page-container-summary__title-text">
|
||||
{ title }
|
||||
{ titleComponent || title }
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
|
||||
{ subtitle }
|
||||
{ subtitleComponent || subtitle }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@ -47,7 +58,9 @@ const ConfirmPageContainerSummary = props => {
|
||||
ConfirmPageContainerSummary.propTypes = {
|
||||
action: PropTypes.string,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
titleComponent: PropTypes.node,
|
||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
subtitleComponent: PropTypes.node,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
identiconAddress: PropTypes.string,
|
||||
|
@ -16,8 +16,9 @@ export default class ConfirmPageContainer extends Component {
|
||||
onEdit: PropTypes.func,
|
||||
showEdit: PropTypes.bool,
|
||||
subtitle: PropTypes.string,
|
||||
subtitleComponent: PropTypes.node,
|
||||
title: PropTypes.string,
|
||||
titleComponent: PropTypes.func,
|
||||
titleComponent: PropTypes.node,
|
||||
// Sender to Recipient
|
||||
fromAddress: PropTypes.string,
|
||||
fromName: PropTypes.string,
|
||||
@ -65,6 +66,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
summaryComponent,
|
||||
detailsComponent,
|
||||
@ -101,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
title={title}
|
||||
titleComponent={titleComponent}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={subtitleComponent}
|
||||
hideSubtitle={hideSubtitle}
|
||||
summaryComponent={summaryComponent}
|
||||
detailsComponent={detailsComponent}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { ETH, GWEI } from '../../constants/common'
|
||||
|
||||
export default class CurrencyDisplay extends PureComponent {
|
||||
@ -7,6 +8,8 @@ export default class CurrencyDisplay extends PureComponent {
|
||||
className: PropTypes.string,
|
||||
displayValue: PropTypes.string,
|
||||
prefix: PropTypes.string,
|
||||
prefixComponent: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
// Used in container
|
||||
currency: PropTypes.oneOf([ETH]),
|
||||
denomination: PropTypes.oneOf([GWEI]),
|
||||
@ -16,15 +19,17 @@ export default class CurrencyDisplay extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, displayValue, prefix } = this.props
|
||||
const { className, displayValue, prefix, prefixComponent, style } = this.props
|
||||
const text = `${prefix || ''}${displayValue}`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
className={classnames('currency-display-component', className)}
|
||||
style={style}
|
||||
title={text}
|
||||
>
|
||||
{ text }
|
||||
{ prefixComponent}
|
||||
<span className="currency-display-component__text">{ text }</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -2,10 +2,26 @@ import { connect } from 'react-redux'
|
||||
import CurrencyDisplay from './currency-display.component'
|
||||
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { value, numberOfDecimals = 2, currency, denomination, hideLabel } = ownProps
|
||||
const mapStateToProps = 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 convertedValue = getValueFromWeiHex({
|
||||
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
||||
@ -14,8 +30,11 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
|
||||
|
||||
return {
|
||||
...restStateProps,
|
||||
...dispatchProps,
|
||||
...restOwnProps,
|
||||
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 proxyquire from 'proxyquire'
|
||||
|
||||
let mapStateToProps
|
||||
let mapStateToProps, mergeProps
|
||||
|
||||
proxyquire('../currency-display.container.js', {
|
||||
'react-redux': {
|
||||
connect: ms => {
|
||||
connect: (ms, md, mp) => {
|
||||
mapStateToProps = ms
|
||||
mergeProps = mp
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
@ -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 = [
|
||||
{
|
||||
props: {
|
||||
@ -98,7 +113,7 @@ describe('CurrencyDisplay container', () => {
|
||||
]
|
||||
|
||||
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',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,11 +1,17 @@
|
||||
@import './app-header/index';
|
||||
|
||||
@import './add-token-button/index';
|
||||
|
||||
@import './button-group/index';
|
||||
|
||||
@import './card/index';
|
||||
|
||||
@import './confirm-page-container/index';
|
||||
|
||||
@import './currency-input/index';
|
||||
|
||||
@import './currency-display/index';
|
||||
|
||||
@import './error-message/index';
|
||||
|
||||
@import './export-text-container/index';
|
||||
@ -49,3 +55,5 @@
|
||||
@import './app-header/index';
|
||||
|
||||
@import './sidebars/index';
|
||||
|
||||
@import './unit-input/index';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import CurrencyDisplay from '../../../currency-display'
|
||||
import { ETH } from '../../../../constants/common'
|
||||
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../../constants/common'
|
||||
|
||||
export default class CancelTransaction extends PureComponent {
|
||||
static propTypes = {
|
||||
@ -13,15 +13,15 @@ export default class CancelTransaction extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className="cancel-transaction-gas-fee">
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="cancel-transaction-gas-fee__eth"
|
||||
currency={ETH}
|
||||
value={value}
|
||||
numberOfDecimals={6}
|
||||
type={PRIMARY}
|
||||
/>
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="cancel-transaction-gas-fee__fiat"
|
||||
value={value}
|
||||
type={SECONDARY}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component'
|
||||
import CurrencyDisplay from '../../../../currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
|
||||
|
||||
describe('CancelTransactionGasFee Component', () => {
|
||||
it('should render', () => {
|
||||
@ -13,12 +13,11 @@ describe('CancelTransactionGasFee Component', () => {
|
||||
)
|
||||
|
||||
assert.ok(wrapper)
|
||||
assert.equal(wrapper.find(CurrencyDisplay).length, 2)
|
||||
const ethDisplay = wrapper.find(CurrencyDisplay).at(0)
|
||||
const fiatDisplay = wrapper.find(CurrencyDisplay).at(1)
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||
const ethDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(0)
|
||||
const fiatDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(1)
|
||||
|
||||
assert.equal(ethDisplay.props().value, '0x3b9aca00')
|
||||
assert.equal(ethDisplay.props().currency, 'ETH')
|
||||
assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth')
|
||||
|
||||
assert.equal(fiatDisplay.props().value, '0x3b9aca00')
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import {
|
||||
formatCurrency,
|
||||
convertTokenToFiat,
|
||||
addFiat,
|
||||
roundExponential,
|
||||
} from '../../../helpers/confirm-transaction/util'
|
||||
import { getWeiHexFromDecimalValue } from '../../../helpers/conversions.util'
|
||||
import { ETH, PRIMARY } from '../../../constants/common'
|
||||
|
||||
export default class ConfirmTokenTransactionBase extends Component {
|
||||
static contextTypes = {
|
||||
@ -36,19 +39,48 @@ export default class ConfirmTokenTransactionBase extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
getSubtitle () {
|
||||
const { currentCurrency, contractExchangeRate } = this.props
|
||||
renderSubtitleComponent () {
|
||||
const { contractExchangeRate, tokenAmount } = this.props
|
||||
|
||||
if (typeof contractExchangeRate === 'undefined') {
|
||||
return this.context.t('noConversionRateAvailable')
|
||||
} else {
|
||||
const fiatTransactionAmount = this.getFiatTransactionAmount()
|
||||
const roundedFiatTransactionAmount = roundExponential(fiatTransactionAmount)
|
||||
return formatCurrency(roundedFiatTransactionAmount, currentCurrency)
|
||||
}
|
||||
const decimalEthValue = (tokenAmount * contractExchangeRate) || 0
|
||||
const hexWeiValue = getWeiHexFromDecimalValue({
|
||||
value: decimalEthValue,
|
||||
fromCurrency: ETH,
|
||||
fromDenomination: ETH,
|
||||
})
|
||||
|
||||
return typeof contractExchangeRate === 'undefined'
|
||||
? (
|
||||
<span>
|
||||
{ this.context.t('noConversionRateAvailable') }
|
||||
</span>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={hexWeiValue}
|
||||
type={PRIMARY}
|
||||
showEthLogo
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if (typeof contractExchangeRate === 'undefined') {
|
||||
@ -67,7 +99,6 @@ export default class ConfirmTokenTransactionBase extends Component {
|
||||
tokenAddress,
|
||||
tokenSymbol,
|
||||
tokenAmount,
|
||||
ethTransactionTotal,
|
||||
...restProps
|
||||
} = this.props
|
||||
|
||||
@ -78,9 +109,9 @@ export default class ConfirmTokenTransactionBase extends Component {
|
||||
toAddress={toAddress}
|
||||
identiconAddress={tokenAddress}
|
||||
title={tokensText}
|
||||
subtitle={this.getSubtitle()}
|
||||
ethTotalTextOverride={`${tokensText} + \u2666 ${ethTransactionTotal}`}
|
||||
fiatTotalTextOverride={this.getFiatTotalTextOverride()}
|
||||
subtitleComponent={this.renderSubtitleComponent()}
|
||||
primaryTotalTextOverride={this.renderPrimaryTotalTextOverride()}
|
||||
secondaryTotalTextOverride={this.getSecondaryTotalTextOverride()}
|
||||
{...restProps}
|
||||
/>
|
||||
)
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
|
||||
import { formatCurrency } from '../../../helpers/confirm-transaction/util'
|
||||
import { isBalanceSufficient } from '../../send/send.utils'
|
||||
import { DEFAULT_ROUTE } from '../../../routes'
|
||||
import {
|
||||
@ -9,6 +8,8 @@ import {
|
||||
TRANSACTION_ERROR_KEY,
|
||||
} from '../../../constants/error-keys'
|
||||
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
|
||||
export default class ConfirmTransactionBase extends Component {
|
||||
static contextTypes = {
|
||||
@ -36,7 +37,9 @@ export default class ConfirmTransactionBase extends Component {
|
||||
fiatTransactionTotal: PropTypes.string,
|
||||
fromAddress: PropTypes.string,
|
||||
fromName: PropTypes.string,
|
||||
hexGasTotal: PropTypes.string,
|
||||
hexTransactionAmount: PropTypes.string,
|
||||
hexTransactionFee: PropTypes.string,
|
||||
hexTransactionTotal: PropTypes.string,
|
||||
isTxReprice: PropTypes.bool,
|
||||
methodData: PropTypes.object,
|
||||
nonce: PropTypes.string,
|
||||
@ -59,8 +62,8 @@ export default class ConfirmTransactionBase extends Component {
|
||||
detailsComponent: PropTypes.node,
|
||||
errorKey: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
ethTotalTextOverride: PropTypes.string,
|
||||
fiatTotalTextOverride: PropTypes.string,
|
||||
primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
secondaryTotalTextOverride: PropTypes.string,
|
||||
hideData: PropTypes.bool,
|
||||
hideDetails: PropTypes.bool,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
@ -70,8 +73,10 @@ export default class ConfirmTransactionBase extends Component {
|
||||
onEditGas: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
subtitle: PropTypes.string,
|
||||
subtitleComponent: PropTypes.node,
|
||||
summaryComponent: PropTypes.node,
|
||||
title: PropTypes.string,
|
||||
titleComponent: PropTypes.node,
|
||||
valid: PropTypes.bool,
|
||||
warning: PropTypes.string,
|
||||
}
|
||||
@ -105,7 +110,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
const {
|
||||
balance,
|
||||
conversionRate,
|
||||
hexGasTotal,
|
||||
hexTransactionFee,
|
||||
txData: {
|
||||
simulationFails,
|
||||
txParams: {
|
||||
@ -116,7 +121,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
|
||||
const insufficientBalance = balance && !isBalanceSufficient({
|
||||
amount,
|
||||
gasTotal: hexGasTotal || '0x0',
|
||||
gasTotal: hexTransactionFee || '0x0',
|
||||
balance,
|
||||
conversionRate,
|
||||
})
|
||||
@ -153,13 +158,10 @@ export default class ConfirmTransactionBase extends Component {
|
||||
renderDetails () {
|
||||
const {
|
||||
detailsComponent,
|
||||
fiatTransactionFee,
|
||||
ethTransactionFee,
|
||||
currentCurrency,
|
||||
fiatTransactionTotal,
|
||||
ethTransactionTotal,
|
||||
fiatTotalTextOverride,
|
||||
ethTotalTextOverride,
|
||||
primaryTotalTextOverride,
|
||||
secondaryTotalTextOverride,
|
||||
hexTransactionFee,
|
||||
hexTransactionTotal,
|
||||
hideDetails,
|
||||
} = this.props
|
||||
|
||||
@ -167,16 +169,13 @@ export default class ConfirmTransactionBase extends Component {
|
||||
return null
|
||||
}
|
||||
|
||||
const formattedCurrency = formatCurrency(fiatTransactionTotal, currentCurrency)
|
||||
|
||||
return (
|
||||
detailsComponent || (
|
||||
<div className="confirm-page-container-content__details">
|
||||
<div className="confirm-page-container-content__gas-fee">
|
||||
<ConfirmDetailRow
|
||||
label="Gas Fee"
|
||||
fiatText={formatCurrency(fiatTransactionFee, currentCurrency)}
|
||||
ethText={`\u2666 ${ethTransactionFee}`}
|
||||
value={hexTransactionFee}
|
||||
headerText="Edit"
|
||||
headerTextClassName="confirm-detail-row__header-text--edit"
|
||||
onHeaderClick={() => this.handleEditGas()}
|
||||
@ -185,11 +184,12 @@ export default class ConfirmTransactionBase extends Component {
|
||||
<div>
|
||||
<ConfirmDetailRow
|
||||
label="Total"
|
||||
fiatText={fiatTotalTextOverride || formattedCurrency}
|
||||
ethText={ethTotalTextOverride || `\u2666 ${ethTransactionTotal}`}
|
||||
value={hexTransactionTotal}
|
||||
primaryText={primaryTotalTextOverride}
|
||||
secondaryText={secondaryTotalTextOverride}
|
||||
headerText="Amount + Gas Fee"
|
||||
headerTextClassName="confirm-detail-row__header-text--total"
|
||||
fiatTextColor="#2f9ae0"
|
||||
primaryValueTextColor="#2f9ae0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -311,6 +311,43 @@ export default class ConfirmTransactionBase extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderTitleComponent () {
|
||||
const { title, titleComponent, hexTransactionAmount } = this.props
|
||||
|
||||
// Title string passed in by props takes priority
|
||||
if (title) {
|
||||
return null
|
||||
}
|
||||
|
||||
return titleComponent || (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={hexTransactionAmount}
|
||||
type={PRIMARY}
|
||||
showEthLogo
|
||||
ethLogoHeight="26"
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderSubtitleComponent () {
|
||||
const { subtitle, subtitleComponent, hexTransactionAmount } = this.props
|
||||
|
||||
// Subtitle string passed in by props takes priority
|
||||
if (subtitle) {
|
||||
return null
|
||||
}
|
||||
|
||||
return subtitleComponent || (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={hexTransactionAmount}
|
||||
type={SECONDARY}
|
||||
showEthLogo
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
isTxReprice,
|
||||
@ -319,12 +356,9 @@ export default class ConfirmTransactionBase extends Component {
|
||||
toName,
|
||||
toAddress,
|
||||
methodData,
|
||||
ethTransactionAmount,
|
||||
fiatTransactionAmount,
|
||||
valid: propsValid = true,
|
||||
errorMessage,
|
||||
errorKey: propsErrorKey,
|
||||
currentCurrency,
|
||||
action,
|
||||
title,
|
||||
subtitle,
|
||||
@ -341,7 +375,6 @@ export default class ConfirmTransactionBase extends Component {
|
||||
const { submitting, submitError } = this.state
|
||||
|
||||
const { name } = methodData
|
||||
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
|
||||
const { valid, errorKey } = this.getErrorKey()
|
||||
|
||||
return (
|
||||
@ -352,8 +385,10 @@ export default class ConfirmTransactionBase extends Component {
|
||||
toAddress={toAddress}
|
||||
showEdit={onEdit && !isTxReprice}
|
||||
action={action || name || this.context.t('unknownFunction')}
|
||||
title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`}
|
||||
subtitle={subtitle || `\u2666 ${ethTransactionAmount}`}
|
||||
title={title}
|
||||
titleComponent={this.renderTitleComponent()}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={this.renderSubtitleComponent()}
|
||||
hideSubtitle={hideSubtitle}
|
||||
summaryComponent={summaryComponent}
|
||||
detailsComponent={this.renderDetails()}
|
||||
|
@ -36,7 +36,9 @@ const mapStateToProps = (state, props) => {
|
||||
fiatTransactionAmount,
|
||||
fiatTransactionFee,
|
||||
fiatTransactionTotal,
|
||||
hexGasTotal,
|
||||
hexTransactionAmount,
|
||||
hexTransactionFee,
|
||||
hexTransactionTotal,
|
||||
tokenData,
|
||||
methodData,
|
||||
txData,
|
||||
@ -87,7 +89,9 @@ const mapStateToProps = (state, props) => {
|
||||
fiatTransactionAmount,
|
||||
fiatTransactionFee,
|
||||
fiatTransactionTotal,
|
||||
hexGasTotal,
|
||||
hexTransactionAmount,
|
||||
hexTransactionFee,
|
||||
hexTransactionTotal,
|
||||
txData,
|
||||
tokenData,
|
||||
methodData,
|
||||
|
@ -48,4 +48,22 @@
|
||||
border-color: $ecstasy;
|
||||
}
|
||||
}
|
||||
|
||||
&__radio-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__radio-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__radio-label {
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ export default class SettingsTab extends PureComponent {
|
||||
sendHexData: PropTypes.bool,
|
||||
currentCurrency: PropTypes.string,
|
||||
conversionDate: PropTypes.number,
|
||||
useETHAsPrimaryCurrency: PropTypes.bool,
|
||||
setUseETHAsPrimaryCurrencyPreference: PropTypes.func,
|
||||
}
|
||||
|
||||
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 () {
|
||||
const { warning, isMascara } = this.props
|
||||
|
||||
@ -346,6 +398,7 @@ export default class SettingsTab extends PureComponent {
|
||||
<div className="settings-page__content">
|
||||
{ warning && <div className="settings-tab__error">{ warning }</div> }
|
||||
{ this.renderCurrentConversion() }
|
||||
{ this.renderUseEthAsPrimaryCurrency() }
|
||||
{ this.renderCurrentLocale() }
|
||||
{ this.renderNewRpcUrl() }
|
||||
{ this.renderStateLogs() }
|
||||
|
@ -11,7 +11,9 @@ import {
|
||||
updateCurrentLocale,
|
||||
setFeatureFlag,
|
||||
showModal,
|
||||
setUseETHAsPrimaryCurrencyPreference,
|
||||
} from '../../../../actions'
|
||||
import { preferencesSelector } from '../../../../selectors'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { appState: { warning }, metamask } = state
|
||||
@ -24,6 +26,7 @@ const mapStateToProps = state => {
|
||||
isMascara,
|
||||
currentLocale,
|
||||
} = metamask
|
||||
const { useETHAsPrimaryCurrency } = preferencesSelector(state)
|
||||
|
||||
return {
|
||||
warning,
|
||||
@ -34,6 +37,7 @@ const mapStateToProps = state => {
|
||||
useBlockie,
|
||||
sendHexData,
|
||||
provider,
|
||||
useETHAsPrimaryCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +54,9 @@ const mapDispatchToProps = dispatch => {
|
||||
},
|
||||
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
|
||||
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 { checksumAddress } from '../../../util'
|
||||
import Identicon from '../../identicon'
|
||||
import CurrencyDisplay from '../currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
|
||||
export default class AccountListItem extends Component {
|
||||
|
||||
@ -25,8 +26,6 @@ export default class AccountListItem extends Component {
|
||||
const {
|
||||
account,
|
||||
className,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
displayAddress = false,
|
||||
displayBalance = true,
|
||||
handleClick,
|
||||
@ -57,16 +56,20 @@ export default class AccountListItem extends Component {
|
||||
{ checksumAddress(address) }
|
||||
</div>}
|
||||
|
||||
{displayBalance && <CurrencyDisplay
|
||||
className="account-list-item__account-balances"
|
||||
conversionRate={conversionRate}
|
||||
convertedBalanceClassName="account-list-item__account-secondary-balance"
|
||||
convertedCurrency={currentCurrency}
|
||||
primaryBalanceClassName="account-list-item__account-primary-balance"
|
||||
primaryCurrency="ETH"
|
||||
readOnly={true}
|
||||
value={balance}
|
||||
/>}
|
||||
{
|
||||
displayBalance && (
|
||||
<div className="account-list-item__account-balances">
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={balance}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
value={balance}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</div>)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import proxyquire from 'proxyquire'
|
||||
import Identicon from '../../../identicon'
|
||||
import CurrencyDisplay from '../../currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
|
||||
|
||||
const utilsMethodStubs = {
|
||||
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
|
||||
@ -114,17 +114,11 @@ describe('AccountListItem Component', function () {
|
||||
|
||||
it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => {
|
||||
wrapper.setProps({ displayBalance: true })
|
||||
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||
assert.deepEqual(
|
||||
wrapper.find(CurrencyDisplay).props(),
|
||||
wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(),
|
||||
{
|
||||
className: 'account-list-item__account-balances',
|
||||
conversionRate: 4,
|
||||
convertedBalanceClassName: 'account-list-item__account-secondary-balance',
|
||||
convertedCurrency: 'mockCurrentyCurrency',
|
||||
primaryBalanceClassName: 'account-list-item__account-primary-balance',
|
||||
primaryCurrency: 'ETH',
|
||||
readOnly: true,
|
||||
type: 'PRIMARY',
|
||||
value: 'mockBalance',
|
||||
}
|
||||
)
|
||||
@ -132,7 +126,7 @@ describe('AccountListItem Component', function () {
|
||||
|
||||
it('should not render a CurrencyDisplay if displayBalance is false', () => {
|
||||
wrapper.setProps({ displayBalance: false })
|
||||
assert.equal(wrapper.find(CurrencyDisplay).length, 0)
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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 SendRowWrapper from '../send-row-wrapper/'
|
||||
import AmountMaxButton from './amount-max-button/'
|
||||
import CurrencyDisplay from '../../currency-display'
|
||||
import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input'
|
||||
import UserPreferencedTokenInput from '../../../user-preferenced-token-input'
|
||||
|
||||
export default class SendAmountRow extends Component {
|
||||
|
||||
@ -84,16 +85,25 @@ export default class SendAmountRow extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderInput () {
|
||||
const { amount, inError, selectedToken } = this.props
|
||||
const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput
|
||||
|
||||
return (
|
||||
<Component
|
||||
onChange={newAmount => this.validateAmount(newAmount)}
|
||||
onBlur={newAmount => {
|
||||
this.updateGas(newAmount)
|
||||
this.updateAmount(newAmount)
|
||||
}}
|
||||
error={inError}
|
||||
value={amount}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
amount,
|
||||
amountConversionRate,
|
||||
convertedCurrency,
|
||||
gasTotal,
|
||||
inError,
|
||||
primaryCurrency,
|
||||
selectedToken,
|
||||
} = this.props
|
||||
const { gasTotal, inError } = this.props
|
||||
|
||||
return (
|
||||
<SendRowWrapper
|
||||
@ -102,20 +112,7 @@ export default class SendAmountRow extends Component {
|
||||
errorType={'amount'}
|
||||
>
|
||||
{!inError && gasTotal && <AmountMaxButton />}
|
||||
<CurrencyDisplay
|
||||
conversionRate={amountConversionRate}
|
||||
convertedCurrency={convertedCurrency}
|
||||
onBlur={newAmount => {
|
||||
this.updateGas(newAmount)
|
||||
this.updateAmount(newAmount)
|
||||
}}
|
||||
onChange={newAmount => this.validateAmount(newAmount)}
|
||||
inError={inError}
|
||||
primaryCurrency={primaryCurrency || 'ETH'}
|
||||
selectedToken={selectedToken}
|
||||
value={amount}
|
||||
step="any"
|
||||
/>
|
||||
{ this.renderInput() }
|
||||
</SendRowWrapper>
|
||||
)
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
|
||||
|
||||
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
|
||||
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
|
||||
import CurrencyDisplay from '../../../currency-display'
|
||||
import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
|
||||
|
||||
const propsMethodSpies = {
|
||||
setMaxModeTo: sinon.spy(),
|
||||
@ -150,26 +150,19 @@ describe('SendAmountRow Component', function () {
|
||||
assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton))
|
||||
})
|
||||
|
||||
it('should render a CurrencyDisplay as the second child of the SendRowWrapper', () => {
|
||||
assert(wrapper.find(SendRowWrapper).childAt(1).is(CurrencyDisplay))
|
||||
it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => {
|
||||
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 {
|
||||
conversionRate,
|
||||
convertedCurrency,
|
||||
onBlur,
|
||||
onChange,
|
||||
inError,
|
||||
primaryCurrency,
|
||||
selectedToken,
|
||||
error,
|
||||
value,
|
||||
} = wrapper.find(SendRowWrapper).childAt(1).props()
|
||||
assert.equal(conversionRate, 'mockAmountConversionRate')
|
||||
assert.equal(convertedCurrency, 'mockConvertedCurrency')
|
||||
assert.equal(inError, false)
|
||||
assert.equal(primaryCurrency, 'mockPrimaryCurrency')
|
||||
assert.deepEqual(selectedToken, { address: 'mockTokenAddress' })
|
||||
assert.equal(error, false)
|
||||
assert.equal(value, 'mockAmount')
|
||||
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
|
||||
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
|
||||
@ -192,11 +185,5 @@ describe('SendAmountRow Component', function () {
|
||||
['mockNewAmount']
|
||||
)
|
||||
})
|
||||
|
||||
it('should pass the default primaryCurrency to the CurrencyDisplay if primaryCurrency is falsy', () => {
|
||||
wrapper.setProps({ primaryCurrency: null })
|
||||
const { primaryCurrency } = wrapper.find(SendRowWrapper).childAt(1).props()
|
||||
assert.equal(primaryCurrency, 'ETH')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import CurrencyDisplay from '../../../../send/currency-display'
|
||||
|
||||
import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../../../constants/common'
|
||||
|
||||
export default class GasFeeDisplay extends Component {
|
||||
|
||||
@ -19,27 +19,24 @@ export default class GasFeeDisplay extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const {
|
||||
conversionRate,
|
||||
gasTotal,
|
||||
onClick,
|
||||
primaryCurrency = 'ETH',
|
||||
convertedCurrency,
|
||||
gasLoadingError,
|
||||
} = this.props
|
||||
const { gasTotal, onClick, gasLoadingError } = this.props
|
||||
|
||||
return (
|
||||
<div className="send-v2__gas-fee-display">
|
||||
{gasTotal
|
||||
? <CurrencyDisplay
|
||||
primaryCurrency={primaryCurrency}
|
||||
convertedCurrency={convertedCurrency}
|
||||
value={gasTotal}
|
||||
conversionRate={conversionRate}
|
||||
gasLoadingError={gasLoadingError}
|
||||
convertedPrefix={'$'}
|
||||
readOnly
|
||||
/>
|
||||
? (
|
||||
<div className="currency-display">
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={gasTotal}
|
||||
type={PRIMARY}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="currency-display__converted-value"
|
||||
value={gasTotal}
|
||||
type={SECONDARY}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: gasLoadingError
|
||||
? <div className="currency-display.currency-display--message">
|
||||
{this.context.t('setGasPrice')}
|
||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||
import assert from 'assert'
|
||||
import {shallow} from 'enzyme'
|
||||
import GasFeeDisplay from '../gas-fee-display.component'
|
||||
import CurrencyDisplay from '../../../../../send/currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
|
||||
import sinon from 'sinon'
|
||||
|
||||
|
||||
@ -29,17 +29,15 @@ describe('SendGasRow Component', function () {
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a CurrencyDisplay component', () => {
|
||||
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||
})
|
||||
|
||||
it('should render the CurrencyDisplay with the correct props', () => {
|
||||
const {
|
||||
conversionRate,
|
||||
convertedCurrency,
|
||||
type,
|
||||
value,
|
||||
} = wrapper.find(CurrencyDisplay).props()
|
||||
assert.equal(conversionRate, 20)
|
||||
assert.equal(convertedCurrency, 'mockConvertedCurrency')
|
||||
} = wrapper.find(UserPreferencedCurrencyDisplay).at(0).props()
|
||||
assert.equal(type, 'PRIMARY')
|
||||
assert.equal(value, 'mockGasTotal')
|
||||
})
|
||||
|
||||
|
@ -52,12 +52,12 @@ ShiftListItem.prototype.render = function () {
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
src: 'https://info.shapeshift.io/sites/default/files/logo.png',
|
||||
src: 'https://shapeshift.io/logo.png',
|
||||
style: {
|
||||
height: '35px',
|
||||
width: '132px',
|
||||
position: 'absolute',
|
||||
clip: 'rect(0px,23px,34px,0px)',
|
||||
clip: 'rect(0px,30px,34px,0px)',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
@ -132,7 +132,6 @@ ShiftListItem.prototype.renderInfo = function () {
|
||||
case 'no_deposits':
|
||||
return h('.flex-column', {
|
||||
style: {
|
||||
width: '200px',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}, [
|
||||
|
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 Card from '../card'
|
||||
import CurrencyDisplay from '../currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import HexToDecimal from '../hex-to-decimal'
|
||||
import { ETH, GWEI } from '../../constants/common'
|
||||
import { ETH, GWEI, PRIMARY, SECONDARY } from '../../constants/common'
|
||||
import { getHexGasTotal } from '../../helpers/confirm-transaction/util'
|
||||
import { sumHexes } from '../../helpers/transactions.util'
|
||||
|
||||
@ -40,9 +41,9 @@ export default class TransactionBreakdown extends PureComponent {
|
||||
className="transaction-breakdown__card"
|
||||
>
|
||||
<TransactionBreakdownRow title={t('amount')}>
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="transaction-breakdown__value"
|
||||
currency={ETH}
|
||||
type={PRIMARY}
|
||||
value={value}
|
||||
/>
|
||||
</TransactionBreakdownRow>
|
||||
@ -79,14 +80,14 @@ export default class TransactionBreakdown extends PureComponent {
|
||||
</TransactionBreakdownRow>
|
||||
<TransactionBreakdownRow title={t('total')}>
|
||||
<div>
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="transaction-breakdown__value transaction-breakdown__value--eth-total"
|
||||
currency={ETH}
|
||||
type={PRIMARY}
|
||||
value={totalInHex}
|
||||
numberOfDecimals={6}
|
||||
/>
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="transaction-breakdown__value"
|
||||
type={SECONDARY}
|
||||
value={totalInHex}
|
||||
/>
|
||||
</div>
|
||||
|
@ -4,12 +4,12 @@ import classnames from 'classnames'
|
||||
import Identicon from '../identicon'
|
||||
import TransactionStatus from '../transaction-status'
|
||||
import TransactionAction from '../transaction-action'
|
||||
import CurrencyDisplay from '../currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import TokenCurrencyDisplay from '../token-currency-display'
|
||||
import TransactionListItemDetails from '../transaction-list-item-details'
|
||||
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
|
||||
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
|
||||
import { ETH } from '../../constants/common'
|
||||
import { PRIMARY, SECONDARY } from '../../constants/common'
|
||||
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
|
||||
import { getStatusKey } from '../../helpers/transactions.util'
|
||||
|
||||
@ -103,12 +103,11 @@ export default class TransactionListItem extends PureComponent {
|
||||
prefix="-"
|
||||
/>
|
||||
) : (
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="transaction-list-item__amount transaction-list-item__amount--primary"
|
||||
value={value}
|
||||
type={PRIMARY}
|
||||
prefix="-"
|
||||
numberOfDecimals={2}
|
||||
currency={ETH}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -119,10 +118,11 @@ export default class TransactionListItem extends PureComponent {
|
||||
return token
|
||||
? null
|
||||
: (
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="transaction-list-item__amount transaction-list-item__amount--secondary"
|
||||
prefix="-"
|
||||
value={value}
|
||||
prefix="-"
|
||||
type={SECONDARY}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import sinon from 'sinon'
|
||||
import TokenBalance from '../../token-balance'
|
||||
import CurrencyDisplay from '../../currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { SEND_ROUTE } from '../../../routes'
|
||||
import TransactionViewBalance from '../transaction-view-balance.component'
|
||||
|
||||
@ -35,7 +35,7 @@ describe('TransactionViewBalance Component', () => {
|
||||
|
||||
assert.equal(wrapper.find('.transaction-view-balance').length, 1)
|
||||
assert.equal(wrapper.find('.transaction-view-balance__button').length, 2)
|
||||
assert.equal(wrapper.find(CurrencyDisplay).length, 2)
|
||||
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
|
||||
|
||||
const buttons = wrapper.find('.transaction-view-balance__buttons')
|
||||
assert.equal(propsMethodSpies.showDepositModal.callCount, 0)
|
||||
|
@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
|
||||
import Button from '../button'
|
||||
import Identicon from '../identicon'
|
||||
import TokenBalance from '../token-balance'
|
||||
import CurrencyDisplay from '../currency-display'
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import { SEND_ROUTE } from '../../routes'
|
||||
import { ETH } from '../../constants/common'
|
||||
import { PRIMARY, SECONDARY } from '../../constants/common'
|
||||
|
||||
export default class TransactionViewBalance extends PureComponent {
|
||||
static contextTypes = {
|
||||
@ -33,15 +33,17 @@ export default class TransactionViewBalance extends PureComponent {
|
||||
/>
|
||||
) : (
|
||||
<div className="transaction-view-balance__balance">
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="transaction-view-balance__primary-balance"
|
||||
value={balance}
|
||||
currency={ETH}
|
||||
numberOfDecimals={3}
|
||||
type={PRIMARY}
|
||||
ethNumberOfDecimals={3}
|
||||
/>
|
||||
<CurrencyDisplay
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="transaction-view-balance__secondary-balance"
|
||||
value={balance}
|
||||
type={SECONDARY}
|
||||
ethNumberOfDecimals={3}
|
||||
/>
|
||||
</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)
|
@ -17,7 +17,7 @@ const TokenList = require('./token-list')
|
||||
const selectors = require('../selectors')
|
||||
const { ADD_TOKEN_ROUTE } = require('../routes')
|
||||
|
||||
import Button from './button'
|
||||
import AddTokenButton from './add-token-button'
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
@ -100,15 +100,30 @@ WalletView.prototype.renderWalletBalance = function () {
|
||||
])
|
||||
}
|
||||
|
||||
WalletView.prototype.renderAddToken = function () {
|
||||
const {
|
||||
sidebarOpen,
|
||||
hideSidebar,
|
||||
history,
|
||||
} = this.props
|
||||
|
||||
return h(AddTokenButton, {
|
||||
onClick () {
|
||||
history.push(ADD_TOKEN_ROUTE)
|
||||
if (sidebarOpen) {
|
||||
hideSidebar()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
WalletView.prototype.render = function () {
|
||||
const {
|
||||
responsiveDisplayClassname,
|
||||
selectedAddress,
|
||||
keyrings,
|
||||
showAccountDetailModal,
|
||||
sidebarOpen,
|
||||
hideSidebar,
|
||||
history,
|
||||
identities,
|
||||
} = this.props
|
||||
// temporary logs + fake extra wallets
|
||||
@ -201,14 +216,7 @@ WalletView.prototype.render = function () {
|
||||
|
||||
h(TokenList),
|
||||
|
||||
h(Button, {
|
||||
type: 'primary',
|
||||
className: 'wallet-view__add-token-button',
|
||||
onClick: () => {
|
||||
history.push(ADD_TOKEN_ROUTE)
|
||||
sidebarOpen && hideSidebar()
|
||||
},
|
||||
}, this.context.t('addToken')),
|
||||
this.renderAddToken(),
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
export const ETH = 'ETH'
|
||||
export const GWEI = 'GWEI'
|
||||
export const WEI = 'WEI'
|
||||
|
||||
export const PRIMARY = 'PRIMARY'
|
||||
export const SECONDARY = 'SECONDARY'
|
||||
|
@ -120,18 +120,6 @@ $wallet-view-bg: $alabaster;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__add-token-button {
|
||||
flex: 0 0 auto;
|
||||
margin: 36px auto;
|
||||
background: none;
|
||||
transition: border-color .3s ease;
|
||||
width: 150px;
|
||||
|
||||
&:hover {
|
||||
border-color: $curious-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 576px) {
|
||||
|
@ -14,7 +14,13 @@ import {
|
||||
hexGreaterThan,
|
||||
} from '../helpers/confirm-transaction/util'
|
||||
|
||||
import { getTokenData, getMethodData, isSmartContractAddress } from '../helpers/transactions.util'
|
||||
import {
|
||||
getTokenData,
|
||||
getMethodData,
|
||||
isSmartContractAddress,
|
||||
sumHexes,
|
||||
} from '../helpers/transactions.util'
|
||||
|
||||
import { getSymbolAndDecimals } from '../token-util'
|
||||
import { conversionUtil } from '../conversion-util'
|
||||
|
||||
@ -31,7 +37,6 @@ const CLEAR_CONFIRM_TRANSACTION = createActionType('CLEAR_CONFIRM_TRANSACTION')
|
||||
const UPDATE_TRANSACTION_AMOUNTS = createActionType('UPDATE_TRANSACTION_AMOUNTS')
|
||||
const UPDATE_TRANSACTION_FEES = createActionType('UPDATE_TRANSACTION_FEES')
|
||||
const UPDATE_TRANSACTION_TOTALS = createActionType('UPDATE_TRANSACTION_TOTALS')
|
||||
const UPDATE_HEX_GAS_TOTAL = createActionType('UPDATE_HEX_GAS_TOTAL')
|
||||
const UPDATE_TOKEN_PROPS = createActionType('UPDATE_TOKEN_PROPS')
|
||||
const UPDATE_NONCE = createActionType('UPDATE_NONCE')
|
||||
const UPDATE_TO_SMART_CONTRACT = createActionType('UPDATE_TO_SMART_CONTRACT')
|
||||
@ -53,7 +58,9 @@ const initState = {
|
||||
ethTransactionAmount: '',
|
||||
ethTransactionFee: '',
|
||||
ethTransactionTotal: '',
|
||||
hexGasTotal: '',
|
||||
hexTransactionAmount: '',
|
||||
hexTransactionFee: '',
|
||||
hexTransactionTotal: '',
|
||||
nonce: '',
|
||||
toSmartContract: false,
|
||||
fetchingData: false,
|
||||
@ -99,30 +106,28 @@ export default function reducer ({ confirmTransaction: confirmState = initState
|
||||
methodData: {},
|
||||
}
|
||||
case UPDATE_TRANSACTION_AMOUNTS:
|
||||
const { fiatTransactionAmount, ethTransactionAmount } = action.payload
|
||||
const { fiatTransactionAmount, ethTransactionAmount, hexTransactionAmount } = action.payload
|
||||
return {
|
||||
...confirmState,
|
||||
fiatTransactionAmount: fiatTransactionAmount || confirmState.fiatTransactionAmount,
|
||||
ethTransactionAmount: ethTransactionAmount || confirmState.ethTransactionAmount,
|
||||
hexTransactionAmount: hexTransactionAmount || confirmState.hexTransactionAmount,
|
||||
}
|
||||
case UPDATE_TRANSACTION_FEES:
|
||||
const { fiatTransactionFee, ethTransactionFee } = action.payload
|
||||
const { fiatTransactionFee, ethTransactionFee, hexTransactionFee } = action.payload
|
||||
return {
|
||||
...confirmState,
|
||||
fiatTransactionFee: fiatTransactionFee || confirmState.fiatTransactionFee,
|
||||
ethTransactionFee: ethTransactionFee || confirmState.ethTransactionFee,
|
||||
hexTransactionFee: hexTransactionFee || confirmState.hexTransactionFee,
|
||||
}
|
||||
case UPDATE_TRANSACTION_TOTALS:
|
||||
const { fiatTransactionTotal, ethTransactionTotal } = action.payload
|
||||
const { fiatTransactionTotal, ethTransactionTotal, hexTransactionTotal } = action.payload
|
||||
return {
|
||||
...confirmState,
|
||||
fiatTransactionTotal: fiatTransactionTotal || confirmState.fiatTransactionTotal,
|
||||
ethTransactionTotal: ethTransactionTotal || confirmState.ethTransactionTotal,
|
||||
}
|
||||
case UPDATE_HEX_GAS_TOTAL:
|
||||
return {
|
||||
...confirmState,
|
||||
hexGasTotal: action.payload,
|
||||
hexTransactionTotal: hexTransactionTotal || confirmState.hexTransactionTotal,
|
||||
}
|
||||
case UPDATE_TOKEN_PROPS:
|
||||
const { tokenSymbol = '', tokenDecimals = '' } = action.payload
|
||||
@ -222,13 +227,6 @@ export function updateTransactionTotals (totals) {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateHexGasTotal (hexGasTotal) {
|
||||
return {
|
||||
type: UPDATE_HEX_GAS_TOTAL,
|
||||
payload: hexGasTotal,
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTokenProps (tokenProps) {
|
||||
return {
|
||||
type: UPDATE_TOKEN_PROPS,
|
||||
@ -297,7 +295,7 @@ export function updateTxDataAndCalculate (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({
|
||||
value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
|
||||
@ -306,31 +304,39 @@ export function updateTxDataAndCalculate (txData) {
|
||||
value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6,
|
||||
})
|
||||
|
||||
dispatch(updateTransactionAmounts({ fiatTransactionAmount, ethTransactionAmount }))
|
||||
dispatch(updateTransactionAmounts({
|
||||
fiatTransactionAmount,
|
||||
ethTransactionAmount,
|
||||
hexTransactionAmount: value,
|
||||
}))
|
||||
|
||||
const hexGasTotal = getHexGasTotal({ gasLimit, gasPrice })
|
||||
|
||||
dispatch(updateHexGasTotal(hexGasTotal))
|
||||
const hexTransactionFee = getHexGasTotal({ gasLimit, gasPrice })
|
||||
|
||||
const fiatTransactionFee = getTransactionFee({
|
||||
value: hexGasTotal,
|
||||
value: hexTransactionFee,
|
||||
toCurrency: currentCurrency,
|
||||
numberOfDecimals: 2,
|
||||
conversionRate,
|
||||
})
|
||||
const ethTransactionFee = getTransactionFee({
|
||||
value: hexGasTotal,
|
||||
value: hexTransactionFee,
|
||||
toCurrency: 'ETH',
|
||||
numberOfDecimals: 6,
|
||||
conversionRate,
|
||||
})
|
||||
|
||||
dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee }))
|
||||
dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee, hexTransactionFee }))
|
||||
|
||||
const fiatTransactionTotal = addFiat(fiatTransactionFee, fiatTransactionAmount)
|
||||
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: '',
|
||||
ethTransactionFee: '',
|
||||
ethTransactionTotal: '',
|
||||
hexGasTotal: '',
|
||||
hexTransactionAmount: '',
|
||||
hexTransactionFee: '',
|
||||
hexTransactionTotal: '',
|
||||
nonce: '',
|
||||
toSmartContract: false,
|
||||
fetchingData: false,
|
||||
@ -34,7 +36,6 @@ const CLEAR_METHOD_DATA = 'metamask/confirm-transaction/CLEAR_METHOD_DATA'
|
||||
const UPDATE_TRANSACTION_AMOUNTS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS'
|
||||
const UPDATE_TRANSACTION_FEES = 'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES'
|
||||
const UPDATE_TRANSACTION_TOTALS = 'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS'
|
||||
const UPDATE_HEX_GAS_TOTAL = 'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL'
|
||||
const UPDATE_TOKEN_PROPS = 'metamask/confirm-transaction/UPDATE_TOKEN_PROPS'
|
||||
const UPDATE_NONCE = 'metamask/confirm-transaction/UPDATE_NONCE'
|
||||
const UPDATE_TO_SMART_CONTRACT = 'metamask/confirm-transaction/UPDATE_TO_SMART_CONTRACT'
|
||||
@ -65,7 +66,9 @@ describe('Confirm Transaction Duck', () => {
|
||||
ethTransactionAmount: '1',
|
||||
ethTransactionFee: '0.000021',
|
||||
ethTransactionTotal: '469.27',
|
||||
hexGasTotal: '0x1319718a5000',
|
||||
hexTransactionAmount: '',
|
||||
hexTransactionFee: '0x1319718a5000',
|
||||
hexTransactionTotal: '',
|
||||
nonce: '0x0',
|
||||
toSmartContract: false,
|
||||
fetchingData: false,
|
||||
@ -186,12 +189,14 @@ describe('Confirm Transaction Duck', () => {
|
||||
payload: {
|
||||
fiatTransactionAmount: '123.45',
|
||||
ethTransactionAmount: '.5',
|
||||
hexTransactionAmount: '0x1',
|
||||
},
|
||||
}),
|
||||
{
|
||||
...mockState.confirmTransaction,
|
||||
fiatTransactionAmount: '123.45',
|
||||
ethTransactionAmount: '.5',
|
||||
hexTransactionAmount: '0x1',
|
||||
}
|
||||
)
|
||||
})
|
||||
@ -203,12 +208,14 @@ describe('Confirm Transaction Duck', () => {
|
||||
payload: {
|
||||
fiatTransactionFee: '123.45',
|
||||
ethTransactionFee: '.5',
|
||||
hexTransactionFee: '0x1',
|
||||
},
|
||||
}),
|
||||
{
|
||||
...mockState.confirmTransaction,
|
||||
fiatTransactionFee: '123.45',
|
||||
ethTransactionFee: '.5',
|
||||
hexTransactionFee: '0x1',
|
||||
}
|
||||
)
|
||||
})
|
||||
@ -220,25 +227,14 @@ describe('Confirm Transaction Duck', () => {
|
||||
payload: {
|
||||
fiatTransactionTotal: '123.45',
|
||||
ethTransactionTotal: '.5',
|
||||
hexTransactionTotal: '0x1',
|
||||
},
|
||||
}),
|
||||
{
|
||||
...mockState.confirmTransaction,
|
||||
fiatTransactionTotal: '123.45',
|
||||
ethTransactionTotal: '.5',
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should update hexGasTotal when receiving an UPDATE_HEX_GAS_TOTAL action', () => {
|
||||
assert.deepEqual(
|
||||
ConfirmTransactionReducer(mockState, {
|
||||
type: UPDATE_HEX_GAS_TOTAL,
|
||||
payload: '0x0',
|
||||
}),
|
||||
{
|
||||
...mockState.confirmTransaction,
|
||||
hexGasTotal: '0x0',
|
||||
hexTransactionTotal: '0x1',
|
||||
}
|
||||
)
|
||||
})
|
||||
@ -435,19 +431,6 @@ describe('Confirm Transaction Duck', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should create an action to update hexGasTotal', () => {
|
||||
const hexGasTotal = '0x0'
|
||||
const expectedAction = {
|
||||
type: UPDATE_HEX_GAS_TOTAL,
|
||||
payload: hexGasTotal,
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
actions.updateHexGasTotal(hexGasTotal),
|
||||
expectedAction
|
||||
)
|
||||
})
|
||||
|
||||
it('should create an action to update tokenProps', () => {
|
||||
const tokenProps = {
|
||||
tokenDecimals: '1',
|
||||
@ -568,7 +551,6 @@ describe('Confirm Transaction Duck', () => {
|
||||
const expectedActions = [
|
||||
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
||||
'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
||||
]
|
||||
@ -637,7 +619,6 @@ describe('Confirm Transaction Duck', () => {
|
||||
const expectedActions = [
|
||||
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
||||
'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
||||
]
|
||||
@ -687,7 +668,6 @@ describe('Confirm Transaction Duck', () => {
|
||||
const expectedActions = [
|
||||
'metamask/confirm-transaction/UPDATE_TX_DATA',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_AMOUNTS',
|
||||
'metamask/confirm-transaction/UPDATE_HEX_GAS_TOTAL',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_FEES',
|
||||
'metamask/confirm-transaction/UPDATE_TRANSACTION_TOTALS',
|
||||
]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user