mirror of
https://github.com/status-im/safe-react.git
synced 2025-02-18 12:36:34 +00:00
Trim spaces from AddressInput (#1142)
* Remove spaces * Change naming convention to make clear that only edge whitespaces are removed Fix function documentation in string util * Add trim spaces from address input in AddToken and AddAsset * Use validator type * Trim spaces on Safe App links Co-authored-by: Mati Dastugue <mdastugu@amazon.com> Co-authored-by: Mati Dastugue <matias.dastugue@altoros.com> Co-authored-by: Mikhail Mikheev <mmvsha73@gmail.com>
This commit is contained in:
parent
bbfa7d8166
commit
89c17180de
@ -220,17 +220,17 @@
|
|||||||
"web3": "1.2.11"
|
"web3": "1.2.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/history": "4.6.2",
|
|
||||||
"@types/lodash.memoize": "^4.1.6",
|
|
||||||
"@types/react-router-dom": "^5.1.5",
|
|
||||||
"@types/react-redux": "^7.1.9",
|
|
||||||
"@testing-library/jest-dom": "5.11.1",
|
"@testing-library/jest-dom": "5.11.1",
|
||||||
"@testing-library/react": "10.4.7",
|
"@testing-library/react": "10.4.7",
|
||||||
"@testing-library/user-event": "12.0.13",
|
"@testing-library/user-event": "12.0.13",
|
||||||
|
"@types/history": "4.6.2",
|
||||||
"@types/jest": "^26.0.7",
|
"@types/jest": "^26.0.7",
|
||||||
|
"@types/lodash.memoize": "^4.1.6",
|
||||||
"@types/node": "14.0.25",
|
"@types/node": "14.0.25",
|
||||||
"@types/react": "^16.9.43",
|
"@types/react": "^16.9.43",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
|
"@types/react-redux": "^7.1.9",
|
||||||
|
"@types/react-router-dom": "^5.1.5",
|
||||||
"@types/styled-components": "^5.1.1",
|
"@types/styled-components": "^5.1.1",
|
||||||
"@typescript-eslint/eslint-plugin": "3.7.0",
|
"@typescript-eslint/eslint-plugin": "3.7.0",
|
||||||
"@typescript-eslint/parser": "3.7.0",
|
"@typescript-eslint/parser": "3.7.0",
|
||||||
@ -254,6 +254,7 @@
|
|||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"prettier": "2.0.5",
|
"prettier": "2.0.5",
|
||||||
"react-app-rewired": "^2.1.6",
|
"react-app-rewired": "^2.1.6",
|
||||||
|
"source-map-explorer": "^2.4.2",
|
||||||
"truffle": "5.1.35",
|
"truffle": "5.1.35",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
"wait-on": "5.1.0",
|
"wait-on": "5.1.0",
|
||||||
|
@ -3,13 +3,27 @@ import { Field } from 'react-final-form'
|
|||||||
import { OnChange } from 'react-final-form-listeners'
|
import { OnChange } from 'react-final-form-listeners'
|
||||||
|
|
||||||
import TextField from 'src/components/forms/TextField'
|
import TextField from 'src/components/forms/TextField'
|
||||||
import { composeValidators, mustBeEthereumAddress, required } from 'src/components/forms/validator'
|
import { Validator, composeValidators, mustBeEthereumAddress, required } from 'src/components/forms/validator'
|
||||||
|
import { trimSpaces } from 'src/utils/strings'
|
||||||
import { getAddressFromENS } from 'src/logic/wallets/getWeb3'
|
import { getAddressFromENS } from 'src/logic/wallets/getWeb3'
|
||||||
import { isValidEnsName } from 'src/logic/wallets/ethAddresses'
|
import { isValidEnsName } from 'src/logic/wallets/ethAddresses'
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
|
export interface AddressInputProps {
|
||||||
|
fieldMutator: (address: string) => void
|
||||||
|
name?: string
|
||||||
|
text?: string
|
||||||
|
placeholder?: string
|
||||||
|
inputAdornment?: { endAdornment: React.ReactElement } | undefined
|
||||||
|
testId: string
|
||||||
|
validators?: Validator[]
|
||||||
|
defaultValue?: string
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
const AddressInput = ({
|
const AddressInput = ({
|
||||||
className = '',
|
className = '',
|
||||||
name = 'recipientAddress',
|
name = 'recipientAddress',
|
||||||
@ -21,7 +35,7 @@ const AddressInput = ({
|
|||||||
validators = [],
|
validators = [],
|
||||||
defaultValue,
|
defaultValue,
|
||||||
disabled,
|
disabled,
|
||||||
}: any) => (
|
}: AddressInputProps): React.ReactElement => (
|
||||||
<>
|
<>
|
||||||
<Field
|
<Field
|
||||||
className={className}
|
className={className}
|
||||||
@ -38,14 +52,15 @@ const AddressInput = ({
|
|||||||
/>
|
/>
|
||||||
<OnChange name={name}>
|
<OnChange name={name}>
|
||||||
{async (value) => {
|
{async (value) => {
|
||||||
if (isValidEnsName(value)) {
|
const address = trimSpaces(value)
|
||||||
|
if (isValidEnsName(address)) {
|
||||||
try {
|
try {
|
||||||
const resolverAddr = await getAddressFromENS(value)
|
const resolverAddr = await getAddressFromENS(address)
|
||||||
fieldMutator(resolverAddr)
|
fieldMutator(resolverAddr)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to resolve address for ENS name: ', err)
|
console.error('Failed to resolve address for ENS name: ', err)
|
||||||
}
|
}
|
||||||
}
|
} else fieldMutator(address)
|
||||||
}}
|
}}
|
||||||
</OnChange>
|
</OnChange>
|
||||||
</>
|
</>
|
||||||
|
@ -3,15 +3,19 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Field } from 'react-final-form'
|
import { Field } from 'react-final-form'
|
||||||
|
|
||||||
|
import { trimSpaces } from 'src/utils/strings'
|
||||||
|
|
||||||
const DebounceValidationField = ({ debounce = 1000, validate, ...rest }: any) => {
|
const DebounceValidationField = ({ debounce = 1000, validate, ...rest }: any) => {
|
||||||
let clearTimeout
|
let clearTimeout
|
||||||
|
|
||||||
const localValidation = (value, values, fieldState) => {
|
const localValidation = (value, values, fieldState) => {
|
||||||
|
const url = trimSpaces(value)
|
||||||
|
|
||||||
if (fieldState.active) {
|
if (fieldState.active) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (clearTimeout) clearTimeout()
|
if (clearTimeout) clearTimeout()
|
||||||
const timerId = setTimeout(() => {
|
const timerId = setTimeout(() => {
|
||||||
resolve(validate(value, values, fieldState))
|
resolve(validate(url, values, fieldState))
|
||||||
}, debounce)
|
}, debounce)
|
||||||
clearTimeout = () => {
|
clearTimeout = () => {
|
||||||
clearTimeout(timerId)
|
clearTimeout(timerId)
|
||||||
@ -19,11 +23,11 @@ const DebounceValidationField = ({ debounce = 1000, validate, ...rest }: any) =>
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return validate(value, values, fieldState)
|
return validate(url, values, fieldState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Field {...rest} validate={localValidation} />
|
return <Field {...rest} format={trimSpaces} validate={localValidation} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DebounceValidationField
|
export default DebounceValidationField
|
||||||
|
@ -7,7 +7,7 @@ import memoize from 'lodash.memoize'
|
|||||||
type ValidatorReturnType = string | undefined
|
type ValidatorReturnType = string | undefined
|
||||||
type GenericValidatorType = (...args: unknown[]) => ValidatorReturnType
|
type GenericValidatorType = (...args: unknown[]) => ValidatorReturnType
|
||||||
type AsyncValidator = (...args: unknown[]) => Promise<ValidatorReturnType>
|
type AsyncValidator = (...args: unknown[]) => Promise<ValidatorReturnType>
|
||||||
type Validator = GenericValidatorType | AsyncValidator
|
export type Validator = GenericValidatorType | AsyncValidator
|
||||||
|
|
||||||
export const required = (value?: string): ValidatorReturnType => {
|
export const required = (value?: string): ValidatorReturnType => {
|
||||||
const required = 'Required'
|
const required = 'Required'
|
||||||
|
@ -107,7 +107,6 @@ const Details = ({ classes, errors, form }) => {
|
|||||||
<Block className={classes.root} margin="lg">
|
<Block className={classes.root} margin="lg">
|
||||||
<Col xs={11}>
|
<Col xs={11}>
|
||||||
<AddressInput
|
<AddressInput
|
||||||
component={TextField}
|
|
||||||
fieldMutator={(val) => {
|
fieldMutator={(val) => {
|
||||||
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
|
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
|
||||||
}}
|
}}
|
||||||
@ -123,7 +122,6 @@ const Details = ({ classes, errors, form }) => {
|
|||||||
name={FIELD_LOAD_ADDRESS}
|
name={FIELD_LOAD_ADDRESS}
|
||||||
placeholder="Safe Address*"
|
placeholder="Safe Address*"
|
||||||
text="Safe Address"
|
text="Safe Address"
|
||||||
type="text"
|
|
||||||
testId="load-safe-address-field"
|
testId="load-safe-address-field"
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -135,7 +135,6 @@ const SafeOwners = (props) => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col className={classes.ownerAddress} xs={6}>
|
<Col className={classes.ownerAddress} xs={6}>
|
||||||
<AddressInput
|
<AddressInput
|
||||||
component={TextField}
|
|
||||||
fieldMutator={(val) => {
|
fieldMutator={(val) => {
|
||||||
form.mutators.setValue(addressName, val)
|
form.mutators.setValue(addressName, val)
|
||||||
}}
|
}}
|
||||||
@ -151,7 +150,6 @@ const SafeOwners = (props) => {
|
|||||||
name={addressName}
|
name={addressName}
|
||||||
placeholder="Owner Address*"
|
placeholder="Owner Address*"
|
||||||
text="Owner Address"
|
text="Owner Address"
|
||||||
type="text"
|
|
||||||
validators={[getAddressValidator(otherAccounts, index)]}
|
validators={[getAddressValidator(otherAccounts, index)]}
|
||||||
testId={`create-safe-address-field-${index}`}
|
testId={`create-safe-address-field-${index}`}
|
||||||
/>
|
/>
|
||||||
|
@ -5,6 +5,7 @@ import Autocomplete from '@material-ui/lab/Autocomplete'
|
|||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import { trimSpaces } from 'src/utils/strings'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
@ -75,19 +76,20 @@ const AddressBookInput = ({
|
|||||||
|
|
||||||
const [inputAddValue, setInputAddValue] = useState(recipientAddress)
|
const [inputAddValue, setInputAddValue] = useState(recipientAddress)
|
||||||
|
|
||||||
const onAddressInputChanged = async (addressValue: string): Promise<void> => {
|
const onAddressInputChanged = async (value: string): Promise<void> => {
|
||||||
setInputAddValue(addressValue)
|
const normalizedAddress = trimSpaces(value)
|
||||||
let resolvedAddress = addressValue
|
setInputAddValue(normalizedAddress)
|
||||||
|
let resolvedAddress = normalizedAddress
|
||||||
let isValidText
|
let isValidText
|
||||||
if (inputTouched && !addressValue) {
|
if (inputTouched && !normalizedAddress) {
|
||||||
setIsValidForm(false)
|
setIsValidForm(false)
|
||||||
setValidationText('Required')
|
setValidationText('Required')
|
||||||
setIsValidAddress(false)
|
setIsValidAddress(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (addressValue) {
|
if (normalizedAddress) {
|
||||||
if (isValidEnsName(addressValue)) {
|
if (isValidEnsName(normalizedAddress)) {
|
||||||
resolvedAddress = await getAddressFromENS(addressValue)
|
resolvedAddress = await getAddressFromENS(normalizedAddress)
|
||||||
setInputAddValue(resolvedAddress)
|
setInputAddValue(resolvedAddress)
|
||||||
}
|
}
|
||||||
isValidText = mustBeEthereumAddress(resolvedAddress)
|
isValidText = mustBeEthereumAddress(resolvedAddress)
|
||||||
@ -101,13 +103,13 @@ const AddressBookInput = ({
|
|||||||
const filteredADBK = adbkToFilter.filter((adbkEntry) => {
|
const filteredADBK = adbkToFilter.filter((adbkEntry) => {
|
||||||
const { address, name } = adbkEntry
|
const { address, name } = adbkEntry
|
||||||
return (
|
return (
|
||||||
name.toLowerCase().includes(addressValue.toLowerCase()) ||
|
name.toLowerCase().includes(normalizedAddress.toLowerCase()) ||
|
||||||
address.toLowerCase().includes(addressValue.toLowerCase())
|
address.toLowerCase().includes(normalizedAddress.toLowerCase())
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
setADBKList(filteredADBK)
|
setADBKList(filteredADBK)
|
||||||
if (!isValidText) {
|
if (!isValidText) {
|
||||||
setSelectedEntry({ address: addressValue })
|
setSelectedEntry({ address: normalizedAddress })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setIsValidForm(isValidText === undefined)
|
setIsValidForm(isValidText === undefined)
|
||||||
|
@ -48,7 +48,7 @@ const formMutators = {
|
|||||||
|
|
||||||
const useStyles = makeStyles(styles as any)
|
const useStyles = makeStyles(styles as any)
|
||||||
|
|
||||||
const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedToken = '' }) => {
|
const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedToken = '' }): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const tokens = useSelector(extendedSafeTokensSelector)
|
const tokens = useSelector(extendedSafeTokensSelector)
|
||||||
const addressBook = useSelector(getAddressBook)
|
const addressBook = useSelector(getAddressBook)
|
||||||
|
@ -9,7 +9,8 @@ import { getSymbolAndDecimalsFromContract } from './utils'
|
|||||||
import Field from 'src/components/forms/Field'
|
import Field from 'src/components/forms/Field'
|
||||||
import GnoForm from 'src/components/forms/GnoForm'
|
import GnoForm from 'src/components/forms/GnoForm'
|
||||||
import TextField from 'src/components/forms/TextField'
|
import TextField from 'src/components/forms/TextField'
|
||||||
import { composeValidators, minMaxLength, mustBeEthereumAddress, required } from 'src/components/forms/validator'
|
import AddressInput from 'src/components/forms/AddressInput'
|
||||||
|
import { composeValidators, minMaxLength, required } from 'src/components/forms/validator'
|
||||||
import Block from 'src/components/layout/Block'
|
import Block from 'src/components/layout/Block'
|
||||||
import Button from 'src/components/layout/Button'
|
import Button from 'src/components/layout/Button'
|
||||||
import Col from 'src/components/layout/Col'
|
import Col from 'src/components/layout/Col'
|
||||||
@ -80,93 +81,102 @@ const AddCustomAsset = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formMutators = {
|
||||||
|
setAssetAddress: (args, state, utils) => {
|
||||||
|
utils.changeValue(state, 'address', () => args[0])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
setActiveScreen(parentList)
|
setActiveScreen(parentList)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GnoForm initialValues={formValues} onSubmit={handleSubmit} testId={ADD_CUSTOM_ASSET_FORM}>
|
<GnoForm
|
||||||
{() => (
|
initialValues={formValues}
|
||||||
<>
|
onSubmit={handleSubmit}
|
||||||
<Block className={classes.formContainer}>
|
formMutators={formMutators}
|
||||||
<Paragraph className={classes.title} noMargin size="lg" weight="bolder">
|
testId={ADD_CUSTOM_ASSET_FORM}
|
||||||
Add custom asset
|
>
|
||||||
</Paragraph>
|
{(...args) => {
|
||||||
<Field
|
const mutators = args[3]
|
||||||
className={classes.addressInput}
|
|
||||||
component={TextField}
|
return (
|
||||||
name="address"
|
<>
|
||||||
placeholder="Asset contract address*"
|
<Block className={classes.formContainer}>
|
||||||
testId={ADD_CUSTOM_ASSET_ADDRESS_INPUT_TEST_ID}
|
<Paragraph className={classes.title} noMargin size="lg" weight="bolder">
|
||||||
text="Token contract address*"
|
Add custom asset
|
||||||
type="text"
|
</Paragraph>
|
||||||
validate={composeValidators(
|
<AddressInput
|
||||||
required,
|
fieldMutator={mutators.setAssetAddress}
|
||||||
mustBeEthereumAddress,
|
className={classes.addressInput}
|
||||||
doesntExistInAssetsList(nftAssetsList),
|
name="address"
|
||||||
addressIsAssetContract,
|
placeholder="Asset contract address*"
|
||||||
)}
|
testId={ADD_CUSTOM_ASSET_ADDRESS_INPUT_TEST_ID}
|
||||||
/>
|
text="Asset contract address*"
|
||||||
<FormSpy
|
validators={[doesntExistInAssetsList(nftAssetsList), addressIsAssetContract]}
|
||||||
onChange={formSpyOnChangeHandler}
|
/>
|
||||||
subscription={{
|
<FormSpy
|
||||||
values: true,
|
onChange={formSpyOnChangeHandler}
|
||||||
errors: true,
|
subscription={{
|
||||||
validating: true,
|
values: true,
|
||||||
dirty: true,
|
errors: true,
|
||||||
submitSucceeded: true,
|
validating: true,
|
||||||
}}
|
dirty: true,
|
||||||
/>
|
submitSucceeded: true,
|
||||||
<Row>
|
}}
|
||||||
<Col layout="column" xs={6}>
|
/>
|
||||||
<Field
|
<Row>
|
||||||
className={classes.addressInput}
|
<Col layout="column" xs={6}>
|
||||||
component={TextField}
|
|
||||||
name="symbol"
|
|
||||||
placeholder="Token symbol*"
|
|
||||||
testId={ADD_CUSTOM_ASSET_SYMBOLS_INPUT_TEST_ID}
|
|
||||||
text="Token symbol"
|
|
||||||
type="text"
|
|
||||||
validate={composeValidators(required, minMaxLength(2, 12))}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
className={classes.addressInput}
|
|
||||||
component={TextField}
|
|
||||||
disabled
|
|
||||||
name="decimals"
|
|
||||||
placeholder="Token decimals*"
|
|
||||||
testId={ADD_CUSTOM_ASSET_DECIMALS_INPUT_TEST_ID}
|
|
||||||
text="Token decimals*"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<Block justify="center">
|
|
||||||
<Field
|
<Field
|
||||||
className={classes.checkbox}
|
className={classes.addressInput}
|
||||||
component={Checkbox}
|
component={TextField}
|
||||||
name="showForAllSafes"
|
name="symbol"
|
||||||
type="checkbox"
|
placeholder="Token symbol*"
|
||||||
label="Activate assets for all Safes"
|
testId={ADD_CUSTOM_ASSET_SYMBOLS_INPUT_TEST_ID}
|
||||||
|
text="Token symbol"
|
||||||
|
type="text"
|
||||||
|
validate={composeValidators(required, minMaxLength(2, 12))}
|
||||||
/>
|
/>
|
||||||
</Block>
|
<Field
|
||||||
</Col>
|
className={classes.addressInput}
|
||||||
<Col align="center" layout="column" xs={6}>
|
component={TextField}
|
||||||
<Paragraph className={classes.tokenImageHeading}>Token Image</Paragraph>
|
disabled
|
||||||
<Img alt="Token image" height={100} src={TokenPlaceholder} />
|
name="decimals"
|
||||||
</Col>
|
placeholder="Token decimals*"
|
||||||
|
testId={ADD_CUSTOM_ASSET_DECIMALS_INPUT_TEST_ID}
|
||||||
|
text="Token decimals*"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<Block justify="center">
|
||||||
|
<Field
|
||||||
|
className={classes.checkbox}
|
||||||
|
component={Checkbox}
|
||||||
|
name="showForAllSafes"
|
||||||
|
type="checkbox"
|
||||||
|
label="Activate assets for all Safes"
|
||||||
|
/>
|
||||||
|
</Block>
|
||||||
|
</Col>
|
||||||
|
<Col align="center" layout="column" xs={6}>
|
||||||
|
<Paragraph className={classes.tokenImageHeading}>Token Image</Paragraph>
|
||||||
|
<Img alt="Token image" height={100} src={TokenPlaceholder} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Block>
|
||||||
|
<Hairline />
|
||||||
|
<Row align="center" className={classes.buttonRow}>
|
||||||
|
<Button minHeight={42} minWidth={140} onClick={goBack}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" minHeight={42} minWidth={140} type="submit" variant="contained">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</>
|
||||||
<Hairline />
|
)
|
||||||
<Row align="center" className={classes.buttonRow}>
|
}}
|
||||||
<Button minHeight={42} minWidth={140} onClick={goBack}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button color="primary" minHeight={42} minWidth={140} type="submit" variant="contained">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</GnoForm>
|
</GnoForm>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,8 @@ import { addressIsTokenContract, doesntExistInTokenList } from './validators'
|
|||||||
import Field from 'src/components/forms/Field'
|
import Field from 'src/components/forms/Field'
|
||||||
import GnoForm from 'src/components/forms/GnoForm'
|
import GnoForm from 'src/components/forms/GnoForm'
|
||||||
import TextField from 'src/components/forms/TextField'
|
import TextField from 'src/components/forms/TextField'
|
||||||
import { composeValidators, minMaxLength, mustBeEthereumAddress, required } from 'src/components/forms/validator'
|
import AddressInput from 'src/components/forms/AddressInput'
|
||||||
|
import { composeValidators, minMaxLength, required } from 'src/components/forms/validator'
|
||||||
import Block from 'src/components/layout/Block'
|
import Block from 'src/components/layout/Block'
|
||||||
import Button from 'src/components/layout/Button'
|
import Button from 'src/components/layout/Button'
|
||||||
import Col from 'src/components/layout/Col'
|
import Col from 'src/components/layout/Col'
|
||||||
@ -101,93 +102,102 @@ const AddCustomToken = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formMutators = {
|
||||||
|
setTokenAddress: (args, state, utils) => {
|
||||||
|
utils.changeValue(state, 'address', () => args[0])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
setActiveScreen(parentList)
|
setActiveScreen(parentList)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GnoForm initialValues={formValues} onSubmit={handleSubmit} testId={ADD_CUSTOM_TOKEN_FORM}>
|
<GnoForm
|
||||||
{() => (
|
initialValues={formValues}
|
||||||
<>
|
onSubmit={handleSubmit}
|
||||||
<Block className={classes.formContainer}>
|
formMutators={formMutators}
|
||||||
<Paragraph className={classes.title} noMargin size="lg" weight="bolder">
|
testId={ADD_CUSTOM_TOKEN_FORM}
|
||||||
Add custom token
|
>
|
||||||
</Paragraph>
|
{(...args) => {
|
||||||
<Field
|
const mutators = args[3]
|
||||||
className={classes.addressInput}
|
|
||||||
component={TextField}
|
return (
|
||||||
name="address"
|
<>
|
||||||
placeholder="Token contract address*"
|
<Block className={classes.formContainer}>
|
||||||
testId={ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID}
|
<Paragraph className={classes.title} noMargin size="lg" weight="bolder">
|
||||||
text="Token contract address*"
|
Add custom token
|
||||||
type="text"
|
</Paragraph>
|
||||||
validate={composeValidators(
|
<AddressInput
|
||||||
required,
|
fieldMutator={mutators.setTokenAddress}
|
||||||
mustBeEthereumAddress,
|
className={classes.addressInput}
|
||||||
doesntExistInTokenList(tokens),
|
name="address"
|
||||||
addressIsTokenContract,
|
placeholder="Token contract address*"
|
||||||
)}
|
testId={ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID}
|
||||||
/>
|
text="Token contract address*"
|
||||||
<FormSpy
|
validators={[doesntExistInTokenList(tokens), addressIsTokenContract]}
|
||||||
onChange={formSpyOnChangeHandler}
|
/>
|
||||||
subscription={{
|
<FormSpy
|
||||||
values: true,
|
onChange={formSpyOnChangeHandler}
|
||||||
errors: true,
|
subscription={{
|
||||||
validating: true,
|
values: true,
|
||||||
dirty: true,
|
errors: true,
|
||||||
submitSucceeded: true,
|
validating: true,
|
||||||
}}
|
dirty: true,
|
||||||
/>
|
submitSucceeded: true,
|
||||||
<Row>
|
}}
|
||||||
<Col layout="column" xs={6}>
|
/>
|
||||||
<Field
|
<Row>
|
||||||
className={classes.addressInput}
|
<Col layout="column" xs={6}>
|
||||||
component={TextField}
|
|
||||||
name="symbol"
|
|
||||||
placeholder="Token symbol*"
|
|
||||||
testId={ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID}
|
|
||||||
text="Token symbol"
|
|
||||||
type="text"
|
|
||||||
validate={composeValidators(required, minMaxLength(2, 12))}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
className={classes.addressInput}
|
|
||||||
component={TextField}
|
|
||||||
disabled
|
|
||||||
name="decimals"
|
|
||||||
placeholder="Token decimals*"
|
|
||||||
testId={ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID}
|
|
||||||
text="Token decimals*"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<Block justify="center">
|
|
||||||
<Field
|
<Field
|
||||||
className={classes.checkbox}
|
className={classes.addressInput}
|
||||||
component={Checkbox}
|
component={TextField}
|
||||||
name="showForAllSafes"
|
name="symbol"
|
||||||
type="checkbox"
|
placeholder="Token symbol*"
|
||||||
label="Activate token for all Safes"
|
testId={ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID}
|
||||||
|
text="Token symbol"
|
||||||
|
type="text"
|
||||||
|
validate={composeValidators(required, minMaxLength(2, 12))}
|
||||||
/>
|
/>
|
||||||
</Block>
|
<Field
|
||||||
</Col>
|
className={classes.addressInput}
|
||||||
<Col align="center" layout="column" xs={6}>
|
component={TextField}
|
||||||
<Paragraph className={classes.tokenImageHeading}>Token Image</Paragraph>
|
disabled
|
||||||
<Img alt="Token image" height={100} src={TokenPlaceholder} />
|
name="decimals"
|
||||||
</Col>
|
placeholder="Token decimals*"
|
||||||
|
testId={ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID}
|
||||||
|
text="Token decimals*"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<Block justify="center">
|
||||||
|
<Field
|
||||||
|
className={classes.checkbox}
|
||||||
|
component={Checkbox}
|
||||||
|
name="showForAllSafes"
|
||||||
|
type="checkbox"
|
||||||
|
label="Activate token for all Safes"
|
||||||
|
/>
|
||||||
|
</Block>
|
||||||
|
</Col>
|
||||||
|
<Col align="center" layout="column" xs={6}>
|
||||||
|
<Paragraph className={classes.tokenImageHeading}>Token Image</Paragraph>
|
||||||
|
<Img alt="Token image" height={100} src={TokenPlaceholder} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Block>
|
||||||
|
<Hairline />
|
||||||
|
<Row align="center" className={classes.buttonRow}>
|
||||||
|
<Button minHeight={42} minWidth={140} onClick={goBack}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" minHeight={42} minWidth={140} type="submit" variant="contained">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</>
|
||||||
<Hairline />
|
)
|
||||||
<Row align="center" className={classes.buttonRow}>
|
}}
|
||||||
<Button minHeight={42} minWidth={140} onClick={goBack}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button color="primary" minHeight={42} minWidth={140} type="submit" variant="contained">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</GnoForm>
|
</GnoForm>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -120,7 +120,6 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => {
|
|||||||
<Col xs={8}>
|
<Col xs={8}>
|
||||||
<AddressInput
|
<AddressInput
|
||||||
className={classes.addressInput}
|
className={classes.addressInput}
|
||||||
component={TextField}
|
|
||||||
fieldMutator={mutators.setOwnerAddress}
|
fieldMutator={mutators.setOwnerAddress}
|
||||||
name="ownerAddress"
|
name="ownerAddress"
|
||||||
placeholder="Owner address*"
|
placeholder="Owner address*"
|
||||||
|
@ -45,3 +45,10 @@ export const textShortener = ({ charsEnd = 10, charsStart = 10, ellipsis = '...'
|
|||||||
|
|
||||||
return `${textStart}${ellipsis}${textEnd}`
|
return `${textStart}${ellipsis}${textEnd}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util to remove whitespace from both sides of a string.
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string} string without side whitespaces
|
||||||
|
*/
|
||||||
|
export const trimSpaces = (value: string): string => (value === undefined ? '' : value.trim())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user