1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +01:00

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

[new-ui] Improve ENS input errors and update ens validation on network change
This commit is contained in:
Dan J Miller 2018-06-27 17:09:44 -02:30 committed by GitHub
commit 1839ab5346
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 74 additions and 41 deletions

View File

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

View File

@ -12,6 +12,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete') const ToAutoComplete = require('./send/to-autocomplete')
const log = require('loglevel') const log = require('loglevel')
const { isValidENSAddress } = require('../util')
EnsInput.contextTypes = { EnsInput.contextTypes = {
t: PropTypes.func, t: PropTypes.func,
@ -25,31 +26,34 @@ function EnsInput () {
Component.call(this) Component.call(this)
} }
EnsInput.prototype.onChange = function (recipient) {
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
this.props.onChange({ toAddress: recipient })
if (!networkHasEnsSupport) return
if (recipient.match(ensRE) === null) {
return this.setState({
loadingEns: false,
ensResolution: null,
ensFailure: null,
toError: null,
})
}
this.setState({
loadingEns: true,
})
this.checkName(recipient)
}
EnsInput.prototype.render = function () { EnsInput.prototype.render = function () {
const props = this.props const props = this.props
const opts = extend(props, { const opts = extend(props, {
list: 'addresses', list: 'addresses',
onChange: (recipient) => { onChange: this.onChange.bind(this),
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
props.onChange(recipient)
if (!networkHasEnsSupport) return
if (recipient.match(ensRE) === null) {
return this.setState({
loadingEns: false,
ensResolution: null,
ensFailure: null,
})
}
this.setState({
loadingEns: true,
})
this.checkName(recipient)
},
}) })
return h('div', { return h('div', {
style: { width: '100%', position: 'relative' }, style: { width: '100%', position: 'relative' },
@ -85,17 +89,27 @@ EnsInput.prototype.lookupEnsName = function (recipient) {
nickname: recipient.trim(), nickname: recipient.trim(),
hoverText: address + '\n' + this.context.t('clickCopy'), hoverText: address + '\n' + this.context.t('clickCopy'),
ensFailure: false, ensFailure: false,
toError: null,
}) })
} }
}) })
.catch((reason) => { .catch((reason) => {
log.error(reason) const setStateObj = {
return this.setState({
loadingEns: false, loadingEns: false,
ensResolution: ZERO_ADDRESS, ensResolution: recipient,
ensFailure: true, 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 // 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. // the user's own accounts, a default of a one-space string is used.
const nickname = state.nickname || ' ' 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 && if (prevState && ensResolution && this.props.onChange &&
ensResolution !== prevState.ensResolution) { 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) { 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) { if (loadingEns) {
return h('img', { return h('img', {

View File

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

View File

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

View File

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

View File

@ -40,6 +40,12 @@ describe('send-to-row utils', () => {
to: null, 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',
})
})
}) })
}) })

View File

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