152 lines
3.4 KiB
TypeScript
152 lines
3.4 KiB
TypeScript
import { TanStackDevtools } from "@tanstack/react-devtools";
|
|
import type { QueryClient } from "@tanstack/react-query";
|
|
import {
|
|
createRootRouteWithContext,
|
|
HeadContent,
|
|
redirect,
|
|
Scripts,
|
|
} from "@tanstack/react-router";
|
|
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
|
|
import type * as React from "react";
|
|
import { useEffect } from "react";
|
|
import { toast } from "sonner";
|
|
import type { AuthSession } from "#/lib/auth.serverfn";
|
|
import { fetchSession, sessionQueryKey } from "#/lib/auth-client";
|
|
import { client } from "#/lib/eden";
|
|
import {
|
|
clearJoinIdFromLocation,
|
|
clearPendingPartyJoin,
|
|
getJoinIdFromLocation,
|
|
readPendingPartyJoin,
|
|
writePendingPartyJoin,
|
|
} from "#/lib/party-join";
|
|
import { Toaster } from "@/components/ui/sonner";
|
|
import TanStackQueryDevtools from "../integrations/tanstack-query/devtools";
|
|
import appCss from "../styles.css?url";
|
|
|
|
interface MyRouterContext {
|
|
queryClient: QueryClient;
|
|
}
|
|
|
|
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|
head: () => ({
|
|
meta: [
|
|
{
|
|
charSet: "utf-8",
|
|
},
|
|
{
|
|
name: "viewport",
|
|
content: "width=device-width, initial-scale=1",
|
|
},
|
|
{
|
|
title: "Music Quiz",
|
|
},
|
|
],
|
|
links: [
|
|
{
|
|
rel: "stylesheet",
|
|
href: appCss,
|
|
},
|
|
],
|
|
}),
|
|
shellComponent: RootDocument,
|
|
beforeLoad: async ({ context, location }) => {
|
|
const authPublicPaths = new Set(["/login"]);
|
|
const isAuthPublicPath = authPublicPaths.has(location.pathname);
|
|
let session: AuthSession | null;
|
|
if (typeof window === "undefined") {
|
|
const { getSession } = await import("../lib/auth.serverfn");
|
|
session = await getSession();
|
|
} else {
|
|
session = await context.queryClient.fetchQuery({
|
|
queryKey: sessionQueryKey,
|
|
queryFn: fetchSession,
|
|
staleTime: 30_000,
|
|
});
|
|
}
|
|
|
|
const user = session?.user;
|
|
|
|
if (!user && !isAuthPublicPath) {
|
|
throw redirect({
|
|
to: "/login",
|
|
search: { redirect: location.href },
|
|
});
|
|
}
|
|
|
|
return { user, session };
|
|
},
|
|
});
|
|
|
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
const { user } = Route.useRouteContext();
|
|
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
const joinId = getJoinIdFromLocation();
|
|
if (!joinId) return;
|
|
const storedId = readPendingPartyJoin();
|
|
if (storedId !== joinId) {
|
|
writePendingPartyJoin(joinId);
|
|
}
|
|
clearJoinIdFromLocation();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!user) return;
|
|
const joinId = readPendingPartyJoin();
|
|
if (!joinId) return;
|
|
|
|
let cancelled = false;
|
|
const attemptJoin = async () => {
|
|
try {
|
|
const result = await client.api.party.join.post({
|
|
targetUserId: joinId,
|
|
});
|
|
|
|
if (result?.error) {
|
|
toast.error(result.error);
|
|
clearPendingPartyJoin();
|
|
} else {
|
|
toast.success("Joined party.");
|
|
clearPendingPartyJoin();
|
|
}
|
|
} catch (_error) {
|
|
if (!cancelled) {
|
|
toast.error("Failed to join party.");
|
|
}
|
|
}
|
|
};
|
|
|
|
attemptJoin();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [user]);
|
|
|
|
return (
|
|
<html lang="en" suppressHydrationWarning>
|
|
<head>
|
|
<HeadContent />
|
|
</head>
|
|
<body className="font-sans antialiased wrap-anywhere dark">
|
|
{children}
|
|
<Toaster />
|
|
<TanStackDevtools
|
|
config={{
|
|
position: "bottom-right",
|
|
}}
|
|
plugins={[
|
|
{
|
|
name: "Tanstack Router",
|
|
render: <TanStackRouterDevtoolsPanel />,
|
|
},
|
|
TanStackQueryDevtools,
|
|
]}
|
|
/>
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|