/** * Auth Store - Zustand store for authentication state management * Based on gamilit implementation with session management */ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; export interface User { id: string; email: string; firstName: string | null; lastName: string | null; tenantId: string; status: string; role?: string; } interface AuthState { // State user: User | null; accessToken: string | null; refreshToken: string | null; isAuthenticated: boolean; isLoading: boolean; isInitialized: boolean; error: string | null; sessionExpiresAt: number | null; // Actions setUser: (user: User | null) => void; setTokens: (accessToken: string, refreshToken: string) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; setInitialized: (initialized: boolean) => void; logout: () => void; clearError: () => void; checkSession: () => boolean; extendSession: () => void; } // Session duration: 7 days const SESSION_DURATION = 7 * 24 * 60 * 60 * 1000; export const useAuthStore = create()( persist( (set, get) => ({ // Initial state user: null, accessToken: null, refreshToken: null, isAuthenticated: false, isLoading: true, // Start as loading until initialized isInitialized: false, error: null, sessionExpiresAt: null, // Set user and authentication state setUser: (user) => set({ user, isAuthenticated: !!user, isLoading: false, isInitialized: true, error: null, }), // Set tokens and extend session setTokens: (accessToken, refreshToken) => set({ accessToken, refreshToken, isAuthenticated: true, isLoading: false, isInitialized: true, sessionExpiresAt: Date.now() + SESSION_DURATION, error: null, }), // Set loading state setLoading: (isLoading) => set({ isLoading }), // Set error setError: (error) => set({ error, isLoading: false }), // Set initialized (called after initial session check) setInitialized: (isInitialized) => set({ isInitialized, isLoading: false }), // Clear auth state (logout) logout: () => set({ user: null, accessToken: null, refreshToken: null, isAuthenticated: false, isLoading: false, error: null, sessionExpiresAt: null, }), // Clear error clearError: () => set({ error: null }), // Check if session is still valid checkSession: () => { const { sessionExpiresAt, accessToken } = get(); // No token = not authenticated if (!accessToken) { return false; } // No expiration set = assume valid (will be validated by API) if (!sessionExpiresAt) { return true; } // Check if session has expired const isValid = Date.now() < sessionExpiresAt; if (!isValid) { // Session expired - clear auth get().logout(); } return isValid; }, // Extend session expiration extendSession: () => set({ sessionExpiresAt: Date.now() + SESSION_DURATION, }), }), { name: 'erp-construccion-auth', partialize: (state) => ({ accessToken: state.accessToken, refreshToken: state.refreshToken, user: state.user, isAuthenticated: state.isAuthenticated, sessionExpiresAt: state.sessionExpiresAt, }), // Rehydrate: mark as initialized after loading from storage onRehydrateStorage: () => (state) => { if (state) { // Check if session is still valid after rehydration const isValid = state.checkSession(); state.setInitialized(true); if (!isValid) { state.logout(); } } }, } ) ); // Selector hooks for performance export const useIsAuthenticated = () => useAuthStore((state) => state.isAuthenticated); export const useUser = () => useAuthStore((state) => state.user); export const useAuthLoading = () => useAuthStore((state) => state.isLoading); export const useAuthInitialized = () => useAuthStore((state) => state.isInitialized);