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-10-17 11:56:54 -04:00
import { isChrome , isIOS , isIPad13 , isMacOs , isSafari , deviceDetect , osName , osVersion , getUA } from 'react-device-detect' ;
2021-09-14 20:33:27 -04:00
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-09-29 11:19:24 -04:00
// Currently selected dose
const [ selectedDose , setSelectedDose ] = useState < number > ( 2 ) ;
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-09-29 11:19:24 -04:00
const [ payloadBody , setPayloadBody ] = useState < PayloadBody > ( undefined ) ;
2021-06-25 12:18:25 +02:00
2021-09-29 11:19:24 -04:00
const [ saveLoading , setSaveLoading ] = useState < boolean > ( false ) ;
const [ fileLoading , setFileLoading ] = 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 ) ;
2021-09-29 11:19:24 -04:00
const [ addErrorMessages , _setAddErrorMessages ] = useState < Array < string > > ( [ ] ) ;
const [ fileErrorMessages , _setFileErrorMessages ] = useState < Array < string > > ( [ ] ) ;
2021-09-19 12:21:59 -04:00
2021-09-29 11:19:24 -04:00
const [ showDoseOption , setShowDoseOption ] = useState < boolean > ( false ) ;
2021-09-28 03:02: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
2021-09-29 11:19:24 -04:00
const setAddErrorMessage = ( 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-29 11:19:24 -04:00
_setAddErrorMessages ( Array . from ( new Set ( [ . . . addErrorMessages , translation !== message ? translation : message ] ) ) ) ;
2021-07-02 20:55:26 +02:00
} ;
2021-09-29 11:19:24 -04:00
const setFileErrorMessage = ( 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 ) ) ;
2021-09-29 11:19:24 -04:00
_setFileErrorMessages ( Array . from ( new Set ( [ . . . addErrorMessages , translation !== message ? translation : message ] ) ) ) ;
2021-07-02 20:55:26 +02:00
} ;
2021-09-24 01:53:16 -04:00
2021-09-28 03:02:16 -04:00
// const setWarningMessage = (message: string) => {
// if (!message) {
// return;
// }
2021-09-24 01:53:16 -04:00
2021-09-28 03:02:16 -04:00
// const translation = t('errors:'.concat(message));
// _setWarningMessages(Array.from(new Set([...warningMessages, translation !== message ? translation : message])));
// }
2021-09-24 01:53:16 -04:00
2021-09-29 11:19:24 -04:00
const deleteAddErrorMessage = ( message : string ) = > {
_setAddErrorMessages ( addErrorMessages . filter ( item = > item !== message ) )
}
const deleteFileErrorMessage = ( message : string ) = > {
_setFileErrorMessages ( addErrorMessages . filter ( item = > item !== message ) )
2021-09-23 16:49:07 -04:00
}
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 ) {
2021-09-26 21:10:32 -04:00
inputFile . current . addEventListener ( 'change' , ( ) = > {
2021-06-30 03:13:01 +02:00
let selectedFile = inputFile . current . files [ 0 ] ;
if ( selectedFile !== undefined ) {
2021-09-29 11:19:24 -04:00
setFileLoading ( true ) ;
2021-06-30 03:13:01 +02:00
setQrCode ( undefined ) ;
2021-09-29 11:19:24 -04:00
setPayloadBody ( undefined ) ;
setFile ( undefined ) ;
setShowDoseOption ( false ) ;
2021-09-19 12:21:59 -04:00
setGenerated ( false ) ;
2021-09-29 11:19:24 -04:00
deleteAddErrorMessage ( t ( 'errors:' . concat ( 'noFileOrQrCode' ) ) ) ;
_setFileErrorMessages ( [ ] ) ;
2021-09-23 16:49:07 -04:00
checkBrowserType ( ) ;
2021-09-29 11:19:24 -04:00
getPayload ( selectedFile ) ;
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-09-29 11:19:24 -04:00
async function getPayload ( file ) {
try {
2021-09-29 16:13:13 -04:00
const payload = await getPayloadBodyFromFile ( file ) ;
2021-09-29 11:19:24 -04:00
setPayloadBody ( payload ) ;
setFileLoading ( false ) ;
setFile ( file ) ;
2021-09-29 21:39:11 -04:00
if ( payload . rawData . length == 0 ) {
if ( Object . keys ( payload . receipts ) . length === 1 ) {
setSelectedDose ( parseInt ( Object . keys ( payload . receipts ) [ 0 ] ) ) ;
} else {
setShowDoseOption ( true ) ;
}
}
2021-09-29 11:19:24 -04:00
} catch ( e ) {
setFile ( file ) ;
setFileLoading ( false ) ;
if ( e != undefined ) {
console . error ( e ) ;
2021-10-06 13:10:36 -04:00
// Don't report known errors to Sentry
if ( ! e . message . includes ( 'invalidFileType' ) &&
2021-10-15 22:32:38 -04:00
! e . message . includes ( 'No SHC QR code found' ) ) {
2021-10-06 13:10:36 -04:00
Sentry . captureException ( e ) ;
}
2021-09-29 11:19:24 -04:00
if ( e . message != undefined ) {
setFileErrorMessage ( e . message ) ;
} else {
setFileErrorMessage ( "Unable to continue." ) ;
}
} else {
setFileErrorMessage ( "Unexpected error. Sorry." ) ;
}
}
}
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-10-16 06:00:36 -04:00
// 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' ;
2021-08-27 01:15:49 -04:00
}
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 ) {
2021-09-29 11:19:24 -04:00
setAddErrorMessage ( 'noCameraAccess' ) ;
2021-07-25 02:14:09 +02:00
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-09-29 11:19:24 -04:00
setAddErrorMessage ( "noCameraFound" ) ;
2021-07-25 01:37:58 +02:00
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-09-29 11:19:24 -04:00
setAddErrorMessage ( 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 ) {
2021-10-06 16:24:35 -04:00
// Fail silently - we shouldn't blow up receipt processing because we couldn't increment our counter
2021-09-19 12:21:59 -04:00
console . error ( e ) ;
2021-10-06 16:24:35 -04:00
//return Promise.reject(e);
2021-09-19 12:21:59 -04:00
}
}
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-09-29 11:19:24 -04:00
setSaveLoading ( true ) ;
2021-06-30 03:13:01 +02:00
2021-07-01 00:49:05 +02:00
if ( ! file && ! qrCode ) {
2021-09-29 11:19:24 -04:00
setAddErrorMessage ( 'noFileOrQrCode' )
setSaveLoading ( false ) ;
2021-07-01 00:49:05 +02:00
return ;
}
2021-06-30 03:13:01 +02:00
2021-07-01 00:49:05 +02:00
try {
2021-09-29 11:19:24 -04:00
if ( payloadBody ) {
2021-10-01 03:23:02 -04:00
let selectedReceipt ;
2021-10-13 17:47:06 -04:00
let filenameDetails = '' ;
if ( payloadBody . rawData . length > 0 ) {
// This is an SHC receipt, so do our SHC thing
selectedReceipt = payloadBody . shcReceipt ;
filenameDetails = selectedReceipt . cardOrigin . replace ( ' ' , '-' ) ;
2021-10-01 03:23:02 -04:00
} else {
selectedReceipt = payloadBody . receipts [ selectedDose ] ;
2021-10-13 17:47:06 -04:00
const vaxName = selectedReceipt . vaccineName . replace ( ' ' , '-' ) ;
const passDose = selectedReceipt . numDoses ;
filenameDetails = ` ${ vaxName } - ${ passDose } ` ;
2021-10-01 03:23:02 -04:00
}
const passName = selectedReceipt . name . replace ( ' ' , '-' ) ;
2021-10-13 17:47:06 -04:00
const covidPassFilename = ` grassroots-receipt- ${ passName } - ${ filenameDetails } .pkpass ` ;
2021-09-25 21:47:48 -04:00
2021-09-29 11:19:24 -04:00
const pass = await PassData . generatePass ( payloadBody , selectedDose ) ;
2021-09-21 20:01:38 -04:00
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-10-14 10:32:02 -04:00
await incrementCount ( ) ;
2021-09-25 21:47:48 -04:00
saveAs ( passBlob , covidPassFilename ) ;
2021-09-29 11:19:24 -04:00
setSaveLoading ( false ) ;
2021-08-27 01:15:49 -04:00
}
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-10-06 13:10:36 -04:00
if ( e ) {
2021-09-23 05:10:11 -04:00
console . error ( e ) ;
Sentry . captureException ( e ) ;
2021-10-06 13:10:36 -04:00
if ( e . message ) {
2021-09-29 11:19:24 -04:00
setAddErrorMessage ( e . message ) ;
2021-09-23 05:10:11 -04:00
} else {
2021-09-29 11:19:24 -04:00
setAddErrorMessage ( "Unable to continue." ) ;
2021-09-23 05:10:11 -04:00
}
2021-09-21 23:17:45 -04:00
} else {
2021-09-29 11:19:24 -04:00
setAddErrorMessage ( "Unexpected error. Sorry." ) ;
2021-09-23 05:10:11 -04:00
}
2021-10-06 13:10:36 -04:00
setSaveLoading ( false ) ;
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() {
2021-09-29 11:19:24 -04:00
setSaveLoading ( true ) ;
2021-09-09 00:27:43 -04:00
if ( ! file && ! qrCode ) {
2021-09-29 11:19:24 -04:00
setAddErrorMessage ( 'noFileOrQrCode' ) ;
setSaveLoading ( false ) ;
2021-09-09 00:27:43 -04:00
return ;
}
try {
2021-09-19 12:21:59 -04:00
2021-10-01 03:23:02 -04:00
let selectedReceipt ;
2021-10-13 17:47:06 -04:00
let photoBlob : Blob ;
let filenameDetails = '' ;
if ( payloadBody . rawData . length > 0 ) {
// This is an SHC receipt, so do our SHC thing
selectedReceipt = payloadBody . shcReceipt ;
photoBlob = await Photo . generateSHCPass ( payloadBody ) ;
filenameDetails = selectedReceipt . cardOrigin . replace ( ' ' , '-' ) ;
2021-10-01 03:23:02 -04:00
} else {
2021-10-13 17:47:06 -04:00
// This is an old-style ON custom QR code Receipt
2021-10-01 03:23:02 -04:00
selectedReceipt = payloadBody . receipts [ selectedDose ] ;
2021-10-13 17:47:06 -04:00
const vaxName = selectedReceipt . vaccineName . replace ( ' ' , '-' ) ;
const passDose = selectedReceipt . numDoses ;
photoBlob = await Photo . generatePass ( payloadBody , passDose ) ;
filenameDetails = ` ${ vaxName } - ${ passDose } ` ;
2021-10-01 03:23:02 -04:00
}
const passName = selectedReceipt . name . replace ( ' ' , '-' ) ;
2021-10-13 17:47:06 -04:00
const covidPassFilename = ` grassroots-receipt- ${ passName } - ${ filenameDetails } .png ` ;
2021-09-29 20:51:44 -04:00
2021-09-19 12:21:59 -04:00
await incrementCount ( ) ;
2021-10-01 03:23:02 -04:00
2021-09-29 20:51:44 -04:00
saveAs ( photoBlob , covidPassFilename ) ;
2021-09-19 12:21:59 -04:00
2021-09-09 00:27:43 -04:00
// need to clean up
2021-10-13 17:47:06 -04:00
if ( document . getElementById ( 'qrcode' ) . hasChildNodes ( ) ) {
document . getElementById ( 'qrcode' ) . firstChild . remove ( ) ;
}
if ( document . getElementById ( 'shc-qrcode' ) . hasChildNodes ( ) ) {
document . getElementById ( 'shc-qrcode' ) . firstChild . remove ( ) ;
}
// Hide both our possible passes
document . getElementById ( 'pass-image' ) . hidden = true ;
document . getElementById ( 'shc-pass-image' ) . hidden = true ;
2021-09-09 00:27:43 -04:00
2021-09-29 11:19:24 -04:00
setSaveLoading ( false ) ;
2021-09-09 00:27:43 -04:00
} catch ( e ) {
2021-10-06 13:10:36 -04:00
2021-09-14 20:33:27 -04:00
Sentry . captureException ( e ) ;
2021-10-06 13:10:36 -04:00
2021-09-29 11:19:24 -04:00
setAddErrorMessage ( e . message ) ;
setSaveLoading ( false ) ;
2021-09-09 00:27:43 -04:00
}
}
2021-09-29 11:19:24 -04:00
const setDose = ( e ) = > {
setSelectedDose ( e . target . value ) ;
}
2021-09-09 17:08:49 -04:00
2021-09-23 16:49:07 -04:00
function checkBrowserType() {
2021-09-09 17:08:49 -04:00
2021-09-23 11:59:20 -04:00
// if (isIPad13) {
2021-09-29 11:19:24 -04:00
// setAddErrorMessage('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) {
2021-09-29 11:19:24 -04:00
// setAddErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.');
2021-09-23 16:49:07 -04:00
// setIsDisabledAppleWallet(true);
// }
2021-10-17 11:56:54 -04:00
const uaIsiOS15 = getUA . includes ( 'iPhone OS 15' ) ;
if ( isIOS && ( ( ! osVersion . startsWith ( '15' ) ) && ! uaIsiOS15 ) ) {
2021-10-17 11:58:10 -04:00
const message = ` Not iOS15 error: isIOS= ${ isIOS } osVersion= ${ osVersion } UA= ${ getUA } `
console . warn ( message ) ;
Sentry . captureMessage ( message ) ;
2021-10-16 14:54:16 -04:00
setAddErrorMessage ( 'Sorry, iOS 15+ is needed for the Apple Wallet functionality to work with Smart Health Card' )
setIsDisabledAppleWallet ( true ) ;
return ;
}
if ( isMacOs ) {
setAddErrorMessage ( 'Reminder: iOS 15+ is needed for the Apple Wallet functionality to work with Smart Health Card' )
return ;
}
2021-09-23 16:49:07 -04:00
if ( isIOS && ! isSafari ) {
2021-09-29 11:19:24 -04:00
// setAddErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS');
setAddErrorMessage ( 'Sorry, only Safari can be used to add a Wallet Pass on iOS' ) ;
2021-09-23 16:49:07 -04:00
setIsDisabledAppleWallet ( true ) ;
2021-10-16 14:54:16 -04:00
return ;
2021-09-23 16:49:07 -04:00
}
2021-09-28 03:02:16 -04:00
// } else if (!isIOS) {
// 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');
// setIsDisabledAppleWallet(false);
// }
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' ) } & nbsp ;
< 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 > & nbsp ;
2021-09-27 16:05:33 -04:00
{ t ( 'index:downloadSignedPDF' ) } < br / > < br / >
{ t ( 'index:reminderNotToRepeat' ) }
2021-08-27 01:15:49 -04:00
< / 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-29 11:19:24 -04:00
< div className = "grid grid-cols-1 md:grid-cols-2 gap-5 items-center justify-start" >
2021-06-30 03:13:01 +02:00
< 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 >
2021-09-29 11:19:24 -04:00
< div id = "spin" className = { fileLoading ? undefined : "hidden" } >
< svg className = "animate-spin h-5 w-5 ml-4" viewBox = "0 0 24 24" >
< 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 >
2021-06-30 03:13:01 +02:00
< / div >
< input type = 'file'
id = 'file'
2021-10-08 23:05:58 -04:00
accept = "application/pdf,.png,.jpg,.jpeg,.gif,.webp"
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-09-29 11:19:24 -04:00
{ fileErrorMessages . map ( ( message , i ) = >
< Alert message = { message } key = { 'error-' + i } type = "error" / >
) }
2021-06-30 03:13:01 +02:00
< / div >
} / >
2021-08-27 01:15:49 -04:00
2021-09-29 11:19:24 -04:00
{ showDoseOption && < Card step = "3" heading = { 'Choose dose number' } content = {
2021-06-30 03:13:01 +02:00
< div className = "space-y-5" >
< p >
2021-09-29 11:19:24 -04:00
{ t ( 'index:formatChange' ) }
< br / > < br / >
{ t ( 'index:saveMultiple' ) }
< / p >
< link href = "https://cdn.jsdelivr.net/npm/@tailwindcss/custom-forms@0.2.1/dist/custom-forms.css" rel = "stylesheet" / >
< div className = "block" >
< div className = "mt-2" >
{ payloadBody && Object . keys ( payloadBody . receipts ) . map ( key = >
< div key = { key } >
< label className = "inline-flex items-center" >
< input onChange = { setDose } type = "radio" className = "form-radio" name = "radio" value = { key } checked = { parseInt ( key ) == selectedDose } / >
< span className = "ml-2" > Dose { key } < / span >
< / label >
< / div >
) }
< / div >
< / div >
< / div >
} / > }
< Card step = { showDoseOption ? '4' : '3' } heading = { t ( 'index:addToWalletHeader' ) } content = {
2021-06-30 03:13:01 +02:00
< div className = "space-y-5" >
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-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-29 11:19:24 -04:00
< button disabled = { isDisabledAppleWallet || saveLoading || ! payloadBody } 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
& nbsp ; & nbsp ; & nbsp ; & nbsp ;
2021-09-29 11:19:24 -04:00
< button id = "saveAsPhoto" type = "button" disabled = { saveLoading || ! payloadBody } 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-09-29 11:19:24 -04:00
< div id = "spin" className = { saveLoading ? 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-29 11:19:24 -04:00
{ addErrorMessages . map ( ( message , i ) = >
2021-09-23 16:49:07 -04:00
< Alert message = { message } key = { 'error-' + i } type = "error" / >
) }
2021-09-28 03:02:16 -04:00
{ / * { w a r n i n g M e s s a g e s . m a p ( ( m e s s a g e , i ) = >
2021-09-24 01:53:16 -04:00
< Alert message = { message } key = { 'warning-' + i } type = "warning" / >
2021-09-28 03:02:16 -04:00
) } * / }
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 >
& nbsp ; & nbsp ; & nbsp ; & nbsp ;
< / 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 ;