diff --git a/react/package.json b/react/package.json index 81b81d2..656d1fd 100644 --- a/react/package.json +++ b/react/package.json @@ -38,10 +38,12 @@ "bluebird": "^3.5.0", "express": "^4.14.0", "file-loader": "^0.10.0", + "qrcode.react": "^0.7.1", "rc-tree": "^1.4.6", "react": "^15.3.1", "react-dom": "^15.3.1", "react-hot-loader": "^1.3.0", + "react-qr-reader": "^1.1.3", "react-redux": "^5.0.3", "react-router": "^3.0.2", "react-router-redux": "^4.0.4", diff --git a/react/src/components/dashboard/qrModal/qrModal.js b/react/src/components/dashboard/qrModal/qrModal.js new file mode 100755 index 0000000..0866cc9 --- /dev/null +++ b/react/src/components/dashboard/qrModal/qrModal.js @@ -0,0 +1,78 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Store from '../../../store'; +import { translate } from '../../../translate/translate'; +import QrReader from 'react-qr-reader' +import { + QRModalRender, + QRModalReaderRender +} from './qrModal.render'; + +class QRModal extends React.Component { + constructor(props) { + super(props); + this.state = { + modalIsOpen: false, + error: null, + }; + this.openModal = this.openModal.bind(this); + this.closeModal = this.closeModal.bind(this); + this.handleScan = this.handleScan.bind(this); + this.handleError = this.handleError.bind(this); + document.body.addEventListener('click', this.closeModal); + } + + handleScan(data) { + if (data !== null) { + if (this.props.mode === 'scan') { + this.props.setRecieverFromScan(data) + } + this.closeModal(); + } + } + + handleError(err) { + this.setState({ + error: err, + }); + } + + openModal() { + this.setState({ + modalIsOpen: true + }); + + if (this.props.mode === 'scan') { + ReactDOM.render( + , document.getElementById('webcam')); + } + } + + closeModal() { + this.setState({ + modalIsOpen: false, + }); + + if (this.props.mode === 'scan') { + ReactDOM.unmountComponentAtNode(document.getElementById('webcam')); + } + } + + render() { + if (this.props.mode === 'scan') { + return QRModalReaderRender.call(this); + } else { + return QRModalRender.call(this); + } + } +} + +export default QRModal; \ No newline at end of file diff --git a/react/src/components/dashboard/qrModal/qrModal.render.js b/react/src/components/dashboard/qrModal/qrModal.render.js new file mode 100644 index 0000000..0524407 --- /dev/null +++ b/react/src/components/dashboard/qrModal/qrModal.render.js @@ -0,0 +1,82 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import QRCode from 'qrcode.react'; + +export const QRModalRender = function () { + return ( + + + + +
+
+
+
+ +

{ translate('INDEX.SCAN_QR_CODE') }

+
+
+
+
+ +
+
+
+
+
+
+
+
+ ); +}; + +export const QRModalReaderRender = function () { + return ( + + +
+
+
+
+ +

{ translate('INDEX.SCAN_QRCODE_WEBCAM') }

+
+
+
+
+
+ { this.state.error } +
+
+
+
+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/react/src/components/dashboard/receiveCoin/receiveCoin.render.js b/react/src/components/dashboard/receiveCoin/receiveCoin.render.js index f5acf16..108fb88 100644 --- a/react/src/components/dashboard/receiveCoin/receiveCoin.render.js +++ b/react/src/components/dashboard/receiveCoin/receiveCoin.render.js @@ -1,5 +1,6 @@ import React from 'react'; import { translate } from '../../../translate/translate'; +import QRModal from '../qrModal/qrModal'; export const AddressActionsBasiliskModeRender = function(address) { return ( @@ -25,6 +26,9 @@ export const AddressActionsBasiliskModeRender = function(address) { onClick={ () => this._validateAddressBasilisk(address) }> + ); }; @@ -41,6 +45,9 @@ export const AddressActionsNonBasiliskModeRender = function(address, type) { onClick={ () => this._copyCoinAddress(address) }> { translate('INDEX.COPY') } + ); }; @@ -140,4 +147,5 @@ export const ReceiveCoinRender = function() { ); -}; \ No newline at end of file +}; + diff --git a/react/src/components/dashboard/sendCoin/sendCoin.js b/react/src/components/dashboard/sendCoin/sendCoin.js index 3d41a4b..39566aa 100644 --- a/react/src/components/dashboard/sendCoin/sendCoin.js +++ b/react/src/components/dashboard/sendCoin/sendCoin.js @@ -55,6 +55,7 @@ class SendCoin extends React.Component { currentStackLength: 0, totalStackLength: 0, utxoMethodInProgress: false, + isCameraFeatureDetected: false, }; this.updateInput = this.updateInput.bind(this); this.handleBasiliskSend = this.handleBasiliskSend.bind(this); @@ -64,15 +65,44 @@ class SendCoin extends React.Component { this.toggleSendAPIType = this.toggleSendAPIType.bind(this); this._fetchNewUTXOData = this._fetchNewUTXOData.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); + this.setRecieverFromScan = this.setRecieverFromScan.bind(this); + this.detectCamera = this.detectCamera.bind(this); socket.on('messages', msg => this.updateSocketsData(msg)); } + // test device camera capabilities + detectCamera() { + const _getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; + + _getUserMedia( + { 'video': true }, + function() { + this.setState({ + isCameraFeatureDetected: true, + }); + }, + function() { + console.warn('this device doesn\'t have camera!'); + } + ); + } + + setRecieverFromScan(receiver) { + this.setState({ + sendTo: receiver + }); + + document.getElementById('edexcoinSendTo').focus(); + } + componentWillMount() { document.addEventListener( 'click', this.handleClickOutside, false ); + + this.detectCamera(); } componentWillUnmount() { @@ -173,7 +203,7 @@ class SendCoin extends React.Component { this, refreshCacheData, isReadyToUpdate, - waitUntilCallIsFinished, + waitUntilCallIsFinished, timestamp ); } @@ -745,7 +775,6 @@ class SendCoin extends React.Component { return null; } - render() { if (this.props.ActiveCoin && this.props.ActiveCoin.send && diff --git a/react/src/components/dashboard/sendCoin/sendCoin.render.js b/react/src/components/dashboard/sendCoin/sendCoin.render.js index 9e313ee..3745132 100644 --- a/react/src/components/dashboard/sendCoin/sendCoin.render.js +++ b/react/src/components/dashboard/sendCoin/sendCoin.render.js @@ -5,6 +5,8 @@ import { secondsToString } from '../../../util/time'; +import QRModal from '../qrModal/qrModal'; + export const UTXOCacheInfoRender = function(refreshCacheData, isReadyToUpdate, waitUntilCallIsFinished, timestamp) { return (
@@ -137,7 +139,7 @@ export const OASendUIRender = function() { export const SendApiTypeSelectorRender = function() { return (
-
+
+ { this.stateisCameraFeatureDetected && +
+ +
+ }
); }; diff --git a/react/src/translate/en.js b/react/src/translate/en.js index 8a47b93..4d5937c 100644 --- a/react/src/translate/en.js +++ b/react/src/translate/en.js @@ -249,7 +249,10 @@ export const _lang = { 'INFO': 'Info', 'ENTER': 'Enter', 'ADDR_SM': 'address', - 'ACTIVATING': 'Activating' + 'ACTIVATING': 'Activating', + 'QRCODE': 'Show QR code', + 'SCAN_QR_CODE': 'Scan QR Code', + 'SCAN_QRCODE_WEBCAM': 'Scan QR Code with webcam' }, 'ATOMIC': { 'RAW_OUTPUT': 'Raw Output',