feat: Init Change to UseAppRouter

This commit is contained in:
Philip 2024-10-14 22:09:25 +02:00
parent 39416c973a
commit 82ad7b2818
26 changed files with 333 additions and 433 deletions

View file

@ -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
View file

@ -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",

View file

@ -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
View file

@ -0,0 +1 @@
cache

View file

@ -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,
},
};
}

View file

@ -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,
},
};
}

View 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>
);
}

View file

@ -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,
},
};
}

View file

@ -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,
},
};
}

View 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>
</>
);
}

View file

@ -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,
},
};
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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";

View file

@ -1,3 +1,5 @@
"use client";
/* eslint-disable @typescript-eslint/no-explicit-any */
import Image from "next/image";
import { useTranslations } from "next-intl";

View file

@ -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>

View file

@ -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,
},
};
}

View file

@ -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
View 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
View 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
View 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*"],
};

View file

@ -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,
},
};
}

View file

@ -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>
</>
);
}

View file

@ -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>
)
}

View file

@ -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
}
};
}

View file

@ -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"]
}