Files
car-crushers-portal/app/root.tsx

253 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
ChakraProvider,
Container,
cookieStorageManagerSSR,
Heading,
Link,
Text,
} from "@chakra-ui/react";
import { ClientStyleContext, ServerStyleContext } from "./context.js";
import fontStyle from "@fontsource-variable/plus-jakarta-sans/index.css";
import Forbidden from "../components/Forbidden.js";
import globalStyles from "../index.css";
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
useLoaderData,
useRouteError,
useRouteLoaderData,
} from "@remix-run/react";
import { type ErrorResponse } from "@remix-run/router";
import { LinksFunction } from "@remix-run/cloudflare";
import Login from "../components/Login.js";
import Navigation from "../components/Navigation.js";
import { type ReactNode, StrictMode, useContext, useEffect } from "react";
import theme from "../theme.js";
import { withEmotionCache } from "@emotion/react";
import {
captureRemixErrorBoundaryError,
setUser,
withSentry,
} from "@sentry/remix";
export function ErrorBoundary() {
const error = useRouteError() as ErrorResponse;
if (!isRouteErrorResponse(error))
return (
<DocumentWrapper loaderData={{ hide: true }}>
<Container maxW="container.lg" pt="8vh" textAlign="left">
<Heading size="4xl">???</Heading>
<br />
<Text fontSize="xl">Something bad happened!</Text>
<br />
<br />
<br />
<Text>Details: {error}</Text>
<br />
<br />
<Link color="#646cff" onClick={() => location.reload()}>
Refresh
</Link>
</Container>
</DocumentWrapper>
);
const { status } = error;
const loaderData = useRouteLoaderData<typeof loader>("root") || {};
switch (status) {
case 303:
return "";
case 401:
return (
<DocumentWrapper loaderData={loaderData}>
<Login />
</DocumentWrapper>
);
case 403:
return (
<DocumentWrapper loaderData={loaderData}>
<Forbidden />
</DocumentWrapper>
);
case 404:
return (
<DocumentWrapper loaderData={{ ...loaderData, hide: true }}>
<Container maxW="container.lg" pt="8vh" textAlign="left">
<Heading size="4xl">404</Heading>
<br />
<Text fontSize="xl">There is nothing to find here.</Text>
<br />
<br />
<br />
<Link color="#646cff" onClick={() => history.go(-1)}>
Go back
</Link>
</Container>
</DocumentWrapper>
);
default:
captureRemixErrorBoundaryError(useRouteError());
return (
<DocumentWrapper loaderData={loaderData}>
<Container maxW="container.lg" pt="8vh" textAlign="left">
<Heading size="4xl">500</Heading>
<br />
<Text fontSize="xl">S̶̡͈̠̗̠͖͙̭o̶̶͕͚̥͍̪̤m̸̨͏͈͔̖͚̖̰̱͞e҉̵͖͚͇̀t̕͟͠͏͎̺̯̲̱̣̤̠̟͙̠̙̫̬ḩ̸̭͓̬͎̙̀į̞̮͉͖̰̥̹͚̫̙̪̗̜̳̕ͅn҉͔̯̪̗̝̝͖̲͇͍͎̲̲̤̖̫͈̪͡g̴̰̻̙̝͉̭͇̖̰̝̙͕̼͙͘͜ ̵̶̫̥̳̲̘̻̗͈͕̭̲͇̘̜̺̟̥̖̥b̴̙̭̹͕̞͠r̞͎̠̩͈̖̰̞̯̯͢͢͠ͅo̝̯̗̹̳͍̰͉͕̘̰̠̺̥̰͔̕ͅk̵̸̻̠͕̺̦̦͖̲̺̦̞̝̞͞͡e̶͏̤̼̼͔̘̰̰̭͈̀͞͡</Text>
<br />
<br />
<br />
<Link color="#646cff" onClick={() => location.reload()}>
Reload
</Link>
</Container>
</DocumentWrapper>
);
}
}
export const links: LinksFunction = () => {
return [
{ href: "/favicon.ico", rel: "icon" },
{ href: "/files/logo192.png", rel: "apple-touch-icon", type: "image/png" },
{ href: fontStyle, rel: "stylesheet" },
{ href: globalStyles, rel: "stylesheet" },
];
};
export async function loader({
context,
}: {
context: RequestContext;
}): Promise<{ [k: string]: any }> {
let data: { [k: string]: string } = {};
if (context.data.current_user) data = { ...context.data.current_user };
if (context.env.REMIX_DSN) data.dsn = context.env.REMIX_DSN;
if (context.data.nonce) data.nonce = context.data.nonce;
if (context.data.theme) data.theme = context.data.theme;
return data;
}
export function meta() {
return [{ title: "Car Crushers" }];
}
function DocumentWrapper(props: {
loaderData: { [k: string]: any };
children: ReactNode;
}) {
const { children: child, loaderData } = props;
const Document = withEmotionCache(
({ children }: { children: ReactNode }, emotionCache) => {
const serverStyleData = useContext(ServerStyleContext);
const clientStyleData = useContext(ClientStyleContext);
useEffect(() => {
emotionCache.sheet.container = document.head;
const tags = emotionCache.sheet.tags;
emotionCache.sheet.flush();
tags.forEach((tag) => {
(emotionCache.sheet as any)._insertTag(tag);
});
clientStyleData?.reset();
}, []);
const body = (
<StrictMode>
<ChakraProvider
colorModeManager={cookieStorageManagerSSR(
typeof document === "undefined"
? `chakra-ui-color-mode=${loaderData.theme}`
: document.cookie,
)}
theme={theme}
>
<div className="App">
<Navigation {...loaderData} />
{children}
<Scripts />
</div>
</ChakraProvider>
</StrictMode>
);
return (
<html data-theme={loaderData.theme} lang="en-US">
<head>
<Links />
<style>
{`
:root {
color-scheme: ${loaderData.theme};
}
`}
</style>
{serverStyleData?.map(({ key, ids, css }) => (
<style
key={key}
data-emotion={`${key} ${ids.join(" ")}`}
dangerouslySetInnerHTML={{ __html: css }}
/>
))}
<meta charSet="UTF-8" />
{loaderData.dsn ? (
<meta name="dsn" content={loaderData.dsn} />
) : null}
<meta name="theme-color" content="#00a8f8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<Meta />
</head>
<body
{...(loaderData.theme && {
className: `chakra-ui-${loaderData.theme}`,
})}
>
{body}
</body>
</html>
);
},
);
return <Document>{child}</Document>;
}
function App() {
const loaderData = useLoaderData<typeof loader>();
if (
loaderData.id &&
[0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].find(
(p) => loaderData.permissions & (1 << p),
)
)
setUser({
email: loaderData.email,
id: loaderData.id,
username: loaderData.username,
});
return (
<DocumentWrapper loaderData={loaderData}>
<Outlet />
</DocumentWrapper>
);
}
export default withSentry(App);