add qrcode scanner component, logic wip
This commit is contained in:
parent
c7f355de04
commit
d882f17d7d
17
package.json
17
package.json
|
@ -53,6 +53,7 @@
|
||||||
"react-final-form-listeners": "^1.0.2",
|
"react-final-form-listeners": "^1.0.2",
|
||||||
"react-hot-loader": "4.12.8",
|
"react-hot-loader": "4.12.8",
|
||||||
"react-infinite-scroll-component": "^4.5.2",
|
"react-infinite-scroll-component": "^4.5.2",
|
||||||
|
"react-qr-reader": "^2.2.1",
|
||||||
"react-redux": "7.1.0",
|
"react-redux": "7.1.0",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"recompose": "^0.30.0",
|
"recompose": "^0.30.0",
|
||||||
|
@ -104,17 +105,17 @@
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"css-loader": "3.1.0",
|
"css-loader": "3.1.0",
|
||||||
"detect-port": "^1.2.2",
|
"detect-port": "^1.2.2",
|
||||||
"eslint": "6.0.1",
|
"eslint": "5.16.0",
|
||||||
"eslint-config-airbnb": "17.1.1",
|
"eslint-config-airbnb": "17.1.1",
|
||||||
"eslint-plugin-flowtype": "3.11.1",
|
"eslint-plugin-flowtype": "3.12.1",
|
||||||
"eslint-plugin-import": "2.18.0",
|
"eslint-plugin-import": "2.18.1",
|
||||||
"eslint-plugin-jest": "22.10.0",
|
"eslint-plugin-jest": "22.11.1",
|
||||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||||
"eslint-plugin-react": "7.14.2",
|
"eslint-plugin-react": "7.14.2",
|
||||||
"ethereumjs-abi": "^0.6.7",
|
"ethereumjs-abi": "^0.6.7",
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"file-loader": "4.1.0",
|
"file-loader": "4.1.0",
|
||||||
"flow-bin": "0.102.0",
|
"flow-bin": "0.103.0",
|
||||||
"fs-extra": "8.1.0",
|
"fs-extra": "8.1.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^3.0.4",
|
"html-webpack-plugin": "^3.0.4",
|
||||||
|
@ -131,9 +132,9 @@
|
||||||
"storybook-host": "5.1.0",
|
"storybook-host": "5.1.0",
|
||||||
"storybook-router": "^0.3.3",
|
"storybook-router": "^0.3.3",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
"truffle": "5.0.27",
|
"truffle": "5.0.28",
|
||||||
"truffle-contract": "4.0.24",
|
"truffle-contract": "4.0.25",
|
||||||
"truffle-solidity-loader": "0.1.26",
|
"truffle-solidity-loader": "0.1.27",
|
||||||
"uglifyjs-webpack-plugin": "2.1.3",
|
"uglifyjs-webpack-plugin": "2.1.3",
|
||||||
"webpack": "4.36.1",
|
"webpack": "4.36.1",
|
||||||
"webpack-bundle-analyzer": "3.3.2",
|
"webpack-bundle-analyzer": "3.3.2",
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 55.2 (78181) - https://sketchapp.com -->
|
||||||
|
<title>Group</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Icon-/-QR-Icon" transform="translate(-11.000000, -11.000000)" fill="#a2a8ba">
|
||||||
|
<g id="QR-Icon" transform="translate(2.000000, 2.000000)">
|
||||||
|
<g id="Group" transform="translate(9.000000, 9.000000)">
|
||||||
|
<path d="M2,2 L2,4 L4,4 L4,2 L2,2 Z M6,0 L6,6 L0,6 L0,0 L6,0 Z" id="Rectangle" fill-rule="nonzero"></path>
|
||||||
|
<path d="M2,12 L2,14 L4,14 L4,12 L2,12 Z M6,10 L6,16 L0,16 L0,10 L6,10 Z" id="Rectangle-Copy-2" fill-rule="nonzero"></path>
|
||||||
|
<path d="M12,2 L12,4 L14,4 L14,2 L12,2 Z M16,0 L16,6 L10,6 L10,0 L16,0 Z" id="Rectangle-Copy" fill-rule="nonzero"></path>
|
||||||
|
<rect id="Rectangle" x="7" y="2" width="2" height="4"></rect>
|
||||||
|
<path d="M7,9 L5,9 L5,7 L7,7 L9,7 L9,11 L7,11 L7,9 Z" id="Combined-Shape"></path>
|
||||||
|
<rect id="Rectangle-Copy-4" x="0" y="7" width="2" height="2"></rect>
|
||||||
|
<rect id="Rectangle-Copy-5" x="10" y="7" width="2" height="2"></rect>
|
||||||
|
<rect id="Rectangle-Copy-6" x="14" y="7" width="2" height="2"></rect>
|
||||||
|
<rect id="Rectangle-Copy-7" x="12" y="9" width="2" height="2"></rect>
|
||||||
|
<rect id="Rectangle-Copy-10" x="12" y="14" width="2" height="2"></rect>
|
||||||
|
<path d="M9,12 L10,12 L10,11 L12,11 L12,14 L10,14 L9,14 L9,16 L7,16 L7,12 L9,12 Z" id="Combined-Shape"></path>
|
||||||
|
<rect id="Rectangle-Copy-9" x="14" y="11" width="2" height="3"></rect>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -1,10 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import React, { PureComponent } from 'react'
|
import * as React from 'react'
|
||||||
import { capitalize } from '~/utils/css'
|
import { capitalize } from '~/utils/css'
|
||||||
import { type Size } from '~/theme/size'
|
import { type Size } from '~/theme/size'
|
||||||
import styles from './index.scss'
|
import styles from './index.scss'
|
||||||
|
|
||||||
|
const { PureComponent } = React
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import QrReader from 'react-qr-reader'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import Close from '@material-ui/icons/Close'
|
||||||
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import Button from '~/components/layout/Button'
|
||||||
|
import Block from '~/components/layout/Block'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import Hairline from '~/components/layout/Hairline'
|
||||||
|
import Col from '~/components/layout/Col'
|
||||||
|
import Modal from '~/components/Modal'
|
||||||
|
import { lg, sm, background } from '~/theme/variables'
|
||||||
|
import { checkWebcam } from './utils'
|
||||||
|
|
||||||
|
const { useEffect, useState } = React
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
heading: {
|
||||||
|
padding: `${sm} ${lg}`,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
maxHeight: '75px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
},
|
||||||
|
loaderContainer: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
manage: {
|
||||||
|
fontSize: '24px',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
height: '35px',
|
||||||
|
width: '35px',
|
||||||
|
},
|
||||||
|
detailsContainer: {
|
||||||
|
backgroundColor: background,
|
||||||
|
maxHeight: '420px',
|
||||||
|
},
|
||||||
|
buttonRow: {
|
||||||
|
height: '84px',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
'&:last-child': {
|
||||||
|
marginLeft: sm,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void,
|
||||||
|
classes: Object,
|
||||||
|
isOpen: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScanQRModal = ({ classes, onClose, isOpen }: Props) => {
|
||||||
|
const [hasWebcam, setHasWebcam] = useState(null)
|
||||||
|
const scannerRef = React.createRef()
|
||||||
|
const openImageDialog = () => {
|
||||||
|
scannerRef.current.openImageDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkWebcam(
|
||||||
|
() => {
|
||||||
|
setHasWebcam(true)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
setHasWebcam(false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasWebcam === false) {
|
||||||
|
openImageDialog()
|
||||||
|
}
|
||||||
|
}, [hasWebcam])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title="Receive Tokens" description="Receive Tokens Form" handleClose={onClose} open={isOpen}>
|
||||||
|
<Row align="center" grow className={classes.heading}>
|
||||||
|
<Paragraph className={classes.manage} weight="bolder" noMargin>
|
||||||
|
Scan QR
|
||||||
|
</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.close} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
<Col layout="column" middle="xs" className={classes.detailsContainer}>
|
||||||
|
{hasWebcam === null ? (
|
||||||
|
<Block align="center" className={classes.loaderContainer}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Block>
|
||||||
|
) : (
|
||||||
|
<QrReader
|
||||||
|
ref={scannerRef}
|
||||||
|
legacyMode={!hasWebcam}
|
||||||
|
onScan={(data) => {
|
||||||
|
if (data) console.log(data)
|
||||||
|
}}
|
||||||
|
onError={(err) => {
|
||||||
|
console.error(err)
|
||||||
|
}}
|
||||||
|
style={{ width: '400px', height: '400px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
<Hairline />
|
||||||
|
<Row align="center" className={classes.buttonRow}>
|
||||||
|
<Button color="secondary" className={classes.button} minHeight={42} minWidth={140} onClick={onClose} variant="contained">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
className={classes.button}
|
||||||
|
minWidth={140}
|
||||||
|
minHeight={42}
|
||||||
|
onClick={() => {
|
||||||
|
if (hasWebcam) {
|
||||||
|
setHasWebcam(false)
|
||||||
|
} else {
|
||||||
|
openImageDialog()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
Upload an image
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(ScanQRModal)
|
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
navigator.getMedia = navigator.getUserMedia // use the proper vendor prefix
|
||||||
|
|| navigator.webkitGetUserMedia
|
||||||
|
|| navigator.mozGetUserMedia
|
||||||
|
|| navigator.msGetUserMedia
|
||||||
|
|
||||||
|
export const checkWebcam = (success: Function, err: Function) => navigator.getMedia(
|
||||||
|
{ video: true },
|
||||||
|
() => {
|
||||||
|
success()
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
err()
|
||||||
|
},
|
||||||
|
)
|
|
@ -26,6 +26,8 @@ import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||||
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import trash from '~/assets/icons/trash.svg'
|
import trash from '~/assets/icons/trash.svg'
|
||||||
|
import QRIcon from '~/assets/icons/qrcode.svg'
|
||||||
|
import ScanQRModal from './ScanQRModal'
|
||||||
import { getAddressValidators } from './validators'
|
import { getAddressValidators } from './validators'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
|
@ -67,8 +69,16 @@ const SafeOwners = (props: Props) => {
|
||||||
classes, errors, otherAccounts, values, updateInitialProps,
|
classes, errors, otherAccounts, values, updateInitialProps,
|
||||||
} = props
|
} = props
|
||||||
const [numOwners, setNumOwners] = useState<number>(1)
|
const [numOwners, setNumOwners] = useState<number>(1)
|
||||||
|
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||||
const validOwners = getNumOwnersFrom(values)
|
const validOwners = getNumOwnersFrom(values)
|
||||||
|
|
||||||
|
const openQrModal = () => {
|
||||||
|
setQrModalOpen(true)
|
||||||
|
}
|
||||||
|
const closeQrModal = () => {
|
||||||
|
setQrModalOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
const onRemoveRow = (index: number) => () => {
|
const onRemoveRow = (index: number) => () => {
|
||||||
if (numOwners === 2) {
|
if (numOwners === 2) {
|
||||||
const { form } = props
|
const { form } = props
|
||||||
|
@ -115,7 +125,7 @@ const SafeOwners = (props: Props) => {
|
||||||
text="Owner Name"
|
text="Owner Name"
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={7}>
|
<Col xs={6}>
|
||||||
<Field
|
<Field
|
||||||
name={addressName}
|
name={addressName}
|
||||||
component={TextField}
|
component={TextField}
|
||||||
|
@ -134,6 +144,9 @@ const SafeOwners = (props: Props) => {
|
||||||
text="Owner Address"
|
text="Owner Address"
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
|
||||||
|
<Img src={QRIcon} height={20} alt="Scan QR" onClick={openQrModal} />
|
||||||
|
</Col>
|
||||||
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
|
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
|
||||||
{index > 0 && <Img src={trash} height={20} alt="Delete" onClick={onRemoveRow(index)} />}
|
{index > 0 && <Img src={trash} height={20} alt="Delete" onClick={onRemoveRow(index)} />}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -178,6 +191,7 @@ owner(s)
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
|
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} />}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ export const styles = () => ({
|
||||||
},
|
},
|
||||||
owner: {
|
owner: {
|
||||||
padding: `0 ${lg}`,
|
padding: `0 ${lg}`,
|
||||||
|
marginTop: '12px',
|
||||||
|
'&:first-child': {
|
||||||
|
marginTop: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
padding: `${sm} ${lg}`,
|
padding: `${sm} ${lg}`,
|
||||||
|
@ -29,7 +33,6 @@ export const styles = () => ({
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
height: '56px',
|
height: '56px',
|
||||||
marginTop: '12px',
|
|
||||||
maxWidth: '50px',
|
maxWidth: '50px',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||||
import Link from '~/components/layout/Link'
|
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
|
import Link from '~/components/layout/Link'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
|
|
Loading…
Reference in New Issue