172 lines
4.5 KiB
TypeScript
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;
|