Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

Feature #388: Use adbk list in new transactions #461

Merged
merged 29 commits into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1ddd174
Development (#378)
germartinez Dec 18, 2019
34aacb7
Merge branch 'development' of https://github.com/gnosis/safe-react
Agupane Dec 18, 2019
c4f39a4
Merge branch 'development' of https://github.com/gnosis/safe-react in…
Agupane Dec 26, 2019
fdf4e48
Merge branch 'development' of https://github.com/gnosis/safe-react in…
Agupane Jan 15, 2020
7e7980d
Adds material ui lab
Agupane Jan 16, 2020
b13d59a
Add styling to inputs
Agupane Jan 17, 2020
f01b6b7
Fix missing field mutator
Agupane Jan 17, 2020
815abb2
Fixs validation of eth and contract address
Agupane Jan 20, 2020
cf87487
Fixs InputProps
Agupane Jan 20, 2020
7fd58ab
Fixs default value for input
Agupane Jan 20, 2020
27255d7
Fixs value with defaultValue
Agupane Jan 20, 2020
2c57b90
Ignorecase for search by name or by address
Agupane Jan 21, 2020
30cac63
Fix send funds
Agupane Jan 21, 2020
ec25d17
Fix font size on input
Agupane Jan 21, 2020
0667ecf
Adds eth display on item selected
Agupane Jan 21, 2020
218cd80
Fix no name displayed
Agupane Jan 22, 2020
5628efe
Adds keyboard control to autocomplete
Agupane Jan 24, 2020
308018a
Merge branch 'development' of https://github.com/gnosis/safe-react in…
Agupane Jan 24, 2020
1538ca2
fix a11y issue with recipient, fix typo
mmv08 Jan 24, 2020
1c2e03f
Fix also the key enters for custom transactions
Agupane Jan 27, 2020
3ff504a
Fixs null gas price
Agupane Jan 27, 2020
c92869d
Fixs selected entry empty check
Agupane Jan 29, 2020
8c15d41
Add pristine state to AddressBookInput
fernandomg Jan 29, 2020
90b10b4
Autofocus address book input
fernandomg Jan 30, 2020
e1bea57
Fix is valid address check
Agupane Jan 30, 2020
62be514
Fixs ens address validation
Agupane Jan 31, 2020
718e878
Fix copy address button closes list
Agupane Jan 31, 2020
dea7123
Fixes wrong verbiage:
Agupane Jan 31, 2020
77a857b
Fixes wrong verbiage:
Agupane Jan 31, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@gnosis.pm/util-contracts": "2.0.4",
"@material-ui/core": "4.8.0",
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "^4.0.0-alpha.39",
"@portis/web3": "^2.0.0-beta.45",
"@testing-library/jest-dom": "4.2.4",
"@toruslabs/torus-embed": "0.2.10",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// @flow
import React, { useState, useEffect } from 'react'
import {
withStyles,
} from '@material-ui/core/styles'
import { useSelector } from 'react-redux'
import Autocomplete from '@material-ui/lab/Autocomplete'
import TextField from '@material-ui/core/TextField'
import makeStyles from '@material-ui/core/styles/makeStyles'
import { styles } from './style'
import { getAddressBookListSelector } from '~/logic/addressBook/store/selectors'
import { mustBeEthereumAddress, mustBeEthereumContractAddress } from '~/components/forms/validator'
import Identicon from '~/components/Identicon'


type Props = {
classes: Object,
fieldMutator: Function,
setSelectedEntry: Function,
isCustomTx?: boolean,
recipientAddress?: string,
pristine: boolean,
}


const textFieldLabelStyle = makeStyles(() => ({
root: {
overflow: 'hidden',
borderRadius: 4,
fontSize: '15px',
width: '500px',
},
}))

const textFieldInputStyle = makeStyles(() => ({
root: {
fontSize: '14px',
width: '420px',
},
}))

const AddressBookInput = ({
classes, fieldMutator, isCustomTx, recipientAddress, setSelectedEntry, pristine,
}: Props) => {
const addressBook = useSelector(getAddressBookListSelector)
const [addressInput, setAddressInput] = useState(recipientAddress)
const [isValidForm, setIsValidForm] = useState(true)
const [validationText, setValidationText] = useState(true)
const [inputTouched, setInputTouched] = useState(false)
const [blurred, setBlurred] = useState(pristine)

useEffect(() => {
const validate = async () => {
if (inputTouched && !addressInput) {
setIsValidForm(false)
setValidationText('Required')
} else if (addressInput) {
let isValidText = mustBeEthereumAddress(addressInput)
if (isCustomTx && isValidText === undefined) {
isValidText = await mustBeEthereumContractAddress(addressInput)
}
setIsValidForm(isValidText === undefined)
setValidationText(isValidText)
fieldMutator(addressInput)
}
}
validate()
}, [addressInput])

const labelStyling = textFieldLabelStyle()
const txInputStyling = textFieldInputStyle()

return (
<>
<Autocomplete
id="free-solo-demo"
freeSolo
open={!blurred}
onClose={() => setBlurred(true)}
role="listbox"
options={addressBook.toArray()}
style={{ display: 'flex', flexGrow: 1 }}
closeIcon={null}
filterOptions={(optionsArray, { inputValue }) => optionsArray.filter((item) => {
const inputLowerCase = inputValue.toLowerCase()
const foundName = item.name.toLowerCase()
.includes(inputLowerCase)
const foundAddress = item.address.toLowerCase().includes(inputLowerCase)
return foundName || foundAddress
})}
getOptionLabel={(adbkEntry) => adbkEntry.address || ''}
onOpen={() => {
setSelectedEntry(null)
setBlurred(false)
}}
defaultValue={{ address: recipientAddress }}
onChange={(event, value) => {
let address = ''
let name = ''
if (value) {
address = value.address
name = value.name
}
setAddressInput(address)
setSelectedEntry({ address, name })
fieldMutator(address)
}}
renderOption={(adbkEntry) => {
const { name, address } = adbkEntry
return (
<div className={classes.itemOptionList}>
<div className={classes.identicon}>
<Identicon address={address} diameter={32} />
</div>
<div className={classes.adbkEntryName}>
<span>{name}</span>
<span>{address}</span>
</div>
</div>
)
}}
renderInput={(params) => (
<TextField
{...params}
label={!isValidForm ? validationText : 'Recipient'}
error={!isValidForm}
fullWidth
autoFocus={!blurred}
variant="filled"
id="filled-error-helper-text"
onChange={(event) => {
setInputTouched(true)
setAddressInput(event.target.value)
}}
InputProps={
{
...params.InputProps,
classes: {
...txInputStyling,
},
}
}
InputLabelProps={{
shrink: true,
required: true,
classes: labelStyling,
}}
/>
)}
/>
</>
)
}

export default withStyles(styles)(AddressBookInput)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @flow

export const styles = () => ({
itemOptionList: {
display: 'flex',
},

Agupane marked this conversation as resolved.
Show resolved Hide resolved
adbkEntryName: {
display: 'flex',
flexDirection: 'column',
fontSize: '14px',
},
identicon: {
display: 'flex',
padding: '5px',
flexDirection: 'column',
justifyContent: 'center',
},

root: {
fontSize: '14px',
backgroundColor: 'red',
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import IconButton from '@material-ui/core/IconButton'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import GnoForm from '~/components/forms/GnoForm'
import AddressInput from '~/components/forms/AddressInput'
import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button'
import ScanQRModal from '~/components/ScanQRModal'
Expand All @@ -19,13 +18,18 @@ import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import TextareaField from '~/components/forms/TextareaField'
import {
composeValidators, mustBeFloat, maxValue, mustBeEthereumContractAddress,
composeValidators, mustBeFloat, maxValue,
} from '~/components/forms/validator'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import QRIcon from '~/assets/icons/qrcode.svg'
import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style'
import { sm } from '~/theme/variables'
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
import Identicon from '~/components/Identicon'
import CopyBtn from '~/components/CopyBtn'
import EtherscanBtn from '~/components/EtherscanBtn'


type Props = {
onClose: () => void,
Expand All @@ -47,6 +51,18 @@ const SendCustomTx = ({
initialValues,
}: Props) => {
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
address: '',
name: '',
})
const [pristine, setPristine] = useState<boolean>(true)

React.useMemo(() => {
if (selectedEntry === null && pristine) {
setPristine(false)
}
}, [selectedEntry, pristine])

const handleSubmit = (values: Object) => {
if (values.data || values.value) {
onSubmit(values)
Expand Down Expand Up @@ -109,31 +125,68 @@ const SendCustomTx = ({
<Hairline />
</Col>
</Row>
<Row margin="md">
<Col xs={11}>
<AddressInput
name="recipientAddress"
component={TextField}
placeholder="Recipient*"
text="Recipient*"
className={classes.addressInput}
fieldMutator={mutators.setRecipient}
validators={[mustBeEthereumContractAddress]}
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes}>
<Img
src={QRIcon}
className={classes.qrCodeBtn}
role="button"
height={20}
alt="Scan QR"
onClick={() => {
openQrModal()
}}
/>
</Col>
</Row>
{ selectedEntry && selectedEntry.address ? (
<div
role="listbox"
tabIndex="0"
onClick={() => setSelectedEntry(null)}
onKeyDown={(e) => {
if (e.keyCode !== 9) {
setSelectedEntry(null)
}
}}
>
<Row margin="xs">
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
Recipient
</Paragraph>
</Row>
<Row margin="md" align="center">
<Col xs={1}>
<Identicon address={selectedEntry.address} diameter={32} />
</Col>
<Col xs={11} layout="column">
<Block justify="left">
<Block>
<Paragraph weight="bolder" className={classes.address} noMargin onClick={() => setSelectedEntry(null)}>
{selectedEntry.name}
</Paragraph>
<Paragraph weight="bolder" className={classes.address} noMargin onClick={() => setSelectedEntry(null)}>
{selectedEntry.address}
</Paragraph>
</Block>
<CopyBtn content={selectedEntry.address} />
<EtherscanBtn type="address" value={selectedEntry.address} />
</Block>
</Col>
</Row>
</div>
) : (
<>
<Row margin="md">
<Col xs={11}>
<AddressBookInput
pristine={pristine}
fieldMutator={mutators.setRecipient}
setSelectedEntry={setSelectedEntry}
isCustomTx
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes}>
<Img
src={QRIcon}
className={classes.qrCodeBtn}
role="button"
height={20}
alt="Scan QR"
onClick={() => {
openQrModal()
}}
/>
</Col>
</Row>
</>
)}
<Row margin="xs">
<Col between="lg">
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
Expand Down Expand Up @@ -183,6 +236,7 @@ const SendCustomTx = ({
color="primary"
data-testid="review-tx-btn"
className={classes.submitButton}
disabled={!selectedEntry || !selectedEntry.address}
>
Review
</Button>
Expand Down
Loading