Files
Krow-workspace/apps/web/src/features/auth/authSlice.ts
2026-01-29 11:43:19 +05:30

172 lines
4.5 KiB
TypeScript

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;