1
0
mirror of https://github.com/covidpass-org/covidpass.git synced 2025-02-23 23:17:37 +01:00
This commit is contained in:
Billy Lo 2021-09-23 19:15:26 -04:00
commit a61ab43551
5 changed files with 99 additions and 59 deletions

View File

@ -1,24 +1,39 @@
import {useTranslation} from 'next-i18next'; import { useTranslation } from 'next-i18next';
interface AlertProps { interface AlertProps {
onClose: () => void; onClose?: () => void;
errorMessage: string; type: 'error' | 'warning';
message: string;
} }
function Alert(props: AlertProps): JSX.Element { function Alert(props: AlertProps): JSX.Element {
const { t } = useTranslation(['index', 'errors']); const { t } = useTranslation(['index', 'errors']);
let color = 'red';
let icon;
switch (props.type) {
case 'error':
color = 'red'
// icon = () =>
// <svg className="w-4 h-4 fill-current" viewBox="0 0 20 20"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" fill-rule="evenodd"></path></svg>
break;
case 'warning':
color = 'yellow'
break;
}
return ( return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 mt-5 rounded relative" role="alert"> <div className={`flex items-center bg-red-100 border border-red-400 text-red-700 px-4 py-3 mt-5 rounded relative`} role="alert">
<span className="block sm:inline pr-6" id="message">{props.errorMessage}</span> {icon && icon()}
<span className="absolute top-0 bottom-0 right-0 px-4 py-3" onClick={props.onClose}> <span className="block sm:inline pr-6" id="message">{props.message}</span>
<svg className="fill-current h-6 w-6 text-red-500" role="button" xmlns="http://www.w3.org/2000/svg" {props.onClose && <span className="absolute right-0 px-4 py-3" onClick={props.onClose}>
viewBox="0 0 20 20"> <svg className={`fill-current h-6 w-6 text-red-500`} role="button" xmlns="http://www.w3.org/2000/svg"
<title>{t('index:errorClose')}</title> viewBox="0 0 20 20">
<path <title>{t('index:errorClose')}</title>
d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"/> <path
</svg> d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z" />
</span> </svg>
</span>}
</div> </div>
) )
} }

View File

@ -36,11 +36,13 @@ function Form(): JSX.Element {
const [qrCode, setQrCode] = useState<Result>(undefined); const [qrCode, setQrCode] = useState<Result>(undefined);
const [file, setFile] = useState<File>(undefined); const [file, setFile] = useState<File>(undefined);
const [errorMessage, _setErrorMessage] = useState<string>(undefined);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [passCount, setPassCount] = useState<string>(''); const [passCount, setPassCount] = useState<string>('');
const [generated, setGenerated] = useState<boolean>(false); // this flag represents the file has been used to generate a pass const [generated, setGenerated] = useState<boolean>(false); // this flag represents the file has been used to generate a pass
const [isDisabledAppleWallet, setIsDisabledAppleWallet] = useState<boolean>(false);
const [errorMessages, _setErrorMessages] = useState<Array<string>>([]);
const hitcountHost = 'https://stats.vaccine-ontario.ca'; const hitcountHost = 'https://stats.vaccine-ontario.ca';
@ -75,14 +77,18 @@ function Form(): JSX.Element {
// Check if there is a translation and replace message accordingly // Check if there is a translation and replace message accordingly
const setErrorMessage = (message: string) => { const setErrorMessage = (message: string) => {
if (message == undefined) { if (message == undefined) {
_setErrorMessage(undefined);
return; return;
} }
const translation = t('errors:'.concat(message)); const translation = t('errors:'.concat(message));
_setErrorMessage(translation !== message ? translation : message); _setErrorMessages(Array.from(new Set([...errorMessages, translation !== message ? translation : message])));
}; };
const deleteErrorMessage = (message: string) =>{
console.log(errorMessages)
_setErrorMessages(errorMessages.filter(item => item !== message))
}
// File Input ref // File Input ref
const inputFile = useRef<HTMLInputElement>(undefined) const inputFile = useRef<HTMLInputElement>(undefined)
@ -95,6 +101,8 @@ function Form(): JSX.Element {
setQrCode(undefined); setQrCode(undefined);
setFile(selectedFile); setFile(selectedFile);
setGenerated(false); setGenerated(false);
deleteErrorMessage(t('errors:'.concat('noFileOrQrCode')));
checkBrowserType();
} }
}); });
} }
@ -261,7 +269,7 @@ function Form(): JSX.Element {
setLoading(true); setLoading(true);
if (!file && !qrCode) { if (!file && !qrCode) {
setErrorMessage('noFileOrQrCode') setErrorMessage('noFileOrQrCode');
setLoading(false); setLoading(false);
return; return;
} }
@ -289,25 +297,38 @@ function Form(): JSX.Element {
setLoading(false); setLoading(false);
} }
} }
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')}&nbsp;
<Link href="https://verifier.vaccine-ontario.ca/">
<a className="underline">verifier.vaccine-ontario.ca </a>
</Link>
</p>
</li>
async function checkBrowserType() { function checkBrowserType() {
// if (isIPad13) { // if (isIPad13) {
// setErrorMessage('Sorry. Apple does not support the use of Wallet on iPad. Please use iPhone/Safari.'); // setErrorMessage('Sorry. Apple does not support the use of Wallet on iPad. Please use iPhone/Safari.');
// document.getElementById('download').setAttribute('disabled','true'); // setIsDisabledAppleWallet(true);
// } // }
if (!isSafari && !isChrome) { // if (!isSafari && !isChrome) {
setErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.'); // setErrorMessage('Sorry. Apple Wallet pass can be added using Safari or Chrome only.');
document.getElementById('download').setAttribute('disabled','true'); // setIsDisabledAppleWallet(true);
} // }
// if (isIOS && (!osVersion.includes('13') && !osVersion.includes('14') && !osVersion.includes('15'))) { // if (isIOS && (!osVersion.includes('13') && !osVersion.includes('14') && !osVersion.includes('15'))) {
// setErrorMessage('Sorry, iOS 13+ is needed for the Apple Wallet functionality to work') // setErrorMessage('Sorry, iOS 13+ is needed for the Apple Wallet functionality to work')
// document.getElementById('download').setAttribute('disabled','true') // setIsDisabledAppleWallet(true);
// }
// if (isIOS && !isSafari) {
// setErrorMessage('Sorry, only Safari can be used to add a Wallet Pass on iOS')
// document.getElementById('download').setAttribute('disabled','true')
// } // }
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')
}
} }
return ( return (
@ -374,20 +395,20 @@ function Form(): JSX.Element {
<Card step="3" heading={t('index:addToWalletHeader')} content={ <Card step="3" heading={t('index:addToWalletHeader')} content={
<div className="space-y-5"> <div className="space-y-5">
<p> {/* <p>
{t('index:dataPrivacyDescription')} {t('index:dataPrivacyDescription')}
{/* <Link href="/privacy"> <Link href="/privacy">
<a> <a>
{t('index:privacyPolicy')} {t('index:privacyPolicy')}
</a> </a>
</Link>. */} </Link>.
</p> </p> */}
<div> <div>
<ul className="list-none"> <ul className="list-none">
<Check text={t('createdOnDevice')}/> <Check text={t('createdOnDevice')}/>
<Check text={t('qrCode')}/> <Check text={t('qrCode')}/>
<Check text={t('openSourceTransparent')}/> <Check text={t('openSourceTransparent')}/>
<Check text={t('verifierLink')}/> {verifierLink()}
{passCount && <Check text={passCount + ' ' + t('numPasses')}/>} {passCount && <Check text={passCount + ' ' + t('numPasses')}/>}
{/* <Check text={t('hostedInEU')}/> */} {/* <Check text={t('hostedInEU')}/> */}
@ -395,8 +416,8 @@ function Form(): JSX.Element {
</div> </div>
<div className="flex flex-row items-center justify-start"> <div className="flex flex-row items-center justify-start">
<button id="download" type="submit" value='applewallet' name='action' <button disabled={isDisabledAppleWallet} id="download" type="submit" value='applewallet' name='action'
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400"> className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
{t('index:addToWallet')} {t('index:addToWallet')}
</button> </button>
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
@ -414,13 +435,13 @@ function Form(): JSX.Element {
</svg> </svg>
</div> </div>
</div> </div>
{errorMessages.map((message, i) =>
<Alert message={message} key={'error-' + i} type="error" />
)}
</div> </div>
}/> }/>
</form> </form>
<canvas id="canvas" style={{display: "none"}}/> <canvas id="canvas" style={{ display: "none" }} />
{
errorMessage && <Alert errorMessage={errorMessage} onClose={() => setErrorMessage(undefined)}/>
}
</div> </div>
) )
} }

View File

@ -7,20 +7,21 @@ import Card from '../components/Card'
function Faq(): JSX.Element { function Faq(): JSX.Element {
const { t } = useTranslation(['common', 'index', 'faq']); const { t } = useTranslation(['common', 'index', 'faq']);
const questionList = [ const questionList = [
{description: 'Which version of iOS does this support?', answer: 'iOS 13.7 is the minimum at the moment. We are looking for adjustments for older iOSes, but it will take a bit of time.', key: 1}, {description: 'Which version of iOS does this support?', answer: 'iOS 13.7 is the minimum at the moment. We are looking for adjustments for older iOSes, but it will take a bit of time.'},
{description: 'Why have we taken time to build this?', answer: 'Gives Ontarians/organizations something easy to use (volunteered-developed, unofficial) until the official provincial app comes out in October.', key: 2}, {description: "What are the supported browsers?", answer: 'For iPhones, only Safari is supported for importing to your Apple Wallet. For any other devices, we recommend that you save it as photo using your browser of choice. Broswers built internally into mobile apps (e.g. Facebook, Twitter, Instagram) are known to have issues.'},
{description: 'Who made this?', answer: 'The same group of volunteers (Billy Lo, Ryan Slobojan, Evert Timberg, Jason Liu, Anujan Mathisekaran, Lisa Discepola, Samantha Finn, Madison Pearce) who created the all-in-one vaccine appointment finding tool at vaccine-ontario.ca.', key: 3}, {description: 'Why have we taken time to build this?', answer: 'Gives Ontarians/organizations something easy to use (volunteered-developed, unofficial) until the official provincial app comes out in October.'},
{description: 'Should I use the official provincial apps when they come out on 22nd October?', answer: 'YES. Once the official QR code from the province is available, you will also be able to refresh what\'s in your Apple Wallet as well. More details will follow.', key: 4}, {description: 'Who made this?', answer: 'The same group of volunteers (Billy Lo, Ryan Slobojan, Evert Timberg, Jason Liu, Anujan Mathisekaran, Lisa Discepola, Samantha Finn, Madison Pearce) who created the all-in-one vaccine appointment finding tool at vaccine-ontario.ca.'},
{description: 'How is the data on my vaccination receipt processed?', answer: 'It checks the receipt for an official signature from the province. If present, the receipt data is converted into Apple\'s format and then added into your iOS Wallet app.', key: 5}, {description: 'Should I use the official provincial apps when they come out on 22nd October?', answer: 'YES. Once the official QR code from the province is available, you will also be able to refresh what\'s in your Apple Wallet as well. More details will follow.'},
{description: 'How can organizations validate this QR code?', answer: 'Just aim your standard camera app (iPhone/Android) at the code, and it will bring up a web page that shows the verification result.', key: 6}, {description: 'How is the data on my vaccination receipt processed?', answer: 'It checks the receipt for an official signature from the province. If present, the receipt data is converted into Apple\'s format and then added into your iOS Wallet app.'},
{description: 'Is this free and private?', answer: 'Similar to VaxHuntersCanada, there are no commerical interests. Just volunteers trying to do our part to help the community.', key: 7}, {description: 'How can organizations validate this QR code?', answer: 'Just aim your standard camera app (iPhone/Android) at the code, and it will bring up a web page that shows the verification result.'},
{description: 'Do you have plans for Android support?', answer: 'Yes. We are working with Google to gain access to the APIs required. Meanwhile, you can also use this tool to download an Apple Wallet pass and import that into Google Pay Wallet using apps such as Pass2Pay.', key: 8}, {description: 'Is this free and private?', answer: 'Similar to VaxHuntersCanada, there are no commerical interests. Just volunteers trying to do our part to help the community.'},
{description: 'How about BC, Quebec and Alberta?', answer: 'We will be investigating BC shortly. If you are interested in contributing, email us at grassroots@vaccine-ontario.ca', key: 9}, {description: 'Do you have plans for Android support?', answer: 'Yes. We are working with Google to gain access to the APIs required. Meanwhile, you can also use this tool to download an Apple Wallet pass and import that into Google Pay Wallet using apps such as Pass2Pay.'},
{description: 'How can I stay up-to-date on your progress?', answer: 'We will post regular updates on Twitter @grassroots_team.', key: 10}, {description: 'How about BC, Quebec and Alberta?', answer: 'We will be investigating BC shortly. If you are interested in contributing, email us at grassroots@vaccine-ontario.ca'},
{description: 'I only have an emailed receipt (e.g. Red/White OHIP card users). Can I still use this tool?', answer: 'Not right now unfortunately. But we expect to be able to support that after the official app is released on 22 Oct.', key: 11}, {description: 'How can I stay up-to-date on your progress?', answer: 'We will post regular updates on Twitter @grassroots_team.'},
{description: 'What does the colour of the Apple Wallet pass mean?', answer: 'Dose 1 is shown as Orange; dose 2+ in green for easy differentiation without reading the text.', key: 12}, {description: 'I only have an emailed receipt (e.g. Red/White OHIP card users). Can I still use this tool?', answer: 'Not right now unfortunately. But we expect to be able to support that after the official app is released on 22 Oct.'},
{description: 'How about Apple Watch?', answer: 'If you have iCloud sync enabled, you will see the pass on the watch too.', key: 13}, {description: 'What does the colour of the Apple Wallet pass mean?', answer: 'Dose 1 is shown as Orange; dose 2+ in green for easy differentiation without reading the text.'},
{description: 'I have more questions. Can you please help me?', answer: 'Sure. Just email us at grassroots@vaccine-ontario.ca.', key: 14} {description: 'How about Apple Watch?', answer: 'If you have iCloud sync enabled, you will see the pass on the watch too.'},
{description: 'I have more questions. Can you please help me?', answer: 'Sure. Just email us at grassroots@vaccine-ontario.ca.'}
]; ];
@ -30,11 +31,11 @@ function Faq(): JSX.Element {
<div className="space-y-3"> <div className="space-y-3">
<p className="font-bold">{t('faq:heading')}</p> <p className="font-bold">{t('faq:heading')}</p>
<ol> <ol>
{questionList.map(question => { {questionList.map((question, i) => {
return ( return (
<div> <div>
<li key={question.key}><b>{question.key}. {question.description}</b></li> <li key={i}><b>{i+1}. {question.description}</b></li>
<li key={question.key}>{question.answer}</li> <li key={i}>{question.answer}</li>
<br></br> <br></br>
</div> </div>
); );

View File

@ -5,11 +5,14 @@ import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import Form from '../components/Form'; import Form from '../components/Form';
import Card from '../components/Card'; import Card from '../components/Card';
import Page from '../components/Page'; import Page from '../components/Page';
import Alert from '../components/Alert';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
function Index(): JSX.Element { function Index(): JSX.Element {
const { t } = useTranslation(['common', 'index', 'errors']); const { t } = useTranslation(['common', 'index', 'errors']);
const [warning, setWarning] = useState("If you previously created a vaccination receipt before Sept. 23rd and need to add your date of birth on your vaccination receipt, please reimport your Ministry of Health official vaccination receipt again below and the date of birth will now be visible on the created receipt.")
const title = 'Grassroots - Ontario vaccination receipt to your Apple wallet'; const title = 'Grassroots - Ontario vaccination receipt to your Apple wallet';
const description = 'Stores it on iPhone with a QR code for others to validate in a privacy respecting way.'; const description = 'Stores it on iPhone with a QR code for others to validate in a privacy respecting way.';
@ -40,10 +43,10 @@ function Index(): JSX.Element {
/> />
<Page content={ <Page content={
<div className="space-y-5"> <div className="space-y-5">
{warning && <Alert type="error" onClose={() => setWarning(undefined)} message={warning} />}
<Card content={ <Card content={
<div><p>{t('common:subtitle')}</p><br /><p>{t('common:subtitle2')}</p><br /><p><b>{t('common:update1Date')}</b> - {t('common:update1')}</p><br /><p>{t('common:continueSpirit')}</p></div> <div><p>{t('common:subtitle')}</p><br /><p>{t('common:subtitle2')}</p><br /><p><b>{t('common:update1Date')}</b> - {t('common:update1')}</p><br /><p>{t('common:continueSpirit')}</p></div>
}/> }/>
<Form/> <Form/>
</div> </div>
}/> }/>

View File

@ -13,7 +13,7 @@ downloadReceipt: Download official receipt from Ontario Ministry of Health
visit: Visit visit: Visit
ontarioHealth: Ontario Ministry of Health ontarioHealth: Ontario Ministry of Health
gotoOntarioHealth: Go to 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. 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. If you have completed this step before, you can proceed to the next step to prevent downloading the same file multiple times.
pickColor: Pick a Color pickColor: Pick a Color
pickColorDescription: Pick a background color for your pass. pickColorDescription: Pick a background color for your pass.
colorWhite: white colorWhite: white
@ -34,7 +34,7 @@ privacyPolicy: Privacy Policy
createdOnDevice: No personal data is sent to the Internet. createdOnDevice: No personal data is sent to the Internet.
qrCode: QR code is for verification only, with no personal info. qrCode: QR code is for verification only, with no personal info.
openSourceTransparent: Source code is free and open for re-use/contributions on GitHub. openSourceTransparent: Source code is free and open for re-use/contributions on GitHub.
verifierLink: QR code verifier available at https://verifier.vaccine-ontario.ca verifierLink: QR code verifier available at
numPasses: receipts processed since Sept 2, 2021 numPasses: receipts processed since Sept 2, 2021
demo: Video Demo demo: Video Demo
whatsnew: What's New whatsnew: What's New