1
0
mirror of https://github.com/covidpass-org/covidpass.git synced 2025-02-22 06:27:49 +01:00
This commit is contained in:
Billy Lo 2021-10-20 15:35:08 -04:00
commit ef289a4890
10 changed files with 105 additions and 176 deletions

View File

@ -1,9 +1,8 @@
import {saveAs} from 'file-saver';
import React, {FormEvent, useEffect, useRef, useState, Component} from "react";
import React, {FormEvent, useEffect, useRef, useState} from "react";
import {BrowserQRCodeReader, IScannerControls} from "@zxing/browser";
import {Result} from "@zxing/library";
import {useTranslation} from 'next-i18next';
import Link from 'next/link';
import Card from "./Card";
import Alert from "./Alert";
@ -25,8 +24,7 @@ const options = [
{ label: 'Nova Scotia', value: 'https://novascotia.flow.canimmunize.ca/en/portal'},
{ label: 'Québec', value: 'https://covid19.quebec.ca/PreuveVaccinale'},
{ label: 'Saskatchewan', value: 'https://services.saskatchewan.ca/#/login'},
{ label: 'Yukon', value: 'https://service.yukon.ca/forms/en/get-covid19-proof-of-vaccination'}
{ label: 'Yukon', value: 'https://service.yukon.ca/forms/en/get-covid19-proof-of-vaccination'},
]
function Form(): JSX.Element {
@ -63,7 +61,6 @@ function Form(): JSX.Element {
// const [warningMessages, _setWarningMessages] = useState<Array<string>>([]);
const hitcountHost = 'https://stats.vaccine-ontario.ca';
// Check if there is a translation and replace message accordingly
const setAddErrorMessage = (message: string) => {
if (!message) {
@ -83,21 +80,9 @@ function Form(): JSX.Element {
_setFileErrorMessages(Array.from(new Set([...addErrorMessages, translation !== message ? translation : message])));
};
// const setWarningMessage = (message: string) => {
// if (!message) {
// return;
// }
// const translation = t('errors:'.concat(message));
// _setWarningMessages(Array.from(new Set([...warningMessages, translation !== message ? translation : message])));
// }
const deleteAddErrorMessage = (message: string) =>{
_setAddErrorMessages(addErrorMessages.filter(item => item !== message))
}
const deleteFileErrorMessage = (message: string) =>{
_setFileErrorMessages(addErrorMessages.filter(item => item !== message))
}
// File Input ref
const inputFile = useRef<HTMLInputElement>(undefined)
@ -171,16 +156,14 @@ function Form(): JSX.Element {
// Clear out any currently-selected files
inputFile.current.value = '';
// Hide our existing pass image
document.getElementById('shc-pass-image').hidden = true;
document.getElementById('shc-image-header').hidden = true;
inputFile.current.click();
}
async function gotoOntarioHealth(e) {
e.preventDefault();
// window.open('https://covid19.ontariohealth.ca','_blank'); // this created many extra steps in mobile chrome to return to the grassroots main window... if user has many windows open, they get lost (BACK button on the same window is easier for user to return)
window.location.href = 'https://covid19.ontariohealth.ca';
}
async function goToFAQ(e) {
e.preventDefault();
window.location.href = '/faq';
@ -259,6 +242,10 @@ function Form(): JSX.Element {
)
);
// Hide our existing pass image
document.getElementById('shc-pass-image').hidden = true;
document.getElementById('shc-image-header').hidden = true;
setQrCode(undefined);
setPayloadBody(undefined);
setFile(undefined);
@ -350,7 +337,7 @@ function Form(): JSX.Element {
//TODO: merge with addToWallet for common flow
async function renderPhoto(payloadBody : PayloadBody) {
async function renderPhoto(payloadBody : PayloadBody, shouldRegister = true) {
console.log('renderPhoto');
if (!payloadBody) {
console.log('no payload body');
@ -369,9 +356,10 @@ function Form(): JSX.Element {
}
document.getElementById('shc-pass-image').hidden = false;
document.getElementById('shc-image-header').hidden = false;
console.log('made canvas visible');
const newPhotoBlob = await Photo.generateSHCPass(payloadBody);
const newPhotoBlob = await Photo.generateSHCPass(payloadBody, shouldRegister);
console.log('generated blob');
setPhotoBlob(newPhotoBlob);
}
@ -384,6 +372,10 @@ function Form(): JSX.Element {
}
}
async function refreshPhoto() {
await renderPhoto(payloadBody, false);
}
async function saveAsPhoto() {
setSaveLoading(true);
@ -407,7 +399,6 @@ function Form(): JSX.Element {
setSaveLoading(false);
} catch (e) {
Sentry.captureException(e);
setAddErrorMessage(e.message);
@ -442,7 +433,6 @@ function Form(): JSX.Element {
if (isMacOs) {
setAddErrorMessage('Reminder: iOS 15+ is needed for the Apple Wallet functionality to work with Smart Health Card')
return;
}
if (isIOS && !isSafari) {
@ -450,7 +440,6 @@ function Form(): JSX.Element {
setAddErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS');
setIsDisabledAppleWallet(true);
return;
}
// } else if (!isIOS) {
// setWarningMessage('Only Safari on iOS is officially supported for Wallet import at the moment - ' +
@ -463,38 +452,20 @@ function Form(): JSX.Element {
window.location.href = option.value;
}
function promptTextCreator(value) {
return 'Select province...';
}
return (
<div>
<form className="space-y-5" id="form" onSubmit={addToWallet}>
<Card step="1" heading={t('index:downloadReceipt')} content={
<div className="space-y-5">
<p>
{/* <Select
<div>If you need to download your proof-of-vaccination, please select your province in the drop-down to be redirected to your provincial proof-of-vaccination portal.<br/> <b>IF YOU HAVE YOUR PROOF-OF-VACCINATION, SKIP THIS STEP AND PROCEED DIRECTLY TO STEP 2</b></div>
<Select
className="dark"
defaultValue={selectedOption}
onChange={e => { setSelectedOption; gotoLink(e)}}
options={options}
/> */}
{t('index:visit')}&nbsp;
<Link href="https://covid19.ontariohealth.ca">
<a className="underline" target="_blank">
{t('index:ontarioHealth')}
</a>
</Link>&nbsp;
{t('index:downloadSignedPDF')}<br/><br/>
{t('index:reminderNotToRepeat')}
/>
</p>
<button id="ontariohealth" onClick={gotoOntarioHealth}
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
{t('index:gotoOntarioHealth')}
</button>
</div>
}/>
@ -594,11 +565,11 @@ function Form(): JSX.Element {
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
{t('index:addToWallet')}
</button>
&nbsp;&nbsp;&nbsp;&nbsp;
{/*&nbsp;&nbsp;&nbsp;&nbsp;
<button id="saveAsPhoto" type="button" disabled={saveLoading || !payloadBody} value='photo' name='action' onClick={saveAsPhoto}
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
{t('index:saveAsPhoto')}
</button>
</button>*/}
<div id="spin" className={saveLoading ? undefined : "hidden"}>
<svg className="animate-spin h-5 w-5 ml-4" viewBox="0 0 24 24">
@ -612,9 +583,54 @@ function Form(): JSX.Element {
{addErrorMessages.map((message, i) =>
<Alert message={message} key={'error-' + i} type="error" />
)}
{/* {warningMessages.map((message, i) =>
<Alert message={message} key={'warning-' + i} type="warning" />
)} */}
<div id="shc-image-header" hidden><b>To Save your Vaccination Card as a photo, please click on or save the image below:</b><br/>If the image below does not look correct and you are trying to save the photo, please click the Refresh button below - on some older devices, the image does not appear to draw correctly the first time, but refreshing once or twice should fix it. Sorry for the inconvenience.
<button id="renderPhoto" type="button" disabled={saveLoading || !payloadBody} value='renderPhoto' name='action' onClick={refreshPhoto}
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
Refresh Photo Card
</button>
</div>
<br/>
<div id="shc-pass-image" style={{backgroundColor: "white", color: "black", fontFamily: 'Arial', fontSize: 10, width: '350px', padding: '10px', border: '1px solid', margin: '0px'}} hidden>
<table style={{verticalAlign: "middle"}}>
<tbody>
<tr>
<td><img src='shield-black.svg' width='50' height='50' /></td>
<td style={{fontSize: 20, width: 280}}>
<span style={{marginLeft: '11px', whiteSpace: 'nowrap'}}><b>COVID-19 Vaccination Card</b></span><br/>
<span style={{marginLeft: '11px'}}><b id='shc-card-origin'></b></span>
</td>
</tr>
</tbody>
</table>
<br/>
<br/>
<div style={{fontSize:14, textAlign: 'center'}}>
<span id='shc-card-name' ></span>&nbsp;&nbsp;&nbsp;&nbsp;(<span id='shc-card-dob'></span>)
</div>
<br/>
<br/>
<table style={{textAlign: "center", width: "100%"}}>
<tbody>
<tr>
<td id='shc-card-vaccine-name-1'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-name-2'></td>
</tr>
<tr>
<td id='shc-card-vaccine-date-1'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-date-2'></td>
</tr>
<tr id='extraRow1' hidden>
<td id='shc-card-vaccine-name-3'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-name-4'></td>
</tr>
<tr id='extraRow2' hidden>
<td id='shc-card-vaccine-date-3'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-date-4'></td>
</tr>
</tbody>
</table>
<div id='shc-card-vaccine' style={{width:'63%', display:'block', marginLeft: 'auto', marginRight: 'auto'}}></div>
<div id='shc-qrcode' style={{width:'63%', display:'block', marginLeft: 'auto', marginRight: 'auto'}}></div>
<br/>
</div>{<a id="shc-pass-img-link" download="vaccination-card.png"><img id="shc-pass-img"/></a>}
</div>
}/>
@ -641,7 +657,6 @@ function Form(): JSX.Element {
</div>
}/>
</form>
<canvas id="canvas" style={{ display: "none" }} />
</div>
)
}

View File

@ -34,114 +34,13 @@ function Page(props: PageProps): JSX.Element {
<a href="https://github.com/billylo1/covidpass" className="underline">{t('common:gitHub')}</a>
<a href="https://vaccine-ontario.ca" className="underline">{t('common:returnToMainSite')}</a>
</nav>
<div className="flex pt-4 flex-row space-x-4 justify-center text-md flex-wrap">Last updated: 2021-10-18 (v2.1.3)</div>
<div className="flex pt-4 flex-row space-x-4 justify-center text-md flex-wrap">Last updated: 2021-10-19 (v2.1.7)</div>
</footer>
</main>
</div>
<br/>
<br/>
<br/>
<div id="pass-image" style={{backgroundColor: "orangered", color: "white", fontFamily: 'Arial', fontSize: 10, width: '350px', padding: '10px'}} hidden>
<table style={{verticalAlign: "middle"}}>
<tbody>
<tr>
<td><img src='shield4.svg' width='50' height='50' /></td>
<td style={{fontSize: 20, width: 280}}><span style={{marginLeft: '11px'}}><b>Vaccination Receipt</b></span></td>
</tr>
</tbody>
</table>
<br/>
<br/>
<div style={{height:12}}><b>VACCINE</b></div>
<div id='vaccineName' style={{fontSize:28}}></div>
<br/>
<br/>
<table style={{fontSize:12, border: 0 }}>
<tbody>
<tr>
<td style={{width: 220}}><b>AUTHORIZED ORGANIZATION</b></td>
<td><b>VACC. DATE</b></td>
</tr>
<tr>
<td id='organization' style={{width: 220}}></td>
<td id='vaccinationDate' style={{width:120}}></td>
</tr>
<tr id='extraRow2' hidden>
<td id='organization2' style={{width: 220}}></td>
<td id='vaccinationDate2' style={{width:120}}></td>
</tr>
<tr id='extraRow1' hidden>
<td id='organization1' style={{width: 220}}></td>
<td id='vaccinationDate1' style={{width:120}}></td>
</tr>
<tr style={{height: 20}}></tr>
<tr>
<td><b>NAME</b></td>
<td><b>DATE OF BIRTH</b></td>
</tr>
<tr>
<td id='name' style={{fontSize: 12}}></td>
<td id='dob' style={{fontSize: 12}}></td>
</tr>
<tr style={{height: 20}}></tr>
<tr>
<td><b></b></td>
<td><b>QR CODE EXPIRY</b></td>
</tr>
<tr>
<td id='null' style={{fontSize: 12}}></td>
<td id='expiry' style={{fontSize: 12}}>2021-10-22</td>
</tr>
</tbody>
</table>
<br/>
<br/>
<div id='qrcode' style={{width:'63%', display:'block', marginLeft: 'auto', marginRight: 'auto'}}></div>
<br/>
<br/>
</div>
<div id="shc-pass-image" style={{backgroundColor: "white", color: "black", fontFamily: 'Arial', fontSize: 10, width: '350px', padding: '10px'}} hidden>
<table style={{verticalAlign: "middle"}}>
<tbody>
<tr>
<td><img src='shield-black.svg' width='50' height='50' /></td>
<td style={{fontSize: 20, width: 280}}>
<span style={{marginLeft: '11px', whiteSpace: 'nowrap'}}><b>COVID-19 Vaccination Card</b></span><br/>
<span style={{marginLeft: '11px'}}><b id='shc-card-origin'></b></span>
</td>
</tr>
</tbody>
</table>
<br/>
<br/>
<div style={{fontSize:14, textAlign: 'center'}}>
<span id='shc-card-name' ></span>&nbsp;&nbsp;&nbsp;&nbsp;(<span id='shc-card-dob'></span>)
</div>
<br/>
<br/>
<table style={{textAlign: "center", width: "100%"}}>
<tbody>
<tr>
<td id='shc-card-vaccine-name-1'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-name-2'></td>
</tr>
<tr>
<td id='shc-card-vaccine-date-1'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-date-2'></td>
</tr>
<tr id='extraRow1' hidden>
<td id='shc-card-vaccine-name-3'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-name-4'></td>
</tr>
<tr id='extraRow2' hidden>
<td id='shc-card-vaccine-date-3'></td>&nbsp;&nbsp;<td id='shc-card-vaccine-date-4'></td>
</tr>
</tbody>
</table>
<div id='shc-card-vaccine' style={{width:'63%', display:'block', marginLeft: 'auto', marginRight: 'auto'}}></div>
<div id='shc-qrcode' style={{width:'63%', display:'block', marginLeft: 'auto', marginRight: 'auto'}}></div>
<br/>
<br/>
</div>
<canvas id="canvas" style={{display: 'none'}}/>
</div>
)

View File

@ -1,6 +1,6 @@
{
"name": "grassroots_covidpass",
"version": "2.1.3",
"version": "2.1.7",
"author": "Billy Lo <billy@vaccine-ontario.ca>",
"license": "MIT",
"private": false,
@ -33,7 +33,7 @@
"file-saver": "^2.0.5",
"fs-extra": "^10.0.0",
"fs.promises": "^0.1.2",
"html-to-image": "^1.7.0",
"html-to-image": "^1.9.0",
"html2canvas": "^1.3.2",
"jpeg-js": "^0.4.3",
"jsqr": "^1.4.0",

View File

@ -8,7 +8,7 @@ startCamera: Start Camera
openFile: Select File
foundQrCode: Found valid Smart Health Card QR Code!
downloadReceipt: Download official receipt or take a picture of your QR code
visit: If you are in Ontario, please visit
visit: If you need to download your proof-of-vaccination, please select your province in the drop-down to be redirected to your provincial proof-of-vaccination portal. IF YOU HAVE YOUR PROOF-OF-VACCINATION, SKIP THIS STEP AND PROCEED DIRECTLY TO STEP 2
ontarioHealth: Ontario Ministry of Health
gotoOntarioHealth: Go to Ontario Ministry of Health
downloadSignedPDF: and enter your information to display your official vaccination receipt. Press the Share Icon at the bottom, "Save As Files" to store it onto your iPhone. You can also take a picture or screenshot of your QR code with your phone (please make sure the picture is good-quality, is not blurry, and captures ALL of the QR code!), or scan it directly with your camera below in step 2

View File

@ -112,7 +112,8 @@ export async function decodedStringToReceipt(decoded: object) : Promise<SHCRecei
case 'Patient':
// Assume there is only one name on the card.
const nameObj = resource.name[0];
name = `${nameObj.given.join(' ')} ${nameObj.family}`;
const givenName = (nameObj.given ? nameObj.given.join(' ') : '');
name = `${givenName} ${nameObj.family}`;
dateOfBirth = resource.birthDate;
console.log('Detected Patient resource, added name and birthdate');

View File

@ -69,7 +69,7 @@ export class PassData {
// Create Payload
try {
const results = await PassPhotoCommon.preparePayload(payloadBody, numDose);
const results = await PassPhotoCommon.preparePayload(payloadBody, true, numDose);
const payload = results.payload;
// Create pass data

View File

@ -98,7 +98,7 @@ function generateSHCRegisterPayload(payload : Payload) : any {
export class PassPhotoCommon {
static async preparePayload(payloadBody: PayloadBody, numDose: number = 0) : Promise<PackageResult> {
static async preparePayload(payloadBody: PayloadBody, shouldRegister = true, numDose: number = 0) : Promise<PackageResult> {
console.log('preparePayload');
@ -106,9 +106,11 @@ export class PassPhotoCommon {
const payload: Payload = new Payload(payloadBody, numDose);
payload.serialNumber = uuid4();
const wasSuccess = await registerPass(payload);
if (!wasSuccess) {
return Promise.reject(`Error while trying to register pass!`);
if (shouldRegister) {
const wasSuccess = await registerPass(payload);
if (!wasSuccess) {
return Promise.reject(`Error while trying to register pass!`);
}
}
// Create QR Code Object

View File

@ -219,7 +219,4 @@ function processSHCReceipt(receipt: SHCReceipt, generic: PassDictionary) {
)
}
}

View File

@ -1,6 +1,6 @@
import {Constants} from "./constants";
import {PayloadBody} from "./payload";
import { toBlob } from 'html-to-image';
import { toBlob, toPng } from 'html-to-image';
import {QrCode,PassPhotoCommon} from './passphoto-common';
import { Encoder, QRByte, QRNumeric, ErrorCorrectionLevel } from '@nuintun/qrcode';
@ -21,7 +21,7 @@ export class Photo {
// Create Payload
try {
console.log('generatePass');
const results = await PassPhotoCommon.preparePayload(payloadBody, numDose);
const results = await PassPhotoCommon.preparePayload(payloadBody, true, numDose);
const payload = results.payload;
const qrCode = results.qrCode;
@ -92,11 +92,11 @@ export class Photo {
}
}
static async generateSHCPass(payloadBody: PayloadBody): Promise<Blob> {
static async generateSHCPass(payloadBody: PayloadBody, shouldRegister = true): Promise<Blob> {
// Create Payload
try {
console.log('generateSHCPass');
const results = await PassPhotoCommon.preparePayload(payloadBody);
const results = await PassPhotoCommon.preparePayload(payloadBody, shouldRegister);
const qrCode = results.qrCode;
const body = document.getElementById('shc-pass-image');
body.hidden = false;
@ -137,7 +137,22 @@ export class Photo {
qrImage.src = qrcode.toDataURL(2, 15);
document.getElementById('shc-qrcode').appendChild(qrImage);
return await toBlob(body);
// Fix for Safari photo rendering issue?
const pngData = await toPng(body);
document.getElementById('shc-pass-img')['src'] = pngData;
const imgLink = document.getElementById('shc-pass-img-link');
imgLink['href'] = pngData;
const selectedReceipt = payloadBody.shcReceipt;
const filenameDetails = selectedReceipt.cardOrigin.replace(' ', '-');
const passName = selectedReceipt.name.replace(' ', '-');
imgLink['download'] = `grassroots-receipt-${passName}-${filenameDetails}.png`;
const retBlob = await toBlob(body);
body.hidden = true;
return retBlob;
} catch (e) {
return Promise.reject(e);

View File

@ -3,7 +3,7 @@ import { Integrations } from '@sentry/tracing';
export const initSentry = () => {
SentryModule.init({
release: 'grassroots_covidpass@2.1.3', // App version. Needs to be manually updated as we go unless we make the build smarter
release: 'grassroots_covidpass@2.1.7', // App version. Needs to be manually updated as we go unless we make the build smarter
dsn: 'https://7120dcf8548c4c5cb148cdde2ed6a778@o1015766.ingest.sentry.io/5981424',
integrations: [
new Integrations.BrowserTracing(),