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} */
|
||||
let nextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
productionBrowserSourceMaps: true,
|
||||
i18n: {
|
||||
locales: ["de", "en", "fr", "es", "pl", "cz"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/datenschutz",
|
||||
destination: "/privacy-policy",
|
||||
},
|
||||
{
|
||||
source: "/impressum",
|
||||
destination: "/en/impressum",
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
@ -23,6 +26,8 @@ const millionConfig = {
|
|||
auto: { rsc: true },
|
||||
};
|
||||
|
||||
// Apply Million's optimization
|
||||
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-intl": "^3.20.0",
|
||||
"nookies": "^2.5.2",
|
||||
"prom-client": "^15.1.3",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"sass": "^1.79.4",
|
||||
|
@ -2869,6 +2870,15 @@
|
|||
"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": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
@ -3987,6 +3997,12 @@
|
|||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
|
@ -7715,6 +7731,19 @@
|
|||
"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": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
|
@ -8651,6 +8680,15 @@
|
|||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"next": "14.2.15",
|
||||
"next-intl": "^3.20.0",
|
||||
"nookies": "^2.5.2",
|
||||
"prom-client": "^15.1.3",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"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 React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import Container from "@/components/elements/container";
|
||||
import Nav from "@/components/nav";
|
||||
|
||||
const Impressum = () => {
|
||||
export default function Impressum() {
|
||||
const t = useTranslations();
|
||||
const [impressum, setImpressum] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
fetch("https://philipbrembeck.com/impressum.txt", { method: "GET" })
|
||||
fetch("https://philipbrembeck.com/impressum.txt")
|
||||
.then((response) => response.text())
|
||||
.then((text) => setImpressum(text))
|
||||
.catch((error) => console.error(error));
|
||||
|
@ -25,14 +26,4 @@ const Impressum = () => {
|
|||
</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 IngredientsCheck from "@/components/ingredientscheck";
|
||||
import Nav from "@/components/nav";
|
||||
|
||||
export default function ingredients() {
|
||||
export default function IngredientsPage() {
|
||||
return (
|
||||
<>
|
||||
<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 Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { setCookie } from "nookies";
|
||||
|
||||
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 ModalWrapper from "@/components/elements/modalwrapper";
|
||||
import Nav from "@/components/nav";
|
||||
import { Link } from "@/i18n/routing";
|
||||
|
||||
export default function More() {
|
||||
const router = useRouter();
|
||||
const t = useTranslations("More");
|
||||
const currentLocale = useLocale();
|
||||
|
||||
function handleLanguageChange(locale: string) {
|
||||
setCookie(null, "NEXT_LOCALE", locale, {
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
path: "/",
|
||||
});
|
||||
router.push("/more", undefined, { locale });
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -99,10 +98,7 @@ export default function More() {
|
|||
<span className="unknown icon-right-open"></span>
|
||||
</div>
|
||||
</Link>
|
||||
<a
|
||||
href="https://frontendnet.work/veganify-api"
|
||||
className="Grid links"
|
||||
>
|
||||
<a href="https://frontendnet.work/veganify-api" className="Grid links">
|
||||
<div className="Grid-cell description">{t("apidocumentation")}</div>
|
||||
<div className="Grid-cell icons">
|
||||
<span className="unknown icon-right-open"></span>
|
||||
|
@ -131,114 +127,39 @@ export default function More() {
|
|||
/>
|
||||
<h1>{t("language")}</h1>
|
||||
</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
|
||||
key={code}
|
||||
className="nolink"
|
||||
href="/more"
|
||||
locale="en"
|
||||
onClick={() => handleLanguageChange("en")}
|
||||
href={`/more`}
|
||||
locale={
|
||||
code as "en" | "de" | "es" | "fr" | "pl" | "cz" | undefined
|
||||
}
|
||||
onClick={() => handleLanguageChange(code)}
|
||||
>
|
||||
<div
|
||||
className={router.locale === "en" ? "option active" : "option"}
|
||||
className={
|
||||
currentLocale === code ? "option active" : "option"
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name="flexRadioDefault"
|
||||
checked={router.locale === "en"}
|
||||
checked={currentLocale === code}
|
||||
readOnly
|
||||
/>
|
||||
<span className="price">{t("english")}</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>
|
||||
<span className="price">{t(name)}</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<span className="info" id="cookieinfo">
|
||||
{t("thissetsacookie")}
|
||||
</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 InstallPrompt from "@/components/elements/pwainstall";
|
||||
|
@ -6,6 +6,11 @@ import Shortcut from "@/components/elements/shortcutinstall";
|
|||
import Footer from "@/components/footer";
|
||||
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() {
|
||||
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 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 = () => (
|
||||
<span onClick={() => Router.back()} style={{cursor: "pointer"}} className="icon-left-open back"/>
|
||||
);
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const BackButton = () => {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<span
|
||||
onClick={() => router.back()}
|
||||
style={{ cursor: "pointer" }}
|
||||
className="icon-left-open back"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackButton;
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import Veganify from "@frontendnetwork/veganify";
|
||||
import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
@ -60,7 +62,6 @@ const ProductSearch = () => {
|
|||
);
|
||||
setLoading(false);
|
||||
if (data.status === 200) {
|
||||
|
||||
if ("product" in data) {
|
||||
setResult(data.product);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Image from "next/image";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import Veganify from "@frontendnetwork/veganify";
|
||||
import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
import React, { useState, FormEvent } from "react";
|
||||
import React, { useState, useCallback } from "react";
|
||||
|
||||
import ModalWrapper from "@/components/elements/modalwrapper";
|
||||
|
||||
|
@ -14,37 +16,41 @@ const IngredientsCheck = () => {
|
|||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||
const handleSubmit = useCallback(
|
||||
async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setVegan(null);
|
||||
setSurelyVegan([]);
|
||||
setNotVegan([]);
|
||||
setMaybeVegan([]);
|
||||
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);
|
||||
try {
|
||||
const data = await Veganify.checkIngredientsList(
|
||||
ingredients.value,
|
||||
process.env.NEXT_PUBLIC_STAGING === "true" ? true : false
|
||||
ingredients,
|
||||
process.env.NEXT_PUBLIC_STAGING === "true"
|
||||
);
|
||||
setVegan(data.data.vegan);
|
||||
setSurelyVegan(data.data.surely_vegan);
|
||||
setNotVegan(data.data.not_vegan);
|
||||
setMaybeVegan(data.data.maybe_vegan);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkIngredients();
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -68,9 +74,9 @@ const IngredientsCheck = () => {
|
|||
placeholder={t("entercommaseperated")}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
name="checkingredients"
|
||||
aria-label={t("submit")}
|
||||
role="button"
|
||||
>
|
||||
<span className="icon-right-open"></span>
|
||||
</button>
|
||||
|
|
|
@ -1,76 +1,17 @@
|
|||
import { GetStaticPropsContext } from "next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { Link, usePathname } from "@/i18n/routing";
|
||||
|
||||
export default function Nav() {
|
||||
const t = useTranslations("Nav");
|
||||
const router = useRouter();
|
||||
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");
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
const pathname = usePathname();
|
||||
|
||||
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">
|
||||
<div className="flex-container">
|
||||
<div
|
||||
className={
|
||||
router.pathname == "/" ? "flex-item active" : "flex-item"
|
||||
}
|
||||
>
|
||||
<div className={pathname === "/" ? "flex-item active" : "flex-item"}>
|
||||
<Link href="/">
|
||||
<span className="icon icon-vegancheck"></span>
|
||||
<span className="menu-item">{t("home")}</span>
|
||||
|
@ -78,9 +19,7 @@ export default function Nav() {
|
|||
</div>
|
||||
<div
|
||||
className={
|
||||
router.pathname == "/ingredients"
|
||||
? "flex-item active"
|
||||
: "flex-item"
|
||||
pathname === "/ingredients" ? "flex-item active" : "flex-item"
|
||||
}
|
||||
>
|
||||
<Link href="/ingredients">
|
||||
|
@ -91,7 +30,7 @@ export default function Nav() {
|
|||
<div
|
||||
className={
|
||||
["/more", "/tos", "/privacy-policy", "/impressum"].includes(
|
||||
router.pathname
|
||||
pathname
|
||||
)
|
||||
? "flex-item active"
|
||||
: "flex-item"
|
||||
|
@ -104,14 +43,5 @@ export default function Nav() {
|
|||
</div>
|
||||
</div>
|
||||
</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": ".",
|
||||
"paths": {
|
||||
"@/*": ["./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"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue