import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import type { PayloadAction } from "@reduxjs/toolkit"; import { loginWithEmail, logout, getCurrentUser } from "../../services/authService"; import { fetchUserData } from "../../services/firestoreService"; import type { User } from "firebase/auth"; export interface AuthUser { uid: string; email: string | null; displayName: string | null; photoURL: string | null; userRole?: "admin" | "client" | "vendor"; } interface AuthState { user: AuthUser | null; isAuthenticated: boolean; status: "idle" | "loading" | "succeeded" | "failed"; error: string | null; } const initialState: AuthState = { user: null, isAuthenticated: false, status: "idle", error: null, }; /** * Async thunk for user login * Fetches user data including role from Firestore after authentication */ export const loginUser = createAsyncThunk( "auth/loginUser", async ({ email, password }: { email: string; password: string }, { rejectWithValue }) => { const result = await loginWithEmail(email, password); if (!result.success) { return rejectWithValue(result.error); } const firebaseUser = result.user as User; // Fetch user role from Firestore let userRole: "admin" | "client" | "vendor" = "client"; try { const userData = await fetchUserData(firebaseUser.uid); if (userData) { userRole = userData.userRole; } } catch (error) { console.error("Failed to fetch user role:", error); // Continue with default role on error } return { uid: firebaseUser.uid, email: firebaseUser.email, displayName: firebaseUser.displayName, photoURL: firebaseUser.photoURL, userRole: userRole, }; } ); /** * Async thunk for user logout */ export const logoutUser = createAsyncThunk("auth/logoutUser", async (_, { rejectWithValue }) => { const result = await logout(); if (!result.success) { return rejectWithValue(result.error); } return null; }); /** * Async thunk to check if user is already logged in * Fetches user role from Firestore on app initialization */ export const checkAuthStatus = createAsyncThunk("auth/checkAuthStatus", async (_, { rejectWithValue }) => { const currentUser = getCurrentUser(); if (currentUser) { // Fetch user role from Firestore let userRole: "admin" | "client" | "vendor" = "client"; try { const userData = await fetchUserData(currentUser.uid); if (userData) { userRole = userData.userRole; } } catch (error) { console.error("Failed to fetch user role:", error); // Continue with default role on error } return { uid: currentUser.uid, email: currentUser.email, displayName: currentUser.displayName, photoURL: currentUser.photoURL, userRole: userRole, }; } return null; }); const authSlice = createSlice({ name: "auth", initialState, reducers: { clearError: (state) => { state.error = null; }, setUserRole: (state, action: PayloadAction<"admin" | "client" | "vendor">) => { if (state.user) { state.user.userRole = action.payload; } }, }, extraReducers: (builder) => { // Login thunk builder .addCase(loginUser.pending, (state) => { state.status = "loading"; state.error = null; }) .addCase(loginUser.fulfilled, (state, action) => { state.status = "succeeded"; state.isAuthenticated = true; state.user = action.payload; state.error = null; }) .addCase(loginUser.rejected, (state, action) => { state.status = "failed"; state.error = action.payload as string; state.isAuthenticated = false; }); // Logout thunk builder .addCase(logoutUser.fulfilled, (state) => { state.user = null; state.isAuthenticated = false; state.status = "idle"; state.error = null; }) .addCase(logoutUser.rejected, (state, action) => { state.error = action.payload as string; }); // Check auth status thunk builder .addCase(checkAuthStatus.fulfilled, (state, action) => { if (action.payload) { state.user = action.payload; state.isAuthenticated = true; } else { state.user = null; state.isAuthenticated = false; } state.status = "idle"; }); }, }); export const { clearError, setUserRole } = authSlice.actions; export default authSlice.reducer;