From c869245bbf697083b94c842ffb65901d7d779749 Mon Sep 17 00:00:00 2001 From: Eknoor Singh Date: Fri, 14 Feb 2025 10:43:44 +0530 Subject: [PATCH] Implemented feedback changes --- src/components/CustomTable/index.tsx | 2 +- src/components/MenuContent/index.tsx | 4 +- src/components/SideMenu/index.tsx | 4 +- src/lib/https.ts | 2 +- src/pages/AdminList/index.tsx | 4 +- src/pages/Auth/SignUp/index.tsx | 17 ++- src/pages/ProfilePage/index.tsx | 8 +- src/redux/reducers.ts | 4 + src/redux/slices/adminSlice.ts | 155 ++++++++++++++++++++ src/redux/slices/authSlice.ts | 206 ++------------------------- src/redux/slices/profileSlice.ts | 71 +++++++++ src/redux/store/store.ts | 4 + src/router.tsx | 75 ++++++---- src/superAdminRouter.tsx | 25 ---- 14 files changed, 321 insertions(+), 260 deletions(-) create mode 100644 src/redux/slices/adminSlice.ts create mode 100644 src/redux/slices/profileSlice.ts delete mode 100644 src/superAdminRouter.tsx diff --git a/src/components/CustomTable/index.tsx b/src/components/CustomTable/index.tsx index c03f7bc..9b8c7c3 100644 --- a/src/components/CustomTable/index.tsx +++ b/src/components/CustomTable/index.tsx @@ -7,7 +7,7 @@ import TableContainer from "@mui/material/TableContainer" import TableHead from "@mui/material/TableHead" import TableRow from "@mui/material/TableRow" import Paper, { paperClasses } from "@mui/material/Paper" -import { deleteAdmin } from "../../redux/slices/authSlice" +import { deleteAdmin } from "../../redux/slices/adminSlice" import { useDispatch } from "react-redux" import { Box, diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index d1b724c..481b2ec 100644 --- a/src/components/MenuContent/index.tsx +++ b/src/components/MenuContent/index.tsx @@ -43,7 +43,9 @@ type PropType = { export default function MenuContent({ hidden }: PropType) { const location = useLocation(); - const userRole = useSelector((state: RootState) => state.auth.user?.role); + const userRole = useSelector( + (state: RootState) => state.profile.user?.role + ); const mainListItems = [ ...baseMenuItems, diff --git a/src/components/SideMenu/index.tsx b/src/components/SideMenu/index.tsx index 009f59a..1a36cb9 100644 --- a/src/components/SideMenu/index.tsx +++ b/src/components/SideMenu/index.tsx @@ -11,7 +11,7 @@ import React, { useEffect } from "react"; import { ArrowLeftIcon, ArrowRightIcon } from "@mui/x-date-pickers"; import { AppDispatch, RootState } from "../../redux/store/store"; import { Button } from "@mui/material"; -import { fetchAdminProfile } from "../../redux/slices/authSlice"; +import { fetchAdminProfile } from "../../redux/slices/profileSlice"; const drawerWidth = 240; @@ -34,7 +34,7 @@ export default function SideMenu() { //Dispatch is called with user from Authstate Interface const dispatch = useDispatch(); - const { user } = useSelector((state: RootState) => state?.auth); + const { user } = useSelector((state: RootState) => state?.profile); useEffect(() => { dispatch(fetchAdminProfile()); diff --git a/src/lib/https.ts b/src/lib/https.ts index 5369fe1..884746e 100644 --- a/src/lib/https.ts +++ b/src/lib/https.ts @@ -6,7 +6,7 @@ const http = axios.create({ http.interceptors.request.use((config) => { const authToken = localStorage.getItem("authToken"); if (authToken) { - config.headers.Authorization = authToken; + config.headers.Authorization = `Bearer ${authToken}`; } return config; diff --git a/src/pages/AdminList/index.tsx b/src/pages/AdminList/index.tsx index 6f6492f..3ca4ba1 100644 --- a/src/pages/AdminList/index.tsx +++ b/src/pages/AdminList/index.tsx @@ -4,7 +4,7 @@ import AddEditCategoryModal from "../../components/AddEditCategoryModal"; import { useForm } from "react-hook-form"; import CustomTable, { Column } from "../../components/CustomTable"; import { useDispatch, useSelector } from "react-redux"; -import { adminList, updateAdmin } from "../../redux/slices/authSlice"; +import { adminList, updateAdmin } from "../../redux/slices/adminSlice"; import { AppDispatch, RootState } from "../../redux/store/store"; // Import RootState for selector export default function AdminList() { @@ -17,7 +17,7 @@ export default function AdminList() { const dispatch = useDispatch(); // Fetching admin data from the Redux store - const admins = useSelector((state: RootState) => state.auth.admins); + const admins = useSelector((state: RootState) => state.admin.admins); // Dispatching the API call when the component mounts useEffect(() => { diff --git a/src/pages/Auth/SignUp/index.tsx b/src/pages/Auth/SignUp/index.tsx index b28fcef..ae83e39 100644 --- a/src/pages/Auth/SignUp/index.tsx +++ b/src/pages/Auth/SignUp/index.tsx @@ -96,6 +96,10 @@ export default function SignUp(props: { disableCustomTheme?: boolean }) { const navigate = useNavigate(); const [phone, setPhone] = React.useState(""); + const roleOptions = [ + { value: "admin", label: "Admin" }, + { value: "user", label: "User" } + ]; // Enhanced email validation regex const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/; @@ -266,11 +270,14 @@ export default function SignUp(props: { disableCustomTheme?: boolean }) { } error={!!errors.role} > - - SuperAdmin - - Admin - User + {roleOptions.map((option) => ( + + {option.label} + + ))} )} /> diff --git a/src/pages/ProfilePage/index.tsx b/src/pages/ProfilePage/index.tsx index 2b9987b..0f96708 100644 --- a/src/pages/ProfilePage/index.tsx +++ b/src/pages/ProfilePage/index.tsx @@ -16,7 +16,7 @@ import { import { useDispatch, useSelector } from "react-redux"; import { AppDispatch, RootState } from "../../redux/store/store"; -import { fetchAdminProfile } from "../../redux/slices/authSlice"; +import { fetchAdminProfile } from "../../redux/slices/profileSlice"; const ProfilePage = () => { //Eknoor singh @@ -24,7 +24,7 @@ const ProfilePage = () => { //Dispatch is called and user, isLoading, and error from Authstate Interface const dispatch = useDispatch(); const { user, isLoading, error } = useSelector( - (state: RootState) => state?.auth + (state: RootState) => state?.profile ); useEffect(() => { @@ -56,10 +56,6 @@ const ProfilePage = () => { ); } - console.log(user?.name); - console.log(user?.email); - console.log(user?.role); - return ( diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index 0f22e9d..03caa21 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -1,9 +1,13 @@ import { combineReducers } from "@reduxjs/toolkit"; import authReducer from "./slices/authSlice"; +import adminReducer from "./slices/adminSlice"; +import profileReducer from "./slices/profileSlice"; const rootReducer = combineReducers({ authReducer, + adminReducer, + profileReducer }); export type RootState = ReturnType; diff --git a/src/redux/slices/adminSlice.ts b/src/redux/slices/adminSlice.ts new file mode 100644 index 0000000..651b98c --- /dev/null +++ b/src/redux/slices/adminSlice.ts @@ -0,0 +1,155 @@ +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import http from "../../lib/https"; +import { toast } from "react-toastify"; + +// Interfaces +interface User { + token: string | null; + id: string; + name: string; + email: string; + role: string; + phone: string; +} + +interface Admin { + id: string; + name: string; + role: string; + email: string; + phone: string; +} + +interface AuthState { + user: User | null; + admins: Admin[]; + isAuthenticated: boolean; + isLoading: boolean; + error: string | null; + token: string | null; +} + +// Fetch Admin List +export const adminList = createAsyncThunk< + Admin[], + void, + { rejectValue: string } +>("FetchAdminList", async (_, { rejectWithValue }) => { + try { + const response = await http.get("auth/admin-list"); + return response?.data?.data; + } catch (error: any) { + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + +// Delete Admin +export const deleteAdmin = createAsyncThunk< + string, + string, + { rejectValue: string } +>("deleteAdmin", async (id, { rejectWithValue }) => { + try { + const response = await http.delete(`auth/${id}/delete-admin`); + toast.success(response.data?.message); + return id; + } catch (error: any) { + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + +// Update Admin +export const updateAdmin = createAsyncThunk( + "UpdateAdmin", + async ( + { id, name, role }: { id: any; name: string; role: string }, + { rejectWithValue } + ) => { + try { + const response = await http.put(`auth/${id}/update-admin`, { + name, + role, + }); + toast.success("Admin updated successfully"); + return response?.data; + } catch (error: any) { + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } + } +); + +const initialState: AuthState = { + user: null, + admins: [], + isAuthenticated: false, + isLoading: false, + error: null, + token: null, +}; + +const adminSlice = createSlice({ + name: "admin", + initialState, + reducers: { + }, + extraReducers: (builder) => { + builder + .addCase(adminList.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase( + adminList.fulfilled, + (state, action: PayloadAction) => { + state.isLoading = false; + state.admins = action.payload; + } + ) + .addCase( + adminList.rejected, + (state, action: PayloadAction) => { + state.isLoading = false; + state.error = action.payload || "An error occurred"; + } + ) + .addCase(deleteAdmin.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteAdmin.fulfilled, (state, action) => { + state.isLoading = false; + state.admins = state.admins.filter( + (admin) => String(admin.id) !== String(action.payload) + ); + }) + .addCase(deleteAdmin.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload || "Failed to delete admin"; + }) + .addCase(updateAdmin.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(updateAdmin.fulfilled, (state, action) => { + const updatedAdmin = action.payload; + state.admins = state?.admins?.map((admin) => + admin?.id === updatedAdmin?.id ? updatedAdmin : admin + ); + state.isLoading = false; + }) + .addCase(updateAdmin.rejected, (state, action) => { + state.isLoading = false; + state.error = + typeof action.payload === "string" + ? action.payload + : "Something went wrong while updating Admin!"; + }); + }, +}); + +export default adminSlice.reducer; diff --git a/src/redux/slices/authSlice.ts b/src/redux/slices/authSlice.ts index 2915ccb..e3c4997 100644 --- a/src/redux/slices/authSlice.ts +++ b/src/redux/slices/authSlice.ts @@ -7,6 +7,7 @@ import { toast } from "react-toastify"; //date:- 12-Feb-2025 //Token for the user has been declared interface User { + data: any; token: string | null; map( arg0: ( @@ -43,10 +44,10 @@ export const loginUser = createAsyncThunk< User, { email: string; password: string }, { rejectValue: string } ->("auth/login", async ({ email, password }, { rejectWithValue }) => { +>("LoginUser", async ({ email, password }, { rejectWithValue }) => { try { // this is endpoint not action name - const response = await http.post("admin/login", { + const response = await http.post("auth/login", { email, password, }); @@ -82,112 +83,6 @@ export const registerUser = createAsyncThunk< } }); -//created by Eknoor and jaanvi -//date: 10-Feb-2025 -//Fetching list of admins - -export const adminList = createAsyncThunk< - Admin[], - void, - { rejectValue: string } ->("/auth", async (_, { rejectWithValue }) => { - try { - const response = await http.get("/auth"); - console.log(response?.data?.data); - return response?.data?.data?.map( - (admin: { - id: string; - name: string; - role: string; - email: string; - }) => ({ - id: admin?.id, - name: admin?.name, - role: admin?.role || "N/A", - email: admin?.email, - }) - ); - } catch (error: any) { - return rejectWithValue( - error.response?.data?.message || "An error occurred" - ); - } -}); - -//created by Eknoor -//date: 11-Feb-2025 -//function for deleting admin - -export const deleteAdmin = createAsyncThunk< - string, - string, - { rejectValue: string } ->("deleteAdmin", async (id, { rejectWithValue }) => { - try { - const response = await http.delete(`/auth/${id}`); - toast.success(response.data?.message); - return id; - } catch (error: any) { - return rejectWithValue( - error.response?.data?.message || "An error occurred" - ); - } -}); - -export const updateAdmin = createAsyncThunk( - "/auth/id", - async ( - { id, name, role }: { id: any; name: string; role: string }, - { rejectWithValue } - ) => { - try { - const response = await http.put(`/auth/${id}`, { name, role }); - toast.success("Admin updated successfully"); - console.log(response?.data); - return response?.data; - } catch (error: any) { - return rejectWithValue( - error.response?.data?.message || "An error occurred" - ); - } - } -); - -//Eknoor singh -//date:- 12-Feb-2025 -//Function for fetching profile of a particular user has been implemented with Redux. -export const fetchAdminProfile = createAsyncThunk< - User, - void, - { rejectValue: string } ->("auth/fetchAdminProfile", async (_, { rejectWithValue }) => { - try { - const token = localStorage?.getItem("authToken"); - if (!token) throw new Error("No token found"); - - const response = await http?.get("/auth/profile", { - headers: { Authorization: `Bearer ${token}` }, // Ensure 'Bearer' prefix - }); - - console.log("API Response:", response?.data); // Debugging - - if (!response.data?.data) { - throw new Error("Invalid API response"); - } - - return response?.data?.data; // Fix: Return only `data`, assuming it contains user info. - } catch (error: any) { - console.error( - "Profile Fetch Error:", - error?.response?.data || error?.message - ); - return rejectWithValue( - error?.response?.data?.message || "An error occurred" - ); - } -}); -// TODO: create logout action and delete token then handle logout cases - const initialState: AuthState = { user: null, admins: [], @@ -204,15 +99,6 @@ const authSlice = createSlice({ name: "auth", initialState, reducers: { - logout: (state) => { - state.user = null; - state.isAuthenticated = false; - //Eknoor singh - //date:- 12-Feb-2025 - //Token is removed from local storage and set to null - state.token = null; - localStorage.removeItem("authToken"); - }, }, extraReducers: (builder) => { builder @@ -224,9 +110,10 @@ const authSlice = createSlice({ .addCase(loginUser.fulfilled, (state, action) => { state.isLoading = false; state.isAuthenticated = true; - state.user = action.payload; // Fix: Extract correct payload - state.token = action.payload.token; // Store token in Redux + state.user = action.payload.data; + state.token = action.payload.data.token; }) + .addCase( loginUser.rejected, (state, action: PayloadAction) => { @@ -253,83 +140,16 @@ const authSlice = createSlice({ state.isLoading = false; state.error = action.payload || "An error occurred"; } - ) + ); - // created by Jaanvi and Eknoor - //AdminList - .addCase(adminList.pending, (state) => { - state.isLoading = true; - state.error = null; - }) - .addCase( - adminList.fulfilled, - (state, action: PayloadAction) => { - state.isLoading = false; - state.admins = action.payload; - } - ) + // created by Jaanvi and Eknoor + //AdminList - .addCase( - adminList.rejected, - (state, action: PayloadAction) => { - state.isLoading = false; - state.error = action.payload || "An error occurred"; - } - ) - - //created by Eknoor - //date: 11-Feb-2025 - //cases for deleting admin - .addCase(deleteAdmin.pending, (state) => { - state.isLoading = true; - }) - .addCase(deleteAdmin.fulfilled, (state, action) => { - state.isLoading = false; - state.admins = state.admins.filter( - (admin) => String(admin.id) !== String(action.payload) - ); - }) - .addCase(deleteAdmin.rejected, (state, action) => { - state.isLoading = false; - state.error = action.payload || "Failed to delete admin"; - }) - .addCase(updateAdmin.pending, (state) => { - state.isLoading = true; - state.error = null; - }) - .addCase(updateAdmin.fulfilled, (state, action) => { - const updatedAdmin = action.payload; - state.admins = state?.admins?.map((admin) => - admin?.id === updatedAdmin?.id ? updatedAdmin : admin - ); - - state.isLoading = false; - }) - .addCase(updateAdmin.rejected, (state, action) => { - state.isLoading = false; - state.error = - action.payload || - "Something went wrong while updating Admin!!"; - }) - - //Eknoor singh - //date:- 12-Feb-2025 - //Reducers for fetching profiles has been implemented - .addCase(fetchAdminProfile.pending, (state) => { - state.isLoading = true; - state.error = null; - }) - .addCase(fetchAdminProfile.fulfilled, (state, action) => { - state.isLoading = false; - state.user = action.payload; - state.isAuthenticated = true; - }) - .addCase(fetchAdminProfile.rejected, (state, action) => { - state.isLoading = false; - state.error = action.payload || "Failed to fetch admin profile"; - }); + //Eknoor singh + //date:- 12-Feb-2025 + //Reducers for fetching profiles has been implemente }, }); -export const { logout } = authSlice.actions; +// export const { logout } = authSlice.actions; export default authSlice.reducer; diff --git a/src/redux/slices/profileSlice.ts b/src/redux/slices/profileSlice.ts new file mode 100644 index 0000000..ed9dfa1 --- /dev/null +++ b/src/redux/slices/profileSlice.ts @@ -0,0 +1,71 @@ +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; +import http from "../../lib/https"; + +interface User { + token: string | null; + id: string; + name: string; + email: string; + role: string; + phone: string; +} + +interface AuthState { + user: User | null; + isAuthenticated: boolean; + isLoading: boolean; + error: string | null; +} + +export const fetchAdminProfile = createAsyncThunk< + User, + void, + { rejectValue: string } +>("GetAdminProfile", async (_, { rejectWithValue }) => { + try { + const token = localStorage?.getItem("authToken"); + if (!token) throw new Error("No token found"); + + const response = await http.get("/auth/profile", { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!response.data?.data) throw new Error("Invalid API response"); + + return response.data.data; + } catch (error: any) { + return rejectWithValue(error?.response?.data?.message || "An error occurred"); + } +}); + +const initialState: AuthState = { + user: null, + isAuthenticated: false, + isLoading: false, + error: null, +}; + +const profileSlice = createSlice({ + name: "profile", + initialState, + reducers: { + }, + extraReducers: (builder) => { + builder + .addCase(fetchAdminProfile.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase(fetchAdminProfile.fulfilled, (state, action) => { + state.isLoading = false; + state.user = action.payload; + state.isAuthenticated = true; + }) + .addCase(fetchAdminProfile.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload || "Failed to fetch admin profile"; + }); + }, +}); + +export default profileSlice.reducer; diff --git a/src/redux/store/store.ts b/src/redux/store/store.ts index 3f04936..97e933e 100644 --- a/src/redux/store/store.ts +++ b/src/redux/store/store.ts @@ -1,8 +1,12 @@ import { configureStore } from '@reduxjs/toolkit'; import authReducer from '../slices/authSlice.ts' +import adminReducer from "../slices/adminSlice.ts" +import profileReducer from "../slices/profileSlice.ts" const store = configureStore({ reducer: { auth: authReducer, + admin: adminReducer, + profile: profileReducer }, }); diff --git a/src/router.tsx b/src/router.tsx index d848c13..a6c7e65 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,7 +1,16 @@ -import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom"; +import { + Routes as BaseRoutes, + Navigate, + Route, + RouteProps, +} from "react-router-dom"; import React, { Suspense } from "react"; +import { useSelector } from "react-redux"; +import { RootState } from "./redux/store/store"; import LoadingComponent from "./components/Loading"; import DashboardLayout from "./layouts/DashboardLayout"; + +// Page imports import Login from "./pages/Auth/Login"; import SignUp from "./pages/Auth/SignUp"; import Dashboard from "./pages/Dashboard"; @@ -9,27 +18,44 @@ import Vehicles from "./pages/Vehicles"; import AdminList from "./pages/AdminList"; import ProfilePage from "./pages/ProfilePage"; -import SuperAdminRouter from "./superAdminRouter"; - -function ProtectedRoute({ - caps, - component, -}: { +interface ProtectedRouteProps { caps: string[]; component: React.ReactNode; -}) { - if (!localStorage.getItem("authToken")) - return ; - - return component; } +interface SuperAdminRouteProps { + children: React.ReactNode; +} + +// Protected Route Component +const ProtectedRoute: React.FC = ({ caps, component }) => { + if (!localStorage.getItem("authToken")) { + return ; + } + return <>{component}; +}; + +// Super Admin Route Component +const SuperAdminRoute: React.FC = ({ children }) => { + const userRole = useSelector( + (state: RootState) => state.profile.user?.role + ); + + if (userRole !== "superadmin") { + return ; + } + return <>{children}; +}; + +// Combined Router Component export default function AppRouter() { return ( }> + {/* Default Route */} } index /> + {/* Auth Routes */} } /> } /> + } + /> + } + /> + {/* Dashboard Routes */} }> + - + } /> } /> - 404} /> - } /> - } - /> + {/* Catch-all Route */} 404} /> diff --git a/src/superAdminRouter.tsx b/src/superAdminRouter.tsx deleted file mode 100644 index 812d459..0000000 --- a/src/superAdminRouter.tsx +++ /dev/null @@ -1,25 +0,0 @@ -//Eknoor singh and jaanvi -//date:- 12-Feb-2025 -//seperate route for super admin implemented - -import React from "react"; -import { useSelector } from "react-redux"; -import { Navigate } from "react-router-dom"; -import { RootState } from "./redux/store/store"; - -interface SuperAdminRouteProps { - children: React.ReactNode; -} - -const SuperAdminRouter: React.FC = ({ children }) => { - const userRole = useSelector((state: RootState) => state.auth.user?.role); - - if (userRole !== "superadmin") { - // Redirect to dashboard if user is not a superadmin - return ; - } - - return <>{children}; -}; - -export default SuperAdminRouter;