Files
zopu-code/apps/web/src/root.tsx
T

124 lines
3.2 KiB
TypeScript

import { useConvexAccessToken, WebAuthProvider } from "@code/auth/web";
import { env } from "@code/env/web";
import { Toaster } from "@code/ui/components/sonner";
import { FlueProvider } from "@flue/react";
import "./index.css";
import { createFlueClient } from "@flue/sdk";
import { useMemo } from "react";
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import type { Route } from "./+types/root";
import { ThemeProvider } from "./components/theme-provider";
import { createFlueFetch } from "./lib/flue-transport";
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",
},
];
const flueBaseUrl = new URL(env.VITE_FLUE_URL);
const flueFetch = createFlueFetch({ baseUrl: flueBaseUrl });
const AuthenticatedFlueProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const resolveAccessToken = useConvexAccessToken();
const client = useMemo(
() =>
createFlueClient({
baseUrl: flueBaseUrl.toString(),
fetch: flueFetch,
headers: async () => {
const accessToken = await resolveAccessToken();
const headers: Record<string, string> = {};
if (accessToken) {
headers.authorization = `Bearer ${accessToken}`;
}
return headers;
},
}),
[resolveAccessToken]
);
return <FlueProvider client={client}>{children}</FlueProvider>;
};
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" />
<Meta />
<Links />
</head>
<body className="bg-[#11110f]">
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
const App = () => (
<WebAuthProvider>
<AuthenticatedFlueProvider>
<ThemeProvider
attribute="class"
defaultTheme="light"
forcedTheme="light"
disableTransitionOnChange
storageKey="vite-ui-theme"
>
<Outlet />
<Toaster richColors />
</ThemeProvider>
</AuthenticatedFlueProvider>
</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>
);
};