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

Commit

Permalink
(Fix) #511 - QR scan button (#873)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Agupane authored May 8, 2020
1 parent 8bc5de3 commit 4098a8b
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 156 deletions.
51 changes: 51 additions & 0 deletions src/components/ScanQRModal/ScanQRWrapper/index.jsx
Original file line number Diff line number Diff line change
@@ -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} />}
</>
)
}
2 changes: 1 addition & 1 deletion src/logic/addressBook/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const saveAddressBook = async (addressBook: AddressBook) => {
export const getAddressesListFromAdbk = (addressBook: AddressBook) =>
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)
if (entry) {
return entry.name
Expand Down
131 changes: 73 additions & 58 deletions src/routes/load/components/DetailsForm/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { withStyles } from '@material-ui/core/styles'
import CheckCircle from '@material-ui/icons/CheckCircle'
import * as React from 'react'

import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import OpenPaper from '~/components/Stepper/OpenPaper'
import AddressInput from '~/components/forms/AddressInput'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import { mustBeEthereumAddress, noErrorsOn, required } from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col'
import Paragraph from '~/components/layout/Paragraph'
import { SAFE_MASTER_COPY_ADDRESS_V10, getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
import { getWeb3 } from '~/logic/wallets/getWeb3'
Expand Down Expand Up @@ -80,64 +82,77 @@ export const safeFieldsValidation = async (values: Object) => {
return errors
}

const Details = ({ classes, errors, form }: Props) => (
<>
<Block margin="md">
<Paragraph color="primary" noMargin size="md">
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.
<br />
Your connected wallet does not have to be the owner of this Safe. In this case, the interface will provide you a
read-only view.
</Paragraph>
</Block>
<Block className={classes.root}>
<Field
component={TextField}
name={FIELD_LOAD_NAME}
placeholder="Name of the Safe"
text="Safe name"
type="text"
validate={required}
/>
</Block>
<Block className={classes.root} margin="lg">
<AddressInput
component={TextField}
fieldMutator={(val) => {
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
}}
inputAdornment={
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
endAdornment: (
<InputAdornment position="end">
<CheckCircle className={classes.check} />
</InputAdornment>
),
}
}
name={FIELD_LOAD_ADDRESS}
placeholder="Safe Address*"
text="Safe Address"
type="text"
/>
</Block>
<Block margin="sm">
<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 Details = ({ classes, errors, form }: Props) => {
const handleScan = (value, closeQrModal) => {
form.mutators.setValue(FIELD_LOAD_ADDRESS, value)
closeQrModal()
}
return (
<>
<Block margin="md">
<Paragraph color="primary" noMargin size="md">
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.
<br />
Your connected wallet does not have to be the owner of this Safe. In this case, the interface will provide you
a read-only view.
</Paragraph>
</Block>
<Block className={classes.root}>
<Col xs={11}>
<Field
component={TextField}
name={FIELD_LOAD_NAME}
placeholder="Name of the Safe"
text="Safe name"
type="text"
validate={required}
/>
</Col>
</Block>
<Block className={classes.root} margin="lg">
<Col xs={11}>
<AddressInput
component={TextField}
fieldMutator={(val) => {
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
}}
inputAdornment={
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
endAdornment: (
<InputAdornment position="end">
<CheckCircle className={classes.check} />
</InputAdornment>
),
}
}
name={FIELD_LOAD_ADDRESS}
placeholder="Safe Address*"
text="Safe Address"
type="text"
/>
</Col>
<Col center="xs" className={classes} middle="xs" xs={1}>
<ScanQRWrapper handleScan={handleScan} />
</Col>
</Block>
<Block margin="sm">
<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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { useSelector } from 'react-redux'
import { styles } from './style'

import Modal from '~/components/Modal'
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import AddressInput from '~/components/forms/AddressInput'
import Field from '~/components/forms/Field'
import GnoForm from '~/components/forms/GnoForm'
import TextField from '~/components/forms/TextField'
import { composeValidators, minMaxLength, required, uniqueAddress } from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button'
import Col from '~/components/layout/Col'
import Hairline from '~/components/layout/Hairline'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
Expand Down Expand Up @@ -81,34 +83,53 @@ const CreateEditEntryModalComponent = ({
<GnoForm formMutators={formMutators} onSubmit={onFormSubmitted}>
{(...args) => {
const mutators = args[3]
const handleScan = (value, closeQrModal) => {
let scannedAddress = value

if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}

mutators.setOwnerAddress(scannedAddress)
closeQrModal()
}
return (
<>
<Block className={classes.container}>
<Row margin="md">
<Field
className={classes.addressInput}
component={TextField}
defaultValue={entryToEdit ? entryToEdit.entry.name : undefined}
name="name"
placeholder={entryToEdit ? 'Entry name' : 'New entry'}
testId={CREATE_ENTRY_INPUT_NAME_ID}
text={entryToEdit ? 'Entry*' : 'New entry*'}
type="text"
validate={composeValidators(required, minMaxLength(1, 50))}
/>
<Col xs={11}>
<Field
className={classes.addressInput}
component={TextField}
defaultValue={entryToEdit ? entryToEdit.entry.name : undefined}
name="name"
placeholder={entryToEdit ? 'Entry name' : 'New entry'}
testId={CREATE_ENTRY_INPUT_NAME_ID}
text={entryToEdit ? 'Entry*' : 'New entry*'}
type="text"
validate={composeValidators(required, minMaxLength(1, 50))}
/>
</Col>
</Row>
<Row margin="md">
<AddressInput
className={classes.addressInput}
defaultValue={entryToEdit ? entryToEdit.entry.address : undefined}
disabled={!!entryToEdit}
fieldMutator={mutators.setOwnerAddress}
name="address"
placeholder="Owner address*"
testId={CREATE_ENTRY_INPUT_ADDRESS_ID}
text="Owner address*"
validators={entryToEdit ? undefined : [entryDoesntExist]}
/>
<Col xs={11}>
<AddressInput
className={classes.addressInput}
defaultValue={entryToEdit ? entryToEdit.entry.address : undefined}
disabled={!!entryToEdit}
fieldMutator={mutators.setOwnerAddress}
name="address"
placeholder="Owner address*"
testId={CREATE_ENTRY_INPUT_ADDRESS_ID}
text="Owner address*"
validators={entryToEdit ? undefined : [entryDoesntExist]}
/>
</Col>
{!entryToEdit ? (
<Col center="xs" className={classes} middle="xs" xs={1}>
<ScanQRWrapper handleScan={handleScan} />
</Col>
) : null}
</Row>
</Block>
<Hairline />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,21 @@ import ArrowDown from '../assets/arrow-down.svg'

import { styles } from './style'

import QRIcon from '~/assets/icons/qrcode.svg'
import CopyBtn from '~/components/CopyBtn'
import EtherscanBtn from '~/components/EtherscanBtn'
import Identicon from '~/components/Identicon'
import ScanQRModal from '~/components/ScanQRModal'
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
import WhenFieldChanges from '~/components/WhenFieldChanges'
import GnoForm from '~/components/forms/GnoForm'
import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button'
import Col from '~/components/layout/Col'
import Hairline from '~/components/layout/Hairline'
import Img from '~/components/layout/Img'
import Paragraph from '~/components/layout/Paragraph'
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 { nftTokensSelector, safeActiveSelectorMap } from '~/logic/collectibles/store/selectors'
import type { NFTToken } from '~/routes/safe/components/Balances/Collectibles/types'
Expand Down Expand Up @@ -60,7 +61,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
const nftAssets: NFTAssetsState = useSelector(safeActiveSelectorMap)
const nftTokens: NFTTokensState = useSelector(nftTokensSelector)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const addressBook: AddressBook = useSelector(getAddressBook)
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
address: recipientAddress || initialValues.recipientAddress,
name: '',
Expand All @@ -85,14 +86,6 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
onNext(values)
}

const openQrModal = () => {
setQrModalOpen(true)
}

const closeQrModal = () => {
setQrModalOpen(false)
}

return (
<>
<Row align="center" className={classes.heading} grow>
Expand All @@ -112,14 +105,18 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
const { assetAddress } = formState.values
const selectedNFTTokens = nftTokens.filter((nftToken) => nftToken.assetAddress === assetAddress)

const handleScan = (value) => {
const handleScan = (value, closeQrModal) => {
let scannedAddress = value

if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}

const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
mutators.setRecipient(scannedAddress)
setSelectedEntry({
name: scannedName,
address: scannedAddress,
})
closeQrModal()
}

Expand Down Expand Up @@ -200,16 +197,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
/>
</Col>
<Col center="xs" className={classes} middle="xs" xs={1}>
<Img
alt="Scan QR"
className={classes.qrCodeBtn}
height={20}
onClick={() => {
openQrModal()
}}
role="button"
src={QRIcon}
/>
<ScanQRWrapper handleScan={handleScan} />
</Col>
</Row>
</>
Expand Down Expand Up @@ -256,7 +244,6 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
Review
</Button>
</Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={handleScan} />}
</>
)
}}
Expand Down
Loading

0 comments on commit 4098a8b

Please sign in to comment.