import "../styles/globals.css"; import { useEffect, useState } from "react"; import { SessionProvider, useSession } from "next-auth/react"; import { ThemeOptions, ThemeProvider, createTheme } from "@mui/material/styles"; import { Alert, Snackbar, CssBaseline, GlobalStyles, Backdrop, CircularProgress, useMediaQuery, AlertColor, } from "@mui/material"; import { useRouter } from "next/router"; import { AppProps } from "next/app"; import { NotifyContext, UserContext, TranslateContext, } from "../Modules/context"; import getLocales from "../locales/getLocales"; import Head from "next/head"; interface Notification { message?: string; severity?: AlertColor; } const userData: { [key: number]: string | 0 } = {}; // The 0 means laoding function MyApp({ Component, pageProps }: AppProps) { const { data: session } = useSession(); const [translationData, setTranslationData] = useState< Record >(pageProps?.t?.translate ? pageProps.t.translate : {}); const [themeData, setThemeData] = useState<{ dark: ThemeOptions; light: ThemeOptions; }>(pageProps?.t?.theme ? pageProps.t.theme : undefined); const [notifications, setNotifications] = useState({}); // Used to send notification function notify(message: string, severity: AlertColor = "info") { setNotifications({ message: message, severity: severity, }); } // Gets the username asyncronously async function getUser(id: number, reset = false): Promise { if (userData[id] === undefined || reset) { userData[id] = 0; // Loading state to prevent lots of requests at the same time const data = await fetch(`/api/user/${id}`).then((e) => e.json().catch(() => ""), ); userData[id] = data; } let i = 0; while (userData[id] === 0 && i++ < 100) { console.log(userData); await new Promise((resolve) => setTimeout(resolve, 100)); } if (userData[id] === 0) { userData[id] = ""; } return userData[id]; } // Gets the username syncronously or just returns an empty string function getUserNow(id: number) { if (userData[id]) { return userData[id]; } return ""; } const router = useRouter(); // Grabs the theme data if required useEffect(() => { if (themeData === undefined) { fetch("/api/theme") .then((e) => e.json()) .then((data) => { setThemeData(data); }); } }, [themeData, setThemeData]); // Grabs the translations if required useEffect(() => { // If the user is logged in the locale is saved to local storage if (session && session.user.locale) { localStorage.locale = session.user.locale; } const findLocale = async () => { // Requests the locale if it is not set by testing on the main page if (!localStorage.locale) { const data = (await fetch("/")).url.split("/").pop(); if (router.locales?.includes(String(data))) { localStorage.locale = data; } else { localStorage.locale = "en"; } } // If the locale that is wanted is different then the one requested a redirect happens. if ( router.locale !== localStorage.locale && router.locales?.includes(localStorage.locale) ) { router.push( { pathname: router.pathname, query: router.query, }, undefined, { locale: localStorage.locale, }, ); return; } const downloadTranslations = async (locale: string) => { const data = await getLocales(locale); setTranslationData(data ? data : {}); }; // Downloads the text data if it is needed if (router.locale === "en") { if (Object.values(translationData).length !== 0) { setTranslationData({}); } } else if (router.locale !== translationData.locale) { downloadTranslations(router.locale as string); } // If the session data shows a different locale then the one set then the locale is changed in the session if (session && session.user.locale !== router.locale) { await fetch("/api/user", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ locale: router.locale, }), }); } }; findLocale(); }, [router, translationData, session]); const t = ( text: string, replacers?: Record, ): string => { let data = text; if (translationData[text] && translationData[text] !== "") { data = translationData[text]; } if (replacers) { replacers.locale = translationData.locale ? translationData.locale : "en"; Object.keys(replacers).forEach((e) => { // Error logging when the translation replacment fails. if (replacers[e] === null || replacers[e] === undefined) { console.error( "Missing locale data for " + e + ". With data: " + data, ); return; } if (typeof replacers[e] === "object") { data = data.replaceAll( `{${e}}`, (replacers[e] as Date).toLocaleDateString(router.locale, { year: "numeric", month: "short", day: "numeric", }), ); } else if (typeof replacers[e] === "number") { data = data.replaceAll( `{${e}}`, replacers[e].toLocaleString(router.locale), ); } else { data = data.replaceAll(`{${e}}`, replacers[e] as string); } }); } return data; }; const [loading, setLoading] = useState(false); // Checks if the page is loading useEffect(() => { const handleStart = (url: string, other: { shallow: boolean }) => url !== router.asPath && !other.shallow && setLoading(true); const handleComplete = () => setLoading(false); router.events.on("routeChangeStart", handleStart); router.events.on("routeChangeComplete", handleComplete); router.events.on("routeChangeError", handleComplete); setInterval(() => handleComplete, 1000); return () => { router.events.off("routeChangeStart", handleStart); router.events.off("routeChangeComplete", handleComplete); router.events.off("routeChangeError", handleComplete); }; }); // Clears the notification on close function handleClose() { setNotifications({}); } // Used to create the theme for the website and starts of in dark to not blind dark theme users const [colorMode, setColorMode] = useState<"light" | "dark" | string>("dark"); function updateColorMode(theme: "light" | "dark" | string, force = false) { if (force || colorMode !== theme) { // Checks if the theme is already saved like this on the server if (session && session.user.theme !== theme) { fetch("/api/user", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ theme }), }); } localStorage.theme = theme; setColorMode(theme); } } // Defaults to dark if this is not supported by the browser const prefersDark = !useMediaQuery("(prefers-color-scheme: light)"); // Sets the color scheme based on preferences) useEffect(() => { const session_exists = !!session; // Checks if a session to read data from exists // Grabs the online preferences if they exist if (session && session.user.theme !== "" && session.user.theme) { updateColorMode(session.user.theme); } else if (localStorage.theme !== "" && localStorage.theme) { // Uses localstorage if available updateColorMode(localStorage.theme, session_exists); } else { // Uses the browser preferences const theme = prefersDark ? "dark" : "light"; updateColorMode(theme, session_exists); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [prefersDark, session]); let completeThemeData: ThemeOptions = {}; if (colorMode === "dark" || colorMode === "light") { completeThemeData = themeData ? themeData[colorMode] : { palette: { mode: colorMode, }, }; } else { try { completeThemeData = JSON.parse(colorMode); } catch { completeThemeData = { palette: { mode: "dark", }, }; } } const theme = createTheme(completeThemeData); // Used to get the host const host = typeof window !== "undefined" ? window.location.origin : process.env.NEXTAUTH_URL; return ( <> {/* Adds the hreflang to the head for SEO purposes */ router.locales?.map((e) => { return e == "en" ? ( ) : ( ); })} {notifications.message && ( {notifications.message} )} {/* Adds loading screen whenever a new page is being opened */} ); } function App(Data: AppProps) { return ( ); } export default App;