aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/app/actions.js32
-rw-r--r--ui/app/app.js7
-rw-r--r--ui/app/components/ens-input.js1
-rw-r--r--ui/app/components/qr-scanner/index.js2
-rw-r--r--ui/app/components/qr-scanner/qr-scanner.component.js186
-rw-r--r--ui/app/components/send/send-content/send-content.component.js6
-rw-r--r--ui/app/components/send/send-content/send-to-row/send-to-row.component.js2
-rw-r--r--ui/app/components/send/send.component.js22
-rw-r--r--ui/app/components/send/send.container.js6
-rw-r--r--ui/app/components/send/send.selectors.js5
-rw-r--r--ui/app/components/send/tests/send-container.test.js2
-rw-r--r--ui/app/components/send/to-autocomplete/to-autocomplete.js8
-rw-r--r--ui/app/css/itcss/components/send.scss11
-rw-r--r--ui/app/reducers/app.js20
14 files changed, 305 insertions, 5 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 7a8d9667d..7608db6dc 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -33,6 +33,12 @@ var actions = {
ALERT_CLOSE: 'UI_ALERT_CLOSE',
showAlert: showAlert,
hideAlert: hideAlert,
+ QR_SCANNER_OPEN: 'UI_QR_SCANNER_OPEN',
+ QR_SCANNER_CLOSE: 'UI_QR_SCANNER_CLOSE',
+ QR_CODE_DETECTED: 'UI_QR_CODE_DETECTED',
+ showQrScanner,
+ hideQrScanner,
+ qrCodeDetected,
// network dropdown open
NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN',
NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE',
@@ -304,6 +310,7 @@ var actions = {
CLEAR_PENDING_TOKENS: 'CLEAR_PENDING_TOKENS',
setPendingTokens,
clearPendingTokens,
+ scanQrCode,
}
module.exports = actions
@@ -1806,6 +1813,25 @@ function hideAlert () {
}
}
+function showQrScanner () {
+ return {
+ type: actions.QR_SCANNER_OPEN,
+ }
+}
+
+function qrCodeDetected (qrCodeData) {
+ return {
+ type: actions.QR_CODE_DETECTED,
+ value: qrCodeData,
+ }
+}
+
+function hideQrScanner () {
+ return {
+ type: actions.QR_SCANNER_CLOSE,
+ }
+}
+
function showLoadingIndication (message) {
return {
@@ -2249,3 +2275,9 @@ function clearPendingTokens () {
type: actions.CLEAR_PENDING_TOKENS,
}
}
+
+function scanQrCode () {
+ return (dispatch, getState) => {
+ dispatch(actions.showQrScanner())
+ }
+}
diff --git a/ui/app/app.js b/ui/app/app.js
index dbb6146d1..9363d21d1 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -39,6 +39,8 @@ const Modal = require('./components/modals/index').Modal
// Global Alert
const Alert = require('./components/alert')
+const QrScanner = require('./components/qr-scanner')
+
const AppHeader = require('./components/app-header')
import UnlockPage from './components/pages/unlock-page'
@@ -132,6 +134,8 @@ class App extends Component {
// global alert
h(Alert, {visible: this.props.alertOpen, msg: alertMessage}),
+ h(QrScanner, {visible: this.props.qrScannerOpen}),
+
h(AppHeader),
// sidebar
@@ -270,6 +274,7 @@ App.propTypes = {
currentView: PropTypes.object,
sidebarOpen: PropTypes.bool,
alertOpen: PropTypes.bool,
+ qrScannerOpen: PropTypes.bool,
hideSidebar: PropTypes.func,
isMascara: PropTypes.bool,
isOnboarding: PropTypes.bool,
@@ -306,6 +311,7 @@ function mapStateToProps (state) {
networkDropdownOpen,
sidebarOpen,
alertOpen,
+ qrScannerOpen,
alertMessage,
isLoading,
loadingMessage,
@@ -333,6 +339,7 @@ function mapStateToProps (state) {
networkDropdownOpen,
sidebarOpen,
alertOpen,
+ qrScannerOpen,
alertMessage,
isLoading,
loadingMessage,
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index b9f99b3d1..cfdf663a5 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -54,6 +54,7 @@ EnsInput.prototype.render = function () {
const opts = extend(props, {
list: 'addresses',
onChange: this.onChange.bind(this),
+ qrScanner: true,
})
return h('div', {
style: { width: '100%', position: 'relative' },
diff --git a/ui/app/components/qr-scanner/index.js b/ui/app/components/qr-scanner/index.js
new file mode 100644
index 000000000..f459f6702
--- /dev/null
+++ b/ui/app/components/qr-scanner/index.js
@@ -0,0 +1,2 @@
+import QrScanner from './qr-scanner.component'
+module.exports = QrScanner
diff --git a/ui/app/components/qr-scanner/qr-scanner.component.js b/ui/app/components/qr-scanner/qr-scanner.component.js
new file mode 100644
index 000000000..a0b52495d
--- /dev/null
+++ b/ui/app/components/qr-scanner/qr-scanner.component.js
@@ -0,0 +1,186 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import {connect} from 'react-redux'
+import { BrowserQRCodeReader } from '@zxing/library'
+import adapter from 'webrtc-adapter' // eslint-disable-line import/no-nodejs-modules, no-unused-vars
+import { hideQrScanner, qrCodeDetected} from '../../actions'
+import Spinner from '../spinner'
+
+class QrScanner extends Component {
+ static propTypes = {
+ visible: PropTypes.bool,
+ hideQrScanner: PropTypes.func,
+ qrCodeDetected: PropTypes.func,
+ }
+ constructor (props) {
+ super(props)
+ this.state = {
+ ready: false,
+ msg: 'Accesing your camera...',
+ }
+ this.scanning = false
+ this.codeReader = null
+ }
+
+ componentDidUpdate () {
+ if (this.props.visible && this.camera && !this.scanning) {
+ this.scanning = true
+ this.initCamera()
+ }
+ }
+
+ initCamera () {
+ console.log('QR-SCANNER: initCamera ')
+ this.codeReader = new BrowserQRCodeReader()
+ this.codeReader.getVideoInputDevices()
+ .then(videoInputDevices => {
+ console.log('QR-SCANNER: getVideoInputDevices ', videoInputDevices)
+ setTimeout(_ => {
+ this.setState({
+ ready: true,
+ msg: 'Place the QR code in front of your camera so we can read it...'})
+ console.log('QR-SCANNER: this.state.ready = true')
+ }, 2000)
+
+ console.log('QR-SCANNER: started scanning...')
+ this.codeReader.decodeFromInputVideoDevice(videoInputDevices[0].deviceId, 'video')
+ .then(content => {
+ console.log('QR-SCANNER: content found!', content)
+ this.codeReader.reset()
+ console.log('QR-SCANNER: stopped scanning...')
+ const result = this.parseContent(content.text)
+ if (result.type !== 'unknown') {
+ console.log('QR-SCANNER: CODE DETECTED', result)
+ this.props.qrCodeDetected(result)
+ this.props.hideQrScanner()
+ this.setState({ ready: false })
+ } else {
+ this.setState({msg: 'Error: We couldn\'t identify that QR code'})
+ console.log('QR-SCANNER: Unknown code')
+ }
+ })
+ .catch(err => {
+ console.log('QR-SCANNER: decodeFromInputVideoDevice threw an exception: ', err)
+ })
+ }).catch(err => {
+ console.log('QR-SCANNER: getVideoInputDevices threw an exception: ', err)
+ })
+ }
+
+ parseContent (content) {
+ let type = 'unknown'
+ let values = {}
+
+ // Here we could add more cases
+ // To parse other codes (transactions for ex.)
+
+ if (content.split('ethereum:').length > 1) {
+ type = 'address'
+ values = {'address': content.split('ethereum:')[1] }
+ }
+ return {type, values}
+ }
+
+
+ stopAndClose = () => {
+ console.log('QR-SCANNER: stopping scanner...')
+ this.codeReader.reset()
+ this.scanning = false
+ this.props.hideQrScanner()
+ this.setState({ ready: false })
+ }
+
+ render () {
+ const { visible } = this.props
+
+ if (!visible) {
+ return null
+ }
+
+ return (
+ <div className={'qr-code-modal-wrapper'}>
+ <div className={'qr-scanner'}
+ style={{
+ position: 'fixed',
+ top: '50%',
+ left: '50%',
+ zIndex: 1050,
+ minWidth: '320px',
+ minHeight: '400px',
+ maxWidth: '300px',
+ maxHeight: '300px',
+ transform: 'translate(-50%, -50%)',
+ backgroundColor: '#ffffff',
+ padding: '15px',
+ }}
+ >
+ <h3 style={{
+ textAlign: 'center',
+ marginBottom: '20px',
+ fontSize: '1.5rem',
+ fontWeight: '500',
+ }}>
+ Scan QR code
+ </h3>
+ <div
+ className={'qr-code-video-wrapper'}
+ style={{
+ overflow: 'hidden',
+ width: '100%',
+ height: '275px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}>
+ <video
+ id="video"
+ style={{
+ width: 'auto',
+ height: '275px',
+ marginLeft: '-15%',
+ display: this.state.ready ? 'block' : 'none',
+ transform: 'scaleX(-1)',
+ }}
+ ref={(cam) => {
+ this.camera = cam
+ }}
+ />
+ { !this.state.ready ? <Spinner color={'#F7C06C'} /> : null}
+ </div>
+ <div className={'qr-code-help'} style={{textAlign: 'center', fontSize: '12px', padding: '15px'}}>
+ {this.state.msg}
+ </div>
+ </div>
+ <div
+ className={'qr-code-modal-overlay'}
+ style={{
+ position: 'fixed',
+ top: '0',
+ right: '0',
+ bottom: '0',
+ left: '0',
+ zIndex: '1040',
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ animationFillMode: 'forwards',
+ animationDuration: '0.3s',
+ animationName: 'anim_171532470906313',
+ animationTimingFunction: 'ease-out',
+ }}
+ onClick={this.stopAndClose}
+ />
+ </div>
+ )
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideQrScanner: () => dispatch(hideQrScanner()),
+ qrCodeDetected: (data) => dispatch(qrCodeDetected(data)),
+ }
+}
+function mapStateToProps (state) {
+ return {}
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(QrScanner)
diff --git a/ui/app/components/send/send-content/send-content.component.js b/ui/app/components/send/send-content/send-content.component.js
index 7a0b1a18e..df7bcb7cc 100644
--- a/ui/app/components/send/send-content/send-content.component.js
+++ b/ui/app/components/send/send-content/send-content.component.js
@@ -11,6 +11,7 @@ export default class SendContent extends Component {
static propTypes = {
updateGas: PropTypes.func,
+ scanQrCode: PropTypes.func,
};
render () {
@@ -18,7 +19,10 @@ export default class SendContent extends Component {
<PageContainerContent>
<div className="send-v2__form">
<SendFromRow />
- <SendToRow updateGas={(updateData) => this.props.updateGas(updateData)} />
+ <SendToRow
+ updateGas={(updateData) => this.props.updateGas(updateData)}
+ scanQrCode={ _ => this.props.scanQrCode()}
+ />
<SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} />
<SendGasRow />
<SendHexDataRow />
diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send/send-content/send-to-row/send-to-row.component.js
index 892ad5d67..1163dcffc 100644
--- a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js
+++ b/ui/app/components/send/send-content/send-to-row/send-to-row.component.js
@@ -17,6 +17,7 @@ export default class SendToRow extends Component {
updateGas: PropTypes.func,
updateSendTo: PropTypes.func,
updateSendToError: PropTypes.func,
+ scanQrCode: PropTypes.func,
};
static contextTypes = {
@@ -51,6 +52,7 @@ export default class SendToRow extends Component {
showError={inError}
>
<EnsInput
+ scanQrCode={_ => this.props.scanQrCode()}
accounts={toAccounts}
closeDropdown={() => closeToDropdown()}
dropdownOpen={toDropdownOpen}
diff --git a/ui/app/components/send/send.component.js b/ui/app/components/send/send.component.js
index 6f1b20c55..fb7eef329 100644
--- a/ui/app/components/send/send.component.js
+++ b/ui/app/components/send/send.component.js
@@ -38,12 +38,29 @@ export default class SendTransactionScreen extends PersistentForm {
updateAndSetGasTotal: PropTypes.func,
updateSendErrors: PropTypes.func,
updateSendTokenBalance: PropTypes.func,
+ scanQrCode: PropTypes.func,
+ qrCodeData: PropTypes.object,
};
static contextTypes = {
t: PropTypes.func,
};
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.qrCodeData) {
+ if (nextProps.qrCodeData.type === 'address') {
+ const scannedAddress = nextProps.qrCodeData.values.address.toLowerCase()
+ const currentAddress = this.props.to && this.props.to.toLowerCase()
+ if (currentAddress !== scannedAddress) {
+ this.props.updateSendTo(scannedAddress)
+ this.updateGas({ to: scannedAddress })
+
+ // Here we should clear props.qrCodeData
+ }
+ }
+ }
+ }
+
updateGas ({ to: updatedToAddress, amount: value } = {}) {
const {
amount,
@@ -170,7 +187,10 @@ export default class SendTransactionScreen extends PersistentForm {
return (
<div className="page-container">
<SendHeader history={history}/>
- <SendContent updateGas={(updateData) => this.updateGas(updateData)}/>
+ <SendContent
+ updateGas={(updateData) => this.updateGas(updateData)}
+ scanQrCode={_ => this.props.scanQrCode()}
+ />
<SendFooter history={history}/>
</div>
)
diff --git a/ui/app/components/send/send.container.js b/ui/app/components/send/send.container.js
index 44ebd2792..67a441a9d 100644
--- a/ui/app/components/send/send.container.js
+++ b/ui/app/components/send/send.container.js
@@ -21,11 +21,14 @@ import {
getSendFromObject,
getSendTo,
getTokenBalance,
+ getQrCodeData,
} from './send.selectors'
import {
+ updateSendTo,
updateSendTokenBalance,
updateGasData,
setGasTotal,
+ scanQrCode,
} from '../../actions'
import {
resetSendState,
@@ -60,6 +63,7 @@ function mapStateToProps (state) {
tokenBalance: getTokenBalance(state),
tokenContract: getSelectedTokenContract(state),
tokenToFiatRate: getSelectedTokenToFiatRate(state),
+ qrCodeData: getQrCodeData(state),
}
}
@@ -89,5 +93,7 @@ function mapDispatchToProps (dispatch) {
},
updateSendErrors: newError => dispatch(updateSendErrors(newError)),
resetSendState: () => dispatch(resetSendState()),
+ scanQrCode: () => dispatch(scanQrCode()),
+ updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
}
}
diff --git a/ui/app/components/send/send.selectors.js b/ui/app/components/send/send.selectors.js
index cf07eafe1..ab3f6d34b 100644
--- a/ui/app/components/send/send.selectors.js
+++ b/ui/app/components/send/send.selectors.js
@@ -46,6 +46,7 @@ const selectors = {
getTokenExchangeRate,
getUnapprovedTxs,
transactionsSelector,
+ getQrCodeData,
}
module.exports = selectors
@@ -282,3 +283,7 @@ function transactionsSelector (state) {
: txsToRender
.sort((a, b) => b.time - a.time)
}
+
+function getQrCodeData (state) {
+ return state.appState.qrCodeData
+}
diff --git a/ui/app/components/send/tests/send-container.test.js b/ui/app/components/send/tests/send-container.test.js
index 7a9120d24..57e332780 100644
--- a/ui/app/components/send/tests/send-container.test.js
+++ b/ui/app/components/send/tests/send-container.test.js
@@ -44,6 +44,7 @@ proxyquire('../send.container.js', {
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
getSendFromObject: (s) => `mockFrom:${s}`,
getTokenBalance: (s) => `mockTokenBalance:${s}`,
+ getQrCodeData: (s) => `mockQrCodeData:${s}`,
},
'../../actions': actionSpies,
'../../ducks/send.duck': duckActionSpies,
@@ -76,6 +77,7 @@ describe('send container', () => {
tokenBalance: 'mockTokenBalance:mockState',
tokenContract: 'mockTokenContract:mockState',
tokenToFiatRate: 'mockTokenToFiatRate:mockState',
+ qrCodeData: 'mockQrCodeData:mockState',
})
})
diff --git a/ui/app/components/send/to-autocomplete/to-autocomplete.js b/ui/app/components/send/to-autocomplete/to-autocomplete.js
index 80cfa7a85..14e30e84c 100644
--- a/ui/app/components/send/to-autocomplete/to-autocomplete.js
+++ b/ui/app/components/send/to-autocomplete/to-autocomplete.js
@@ -94,11 +94,12 @@ ToAutoComplete.prototype.render = function () {
dropdownOpen,
onChange,
inError,
+ qrScanner,
} = this.props
return h('div.send-v2__to-autocomplete', {}, [
- h('input.send-v2__to-autocomplete__input', {
+ h(`input.send-v2__to-autocomplete__input${qrScanner ? '.with-qr' : ''}`, {
placeholder: this.context.t('recipientAddress'),
className: inError ? `send-v2__error-border` : '',
value: to,
@@ -108,7 +109,10 @@ ToAutoComplete.prototype.render = function () {
borderColor: inError ? 'red' : null,
},
}),
-
+ qrScanner && h(`i.fa.fa-qrcode.fa-lg.send-v2__to-autocomplete__qr-code`, {
+ style: { color: '#33333' },
+ onClick: () => this.props.scanQrCode(),
+ }),
!to && h(`i.fa.fa-caret-down.fa-lg.send-v2__to-autocomplete__down-caret`, {
style: { color: '#dedede' },
onClick: () => this.handleInputEvent(),
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index e9c872ea7..ad6199f02 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -626,6 +626,17 @@
top: 18px;
right: 12px;
}
+
+ &__qr-code {
+ position: absolute;
+ top: 21px;
+ left: 13px;
+ cursor: pointer;
+ }
+
+ &__input.with-qr {
+ padding-left: 40px;
+ }
}
&__to-autocomplete, &__memo-text-area, &__hex-data {
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index 50d8bcba7..9775638a7 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -50,7 +50,9 @@ function reduceApp (state, action) {
},
sidebarOpen: false,
alertOpen: false,
+ qrScannerOpen: false,
alertMessage: null,
+ qrCodeData: null,
networkDropdownOpen: false,
currentView: seedWords ? seedConfView : defaultView,
accountDetail: {
@@ -90,7 +92,7 @@ function reduceApp (state, action) {
sidebarOpen: false,
})
- // sidebar methods
+ // alert methods
case actions.ALERT_OPEN:
return extend(appState, {
alertOpen: true,
@@ -102,6 +104,22 @@ function reduceApp (state, action) {
alertOpen: false,
alertMessage: null,
})
+ // qr scanner methods
+ case actions.QR_SCANNER_OPEN:
+ return extend(appState, {
+ qrScannerOpen: true,
+ })
+
+ case actions.QR_SCANNER_CLOSE:
+ return extend(appState, {
+ qrScannerOpen: false,
+ })
+
+ case actions.QR_CODE_DETECTED:
+ return extend(appState, {
+ qrCodeData: action.value,
+ })
+
// modal methods:
case actions.MODAL_OPEN: