import { create } from "zustand"; import { signInWithCustomToken, signOut as firebaseSignOut } from "firebase/auth"; import { apiClient, ApiError } from "@/api/client"; import type { Human } from "@/api/types"; import { firebaseAuth } from "@/firebase"; import { useSessionStore } from "./session-store"; async function signInToFirebase() { try { const { token } = await apiClient.getFirebaseToken(); await signInWithCustomToken(firebaseAuth, token); } catch (e) { console.error("Failed to sign in to Firebase", e); } } type AuthStatus = "idle" | "restoring" | "unauthenticated" | "authenticated"; interface AuthState { status: AuthStatus; user: Human | null; isRequestingCode: boolean; isSigningIn: boolean; isSigningOut: boolean; error: string | null; restoreSession: () => Promise; requestCode: (email: string) => Promise; signIn: (email: string, code: string) => Promise; signOut: () => Promise; clearError: () => void; } export const useAuthStore = create((set) => ({ status: "idle", user: null, isRequestingCode: false, isSigningIn: false, isSigningOut: false, error: null, restoreSession: async () => { const token = useSessionStore.getState().token; if (!token) { set({ status: "unauthenticated" }); return; } set({ status: "restoring" }); try { const user = await apiClient.me(); await signInToFirebase(); set({ status: "authenticated", user }); } catch { useSessionStore.getState().clearToken(); set({ status: "unauthenticated", user: null }); } }, requestCode: async (email: string) => { set({ isRequestingCode: true, error: null }); try { await apiClient.requestCode({ email }); } catch (e) { const message = e instanceof ApiError ? e.message : "Failed to send code"; set({ error: message }); throw e; } finally { set({ isRequestingCode: false }); } }, signIn: async (email: string, code: string) => { set({ isSigningIn: true, error: null }); try { const { human, token } = await apiClient.signIn({ email, code }); useSessionStore.getState().setToken(token); await signInToFirebase(); set({ status: "authenticated", user: human }); } catch (e) { const message = e instanceof ApiError ? e.message : "Failed to sign in"; set({ error: message }); throw e; } finally { set({ isSigningIn: false }); } }, signOut: async () => { set({ isSigningOut: true }); try { await apiClient.signOut(); } catch { // Best-effort — sign out locally regardless } finally { await firebaseSignOut(firebaseAuth).catch((e) => console.error("Firebase sign-out failed", e), ); useSessionStore.getState().clearToken(); set({ status: "unauthenticated", user: null, isSigningOut: false, error: null, }); } }, clearError: () => set({ error: null }), })); // React to token being cleared externally (e.g. 401 from API client) useSessionStore.subscribe((state, prevState) => { if (prevState.token && !state.token) { const authState = useAuthStore.getState(); if (authState.status === "authenticated") { useAuthStore.setState({ status: "unauthenticated", user: null, error: null, }); } } });