From ba2945a3e55a0ecafd096d8a8f057017ecf4fd3f Mon Sep 17 00:00:00 2001 From: jaanvi Date: Thu, 13 Feb 2025 10:11:14 +0530 Subject: [PATCH] Implementation-of-Profile-Api-And-SuperAdmin-Specification --- src/components/CustomTable/index.tsx | 396 ++++++++++-------- src/components/MenuContent/index.tsx | 153 ++++--- .../Modals/DeleteModal/DeleteModal.tsx | 139 +++--- src/components/OptionsMenu/index.tsx | 10 +- src/components/SideMenu/index.tsx | 190 +++++---- src/lib/https.ts | 24 +- src/pages/AdminList/index.tsx | 27 +- src/pages/ProfilePage/index.tsx | 103 +++++ src/redux/slices/authSlice.ts | 177 ++++++-- src/router.tsx | 24 +- src/superAdminRouter.tsx | 26 ++ 11 files changed, 806 insertions(+), 463 deletions(-) create mode 100644 src/pages/ProfilePage/index.tsx create mode 100644 src/superAdminRouter.tsx diff --git a/src/components/CustomTable/index.tsx b/src/components/CustomTable/index.tsx index 34a5b51..447c6de 100644 --- a/src/components/CustomTable/index.tsx +++ b/src/components/CustomTable/index.tsx @@ -1,193 +1,225 @@ -import * as React from "react"; -import { styled } from "@mui/material/styles"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell, { tableCellClasses } from "@mui/material/TableCell"; -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 * as React from "react" +import { styled } from "@mui/material/styles" +import Table from "@mui/material/Table" +import TableBody from "@mui/material/TableBody" +import TableCell, { tableCellClasses } from "@mui/material/TableCell" +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 { useDispatch } from "react-redux" import { - Box, - Button, - dividerClasses, - IconButton, - listClasses, - Menu, -} from "@mui/material"; -import MoreVertRoundedIcon from "@mui/icons-material/MoreVertRounded"; - + Box, + Button, + dividerClasses, + IconButton, + listClasses, + Menu, +} from "@mui/material" +import MoreVertRoundedIcon from "@mui/icons-material/MoreVertRounded" +import DeleteModal from "../Modals/DeleteModal/DeleteModal" +import { AppDispatch } from "../../redux/store/store" + // Styled components for customization const StyledTableCell = styled(TableCell)(({ theme }) => ({ - [`&.${tableCellClasses.head}`]: { - backgroundColor: " #1565c0", - color: theme.palette.common.white, - }, - [`&.${tableCellClasses.body}`]: { - fontSize: 14, - }, -})); - + [`&.${tableCellClasses.head}`]: { + backgroundColor: " #1565c0", + color: theme.palette.common.white, + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14, + }, +})) + const StyledTableRow = styled(TableRow)(({ theme }) => ({ - "&:nth-of-type(odd)": { - backgroundColor: theme.palette.action.hover, - }, - "&:last-child td, &:last-child th": { - border: 0, - }, -})); - -interface Column { - id: string; - label: string; - align?: "left" | "center" | "right"; + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + "&:last-child td, &:last-child th": { + border: 0, + }, +})) + +export interface Column { + id: string + label: string + align?: "left" | "center" | "right" } - + interface Row { - [key: string]: any; + [key: string]: any } - + interface CustomTableProps { - columns: Column[]; - rows: Row[]; - setDeleteModal: Function; - setRowData: Function; - setModalOpen: Function; + columns: Column[] + rows: Row[] + setDeleteModal: Function + setRowData: Function + setModalOpen: Function + deleteModal: boolean } - + const CustomTable: React.FC = ({ - columns, - rows, - setDeleteModal, - setRowData, - setModalOpen, + columns, + rows, + setDeleteModal, + deleteModal, + setRowData, + setModalOpen, }) => { - console.log("columnsss", columns, rows); - - const [anchorEl, setAnchorEl] = React.useState(null); - const open = Boolean(anchorEl); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - const handleClose = () => { - setAnchorEl(null); - }; - - const isImage = (value: any) => { - if (typeof value === "string") { - return value.startsWith("http") || value.startsWith("data:image"); // Check for URL or base64 image - } - return false; - }; - - return ( - - - - - {columns.map((column) => ( - - {column.label} - - ))} - - - - {rows.map((row, rowIndex) => ( - - {columns.map((column) => ( - - {isImage(row[column.id]) ? ( - Row - ) : column.id !== "action" ? ( - row[column.id] - ) : ( - { - handleClick(e); - setRowData(row); - }} - > - - - )} - - ))} - - ))} - -
- {open && ( - - - - - - - )} -
- ); -}; - -export default CustomTable; + // console.log("columnsss", columns, rows) + const dispatch = useDispatch() + const [anchorEl, setAnchorEl] = React.useState(null) + const [selectedRow, setSelectedRow] = React.useState(null) + const open = Boolean(anchorEl) + + const handleClick = (event: React.MouseEvent, row: Row) => { + setAnchorEl(event.currentTarget) + setSelectedRow(row) // Ensure the row data is set + } + + const handleClose = () => { + setAnchorEl(null) + } + + const isImage = (value: any) => { + if (typeof value === "string") { + return value.startsWith("http") || value.startsWith("data:image") // Check for URL or base64 image + } + return false + } + + const handleDeleteButton = (id: string | undefined) => { + if (!id) console.error("ID not found", id) + + dispatch(deleteAdmin(id || "")) + setDeleteModal(false) // Close the modal only after deletion + handleClose() + } + + return ( + + + + + {columns.map((column) => ( + + {column.label} + + ))} + + + + {rows.map((row, rowIndex) => ( + + {columns.map((column) => ( + + {isImage(row[column.id]) ? ( + Row + ) : column.id !== "action" ? ( + row[column.id] + ) : ( + { + handleClick(e, row) + setRowData(row) // Store the selected row + }} + > + + + )} + + ))} + + ))} + +
+ {open && ( + + + + + + {deleteModal && ( + + handleDeleteButton(selectedRow?.id) + } + open={deleteModal} + setDeleteModal={setDeleteModal} + id={selectedRow?.id} + /> + )} + + + )} +
+ ) +} + +export default CustomTable \ No newline at end of file diff --git a/src/components/MenuContent/index.tsx b/src/components/MenuContent/index.tsx index c38a4e3..d1b724c 100644 --- a/src/components/MenuContent/index.tsx +++ b/src/components/MenuContent/index.tsx @@ -1,76 +1,95 @@ -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import Stack from '@mui/material/Stack'; -import HomeRoundedIcon from '@mui/icons-material/HomeRounded'; -import AnalyticsRoundedIcon from '@mui/icons-material/AnalyticsRounded'; -import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; -import { Link, useLocation } from 'react-router-dom'; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import Stack from "@mui/material/Stack"; +import HomeRoundedIcon from "@mui/icons-material/HomeRounded"; +import AnalyticsRoundedIcon from "@mui/icons-material/AnalyticsRounded"; +import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; +import { Link, useLocation } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { RootState } from "../../redux/store/store"; -const mainListItems = [ - { - text: 'Home', - icon: , - url: '/panel/dashboard', - }, - { - text: 'Vehicles', - icon: , - url: '/panel/vehicles', - }, - //created by Eknnor and Jaanvi - { - text: 'Admin List', - icon: , - url: '/panel/adminlist', - }, +const baseMenuItems = [ + { + text: "Home", + icon: , + url: "/panel/dashboard", + }, + { + text: "Vehicles", + icon: , + url: "/panel/vehicles", + }, + //created by Eknnor and Jaanvi +]; + +//Eknoor singh and Jaanvi +//date:- 12-Feb-2025 +//Made a different variable for super admin to access all the details. + +const superAdminOnlyItems = [ + { + text: "Admin List", + icon: , + url: "/panel/adminlist", + }, ]; type PropType = { - hidden: boolean; + hidden: boolean; }; export default function MenuContent({ hidden }: PropType) { - const location = useLocation(); + const location = useLocation(); + const userRole = useSelector((state: RootState) => state.auth.user?.role); - return ( - - - {mainListItems.map((item, index) => ( - - {/* Wrap ListItemButton with Link to enable routing */} - - - {item.icon} - - - - - ))} - - - ); + const mainListItems = [ + ...baseMenuItems, + ...(userRole === "superadmin" ? superAdminOnlyItems : []), + ]; + + return ( + + + {mainListItems.map((item, index) => ( + + {/* Wrap ListItemButton with Link to enable routing */} + + + {item.icon} + + + + + ))} + + + ); } diff --git a/src/components/Modals/DeleteModal/DeleteModal.tsx b/src/components/Modals/DeleteModal/DeleteModal.tsx index 5e242d3..716215d 100644 --- a/src/components/Modals/DeleteModal/DeleteModal.tsx +++ b/src/components/Modals/DeleteModal/DeleteModal.tsx @@ -1,77 +1,84 @@ -import { Box, Button, Modal, Typography } from '@mui/material'; -import { MouseEventHandler } from 'react'; +import { Box, Button, Modal, Typography } from "@mui/material"; type Props = { - open: boolean; - setDeleteModal: Function; - handleDelete: MouseEventHandler; + open: boolean; + setDeleteModal: Function; + handleDelete: (id: string | undefined) => void; + id?: string | undefined; }; const style = { - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: 330, - bgcolor: 'background.paper', - borderRadius: 1.5, - boxShadow: 24, - p: 3, + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 330, + bgcolor: "background.paper", + borderRadius: 1.5, + boxShadow: 24, + p: 3, }; -const btnStyle = { py: 1, px: 5, width: '50%', textTransform: 'capitalize' }; +const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize" }; export default function DeleteModal({ - open, - setDeleteModal, - handleDelete, + open, + setDeleteModal, + handleDelete, + id, }: Props) { - return ( - - - - Delete Record - - - Are you sure you want to delete this record? - - - - - - - - ); + // console.log("DeleteModal opened with ID:", id) + + return ( + + + + Delete Record + + + Are you sure you want to delete this record? + + + + + + + + ); } diff --git a/src/components/OptionsMenu/index.tsx b/src/components/OptionsMenu/index.tsx index 43a33fe..7b42a33 100644 --- a/src/components/OptionsMenu/index.tsx +++ b/src/components/OptionsMenu/index.tsx @@ -11,11 +11,13 @@ import LogoutRoundedIcon from "@mui/icons-material/LogoutRounded"; import MoreVertRoundedIcon from "@mui/icons-material/MoreVertRounded"; import MenuButton from "../MenuButton"; import { Avatar } from "@mui/material"; +import { useNavigate } from "react-router-dom"; const MenuItem = styled(MuiMenuItem)({ margin: "2px 0", }); + export default function OptionsMenu({ avatar }: { avatar?: boolean }) { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); @@ -25,6 +27,10 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) { const handleClose = () => { setAnchorEl(null); }; + const navigate = useNavigate(); + const handleProfile = () =>{ + navigate("/auth/profile"); + } return ( - Profile + + + Profile My account Add another account diff --git a/src/components/SideMenu/index.tsx b/src/components/SideMenu/index.tsx index 1ede6a6..e18ee4e 100644 --- a/src/components/SideMenu/index.tsx +++ b/src/components/SideMenu/index.tsx @@ -1,93 +1,113 @@ -import { styled } from '@mui/material/styles'; -import Avatar from '@mui/material/Avatar'; -import MuiDrawer, { drawerClasses } from '@mui/material/Drawer'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import Typography from '@mui/material/Typography'; -import MenuContent from '../MenuContent'; -import OptionsMenu from '../OptionsMenu'; -import React from 'react'; -import { ArrowLeftIcon, ArrowRightIcon } from '@mui/x-date-pickers'; -import { Button } from '@mui/material'; +import { styled } from "@mui/material/styles"; +import Avatar from "@mui/material/Avatar"; +import MuiDrawer, { drawerClasses } from "@mui/material/Drawer"; +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import MenuContent from "../MenuContent"; +import OptionsMenu from "../OptionsMenu"; +import React, { useEffect } from "react"; +import { ArrowLeftIcon, ArrowRightIcon } from "@mui/x-date-pickers"; +import { Button } from "@mui/material"; +import { fetchProfile } from "../../redux/slices/authSlice"; +import { AppDispatch } from "../../redux/store/store"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../../redux/reducers"; const drawerWidth = 240; const Drawer = styled(MuiDrawer)({ - width: drawerWidth, - flexShrink: 0, - boxSizing: 'border-box', - mt: 10, - [`& .${drawerClasses.paper}`]: { - width: drawerWidth, - boxSizing: 'border-box', - }, + width: drawerWidth, + flexShrink: 0, + boxSizing: "border-box", + mt: 10, + [`& .${drawerClasses.paper}`]: { + width: drawerWidth, + boxSizing: "border-box", + }, }); export default function SideMenu() { - const [open, setOpen] = React.useState(true); - return ( - - - - - - ); + const [open, setOpen] = React.useState(true); + + const dispatch = useDispatch(); + + //Created by : jaanvi and Eknoor + //date: 12-feb-25 + //Extract user profile data from Auth + const { user } = useSelector((state: RootState) => state.auth); + + useEffect(() => { + // Dispatch the fetchProfile thunk to fetch user profile data + dispatch(fetchProfile()); + }, [dispatch]); + + return ( + + + + + + ); } diff --git a/src/lib/https.ts b/src/lib/https.ts index 093782b..5d9a5ce 100644 --- a/src/lib/https.ts +++ b/src/lib/https.ts @@ -15,28 +15,30 @@ // export default http; -import axios, { AxiosInstance } from 'axios'; +import axios, { AxiosInstance } from "axios"; // Axios instance for the production backend const backendHttp = axios.create({ - baseURL: process.env.REACT_APP_BACKEND_URL, + baseURL: process.env.REACT_APP_BACKEND_URL, }); // Axios instance for the local API const apiHttp = axios.create({ - baseURL: "http://localhost:5000/api", + baseURL: "http://localhost:5000/api", }); - // Add interceptors to both instances const addAuthInterceptor = (instance: AxiosInstance) => { - instance.interceptors.request.use((config) => { - const authToken = localStorage.getItem('authToken'); - if (authToken) { - config.headers.Authorization = authToken; - } - return config; - }); + instance.interceptors.request.use((config) => { + const authToken = localStorage.getItem("authToken"); + if (authToken) { + //Created by : jaanvi and Eknoor + //date: 12-feb-25 + //changes in token fetching + config.headers.Authorization = `Bearer ${authToken}`; + } + return config; + }); }; addAuthInterceptor(backendHttp); diff --git a/src/pages/AdminList/index.tsx b/src/pages/AdminList/index.tsx index 7c05c0c..6f6492f 100644 --- a/src/pages/AdminList/index.tsx +++ b/src/pages/AdminList/index.tsx @@ -1,19 +1,14 @@ import React, { useEffect, useState } from "react"; import { Box, Button, Typography } from "@mui/material"; import AddEditCategoryModal from "../../components/AddEditCategoryModal"; -// import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form"; -import CustomTable from "../../components/CustomTable"; -import DeleteModal from "../../components/Modals/DeleteModal/DeleteModal"; +import CustomTable, { Column } from "../../components/CustomTable"; import { useDispatch, useSelector } from "react-redux"; import { adminList, updateAdmin } from "../../redux/slices/authSlice"; import { AppDispatch, RootState } from "../../redux/store/store"; // Import RootState for selector -// Sample data for categories - export default function AdminList() { const [modalOpen, setModalOpen] = useState(false); - const [editRow, setEditRow] = useState(null); const { reset } = useForm(); const [deleteModal, setDeleteModal] = React.useState(false); @@ -31,7 +26,6 @@ export default function AdminList() { const handleClickOpen = () => { setModalOpen(true); - setEditRow(null); }; const handleCloseModal = () => { @@ -39,20 +33,16 @@ export default function AdminList() { reset(); }; - const handleDelete = () => { - setDeleteModal(false); - }; - //By Jaanvi : Edit feature :: 11-feb-25 const handleUpdate = async (id: string, name: string, role: string) => { try { await dispatch(updateAdmin({ id, name, role })); - dispatch(adminList()); // Fetch updated admins list after update + await dispatch(adminList()); // Fetch updated admins list after update } catch (error) { console.error("Update failed", error); } }; - const categoryColumns = [ + const categoryColumns: Column[] = [ { id: "srno", label: "Sr No" }, { id: "name", label: "Name" }, { id: "role", label: "Role" }, @@ -66,10 +56,10 @@ export default function AdminList() { admin: { id: string; name: string; role: string }, index: number ) => ({ - srno: index + 1, id: admin?.id, + srno: index + 1, name: admin?.name, - role: admin?.role || "N/A", + role: admin?.role, }) ) : []; @@ -108,8 +98,8 @@ export default function AdminList() { @@ -119,11 +109,6 @@ export default function AdminList() { editRow={rowData} handleUpdate={handleUpdate} /> - ); } diff --git a/src/pages/ProfilePage/index.tsx b/src/pages/ProfilePage/index.tsx new file mode 100644 index 0000000..97849b6 --- /dev/null +++ b/src/pages/ProfilePage/index.tsx @@ -0,0 +1,103 @@ +import React, { useEffect } from "react"; +import { + Container, + Typography, + CircularProgress, + Card, + CardContent, + Grid, + Avatar, + Box, +} from "@mui/material"; + +import { useDispatch, useSelector } from "react-redux"; +import { AppDispatch, RootState } from "../../redux/store/store"; +import { fetchProfile } from "../../redux/slices/authSlice"; +const ProfilePage = () => { + // const dispatch = useDispatch(); + + const dispatch = useDispatch(); + //Created by : jaanvi and Eknoor + //date: 12-feb-25 + //Extract user profile data from Auth + // Extract profile, loading, and error state from Redux + const { user, isLoading, error } = useSelector( + (state: RootState) => state.auth + ); + + useEffect(() => { + // Dispatch the getProfile thunk to fetch user profile data + + dispatch(fetchProfile()); + }, [dispatch]); + + if (isLoading) { + return ( + + + + ); + } + + if (error) { + return ( + + + Error: {error} + + + ); + } + + if (!user) { + return ( + + No profile data available + + ); + } + + return ( + + + Profile + + + + + + + + + + {user?.name || "N/A"} + + + Email: {user?.email || "N/A"} + + + Phone: {user?.phone || "N/A"} + + + Role: {user?.role || "N/A"} + + + + + + + ); +}; + +export default ProfilePage; \ No newline at end of file diff --git a/src/redux/slices/authSlice.ts b/src/redux/slices/authSlice.ts index 226ffdb..6f3b444 100644 --- a/src/redux/slices/authSlice.ts +++ b/src/redux/slices/authSlice.ts @@ -4,25 +4,49 @@ import { backendHttp, apiHttp } from "../../lib/https"; import { toast } from "react-toastify"; // Define types for state +//By jaanvi:: 12-feb-25 + interface User { + token: string | null; + map( + arg0: ( + admin: { name: any; role: any; email: any; phone: any }, + index: number + ) => { srno: number; name: any; role: any; email: any; phone: any } + ): unknown; id: string; + name: string; email: string; + role: string; + phone: string; } + interface Admin { id: string; name: string; role: string; } - interface AuthState { user: User | null; admins: Admin[]; isAuthenticated: boolean; isLoading: boolean; - error: string | null; + error: object | string | null; + token: string | null; // Store token in Redux state + } +const initialState: AuthState = { + user: null, + admins: [], + isAuthenticated: false, + isLoading: false, + error: null, + token: null, //Intialize token + +}; + // Async thunk for login export const loginUser = createAsyncThunk< User, @@ -30,11 +54,14 @@ export const loginUser = createAsyncThunk< { rejectValue: string } >("auth/login", async ({ email, password }, { rejectWithValue }) => { try { - const response = await backendHttp.post("admin/login", { + const response = await apiHttp.post("auth/login", { email, password, }); - localStorage.setItem("authToken", response.data?.data?.token); // Save token + //changes By Jaanvi + //Store token + localStorage.setItem("authToken", response.data?.data?.token); + toast.success(response.data?.message); return response.data; } catch (error: any) { @@ -44,6 +71,41 @@ export const loginUser = createAsyncThunk< } }); +//created by jaanvi +//date: 12-feb-25 +//function for fetching Profile data function +export const fetchProfile = createAsyncThunk< + User, + void, + { rejectValue: string } +>("auth/fetchProfile", async (_, { rejectWithValue }) => { + try { + //Get the token from localStorage + const token = localStorage.getItem("authToken"); + if (!token) throw new Error("No token found"); + + const response = await apiHttp.get("/auth/profile", { + headers: { Authorization: `Bearer ${token}` }, // Ensuring 'Bearer' prefix + }); + + console.log("API Response:", response.data); // Debugging + + if (!response.data?.data) { + throw new Error("Invalid API response"); + } + + return response.data.data; + } catch (error: any) { + console.error( + "Profile Fetch Error:", + error.response?.data || error.message + ); + return rejectWithValue( + error.response?.data?.message || "An error occurred" + ); + } +}); + // Async thunk for register export const registerUser = createAsyncThunk< User, @@ -63,6 +125,10 @@ export const registerUser = createAsyncThunk< } }); +//created by Eknoor and jaanvi +//date: 10-Feb-2025 +//Fetching list of admins + export const adminList = createAsyncThunk< Admin[], void, @@ -70,11 +136,12 @@ export const adminList = createAsyncThunk< >("/auth", async (_, { rejectWithValue }) => { try { const response = await apiHttp.get("/auth"); + console.log(response?.data?.data); return response?.data?.data?.map( (admin: { id: string; name: string; role: string }) => ({ id: admin.id, - name: admin.name, - role: admin.role || "N/A", + name: admin?.name, + role: admin?.role || "N/A", }) ); } catch (error: any) { @@ -84,8 +151,26 @@ export const adminList = createAsyncThunk< } }); -//By Jaanvi : Edit feature :: 11-feb-25 -// updateAdmin Action +//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 apiHttp.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 ( @@ -105,14 +190,6 @@ export const updateAdmin = createAsyncThunk( } ); -const initialState: AuthState = { - user: null, - admins: [], - isAuthenticated: false, - isLoading: false, - error: null, -}; - const authSlice = createSlice({ name: "auth", initialState, @@ -120,6 +197,8 @@ const authSlice = createSlice({ logout: (state) => { state.user = null; state.isAuthenticated = false; + state.token = null; + localStorage.removeItem("authToken"); }, }, extraReducers: (builder) => { @@ -129,14 +208,12 @@ const authSlice = createSlice({ state.isLoading = true; state.error = null; }) - .addCase( - loginUser.fulfilled, - (state, action: PayloadAction) => { - state.isLoading = false; - state.isAuthenticated = true; - state.user = action.payload; - } - ) + .addCase(loginUser.fulfilled, (state, action) => { + state.isLoading = false; + state.isAuthenticated = true; + state.user = action.payload; + state.token = action.payload.token; // Store token in Redux + }) .addCase( loginUser.rejected, (state, action: PayloadAction) => { @@ -164,7 +241,9 @@ const authSlice = createSlice({ state.error = action.payload || "An error occurred"; } ) - // Fetch admin list + + // created by Jaanvi and Eknoor + //AdminList .addCase(adminList.pending, (state) => { state.isLoading = true; state.error = null; @@ -176,6 +255,7 @@ const authSlice = createSlice({ state.admins = action.payload; } ) + .addCase( adminList.rejected, (state, action: PayloadAction) => { @@ -183,16 +263,57 @@ const authSlice = createSlice({ state.error = action.payload || "An error occurred"; } ) - // update admin cases + + //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 ); - }) - .addCase(updateAdmin.rejected, (state) => { + state.isLoading = false; + }) + .addCase(updateAdmin.rejected, (state, action) => { + state.isLoading = false; + state.error = + action.payload || + "Something went wrong while updating Admin!!"; + }) + //Adding Cases for Profile Slice + //Pending state + .addCase(fetchProfile.pending, (state) => { + state.isLoading = true; state.error = null; + }) + //Fullfilled state + .addCase(fetchProfile.fulfilled, (state, action) => { + state.isLoading = false; + state.user = action.payload; + state.isAuthenticated = true; + }) + //Failed to load state + .addCase(fetchProfile.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload || "Failed to fetch admin profile"; }); }, }); diff --git a/src/router.tsx b/src/router.tsx index 1c45223..d947b32 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,5 +1,4 @@ import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom"; -// import useAuth from "./hooks/useAuth"; import React, { Suspense } from "react"; import LoadingComponent from "./components/Loading"; import DashboardLayout from "./layouts/DashboardLayout"; @@ -8,6 +7,11 @@ import SignUp from "./pages/Auth/SignUp"; import Dashboard from "./pages/Dashboard"; import Vehicles from "./pages/Vehicles"; import AdminList from "./pages/AdminList"; +import ProfilePage from "./pages/ProfilePage"; +// import SuperAdminRouter from "./components/SuperAdminRoute"; +// SuperAdminRouter // Fix: single import with correct path + +import SuperAdminRouter from "./superAdminRouter"; function ProtectedRoute({ caps, @@ -37,6 +41,7 @@ export default function AppRouter() { } /> } /> + }> } + component={ + //Eknoor singh and jaanvi + //date:- 12-Feb-2025 + //Admin list put under protected route for specific use + + + + } /> } /> + 404} /> + } /> + } + /> + 404} /> diff --git a/src/superAdminRouter.tsx b/src/superAdminRouter.tsx new file mode 100644 index 0000000..61fb144 --- /dev/null +++ b/src/superAdminRouter.tsx @@ -0,0 +1,26 @@ +//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;