(Fix) #511 - QR scan button (#873)

* Creates ScanQRWrapper to avoid duplicated logic
Refactors components that uses ScanQRWrapper

* Adds closeQrModal to props.handleScan callback

* Fixs mutators usage on components with qrScanWrapper

* Exports getNameFromAdbk
Fixs displaying address on send funds, also displays the name

* Fixs sendCustomTx qrCode
Fixs sendCollectible qrCode
Fixs loadAddress qrCode
This commit is contained in:
Agustin Pane 2020-05-08 17:09:49 -03:00 committed by GitHub
parent 8bc5de3246
commit 4098a8b9cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 233 additions and 156 deletions

View File

@ -0,0 +1,51 @@
// @flow
import { makeStyles } from '@material-ui/core/styles'
import { useState } from 'react'
import * as React from 'react'
import QRIcon from '~/assets/icons/qrcode.svg'
import ScanQRModal from '~/components/ScanQRModal'
import Img from '~/components/layout/Img'
type Props = {
handleScan: Function,
}
const useStyles = makeStyles({
qrCodeBtn: {
cursor: 'pointer',
},
})
export const ScanQRWrapper = (props: Props) => {
const classes = useStyles()
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const openQrModal = () => {
setQrModalOpen(true)
}
const closeQrModal = () => {
setQrModalOpen(false)
}
const onScanFinished = (value) => {
props.handleScan(value, closeQrModal)
}
return (
<>
<Img
alt="Scan QR"
className={classes.qrCodeBtn}
height={20}
onClick={() => {
openQrModal()
}}
role="button"
src={QRIcon}
/>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={onScanFinished} />}
</>
)
}

View File

@ -25,7 +25,7 @@ export const saveAddressBook = async (addressBook: AddressBook) => {
export const getAddressesListFromAdbk = (addressBook: AddressBook) => export const getAddressesListFromAdbk = (addressBook: AddressBook) =>
Array.from(addressBook).map((entry) => entry.address) Array.from(addressBook).map((entry) => entry.address)
const getNameFromAdbk = (addressBook: AddressBook, userAddress: string): string | null => { export const getNameFromAdbk = (addressBook: AddressBook, userAddress: string): string | null => {
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress) const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
if (entry) { if (entry) {
return entry.name return entry.name

View File

@ -4,12 +4,14 @@ import { withStyles } from '@material-ui/core/styles'
import CheckCircle from '@material-ui/icons/CheckCircle' import CheckCircle from '@material-ui/icons/CheckCircle'
import * as React from 'react' import * as React from 'react'
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import OpenPaper from '~/components/Stepper/OpenPaper' import OpenPaper from '~/components/Stepper/OpenPaper'
import AddressInput from '~/components/forms/AddressInput' import AddressInput from '~/components/forms/AddressInput'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
import { mustBeEthereumAddress, noErrorsOn, required } from '~/components/forms/validator' import { mustBeEthereumAddress, noErrorsOn, required } from '~/components/forms/validator'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import { SAFE_MASTER_COPY_ADDRESS_V10, getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts' import { SAFE_MASTER_COPY_ADDRESS_V10, getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
@ -80,64 +82,77 @@ export const safeFieldsValidation = async (values: Object) => {
return errors return errors
} }
const Details = ({ classes, errors, form }: Props) => ( const Details = ({ classes, errors, form }: Props) => {
<> const handleScan = (value, closeQrModal) => {
<Block margin="md"> form.mutators.setValue(FIELD_LOAD_ADDRESS, value)
<Paragraph color="primary" noMargin size="md"> closeQrModal()
You are about to load an existing Gnosis Safe. First, choose a name and enter the Safe address. The name is only }
stored locally and will never be shared with Gnosis or any third parties. return (
<br /> <>
Your connected wallet does not have to be the owner of this Safe. In this case, the interface will provide you a <Block margin="md">
read-only view. <Paragraph color="primary" noMargin size="md">
</Paragraph> You are about to load an existing Gnosis Safe. First, choose a name and enter the Safe address. The name is
</Block> only stored locally and will never be shared with Gnosis or any third parties.
<Block className={classes.root}> <br />
<Field Your connected wallet does not have to be the owner of this Safe. In this case, the interface will provide you
component={TextField} a read-only view.
name={FIELD_LOAD_NAME} </Paragraph>
placeholder="Name of the Safe" </Block>
text="Safe name" <Block className={classes.root}>
type="text" <Col xs={11}>
validate={required} <Field
/> component={TextField}
</Block> name={FIELD_LOAD_NAME}
<Block className={classes.root} margin="lg"> placeholder="Name of the Safe"
<AddressInput text="Safe name"
component={TextField} type="text"
fieldMutator={(val) => { validate={required}
form.mutators.setValue(FIELD_LOAD_ADDRESS, val) />
}} </Col>
inputAdornment={ </Block>
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && { <Block className={classes.root} margin="lg">
endAdornment: ( <Col xs={11}>
<InputAdornment position="end"> <AddressInput
<CheckCircle className={classes.check} /> component={TextField}
</InputAdornment> fieldMutator={(val) => {
), form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
} }}
} inputAdornment={
name={FIELD_LOAD_ADDRESS} noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
placeholder="Safe Address*" endAdornment: (
text="Safe Address" <InputAdornment position="end">
type="text" <CheckCircle className={classes.check} />
/> </InputAdornment>
</Block> ),
<Block margin="sm"> }
<Paragraph className={classes.links} color="primary" noMargin size="md"> }
By continuing you consent with the{' '} name={FIELD_LOAD_ADDRESS}
<a href="https://safe.gnosis.io/terms" rel="noopener noreferrer" target="_blank"> placeholder="Safe Address*"
terms of use text="Safe Address"
</a>{' '} type="text"
and{' '} />
<a href="https://safe.gnosis.io/privacy" rel="noopener noreferrer" target="_blank"> </Col>
privacy policy <Col center="xs" className={classes} middle="xs" xs={1}>
</a> <ScanQRWrapper handleScan={handleScan} />
. Most importantly, you confirm that your funds are held securely in the Gnosis Safe, a smart contract on the </Col>
Ethereum blockchain. These funds cannot be accessed by Gnosis at any point. </Block>
</Paragraph> <Block margin="sm">
</Block> <Paragraph className={classes.links} color="primary" noMargin size="md">
</> By continuing you consent with the{' '}
) <a href="https://safe.gnosis.io/terms" rel="noopener noreferrer" target="_blank">
terms of use
</a>{' '}
and{' '}
<a href="https://safe.gnosis.io/privacy" rel="noopener noreferrer" target="_blank">
privacy policy
</a>
. Most importantly, you confirm that your funds are held securely in the Gnosis Safe, a smart contract on the
Ethereum blockchain. These funds cannot be accessed by Gnosis at any point.
</Paragraph>
</Block>
</>
)
}
const DetailsForm = withStyles(styles)(Details) const DetailsForm = withStyles(styles)(Details)

View File

@ -8,6 +8,7 @@ import { useSelector } from 'react-redux'
import { styles } from './style' import { styles } from './style'
import Modal from '~/components/Modal' import Modal from '~/components/Modal'
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import AddressInput from '~/components/forms/AddressInput' import AddressInput from '~/components/forms/AddressInput'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
@ -15,6 +16,7 @@ import TextField from '~/components/forms/TextField'
import { composeValidators, minMaxLength, required, uniqueAddress } from '~/components/forms/validator' import { composeValidators, minMaxLength, required, uniqueAddress } from '~/components/forms/validator'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
import Col from '~/components/layout/Col'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
@ -81,34 +83,53 @@ const CreateEditEntryModalComponent = ({
<GnoForm formMutators={formMutators} onSubmit={onFormSubmitted}> <GnoForm formMutators={formMutators} onSubmit={onFormSubmitted}>
{(...args) => { {(...args) => {
const mutators = args[3] const mutators = args[3]
const handleScan = (value, closeQrModal) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
mutators.setOwnerAddress(scannedAddress)
closeQrModal()
}
return ( return (
<> <>
<Block className={classes.container}> <Block className={classes.container}>
<Row margin="md"> <Row margin="md">
<Field <Col xs={11}>
className={classes.addressInput} <Field
component={TextField} className={classes.addressInput}
defaultValue={entryToEdit ? entryToEdit.entry.name : undefined} component={TextField}
name="name" defaultValue={entryToEdit ? entryToEdit.entry.name : undefined}
placeholder={entryToEdit ? 'Entry name' : 'New entry'} name="name"
testId={CREATE_ENTRY_INPUT_NAME_ID} placeholder={entryToEdit ? 'Entry name' : 'New entry'}
text={entryToEdit ? 'Entry*' : 'New entry*'} testId={CREATE_ENTRY_INPUT_NAME_ID}
type="text" text={entryToEdit ? 'Entry*' : 'New entry*'}
validate={composeValidators(required, minMaxLength(1, 50))} type="text"
/> validate={composeValidators(required, minMaxLength(1, 50))}
/>
</Col>
</Row> </Row>
<Row margin="md"> <Row margin="md">
<AddressInput <Col xs={11}>
className={classes.addressInput} <AddressInput
defaultValue={entryToEdit ? entryToEdit.entry.address : undefined} className={classes.addressInput}
disabled={!!entryToEdit} defaultValue={entryToEdit ? entryToEdit.entry.address : undefined}
fieldMutator={mutators.setOwnerAddress} disabled={!!entryToEdit}
name="address" fieldMutator={mutators.setOwnerAddress}
placeholder="Owner address*" name="address"
testId={CREATE_ENTRY_INPUT_ADDRESS_ID} placeholder="Owner address*"
text="Owner address*" testId={CREATE_ENTRY_INPUT_ADDRESS_ID}
validators={entryToEdit ? undefined : [entryDoesntExist]} text="Owner address*"
/> validators={entryToEdit ? undefined : [entryDoesntExist]}
/>
</Col>
{!entryToEdit ? (
<Col center="xs" className={classes} middle="xs" xs={1}>
<ScanQRWrapper handleScan={handleScan} />
</Col>
) : null}
</Row> </Row>
</Block> </Block>
<Hairline /> <Hairline />

View File

@ -9,20 +9,21 @@ import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style' import { styles } from './style'
import QRIcon from '~/assets/icons/qrcode.svg'
import CopyBtn from '~/components/CopyBtn' import CopyBtn from '~/components/CopyBtn'
import EtherscanBtn from '~/components/EtherscanBtn' import EtherscanBtn from '~/components/EtherscanBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import ScanQRModal from '~/components/ScanQRModal' import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import WhenFieldChanges from '~/components/WhenFieldChanges' import WhenFieldChanges from '~/components/WhenFieldChanges'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import Img from '~/components/layout/Img'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
import { getAddressBook } from '~/logic/addressBook/store/selectors'
import { getNameFromAdbk } from '~/logic/addressBook/utils'
import type { NFTAssetsState, NFTTokensState } from '~/logic/collectibles/store/reducer/collectibles' import type { NFTAssetsState, NFTTokensState } from '~/logic/collectibles/store/reducer/collectibles'
import { nftTokensSelector, safeActiveSelectorMap } from '~/logic/collectibles/store/selectors' import { nftTokensSelector, safeActiveSelectorMap } from '~/logic/collectibles/store/selectors'
import type { NFTToken } from '~/routes/safe/components/Balances/Collectibles/types' import type { NFTToken } from '~/routes/safe/components/Balances/Collectibles/types'
@ -60,7 +61,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector) const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const nftAssets: NFTAssetsState = useSelector(safeActiveSelectorMap) const nftAssets: NFTAssetsState = useSelector(safeActiveSelectorMap)
const nftTokens: NFTTokensState = useSelector(nftTokensSelector) const nftTokens: NFTTokensState = useSelector(nftTokensSelector)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false) const addressBook: AddressBook = useSelector(getAddressBook)
const [selectedEntry, setSelectedEntry] = useState<Object | null>({ const [selectedEntry, setSelectedEntry] = useState<Object | null>({
address: recipientAddress || initialValues.recipientAddress, address: recipientAddress || initialValues.recipientAddress,
name: '', name: '',
@ -85,14 +86,6 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
onNext(values) onNext(values)
} }
const openQrModal = () => {
setQrModalOpen(true)
}
const closeQrModal = () => {
setQrModalOpen(false)
}
return ( return (
<> <>
<Row align="center" className={classes.heading} grow> <Row align="center" className={classes.heading} grow>
@ -112,14 +105,18 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
const { assetAddress } = formState.values const { assetAddress } = formState.values
const selectedNFTTokens = nftTokens.filter((nftToken) => nftToken.assetAddress === assetAddress) const selectedNFTTokens = nftTokens.filter((nftToken) => nftToken.assetAddress === assetAddress)
const handleScan = (value) => { const handleScan = (value, closeQrModal) => {
let scannedAddress = value let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) { if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '') scannedAddress = scannedAddress.replace('ethereum:', '')
} }
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
mutators.setRecipient(scannedAddress) mutators.setRecipient(scannedAddress)
setSelectedEntry({
name: scannedName,
address: scannedAddress,
})
closeQrModal() closeQrModal()
} }
@ -200,16 +197,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
/> />
</Col> </Col>
<Col center="xs" className={classes} middle="xs" xs={1}> <Col center="xs" className={classes} middle="xs" xs={1}>
<Img <ScanQRWrapper handleScan={handleScan} />
alt="Scan QR"
className={classes.qrCodeBtn}
height={20}
onClick={() => {
openQrModal()
}}
role="button"
src={QRIcon}
/>
</Col> </Col>
</Row> </Row>
</> </>
@ -256,7 +244,6 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
Review Review
</Button> </Button>
</Row> </Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={handleScan} />}
</> </>
) )
}} }}

View File

@ -10,11 +10,10 @@ import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style' import { styles } from './style'
import QRIcon from '~/assets/icons/qrcode.svg'
import CopyBtn from '~/components/CopyBtn' import CopyBtn from '~/components/CopyBtn'
import EtherscanBtn from '~/components/EtherscanBtn' import EtherscanBtn from '~/components/EtherscanBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import ScanQRModal from '~/components/ScanQRModal' import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
@ -25,9 +24,11 @@ import Button from '~/components/layout/Button'
import ButtonLink from '~/components/layout/ButtonLink' import ButtonLink from '~/components/layout/ButtonLink'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import Img from '~/components/layout/Img'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
import { getAddressBook } from '~/logic/addressBook/store/selectors'
import { getNameFromAdbk } from '~/logic/addressBook/utils'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput' import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
import { safeSelector } from '~/routes/safe/store/selectors' import { safeSelector } from '~/routes/safe/store/selectors'
@ -45,13 +46,13 @@ const useStyles = makeStyles(styles)
const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Props) => { const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Props) => {
const classes = useStyles() const classes = useStyles()
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector) const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const [selectedEntry, setSelectedEntry] = useState<Object | null>({ const [selectedEntry, setSelectedEntry] = useState<Object | null>({
address: recipientAddress || initialValues.recipientAddress, address: recipientAddress || initialValues.recipientAddress,
name: '', name: '',
}) })
const [pristine, setPristine] = useState<boolean>(true) const [pristine, setPristine] = useState<boolean>(true)
const [isValidAddress, setIsValidAddress] = useState<boolean>(true) const [isValidAddress, setIsValidAddress] = useState<boolean>(true)
const addressBook: AddressBook = useSelector(getAddressBook)
React.useMemo(() => { React.useMemo(() => {
if (selectedEntry === null && pristine) { if (selectedEntry === null && pristine) {
@ -65,14 +66,6 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
} }
} }
const openQrModal = () => {
setQrModalOpen(true)
}
const closeQrModal = () => {
setQrModalOpen(false)
}
const formMutators = { const formMutators = {
setMax: (args, state, utils) => { setMax: (args, state, utils) => {
utils.changeValue(state, 'value', () => ethBalance) utils.changeValue(state, 'value', () => ethBalance)
@ -103,14 +96,18 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
shouldDisableSubmitButton = !selectedEntry.address shouldDisableSubmitButton = !selectedEntry.address
} }
const handleScan = (value) => { const handleScan = (value, closeQrModal) => {
let scannedAddress = value let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) { if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '') scannedAddress = scannedAddress.replace('ethereum:', '')
} }
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
mutators.setRecipient(scannedAddress) mutators.setRecipient(scannedAddress)
setSelectedEntry({
name: scannedName,
address: scannedAddress,
})
closeQrModal() closeQrModal()
} }
@ -184,16 +181,7 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
/> />
</Col> </Col>
<Col center="xs" className={classes} middle="xs" xs={1}> <Col center="xs" className={classes} middle="xs" xs={1}>
<Img <ScanQRWrapper handleScan={handleScan} />
alt="Scan QR"
className={classes.qrCodeBtn}
height={20}
onClick={() => {
openQrModal()
}}
role="button"
src={QRIcon}
/>
</Col> </Col>
</Row> </Row>
</> </>
@ -252,7 +240,6 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
Review Review
</Button> </Button>
</Row> </Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={handleScan} />}
</> </>
) )
}} }}

View File

@ -11,11 +11,10 @@ import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style' import { styles } from './style'
import QRIcon from '~/assets/icons/qrcode.svg'
import CopyBtn from '~/components/CopyBtn' import CopyBtn from '~/components/CopyBtn'
import EtherscanBtn from '~/components/EtherscanBtn' import EtherscanBtn from '~/components/EtherscanBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import ScanQRModal from '~/components/ScanQRModal' import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
@ -25,9 +24,11 @@ import Button from '~/components/layout/Button'
import ButtonLink from '~/components/layout/ButtonLink' import ButtonLink from '~/components/layout/ButtonLink'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import Img from '~/components/layout/Img'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
import { getAddressBook } from '~/logic/addressBook/store/selectors'
import { getNameFromAdbk } from '~/logic/addressBook/utils'
import { type Token } from '~/logic/tokens/store/model/token' import { type Token } from '~/logic/tokens/store/model/token'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput' import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
@ -62,7 +63,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
const classes = useStyles() const classes = useStyles()
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector) const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const tokens: Token = useSelector(extendedSafeTokensSelector) const tokens: Token = useSelector(extendedSafeTokensSelector)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false) const addressBook: AddressBook = useSelector(getAddressBook)
const [selectedEntry, setSelectedEntry] = useState<Object | null>({ const [selectedEntry, setSelectedEntry] = useState<Object | null>({
address: recipientAddress || initialValues.recipientAddress, address: recipientAddress || initialValues.recipientAddress,
name: '', name: '',
@ -85,14 +86,6 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
onNext(submitValues) onNext(submitValues)
} }
const openQrModal = () => {
setQrModalOpen(true)
}
const closeQrModal = () => {
setQrModalOpen(false)
}
return ( return (
<> <>
<Row align="center" className={classes.heading} grow> <Row align="center" className={classes.heading} grow>
@ -112,14 +105,18 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
const { token: tokenAddress } = formState.values const { token: tokenAddress } = formState.values
const selectedTokenRecord = tokens.find((token) => token.address === tokenAddress) const selectedTokenRecord = tokens.find((token) => token.address === tokenAddress)
const handleScan = (value) => { const handleScan = (value, closeQrModal) => {
let scannedAddress = value let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) { if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '') scannedAddress = scannedAddress.replace('ethereum:', '')
} }
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
mutators.setRecipient(scannedAddress) mutators.setRecipient(scannedAddress)
setSelectedEntry({
name: scannedName,
address: scannedAddress,
})
closeQrModal() closeQrModal()
} }
@ -198,16 +195,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
/> />
</Col> </Col>
<Col center="xs" className={classes} middle="xs" xs={1}> <Col center="xs" className={classes} middle="xs" xs={1}>
<Img <ScanQRWrapper handleScan={handleScan} />
alt="Scan QR"
className={classes.qrCodeBtn}
height={20}
onClick={() => {
openQrModal()
}}
role="button"
src={QRIcon}
/>
</Col> </Col>
</Row> </Row>
</> </>
@ -276,7 +264,6 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
Review Review
</Button> </Button>
</Row> </Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={handleScan} />}
</> </>
) )
}} }}

View File

@ -7,6 +7,7 @@ import { useSelector } from 'react-redux'
import { styles } from './style' import { styles } from './style'
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import AddressInput from '~/components/forms/AddressInput' import AddressInput from '~/components/forms/AddressInput'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
@ -59,6 +60,16 @@ const OwnerForm = ({ classes, onClose, onSubmit }: Props) => {
{(...args) => { {(...args) => {
const mutators = args[3] const mutators = args[3]
const handleScan = (value, closeQrModal) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
mutators.setOwnerAddress(scannedAddress)
closeQrModal()
}
return ( return (
<> <>
<Block className={classes.formContainer}> <Block className={classes.formContainer}>
@ -91,6 +102,9 @@ const OwnerForm = ({ classes, onClose, onSubmit }: Props) => {
validators={[ownerDoesntExist]} validators={[ownerDoesntExist]}
/> />
</Col> </Col>
<Col center="xs" className={classes} middle="xs" xs={1}>
<ScanQRWrapper handleScan={handleScan} />
</Col>
</Row> </Row>
</Block> </Block>
<Hairline /> <Hairline />

View File

@ -11,6 +11,7 @@ import { styles } from './style'
import CopyBtn from '~/components/CopyBtn' import CopyBtn from '~/components/CopyBtn'
import EtherscanBtn from '~/components/EtherscanBtn' import EtherscanBtn from '~/components/EtherscanBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import AddressInput from '~/components/forms/AddressInput' import AddressInput from '~/components/forms/AddressInput'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
@ -65,6 +66,17 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }: Prop
{(...args) => { {(...args) => {
const mutators = args[3] const mutators = args[3]
const handleScan = (value, closeQrModal) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
mutators.setOwnerAddress(scannedAddress)
closeQrModal()
}
return ( return (
<> <>
<Block className={classes.formContainer}> <Block className={classes.formContainer}>
@ -126,6 +138,9 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }: Prop
validators={[ownerDoesntExist]} validators={[ownerDoesntExist]}
/> />
</Col> </Col>
<Col center="xs" className={classes} middle="xs" xs={1}>
<ScanQRWrapper handleScan={handleScan} />
</Col>
</Row> </Row>
</Block> </Block>
<Hairline /> <Hairline />