import { readMe } from '@directus/sdk'; import { create } from 'zustand'; import { getDirectus } from './client'; import type { DirectusUser } from './client'; export type AuthStatus = 'unknown' | 'anonymous' | 'authenticating' | 'authenticated'; export type AuthState = | { status: 'unknown' } | { status: 'anonymous' } | { status: 'authenticating' } | { status: 'authenticated'; user: DirectusUser }; type AuthActions = { /** Probe `/users/me` once at boot to determine if we already have a session. */ initialize: () => Promise; /** Sign in with email + password using Directus's cookie mode. */ login: (email: string, password: string) => Promise<{ ok: true } | { ok: false; error: string }>; /** Sign out via Directus + clear local state. Errors during the server call are swallowed. */ logout: () => Promise; /** Replace the current user record (e.g. after profile edit). No-op if not authenticated. */ setUser: (user: DirectusUser) => void; }; type Store = AuthState & AuthActions; const USER_FIELDS = ['id', 'email', 'role', 'first_name', 'last_name'] as const; function humanizeAuthError(err: unknown): string { if (err && typeof err === 'object' && 'errors' in err) { const e = err as { errors?: { extensions?: { code?: string }; message?: string }[] }; const code = e.errors?.[0]?.extensions?.code; if (code === 'INVALID_CREDENTIALS') return 'Invalid email or password.'; if (code === 'INVALID_OTP') return 'Invalid one-time password.'; if (code === 'USER_SUSPENDED') return 'This account is suspended. Contact your administrator.'; if (e.errors?.[0]?.message) return e.errors[0].message; } if (err instanceof Error) return err.message; return 'Login failed. Please try again.'; } async function fetchMe(): Promise { const directus = getDirectus(); const result = await directus.request(readMe({ fields: [...USER_FIELDS] })); return result as DirectusUser; } export const useAuthStore = create((set) => ({ status: 'unknown', async initialize() { set({ status: 'authenticating' }); try { const user = await fetchMe(); set({ status: 'authenticated', user }); } catch { // Any failure on /users/me at boot means no session. Swallow. set({ status: 'anonymous' }); } }, async login(email, password) { set({ status: 'authenticating' }); try { const directus = getDirectus(); await directus.login({ email, password }); const user = await fetchMe(); set({ status: 'authenticated', user }); return { ok: true }; } catch (err) { set({ status: 'anonymous' }); return { ok: false, error: humanizeAuthError(err) }; } }, async logout() { try { await getDirectus().logout(); } catch { // Even if the server call fails, drop local state so the user appears // signed out. Logout that fails should not leave the user appearing // logged in. } set({ status: 'anonymous' }); }, setUser(user) { set((state) => (state.status === 'authenticated' ? { ...state, user } : state)); }, }));