src/lib/auth/client.tsx
Linked repository file from this build.
"use client";
import { AccountSetup } from "@/components/account-setup";
import type { ServerSession } from "./server";
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
import { useUser } from "@clerk/nextjs";
import type { Actor, DemoRole } from "@/features/reviews/types";
import { demoActor } from "./roster";
interface SessionValue {
actor: Actor;
isSignedIn: boolean;
ready: boolean;
error: string;
setRole(role: DemoRole): void;
}
const SessionContext = createContext<SessionValue | null>(null);
export function SessionProvider({
children,
initialSession,
}: {
children: ReactNode;
initialSession: ServerSession | null;
}) {
const { user, isLoaded, isSignedIn } = useUser();
const [actor, setActor] = useState(initialSession?.actor ?? demoActor("examiner"));
const [loadedIdentity, setLoadedIdentity] = useState<string | null>(
initialSession ? (initialSession.userId ?? "test-session") : null,
);
const identity = (isLoaded ? user?.id : initialSession?.userId) ?? "test-session";
const signedIn = isLoaded ? Boolean(isSignedIn) : Boolean(initialSession?.userId);
const ready = (isLoaded && !isSignedIn) || loadedIdentity === identity;
const [error, setError] = useState("");
const [setupFields, setSetupFields] = useState<string[] | null>(null);
useEffect(() => {
if (!isLoaded || !isSignedIn || loadedIdentity === identity) return;
let canceled = false;
void fetch("/api/session", { cache: "no-store" })
.then(async (response) => {
const data = await response.json();
if (!response.ok) {
if (!canceled && Array.isArray(data.setupFields)) setSetupFields(data.setupFields);
throw new Error(data.error);
}
if (!canceled) {
setSetupFields(null);
setActor(data.actor);
setLoadedIdentity(identity);
setError("");
}
})
.catch((cause) => {
if (!canceled) setError(cause.message);
});
return () => {
canceled = true;
};
}, [isLoaded, isSignedIn, identity, loadedIdentity]);
function setRole(role: DemoRole) {
if (signedIn) return;
const next = demoActor(role);
// Only the explicitly configured browser-test session accepts this cookie on the server.
document.cookie = `qc-test-staff=${next.staffId}; path=/; SameSite=Lax`;
setActor(next);
}
return (
<SessionContext.Provider
value={{
actor,
isSignedIn: signedIn,
ready,
error,
setRole,
}}
>
{signedIn && setupFields ? <AccountSetup fields={setupFields} /> : children}
</SessionContext.Provider>
);
}
export function useSession() {
const session = useContext(SessionContext);
if (!session) throw new Error("SessionProvider is required.");
return session;
}