Prepare for localization
This commit is contained in:
parent
96dd9a1358
commit
e9b2ccb6a8
|
@ -28,6 +28,7 @@ COPY --from=builder /app/public ./public
|
|||
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder /app/next-i18next.config.js ./next-i18next.config.js
|
||||
|
||||
USER nextjs
|
||||
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import {useTranslation} from 'next-i18next';
|
||||
|
||||
interface AlertProps {
|
||||
onClose: () => void;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
function Alert(props: AlertProps): JSX.Element {
|
||||
const { t } = useTranslation(['index', 'errors']);
|
||||
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 mt-5 rounded relative" role="alert">
|
||||
<strong className="font-bold pr-2" id="heading">Error</strong>
|
||||
<span className="block sm:inline" id="message">{props.errorMessage}</span>
|
||||
<span className="absolute top-0 bottom-0 right-0 px-4 py-3" onClick={props.onClose}>
|
||||
<svg className="fill-current h-6 w-6 text-red-500" role="button" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20">
|
||||
<title>Close</title>
|
||||
<title>{t('index:errorClose')}</title>
|
||||
<path
|
||||
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"/>
|
||||
</svg>
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import Card from "./Card";
|
||||
import {saveAs} from 'file-saver'
|
||||
import {saveAs} from 'file-saver';
|
||||
import React, {FormEvent, useEffect, useRef, useState} from "react";
|
||||
import {BrowserQRCodeReader} from "@zxing/browser";
|
||||
import {Result} from "@zxing/library";
|
||||
import {useTranslation} from 'next-i18next';
|
||||
|
||||
import Card from "./Card";
|
||||
import Alert from "./Alert";
|
||||
import {PayloadBody} from "../src/payload";
|
||||
import {getPayloadBodyFromFile, getPayloadBodyFromQR} from "../src/process";
|
||||
import {PassData} from "../src/pass";
|
||||
import Alert from "./Alert";
|
||||
|
||||
function Form(): JSX.Element {
|
||||
const { t } = useTranslation(['index', 'errors', 'common']);
|
||||
|
||||
// Whether camera is open or not
|
||||
const [isCameraOpen, setIsCameraOpen] = useState<boolean>(false);
|
||||
|
@ -20,9 +23,15 @@ function Form(): JSX.Element {
|
|||
const [qrCode, setQrCode] = useState<Result>(undefined);
|
||||
const [file, setFile] = useState<File>(undefined);
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string>(undefined);
|
||||
const [errorMessage, _setErrorMessage] = useState<string>(undefined);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
// Check if there is a translation and replace message accordingly
|
||||
const setErrorMessage = (message: string) => {
|
||||
const translation = t('errors:'.concat(message));
|
||||
_setErrorMessage(translation !== message ? translation : message);
|
||||
};
|
||||
|
||||
// File Input ref
|
||||
const inputFile = useRef<HTMLInputElement>(undefined)
|
||||
|
||||
|
@ -92,7 +101,7 @@ function Form(): JSX.Element {
|
|||
setLoading(true);
|
||||
|
||||
if (!file && !qrCode) {
|
||||
setErrorMessage("Please scan a QR Code, or select a file to scan")
|
||||
setErrorMessage('noFileOrQrCode')
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
@ -113,7 +122,7 @@ function Form(): JSX.Element {
|
|||
saveAs(passBlob, 'covid.pkpass');
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
setErrorMessage(e.toString());
|
||||
setErrorMessage(e.message);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
@ -121,25 +130,21 @@ function Form(): JSX.Element {
|
|||
return (
|
||||
<div>
|
||||
<form className="space-y-5" id="form" onSubmit={addToWallet}>
|
||||
<Card step="1" heading="Select Certificate" content={
|
||||
<Card step="1" heading={t('index:selectCertificate')} content={
|
||||
<div className="space-y-5">
|
||||
<p>
|
||||
Please select the certificate screenshot or (scanned) PDF page, which you received from your
|
||||
doctor, pharmacy, vaccination centre or online. Note that taking a picture does not work on
|
||||
most devices yet.
|
||||
</p>
|
||||
<p>{t('index:selectCertificateDescription')}</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={isCameraOpen ? hideCameraView : showCameraView}
|
||||
className="focus:outline-none h-20 bg-gray-500 hover:bg-gray-700 text-white font-semibold rounded-md">
|
||||
{isCameraOpen ? "Stop Camera" : "Start Camera"}
|
||||
{isCameraOpen ? t('index:stopCamera') : t('index:startCamera')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={showFileDialog}
|
||||
className="focus:outline-none h-20 bg-gray-500 hover:bg-gray-700 text-white font-semibold rounded-md">
|
||||
Open File (PDF, PNG)
|
||||
{t('index:openFile')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -160,7 +165,7 @@ function Form(): JSX.Element {
|
|||
</svg>
|
||||
<span className="w-full truncate">
|
||||
{
|
||||
qrCode && 'Found QR Code!'
|
||||
qrCode && t('index:foundQrCode')
|
||||
}
|
||||
{
|
||||
file && file.name
|
||||
|
@ -170,22 +175,20 @@ function Form(): JSX.Element {
|
|||
}
|
||||
</div>
|
||||
}/>
|
||||
<Card step="2" heading="Pick a Color" content={
|
||||
<Card step="2" heading={t('index:pickColor')} content={
|
||||
<div className="space-y-5">
|
||||
<p>
|
||||
Pick a background color for your pass.
|
||||
</p>
|
||||
<p>{t('index:pickColorDescription')}</p>
|
||||
<div className="relative inline-block w-full">
|
||||
<select name="color" id="color"
|
||||
className="bg-gray-200 dark:bg-gray-900 focus:outline-none w-full h-10 pl-3 pr-6 text-base rounded-md appearance-none cursor-pointer">
|
||||
<option value="white">white</option>
|
||||
<option value="black">black</option>
|
||||
<option value="grey">grey</option>
|
||||
<option value="green">green</option>
|
||||
<option value="indigo">indigo</option>
|
||||
<option value="blue">blue</option>
|
||||
<option value="purple">purple</option>
|
||||
<option value="teal">teal</option>
|
||||
<option value="white">{t('index:colorWhite')}</option>
|
||||
<option value="black">{t('index:colorBlack')}</option>
|
||||
<option value="grey">{t('index:colorGrey')}</option>
|
||||
<option value="green">{t('index:colorGreen')}</option>
|
||||
<option value="indigo">{t('index:colorIndigo')}</option>
|
||||
<option value="blue">{t('index:colorBlue')}</option>
|
||||
<option value="purple">{t('index:colorPurple')}</option>
|
||||
<option value="teal">{t('index:colorTeal')}</option>
|
||||
</select>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg className="w-5 h-5 fill-current" viewBox="0 0 20 20">
|
||||
|
@ -197,23 +200,27 @@ function Form(): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
}/>
|
||||
<Card step="3" heading="Add to Wallet" content={
|
||||
<Card step="3" heading={t('index:addToWallet')} content={
|
||||
<div className="space-y-5">
|
||||
<p>
|
||||
Data privacy is of special importance when processing health-related data.
|
||||
In order for you to make an informed decision, please read the <a href="/privacy">Privacy
|
||||
Policy</a>.
|
||||
{t('index:dataPrivacyDescription')}
|
||||
<a href="/privacy">
|
||||
{t('index:privacyPolicy')}
|
||||
</a>.
|
||||
</p>
|
||||
<label htmlFor="privacy" className="flex flex-row space-x-4 items-center">
|
||||
<input type="checkbox" id="privacy" value="privacy" required className="h-4 w-4"/>
|
||||
<p>
|
||||
I accept the <a href="/privacy" className="underline">Privacy Policy</a>
|
||||
{t('index:iAcceptThe')}
|
||||
<a href="/privacy" className="underline">
|
||||
{t('index:privacyPolicy')}
|
||||
</a>
|
||||
</p>
|
||||
</label>
|
||||
<div className="flex flex-row items-center justify-start">
|
||||
<button id="download" type="submit"
|
||||
className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
|
||||
Add to Wallet
|
||||
{t('index:addToWallet')}
|
||||
</button>
|
||||
<div id="spin" className={loading ? undefined : "hidden"}>
|
||||
<svg className="animate-spin h-5 w-5 ml-2" viewBox="0 0 24 24">
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import {useTranslation} from 'next-i18next';
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
function Logo(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
return (
|
||||
<Link href="/">
|
||||
<a className="flex flex-row items-center p-3 justify-center space-x-1">
|
||||
|
@ -16,7 +20,7 @@ function Logo(): JSX.Element {
|
|||
</g>
|
||||
</svg>
|
||||
<h1 className="text-3xl font-bold">
|
||||
CovidPass
|
||||
{t('common:title')}
|
||||
</h1>
|
||||
</a>
|
||||
</Link>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import {useTranslation} from 'next-i18next';
|
||||
|
||||
import Head from 'next/head'
|
||||
import Logo from './Logo'
|
||||
import Link from 'next/link'
|
||||
|
@ -7,10 +9,12 @@ interface PageProps {
|
|||
}
|
||||
|
||||
function Page(props: PageProps): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
return (
|
||||
<div className="md:w-2/3 xl:w-2/5 md:mx-auto flex flex-col min-h-screen justify-center px-5 py-12">
|
||||
<Head>
|
||||
<title>CovidPass</title>
|
||||
<title>{t('common:title')}</title>
|
||||
<link rel="icon" href="/favicon.ico"/>
|
||||
</Head>
|
||||
<div>
|
||||
|
@ -21,10 +25,10 @@ function Page(props: PageProps): JSX.Element {
|
|||
|
||||
<footer>
|
||||
<nav className="nav flex pt-4 flex-row space-x-4 justify-center text-md font-bold">
|
||||
<a href="https://www.paypal.com/paypalme/msextro" className="hover:underline">Donate</a>
|
||||
<a href="https://github.com/marvinsxtr/covidpass" className="hover:underline">GitHub</a>
|
||||
<Link href="/privacy"><a className="hover:underline">Privacy Policy</a></Link>
|
||||
<Link href="/imprint"><a className="hover:underline">Imprint</a></Link>
|
||||
<a href="https://www.paypal.com/paypalme/msextro" className="hover:underline">{t('common:donate')}</a>
|
||||
<a href="https://github.com/marvinsxtr/covidpass" className="hover:underline">{t('common:gitHub')}</a>
|
||||
<Link href="/privacy"><a className="hover:underline">{t('common:privacyPolicy')}</a></Link>
|
||||
<Link href="/imprint"><a className="hover:underline">{t('common:imprint')}</a></Link>
|
||||
</nav>
|
||||
</footer>
|
||||
</main>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
localeExtension: 'yml',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
const {i18n} = require('./next-i18next.config');
|
||||
|
||||
module.exports = {
|
||||
i18n,
|
||||
};
|
|
@ -18,6 +18,7 @@
|
|||
"jpeg-js": "^0.4.3",
|
||||
"jsqr": "^1.4.0",
|
||||
"next": "latest",
|
||||
"next-i18next": "^8.5.1",
|
||||
"next-seo": "^4.26.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"pdfjs-dist": "^2.5.207",
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import 'tailwindcss/tailwind.css'
|
||||
import 'tailwindcss/tailwind.css';
|
||||
|
||||
import {DefaultSeo} from 'next-seo';
|
||||
import SEO from '../next-seo.config';
|
||||
import type {AppProps} from 'next/app'
|
||||
import type {AppProps} from 'next/app';
|
||||
import { appWithTranslation } from 'next-i18next';
|
||||
|
||||
function MyApp({Component, pageProps}: AppProps): JSX.Element {
|
||||
return (
|
||||
|
@ -13,4 +14,4 @@ function MyApp({Component, pageProps}: AppProps): JSX.Element {
|
|||
)
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
export default appWithTranslation(MyApp);
|
|
@ -1,57 +1,39 @@
|
|||
import {useTranslation} from 'next-i18next';
|
||||
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
|
||||
|
||||
import Page from '../components/Page'
|
||||
import Card from '../components/Card'
|
||||
|
||||
function Imprint(): JSX.Element {
|
||||
const { t } = useTranslation(['common', 'index', 'imprint']);
|
||||
|
||||
return (
|
||||
<Page content={
|
||||
<Card step="§" heading="Imprint" content={
|
||||
<Card step="§" heading={t('common:imprint')} content={
|
||||
<div className="space-y-2">
|
||||
<p className="font-bold">Information according to § 5 TMG</p>
|
||||
<p className="font-bold">{t('imprint:heading')}</p>
|
||||
<p>
|
||||
Marvin Sextro<br/>
|
||||
Wilhelm-Busch-Str. 8A<br/>
|
||||
30167 Hannover<br/>
|
||||
Marvin Sextro<br />
|
||||
Wilhelm-Busch-Str. 8A<br />
|
||||
30167 Hannover<br />
|
||||
</p>
|
||||
<p className="font-bold">Contact</p>
|
||||
<p className="font-bold">{t('imprint:contact')}</p>
|
||||
<p>
|
||||
marvin.sextro@gmail.com
|
||||
<a href="mailto:marvin.sextro@gmail.com" className="underline">marvin.sextro@gmail.com</a>
|
||||
</p>
|
||||
<p className="font-bold">EU Dispute Resolution</p>
|
||||
<p className="font-bold">{t('imprint:euDisputeResolution')}</p>
|
||||
<p>{t('imprint:euDisputeResolutionParagraph')}</p>
|
||||
<p className="font-bold">{t('imprint:consumerDisputeResolution')}</p>
|
||||
<p>{t('imprint:consumerDisputeResolutionParagraph')}</p>
|
||||
<p className="font-bold">{t('imprint:liabilityForContents')}</p>
|
||||
<p>{t('imprint:liabilityForContentsParagraph')}</p>
|
||||
<p className="font-bold">{t('imprint:liabilityForLinks')}</p>
|
||||
<p>{t('imprint:liabilityForLinksParagraph')}</p>
|
||||
<p className="font-bold">{t('imprint:credits')}</p>
|
||||
<p>
|
||||
The European Commission provides a platform for online dispute resolution (OS): <a
|
||||
href="https://ec.europa.eu/consumers/odr"
|
||||
className="underline">https://ec.europa.eu/consumers/odr</a>. You can find our e-mail address in
|
||||
the imprint above.
|
||||
</p>
|
||||
<p className="font-bold">Consumer dispute resolution / universal arbitration board</p>
|
||||
<p>
|
||||
We are not willing or obliged to participate in dispute resolution proceedings before a consumer
|
||||
arbitration board.
|
||||
</p>
|
||||
<p className="font-bold">Liability for contents</p>
|
||||
<p>
|
||||
As a service provider, we are responsible for our own content on these pages in accordance with
|
||||
§ 7 paragraph 1 TMG under the general laws. According to §§ 8 to 10 TMG, we are not obligated to
|
||||
monitor transmitted or stored information or to investigate circumstances that indicate illegal
|
||||
activity. Obligations to remove or block the use of information under the general laws remain
|
||||
unaffected. However, liability in this regard is only possible from the point in time at which a
|
||||
concrete infringement of the law becomes known. If we become aware of any such infringements, we
|
||||
will remove the relevant content immediately.
|
||||
</p>
|
||||
<p className="font-bold">Liability for links</p>
|
||||
<p>
|
||||
Our offer contains links to external websites of third parties, on whose contents we have no
|
||||
influence. Therefore, we cannot assume any liability for these external contents. The respective
|
||||
provider or operator of the sites is always responsible for the content of the linked sites. The
|
||||
linked pages were checked for possible legal violations at the time of linking. Illegal contents
|
||||
were not recognizable at the time of linking. However, a permanent control of the contents of
|
||||
the linked pages is not reasonable without concrete evidence of a violation of the law. If we
|
||||
become aware of any infringements, we will remove such links immediately.
|
||||
</p>
|
||||
<p className="font-bold">Credits</p>
|
||||
<p>
|
||||
With excerpts from: https://www.e-recht24.de/impressum-generator.html
|
||||
Translated with www.DeepL.com/Translator (free version)
|
||||
{t('imprint:creditsSource')}
|
||||
<br />
|
||||
{t('imprint:creditsTranslation')}
|
||||
</p>
|
||||
</div>
|
||||
}/>
|
||||
|
@ -59,4 +41,12 @@ function Imprint(): JSX.Element {
|
|||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['index', 'imprint', 'common']))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Imprint;
|
|
@ -1,10 +1,14 @@
|
|||
import {NextSeo} from 'next-seo';
|
||||
import {useTranslation} from 'next-i18next';
|
||||
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
|
||||
|
||||
import Form from '../components/Form'
|
||||
import Card from '../components/Card'
|
||||
import Page from '../components/Page'
|
||||
import Form from '../components/Form';
|
||||
import Card from '../components/Card';
|
||||
import Page from '../components/Page';
|
||||
|
||||
function Index(): JSX.Element {
|
||||
const { t } = useTranslation(['common', 'index', 'errors']);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
|
@ -33,10 +37,7 @@ function Index(): JSX.Element {
|
|||
<Page content={
|
||||
<div className="space-y-5">
|
||||
<Card content={
|
||||
<p>
|
||||
Add your EU Digital Covid Vaccination Certificates to your favorite wallet app. On iOS,
|
||||
please use the Safari Browser.
|
||||
</p>
|
||||
<p>{t('common:subtitle')} {t('index:iosHint')}</p>
|
||||
}/>
|
||||
|
||||
<Form/>
|
||||
|
@ -46,4 +47,12 @@ function Index(): JSX.Element {
|
|||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common', 'index', 'errors'])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default Index;
|
||||
|
|
|
@ -1,222 +1,162 @@
|
|||
import {useTranslation} from 'next-i18next';
|
||||
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
|
||||
|
||||
import Page from '../components/Page'
|
||||
import Card from '../components/Card'
|
||||
|
||||
function Privacy(): JSX.Element {
|
||||
const { t } = useTranslation(['common', 'index', 'privacy']);
|
||||
return (
|
||||
<Page content={
|
||||
<Card step="i" heading="Privacy Policy" content={
|
||||
<Card step="i" heading={t('common:privacyPolicy')} content={
|
||||
<div className="space-y-2">
|
||||
<p>
|
||||
Our privacy policy is based on the terms used by the European legislator for the adoption of the
|
||||
General Data Protection Regulation (GDPR).
|
||||
</p>
|
||||
<p className="font-bold">General information</p>
|
||||
<p>{t('privacy:gdprNotice')}</p>
|
||||
<p className="font-bold">{t('privacy:generalInfo')}</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>{t('privacy:generalInfoProcess')}</li>
|
||||
<li>{t('privacy:generalInfoStoring')}</li>
|
||||
<li>{t('privacy:generalInfoThirdParties')}</li>
|
||||
<li>{t('privacy:generalInfoHttps')}</li>
|
||||
<li>{t('privacy:generalInfoLocation')}</li>
|
||||
<li>
|
||||
The whole process of generating the pass file happens locally in your browser. For the
|
||||
signing step, only a hashed representation of your data is sent to the server.
|
||||
{t('privacy:generalInfoGitHub')}
|
||||
|
||||
<a href="https://github.com/marvinsxtr/covidpass" className="underline">
|
||||
GitHub
|
||||
</a>.
|
||||
</li>
|
||||
<li>
|
||||
Your data is not stored beyond the active browser session and the site does not use
|
||||
cookies.
|
||||
{t('privacy:generalInfoLockScreen')}
|
||||
|
||||
<a href="https://support.apple.com/guide/iphone/control-access-information-lock-screen-iph9a2a69136/ios" className="underline">
|
||||
{t('privacy:settings')}
|
||||
</a>.
|
||||
</li>
|
||||
<li>
|
||||
No data is sent to third parties.
|
||||
</li>
|
||||
<li>
|
||||
We transmit your data securely over https.
|
||||
</li>
|
||||
<li>
|
||||
Our server is hosted in Nuremberg, Germany.
|
||||
</li>
|
||||
<li>
|
||||
The source code of this site is available on <a
|
||||
href="https://github.com/marvinsxtr/covidpass" className="underline">GitHub</a>.
|
||||
</li>
|
||||
<li>
|
||||
By default, Apple Wallet passes are accessible from the lock screen. This can be changed
|
||||
in the <a href="https://support.apple.com/de-de/guide/iphone/iph9a2a69136/ios"
|
||||
className="underline">settings</a>.
|
||||
</li>
|
||||
<li>
|
||||
The server provider processes data to provide this site. In order to better understand
|
||||
what measures they take to protect your data, please also read their <a
|
||||
href="https://www.hetzner.com/de/rechtliches/datenschutz/" className="underline">privacy
|
||||
policy</a> and the <a
|
||||
href="https://docs.hetzner.com/general/general-terms-and-conditions/data-privacy-faq/privacy.tsx"
|
||||
className="underline">data privacy FAQ</a>.
|
||||
{t('privacy:generalInfoProvider')}
|
||||
|
||||
<a href="https://www.hetzner.com/de/rechtliches/datenschutz/" className="underline">
|
||||
{t('privacy:privacyPolicy')}
|
||||
</a>
|
||||
|
||||
{t('privacy:andThe')}
|
||||
|
||||
<a href="https://docs.hetzner.com/general/general-terms-and-conditions/data-privacy-faq/privacy.tsx" className="underline">
|
||||
{t('privacy:dataPrivacyFaq')}
|
||||
</a>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p className="font-bold">Contact</p>
|
||||
<p className="font-bold">{t('privacy:contact')}</p>
|
||||
<p>
|
||||
Marvin Sextro<br/>
|
||||
Wilhelm-Busch-Str. 8A<br/>
|
||||
30167 Hannover<br/>
|
||||
Germany<br/>
|
||||
Email: marvin.sextro@gmail.com<br/>
|
||||
Website: <a href="https://marvinsextro.de"
|
||||
className="underline">https://marvinsextro.de</a><br/>
|
||||
</p>
|
||||
<p className="font-bold">Simplified explanation of the process</p>
|
||||
<p>
|
||||
This process is only started after accepting this policy and clicking on the Add to Wallet
|
||||
button.
|
||||
</p>
|
||||
<p>
|
||||
First, the following steps happen locally in your browser:
|
||||
{t('privacy:email')}:
|
||||
|
||||
<a href="mailto:marvin.sextro@gmail.com">marvin.sextro@gmail.com</a>
|
||||
<br/>
|
||||
{t('privacy:website')}:
|
||||
|
||||
<a href="https://marvinsextro.de" className="underline">https://marvinsextro.de</a>
|
||||
</p>
|
||||
<p className="font-bold">{t('privacy:process')}</p>
|
||||
<p>{t('privacy:processFirst')}:</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>Recognizing and extracting the QR code data from your selected certificate</li>
|
||||
<li>Decoding your personal and health-related data from the QR code payload</li>
|
||||
<li>Assembling an incomplete pass file out of your data</li>
|
||||
<li>Generating a file containing hashes of the data stored in the pass file</li>
|
||||
<li>Sending only the file containing the hashes to our server</li>
|
||||
<li>{t('privacy:processRecognizing')}</li>
|
||||
<li>{t('privacy:processDecoding')}</li>
|
||||
<li>{t('privacy:processAssembling')}</li>
|
||||
<li>{t('privacy:processGenerating')}</li>
|
||||
<li>{t('privacy:processSending')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
Second, the following steps happen on our server:
|
||||
</p>
|
||||
<p>{t('privacy:processSecond')}:</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>Receiving and checking the hashes which were generated locally</li>
|
||||
<li>Signing the file containing the hashes</li>
|
||||
<li>Sending the signature back</li>
|
||||
<li>{t('privacy:processReceiving')}</li>
|
||||
<li>{t('privacy:processSigning')}</li>
|
||||
<li>{t('privacy:processSendingBack')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
Finally, the following steps happen locally in your browser:
|
||||
</p>
|
||||
<p>{t('privacy:processThird')}:</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>Assembling the signed pass file out of the incomplete file generated locally and the
|
||||
signature
|
||||
</li>
|
||||
<li>Saving the file on your device</li>
|
||||
<li>{t('privacy:processCompleting')}</li>
|
||||
<li>{t('privacy:processSaving')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p className="font-bold">Locally processed data</p>
|
||||
<p className="font-bold">{t('privacy:locallyProcessedData')}</p>
|
||||
<p>
|
||||
The following data is processed on in your browser to generate the pass file.
|
||||
{t('privacy:the')}
|
||||
|
||||
<a href="https://github.com/ehn-dcc-development/ehn-dcc-schema" className="underline">
|
||||
{t('privacy:schema')}
|
||||
</a>
|
||||
|
||||
{t('privacy:specification')}
|
||||
</p>
|
||||
<p className="font-bold">{t('privacy:serverProvider')}</p>
|
||||
<p>{t('privacy:serverProviderIs')}</p>
|
||||
<p>
|
||||
Processed personal data contained in the QR code:
|
||||
<a href="https://www.hetzner.com/" className="underline">
|
||||
Hetzner Online GmbH
|
||||
</a>
|
||||
<br />
|
||||
Industriestr. 25<br />
|
||||
91710 Gunzenhausen<br />
|
||||
</p>
|
||||
<p>{t('privacy:logFiles')}:</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>Your first and last name</li>
|
||||
<li>Your date of birth</li>
|
||||
<li>{t('privacy:logFilesBrowser')}</li>
|
||||
<li>{t('privacy:logFilesOs')}</li>
|
||||
<li>{t('privacy:logFilesReferrer')}</li>
|
||||
<li>{t('privacy:logFilesTime')}</li>
|
||||
<li>{t('privacy:logFilesIpAddress')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
For each vaccination certificate contained in the QR code, the following data is processed:
|
||||
</p>
|
||||
<p className="font-bold">{t('privacy:rights')}</p>
|
||||
<p>{t('privacy:rightsGranted')}:</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>Targeted disease</li>
|
||||
<li>Vaccine medical product</li>
|
||||
<li>Manufacturer/Marketing Authorization Holder</li>
|
||||
<li>Dose number</li>
|
||||
<li>Total series of doses</li>
|
||||
<li>Date of vaccination</li>
|
||||
<li>Country of vaccination</li>
|
||||
<li>Certificate issuer</li>
|
||||
<li>Unique certificate identifier (UVCI)</li>
|
||||
<li>{t('privacy:rightsAccess')}</li>
|
||||
<li>{t('privacy:rightsErasure')}</li>
|
||||
<li>{t('privacy:rightsRectification')}</li>
|
||||
<li>{t('privacy:rightsPortability')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
For each test certificate contained in the QR code, the following data is processed:
|
||||
</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>Targeted disease</li>
|
||||
<li>Test type</li>
|
||||
<li>NAA Test name</li>
|
||||
<li>RAT Test name and manufacturer</li>
|
||||
<li>Date/Time of Sample Collection</li>
|
||||
<li>Test Result</li>
|
||||
<li>Testing Centre</li>
|
||||
<li>Country of test</li>
|
||||
<li>Certificate Issuer</li>
|
||||
<li>Unique Certificate Identifier (UVCI)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
For each recovery certificate contained in the QR code, the following data is processed:
|
||||
</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>Targeted disease</li>
|
||||
<li>Date of first positive NAA test result</li>
|
||||
<li>Country of test</li>
|
||||
<li>Certificate Issuer</li>
|
||||
<li>Certificate valid from</li>
|
||||
<li>Certificate valid until</li>
|
||||
<li>Unique Certificate Identifier (UVCI)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
The <a href="https://github.com/ehn-dcc-development/ehn-dcc-schema" className="underline">Digital
|
||||
Covid Certificate Schema</a> contains a detailed specification of which data can be contained in
|
||||
the QR code.
|
||||
</p>
|
||||
<p className="font-bold">Server provider</p>
|
||||
<p>
|
||||
Our server provider is <a href="https://www.hetzner.com/" className="underline">Hetzner Online
|
||||
GmbH</a>.
|
||||
The following data may be collected and stored in the server log files:
|
||||
</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>The browser types and versions used</li>
|
||||
<li>The operating system used by the accessing system</li>
|
||||
<li>The website from which an accessing system reaches our website (so-called referrers)
|
||||
</li>
|
||||
<li>The date and time of access</li>
|
||||
<li>The pseudonymised IP addresses</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p className="font-bold">Your rights</p>
|
||||
In accordance with the GDPR you have the following rights:
|
||||
<p className="font-bold">{t('privacy:thirdParties')}</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>
|
||||
Right of access to your data: You have the right to know what data has been collected
|
||||
about you and how it was processed.
|
||||
GitHub:
|
||||
|
||||
<a href="https://docs.github.com/en/github/site-policy/github-privacy-statement" className="underline">
|
||||
{t('common:privacyPolicy')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
Right to be forgotten: Erasure of your personal data.
|
||||
PayPal:
|
||||
|
||||
<a href="https://www.paypal.com/de/webapps/mpp/ua/privacy-full?locale.x=en_EN" className="underline">
|
||||
{t('common:privacyPolicy')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
Right of rectification: You have the right to correct inaccurate data.
|
||||
Gmail/Google:
|
||||
|
||||
<a href="https://policies.google.com/privacy?hl=en-US" className="underline">
|
||||
{t('common:privacyPolicy')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
Right of data portability: You have the right to transfer your data from one processing
|
||||
system into another.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p className="font-bold">Third parties linked</p>
|
||||
<div className="px-4">
|
||||
<ul className="list-disc">
|
||||
<li>
|
||||
GitHub: <a href="https://docs.github.com/en/github/site-policy/github-privacy-statement"
|
||||
className="underline">Privacy Policy</a>
|
||||
</li>
|
||||
<li>
|
||||
PayPal: <a href="https://www.paypal.com/de/webapps/mpp/ua/privacy-full?locale.x=en_EN"
|
||||
className="underline">Privacy Policy</a>
|
||||
</li>
|
||||
<li>
|
||||
Gmail/Google: <a href="https://policies.google.com/privacy?hl=en-US"
|
||||
className="underline">Privacy Policy</a>
|
||||
</li>
|
||||
<li>
|
||||
Apple may sync your passes via iCloud: <a
|
||||
href="https://www.apple.com/legal/privacy/en-ww/privacy.tsx" className="underline">Privacy
|
||||
Policy</a>
|
||||
{t('privacy:appleSync')}:
|
||||
|
||||
<a href="https://www.apple.com/legal/privacy/en-ww/privacy.tsx" className="underline">
|
||||
{t('common:privacyPolicy')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -226,4 +166,12 @@ function Privacy(): JSX.Element {
|
|||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['index', 'privacy', 'common'])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default Privacy;
|
|
@ -0,0 +1,6 @@
|
|||
title: CovidPass
|
||||
subtitle: Add your EU Digital Covid Vaccination Certificates to your favorite wallet apps.
|
||||
privacyPolicy: Privacy Policy
|
||||
donate: Donate
|
||||
gitHub: GitHub
|
||||
imprint: Imprint
|
|
@ -0,0 +1,14 @@
|
|||
noFileOrQrCode: Please scan a QR Code, or select a file
|
||||
signatureFailed: Error while signing pass on server
|
||||
decodingFailed: Failed to decode QR code payload
|
||||
invalidColor: Invalid color
|
||||
vaccinationInfo: Failed to read vaccination information
|
||||
nameMissing: Failed to read name
|
||||
dobMissing: Failed to read date of birth
|
||||
invalidMedicalProduct: Invalid medical product
|
||||
invalidCountryCode: Invalid country code
|
||||
invalidManufacturer: Invalid manufacturer
|
||||
invalidFileType: Invalid file type
|
||||
couldNotDecode: Could not decode QR code from file
|
||||
couldNotFindQrCode: Could not find QR Code in provided file
|
||||
invalidQrCode: Invalid QR code
|
|
@ -0,0 +1,27 @@
|
|||
heading: Information according to § 5 TMG
|
||||
contact: Contact
|
||||
euDisputeResolution: EU Dispute Resolution
|
||||
euDisputeResolutionParagraph: |
|
||||
The European Commission provides a platform for online dispute resolution (OS) https://ec.europa.eu/consumers/odr.
|
||||
You can find our e-mail address in the imprint above.
|
||||
consumerDisputeResolution: Consumer dispute resolution / universal arbitration board
|
||||
consumerDisputeResolutionParagraph: We are not willing or obliged to participate in dispute resolution proceedings before a consumer arbitration board.
|
||||
liabilityForContents: Liability for contents
|
||||
liabilityForContentsParagraph: |
|
||||
As a service provider, we are responsible for our own content on these pages in accordance with § 7 paragraph 1 TMG under the general laws.
|
||||
According to §§ 8 to 10 TMG, we are not obligated to monitor transmitted or stored information or to investigate circumstances that indicate illegal activity.
|
||||
Obligations to remove or block the use of information under the general laws remain unaffected.
|
||||
However, liability in this regard is only possible from the point in time at which a concrete infringement of the law becomes known.
|
||||
If we become aware of any such infringements, we will remove the relevant content immediately.
|
||||
liabilityForLinks: Liability for links
|
||||
liabilityForLinksParagraph: |
|
||||
Our offer contains links to external websites of third parties, on whose contents we have no influence.
|
||||
Therefore, we cannot assume any liability for these external contents.
|
||||
The respective provider or operator of the sites is always responsible for the content of the linked sites.
|
||||
The linked pages were checked for possible legal violations at the time of linking.
|
||||
Illegal contents were not recognizable at the time of linking.
|
||||
However, a permanent control of the contents of the linked pages is not reasonable without concrete evidence of a violation of the law.
|
||||
If we become aware of any infringements, we will remove such links immediately.
|
||||
credits: Credits
|
||||
creditsSource: With excerpts from https://www.e-recht24.de/impressum-generator.html
|
||||
creditsTranslation: Translated with https://www.DeepL.com/Translator (free version)
|
|
@ -0,0 +1,26 @@
|
|||
iosHint: On iOS, please use the Safari Browser.
|
||||
errorClose: Close
|
||||
selectCertificate: Select Certificate
|
||||
selectCertificateDescription: |
|
||||
Please scan the QR code on your certificate or select a screenshot or PDF page with the QR code.
|
||||
Note that selecting a file directly from camera is not supported.
|
||||
stopCamera: Stop Camera
|
||||
startCamera: Start Camera
|
||||
openFile: Select File (PDF, PNG)
|
||||
foundQrCode: Found QR Code!
|
||||
pickColor: Pick a Color
|
||||
pickColorDescription: Pick a background color for your pass.
|
||||
colorWhite: white
|
||||
colorBlack: black
|
||||
colorGrey: grey
|
||||
colorGreen: green
|
||||
colorIndigo: indigo
|
||||
colorBlue: blue
|
||||
colorPurple: purple
|
||||
colorTeal: teal
|
||||
addToWallet: Add to Wallet
|
||||
dataPrivacyDescription: |
|
||||
Data privacy is of special importance when processing health-related data.
|
||||
In order for you to make an informed decision, please read the
|
||||
iAcceptThe: I accept the
|
||||
privacyPolicy: Privacy Policy
|
|
@ -0,0 +1,57 @@
|
|||
gdprNotice: |
|
||||
Our privacy policy is based on the terms used by the European legislator
|
||||
for the adoption of the General Data Protection Regulation (GDPR).
|
||||
generalInfo: General information
|
||||
generalInfoProcess: |
|
||||
The whole process of generating the pass file happens locally in your browser.
|
||||
For the signing step, only a hashed representation of your data is sent to the server.
|
||||
generalInfoStoring: Your data is not stored beyond the active browser session and the site does not use cookies.
|
||||
generalInfoThirdParties: No data is sent to third parties.
|
||||
generalInfoHttps: We transmit your data securely over https.
|
||||
generalInfoLocation: Our server is hosted in Nuremberg, Germany.
|
||||
generalInfoGitHub: The source code of this site is available on
|
||||
generalInfoLockScreen: By default, Apple Wallet passes are accessible from the lock screen. This can be changed in the
|
||||
settings: settings
|
||||
generalInfoProvider: |
|
||||
The server provider processes data to provide this site.
|
||||
In order to better understand what measures they take to protect your data, please also read their
|
||||
privacyPolicy: privacy policy
|
||||
andThe: and the
|
||||
dataPrivacyFaq: data privacy FAQ
|
||||
contact: Contact
|
||||
email: Email
|
||||
website: Website
|
||||
process: Simplified of the process
|
||||
processFirst: First, the following steps happen locally in your browser
|
||||
processSecond: Second, the following steps happen on our server
|
||||
processThird: Finally, the following steps happen locally in your browser
|
||||
processRecognizing: Recognizing and extracting the QR code data from your selected certificate
|
||||
processDecoding: Decoding your personal and health-related data from the QR code payload
|
||||
processAssembling: Assembling an incomplete pass file out of your data
|
||||
processGenerating: Generating a file containing hashes of the data stored in the pass file
|
||||
processSending: Sending only the file containing the hashes to our server
|
||||
processReceiving: Receiving and checking the hashes which were generated locally
|
||||
processSigning: Signing the file containing the hashes
|
||||
processSendingBack: Sending the signature back
|
||||
processCompleting: Assembling the signed pass file out of the incomplete file generated locally and the signature
|
||||
processSaving: Saving the file on your device
|
||||
locallyProcessedData: Locally processed data
|
||||
the: The
|
||||
schema: Digital Covid Certificate Schema
|
||||
specification: contains a detailed specification of which data can be contained in the QR code and will be processed in your browser.
|
||||
serverProvider: Server provider
|
||||
serverProviderIs: Our server provider is
|
||||
logFiles: The following data may be collected and stored in the server log files
|
||||
logFilesBrowser: The browser types and versions used
|
||||
logFilesOs: The operating system used by the accessing system
|
||||
logFilesReferrer: The website from which an accessing system reaches our website (so-called referrers)
|
||||
logFilesTime: The date and time of access
|
||||
logFilesIpAddress: The pseudonymised IP addresses
|
||||
rights: Your rights
|
||||
rightsGranted: In accordance with the GDPR you have the following rights
|
||||
rightsAccess: Right of access to your data; You have the right to know what data has been collected about you and how it was processed.
|
||||
rightsErasure: Right to be forgotten; Erasure of your personal data.
|
||||
rightsRectification: Right of rectification; You have the right to correct inaccurate data.
|
||||
rightsPortability: Right of data portability; You have the right to transfer your data from one processing system into another.
|
||||
thirdParties: Third parties linked
|
||||
appleSync: Apple may sync your passes via iCloud
|
|
@ -26,11 +26,7 @@ export function decodeData(data: string): Object {
|
|||
|
||||
if (data.startsWith(':')) {
|
||||
data = data.substring(1);
|
||||
} else {
|
||||
console.log("Warning: unsafe HC1: header");
|
||||
}
|
||||
} else {
|
||||
console.log("Warning: no HC1: header");
|
||||
}
|
||||
|
||||
var arrayBuffer: Uint8Array = base45.decode(data);
|
||||
|
@ -41,12 +37,8 @@ export function decodeData(data: string): Object {
|
|||
|
||||
var payloadArray: Array<Uint8Array> = cbor.decode(typedArrayToBuffer(arrayBuffer));
|
||||
|
||||
if (!Array.isArray(payloadArray)) {
|
||||
throw new Error('Expecting Array');
|
||||
}
|
||||
|
||||
if (payloadArray.length !== 4) {
|
||||
throw new Error('Expecting Array of length 4');
|
||||
if (!Array.isArray(payloadArray) || payloadArray.length !== 4) {
|
||||
throw new Error('decodingFailed');
|
||||
}
|
||||
|
||||
var plaintext: Uint8Array = payloadArray[2];
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import {toBuffer as createZip} from 'do-not-zip';
|
||||
import {v4 as uuid4} from 'uuid';
|
||||
|
||||
import {Constants} from "./constants";
|
||||
import {Payload, PayloadBody} from "./payload";
|
||||
import {ValueSets} from "./value_sets";
|
||||
import {toBuffer as createZip} from 'do-not-zip';
|
||||
import {v4 as uuid4} from 'uuid';
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
|
@ -79,7 +80,7 @@ export class PassData {
|
|||
})
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw Error("Error while singing Pass on server")
|
||||
throw Error('signatureFailed')
|
||||
}
|
||||
|
||||
return await response.arrayBuffer()
|
||||
|
|
|
@ -36,7 +36,7 @@ export class Payload {
|
|||
let colors = Constants.COLORS;
|
||||
|
||||
if (!(body.color in colors)) {
|
||||
throw new Error('Invalid color');
|
||||
throw new Error('invalidColor');
|
||||
}
|
||||
|
||||
const dark = body.color != 'white'
|
||||
|
@ -48,15 +48,15 @@ export class Payload {
|
|||
const dateOfBirthInformation = body.decodedData['-260']['1']['dob'];
|
||||
|
||||
if (vaccinationInformation == undefined) {
|
||||
throw new Error('Failed to read vaccination information');
|
||||
throw new Error('vaccinationInfo');
|
||||
}
|
||||
|
||||
if (nameInformation == undefined) {
|
||||
throw new Error('Failed to read name');
|
||||
throw new Error('nameMissing');
|
||||
}
|
||||
|
||||
if (dateOfBirthInformation == undefined) {
|
||||
throw new Error('Failed to read date of birth');
|
||||
throw new Error('dobMissing');
|
||||
}
|
||||
|
||||
// Get Medical, country and manufacturer information
|
||||
|
@ -65,13 +65,13 @@ export class Payload {
|
|||
const manufacturerKey = vaccinationInformation['ma'];
|
||||
|
||||
if (!(medialProductKey in valueSets.medicalProducts)) {
|
||||
throw new Error('Invalid medical product key');
|
||||
throw new Error('invalidMedicalProduct');
|
||||
}
|
||||
if (!(countryCode in valueSets.countryCodes)) {
|
||||
throw new Error('Invalid country code')
|
||||
throw new Error('invalidCountryCode')
|
||||
}
|
||||
if (!(manufacturerKey in valueSets.manufacturers)) {
|
||||
throw new Error('Invalid manufacturer')
|
||||
throw new Error('invalidManufacturer')
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,7 +91,6 @@ export class Payload {
|
|||
this.dateOfBirth = dateOfBirthInformation;
|
||||
this.uvci = vaccinationInformation['ci'];
|
||||
this.certificateIssuer = vaccinationInformation['is'];
|
||||
this.medicalProductKey = medialProductKey; // TODO is this needed?
|
||||
|
||||
this.countryOfVaccination = valueSets.countryCodes[countryCode].display;
|
||||
this.vaccineName = valueSets.medicalProducts[medialProductKey].display;
|
||||
|
|
|
@ -23,7 +23,7 @@ export async function getPayloadBodyFromFile(file: File, color: string): Promise
|
|||
imageData = await getImageDataFromPng(fileBuffer)
|
||||
break
|
||||
default:
|
||||
throw Error('Invalid File Type')
|
||||
throw Error('invalidFileType')
|
||||
}
|
||||
|
||||
let code: QRCode;
|
||||
|
@ -33,11 +33,11 @@ export async function getPayloadBodyFromFile(file: File, color: string): Promise
|
|||
inversionAttempts: "dontInvert",
|
||||
});
|
||||
} catch (e) {
|
||||
throw Error("Could not decode QR Code from File");
|
||||
throw Error('couldNotDecode');
|
||||
}
|
||||
|
||||
if (code == undefined) {
|
||||
throw Error("Could not find QR Code in provided File")
|
||||
throw Error('couldNotFindQrCode')
|
||||
}
|
||||
|
||||
// Get raw data
|
||||
|
@ -49,7 +49,7 @@ export async function getPayloadBodyFromFile(file: File, color: string): Promise
|
|||
try {
|
||||
decodedData = decodeData(rawData);
|
||||
} catch (e) {
|
||||
throw Error("Invalid QR Code")
|
||||
throw Error('invalidQrCode')
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -70,7 +70,7 @@ export async function getPayloadBodyFromQR(qrCodeResult: Result, color: string):
|
|||
try {
|
||||
decodedData = decodeData(rawData);
|
||||
} catch (e) {
|
||||
throw Error("Invalid QR Code")
|
||||
throw Error("invalidQrCode")
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue