diff --git a/package.json b/package.json index 72f3288..0d50012 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,19 @@ "gh-pages": "^0.12.0", "react-scripts": "0.8.5" }, + "engines" : { + "node" : ">=6.8.0" + }, "homepage": "https://lukebelliveau.github.io/aclu-matchers", "dependencies": { "aphrodite": "^1.1.0", + "jquery": "^3.1.1", "react": "^15.4.2", "react-dom": "^15.4.2", "react-ga": "^2.1.2", "react-sortable": "^1.2.0", - "react-twitter-widgets": "^1.2.0" + "react-twitter-widgets": "^1.2.0", + "tesseract.js": "^1.0.10" }, "scripts": { "start": "react-scripts start", diff --git a/public/index.html b/public/index.html index 42753b2..654a5d5 100644 --- a/public/index.html +++ b/public/index.html @@ -26,13 +26,19 @@ matchUS Donation Multiplier + + + + height="0" width="0" style="display:none;visibility:hidden"> -
diff --git a/src/components/receiptUpload/ACLUCloudwok.js b/src/components/receiptUpload/ACLUCloudwok.js index 3777ffc..6434aaf 100644 --- a/src/components/receiptUpload/ACLUCloudwok.js +++ b/src/components/receiptUpload/ACLUCloudwok.js @@ -1,4 +1,6 @@ import React from 'react'; +import $ from 'jquery'; +import validateReceipt from '../../validators/OCRValidator'; const success = { title: 'Thank you for multiplying!', @@ -42,9 +44,35 @@ const cloudWokConfig = { "text2": "Terms of Service", "invalid": "Cannot start uploading. Please click on the checkbox first to agree with the Terms of Service." } + }, + "form": { + "button": "Send Receipt(s)", + "sent": "Receipt(s) have been received. Thanks!" } }; +const VALID_RECEIPTS = 'Your receipts seem to be valid.
Please, click on the \'Send Receipt(s)\' button to submit them.'; +const INVALID_RECEIPTS = 'Some receipts seem to be invalid.
Please, review them or click on the \'Send Receipt(s)\' button to submit them anyway.'; + +const displayFeedback = (message) => document.querySelector('.feedback-holder').innerHTML = message; +const disableSubmit = () => { + $('.btn-start-upload').attr('disabled',true); + $('.btn-start-upload').text('Please wait while we process the content of your receipt...'); +}; +const enableSubmit = () => { + $('.btn-start-upload').attr('disabled',false); + $('.btn-start-upload').text(cloudWokConfig.form.button); +}; + +const insertOCRValidationOnForm = () => { + $('form input[type=file]').on('change', async (event) => { + disableSubmit(); + const valid = await validateReceipt(event.target.files[0]); + enableSubmit(); + valid ? displayFeedback(VALID_RECEIPTS) : displayFeedback(INVALID_RECEIPTS); + }); +}; + export const startCloudwok = () => { document.querySelector(".cloudwok-embed").setAttribute("data-config", JSON.stringify(cloudWokConfig)); @@ -85,6 +113,10 @@ const style = ` position: relative; } + .cloudwok-upload-files { + padding-top: 10px; + } + .cloudwok-embed .dropzone .filepicker { background: transparent; color: #33373A; @@ -123,6 +155,10 @@ const style = ` box-shadow: 0 0 0 transparent; } + .text-right, .form-group { + display: none; + } + .btn.btn-success.filepicker { padding: 20px 0 !important; } @@ -143,67 +179,81 @@ const style = ` .fa-asterisk { display: none !important; } + .feedback-holder { + font-family: 'OpenSans'; + font-size: 18px; + } .cloudwok-embed .btn-start-upload { - background: #fff; - color: #186EAB; - border: solid 1px #135A8C; + background: #095d96; + color: white; + padding: 10px 10px; + border: none; + border-radius: 7px; + font-family: 'OpenSans'; + margin-top: 15px; + font-size: 18px; }`; const cloudWokId = process.env.NODE_ENV === 'production' ? 'r3M-' : 'GEKm'; class Cloudwok extends React.Component { + componentDidMount(){ - startCloudwok(); - -/* - CAUTION: This way, madness lies... - This waits until CloudWok is loaded, then adds the dropzone label AFTER the title (otherwise label comes first). - Let's please get rid of CloudWok ASAP. -*/ - -const insertDropzoneText = () => { - const dropzoneTitle = document.getElementsByClassName('btn btn-success filepicker')[0]; - const dropzoneWarningLine1 = 'Image files only - no PDFs please.'; - const dropzoneWarningLine2 = 'Confirmation number & amount must be clearly visible.' - const dropzoneWarning = document.createElement('div'); - dropzoneWarning.className += ' dropzone-text'; - dropzoneWarning.innerHTML = `
${dropzoneWarningLine1}
${dropzoneWarningLine2}
`; - dropzoneTitle.insertAdjacentElement('afterend', dropzoneWarning); -}; -const cloudwokSection = document.getElementsByClassName('cloudwok-embed')[0]; + startCloudwok(); + /* + CAUTION: This way, madness lies... + This waits until CloudWok is loaded, then adds the dropzone label AFTER the title (otherwise label comes first). + Let's please get rid of CloudWok ASAP. + */ -// more efficient but not supported in older browsers -if ( MutationObserver ) { - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (!mutation.addedNodes) return; - for (const i in mutation.addedNodes) { - const node = mutation.addedNodes[i]; - if (node.className === 'dropzone' ) { - insertDropzoneText(); - disconnectObserver(); - } - } - }); - }); + const insertDropzoneText = () => { + const dropzoneTitle = document.getElementsByClassName('btn btn-success filepicker')[0]; + const dropzoneWarningLine1 = 'Image files only - no PDFs please.'; + const dropzoneWarningLine2 = 'Confirmation number & amount must be clearly visible.' + const dropzoneWarning = document.createElement('div'); + dropzoneWarning.className += ' dropzone-text'; + dropzoneWarning.innerHTML = `
${dropzoneWarningLine1}
${dropzoneWarningLine2}
`; + dropzoneTitle.insertAdjacentElement('afterend', dropzoneWarning); + }; - const disconnectObserver = () => observer.disconnect(); + const cloudwokSection = document.getElementsByClassName('cloudwok-embed')[0]; - observer.observe(cloudwokSection, { - childList: true, - subtree: true, - attributes: false, - characterData: false - }); + // more efficient but not supported in older browsers + if ( MutationObserver ) { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (!mutation.addedNodes) return; + for (const i in mutation.addedNodes) { + const node = mutation.addedNodes[i]; + if (node.className === 'dropzone' ) { + insertDropzoneText(); + insertOCRValidationOnForm(); + disconnectObserver(); + } + } + }); + }); -} else { - cloudwokSection.addEventListener("DOMNodeInserted", (e) => { - if (e.target.className === 'dropzone') insertDropzoneText(); - }, false); -} + const disconnectObserver = () => observer.disconnect(); + + observer.observe(cloudwokSection, { + childList: true, + subtree: true, + attributes: false, + characterData: false + }); + + } else { + cloudwokSection.addEventListener("DOMNodeInserted", (e) => { + if (e.target.className === 'dropzone') { + insertDropzoneText(); + insertOCRValidationOnForm(); + } + }, false); + } } render() { @@ -213,12 +263,14 @@ if ( MutationObserver ) { {style}
-
Loading, please wait...
+
+
+
diff --git a/src/validators/OCRValidator.js b/src/validators/OCRValidator.js new file mode 100644 index 0000000..2e6d305 --- /dev/null +++ b/src/validators/OCRValidator.js @@ -0,0 +1,12 @@ +import Tesseract from 'tesseract.js/dist/tesseract'; + +const validateReceipt = async file => { + let valid = null; + await Tesseract.recognize(file).then(result => { + valid = result.text.toLowerCase().includes('donation'); + }); + console.log(`OCR Validation result: ${valid ? 'valid' : 'invalid'}`); + return valid; +}; + +export default validateReceipt;