Resolve ENS name onChange, add ENS support to load safe route

This commit is contained in:
mmv 2019-08-15 18:42:51 +04:00
parent 6f92662e03
commit 2acd0e17bf
8 changed files with 64 additions and 60 deletions

View File

@ -1,13 +1,12 @@
// @flow // @flow
import * as React from 'react' import * as React from 'react'
import { Field } from 'react-final-form' import { Field } from 'react-final-form'
import { OnChange } from 'react-final-form-listeners'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
import { import {
composeValidators, composeValidators,
required, required,
mustBeEthereumAddress, mustBeEthereumAddress,
ifElseValidator,
ensResolverHasAddress,
} from '~/components/forms/validator' } from '~/components/forms/validator'
import { getAddressFromENS } from '~/logic/wallets/getWeb3' import { getAddressFromENS } from '~/logic/wallets/getWeb3'
@ -22,9 +21,7 @@ type Props = {
inputAdornment?: React.Element, inputAdornment?: React.Element,
} }
const isValidEnsName = name => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name) const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
const { useState, useEffect } = React
// an idea for second field was taken from here // an idea for second field was taken from here
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js // https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
@ -46,7 +43,7 @@ const AddressInput = ({
type="text" type="text"
validate={composeValidators( validate={composeValidators(
required, required,
ifElseValidator(isValidEnsName, ensResolverHasAddress, mustBeEthereumAddress), mustBeEthereumAddress,
...validators, ...validators,
)} )}
inputAdornment={inputAdornment} inputAdornment={inputAdornment}
@ -55,7 +52,21 @@ const AddressInput = ({
className={className} className={className}
testId={testId} testId={testId}
/> />
<Field <OnChange name={name}>
{async (value) => {
if (isValidEnsName(value)) {
try {
const resolverAddr = await getAddressFromENS(value)
fieldMutator(resolverAddr)
} catch (err) {
console.error('Failed to resolve address for ENS name: ', err)
}
}
}}
</OnChange>
{/* onBlur - didn't work because of the complex validation
(if you submit before it gets the address, breaks everything) */}
{/* <Field
name={name} name={name}
subscription={{ active: true, value: true }} subscription={{ active: true, value: true }}
render={({ meta, input }) => { render={({ meta, input }) => {
@ -82,7 +93,7 @@ const AddressInput = ({
return null return null
}} }}
/> /> */}
</> </>
) )

View File

@ -1,6 +1,6 @@
// @flow // @flow
import { type FieldValidator } from 'final-form' import { type FieldValidator } from 'final-form'
import { getWeb3, getAddressFromENS } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
export const simpleMemoize = (fn: Function) => { export const simpleMemoize = (fn: Function) => {
let lastArg let lastArg
@ -61,7 +61,7 @@ export const ok = () => undefined
export const mustBeEthereumAddress = simpleMemoize((address: Field) => { export const mustBeEthereumAddress = simpleMemoize((address: Field) => {
const isAddress: boolean = getWeb3().utils.isAddress(address) const isAddress: boolean = getWeb3().utils.isAddress(address)
return isAddress ? undefined : 'Address should be a valid Ethereum address or ENS domain' return isAddress ? undefined : 'Address should be a valid Ethereum address or ENS name'
}) })
export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) => (value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`) export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) => (value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`)
@ -90,24 +90,4 @@ export const differentFrom = (diffValue: string) => (value: string) => {
return undefined return undefined
} }
export const ensResolverHasAddress = async (value: string) => {
let error
try {
await getAddressFromENS(value)
} catch {
error = 'Couldn\'t resolve the address'
}
return error
}
export const noErrorsOn = (name: string, errors: Object) => errors[name] === undefined export const noErrorsOn = (name: string, errors: Object) => errors[name] === undefined
export const ifElseValidator = (ifFunc: Function, thenFunc: Function, elseFunc: Function) => (value: string) => {
if (ifFunc(value)) {
return thenFunc(value)
}
return elseFunc(value)
}

View File

@ -5,6 +5,7 @@ import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
import InputAdornment from '@material-ui/core/InputAdornment' import InputAdornment from '@material-ui/core/InputAdornment'
import CheckCircle from '@material-ui/icons/CheckCircle' import CheckCircle from '@material-ui/icons/CheckCircle'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import AddressInput from '~/components/forms/AddressInput'
import { import {
composeValidators, required, noErrorsOn, mustBeEthereumAddress, composeValidators, required, noErrorsOn, mustBeEthereumAddress,
} from '~/components/forms/validator' } from '~/components/forms/validator'
@ -19,6 +20,7 @@ import { getSafeMasterContract } from '~/logic/contracts/safeContracts'
type Props = { type Props = {
classes: Object, classes: Object,
errors: Object, errors: Object,
form: Object,
} }
const styles = () => ({ const styles = () => ({
@ -80,8 +82,8 @@ export const safeFieldsValidation = async (values: Object) => {
return errors return errors
} }
const Details = ({ classes, errors }: Props) => ( const Details = ({ classes, errors, form }: Props) => (
<React.Fragment> <>
<Block margin="sm"> <Block margin="sm">
<Paragraph noMargin size="md" color="primary"> <Paragraph noMargin size="md" color="primary">
Adding an existing Safe only requires the Safe address. Optionally you can give it a name. In case your Adding an existing Safe only requires the Safe address. Optionally you can give it a name. In case your
@ -99,9 +101,12 @@ const Details = ({ classes, errors }: Props) => (
/> />
</Block> </Block>
<Block margin="lg" className={classes.root}> <Block margin="lg" className={classes.root}>
<Field <AddressInput
name={FIELD_LOAD_ADDRESS} name={FIELD_LOAD_ADDRESS}
component={TextField} component={TextField}
fieldMutator={(val) => {
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
}}
inputAdornment={ inputAdornment={
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && { noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
endAdornment: ( endAdornment: (
@ -117,17 +122,17 @@ const Details = ({ classes, errors }: Props) => (
text="Safe Address" text="Safe Address"
/> />
</Block> </Block>
</React.Fragment> </>
) )
const DetailsForm = withStyles(styles)(Details) const DetailsForm = withStyles(styles)(Details)
const DetailsPage = () => (controls: React.Node, { errors }: Object) => ( const DetailsPage = () => (controls: React.Node, { errors, form }: Object) => (
<React.Fragment> <>
<OpenPaper controls={controls} container={605}> <OpenPaper controls={controls} container={605}>
<DetailsForm errors={errors} /> <DetailsForm errors={errors} form={form} />
</OpenPaper> </OpenPaper>
</React.Fragment> </>
) )
export default DetailsPage export default DetailsPage

View File

@ -29,6 +29,12 @@ const back = () => {
history.goBack() history.goBack()
} }
const formMutators = {
setValue: ([field, value], state, { changeValue }) => {
changeValue(state, field, () => value)
},
}
const Layout = ({ const Layout = ({
provider, onLoadSafeSubmit, network, userAddress, provider, onLoadSafeSubmit, network, userAddress,
}: Props) => { }: Props) => {
@ -36,7 +42,7 @@ const Layout = ({
const initialValues = {} const initialValues = {}
return ( return (
<React.Fragment> <>
{provider ? ( {provider ? (
<Block> <Block>
<Row align="center"> <Row align="center">
@ -45,7 +51,13 @@ const Layout = ({
</IconButton> </IconButton>
<Heading tag="h2">Load existing Safe</Heading> <Heading tag="h2">Load existing Safe</Heading>
</Row> </Row>
<Stepper onSubmit={onLoadSafeSubmit} steps={steps} initialValues={initialValues} testId="load-safe-form"> <Stepper
onSubmit={onLoadSafeSubmit}
steps={steps}
initialValues={initialValues}
mutators={formMutators}
testId="load-safe-form"
>
<StepperPage validate={safeFieldsValidation}>{DetailsForm}</StepperPage> <StepperPage validate={safeFieldsValidation}>{DetailsForm}</StepperPage>
<StepperPage network={network}>{OwnerList}</StepperPage> <StepperPage network={network}>{OwnerList}</StepperPage>
<StepperPage network={network} userAddress={userAddress}> <StepperPage network={network} userAddress={userAddress}>
@ -56,7 +68,7 @@ const Layout = ({
) : ( ) : (
<div>No account detected</div> <div>No account detected</div>
)} )}
</React.Fragment> </>
) )
} }

View File

@ -118,7 +118,7 @@ const OwnerListComponent = (props: Props) => {
}, []) }, [])
return ( return (
<React.Fragment> <>
<Block className={classes.title}> <Block className={classes.title}>
<Paragraph noMargin size="md" color="primary"> <Paragraph noMargin size="md" color="primary">
{`This Safe has ${owners.length} owners. Optional: Provide a name for each owner.`} {`This Safe has ${owners.length} owners. Optional: Provide a name for each owner.`}
@ -159,18 +159,18 @@ const OwnerListComponent = (props: Props) => {
</Row> </Row>
))} ))}
</Block> </Block>
</React.Fragment> </>
) )
} }
const OwnerListPage = withStyles(styles)(OwnerListComponent) const OwnerListPage = withStyles(styles)(OwnerListComponent)
const OwnerList = ({ updateInitialProps }: Object, network: string) => (controls: React$Node, { values }: Object) => ( const OwnerList = ({ updateInitialProps }: Object, network: string) => (controls: React$Node, { values }: Object) => (
<React.Fragment> <>
<OpenPaper controls={controls} padding={false}> <OpenPaper controls={controls} padding={false}>
<OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} /> <OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} />
</OpenPaper> </OpenPaper>
</React.Fragment> </>
) )
export default OwnerList export default OwnerList

View File

@ -51,7 +51,7 @@ const Layout = ({
const initialValues = initialValuesFrom(userAccount) const initialValues = initialValuesFrom(userAccount)
return ( return (
<React.Fragment> <>
{provider ? ( {provider ? (
<Block> <Block>
<Row align="center"> <Row align="center">
@ -75,7 +75,7 @@ const Layout = ({
) : ( ) : (
<div>No web3 provider detected</div> <div>No web3 provider detected</div>
)} )}
</React.Fragment> </>
) )
} }

View File

@ -106,7 +106,7 @@ const SafeOwners = (props: Props) => {
} }
return ( return (
<React.Fragment> <>
<Block className={classes.title}> <Block className={classes.title}>
<Paragraph noMargin size="md" color="primary"> <Paragraph noMargin size="md" color="primary">
Specify the owners of the Safe. Specify the owners of the Safe.
@ -212,14 +212,14 @@ owner(s)
</Row> </Row>
</Block> </Block>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />} {qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
</React.Fragment> </>
) )
} }
const SafeOwnersForm = withStyles(styles)(SafeOwners) const SafeOwnersForm = withStyles(styles)(SafeOwners)
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => ( const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => (
<React.Fragment> <>
<OpenPaper controls={controls} padding={false}> <OpenPaper controls={controls} padding={false}>
<SafeOwnersForm <SafeOwnersForm
otherAccounts={getAccountsFrom(values)} otherAccounts={getAccountsFrom(values)}
@ -229,7 +229,7 @@ const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node
values={values} values={values}
/> />
</OpenPaper> </OpenPaper>
</React.Fragment> </>
) )
export default SafeOwnersPage export default SafeOwnersPage

View File

@ -19,11 +19,7 @@ import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
import { type Token } from '~/logic/tokens/store/model/token' import { type Token } from '~/logic/tokens/store/model/token'
import { import {
composeValidators, composeValidators, required, mustBeFloat, maxValue, greaterThan,
required,
mustBeFloat,
maxValue,
greaterThan,
} from '~/components/forms/validator' } from '~/components/forms/validator'
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField' import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
@ -74,7 +70,7 @@ const SendFunds = ({
} }
return ( return (
<React.Fragment> <>
<Row align="center" grow className={classes.heading}> <Row align="center" grow className={classes.heading}>
<Paragraph weight="bolder" className={classes.manage} noMargin> <Paragraph weight="bolder" className={classes.manage} noMargin>
Send Funds Send Funds
@ -102,7 +98,7 @@ const SendFunds = ({
const { token } = formState.values const { token } = formState.values
return ( return (
<React.Fragment> <>
<Row margin="md"> <Row margin="md">
<Col xs={12}> <Col xs={12}>
<AddressInput <AddressInput
@ -174,12 +170,12 @@ const SendFunds = ({
Review Review
</Button> </Button>
</Row> </Row>
</React.Fragment> </>
) )
}} }}
</GnoForm> </GnoForm>
</Block> </Block>
</React.Fragment> </>
) )
} }