mirror of
https://github.com/frontendnetwork/vegancheck.me
synced 2024-11-14 16:17:08 +00:00
feat: Init Change to UseAppRouter
This commit is contained in:
parent
39416c973a
commit
82ad7b2818
26 changed files with 333 additions and 433 deletions
|
@ -1,20 +1,23 @@
|
||||||
const million = require("million/compiler");
|
import million from 'million/compiler';
|
||||||
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
|
||||||
|
const withNextIntl = createNextIntlPlugin();
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
let nextConfig = {
|
let nextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
productionBrowserSourceMaps: true,
|
productionBrowserSourceMaps: true,
|
||||||
i18n: {
|
|
||||||
locales: ["de", "en", "fr", "es", "pl", "cz"],
|
|
||||||
defaultLocale: "en",
|
|
||||||
},
|
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: "/datenschutz",
|
source: "/datenschutz",
|
||||||
destination: "/privacy-policy",
|
destination: "/privacy-policy",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/impressum",
|
||||||
|
destination: "/en/impressum",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -23,6 +26,8 @@ const millionConfig = {
|
||||||
auto: { rsc: true },
|
auto: { rsc: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Apply Million's optimization
|
||||||
nextConfig = million.next(nextConfig, millionConfig);
|
nextConfig = million.next(nextConfig, millionConfig);
|
||||||
|
|
||||||
module.exports = nextConfig;
|
// Apply next-intl plugin
|
||||||
|
export default withNextIntl(nextConfig);
|
38
package-lock.json
generated
38
package-lock.json
generated
|
@ -19,6 +19,7 @@
|
||||||
"next": "14.2.15",
|
"next": "14.2.15",
|
||||||
"next-intl": "^3.20.0",
|
"next-intl": "^3.20.0",
|
||||||
"nookies": "^2.5.2",
|
"nookies": "^2.5.2",
|
||||||
|
"prom-client": "^15.1.3",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"sass": "^1.79.4",
|
"sass": "^1.79.4",
|
||||||
|
@ -2869,6 +2870,15 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@opentelemetry/api": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
|
@ -3987,6 +3997,12 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bintrees": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
@ -7715,6 +7731,19 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prom-client": {
|
||||||
|
"version": "15.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz",
|
||||||
|
"integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@opentelemetry/api": "^1.4.0",
|
||||||
|
"tdigest": "^0.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16 || ^18 || >=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
@ -8651,6 +8680,15 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tdigest": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bintrees": "1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/temp-dir": {
|
"node_modules/temp-dir": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"next": "14.2.15",
|
"next": "14.2.15",
|
||||||
"next-intl": "^3.20.0",
|
"next-intl": "^3.20.0",
|
||||||
"nookies": "^2.5.2",
|
"nookies": "^2.5.2",
|
||||||
|
"prom-client": "^15.1.3",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"sass": "^1.79.4",
|
"sass": "^1.79.4",
|
||||||
|
|
1
project.inlang/.gitignore
vendored
Normal file
1
project.inlang/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
cache
|
|
@ -1,16 +1,17 @@
|
||||||
import { GetStaticPropsContext } from "next";
|
"use client";
|
||||||
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import React, { useState, useEffect } from "react";
|
import { 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 = () => {
|
export default function 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")
|
||||||
.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));
|
||||||
|
@ -25,14 +26,4 @@ const Impressum = () => {
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
export default Impressum;
|
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`../locales/${locale}.json`)).default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
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 IngredientsPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="modal-root"></div>
|
<div id="modal-root"></div>
|
||||||
|
@ -15,11 +13,3 @@ export default function ingredients() {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`../locales/${locale}.json`)).default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
67
src/app/[locale]/layout.tsx
Normal file
67
src/app/[locale]/layout.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import "@/styles/style.scss";
|
||||||
|
import type { Metadata, Viewport } from "next";
|
||||||
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
|
import { getMessages } from "next-intl/server";
|
||||||
|
|
||||||
|
import Nav from "@/components/nav";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Is it vegan? – Veganify",
|
||||||
|
description:
|
||||||
|
"Are you unsure whether a product is vegan or not? With Veganify you can scan the bar code of an item while shopping and check whether it is vegan or not and that without a lot of other unnecessary information! Try it out now!",
|
||||||
|
openGraph: {
|
||||||
|
title: "Veganify",
|
||||||
|
type: "website",
|
||||||
|
url: "https://veganify.app",
|
||||||
|
images: [{ url: "https://veganify.app/img/og_image.png" }],
|
||||||
|
siteName: "Veganify",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
images: [{ url: "https://veganify.app/img/og_image.png", alt: "Veganify" }],
|
||||||
|
},
|
||||||
|
appleWebApp: {
|
||||||
|
capable: true,
|
||||||
|
title: "Veganify",
|
||||||
|
statusBarStyle: "default",
|
||||||
|
},
|
||||||
|
manifest: "/manifest.json",
|
||||||
|
icons: {
|
||||||
|
icon: "../favicon.ico",
|
||||||
|
apple: "../img/icon.png",
|
||||||
|
},
|
||||||
|
applicationName: "Veganify",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: [
|
||||||
|
{ media: "(prefers-color-scheme: light)", color: "#7f8fa6" },
|
||||||
|
{ media: "(prefers-color-scheme: dark)", color: "#000000" },
|
||||||
|
],
|
||||||
|
width: "device-width",
|
||||||
|
initialScale: 1,
|
||||||
|
maximumScale: 1,
|
||||||
|
viewportFit: "cover",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function LocaleLayout({
|
||||||
|
children,
|
||||||
|
params: { locale },
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: { locale: string };
|
||||||
|
}) {
|
||||||
|
const messages = await getMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale}>
|
||||||
|
<body>
|
||||||
|
<NextIntlClientProvider messages={messages}>
|
||||||
|
<div id="modal-root"></div>
|
||||||
|
<Nav />
|
||||||
|
{children}
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
import { GetStaticPropsContext } from "next";
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { setCookie } from "nookies";
|
import { setCookie } from "nookies";
|
||||||
|
|
||||||
import Container from "@/components/elements/container";
|
import Container from "@/components/elements/container";
|
||||||
|
@ -10,17 +9,17 @@ import SupportOption from "@/components/elements/contents/donate";
|
||||||
import OLEDMode from "@/components/elements/contents/oledmode";
|
import OLEDMode from "@/components/elements/contents/oledmode";
|
||||||
import ModalWrapper from "@/components/elements/modalwrapper";
|
import ModalWrapper from "@/components/elements/modalwrapper";
|
||||||
import Nav from "@/components/nav";
|
import Nav from "@/components/nav";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
export default function More() {
|
export default function More() {
|
||||||
const router = useRouter();
|
|
||||||
const t = useTranslations("More");
|
const t = useTranslations("More");
|
||||||
|
const currentLocale = useLocale();
|
||||||
|
|
||||||
function handleLanguageChange(locale: string) {
|
function handleLanguageChange(locale: string) {
|
||||||
setCookie(null, "NEXT_LOCALE", locale, {
|
setCookie(null, "NEXT_LOCALE", locale, {
|
||||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||||
path: "/",
|
path: "/",
|
||||||
});
|
});
|
||||||
router.push("/more", undefined, { locale });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -99,10 +98,7 @@ export default function More() {
|
||||||
<span className="unknown icon-right-open"></span>
|
<span className="unknown icon-right-open"></span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a href="https://frontendnet.work/veganify-api" className="Grid links">
|
||||||
href="https://frontendnet.work/veganify-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>
|
||||||
|
@ -131,114 +127,39 @@ export default function More() {
|
||||||
/>
|
/>
|
||||||
<h1>{t("language")}</h1>
|
<h1>{t("language")}</h1>
|
||||||
</span>
|
</span>
|
||||||
|
{[
|
||||||
|
{ code: "en", name: "english" },
|
||||||
|
{ code: "de", name: "german" },
|
||||||
|
{ code: "es", name: "spanish" },
|
||||||
|
{ code: "fr", name: "french" },
|
||||||
|
{ code: "pl", name: "polish" },
|
||||||
|
{ code: "cz", name: "czech" },
|
||||||
|
].map(({ code, name }) => (
|
||||||
<Link
|
<Link
|
||||||
|
key={code}
|
||||||
className="nolink"
|
className="nolink"
|
||||||
href="/more"
|
href={`/more`}
|
||||||
locale="en"
|
locale={
|
||||||
onClick={() => handleLanguageChange("en")}
|
code as "en" | "de" | "es" | "fr" | "pl" | "cz" | undefined
|
||||||
|
}
|
||||||
|
onClick={() => handleLanguageChange(code)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={router.locale === "en" ? "option active" : "option"}
|
className={
|
||||||
|
currentLocale === code ? "option active" : "option"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="flexRadioDefault"
|
name="flexRadioDefault"
|
||||||
checked={router.locale === "en"}
|
checked={currentLocale === code}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
<span className="price">{t("english")}</span>
|
<span className="price">{t(name)}</span>
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="nolink"
|
|
||||||
href="/more"
|
|
||||||
locale="de"
|
|
||||||
onClick={() => handleLanguageChange("de")}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={router.locale === "de" ? "option active" : "option"}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
name="flexRadioDefault"
|
|
||||||
checked={router.locale === "de"}
|
|
||||||
/>
|
|
||||||
<span className="price">{t("german")}</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="nolink"
|
|
||||||
href="/more"
|
|
||||||
locale="es"
|
|
||||||
onClick={() => handleLanguageChange("es")}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={router.locale === "es" ? "option active" : "option"}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
name="flexRadioDefault"
|
|
||||||
checked={router.locale === "es"}
|
|
||||||
/>
|
|
||||||
<span className="price">{t("spanish")}</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="nolink"
|
|
||||||
href="/more"
|
|
||||||
locale="fr"
|
|
||||||
onClick={() => handleLanguageChange("fr")}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={router.locale === "fr" ? "option active" : "option"}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
name="flexRadioDefault"
|
|
||||||
checked={router.locale === "fr"}
|
|
||||||
/>
|
|
||||||
<span className="price">{t("french")}</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="nolink"
|
|
||||||
href="/more"
|
|
||||||
locale="pl"
|
|
||||||
onClick={() => handleLanguageChange("pl")}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={router.locale === "pl" ? "option active" : "option"}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
name="flexRadioDefault"
|
|
||||||
checked={router.locale === "pl"}
|
|
||||||
/>
|
|
||||||
<span className="price">{t("polish")}</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className="nolink"
|
|
||||||
href="/more"
|
|
||||||
locale="cz"
|
|
||||||
onClick={() => handleLanguageChange("cz")}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={router.locale === "cz" ? "option active" : "option"}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
name="flexRadioDefault"
|
|
||||||
checked={router.locale === "cz"}
|
|
||||||
/>
|
|
||||||
<span className="price">{t("czech")}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
))}
|
||||||
<span className="info" id="cookieinfo">
|
<span className="info" id="cookieinfo">
|
||||||
{t("thissetsacookie")}
|
{t("thissetsacookie")}
|
||||||
</span>
|
</span>
|
||||||
|
@ -256,11 +177,3 @@ export default function More() {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`../locales/${locale}.json`)).default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { GetStaticPropsContext } from "next";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
import ProductSearch from "@/components/check";
|
import ProductSearch from "@/components/check";
|
||||||
import InstallPrompt from "@/components/elements/pwainstall";
|
import InstallPrompt from "@/components/elements/pwainstall";
|
||||||
|
@ -6,6 +6,11 @@ import Shortcut from "@/components/elements/shortcutinstall";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import Nav from "@/components/nav";
|
import Nav from "@/components/nav";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Veganify - Check if products are vegan",
|
||||||
|
description: "Scan barcodes to check if products are vegan",
|
||||||
|
};
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -24,11 +29,3 @@ export default function Home() {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`../locales/${locale}.json`)).default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
32
src/app/[locale]/privacy-policy/page.tsx
Normal file
32
src/app/[locale]/privacy-policy/page.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import Container from "@/components/elements/container";
|
||||||
|
import Nav from "@/components/nav";
|
||||||
|
|
||||||
|
export default function PrivacyPolicy() {
|
||||||
|
const t = useTranslations("Privacy");
|
||||||
|
const [datenschutz, setDatenschutz] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("https://philipbrembeck.com/datenschutz.txt")
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((text) => setDatenschutz(text))
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Nav />
|
||||||
|
<Container>
|
||||||
|
<p className="small">{t("germanonly")}</p>
|
||||||
|
<div
|
||||||
|
className="privacy"
|
||||||
|
dangerouslySetInnerHTML={{ __html: datenschutz }}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
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";
|
||||||
|
@ -16,11 +15,3 @@ export default function TOS() {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`../locales/${locale}.json`)).default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,7 +1,16 @@
|
||||||
import Router from 'next/router';
|
"use client";
|
||||||
|
|
||||||
const BackButton = () => (
|
import { useRouter } from "next/navigation";
|
||||||
<span onClick={() => Router.back()} style={{cursor: "pointer"}} className="icon-left-open back"/>
|
|
||||||
);
|
const BackButton = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
onClick={() => router.back()}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
className="icon-left-open back"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default BackButton;
|
export default BackButton;
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import Veganify from "@frontendnetwork/veganify";
|
import Veganify from "@frontendnetwork/veganify";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
@ -60,7 +62,6 @@ const ProductSearch = () => {
|
||||||
);
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (data.status === 200) {
|
if (data.status === 200) {
|
||||||
|
|
||||||
if ("product" in data) {
|
if ("product" in data) {
|
||||||
setResult(data.product);
|
setResult(data.product);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
/* 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";
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import Veganify from "@frontendnetwork/veganify";
|
import Veganify from "@frontendnetwork/veganify";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import React, { useState, FormEvent } from "react";
|
import React, { useState, useCallback } from "react";
|
||||||
|
|
||||||
import ModalWrapper from "@/components/elements/modalwrapper";
|
import ModalWrapper from "@/components/elements/modalwrapper";
|
||||||
|
|
||||||
|
@ -14,37 +16,41 @@ const IngredientsCheck = () => {
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
const handleSubmit = useCallback(
|
||||||
|
async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
setVegan(null);
|
setVegan(null);
|
||||||
setSurelyVegan([]);
|
setSurelyVegan([]);
|
||||||
setNotVegan([]);
|
setNotVegan([]);
|
||||||
setMaybeVegan([]);
|
setMaybeVegan([]);
|
||||||
setError(false);
|
setError(false);
|
||||||
event.preventDefault();
|
|
||||||
const ingredients = event.currentTarget.elements.namedItem(
|
|
||||||
"ingredients"
|
|
||||||
) as HTMLInputElement;
|
|
||||||
|
|
||||||
const checkIngredients = async () => {
|
const formData = new FormData(event.currentTarget);
|
||||||
|
const ingredients = formData.get("ingredients") as string;
|
||||||
|
|
||||||
|
if (!ingredients) {
|
||||||
|
setError(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await Veganify.checkIngredientsList(
|
const data = await Veganify.checkIngredientsList(
|
||||||
ingredients.value,
|
ingredients,
|
||||||
process.env.NEXT_PUBLIC_STAGING === "true" ? true : false
|
process.env.NEXT_PUBLIC_STAGING === "true"
|
||||||
);
|
);
|
||||||
setVegan(data.data.vegan);
|
setVegan(data.data.vegan);
|
||||||
setSurelyVegan(data.data.surely_vegan);
|
setSurelyVegan(data.data.surely_vegan);
|
||||||
setNotVegan(data.data.not_vegan);
|
setNotVegan(data.data.not_vegan);
|
||||||
setMaybeVegan(data.data.maybe_vegan);
|
setMaybeVegan(data.data.maybe_vegan);
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(true);
|
setError(true);
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[]
|
||||||
checkIngredients();
|
);
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -68,9 +74,9 @@ const IngredientsCheck = () => {
|
||||||
placeholder={t("entercommaseperated")}
|
placeholder={t("entercommaseperated")}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
type="submit"
|
||||||
name="checkingredients"
|
name="checkingredients"
|
||||||
aria-label={t("submit")}
|
aria-label={t("submit")}
|
||||||
role="button"
|
|
||||||
>
|
>
|
||||||
<span className="icon-right-open"></span>
|
<span className="icon-right-open"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,76 +1,17 @@
|
||||||
import { GetStaticPropsContext } from "next";
|
"use client";
|
||||||
import Head from "next/head";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useEffect } from "react";
|
|
||||||
|
import { Link, usePathname } from "@/i18n/routing";
|
||||||
|
|
||||||
export default function Nav() {
|
export default function Nav() {
|
||||||
const t = useTranslations("Nav");
|
const t = useTranslations("Nav");
|
||||||
const router = useRouter();
|
const pathname = usePathname();
|
||||||
useEffect(() => {
|
|
||||||
const localStorageValue = localStorage.getItem("oled");
|
|
||||||
if (
|
|
||||||
localStorageValue === "true" &&
|
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
||||||
) {
|
|
||||||
document.documentElement.setAttribute("data-theme", "oled");
|
|
||||||
const themeColorMeta = document.querySelector('meta[name="theme-color"][media="(prefers-color-scheme: dark)"]');
|
|
||||||
if (themeColorMeta) {
|
|
||||||
themeColorMeta.setAttribute("content", "#000");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>{t("title")}</title>
|
|
||||||
<meta name="description" content={t("description")} />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<meta property="og:title" content={t("title")} />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:url" content="https://veganify.app" />
|
|
||||||
|
|
||||||
<meta
|
|
||||||
name="twitter:image:src"
|
|
||||||
content="https://veganify.app/img/og_image.png"
|
|
||||||
/>
|
|
||||||
<meta property="twitter:image:alt" content="Veganify" />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="https://veganify.app/img/og_image.png"
|
|
||||||
/>
|
|
||||||
<meta property="og:image:alt" content="Veganify" />
|
|
||||||
<meta property="og:site_name" content="Veganify" />
|
|
||||||
<meta property="og:type" content="object" />
|
|
||||||
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="../favicon.ico" />
|
|
||||||
<link rel="apple-touch-icon" href="../img/icon.png" />
|
|
||||||
|
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="msapplication-starturl" content="/" />
|
|
||||||
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
|
|
||||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#7f8fa6" key="pcl" />
|
|
||||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#141414" key="pcd" />
|
|
||||||
|
|
||||||
<meta name="application-name" content="Veganify" />
|
|
||||||
<meta name="apple-mobile-web-app-title" content="Veganify" />
|
|
||||||
</Head>
|
|
||||||
<nav className="nav">
|
<nav className="nav">
|
||||||
<div className="flex-container">
|
<div className="flex-container">
|
||||||
<div
|
<div className={pathname === "/" ? "flex-item active" : "flex-item"}>
|
||||||
className={
|
|
||||||
router.pathname == "/" ? "flex-item active" : "flex-item"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<span className="icon icon-vegancheck"></span>
|
<span className="icon icon-vegancheck"></span>
|
||||||
<span className="menu-item">{t("home")}</span>
|
<span className="menu-item">{t("home")}</span>
|
||||||
|
@ -78,9 +19,7 @@ export default function Nav() {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
router.pathname == "/ingredients"
|
pathname === "/ingredients" ? "flex-item active" : "flex-item"
|
||||||
? "flex-item active"
|
|
||||||
: "flex-item"
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Link href="/ingredients">
|
<Link href="/ingredients">
|
||||||
|
@ -91,7 +30,7 @@ export default function Nav() {
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
["/more", "/tos", "/privacy-policy", "/impressum"].includes(
|
["/more", "/tos", "/privacy-policy", "/impressum"].includes(
|
||||||
router.pathname
|
pathname
|
||||||
)
|
)
|
||||||
? "flex-item active"
|
? "flex-item active"
|
||||||
: "flex-item"
|
: "flex-item"
|
||||||
|
@ -104,14 +43,5 @@ export default function Nav() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`..//locales/${locale}.json`)).default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { GetStaticProps } from "next";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
messages: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps<Props> = async ({ locale }) => {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: require(`/locales/${locale}.json`),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
13
src/i18n/request.ts
Normal file
13
src/i18n/request.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { getRequestConfig } from "next-intl/server";
|
||||||
|
|
||||||
|
import { routing } from "./routing";
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ locale }) => {
|
||||||
|
// Validate that the incoming `locale` parameter is valid
|
||||||
|
if (!routing.locales.includes(locale as any)) notFound();
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: (await import(`../locales/${locale}.json`)).default,
|
||||||
|
};
|
||||||
|
});
|
10
src/i18n/routing.ts
Normal file
10
src/i18n/routing.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { createSharedPathnamesNavigation } from "next-intl/navigation";
|
||||||
|
import { defineRouting } from "next-intl/routing";
|
||||||
|
|
||||||
|
export const routing = defineRouting({
|
||||||
|
locales: ["en", "de", "es", "fr", "pl", "cz"],
|
||||||
|
defaultLocale: "en",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { Link, redirect, usePathname, useRouter } =
|
||||||
|
createSharedPathnamesNavigation(routing);
|
12
src/middleware.ts
Normal file
12
src/middleware.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import createMiddleware from "next-intl/middleware";
|
||||||
|
|
||||||
|
import { routing } from "./i18n/routing";
|
||||||
|
|
||||||
|
export default createMiddleware(routing, {
|
||||||
|
localeDetection: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
// Match only internationalized pathnames
|
||||||
|
matcher: ["/", "/(de|en|es|fr|pl|cz)/:path*"],
|
||||||
|
};
|
|
@ -1,36 +0,0 @@
|
||||||
import { GetStaticPropsContext } from "next";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
import Container from "@/components/elements/container";
|
|
||||||
import Nav from "@/components/nav";
|
|
||||||
|
|
||||||
export default function NotFound() {
|
|
||||||
const t = useTranslations("404");
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Nav />
|
|
||||||
<Container heading={t("error404")}>
|
|
||||||
<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>',
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`../locales/${locale}.json`)).default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import "@/styles/style.scss";
|
|
||||||
import type { AppProps } from "next/app";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { NextIntlClientProvider } from "next-intl";
|
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NextIntlClientProvider
|
|
||||||
locale={router.locale}
|
|
||||||
timeZone="Europe/Vienna"
|
|
||||||
messages={pageProps.messages}
|
|
||||||
>
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</NextIntlClientProvider>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { Html, Head, Main, NextScript } from 'next/document'
|
|
||||||
|
|
||||||
export default function Document() {
|
|
||||||
return (
|
|
||||||
<Html lang="en">
|
|
||||||
<Head />
|
|
||||||
<body>
|
|
||||||
<Main />
|
|
||||||
<NextScript />
|
|
||||||
</body>
|
|
||||||
</Html>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { GetStaticPropsContext } from 'next'
|
|
||||||
import { useTranslations } from 'next-intl';
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
import Container from "@/components/elements/container";
|
|
||||||
import Nav from "@/components/nav";
|
|
||||||
|
|
||||||
const PrivacyPolicy = () => {
|
|
||||||
const t = useTranslations('Privacy');
|
|
||||||
const [datenschutz, setDatenschutz] = useState('');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('https://philipbrembeck.com/datenschutz.txt')
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(text => setDatenschutz(text))
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Nav />
|
|
||||||
<Container>
|
|
||||||
<p className="small">
|
|
||||||
{t('germanonly')}
|
|
||||||
</p>
|
|
||||||
<div className="privacy" dangerouslySetInnerHTML={{ __html: datenschutz }} />
|
|
||||||
</Container>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PrivacyPolicy;
|
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: GetStaticPropsContext) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
messages: (await import(`../locales/${locale}.json`)).default
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -17,8 +17,19 @@
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "./types/**/*.d.ts"],
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"./types/**/*.d.ts",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue