121 lines
3.1 KiB
TypeScript
121 lines
3.1 KiB
TypeScript
import { WebAuthProvider } from "@code/auth/web";
|
|
import { Toaster } from "@code/ui/components/sonner";
|
|
|
|
import "./index.css";
|
|
import { LoaderCircle } from "lucide-react";
|
|
import {
|
|
isRouteErrorResponse,
|
|
Links,
|
|
Meta,
|
|
Outlet,
|
|
Scripts,
|
|
ScrollRestoration,
|
|
useNavigation,
|
|
} from "react-router";
|
|
|
|
import type { Route } from "./+types/root";
|
|
import { ThemeProvider } from "./components/theme-provider";
|
|
import { loadAuthToken } from "./lib/auth.server";
|
|
|
|
export const loader = ({ request }: Route.LoaderArgs) => loadAuthToken(request);
|
|
|
|
export const links: Route.LinksFunction = () => [
|
|
{ href: "https://fonts.googleapis.com", rel: "preconnect" },
|
|
{
|
|
crossOrigin: "anonymous",
|
|
href: "https://fonts.gstatic.com",
|
|
rel: "preconnect",
|
|
},
|
|
{
|
|
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
|
|
rel: "stylesheet",
|
|
},
|
|
];
|
|
|
|
export const Layout = ({ children }: { children: React.ReactNode }) => (
|
|
<html lang="en">
|
|
<head>
|
|
<meta charSet="utf-8" />
|
|
<meta
|
|
name="viewport"
|
|
content="width=device-width, initial-scale=1, interactive-widget=resizes-content"
|
|
/>
|
|
<Meta />
|
|
<Links />
|
|
</head>
|
|
<body className="bg-[#0e0e0d]">
|
|
{children}
|
|
<ScrollRestoration />
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
);
|
|
|
|
const RouteProgress = () => {
|
|
const navigation = useNavigation();
|
|
if (!navigation.location) {
|
|
return null;
|
|
}
|
|
return (
|
|
<output
|
|
aria-label="Loading page"
|
|
className="fixed inset-x-0 top-0 z-50 h-0.5 overflow-hidden bg-[#d7d3c7]"
|
|
>
|
|
<div className="route-progress-bar h-full w-1/3 bg-[#7f9130]" />
|
|
</output>
|
|
);
|
|
};
|
|
|
|
export const HydrateFallback = () => (
|
|
<main className="grid min-h-svh place-items-center bg-[#f2f0e7] text-[#69675f]">
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<LoaderCircle className="size-4 animate-spin" /> Loading Zopu…
|
|
</div>
|
|
</main>
|
|
);
|
|
|
|
const App = ({ loaderData }: Route.ComponentProps) => (
|
|
<WebAuthProvider initialToken={loaderData.token}>
|
|
<ThemeProvider
|
|
attribute="class"
|
|
defaultTheme="dark"
|
|
forcedTheme="dark"
|
|
disableTransitionOnChange
|
|
storageKey="vite-ui-theme"
|
|
>
|
|
<RouteProgress />
|
|
<Outlet />
|
|
<Toaster richColors />
|
|
</ThemeProvider>
|
|
</WebAuthProvider>
|
|
);
|
|
|
|
export default App;
|
|
|
|
export const ErrorBoundary = ({ error }: Route.ErrorBoundaryProps) => {
|
|
let message = "Oops!";
|
|
let details = "An unexpected error occurred.";
|
|
let stack: string | undefined;
|
|
if (isRouteErrorResponse(error)) {
|
|
const { status, statusText } = error;
|
|
message = status === 404 ? "404" : "Error";
|
|
details =
|
|
status === 404
|
|
? "The requested page could not be found."
|
|
: statusText || details;
|
|
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
|
({ message: details, stack } = error);
|
|
}
|
|
return (
|
|
<main className="pt-16 p-4 container mx-auto">
|
|
<h1>{message}</h1>
|
|
<p>{details}</p>
|
|
{stack && (
|
|
<pre className="w-full p-4 overflow-x-auto">
|
|
<code>{stack}</code>
|
|
</pre>
|
|
)}
|
|
</main>
|
|
);
|
|
};
|