import { create } from 'zustand';
import type { Session, User, AuthChangeEvent } from '@supabase/supabase-js';
import { supabase } from '../lib/supabase';
import {
signIn as authSignIn,
signOut as authSignOut,
getSession,
refreshSession,
} from '../lib/auth';
/**
* Auth store state interface
*/
interface AuthState {
/** Current authenticated user */
user: User | null;
/** Current session */
session: Session | null;
/** Whether auth operations are in progress */
isLoading: boolean;
/** Whether the store has been initialized */
isInitialized: boolean;
/** Last auth error message */
error: string | null;
}
/**
* Auth store actions interface
*/
interface AuthActions {
/** Sign in with email and password */
signIn: (email: string, password: string) => Promise<boolean>;
/** Sign out the current user */
signOut: () => Promise<void>;
/** Initialize the auth store */
initialize: () => Promise<void>;
/** Refresh the current session */
refresh: () => Promise<void>;
/** Clear any auth errors */
clearError: () => void;
/** Set the auth state (for internal use) */
setAuth: (user: User | null, session: Session | null) => void;
}
/**
* Combined auth store type
*/
type AuthStore = AuthState & AuthActions;
/**
* Zustand store for authentication state management
*
* Usage:
* ```typescript
* import { useAuthStore } from './stores/authStore';
*
* // In component
* const { user, isLoading, signIn, signOut } = useAuthStore();
*
* // Or use selectors for performance
* const user = useAuthStore((state) => state.user);
* const signIn = useAuthStore((state) => state.signIn);
* ```
*/
export const useAuthStore = create<AuthStore>((set, get) => ({
// Initial state
user: null,
session: null,
isLoading: true,
isInitialized: false,
error: null,
/**
* Sign in with email and password
*/
signIn: async (email: string, password: string): Promise<boolean> => {
set({ isLoading: true, error: null });
const result = await authSignIn(email, password);
if (result.success && result.user && result.session) {
set({
user: result.user,
session: result.session,
isLoading: false,
error: null,
});
return true;
}
set({
isLoading: false,
error: result.error || 'Sign in failed',
});
return false;
},
/**
* Sign out the current user
*/
signOut: async (): Promise<void> => {
set({ isLoading: true, error: null });
const result = await authSignOut();
if (result.success) {
set({
user: null,
session: null,
isLoading: false,
error: null,
});
} else {
set({
isLoading: false,
error: result.error || 'Sign out failed',
});
}
},
/**
* Initialize the auth store by checking for existing session
*/
initialize: async (): Promise<void> => {
// Prevent re-initialization
if (get().isInitialized) {
return;
}
try {
const { session, error } = await getSession();
if (error) {
console.warn('Auth initialization warning:', error);
}
set({
user: session?.user ?? null,
session,
isLoading: false,
isInitialized: true,
error: null,
});
} catch (error) {
console.error('Auth initialization error:', error);
set({
user: null,
session: null,
isLoading: false,
isInitialized: true,
error: error instanceof Error ? error.message : 'Failed to initialize auth',
});
}
},
/**
* Refresh the current session
*/
refresh: async (): Promise<void> => {
const { session } = get();
if (!session) return;
try {
const result = await refreshSession();
if (result.session) {
set({
session: result.session,
user: result.session.user,
});
} else if (result.error) {
console.warn('Session refresh warning:', result.error);
}
} catch (error) {
console.error('Session refresh error:', error);
}
},
/**
* Clear any auth errors
*/
clearError: (): void => {
set({ error: null });
},
/**
* Set the auth state (for internal use by listeners)
*/
setAuth: (user: User | null, session: Session | null): void => {
set({ user, session, isLoading: false });
},
}));
/**
* Setup auth state listener
* Should be called once when the app initializes
*/
export function setupAuthListener(): () => void {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(
(event: AuthChangeEvent, session: Session | null) => {
console.log('Auth state changed:', event);
const { setAuth } = useAuthStore.getState();
switch (event) {
case 'SIGNED_IN':
case 'TOKEN_REFRESHED':
case 'USER_UPDATED':
setAuth(session?.user ?? null, session);
break;
case 'SIGNED_OUT':
setAuth(null, null);
break;
default:
break;
}
}
);
return () => {
subscription.unsubscribe();
};
}
/**
* Selector hooks for common auth state
*/
export const useUser = () => useAuthStore((state) => state.user);
export const useSession = () => useAuthStore((state) => state.session);
export const useIsLoading = () => useAuthStore((state) => state.isLoading);
export const useIsInitialized = () => useAuthStore((state) => state.isInitialized);
export const useAuthError = () => useAuthStore((state) => state.error);
export const useIsAuthenticated = () => useAuthStore((state) => state.session !== null);