2021-07-02 20:55:26 +02:00
|
|
|
import {saveAs} from 'file-saver';
|
2021-07-01 00:49:05 +02:00
|
|
|
import React, {FormEvent, useEffect, useRef, useState} from "react";
|
2021-07-28 14:28:45 +02:00
|
|
|
import {BrowserQRCodeReader, IScannerControls} from "@zxing/browser";
|
2021-07-01 00:49:05 +02:00
|
|
|
import {Result} from "@zxing/library";
|
2021-07-02 20:55:26 +02:00
|
|
|
import {useTranslation} from 'next-i18next';
|
2021-07-03 13:33:12 +02:00
|
|
|
import Link from 'next/link';
|
2021-07-02 20:55:26 +02:00
|
|
|
|
|
|
|
import Card from "./Card";
|
|
|
|
import Alert from "./Alert";
|
2021-07-27 03:09:51 +02:00
|
|
|
import Check from './Check';
|
2021-07-01 00:49:05 +02:00
|
|
|
import {PayloadBody} from "../src/payload";
|
2021-08-26 00:15:12 -04:00
|
|
|
import {getPayloadBodyFromFile} from "../src/process";
|
2021-07-01 00:49:05 +02:00
|
|
|
import {PassData} from "../src/pass";
|
2021-09-09 00:27:43 -04:00
|
|
|
import {Photo} from "../src/photo";
|
2021-07-28 14:28:45 +02:00
|
|
|
import {COLORS} from "../src/colors";
|
2021-07-29 00:02:51 +02:00
|
|
|
import Colors from './Colors';
|
2021-09-14 20:33:27 -04:00
|
|
|
import {isChrome, isIOS, isIPad13, isMacOs, isSafari, deviceDetect, osName, osVersion} from 'react-device-detect';
|
|
|
|
import * as Sentry from '@sentry/react';
|
2021-09-19 12:21:59 -04:00
|
|
|
import { counterReset } from 'html2canvas/dist/types/css/property-descriptors/counter-reset';
|
2021-09-21 23:17:45 -04:00
|
|
|
import { color } from 'html2canvas/dist/types/css/types/color';
|
2021-09-23 20:12:07 -04:00
|
|
|
import Bullet from './Bullet';
|
2021-09-09 17:08:49 -04:00
|
|
|
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
function Form(): JSX.Element {
|
2021-07-28 14:28:45 +02:00
|
|
|
const {t} = useTranslation(['index', 'errors', 'common']);
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Whether camera is open or not
|
|
|
|
const [isCameraOpen, setIsCameraOpen] = useState<boolean>(false);
|
2021-06-26 00:28:35 +02:00
|
|
|
|
2021-07-28 14:28:45 +02:00
|
|
|
// Currently selected color
|
|
|
|
const [selectedColor, setSelectedColor] = useState<COLORS>(COLORS.WHITE);
|
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Global camera controls
|
2021-07-28 14:28:45 +02:00
|
|
|
const [globalControls, setGlobalControls] = useState<IScannerControls>(undefined);
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Currently selected QR Code / File. Only one of them is set.
|
|
|
|
const [qrCode, setQrCode] = useState<Result>(undefined);
|
|
|
|
const [file, setFile] = useState<File>(undefined);
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-07-01 01:27:17 +02:00
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-09-19 12:21:59 -04:00
|
|
|
const [generated, setGenerated] = useState<boolean>(false); // this flag represents the file has been used to generate a pass
|
2021-09-23 16:49:07 -04:00
|
|
|
|
|
|
|
const [isDisabledAppleWallet, setIsDisabledAppleWallet] = useState<boolean>(false);
|
|
|
|
const [errorMessages, _setErrorMessages] = useState<Array<string>>([]);
|
2021-09-24 01:53:16 -04:00
|
|
|
const [warningMessages, _setWarningMessages] = useState<Array<string>>([]);
|
2021-09-19 12:21:59 -04:00
|
|
|
const hitcountHost = 'https://stats.vaccine-ontario.ca';
|
|
|
|
|
|
|
|
|
2021-07-02 20:55:26 +02:00
|
|
|
// Check if there is a translation and replace message accordingly
|
|
|
|
const setErrorMessage = (message: string) => {
|
2021-09-24 13:19:33 -04:00
|
|
|
if (!message) {
|
2021-07-24 22:56:42 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-02 20:55:26 +02:00
|
|
|
const translation = t('errors:'.concat(message));
|
2021-09-23 16:49:07 -04:00
|
|
|
_setErrorMessages(Array.from(new Set([...errorMessages, translation !== message ? translation : message])));
|
2021-07-02 20:55:26 +02:00
|
|
|
};
|
|
|
|
|
2021-09-24 01:53:16 -04:00
|
|
|
const setWarningMessage = (message: string) => {
|
2021-09-24 13:19:10 -04:00
|
|
|
if (!message) {
|
2021-09-24 01:53:16 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const translation = t('errors:'.concat(message));
|
|
|
|
_setWarningMessages(Array.from(new Set([...warningMessages, translation !== message ? translation : message])));
|
|
|
|
}
|
|
|
|
|
2021-09-23 16:49:07 -04:00
|
|
|
const deleteErrorMessage = (message: string) =>{
|
|
|
|
console.log(errorMessages)
|
|
|
|
_setErrorMessages(errorMessages.filter(item => item !== message))
|
|
|
|
}
|
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// File Input ref
|
|
|
|
const inputFile = useRef<HTMLInputElement>(undefined)
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Add event listener to listen for file change events
|
2021-06-30 03:13:01 +02:00
|
|
|
useEffect(() => {
|
|
|
|
if (inputFile && inputFile.current) {
|
|
|
|
inputFile.current.addEventListener('input', () => {
|
|
|
|
let selectedFile = inputFile.current.files[0];
|
|
|
|
if (selectedFile !== undefined) {
|
|
|
|
setQrCode(undefined);
|
|
|
|
setFile(selectedFile);
|
2021-09-19 12:21:59 -04:00
|
|
|
setGenerated(false);
|
2021-09-23 16:49:07 -04:00
|
|
|
deleteErrorMessage(t('errors:'.concat('noFileOrQrCode')));
|
|
|
|
checkBrowserType();
|
2021-06-30 03:13:01 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-09-14 20:33:27 -04:00
|
|
|
checkBrowserType();
|
2021-06-30 03:13:01 +02:00
|
|
|
}, [inputFile])
|
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Show file Dialog
|
2021-06-30 03:13:01 +02:00
|
|
|
async function showFileDialog() {
|
|
|
|
inputFile.current.click();
|
2021-06-26 21:53:08 +02:00
|
|
|
}
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-09-23 20:12:07 -04:00
|
|
|
async function gotoOntarioHealth(e) {
|
|
|
|
e.preventDefault();
|
2021-08-27 01:15:49 -04:00
|
|
|
window.location.href = 'https://covid19.ontariohealth.ca';
|
|
|
|
}
|
2021-09-23 20:12:07 -04:00
|
|
|
async function goToFAQ(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
window.location.href = '/faq';
|
|
|
|
}
|
2021-08-27 01:15:49 -04:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Hide camera view
|
2021-06-30 03:13:01 +02:00
|
|
|
async function hideCameraView() {
|
|
|
|
if (globalControls !== undefined) {
|
|
|
|
globalControls.stop();
|
2021-06-27 17:29:29 -07:00
|
|
|
}
|
2021-06-30 03:13:01 +02:00
|
|
|
setIsCameraOpen(false);
|
|
|
|
}
|
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Show camera view
|
2021-06-30 03:13:01 +02:00
|
|
|
async function showCameraView() {
|
2021-07-01 00:49:05 +02:00
|
|
|
// Create new QR Code Reader
|
2021-07-25 02:32:28 +02:00
|
|
|
const codeReader = new BrowserQRCodeReader();
|
2021-06-30 03:13:01 +02:00
|
|
|
|
|
|
|
// Needs to be called before any camera can be accessed
|
2021-07-25 02:14:09 +02:00
|
|
|
let deviceList: MediaDeviceInfo[];
|
|
|
|
|
|
|
|
try {
|
|
|
|
deviceList = await BrowserQRCodeReader.listVideoInputDevices();
|
|
|
|
} catch (e) {
|
|
|
|
setErrorMessage('noCameraAccess');
|
|
|
|
return;
|
|
|
|
}
|
2021-07-28 14:28:45 +02:00
|
|
|
|
2021-07-25 02:32:28 +02:00
|
|
|
// Check if camera device is present
|
2021-07-24 22:56:42 +02:00
|
|
|
if (deviceList.length == 0) {
|
2021-07-25 01:37:58 +02:00
|
|
|
setErrorMessage("noCameraFound");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-30 03:13:01 +02:00
|
|
|
// Get preview Element to show camera stream
|
2021-07-01 00:49:05 +02:00
|
|
|
const previewElem: HTMLVideoElement = document.querySelector('#cameraPreview');
|
|
|
|
|
|
|
|
// Set Global controls
|
|
|
|
setGlobalControls(
|
|
|
|
// Start decoding from video device
|
|
|
|
await codeReader.decodeFromVideoDevice(undefined,
|
|
|
|
previewElem,
|
|
|
|
(result, error, controls) => {
|
|
|
|
if (result !== undefined) {
|
|
|
|
setQrCode(result);
|
|
|
|
setFile(undefined);
|
|
|
|
|
|
|
|
controls.stop();
|
|
|
|
|
|
|
|
// Reset
|
|
|
|
setGlobalControls(undefined);
|
|
|
|
setIsCameraOpen(false);
|
|
|
|
}
|
2021-07-25 01:53:02 +02:00
|
|
|
if (error !== undefined) {
|
2021-07-25 02:14:09 +02:00
|
|
|
setErrorMessage(error.message);
|
2021-07-25 01:53:02 +02:00
|
|
|
}
|
2021-07-01 00:49:05 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
setIsCameraOpen(true);
|
|
|
|
}
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-09-19 12:21:59 -04:00
|
|
|
async function incrementCount() {
|
|
|
|
try {
|
|
|
|
if (typeof generated == undefined || !generated) {
|
|
|
|
|
|
|
|
const request = `${hitcountHost}/count?url=pass.vaccine-ontario.ca`;
|
2021-09-26 19:05:02 -04:00
|
|
|
//console.log(request);
|
2021-09-19 12:21:59 -04:00
|
|
|
|
|
|
|
let response = await fetch(request);
|
2021-09-26 19:05:02 -04:00
|
|
|
//console.log(response);
|
2021-09-19 12:21:59 -04:00
|
|
|
|
|
|
|
setGenerated(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
return Promise.reject(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
// Add Pass to wallet
|
|
|
|
async function addToWallet(event: FormEvent<HTMLFormElement>) {
|
2021-08-27 01:15:49 -04:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
event.preventDefault();
|
2021-07-01 01:27:17 +02:00
|
|
|
setLoading(true);
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
if (!file && !qrCode) {
|
2021-07-02 20:55:26 +02:00
|
|
|
setErrorMessage('noFileOrQrCode')
|
2021-07-01 01:27:17 +02:00
|
|
|
setLoading(false);
|
2021-07-01 00:49:05 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-07-28 14:28:45 +02:00
|
|
|
const color = selectedColor;
|
2021-07-01 00:49:05 +02:00
|
|
|
let payloadBody: PayloadBody;
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
try {
|
|
|
|
if (file) {
|
2021-09-09 00:27:43 -04:00
|
|
|
|
2021-09-25 21:47:48 -04:00
|
|
|
//console.log('> get payload');
|
2021-07-01 00:49:05 +02:00
|
|
|
payloadBody = await getPayloadBodyFromFile(file, color);
|
2021-09-25 21:47:48 -04:00
|
|
|
|
|
|
|
const passName = payloadBody.receipt.name.replace(' ', '-');
|
|
|
|
const vaxName = payloadBody.receipt.vaccineName.replace(' ', '-');
|
|
|
|
const passDose = payloadBody.receipt.numDoses;
|
|
|
|
const covidPassFilename = `grassroots-receipt-${passName}-${vaxName}-${passDose}.pkpass`;
|
|
|
|
|
|
|
|
//console.log('> increment count');
|
2021-09-19 12:21:59 -04:00
|
|
|
await incrementCount();
|
|
|
|
|
2021-09-25 21:47:48 -04:00
|
|
|
//console.log('> generatePass');
|
2021-08-27 01:15:49 -04:00
|
|
|
let pass = await PassData.generatePass(payloadBody);
|
2021-09-21 20:01:38 -04:00
|
|
|
|
2021-09-25 21:47:48 -04:00
|
|
|
//console.log('> create blob');
|
2021-08-27 01:15:49 -04:00
|
|
|
const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
|
2021-09-21 20:01:38 -04:00
|
|
|
|
2021-09-25 21:47:48 -04:00
|
|
|
//console.log(`> save blob as ${covidPassFilename}`);
|
|
|
|
saveAs(passBlob, covidPassFilename);
|
2021-08-27 01:15:49 -04:00
|
|
|
setLoading(false);
|
|
|
|
}
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
|
|
|
|
} catch (e) {
|
2021-09-21 23:17:45 -04:00
|
|
|
|
2021-09-23 05:10:11 -04:00
|
|
|
if (e != undefined) {
|
|
|
|
console.error(e);
|
|
|
|
|
|
|
|
Sentry.captureException(e);
|
|
|
|
|
|
|
|
if (e.message != undefined) {
|
|
|
|
setErrorMessage(e.message);
|
|
|
|
} else {
|
|
|
|
setErrorMessage("Unable to continue.");
|
|
|
|
}
|
2021-09-21 23:17:45 -04:00
|
|
|
|
|
|
|
} else {
|
2021-09-23 05:10:11 -04:00
|
|
|
setErrorMessage("Unexpected error. Sorry.");
|
2021-09-21 23:17:45 -04:00
|
|
|
|
2021-09-23 05:10:11 -04:00
|
|
|
}
|
2021-07-01 01:27:17 +02:00
|
|
|
setLoading(false);
|
2021-09-23 05:10:11 -04:00
|
|
|
|
2021-07-01 00:49:05 +02:00
|
|
|
}
|
2021-06-27 17:29:29 -07:00
|
|
|
}
|
2021-06-30 03:13:01 +02:00
|
|
|
|
2021-09-19 12:21:59 -04:00
|
|
|
//TODO: merge with addToWallet for common flow
|
|
|
|
|
2021-09-09 00:27:43 -04:00
|
|
|
async function saveAsPhoto() {
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
if (!file && !qrCode) {
|
2021-09-23 16:49:07 -04:00
|
|
|
setErrorMessage('noFileOrQrCode');
|
2021-09-09 00:27:43 -04:00
|
|
|
setLoading(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let payloadBody: PayloadBody;
|
|
|
|
|
|
|
|
try {
|
2021-09-21 23:17:45 -04:00
|
|
|
payloadBody = await getPayloadBodyFromFile(file, COLORS.GREEN);
|
2021-09-19 12:21:59 -04:00
|
|
|
await incrementCount();
|
|
|
|
|
2021-09-09 16:24:24 -04:00
|
|
|
let photoBlob = await Photo.generatePass(payloadBody);
|
|
|
|
saveAs(photoBlob, 'pass.png');
|
2021-09-19 12:21:59 -04:00
|
|
|
|
2021-09-09 00:27:43 -04:00
|
|
|
// need to clean up
|
|
|
|
const qrcodeElement = document.getElementById('qrcode');
|
|
|
|
const svg = qrcodeElement.firstChild;
|
|
|
|
qrcodeElement.removeChild(svg);
|
|
|
|
const body = document.getElementById('pass-image');
|
|
|
|
body.hidden = true;
|
|
|
|
|
|
|
|
setLoading(false);
|
|
|
|
} catch (e) {
|
2021-09-14 20:33:27 -04:00
|
|
|
Sentry.captureException(e);
|
2021-09-09 00:27:43 -04:00
|
|
|
setErrorMessage(e.message);
|
|
|
|
setLoading(false);
|
|
|
|
}
|
|
|
|
}
|
2021-09-23 16:49:07 -04:00
|
|
|
const verifierLink = () => <li className="flex flex-row items-center">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mx-2 fill-current text-green-500" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
|
|
|
</svg>
|
|
|
|
<p>
|
|
|
|
{t('verifierLink')}
|
|
|
|
<Link href="https://verifier.vaccine-ontario.ca/">
|
|
|
|
<a className="underline">verifier.vaccine-ontario.ca </a>
|
|
|
|
</Link>
|
|
|
|
</p>
|
|
|
|
</li>
|
|
|
|
|
|
|
|
function checkBrowserType() {
|
2021-09-09 17:08:49 -04:00
|
|
|
|
2021-09-23 11:59:20 -04:00
|
|
|
// if (isIPad13) {
|
|
|
|
// setErrorMessage('Sorry. Apple does not support the use of Wallet on iPad. Please use iPhone/Safari.');
|
2021-09-23 16:49:07 -04:00
|
|
|
// setIsDisabledAppleWallet(true);
|
2021-09-23 11:59:20 -04:00
|
|
|
// }
|
2021-09-23 16:49:07 -04:00
|
|
|
// if (!isSafari && !isChrome) {
|
|
|
|
// setErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.');
|
|
|
|
// setIsDisabledAppleWallet(true);
|
|
|
|
// }
|
2021-09-23 11:59:20 -04:00
|
|
|
// if (isIOS && (!osVersion.includes('13') && !osVersion.includes('14') && !osVersion.includes('15'))) {
|
|
|
|
// setErrorMessage('Sorry, iOS 13+ is needed for the Apple Wallet functionality to work')
|
2021-09-23 16:49:07 -04:00
|
|
|
// setIsDisabledAppleWallet(true);
|
2021-09-23 11:59:20 -04:00
|
|
|
// }
|
2021-09-23 16:49:07 -04:00
|
|
|
if (isIOS && !isSafari) {
|
|
|
|
// setErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS');
|
|
|
|
setErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS');
|
|
|
|
setIsDisabledAppleWallet(true);
|
|
|
|
console.log('not safari')
|
2021-09-24 01:53:16 -04:00
|
|
|
} else if (!isIOS) {
|
2021-09-24 13:18:15 -04:00
|
|
|
setWarningMessage('Only Safari on iOS is officially supported for Wallet import at the moment - ' +
|
|
|
|
'for other platforms, please ensure you have an application which can open Apple Wallet .pkpass files');
|
2021-09-24 01:53:16 -04:00
|
|
|
setIsDisabledAppleWallet(false);
|
2021-09-23 16:49:07 -04:00
|
|
|
}
|
2021-09-09 17:08:49 -04:00
|
|
|
}
|
|
|
|
|
2021-06-30 03:13:01 +02:00
|
|
|
return (
|
|
|
|
<div>
|
2021-07-01 00:49:05 +02:00
|
|
|
<form className="space-y-5" id="form" onSubmit={addToWallet}>
|
2021-08-27 01:15:49 -04:00
|
|
|
<Card step="1" heading={t('index:downloadReceipt')} content={
|
|
|
|
<div className="space-y-5">
|
|
|
|
<p>
|
|
|
|
{t('index:visit')}
|
|
|
|
|
|
|
|
<Link href="https://covid19.ontariohealth.ca">
|
2021-09-25 22:40:32 -04:00
|
|
|
<a className="underline" target="_blank">
|
2021-08-27 01:15:49 -04:00
|
|
|
{t('index:ontarioHealth')}
|
|
|
|
</a>
|
|
|
|
</Link>
|
|
|
|
{t('index:downloadSignedPDF')}
|
|
|
|
</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>
|
|
|
|
}/>
|
|
|
|
|
|
|
|
<Card step="2" heading={t('index:selectCertificate')} content={
|
2021-06-30 03:13:01 +02:00
|
|
|
<div className="space-y-5">
|
2021-07-02 20:55:26 +02:00
|
|
|
<p>{t('index:selectCertificateDescription')}</p>
|
2021-09-19 12:21:59 -04:00
|
|
|
<p>{t('index:selectCertificateReminder')}</p>
|
2021-06-30 03:13:01 +02:00
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
onClick={showFileDialog}
|
2021-09-21 16:26:35 -04:00
|
|
|
className="focus:outline-none h-20 bg-green-600 hover:bg-gray-700 text-white font-semibold rounded-md">
|
2021-07-02 20:55:26 +02:00
|
|
|
{t('index:openFile')}
|
2021-06-30 03:13:01 +02:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<input type='file'
|
|
|
|
id='file'
|
2021-09-09 00:27:43 -04:00
|
|
|
accept="application/pdf"
|
2021-06-30 03:13:01 +02:00
|
|
|
ref={inputFile}
|
|
|
|
style={{display: 'none'}}
|
|
|
|
/>
|
|
|
|
|
|
|
|
{(qrCode || file) &&
|
|
|
|
<div className="flex items-center space-x-1">
|
|
|
|
<svg className="h-4 w-4 text-green-600" fill="none" viewBox="0 0 24 24"
|
|
|
|
stroke="currentColor">
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7"/>
|
|
|
|
</svg>
|
2021-07-01 00:49:05 +02:00
|
|
|
<span className="w-full truncate">
|
2021-06-30 03:13:01 +02:00
|
|
|
{
|
2021-07-02 20:55:26 +02:00
|
|
|
qrCode && t('index:foundQrCode')
|
2021-06-30 03:13:01 +02:00
|
|
|
}
|
|
|
|
{
|
|
|
|
file && file.name
|
|
|
|
}
|
|
|
|
</span>
|
2021-07-01 00:49:05 +02:00
|
|
|
</div>
|
|
|
|
}
|
2021-06-30 03:13:01 +02:00
|
|
|
</div>
|
|
|
|
}/>
|
2021-08-27 01:15:49 -04:00
|
|
|
|
2021-09-09 17:08:49 -04:00
|
|
|
<Card step="3" heading={t('index:addToWalletHeader')} content={
|
2021-06-30 03:13:01 +02:00
|
|
|
<div className="space-y-5">
|
2021-09-23 16:49:07 -04:00
|
|
|
{/* <p>
|
2021-07-03 13:33:12 +02:00
|
|
|
{t('index:dataPrivacyDescription')}
|
2021-09-23 16:49:07 -04:00
|
|
|
<Link href="/privacy">
|
2021-07-03 13:33:12 +02:00
|
|
|
<a>
|
|
|
|
{t('index:privacyPolicy')}
|
|
|
|
</a>
|
2021-09-23 16:49:07 -04:00
|
|
|
</Link>.
|
|
|
|
</p> */}
|
2021-07-27 03:09:51 +02:00
|
|
|
<div>
|
|
|
|
<ul className="list-none">
|
2021-07-28 14:28:45 +02:00
|
|
|
<Check text={t('createdOnDevice')}/>
|
2021-09-25 23:23:25 -04:00
|
|
|
<Check text={t('piiNotSent')}/>
|
2021-07-28 14:28:45 +02:00
|
|
|
<Check text={t('openSourceTransparent')}/>
|
2021-09-23 16:49:07 -04:00
|
|
|
{verifierLink()}
|
2021-07-27 03:09:51 +02:00
|
|
|
</ul>
|
|
|
|
</div>
|
2021-08-27 01:15:49 -04:00
|
|
|
|
2021-06-30 03:13:01 +02:00
|
|
|
<div className="flex flex-row items-center justify-start">
|
2021-09-26 08:54:09 -04:00
|
|
|
<button disabled={isDisabledAppleWallet || loading} id="download" type="submit" value='applewallet' name='action'
|
2021-09-23 16:49:07 -04:00
|
|
|
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
|
2021-07-02 20:55:26 +02:00
|
|
|
{t('index:addToWallet')}
|
2021-06-30 03:13:01 +02:00
|
|
|
</button>
|
2021-09-09 12:45:21 -04:00
|
|
|
|
2021-09-26 08:54:09 -04:00
|
|
|
<button id="saveAsPhoto" type="button" disabled={loading} value='photo' name='action' onClick={saveAsPhoto}
|
2021-09-09 00:27:43 -04:00
|
|
|
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
|
|
|
|
{t('index:saveAsPhoto')}
|
2021-09-09 12:45:21 -04:00
|
|
|
</button>
|
2021-09-09 00:27:43 -04:00
|
|
|
|
2021-07-01 01:27:17 +02:00
|
|
|
<div id="spin" className={loading ? undefined : "hidden"}>
|
2021-07-29 00:02:51 +02:00
|
|
|
<svg className="animate-spin h-5 w-5 ml-4" viewBox="0 0 24 24">
|
2021-06-30 03:13:01 +02:00
|
|
|
<circle className="opacity-0" cx="12" cy="12" r="10" stroke="currentColor"
|
|
|
|
strokeWidth="4"/>
|
|
|
|
<path className="opacity-75" fill="currentColor"
|
|
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
|
|
|
|
</svg>
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-09-23 16:49:07 -04:00
|
|
|
{errorMessages.map((message, i) =>
|
|
|
|
<Alert message={message} key={'error-' + i} type="error" />
|
|
|
|
)}
|
2021-09-24 01:53:16 -04:00
|
|
|
{warningMessages.map((message, i) =>
|
|
|
|
<Alert message={message} key={'warning-' + i} type="warning" />
|
|
|
|
)}
|
2021-06-30 03:13:01 +02:00
|
|
|
</div>
|
|
|
|
}/>
|
2021-09-23 20:12:07 -04:00
|
|
|
|
2021-09-23 22:09:48 -04:00
|
|
|
<Card step="?" heading={t('index:questions')} content={
|
2021-09-23 20:12:07 -04:00
|
|
|
<div className="space-y-5">
|
|
|
|
<p>Do you want to use this tool but...</p>
|
|
|
|
<div>
|
2021-09-23 20:27:52 -04:00
|
|
|
<ul>
|
2021-09-25 23:09:46 -04:00
|
|
|
<Bullet text="You would like to understand how your data is handled?"/>
|
2021-09-23 20:12:07 -04:00
|
|
|
<Bullet text="You don't have a health card?"/>
|
|
|
|
<Bullet text="You have a Red/White OHIP card?"/>
|
|
|
|
<Bullet text='You have an iPhone 6 or older?'/>
|
|
|
|
<Bullet text='You have an Android?'/>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex flex-row items-center justify-start">
|
|
|
|
<button id="faq-redirect" onClick={goToFAQ}
|
|
|
|
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
|
|
|
|
Visit our FAQ section for the answers!
|
|
|
|
</button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
}/>
|
2021-06-30 03:13:01 +02:00
|
|
|
</form>
|
2021-09-23 16:49:07 -04:00
|
|
|
<canvas id="canvas" style={{ display: "none" }} />
|
2021-06-30 03:13:01 +02:00
|
|
|
</div>
|
|
|
|
)
|
2021-06-25 12:18:25 +02:00
|
|
|
}
|
2021-07-01 00:49:05 +02:00
|
|
|
|
2021-09-24 01:53:16 -04:00
|
|
|
export default Form;
|