This commit is contained in:
Philip 2023-10-23 18:56:29 +02:00
parent 375c4a212b
commit 12907e5b46
17 changed files with 112 additions and 85 deletions

View file

@ -178,13 +178,16 @@ const ProductSearch = () => {
> >
<legend>{t("enterbarcode")}</legend> <legend>{t("enterbarcode")}</legend>
<fieldset> <fieldset>
<legend>{t("enterbarcode")}</legend>
<Scan <Scan
onDetected={(barcode) => setBarcode(barcode)} onDetected={(barcode) => setBarcode(barcode)}
handleSubmit={(barcode) => handleSubmit(barcode)} handleSubmit={(barcode) => handleSubmit(barcode)}
/> />
<label htmlFor="barcodeInput" className="hidden">{t("enterbarcode")}</label>
<input <input
type="number" type="number"
name="barcode" name="barcode"
id="barcodeInput"
placeholder={t("enterbarcode")} placeholder={t("enterbarcode")}
autoFocus={true} autoFocus={true}
value={barcode} value={barcode}

View file

@ -2,7 +2,7 @@ import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useState } from "react"; import { useState } from "react";
const SupportOption: React.FC = () => { const SupportOption = () => {
const t = useTranslations("More"); const t = useTranslations("More");
const [icon, setIcon] = useState<string>("icon-paypal"); const [icon, setIcon] = useState<string>("icon-paypal");
const [vendor, setVendor] = useState<string>("PayPal"); const [vendor, setVendor] = useState<string>("PayPal");

View file

@ -1,13 +1,15 @@
import { useTranslations } from 'next-intl'; import { useTranslations } from "next-intl";
import React, { useState, useEffect } from 'react'; import { useState, useEffect } from "react";
const OLEDMode: React.FC = () => { const OLEDMode = () => {
const t = useTranslations('More'); const t = useTranslations("More");
const [isChecked, setIsChecked] = useState<boolean>(false); const [isChecked, setIsChecked] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false); const [error, setError] = useState<boolean>(false);
const setThemeColorAttribute = (color: string) => { const setThemeColorAttribute = (color: string) => {
const themeColorElement = document.querySelector<HTMLMetaElement>('meta[name="theme-color"][media="(prefers-color-scheme: dark)"]'); const themeColorElement = document.querySelector<HTMLMetaElement>(
'meta[name="theme-color"][media="(prefers-color-scheme: dark)"]'
);
if (themeColorElement) { if (themeColorElement) {
themeColorElement.setAttribute("content", color); themeColorElement.setAttribute("content", color);
@ -16,7 +18,10 @@ const OLEDMode: React.FC = () => {
useEffect(() => { useEffect(() => {
const localStorageValue = localStorage.getItem("oled"); const localStorageValue = localStorage.getItem("oled");
if (localStorageValue === "true" && window.matchMedia('(prefers-color-scheme: dark)').matches) { if (
localStorageValue === "true" &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
document.documentElement.setAttribute("data-theme", "oled"); document.documentElement.setAttribute("data-theme", "oled");
setThemeColorAttribute("#000"); setThemeColorAttribute("#000");
setIsChecked(true); setIsChecked(true);
@ -24,14 +29,17 @@ const OLEDMode: React.FC = () => {
}, []); }, []);
const handleClick = () => { const handleClick = () => {
if (!isChecked && !window.matchMedia('(prefers-color-scheme: dark)').matches) { if (
!isChecked &&
!window.matchMedia("(prefers-color-scheme: dark)").matches
) {
setError(true); setError(true);
return; return;
} }
if (!isChecked) { if (!isChecked) {
document.documentElement.setAttribute("data-theme", "oled"); document.documentElement.setAttribute("data-theme", "oled");
setThemeColorAttribute("#000"); setThemeColorAttribute("#000");
localStorage.setItem('oled', 'true'); localStorage.setItem("oled", "true");
} else { } else {
localStorage.clear(); localStorage.clear();
document.documentElement.removeAttribute("data-theme"); document.documentElement.removeAttribute("data-theme");
@ -42,11 +50,19 @@ const OLEDMode: React.FC = () => {
}; };
return ( return (
<span className="Grid switcher"> <label htmlFor="oled-switch" className="Grid switcher">
<div className="Grid-cell description"> <div className="Grid-cell description">
OLED-Mode OLED-Mode
<span className="info" id="cookieinfo">{t('thissetsacookie')}</span> <span className="info" id="cookieinfo">
<span className={`info ${error ? "animated fadeIn" : ""}`} id="oledinfo" style={{display: error ? "block" : "none"}}>{t('activatedarkmode')}</span> {t("thissetsacookie")}
</span>
<span
className={`info ${error ? "animated fadeIn" : ""}`}
id="oledinfo"
style={{ display: error ? "block" : "none" }}
>
{t("activatedarkmode")}
</span>
</div> </div>
<div className="Grid-cell icons"> <div className="Grid-cell icons">
<input <input
@ -58,7 +74,7 @@ const OLEDMode: React.FC = () => {
onChange={handleClick} onChange={handleClick}
/> />
</div> </div>
</span> </label>
); );
}; };

View file

@ -9,9 +9,18 @@ interface ModalProps {
children: React.ReactNode; children: React.ReactNode;
} }
const ModalWrapper: React.FC<ModalProps> = ({ children, id, buttonType, buttonClass, buttonText }) => { const ModalWrapper = ({
children,
id,
buttonType,
buttonClass,
buttonText,
}: ModalProps) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const modalRoot = typeof document !== 'undefined' ? document.getElementById("modal-root") : null; const modalRoot =
typeof document !== "undefined"
? document.getElementById("modal-root")
: null;
useEffect(() => { useEffect(() => {
const handleEscapeKeyPress = (event: KeyboardEvent) => { const handleEscapeKeyPress = (event: KeyboardEvent) => {
@ -35,7 +44,6 @@ const ModalWrapper: React.FC<ModalProps> = ({ children, id, buttonType, buttonCl
} }
}; };
document.addEventListener("keydown", handleEscapeKeyPress); document.addEventListener("keydown", handleEscapeKeyPress);
document.addEventListener("touchstart", handleTouchStart); document.addEventListener("touchstart", handleTouchStart);
@ -91,15 +99,12 @@ const ModalWrapper: React.FC<ModalProps> = ({ children, id, buttonType, buttonCl
{buttonText} {buttonText}
</div> </div>
)} )}
{isOpen && modalRoot && {isOpen &&
modalRoot &&
createPortal( createPortal(
<div className="modal_view animated fadeInUp open"> <div className="modal_view animated fadeInUp open">
<div className="modal_close"> <div className="modal_close">
<a <a className="btn-dark" data-dismiss="modal" onClick={closeModal}>
className="btn-dark"
data-dismiss="modal"
onClick={closeModal}
>
× ×
</a> </a>
</div> </div>

View file

@ -9,7 +9,7 @@ interface ShareButtonProps {
barcode: string; barcode: string;
} }
const ShareButton: React.FC<ShareButtonProps> = ({ productName, barcode }) => { const ShareButton = ({ productName, barcode }: ShareButtonProps) => {
const t = useTranslations("Check"); const t = useTranslations("Check");
const [showButton, setShowButton] = useState<boolean>(false); const [showButton, setShowButton] = useState<boolean>(false);
@ -91,7 +91,9 @@ const ShareButton: React.FC<ShareButtonProps> = ({ productName, barcode }) => {
text, text,
url, url,
}) })
.catch((err) => {console.error(err)}); .catch((err) => {
console.error(err);
});
}} }}
> >
{t("share")} {t("share")}

View file

@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useState, useEffect, FC } from "react"; import { useState, useEffect } from "react";
interface ExtendedWindow extends Window { interface ExtendedWindow extends Window {
MSStream?: any; MSStream?: any;
} }
const Shortcut: FC = () => { const Shortcut = () => {
const t = useTranslations("ShortcutPrompt"); const t = useTranslations("ShortcutPrompt");
const [showShortcut, setShowShortcut] = useState<boolean>(false); const [showShortcut, setShowShortcut] = useState<boolean>(false);
@ -16,7 +16,8 @@ const Shortcut: FC = () => {
const windowWithMSStream = window as ExtendedWindow; const windowWithMSStream = window as ExtendedWindow;
const isIOS: boolean = const isIOS: boolean =
/iPad|iPhone|iPod/.test(navigator.userAgent) && !windowWithMSStream.MSStream; /iPad|iPhone|iPod/.test(navigator.userAgent) &&
!windowWithMSStream.MSStream;
if ( if (
!window.matchMedia("(display-mode: standalone)").matches && !window.matchMedia("(display-mode: standalone)").matches &&

View file

@ -5,11 +5,6 @@ import React, { useState, FormEvent } from "react";
import ModalWrapper from "@/components/elements/modalwrapper"; import ModalWrapper from "@/components/elements/modalwrapper";
export interface FlaggedItem {
item: string;
index: number;
}
const IngredientsCheck = () => { const IngredientsCheck = () => {
const t = useTranslations("Ingredients"); const t = useTranslations("Ingredients");
const [flagged, setFlagged] = useState<string[]>([]); const [flagged, setFlagged] = useState<string[]>([]);

View file

@ -0,0 +1,4 @@
export type FlaggedItem = {
item: string;
index: number;
};

View file

@ -1,17 +1,27 @@
import { GetStaticPropsContext } from 'next' import { GetStaticPropsContext } from "next";
import { useTranslations } from 'next-intl'; import { useTranslations } from "next-intl";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
export default function NotFound() { export default function NotFound() {
const t = useTranslations('404'); const t = useTranslations("404");
return ( return (
<> <>
<Nav /> <Nav />
<Container heading={t('error404')}> <Container heading={t("error404")}>
<p>{t('pagedoesnotexist')}</p> <p>{t("pagedoesnotexist")}</p>
<p dangerouslySetInnerHTML={{ __html: t('message', {statuspage: `<a href="https://stats.uptimerobot.com/LY1gRuP5j6/789004495">${t('statuspage')}</a>`, mastodon: '<a href="https://veganism.social/@vegancheck">Mastodon</a>'})}} /> <p
dangerouslySetInnerHTML={{
__html: t("message", {
statuspage: `<a href="https://stats.uptimerobot.com/LY1gRuP5j6/789004495">${t(
"statuspage"
)}</a>`,
mastodon:
'<a href="https://veganism.social/@vegancheck">Mastodon</a>',
}),
}}
/>
</Container> </Container>
</> </>
); );
@ -20,7 +30,7 @@ export default function NotFound() {
export async function getStaticProps({ locale }: GetStaticPropsContext) { export async function getStaticProps({ locale }: GetStaticPropsContext) {
return { return {
props: { props: {
messages: (await import(`../locales/${locale}.json`)).default messages: (await import(`../locales/${locale}.json`)).default,
} },
}; };
} }

View file

@ -1,13 +1,8 @@
import "@/styles/style.scss"; import "@/styles/style.scss";
import { init } from "@socialgouv/matomo-next";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import { NextIntlProvider } from "next-intl"; import { NextIntlProvider } from "next-intl";
import { useEffect } from "react";
export default function App({ Component, pageProps }: AppProps) { export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
init({ url: "https://analytics.vegancheck.me", siteId: "1" });
}, []);
return ( return (
<> <>
<NextIntlProvider messages={pageProps.messages}> <NextIntlProvider messages={pageProps.messages}>

View file

@ -1,28 +1,26 @@
import { GetStaticPropsContext } from 'next' import { GetStaticPropsContext } from "next";
import { useTranslations } from 'next-intl'; import { useTranslations } from "next-intl";
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
const Impressum = () => { const Impressum = () => {
const t = useTranslations(); const t = useTranslations();
const [impressum, setImpressum] = useState(''); const [impressum, setImpressum] = useState("");
useEffect(() => { useEffect(() => {
fetch('https://philipbrembeck.com/impressum.txt', {method: "GET"}) fetch("https://philipbrembeck.com/impressum.txt", { method: "GET" })
.then(response => response.text()) .then((response) => response.text())
.then(text => setImpressum(text)) .then((text) => setImpressum(text))
.catch(error => console.error(error)); .catch((error) => console.error(error));
}, []); }, []);
return ( return (
<> <>
<Nav /> <Nav />
<Container heading={t('More.imprint')}> <Container heading={t("More.imprint")}>
<p className="small"> <p className="small">{t("Privacy.germanonly")}</p>
{t('Privacy.germanonly')}
</p>
<div dangerouslySetInnerHTML={{ __html: impressum }} /> <div dangerouslySetInnerHTML={{ __html: impressum }} />
</Container> </Container>
</> </>
@ -34,7 +32,7 @@ export default Impressum;
export async function getStaticProps({ locale }: GetStaticPropsContext) { export async function getStaticProps({ locale }: GetStaticPropsContext) {
return { return {
props: { props: {
messages: (await import(`../locales/${locale}.json`)).default messages: (await import(`../locales/${locale}.json`)).default,
} },
}; };
} }

View file

@ -1,7 +1,7 @@
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
import IngredientsCheck from '@/components/ingredientscheck' import IngredientsCheck from "@/components/ingredientscheck";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
export default function ingredients() { export default function ingredients() {
@ -9,10 +9,7 @@ export default function ingredients() {
<> <>
<div id="modal-root"></div> <div id="modal-root"></div>
<Nav /> <Nav />
<Container <Container logo={false} backbutton={false}>
logo={false}
backbutton={false}
>
<IngredientsCheck /> <IngredientsCheck />
</Container> </Container>
</> </>

View file

@ -108,7 +108,10 @@ export default function More() {
<span className="unknown icon-right-open"></span> <span className="unknown icon-right-open"></span>
</div> </div>
</Link> </Link>
<a href="https://frontendnet.work/vegancheck-api" className="Grid links"> <a
href="https://frontendnet.work/vegancheck-api"
className="Grid links"
>
<div className="Grid-cell description">{t("apidocumentation")}</div> <div className="Grid-cell description">{t("apidocumentation")}</div>
<div className="Grid-cell icons"> <div className="Grid-cell icons">
<span className="unknown icon-right-open"></span> <span className="unknown icon-right-open"></span>

View file

@ -1,19 +1,17 @@
import { GetStaticPropsContext } from 'next' import { GetStaticPropsContext } from "next";
import { useTranslations } from 'next-intl'; import { useTranslations } from "next-intl";
import Container from "@/components/elements/container"; import Container from "@/components/elements/container";
import Nav from "@/components/nav"; import Nav from "@/components/nav";
export default function TOS() { export default function TOS() {
const t = useTranslations('TOS'); const t = useTranslations("TOS");
return ( return (
<> <>
<Nav /> <Nav />
<Container heading={t('tos')}> <Container heading={t("tos")}>
<p className="small"> <p className="small">{t("englishgermanonly")}</p>
{t('englishgermanonly')} <div dangerouslySetInnerHTML={{ __html: t.raw("tos_content") }} />
</p>
<div dangerouslySetInnerHTML={{__html: t.raw('tos_content')}} />
</Container> </Container>
</> </>
); );
@ -22,7 +20,7 @@ export default function TOS() {
export async function getStaticProps({ locale }: GetStaticPropsContext) { export async function getStaticProps({ locale }: GetStaticPropsContext) {
return { return {
props: { props: {
messages: (await import(`../locales/${locale}.json`)).default messages: (await import(`../locales/${locale}.json`)).default,
} },
}; };
} }

View file

@ -1,3 +1,3 @@
/* Do use: Normalize.css as a CSS reset */ /* Do use: Normalize.css as a CSS reset */
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}details,main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0}code,kbd,pre,samp{font-family:monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{padding:.35em .75em .625em}legend{color:inherit;display:table;max-width:100%;white-space:normal}textarea{overflow:auto}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}[hidden],template{display:none} button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}[type=checkbox],[type=radio],legend{box-sizing:border-box;padding:0}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}details,main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0}code,kbd,pre,samp{font-family:monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{padding:.35em .75em .625em}legend{color:inherit;display:table;max-width:100%;white-space:normal}textarea{overflow:auto}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}[hidden],template{display:none}.hidden{display:none;}

View file

@ -118,7 +118,7 @@
display: block; display: block;
font-weight: 400; font-weight: 400;
font-size: rem(14.4px); font-size: rem(14.4px);
color: #ccc; color: #595959;
margin-top: 0; margin-top: 0;
padding: 0; padding: 0;
line-height: rem(16px); line-height: rem(16px);