Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OCR Validation #29

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@

<link rel="icon" href="./favicon.ico">
<title>matchUS Donation Multiplier</title>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-5FB3K7M');</script>
<!-- End Google Tag Manager -->
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5FB3K7M"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

<div id="root"></div>
</body>
</html>
148 changes: 100 additions & 48 deletions src/components/receiptUpload/ACLUCloudwok.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import $ from 'jquery';
import validateReceipt from '../../validators/OCRValidator';

const success = {
title: 'Thank you for multiplying!',
Expand Down Expand Up @@ -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. <br> Please, click on the \'Send Receipt(s)\' button to submit them.';
const INVALID_RECEIPTS = 'Some receipts seem to be invalid. <br> 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));
Expand Down Expand Up @@ -85,6 +113,10 @@ const style = `
position: relative;
}

.cloudwok-upload-files {
padding-top: 10px;
}

.cloudwok-embed .dropzone .filepicker {
background: transparent;
color: #33373A;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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 = `<div>${dropzoneWarningLine1}</div><div>${dropzoneWarningLine2}</div>`;
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 = `<div>${dropzoneWarningLine1}</div><div>${dropzoneWarningLine2}</div>`;
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() {
Expand All @@ -213,12 +263,14 @@ if ( MutationObserver ) {
{style}
</style>
<div className="cloudwok-embed" data-wokid={ cloudWokId }>
<div className="cloudwok-upload-files"></div>
<form className="cloudwok-upload">
<div className="cloudwok-dropzone">
<div className="cloudwok-loading-screen">Loading, please wait...</div>
</div>
</form>
<div className="cloudwok-upload-files"></div>
<div className="feedback-holder"></div>
<div className="cloudwok-upload-message"></div>
<div className="cloudwok-tos-checkbox"></div>
</div>
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/validators/OCRValidator.js
Original file line number Diff line number Diff line change
@@ -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;