summaryrefslogblamecommitdiff
path: root/apps/mobile/stores/authStore.ts
blob: 4bc4581e11f5726e5d45f45ac274bcafa0a39e3e (plain) (tree)










































































































































































































































                                                                                        
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);