Client side error handling for from, to and amount fields in send.js

feature/default_network_editable
Dan 7 years ago committed by Chi Kei Chan
parent 83cda2b82e
commit 14bdc5a78c
  1. 13
      ui/app/conversion-util.js
  2. 15
      ui/app/css/itcss/components/send.scss
  3. 195
      ui/app/send.js
  4. 5
      ui/app/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,
}

@ -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 {

@ -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))
}

@ -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)
}

Loading…
Cancel
Save