124 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
};
|