2024-09-05 22:29:12 +02:00
|
|
|
import {
|
|
|
|
json,
|
|
|
|
Links,
|
|
|
|
Meta as MetaComponent,
|
|
|
|
Outlet,
|
|
|
|
Scripts,
|
|
|
|
ScrollRestoration,
|
|
|
|
useLoaderData,
|
2024-09-11 19:13:54 +02:00
|
|
|
useRouteError,
|
|
|
|
useRouteLoaderData,
|
2024-09-05 22:29:12 +02:00
|
|
|
} from "@remix-run/react";
|
2024-09-10 21:24:40 +02:00
|
|
|
import { LoaderFunctionArgs } from "@remix-run/node";
|
2024-09-10 20:33:22 +02:00
|
|
|
import { useChangeLanguage } from "remix-i18next/react";
|
|
|
|
import { useTranslation } from "react-i18next";
|
2024-09-05 22:29:12 +02:00
|
|
|
|
|
|
|
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";
|
2024-09-10 20:33:22 +02:00
|
|
|
import { LANGUAGE } from "~/env.server";
|
2024-09-11 19:13:54 +02:00
|
|
|
import { errorCodeDesc } from "./components/ErrorAlert";
|
2024-09-13 14:56:38 +02:00
|
|
|
import { Container } from "react-bootstrap";
|
2024-09-15 16:48:22 +02:00
|
|
|
import { ReactNode } from "react";
|
|
|
|
import BaseNavbar from "~/components/nav/BaseNavbar";
|
2024-09-05 22:29:12 +02:00
|
|
|
|
2024-09-10 21:24:40 +02:00
|
|
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
2024-09-05 22:29:12 +02:00
|
|
|
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 {
|
2024-09-10 20:33:22 +02:00
|
|
|
meUser = await serverRequest<User>("GET", "/users/@me", { token });
|
2024-09-05 22:29:12 +02:00
|
|
|
|
2024-09-06 15:01:44 +02:00
|
|
|
settings = await serverRequest<UserSettings>("GET", "/users/@me/settings", { token });
|
2024-09-05 22:29:12 +02:00
|
|
|
} 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(
|
2024-09-10 20:33:22 +02:00
|
|
|
{ meta, meUser, settings, locale: LANGUAGE },
|
2024-09-05 22:29:12 +02:00
|
|
|
{
|
|
|
|
headers: { "Set-Cookie": setCookie },
|
|
|
|
},
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-09-15 16:48:22 +02:00
|
|
|
export function Layout({ children }: { children: ReactNode }) {
|
2024-09-25 15:14:48 +02:00
|
|
|
const { locale } = useRouteLoaderData<typeof loader>("root") || {
|
2024-09-11 19:13:54 +02:00
|
|
|
meta: {
|
|
|
|
users: {
|
|
|
|
total: 0,
|
|
|
|
active_month: 0,
|
|
|
|
active_week: 0,
|
|
|
|
active_day: 0,
|
|
|
|
},
|
|
|
|
members: 0,
|
|
|
|
version: "",
|
|
|
|
hash: "",
|
|
|
|
},
|
|
|
|
};
|
2024-09-10 20:33:22 +02:00
|
|
|
const { i18n } = useTranslation();
|
2024-09-11 19:13:54 +02:00
|
|
|
i18n.language = locale || "en";
|
|
|
|
useChangeLanguage(locale || "en");
|
2024-09-05 22:29:12 +02:00
|
|
|
|
|
|
|
return (
|
2024-09-25 16:43:53 +02:00
|
|
|
<html lang={locale || "en"} dir={i18n.dir()}>
|
|
|
|
<head>
|
|
|
|
<meta charSet="utf-8" />
|
|
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
|
<MetaComponent />
|
|
|
|
<Links />
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
{children}
|
|
|
|
<ScrollRestoration />
|
|
|
|
<Scripts />
|
2024-09-05 22:29:12 +02:00
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-09-11 19:13:54 +02:00
|
|
|
export function ErrorBoundary() {
|
2024-09-15 16:48:22 +02:00
|
|
|
const data = useRouteLoaderData<typeof loader>("root");
|
|
|
|
|
2024-09-11 19:13:54 +02:00
|
|
|
// 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>
|
2024-09-15 16:48:22 +02:00
|
|
|
<title>{t("error.title")}</title>
|
2024-09-11 19:13:54 +02:00
|
|
|
<MetaComponent />
|
|
|
|
<Links />
|
|
|
|
</head>
|
|
|
|
<body>
|
2024-09-25 15:14:48 +02:00
|
|
|
{data?.meUser && data?.meta ? (
|
|
|
|
<Navbar meta={data.meta} user={data.meUser} />
|
2024-09-15 16:48:22 +02:00
|
|
|
) : (
|
2024-09-25 15:14:48 +02:00
|
|
|
<BaseNavbar />
|
2024-09-15 16:48:22 +02:00
|
|
|
)}
|
|
|
|
<Container>{errorElem}</Container>
|
2024-09-11 19:13:54 +02:00
|
|
|
<Scripts />
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function ApiErrorElem({ error }: { error: ApiError }) {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const errorDesc = errorCodeDesc(t, error.code);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2024-09-15 16:48:22 +02:00
|
|
|
<h1>{t("error.heading")}</h1>
|
2024-09-11 19:13:54 +02:00
|
|
|
<p>{errorDesc}</p>
|
2024-09-15 16:48:22 +02:00
|
|
|
<details>
|
|
|
|
<summary>{t("error.more-info")}</summary>
|
|
|
|
<pre>
|
|
|
|
<code>{JSON.stringify(error, null, " ")}</code>
|
|
|
|
</pre>
|
|
|
|
</details>
|
2024-09-11 19:13:54 +02:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-09-05 22:29:12 +02:00
|
|
|
export default function App() {
|
2024-09-25 16:09:23 +02:00
|
|
|
const { meta, meUser } = useLoaderData<typeof loader>();
|
2024-09-05 22:29:12 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2024-09-25 16:09:23 +02:00
|
|
|
<Navbar meta={meta} user={meUser} />
|
2024-09-13 14:56:38 +02:00
|
|
|
<Container>
|
|
|
|
<Outlet />
|
|
|
|
</Container>
|
2024-09-05 22:29:12 +02:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|