import { useState, useSyncExternalStore, type ReactNode } from "react"; import { Navigate } from "@tanstack/react-router"; import { GROK_PROVIDERS, authEnabled, signIn, signOut } from "./client"; import { hasGateSessionMarker } from "./gate-session-marker"; import { resolveSignInGateState } from "./sign-in-gate"; import { useCurrentUser, useCurrentUserState } from "./use-current-user"; const subscribeToNothing = () => () => {}; const noGateSessionOnServer = () => false; /** * Auth state components — plain wrappers around `useCurrentUserState()`. * * With auth on, visitors are signed out until they authenticate — in the sandbox * live preview too, which does real sign-in. The shared dev user appears only * when auth is disabled (`VITE_AUTH_ENABLED=false`, the shipped default). * While the session is still resolving, gates that care about signed-out state * render nothing so there's no signed-out flash on hard reload. */ /** Where `RedirectToSignIn` sends signed-out visitors. Create this route. */ export const SIGN_IN_PATH = "/login"; /** Render children only when a user is present (real session, or the disabled-auth dev user). */ export function SignedIn({ children }: { children: ReactNode }) { const { user } = useCurrentUserState(); return user ? <>{children} : null; } /** * Render children only once we KNOW the visitor is signed out (`isPending` has * cleared and there is no user). Hidden while the session is still loading. */ export function SignedOut({ children }: { children: ReactNode }) { const { user, isPending } = useCurrentUserState(); if (isPending || user) return null; return <>{children}; } /** * Client-side redirect to the sign-in route (TanStack `` — NOT a full * `window.location` reload). A hard navigation re-bootstraps the SPA and re-runs * session loading, which feels like a second "Loading…" on /login. * * Guard routes by waiting out `isPending` first (see `use-current-user`), then * render this. */ export function RedirectToSignIn({ to = SIGN_IN_PATH }: { to?: string }) { return ; } export function SignInGate({ children, fallback, }: { children: ReactNode; fallback?: ReactNode; }) { const { user, isPending } = useCurrentUserState(); const state = resolveSignInGateState({ isPending, hasUser: user !== null }); if (state === "pending") return null; if (state === "signed_in") return <>{children}; return <>{fallback ?? }; } export function SignInButtons() { return (
{GROK_PROVIDERS.map((p) => ( ))}
); } /** * Minimal signed-in identity chip + sign-out. Restyle freely (see the * `design-ui` skill). Sign-out is only shown when auth is enabled (the * disabled-auth dev user has nothing to sign out of) and the session is not * gate-materialized — behind the gate the next request signs the viewer * straight back in, so a sign-out control there is a broken loop. */ export function UserButton() { const user = useCurrentUser(); // Sign-out can take a moment (and can fail when deployed), so the control // shows it is working and cannot be fired twice. const [signingOut, setSigningOut] = useState(false); const gateSession = useSyncExternalStore( subscribeToNothing, hasGateSessionMarker, noGateSessionOnServer, ); if (!user) return null; const label = user.displayName ?? user.primaryEmail ?? "Account"; return (
{user.profileImageUrl ? ( ) : ( {label.charAt(0).toUpperCase()} )} {label} {authEnabled && !gateSession && ( )}
); }