phase 2: register, login, logout, verify, session, forgat password, delete and cofirm, refresh session all simplified
All checks were successful
Deploy / deploy (push) Successful in 1m31s
Security Tests / security-non-db (push) Successful in 20s
Security Tests / security-db (push) Successful in 25s

This commit is contained in:
2026-03-16 14:19:13 -05:00
parent 60cdcf1fcf
commit a430dfadcf
8 changed files with 879 additions and 641 deletions

View File

@@ -1,4 +1,14 @@
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
import {
createElement,
createContext,
useContext,
type ReactNode,
} from "react";
import {
useQuery,
type UseQueryOptions,
type UseQueryResult,
} from "@tanstack/react-query";
import { http } from "../api/http";
type SessionResponse = {
@@ -16,12 +26,31 @@ type SessionResponse = {
type Options = Omit<UseQueryOptions<SessionResponse, Error>, "queryKey" | "queryFn">;
export function useAuthSession(options?: Options) {
function useAuthSessionQuery(options?: Options) {
return useQuery<SessionResponse, Error>({
queryKey: ["auth", "session"],
queryFn: async () =>
http<SessionResponse>("/auth/session", { skipAuthRedirect: true }),
// Keep session warm across route transitions to avoid duplicate auth calls.
staleTime: 60_000,
refetchOnMount: false,
refetchOnReconnect: false,
retry: false,
...options,
});
}
const AuthSessionContext = createContext<UseQueryResult<SessionResponse, Error> | null>(null);
export function AuthSessionProvider({ children }: { children: ReactNode }) {
const session = useAuthSessionQuery();
return createElement(AuthSessionContext.Provider, { value: session }, children);
}
export function useAuthSession(options?: Options) {
const contextSession = useContext(AuthSessionContext);
if (contextSession) {
return contextSession;
}
return useAuthSessionQuery(options);
}

View File

@@ -11,6 +11,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ToastProvider } from "./components/Toast";
import { RequireAuth } from "./components/RequireAuth";
import { BetaGate } from "./components/BetaGate";
import { AuthSessionProvider } from "./hooks/useAuthSession";
import App from "./App";
import "./styles.css";
@@ -196,9 +197,11 @@ const router = createBrowserRouter(
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={client}>
<ToastProvider>
<RouterProvider router={router} />
</ToastProvider>
<AuthSessionProvider>
<ToastProvider>
<RouterProvider router={router} />
</ToastProvider>
</AuthSessionProvider>
</QueryClientProvider>
</React.StrictMode>
);

View File

@@ -57,7 +57,8 @@ export default function LoginPage() {
body: { email, password },
skipAuthRedirect: true,
});
qc.clear();
await qc.invalidateQueries({ queryKey: ["auth", "session"] });
await session.refetch();
navigate(next || "/", { replace: true });
} catch (err) {
const status = (err as { status?: number; code?: string })?.status;

View File

@@ -78,12 +78,13 @@ export default function RegisterPage() {
skipAuthRedirect: true,
}
);
qc.clear();
if (result.needsVerification) {
navigate(`/verify?email=${encodeURIComponent(email)}&next=${encodeURIComponent(next || "/")}`, {
replace: true,
});
} else {
await qc.invalidateQueries({ queryKey: ["auth", "session"] });
await session.refetch();
navigate(next || "/", { replace: true });
}
} catch (err) {