feat(auth): implement email/password login form
This commit is contained in:
141
apps/web/src/features/auth/authSlice.ts
Normal file
141
apps/web/src/features/auth/authSlice.ts
Normal 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;
|
||||
Reference in New Issue
Block a user