ProfilePage Edit api integration and Create EditProfile Modal
This commit is contained in:
parent
e717d6c58c
commit
570faa40d4
|
@ -67,29 +67,27 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
|||
backgroundColor: "#2A2A2A",
|
||||
cursor: "pointer",
|
||||
},
|
||||
// Make the "Action" header cell sticky
|
||||
|
||||
"&.action-cell": {
|
||||
right: 0,
|
||||
zIndex: 11, // Higher z-index to ensure it stays above other headers
|
||||
zIndex: 11,
|
||||
boxShadow: "-4px 0 8px rgba(0, 0, 0, 0.1)",
|
||||
},
|
||||
},
|
||||
[`&.${tableCellClasses.body}`]: {
|
||||
fontSize: "16px",
|
||||
padding: "12px 16px",
|
||||
borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
|
||||
borderBottom: "none",
|
||||
color: "#333333",
|
||||
transition: "background-color 0.2s ease",
|
||||
fontWeight:500,
|
||||
// Make the "Action" body cell sticky
|
||||
fontWeight: 500,
|
||||
"&.action-cell": {
|
||||
position: "sticky",
|
||||
right: 0,
|
||||
zIndex: 2,
|
||||
boxShadow: "-4px 0 8px rgba(0, 0, 0, 0.1)",
|
||||
backgroundColor: "#DFECF1", // Match row background
|
||||
backgroundColor: "#DFECF1",
|
||||
"&:hover": {
|
||||
backgroundColor: "#D0E1E9", // Match row hover background
|
||||
backgroundColor: "#D0E1E9",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -100,11 +98,11 @@ const StyledTableRow = styled(TableRow)(({ theme }) => ({
|
|||
"&:hover": {
|
||||
backgroundColor: "#D0E1E9",
|
||||
},
|
||||
"& td, & th": {
|
||||
borderColor: "#454545",
|
||||
borderWidth: "1px",
|
||||
borderBottom: "1px solid #454545",
|
||||
},
|
||||
// "& td, & th": {
|
||||
// borderColor: "#454545",
|
||||
// borderWidth: "1px",
|
||||
// // borderBottom: "1px solid #454545",
|
||||
// },
|
||||
}));
|
||||
|
||||
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
|
||||
|
|
315
src/components/Modals/EditProfileModal/editProfileModal.tsx
Normal file
315
src/components/Modals/EditProfileModal/editProfileModal.tsx
Normal file
|
@ -0,0 +1,315 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Box, Button, Typography, Modal } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import {
|
||||
CustomIconButton,
|
||||
CustomTextField,
|
||||
} from "../../AddUserModal/styled.css";
|
||||
|
||||
interface EditProfileModalProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleUpdate: (
|
||||
name: string,
|
||||
phone: string,
|
||||
bio?: string,
|
||||
profilePhoto?: string | null
|
||||
) => void;
|
||||
editUser: any;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
phone: string;
|
||||
bio?: string;
|
||||
profilePhoto?: string | null;
|
||||
}
|
||||
|
||||
|
||||
const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
||||
open,
|
||||
handleClose,
|
||||
handleUpdate,
|
||||
editUser,
|
||||
}) => {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
reset,
|
||||
} = useForm<FormData>();
|
||||
|
||||
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (editUser) {
|
||||
setValue("name", editUser.name || "");
|
||||
setValue("phone", editUser.phone || "");
|
||||
setValue("bio", editUser.bio || "");
|
||||
setImagePreview(editUser.profilePhoto || null);
|
||||
}
|
||||
}, [editUser, setValue]);
|
||||
|
||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const imageUrl = URL.createObjectURL(file);
|
||||
setImagePreview(imageUrl);
|
||||
setValue("profilePhoto", imageUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
handleClose();
|
||||
|
||||
};
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
console.log("Form Data:-----", data);
|
||||
handleUpdate(data.name, data.phone, data.bio, data.profilePhoto);
|
||||
reset();
|
||||
handleModalClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={(e, reason) => {
|
||||
if (reason !== "backdropClick") handleModalClose();
|
||||
}}
|
||||
aria-labelledby="edit-profile-modal"
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: 500,
|
||||
boxShadow: 24,
|
||||
p: 0,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#000000",
|
||||
color: "#D0E1E9",
|
||||
padding: "20px 24px",
|
||||
borderRadius: "10px 10px 0 0",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" fontWeight={600} fontSize="16px">
|
||||
Edit Profile
|
||||
</Typography>
|
||||
<CustomIconButton onClick={handleModalClose}>
|
||||
<CloseIcon />
|
||||
</CustomIconButton>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#b5c4cb",
|
||||
padding: "20px",
|
||||
borderRadius: "0 0 10px 10px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* Name Field */}
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color="#000000"
|
||||
>
|
||||
Full Name
|
||||
</Typography>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Name is required",
|
||||
minLength: {
|
||||
value: 3,
|
||||
message:
|
||||
"Minimum 3 characters required",
|
||||
},
|
||||
maxLength: {
|
||||
value: 30,
|
||||
message:
|
||||
"Maximum 30 characters allowed",
|
||||
},
|
||||
pattern: {
|
||||
value: /^[A-Za-z\s]+$/,
|
||||
message:
|
||||
"Only letters and spaces allowed",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Enter your name"
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Phone Field */}
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color="#000000"
|
||||
>
|
||||
Phone Number
|
||||
</Typography>
|
||||
<Controller
|
||||
name="phone"
|
||||
control={control}
|
||||
rules={{
|
||||
required: "Phone number is required",
|
||||
validate: (value) => {
|
||||
if (!/^[0-9]*$/.test(value))
|
||||
return "Only numbers are allowed";
|
||||
if (value.length < 6)
|
||||
return "Must be at least 6 digits";
|
||||
if (value.length > 14)
|
||||
return "Must be at most 14 digits";
|
||||
return true;
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Enter phone number"
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
error={!!errors.phone}
|
||||
helperText={errors.phone?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Bio Field */}
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color="#000000"
|
||||
>
|
||||
Bio
|
||||
</Typography>
|
||||
<Controller
|
||||
name="bio"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<CustomTextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder="Add your bio details here..."
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Upload Image */}
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color="#000000"
|
||||
>
|
||||
Upload Profile Photo
|
||||
</Typography>
|
||||
<Button
|
||||
component="label"
|
||||
sx={{
|
||||
backgroundColor: "#000000",
|
||||
color: "#D0E1E9",
|
||||
borderRadius: "8px",
|
||||
mt: 1,
|
||||
"&:hover": { backgroundColor: "#454545" },
|
||||
}}
|
||||
>
|
||||
Choose Image
|
||||
<input
|
||||
type="file"
|
||||
hidden
|
||||
accept="image/*"
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
</Button>
|
||||
{imagePreview && (
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" color="#000000">
|
||||
Preview (
|
||||
{imagePreview.startsWith("blob")
|
||||
? "New"
|
||||
: "Existing"}
|
||||
):
|
||||
</Typography>
|
||||
<img
|
||||
src={imagePreview}
|
||||
alt="Profile Preview"
|
||||
style={{
|
||||
maxWidth: "40%",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Footer */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
mt: 3,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
sx={{
|
||||
backgroundColor: "#000000",
|
||||
color: "#D0E1E9",
|
||||
borderRadius: "8px",
|
||||
fontSize: "16px",
|
||||
width: "140px",
|
||||
"&:hover": { backgroundColor: "#454545" },
|
||||
}}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditProfileModal;
|
|
@ -169,3 +169,435 @@ export default function ViewModal({ open, setViewModal, id }: Props) {
|
|||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
// import {
|
||||
// Box,
|
||||
// Button,
|
||||
// Modal,
|
||||
// Typography,
|
||||
// Tabs,
|
||||
// Tab,
|
||||
// Divider,
|
||||
// Grid,
|
||||
// } from "@mui/material";
|
||||
// import { AppDispatch, RootState } from "../../../redux/store/store";
|
||||
// import { useDispatch, useSelector } from "react-redux";
|
||||
// import { useEffect, useState } from "react";
|
||||
// import CloseIcon from "@mui/icons-material/Close";
|
||||
// import { managerList } from "../../../redux/slices/managerSlice";
|
||||
// import {
|
||||
// AsyncThunkAction,
|
||||
// ThunkDispatch,
|
||||
// UnknownAction,
|
||||
// } from "@reduxjs/toolkit";
|
||||
|
||||
// type Props = {
|
||||
// open: boolean;
|
||||
// setViewModal: Function;
|
||||
// handleView: (id: string | undefined) => void;
|
||||
// id?: string;
|
||||
// };
|
||||
|
||||
// interface TabPanelProps {
|
||||
// children?: React.ReactNode;
|
||||
// index: number;
|
||||
// value: number;
|
||||
// }
|
||||
|
||||
// const style = {
|
||||
// position: "absolute",
|
||||
// top: "50%",
|
||||
// left: "50%",
|
||||
// transform: "translate(-50%, -50%)",
|
||||
// width: 600,
|
||||
// borderRadius: 2,
|
||||
// boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
|
||||
// p: 0,
|
||||
// display: "flex",
|
||||
// flexDirection: "column",
|
||||
// alignItems: "center",
|
||||
// };
|
||||
|
||||
// function TabPanel(props: TabPanelProps) {
|
||||
// const { children, value, index, ...other } = props;
|
||||
// return (
|
||||
// <div
|
||||
// role="tabpanel"
|
||||
// hidden={value !== index}
|
||||
// id={`tabpanel-${index}`}
|
||||
// aria-labelledby={`tab-${index}`}
|
||||
// {...other}
|
||||
// >
|
||||
// {value === index && <Box sx={{ p: 3 }}>{children}</Box>}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default function ViewModal({ open, setViewModal, id }: Props) {
|
||||
// const { admins } = useSelector((state: RootState) => state.adminReducer);
|
||||
// const { managers } = useSelector(
|
||||
// (state: RootState) => state.managerReducer
|
||||
// );
|
||||
// const [selectedAdmin, setSelectedAdmin] = useState<any>(null);
|
||||
// const selectedManager = managers.find(
|
||||
// (manager) => String(manager.id) === String(selectedAdmin?.id)
|
||||
// );
|
||||
|
||||
// const dispatch = useDispatch();
|
||||
|
||||
// const [tabValue, setTabValue] = useState(0);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (id) {
|
||||
// const admin = admins.find((admin) => admin.id === id);
|
||||
// setSelectedAdmin(admin);
|
||||
// }
|
||||
// }, [id, admins]);
|
||||
// useEffect(() => {
|
||||
// if (open) {
|
||||
// //dispatch(managerList());
|
||||
// }
|
||||
// }, [open, dispatch]);
|
||||
// const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
// setTabValue(newValue);
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Modal
|
||||
// open={open}
|
||||
// aria-labelledby="modal-title"
|
||||
// aria-describedby="modal-description"
|
||||
// >
|
||||
// <Box sx={style}>
|
||||
// <Box
|
||||
// sx={{
|
||||
// display: "flex",
|
||||
// justifyContent: "space-between",
|
||||
// alignItems: "center",
|
||||
// backgroundColor: "#000000",
|
||||
// color: "#D0E1E9",
|
||||
// padding: "20px 24px",
|
||||
// borderRadius: "10px 10px 0 0",
|
||||
// width: "100%",
|
||||
// boxSizing: "border-box",
|
||||
// }}
|
||||
// >
|
||||
// <Typography
|
||||
// id="modal-title"
|
||||
// variant="h5"
|
||||
// fontWeight="bold"
|
||||
// sx={{ width: "100%" }}
|
||||
// >
|
||||
// <Box
|
||||
// sx={{
|
||||
// display: "flex",
|
||||
// alignItems: "center",
|
||||
// justifyContent: "space-between",
|
||||
// width: "100%",
|
||||
// }}
|
||||
// >
|
||||
// <Box
|
||||
// sx={{
|
||||
// flex: 1,
|
||||
// textAlign: "center",
|
||||
// color: "#D0E1E9",
|
||||
// }}
|
||||
// >
|
||||
// {selectedAdmin?.name || "Admin"}'s Details
|
||||
// </Box>
|
||||
// <Box
|
||||
// onClick={() => setViewModal(false)}
|
||||
// sx={{
|
||||
// cursor: "pointer",
|
||||
// display: "flex",
|
||||
// alignItems: "center",
|
||||
// color: "#D0E1E9",
|
||||
// }}
|
||||
// >
|
||||
// <CloseIcon />
|
||||
// </Box>
|
||||
// </Box>
|
||||
// </Typography>
|
||||
// </Box>
|
||||
|
||||
// <Box sx={{ width: "100%", backgroundColor: "#b5c4cb" }}>
|
||||
// <Tabs
|
||||
// value={tabValue}
|
||||
// onChange={handleTabChange}
|
||||
// aria-label="admin details tabs"
|
||||
// sx={{
|
||||
// backgroundColor: "#000000",
|
||||
// "& .MuiTabs-indicator": {
|
||||
// backgroundColor: "#D0E1E9",
|
||||
// },
|
||||
// }}
|
||||
// >
|
||||
// <Tab
|
||||
// label="Admin Details"
|
||||
// sx={{
|
||||
// color: "#D0E1E9",
|
||||
// "&.Mui-selected": { color: "#D0E1E9" },
|
||||
// }}
|
||||
// />
|
||||
// <Tab
|
||||
// label="Manager Details"
|
||||
// sx={{
|
||||
// color: "#D0E1E9",
|
||||
// "&.Mui-selected": { color: "#D0E1E9" },
|
||||
// }}
|
||||
// />
|
||||
// <Tab
|
||||
// label="Charging Stations"
|
||||
// sx={{
|
||||
// color: "#D0E1E9",
|
||||
// "&.Mui-selected": { color: "#D0E1E9" },
|
||||
// }}
|
||||
// />
|
||||
// <Tab
|
||||
// label="User Details"
|
||||
// sx={{
|
||||
// color: "#D0E1E9",
|
||||
// "&.Mui-selected": { color: "#D0E1E9" },
|
||||
// }}
|
||||
// />
|
||||
// </Tabs>
|
||||
|
||||
// <TabPanel value={tabValue} index={0}>
|
||||
// {selectedAdmin ? (
|
||||
// <Grid container spacing={3}>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Name:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedAdmin.name}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Phone:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedAdmin.phone}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Email:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedAdmin.email}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Address:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedAdmin?.Admins?.[0]
|
||||
// ?.registeredAddress ?? "N/A"}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// </Grid>
|
||||
// ) : (
|
||||
// <Typography align="center" color="#454545">
|
||||
// No admin found with this ID
|
||||
// </Typography>
|
||||
// )}
|
||||
// </TabPanel>
|
||||
|
||||
// <TabPanel value={tabValue} index={1}>
|
||||
// {selectedManager ? (
|
||||
// <Grid container spacing={3}>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Manager Name:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedManager.name ?? "N/A"}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Manager Email:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedManager.email ?? "N/A"}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Phone:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedManager.phone ?? "N/A"}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item xs={6}>
|
||||
// <Typography variant="body1" color="#000000">
|
||||
// <strong>Station Name:</strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// {selectedManager.chargingStation
|
||||
// ?.name ?? "N/A"}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// </Grid>
|
||||
// ) : (
|
||||
// <Typography align="center" color="#454545">
|
||||
// No manager details available
|
||||
// </Typography>
|
||||
// )}
|
||||
// </TabPanel>
|
||||
|
||||
// <TabPanel value={tabValue} index={2}>
|
||||
// {selectedAdmin?.chargingStations?.length > 0 ? (
|
||||
// <Grid container spacing={3}>
|
||||
// {selectedAdmin.chargingStations.map(
|
||||
// (station: any, index: number) => (
|
||||
// <Grid item xs={12} key={index}>
|
||||
// <Typography
|
||||
// variant="body1"
|
||||
// color="#000000"
|
||||
// >
|
||||
// <strong>
|
||||
// Station {index + 1}:
|
||||
// </strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// Name:{" "}
|
||||
// {station.name ?? "N/A"}
|
||||
// </Typography>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// Location:{" "}
|
||||
// {station.location ?? "N/A"}
|
||||
// </Typography>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// Status:{" "}
|
||||
// {station.status ?? "N/A"}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// {index <
|
||||
// selectedAdmin.chargingStations
|
||||
// .length -
|
||||
// 1 && (
|
||||
// <Divider sx={{ my: 2 }} />
|
||||
// )}
|
||||
// </Grid>
|
||||
// )
|
||||
// )}
|
||||
// </Grid>
|
||||
// ) : (
|
||||
// <Typography align="center" color="#454545">
|
||||
// No charging stations assigned
|
||||
// </Typography>
|
||||
// )}
|
||||
// </TabPanel>
|
||||
|
||||
// <TabPanel value={tabValue} index={3}>
|
||||
// {selectedAdmin?.users?.length > 0 ? (
|
||||
// <Grid container spacing={3}>
|
||||
// {selectedAdmin.users.map(
|
||||
// (user: any, index: number) => (
|
||||
// <Grid item xs={12} key={index}>
|
||||
// <Typography
|
||||
// variant="body1"
|
||||
// color="#000000"
|
||||
// >
|
||||
// <strong>
|
||||
// User {index + 1}:
|
||||
// </strong>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// Name: {user.name ?? "N/A"}
|
||||
// </Typography>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// Email: {user.email ?? "N/A"}
|
||||
// </Typography>
|
||||
// <Typography
|
||||
// variant="body2"
|
||||
// color="#454545"
|
||||
// >
|
||||
// Role: {user.role ?? "N/A"}
|
||||
// </Typography>
|
||||
// </Typography>
|
||||
// {index <
|
||||
// selectedAdmin.users.length -
|
||||
// 1 && (
|
||||
// <Divider sx={{ my: 2 }} />
|
||||
// )}
|
||||
// </Grid>
|
||||
// )
|
||||
// )}
|
||||
// </Grid>
|
||||
// ) : (
|
||||
// <Typography align="center" color="#454545">
|
||||
// No users assigned
|
||||
// </Typography>
|
||||
// )}
|
||||
// </TabPanel>
|
||||
|
||||
// <Box
|
||||
// sx={{
|
||||
// display: "flex",
|
||||
// justifyContent: "flex-end",
|
||||
// p: 2,
|
||||
// borderRadius: "0 0 10px 10px",
|
||||
// }}
|
||||
// >
|
||||
// <Button
|
||||
// onClick={() => setViewModal(false)}
|
||||
// sx={{
|
||||
// backgroundColor: "#000000",
|
||||
// color: "#D0E1E9",
|
||||
// borderRadius: "8px",
|
||||
// fontSize: "16px",
|
||||
// width: "117px",
|
||||
// "&:hover": { backgroundColor: "#454545" },
|
||||
// }}
|
||||
// >
|
||||
// Close
|
||||
// </Button>
|
||||
// </Box>
|
||||
// </Box>
|
||||
// </Box>
|
||||
// </Modal>
|
||||
// );
|
||||
// }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect } from "react";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
|
@ -14,19 +14,60 @@ import {
|
|||
} from "@mui/material";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppDispatch, RootState } from "../../redux/store/store";
|
||||
import { fetchAdminProfile } from "../../redux/slices/profileSlice";
|
||||
import {
|
||||
fetchAdminProfile,
|
||||
updateProfile,
|
||||
} from "../../redux/slices/profileSlice";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import EditProfileModal from "../../components/Modals/EditProfileModal/editProfileModal";
|
||||
import { CustomIconButton } from "../../components/AddUserModal/styled.css";
|
||||
|
||||
const ProfilePage = () => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const { user, isLoading } = useSelector(
|
||||
(state: RootState) => state?.profileReducer
|
||||
const { user, loading } = useSelector(
|
||||
(state: RootState) => state.profileReducer
|
||||
);
|
||||
|
||||
const [openEditModal, setOpenEditModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchAdminProfile());
|
||||
}, [dispatch]);
|
||||
|
||||
if (isLoading) {
|
||||
const handleOpenEditModal = () => {
|
||||
setOpenEditModal(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpenEditModal(false);
|
||||
};
|
||||
|
||||
const handleUpdate = (
|
||||
name: string,
|
||||
phone: string,
|
||||
bio?: string,
|
||||
profilePhoto?: string | null
|
||||
) => {
|
||||
console.log("Dispatching updateProfile...");
|
||||
dispatch(updateProfile({ name, phone, bio, profilePhoto }));
|
||||
};
|
||||
|
||||
|
||||
// Memoizing the user data for optimization
|
||||
const displayUser = useMemo(
|
||||
() => ({
|
||||
name: user?.name || "N/A",
|
||||
email: user?.email || "N/A",
|
||||
phone: user?.phone || "N/A",
|
||||
bio: user?.bio || "No bio available.",
|
||||
userType: user?.userType || "N/A",
|
||||
profilePhoto: user?.profilePhoto || "/avatar.png", // Default image path
|
||||
}),
|
||||
[user]
|
||||
);
|
||||
|
||||
// Show loading indicator if data is being fetched
|
||||
if (loading) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -42,12 +83,7 @@ const ProfilePage = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
sx={{
|
||||
py:1,
|
||||
|
||||
}}
|
||||
>
|
||||
<Container sx={{ py: 1 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
|
@ -61,42 +97,68 @@ const ProfilePage = () => {
|
|||
p: { xs: 2, sm: 3 },
|
||||
mx: "auto",
|
||||
backgroundColor: "#000000",
|
||||
|
||||
}}
|
||||
// sx={{
|
||||
// width: "1132px",
|
||||
// height: "331px",
|
||||
// gap: "24px",
|
||||
// borderRadius: "12px",
|
||||
// padding: "16px",
|
||||
// maxWidth: "100%",
|
||||
// margin: "0 auto",
|
||||
// backgroundColor: "#1C1C1C",
|
||||
// }}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<Avatar
|
||||
alt="User Avatar"
|
||||
src="/avatar.png"
|
||||
sx={{ width: 60, height: 60 }}
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
position="relative"
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
sx={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
"&:hover .edit-icon": { opacity: 1 },
|
||||
"&:hover .avatar-img": { opacity: 0.4 },
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
alt="User Avatar"
|
||||
src={displayUser.profilePhoto}
|
||||
sx={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
transition: "opacity 0.3s",
|
||||
}}
|
||||
className="avatar-img"
|
||||
/>
|
||||
<CustomIconButton
|
||||
onClick={handleOpenEditModal}
|
||||
className="edit-icon"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
color: "white",
|
||||
opacity: 0,
|
||||
transition: "opacity 0.3s",
|
||||
}}
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</CustomIconButton>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ color: "#D0E1E9", fontWeight: 500 }}
|
||||
>
|
||||
{user?.name || "No Admin"}
|
||||
{displayUser.name}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: "#D9D8D8" }}
|
||||
>
|
||||
{user?.userType || "N/A"}
|
||||
{displayUser.userType}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Divider
|
||||
flexItem
|
||||
sx={{ backgroundColor: "rgba(32, 32, 32, 0.5)" }}
|
||||
|
@ -117,80 +179,72 @@ const ProfilePage = () => {
|
|||
>
|
||||
Personal Information
|
||||
</Typography>
|
||||
{/* <Link
|
||||
<Link
|
||||
component="button"
|
||||
variant="body1"
|
||||
href="/edit-profile"
|
||||
color="#52ACDF"
|
||||
onClick={handleOpenEditModal}
|
||||
>
|
||||
Edit
|
||||
</Link> */}
|
||||
</Link>
|
||||
</Stack>
|
||||
|
||||
<Grid container spacing={3} >
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: "#D0E1E9",
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
sx={{ color: "#D0E1E9", fontWeight: 500 }}
|
||||
>
|
||||
Full Name:
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#D9D8D8">
|
||||
{user?.name || "N/A"}
|
||||
{displayUser.name}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: "#D0E1E9",
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
sx={{ color: "#D0E1E9", fontWeight: 500 }}
|
||||
>
|
||||
Phone:
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#D9D8D8">
|
||||
{user?.phone || "N/A"}
|
||||
{displayUser.phone}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: "#D0E1E9",
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
sx={{ color: "#D0E1E9", fontWeight: 500 }}
|
||||
>
|
||||
Email:
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#D9D8D8">
|
||||
{user?.email || "N/A"}
|
||||
{displayUser.email}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: "#D0E1E9",
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
sx={{ color: "#D0E1E9", fontWeight: 500 }}
|
||||
>
|
||||
Bio:
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#D9D8D8">
|
||||
{user?.bio || "No bio available."}
|
||||
{displayUser.bio}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<EditProfileModal
|
||||
open={openEditModal}
|
||||
handleClose={handleClose}
|
||||
handleUpdate={handleUpdate} // Passing the handleUpdate function to the modal
|
||||
editUser={user} // Pass the current user to pre-fill the form in modal
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import http from "../../lib/https";
|
||||
import { toast } from "sonner";
|
||||
import { string } from "prop-types";
|
||||
|
||||
interface User {
|
||||
token: string | null;
|
||||
|
@ -9,14 +10,26 @@ interface User {
|
|||
email: string;
|
||||
userType: string;
|
||||
phone: string;
|
||||
bio?: string;
|
||||
profilePhoto?: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
users: User[];
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
loading: boolean;
|
||||
error: null | string;
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
users: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
};
|
||||
|
||||
export const fetchAdminProfile = createAsyncThunk<
|
||||
User,
|
||||
void,
|
||||
|
@ -32,18 +45,45 @@ export const fetchAdminProfile = createAsyncThunk<
|
|||
|
||||
return response.data.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error Fetching Profile" + error);
|
||||
toast.error("Error Fetching Profile: " + error.message);
|
||||
return rejectWithValue(
|
||||
error?.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const initialState: AuthState = {
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
};
|
||||
export const updateProfile = createAsyncThunk<
|
||||
User,
|
||||
{
|
||||
name: string;
|
||||
phone?: string;
|
||||
bio?: string;
|
||||
profilePhoto?: string | null;
|
||||
},
|
||||
{ rejectValue: string }
|
||||
>(
|
||||
"updateProfile",
|
||||
async ({ name, phone, bio, profilePhoto }, { rejectWithValue }) => {
|
||||
try {
|
||||
const payload: any = { name };
|
||||
if (phone) payload.phone = phone;
|
||||
if (bio) payload.bio = bio;
|
||||
if (profilePhoto) payload.profilePhoto = profilePhoto;
|
||||
|
||||
const response = await http.put("/edit-profile", payload);
|
||||
console.log("-----------", response);
|
||||
|
||||
toast.success("Profile updated successfully");
|
||||
return response.data.data;
|
||||
} catch (error: any) {
|
||||
toast.error("Error updating the profile: " + error.message);
|
||||
return rejectWithValue(
|
||||
error?.response?.data?.message || "An error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const profileSlice = createSlice({
|
||||
name: "profile",
|
||||
|
@ -52,15 +92,29 @@ const profileSlice = createSlice({
|
|||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchAdminProfile.pending, (state) => {
|
||||
state.isLoading = true;
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(fetchAdminProfile.fulfilled, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.loading = false;
|
||||
state.user = action.payload;
|
||||
state.isAuthenticated = true;
|
||||
})
|
||||
.addCase(fetchAdminProfile.rejected, (state) => {
|
||||
state.isLoading = false;
|
||||
.addCase(fetchAdminProfile.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
.addCase(updateProfile.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.user = action.payload; // Update current user only
|
||||
toast.success("Profile Details updated successfully");
|
||||
})
|
||||
|
||||
.addCase(updateProfile.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
.addCase(updateProfile.pending, (state) => {
|
||||
state.loading = true;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue