sam
0f3ab19f6f
This means it's not possible to manually change the theme, but all major operating systems support global dark mode now, so it shouldn't be a huge problem. Will re-add the dark mode toggle if the Sec-CH-Prefers-Color-Scheme header gets added to Firefox and Safari.
162 lines
3.7 KiB
TypeScript
162 lines
3.7 KiB
TypeScript
import {
|
|
json,
|
|
Links,
|
|
Meta as MetaComponent,
|
|
Outlet,
|
|
Scripts,
|
|
ScrollRestoration,
|
|
useLoaderData,
|
|
useRouteError,
|
|
useRouteLoaderData,
|
|
} from "@remix-run/react";
|
|
import { LoaderFunctionArgs } from "@remix-run/node";
|
|
import { useChangeLanguage } from "remix-i18next/react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import serverRequest, { getCookie, writeCookie } from "./lib/request.server";
|
|
import Meta from "./lib/api/meta";
|
|
import Navbar from "./components/nav/Navbar";
|
|
import { User, UserSettings } from "./lib/api/user";
|
|
import { ApiError, ErrorCode } from "./lib/api/error";
|
|
|
|
import "./app.scss";
|
|
import getLocalSettings from "./lib/settings.server";
|
|
import { LANGUAGE } from "~/env.server";
|
|
import { errorCodeDesc } from "./components/ErrorAlert";
|
|
import { Container } from "react-bootstrap";
|
|
import { ReactNode } from "react";
|
|
import BaseNavbar from "~/components/nav/BaseNavbar";
|
|
|
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
const meta = await serverRequest<Meta>("GET", "/meta");
|
|
|
|
const token = getCookie(request, "pronounscc-token");
|
|
let setCookie = "";
|
|
|
|
let meUser: User | undefined;
|
|
let settings = getLocalSettings(request);
|
|
if (token) {
|
|
try {
|
|
meUser = await serverRequest<User>("GET", "/users/@me", { token });
|
|
|
|
settings = await serverRequest<UserSettings>("GET", "/users/@me/settings", { token });
|
|
} catch (e) {
|
|
// If we get an unauthorized error, clear the token, as it's not valid anymore.
|
|
if ((e as ApiError).code === ErrorCode.AuthenticationRequired) {
|
|
setCookie = writeCookie("pronounscc-token", token, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
return json(
|
|
{ meta, meUser, settings, locale: LANGUAGE },
|
|
{
|
|
headers: { "Set-Cookie": setCookie },
|
|
},
|
|
);
|
|
};
|
|
|
|
export function Layout({ children }: { children: ReactNode }) {
|
|
const { locale } = useRouteLoaderData<typeof loader>("root") || {
|
|
meta: {
|
|
users: {
|
|
total: 0,
|
|
active_month: 0,
|
|
active_week: 0,
|
|
active_day: 0,
|
|
},
|
|
members: 0,
|
|
version: "",
|
|
hash: "",
|
|
},
|
|
};
|
|
const { i18n } = useTranslation();
|
|
i18n.language = locale || "en";
|
|
useChangeLanguage(locale || "en");
|
|
|
|
return (
|
|
<html
|
|
lang={locale || "en"}
|
|
dir={i18n.dir()}
|
|
>
|
|
<head>
|
|
<meta charSet="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<MetaComponent />
|
|
<Links />
|
|
</head>
|
|
<body>
|
|
{children}
|
|
<ScrollRestoration />
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
|
|
export function ErrorBoundary() {
|
|
const data = useRouteLoaderData<typeof loader>("root");
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const error: any = useRouteError();
|
|
const { t } = useTranslation();
|
|
|
|
console.log(error);
|
|
|
|
const errorElem =
|
|
"code" in error && "message" in error ? (
|
|
<ApiErrorElem error={error as ApiError} />
|
|
) : (
|
|
<>{t("error.errors.generic-error")}</>
|
|
);
|
|
|
|
return (
|
|
<html lang="en">
|
|
<head>
|
|
<title>{t("error.title")}</title>
|
|
<MetaComponent />
|
|
<Links />
|
|
</head>
|
|
<body>
|
|
{data?.meUser && data?.meta ? (
|
|
<Navbar meta={data.meta} user={data.meUser} />
|
|
) : (
|
|
<BaseNavbar />
|
|
)}
|
|
<Container>{errorElem}</Container>
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
|
|
function ApiErrorElem({ error }: { error: ApiError }) {
|
|
const { t } = useTranslation();
|
|
const errorDesc = errorCodeDesc(t, error.code);
|
|
|
|
return (
|
|
<>
|
|
<h1>{t("error.heading")}</h1>
|
|
<p>{errorDesc}</p>
|
|
<details>
|
|
<summary>{t("error.more-info")}</summary>
|
|
<pre>
|
|
<code>{JSON.stringify(error, null, " ")}</code>
|
|
</pre>
|
|
</details>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
const { meta, meUser, settings } = useLoaderData<typeof loader>();
|
|
|
|
return (
|
|
<>
|
|
<Navbar meta={meta} user={meUser} settings={settings} />
|
|
<Container>
|
|
<Outlet />
|
|
</Container>
|
|
</>
|
|
);
|
|
}
|