aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan <danjm.com@gmail.com>2017-09-21 01:37:12 +0800
committerChi Kei Chan <chikeichan@gmail.com>2017-09-23 03:10:30 +0800
commit14bdc5a78c8529742754d69b8e45693b06b380fe (patch)
tree21d344592c90c2902fae5be9bf1603118c886ffe
parent83cda2b82e082a5a9b2ee35a9b6d55be43b0d788 (diff)
downloadtangerine-wallet-browser-14bdc5a78c8529742754d69b8e45693b06b380fe.tar
tangerine-wallet-browser-14bdc5a78c8529742754d69b8e45693b06b380fe.tar.gz
tangerine-wallet-browser-14bdc5a78c8529742754d69b8e45693b06b380fe.tar.bz2
tangerine-wallet-browser-14bdc5a78c8529742754d69b8e45693b06b380fe.tar.lz
tangerine-wallet-browser-14bdc5a78c8529742754d69b8e45693b06b380fe.tar.xz
tangerine-wallet-browser-14bdc5a78c8529742754d69b8e45693b06b380fe.tar.zst
tangerine-wallet-browser-14bdc5a78c8529742754d69b8e45693b06b380fe.zip
Client side error handling for from, to and amount fields in send.js
-rw-r--r--ui/app/conversion-util.js13
-rw-r--r--ui/app/css/itcss/components/send.scss15
-rw-r--r--ui/app/send.js195
-rw-r--r--ui/app/util.js5
4 files changed, 184 insertions, 44 deletions
diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js
index 0ede77487..7e02fe2bd 100644
--- a/ui/app/conversion-util.js
+++ b/ui/app/conversion-util.js
@@ -123,7 +123,20 @@ const addCurrencies = (a, b, { toNumericBase, numberOfDecimals }) => {
})
}
+const conversionGreaterThan = (
+ { value, fromNumericBase },
+ { value: compareToValue, fromNumericBase: compareToBase },
+) => {
+ const firstValue = converter({ value, fromNumericBase })
+ const secondValue = converter({
+ value: compareToValue,
+ fromNumericBase: compareToBase,
+ })
+ return firstValue.gt(secondValue)
+}
+
module.exports = {
conversionUtil,
addCurrencies,
+ conversionGreaterThan,
} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 2d6374aa2..84f678130 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -104,6 +104,16 @@
color: $red;
}
}
+
+ .send-screen-input-wrapper__error-message {
+ display: block;
+ position: absolute;
+ bottom: 4px;
+ font-size: 12px;
+ line-height: 12px;
+ left: 8px;
+ color: $red;
+ }
}
.send-screen-input {
@@ -295,6 +305,11 @@
width: 163px;
text-align: center;
}
+
+ &__send-button__disabled {
+ opacity: 0.5;
+ cursor: auto;
+ }
}
.send-token {
diff --git a/ui/app/send.js b/ui/app/send.js
index 481682bc8..16fe470be 100644
--- a/ui/app/send.js
+++ b/ui/app/send.js
@@ -18,8 +18,8 @@ const {
signTx,
} = require('./actions')
const { stripHexPrefix, addHexPrefix } = require('ethereumjs-util')
-const { isHex, numericBalance, isValidAddress } = require('./util')
-const { conversionUtil } = require('./conversion-util')
+const { isHex, numericBalance, isValidAddress, allNull } = require('./util')
+const { conversionUtil, conversionGreaterThan } = require('./conversion-util')
const BigNumber = require('bignumber.js')
module.exports = connect(mapStateToProps)(SendTransactionScreen)
@@ -51,7 +51,7 @@ function mapStateToProps (state) {
error: warning && warning.split('.')[0],
account,
identity: identities[address],
- balance: account ? numericBalance(account.balance) : null,
+ balance: account ? account.balance : null,
}
}
@@ -65,8 +65,8 @@ function SendTransactionScreen () {
newTx: {
from: '',
to: '',
- // these values are hardcoded, so "Next" can be clicked
- amount: '0x0', // see L544
+ amount: 0,
+ amountToSend: '0x0',
gasPrice: '0x5d21dba00',
gas: '0x7b0d',
txData: null,
@@ -74,6 +74,8 @@ function SendTransactionScreen () {
},
activeCurrency: 'USD',
tooltipIsOpen: false,
+ errors: {},
+ isValid: false,
}
this.back = this.back.bind(this)
@@ -81,12 +83,26 @@ function SendTransactionScreen () {
this.onSubmit = this.onSubmit.bind(this)
this.setActiveCurrency = this.setActiveCurrency.bind(this)
this.toggleTooltip = this.toggleTooltip.bind(this)
+ this.validate = this.validate.bind(this)
+ this.getAmountToSend = this.getAmountToSend.bind(this)
+ this.setErrorsFor = this.setErrorsFor.bind(this)
+ this.clearErrorsFor = this.clearErrorsFor.bind(this)
this.renderFromInput = this.renderFromInput.bind(this)
this.renderToInput = this.renderToInput.bind(this)
this.renderAmountInput = this.renderAmountInput.bind(this)
this.renderGasInput = this.renderGasInput.bind(this)
this.renderMemoInput = this.renderMemoInput.bind(this)
+ this.renderErrorMessage = this.renderErrorMessage.bind(this)
+}
+
+SendTransactionScreen.prototype.renderErrorMessage = function(errorType, warning) {
+ const { errors } = this.state
+ const errorMessage = errors[errorType];
+
+ return errorMessage || warning
+ ? h('div.send-screen-input-wrapper__error-message', [ errorMessage || warning ])
+ : null
}
SendTransactionScreen.prototype.renderFromInput = function (from, identities) {
@@ -106,6 +122,8 @@ SendTransactionScreen.prototype.renderFromInput = function (from, identities) {
},
})
},
+ onBlur: () => this.setErrorsFor('from'),
+ onFocus: () => this.clearErrorsFor('from'),
}),
h('datalist#accounts', [
@@ -118,6 +136,8 @@ SendTransactionScreen.prototype.renderFromInput = function (from, identities) {
}),
]),
+ this.renderErrorMessage('from'),
+
])
}
@@ -139,6 +159,8 @@ SendTransactionScreen.prototype.renderToInput = function (to, identities, addres
},
})
},
+ onBlur: () => this.setErrorsFor('to'),
+ onFocus: () => this.clearErrorsFor('to'),
}),
h('datalist#addresses', [
@@ -160,6 +182,8 @@ SendTransactionScreen.prototype.renderToInput = function (to, identities, addres
}),
]),
+ this.renderErrorMessage('to'),
+
])
}
@@ -183,12 +207,17 @@ SendTransactionScreen.prototype.renderAmountInput = function (activeCurrency) {
this.state.newTx,
{
amount: event.target.value,
+ amountToSend: this.getAmountToSend(event.target.value),
}
),
})
},
+ onBlur: () => this.setErrorsFor('amount'),
+ onFocus: () => this.clearErrorsFor('amount'),
}),
+ this.renderErrorMessage('amount'),
+
])
}
@@ -260,14 +289,13 @@ SendTransactionScreen.prototype.render = function () {
const props = this.props
const {
- // selectedIdentity,
- // network,
+ warning,
identities,
addressBook,
conversionRate,
} = props
- const { blockGasLimit, newTx, activeCurrency } = this.state
+ const { blockGasLimit, newTx, activeCurrency, isValid } = this.state
const { gas, gasPrice } = newTx
return (
@@ -292,12 +320,15 @@ SendTransactionScreen.prototype.render = function () {
this.renderMemoInput(),
+ this.renderErrorMessage(null, warning),
+
]),
// Buttons underneath card
h('section.flex-column.flex-center', [
h('button.btn-secondary.send-screen__send-button', {
- onClick: (event) => this.onSubmit(event),
+ className: !isValid && 'send-screen__send-button__disabled',
+ onClick: (event) => isValid && this.onSubmit(event),
}, 'Next'),
h('button.btn-tertiary.send-screen__cancel-button', {
onClick: this.back,
@@ -325,62 +356,140 @@ SendTransactionScreen.prototype.back = function () {
this.props.dispatch(backToAccountDetail(address))
}
-SendTransactionScreen.prototype.onSubmit = function (event) {
- event.preventDefault()
- const { warning } = this.props
- const state = this.state || {}
+SendTransactionScreen.prototype.validate = function (balance, amountToSend, { to, from }) {
+ const sufficientBalance = conversionGreaterThan(
+ {
+ value: balance,
+ fromNumericBase: 'hex',
+ },
+ {
+ value: amountToSend,
+ fromNumericBase: 'hex',
+ },
+ )
- const recipient = state.newTx.to
- const nickname = state.nickname || ' '
+ const amountLessThanZero = conversionGreaterThan(
+ {
+ value: 0,
+ fromNumericBase: 'dec',
+ },
+ {
+ value: amountToSend,
+ fromNumericBase: 'hex',
+ },
+ )
- // TODO: convert this to hex when created and include it in send
- const txData = state.newTx.memo
+ const errors = {}
- let message
+ if (!sufficientBalance) {
+ errors.amount = 'Insufficient funds.'
+ }
- // if (value.gt(balance)) {
- // message = 'Insufficient funds.'
- // return this.props.dispatch(actions.displayWarning(message))
- // }
+ if (amountLessThanZero) {
+ errors.amount = 'Can not send negative amounts of ETH.'
+ }
- // if (input < 0) {
- // message = 'Can not send negative amounts of ETH.'
- // return this.props.dispatch(actions.displayWarning(message))
- // }
+ if (!from) {
+ errors.from = 'Required'
+ }
- if (!isValidAddress(recipient) && !recipient) {
- message = 'Recipient address is invalid.'
- return this.props.dispatch(displayWarning(message))
+ if (from && !isValidAddress(from)) {
+ errors.from = 'Sender address is invalid.'
}
- if (txData && !isHex(stripHexPrefix(txData))) {
- message = 'Transaction data must be hex string.'
- return this.props.dispatch(displayWarning(message))
+ if (!to) {
+ errors.to = 'Required'
}
- this.props.dispatch(hideWarning())
+ if (to && !isValidAddress(to)) {
+ errors.to = 'Recipient address is invalid.'
+ }
- this.props.dispatch(addToAddressBook(recipient, nickname))
+ // if (txData && !isHex(stripHexPrefix(txData))) {
+ // message = 'Transaction data must be hex string.'
+ // return this.props.dispatch(displayWarning(message))
+ // }
+
+ return {
+ isValid: allNull(errors),
+ errors,
+ }
+}
+
+SendTransactionScreen.prototype.getAmountToSend = function (amount) {
+ const { activeCurrency } = this.state
+ const { conversionRate } = this.props
// TODO: need a clean way to integrate this into conversionUtil
- const sendConversionRate = state.activeCurrency === 'ETH'
- ? this.props.conversionRate
- : new BigNumber(1.0).div(this.props.conversionRate)
+ const sendConversionRate = activeCurrency === 'ETH'
+ ? conversionRate
+ : new BigNumber(1.0).div(conversionRate)
- const sendAmount = conversionUtil(this.state.newTx.amount, {
+ return conversionUtil(amount, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
- fromCurrency: state.activeCurrency,
+ fromCurrency: activeCurrency,
toCurrency: 'ETH',
toDenomination: 'WEI',
conversionRate: sendConversionRate,
})
-
+}
+
+SendTransactionScreen.prototype.setErrorsFor = function (field) {
+ const { balance } = this.props
+ const { newTx, errors: previousErrors } = this.state
+ const { amountToSend } = newTx
+
+ const {
+ isValid,
+ errors: newErrors
+ } = this.validate(balance, amountToSend, newTx)
+
+ const nextErrors = Object.assign({}, previousErrors, {
+ [field]: newErrors[field] || null
+ })
+
+ if (!isValid) {
+ this.setState({
+ errors: nextErrors,
+ isValid,
+ })
+ }
+}
+
+SendTransactionScreen.prototype.clearErrorsFor = function (field) {
+ const { errors: previousErrors } = this.state
+ const nextErrors = Object.assign({}, previousErrors, {
+ [field]: null
+ })
+
+ this.setState({
+ errors: nextErrors,
+ isValid: allNull(nextErrors),
+ })
+}
+
+SendTransactionScreen.prototype.onSubmit = function (event) {
+ event.preventDefault()
+ const { warning, balance, amountToSend } = this.props
+ const state = this.state || {}
+
+ const recipient = state.newTx.to
+ const sender = state.newTx.from
+ const nickname = state.nickname || ' '
+
+ // TODO: convert this to hex when created and include it in send
+ const txData = state.newTx.memo
+
+ this.props.dispatch(hideWarning())
+
+ this.props.dispatch(addToAddressBook(recipient, nickname))
+
var txParams = {
from: this.state.newTx.from,
to: this.state.newTx.to,
- value: sendAmount,
+ value: amountToSend,
gas: this.state.newTx.gas,
gasPrice: this.state.newTx.gasPrice,
@@ -389,7 +498,5 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
if (recipient) txParams.to = addHexPrefix(recipient)
if (txData) txParams.data = txData
- if (!warning) {
- this.props.dispatch(signTx(txParams))
- }
+ this.props.dispatch(signTx(txParams))
}
diff --git a/ui/app/util.js b/ui/app/util.js
index 7aace1b3c..82a5f9f29 100644
--- a/ui/app/util.js
+++ b/ui/app/util.js
@@ -55,6 +55,7 @@ module.exports = {
getContractAtAddress,
exportAsFile: exportAsFile,
isInvalidChecksumAddress,
+ allNull,
}
function valuesFor (obj) {
@@ -273,3 +274,7 @@ function exportAsFile (filename, data) {
document.body.removeChild(elem)
}
}
+
+function allNull (obj) {
+ return Object.entries(obj).every(([key, value]) => value === null)
+}