feat(auth): implement email/password login form

This commit is contained in:
dhinesh-m24
2026-01-28 15:33:05 +05:30
parent 959a8c41e9
commit 6e81a062ab
18 changed files with 4287 additions and 8 deletions

View File

@@ -0,0 +1,141 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { loginWithEmail, logout, getCurrentUser } from "../../services/authService";
import type { User } from "firebase/auth";
export interface AuthUser {
uid: string;
email: string | null;
displayName: string | null;
photoURL: string | null;
role?: string;
}
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
*/
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;
return {
uid: firebaseUser.uid,
email: firebaseUser.email,
displayName: firebaseUser.displayName,
photoURL: firebaseUser.photoURL,
};
}
);
/**
* 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
*/
export const checkAuthStatus = createAsyncThunk("auth/checkAuthStatus", async () => {
const currentUser = getCurrentUser();
if (currentUser) {
return {
uid: currentUser.uid,
email: currentUser.email,
displayName: currentUser.displayName,
photoURL: currentUser.photoURL,
};
}
return null;
});
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
clearError: (state) => {
state.error = null;
},
setRole: (state, action: PayloadAction<string>) => {
if (state.user) {
state.user.role = 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, setRole } = authSlice.actions;
export default authSlice.reducer;