Merge pull request #4574 from MetaMask/i4409-i4410-ens-input-enhancements

[new-ui] Improve ENS input errors and update ens validation on network change
feature/default_network_editable
Dan J Miller 7 years ago committed by GitHub
commit 1839ab5346
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/_locales/en/messages.json
  2. 77
      ui/app/components/ens-input.js
  3. 6
      ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
  4. 6
      ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
  5. 12
      ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js
  6. 6
      ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js
  7. 5
      ui/app/util.js

@ -265,6 +265,9 @@
"encryptNewDen": {
"message": "Encrypt your new DEN"
},
"ensNameNotFound": {
"message": "ENS name not found"
},
"enterPassword": {
"message": "Enter password"
},

@ -12,6 +12,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete')
const log = require('loglevel')
const { isValidENSAddress } = require('../util')
EnsInput.contextTypes = {
t: PropTypes.func,
@ -25,31 +26,34 @@ function EnsInput () {
Component.call(this)
}
EnsInput.prototype.render = function () {
const props = this.props
const opts = extend(props, {
list: 'addresses',
onChange: (recipient) => {
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
EnsInput.prototype.onChange = function (recipient) {
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
props.onChange(recipient)
this.props.onChange({ toAddress: recipient })
if (!networkHasEnsSupport) return
if (!networkHasEnsSupport) return
if (recipient.match(ensRE) === null) {
return this.setState({
loadingEns: false,
ensResolution: null,
ensFailure: null,
})
}
if (recipient.match(ensRE) === null) {
return this.setState({
loadingEns: false,
ensResolution: null,
ensFailure: null,
toError: null,
})
}
this.setState({
loadingEns: true,
})
this.checkName(recipient)
},
this.setState({
loadingEns: true,
})
this.checkName(recipient)
}
EnsInput.prototype.render = function () {
const props = this.props
const opts = extend(props, {
list: 'addresses',
onChange: this.onChange.bind(this),
})
return h('div', {
style: { width: '100%', position: 'relative' },
@ -85,17 +89,27 @@ EnsInput.prototype.lookupEnsName = function (recipient) {
nickname: recipient.trim(),
hoverText: address + '\n' + this.context.t('clickCopy'),
ensFailure: false,
toError: null,
})
}
})
.catch((reason) => {
log.error(reason)
return this.setState({
const setStateObj = {
loadingEns: false,
ensResolution: ZERO_ADDRESS,
ensResolution: recipient,
ensFailure: true,
hoverText: reason.message,
})
toError: null,
}
if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') {
setStateObj.hoverText = this.context.t('ensNameNotFound')
setStateObj.toError = 'ensNameNotFound'
setStateObj.ensFailure = false
} else {
log.error(reason)
setStateObj.hoverText = reason.message
}
return this.setState(setStateObj)
})
}
@ -105,9 +119,14 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
// If an address is sent without a nickname, meaning not from ENS or from
// the user's own accounts, a default of a one-space string is used.
const nickname = state.nickname || ' '
if (prevProps.network !== this.props.network) {
const provider = global.ethereumProvider
this.ens = new ENS({ provider, network: this.props.network })
this.onChange(ensResolution)
}
if (prevState && ensResolution && this.props.onChange &&
ensResolution !== prevState.ensResolution) {
this.props.onChange(ensResolution, nickname)
this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError })
}
}
@ -124,7 +143,9 @@ EnsInput.prototype.ensIcon = function (recipient) {
}
EnsInput.prototype.ensIconContents = function (recipient) {
const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS}
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS }
if (toError) return
if (loadingEns) {
return h('img', {

@ -19,9 +19,9 @@ export default class SendToRow extends Component {
updateSendToError: PropTypes.func,
};
handleToChange (to, nickname = '') {
handleToChange (to, nickname = '', toError) {
const { updateSendTo, updateSendToError, updateGas } = this.props
const toErrorObject = getToErrorObject(to)
const toErrorObject = getToErrorObject(to, toError)
updateSendTo(to, nickname)
updateSendToError(toErrorObject)
if (toErrorObject.to === null) {
@ -53,7 +53,7 @@ export default class SendToRow extends Component {
inError={inError}
name={'address'}
network={network}
onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)}
onChange={({ toAddress, nickname, toError }) => this.handleToChange(toAddress, nickname, toError)}
openDropdown={() => openToDropdown()}
placeholder={this.context.t('recipientAddress')}
to={to}

@ -4,12 +4,10 @@ const {
} = require('../../send.constants')
const { isValidAddress } = require('../../../../util')
function getToErrorObject (to) {
let toError = null
function getToErrorObject (to, toError = null) {
if (!to) {
toError = REQUIRED_ERROR
} else if (!isValidAddress(to)) {
} else if (!isValidAddress(to) && !toError) {
toError = INVALID_RECIPIENT_ADDRESS_ERROR
}

@ -6,8 +6,8 @@ import proxyquire from 'proxyquire'
const SendToRow = proxyquire('../send-to-row.component.js', {
'./send-to-row.utils.js': {
getToErrorObject: (to) => ({
to: to === false ? null : `mockToErrorObject:${to}`,
getToErrorObject: (to, toError) => ({
to: to === false ? null : `mockToErrorObject:${to}${toError}`,
}),
},
}).default
@ -67,11 +67,11 @@ describe('SendToRow Component', function () {
it('should call updateSendToError', () => {
assert.equal(propsMethodSpies.updateSendToError.callCount, 0)
instance.handleToChange('mockTo2')
instance.handleToChange('mockTo2', '', 'mockToError')
assert.equal(propsMethodSpies.updateSendToError.callCount, 1)
assert.deepEqual(
propsMethodSpies.updateSendToError.getCall(0).args,
[{ to: 'mockToErrorObject:mockTo2' }]
[{ to: 'mockToErrorObject:mockTo2mockToError' }]
)
})
@ -138,11 +138,11 @@ describe('SendToRow Component', function () {
openDropdown()
assert.equal(propsMethodSpies.openToDropdown.callCount, 1)
assert.equal(SendToRow.prototype.handleToChange.callCount, 0)
onChange('mockNewTo', 'mockNewNickname')
onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError' })
assert.equal(SendToRow.prototype.handleToChange.callCount, 1)
assert.deepEqual(
SendToRow.prototype.handleToChange.getCall(0).args,
['mockNewTo', 'mockNewNickname']
['mockNewTo', 'mockNewNickname', 'mockToError']
)
})
})

@ -40,6 +40,12 @@ describe('send-to-row utils', () => {
to: null,
})
})
it('should return the passed error if to is truthy but invalid if to is truthy and valid', () => {
assert.deepEqual(getToErrorObject('invalid #$ 345878', 'someExplicitError'), {
to: 'someExplicitError',
})
})
})
})

@ -36,6 +36,7 @@ module.exports = {
miniAddressSummary: miniAddressSummary,
isAllOneCase: isAllOneCase,
isValidAddress: isValidAddress,
isValidENSAddress,
numericBalance: numericBalance,
parseBalance: parseBalance,
formatBalance: formatBalance,
@ -87,6 +88,10 @@ function isValidAddress (address) {
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
}
function isValidENSAddress (address) {
return address.match(/^.{7,}\.(eth|test)$/)
}
function isInvalidChecksumAddress (address) {
var prefixed = ethUtil.addHexPrefix(address)
if (address === '0x0000000000000000000000000000000000000000') return false

Loading…
Cancel
Save