From 9c7e4b9feefb4c04ea3ad92ed7e74c66fd8207dc Mon Sep 17 00:00:00 2001 From: Swayam Rana <122097724+SwayamRana808@users.noreply.github.com> Date: Wed, 22 May 2024 22:00:47 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=80=20update=20functionality=20for=20A?= =?UTF-8?q?dmin=20profile=20(#931)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update functionality for Admin profile * update functionality for Admin profile * update functionality admin page * update functionality admin page * #update functionality admin page * #update functionality admin page * #update functionality admin page * update admin api fix --- backend/app/models/Admin.js | 4 + backend/app/routes/admin/getAdmins.js | 1 + backend/app/routes/admin/index.js | 17 +- backend/app/routes/admin/updateAdmin.js | 42 +++- frontend/src/pages/Admin/Admin.jsx | 23 ++- .../Admin/Components/Profile/Profile.jsx | 195 ++++++++++++++++-- .../Components/Profile/profile.module.scss | 39 +++- frontend/src/pages/Admin/admin.module.scss | 2 + 8 files changed, 290 insertions(+), 33 deletions(-) diff --git a/backend/app/models/Admin.js b/backend/app/models/Admin.js index 18b68209..e3d0478f 100644 --- a/backend/app/models/Admin.js +++ b/backend/app/models/Admin.js @@ -33,6 +33,10 @@ const adminSchema = new Schema( trim: true, unique: true, }, + image: { + type: String, + trim: true, + }, }, { timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } } ); diff --git a/backend/app/routes/admin/getAdmins.js b/backend/app/routes/admin/getAdmins.js index 887c3a38..8aebb616 100644 --- a/backend/app/routes/admin/getAdmins.js +++ b/backend/app/routes/admin/getAdmins.js @@ -15,6 +15,7 @@ const getAdminsAggregate = (match, page) => { email: 1, contact: 1, isSuperAdmin: 1, + image:1 }, }, { $skip: constants.PAGINATION_LIMIT.GET_ADMINS * (Number(page) - 1) }, diff --git a/backend/app/routes/admin/index.js b/backend/app/routes/admin/index.js index 35ff2d0f..c8d6929f 100644 --- a/backend/app/routes/admin/index.js +++ b/backend/app/routes/admin/index.js @@ -1,7 +1,9 @@ const router = require('express').Router({ mergeParams: true }); +const multer = require('multer'); +const { nanoid } = require('nanoid'); +const path = require('path'); const validationMiddleware = require('../../../helpers/middlewares/validation'); const { authMiddleware } = require('../../../helpers/middlewares/auth'); - const { postSuperAdminSchema, getAdminsSchema, @@ -23,6 +25,17 @@ const resetPassword = require('./resetPassword'); const updateAdmin = require('./updateAdmin'); const deleteAdmin = require('./deleteAdmin'); + + +const store = multer.diskStorage({ + destination: 'uploads/Admin/', + filename: (req, file, cb) => { + const uniqueFilename = nanoid() + path.extname(file.originalname); + cb(null, uniqueFilename); + }, +}); +const upload = multer({ storage: store }); + router.get('/', validationMiddleware(getAdminsSchema, 'query'), authMiddleware, getAdmins); router.get('/createSuperAdmin', createSuperAdmin); @@ -33,7 +46,7 @@ router.post('/forgotpassword', validationMiddleware(forgotPasswordSchema), forgo router.post('/resetpassword/:token', validationMiddleware(resetPasswordSchema), resetPassword); router.put('/password', validationMiddleware(passwordChangeSchema), authMiddleware, changePassword); -router.put('/:id/:token', validationMiddleware(updateAdminSchema), updateAdmin); +router.put('/:id/:token', validationMiddleware(updateAdminSchema),upload.single('image') ,updateAdmin); router.delete('/deleteAdmin', validationMiddleware(deleteAdminSchema), authMiddleware, deleteAdmin); diff --git a/backend/app/routes/admin/updateAdmin.js b/backend/app/routes/admin/updateAdmin.js index 9984d1e8..453f7f4e 100644 --- a/backend/app/routes/admin/updateAdmin.js +++ b/backend/app/routes/admin/updateAdmin.js @@ -1,18 +1,42 @@ const Admin = require('../../models/Admin'); +const fs = require('fs'); +const path = require('path'); module.exports =async (req, res) => { + const admin = await Admin.findById(req.params.id); + if (!admin) { + return res.status(404).json({ error: 'Admin not found' }); + } + + // Delete previous image if it exists + if (admin.image && admin.image!=="undefined" && req.file?.path) { + if (fs.existsSync(path.join(__dirname,'..' ,'..','..', admin.image))) { + fs.unlinkSync(path.join(__dirname,'..' ,'..','..', admin.image)); + } + } + try { - const updatedAdmin = await Admin.findByIdAndUpdate( - req.params.id, - { - $set: req.body, - }, - { new: true } - ); - res.status(200).json(updatedAdmin); + const updateFields = { + firstName:req.body.firstName, + lastName:req.body.lastName, + contact:req.body.contact, + username:req.body.username + }; + if (req.file?.path) { + updateFields.image = req.file.path; + }else{ + updateFields.image = admin.image; + } + const updatedAdmin = await Admin.findByIdAndUpdate( + req.params.id, + { $set: updateFields }, + { new: true } + ); + return res.status(200).json({"Req.Body":updateFields,"updatedDoc":updatedAdmin}); } catch (err) { - res.status(500).json(err); + return res.status(500).json(err); } + res.send('Done'); }; diff --git a/frontend/src/pages/Admin/Admin.jsx b/frontend/src/pages/Admin/Admin.jsx index cf26fab2..8138b925 100644 --- a/frontend/src/pages/Admin/Admin.jsx +++ b/frontend/src/pages/Admin/Admin.jsx @@ -33,12 +33,13 @@ export const Admin = (props) => { const toggleNav = () => setIsMenuOpen(!isMenuOpen); const closeMobileMenu = () => setIsMenuOpen(false); const dispatch = useDispatch(); - const firstName = localStorage.getItem("firstName"); + const [update,setUpdate]=useState(true); const [qId,setQId] = useState("") const [adminData, setAdminData] = useState({}); + const [image,setImage]=useState('./images/admin.png'); const FetchAdminData = async () => { try { - const baseUrl = `${END_POINT}/admin`; + const baseUrl = `${END_POINT}/admin/`; const params = { type: "self", email: localStorage.getItem("email"), @@ -47,8 +48,18 @@ export const Admin = (props) => { "Content-Type": "application/json", Authorization: `Bearer ${localStorage.getItem("token")}`, }; - // Make the GET request using Axios + const response = await axios.get(baseUrl, { params, headers }); + let formattedPath = response.data[0].image?.replace(/\\/g, "/"); + if (formattedPath?.startsWith("uploads/")) { + formattedPath = formattedPath.replace("uploads/", ""); + if (formattedPath && formattedPath !=="undefined") { + formattedPath = `${END_POINT}/${formattedPath}`; + } + } + if(formattedPath!=="undefined" && formattedPath){ + setImage(formattedPath); + } setAdminData(response.data[0]); } catch (error) { console.error("There was a problem with the fetch operation:", error); @@ -68,7 +79,7 @@ export const Admin = (props) => { logout(dispatch); } return true; - }, [dispatch]); + }, [dispatch,update]); return (
@@ -76,7 +87,7 @@ export const Admin = (props) => {
setTab(0)}> admin_img @@ -212,7 +223,7 @@ export const Admin = (props) => {
{tab === 0 ? ( - + {setUpdate(!update)}} /> ) : tab === 1 ? ( ) : tab === 2 ? ( diff --git a/frontend/src/pages/Admin/Components/Profile/Profile.jsx b/frontend/src/pages/Admin/Components/Profile/Profile.jsx index 7461ad16..abfafad4 100644 --- a/frontend/src/pages/Admin/Components/Profile/Profile.jsx +++ b/frontend/src/pages/Admin/Components/Profile/Profile.jsx @@ -1,25 +1,158 @@ import React, { useEffect, useState } from "react"; import EditIcon from "@material-ui/icons/Edit"; import CloseIcon from "@material-ui/icons/Close"; -import style from "./profile.module.scss"; - +import Joi from "joi-browser"; +import { Button2 } from "../../../../components/util/Button"; +import "../../../../pages/Resources/components/ResourceSharingForm/resource-sharing-form.module.scss" +import style from "./profile.module.scss"; +import Loader from "../../../../components/util/Loader"; +import { END_POINT } from "../../../../config/api"; +import { SimpleToast } from "../../../../components/util/Toast"; export function Profile(props) { const [name, setName] = useState("Super Admin Name"); + const [firstName,setFirstName]=useState(""); + const [lastName,setLastName]=useState(""); const [email, setEmail] = useState("xyz@gmail.com"); + const [username, setUsername] = useState("xyz"); const [phone, setPhone] = useState("+91-123456789"); const [edit, setEdit] = useState(false); + const [errors, setErrors] = useState({}); + const [loading, setLoading] = useState(false); + const [open, setOpenToast] = useState(false); + const [toastMessage, setToastMessage] = useState(""); + const [severity, setSeverity] = useState("success"); + const [picUrl, setPicUrl] = useState(props.adminData.image); + const [pic, setPic] = useState(); + + const errorMessages = { + firstName: "Invalid First Name", + lastName: "Invalid Last Name", + email: "Invalid Email", + phone: "Invalid Phone Number Format +91XXXXXXXXX", + username:"Invalid Username" + }; + + const validationSchema = { + firstName: Joi.string().regex(/^[A-Za-z]+$/).required().label("First Name"), + lastName: Joi.string().regex(/^[A-Za-z]*$/).required().label("Last Name"), + email: Joi.string().email().required().label("Email"), + phone: Joi.string().regex(/[+]91[6-9]{1}[0-9]{9}$/).required().label("Contact Number"), + username: Joi.string().regex(/^[A-Za-z]+$/).required().label("Username") + }; + + const handleCloseToast = () => { + setTimeout(() => { + setOpenToast(false); + }, 500); + }; + + const validateForm = () => { + const obj = { firstName, lastName, email, phone,username }; + const options = { abortEarly: false }; + const { error } = Joi.validate(obj, validationSchema, options); + if (!error) return null; + + const errors = {}; + error.details.forEach(err => { + const field = err.path[0]; + errors[field] = errorMessages[field] || "Invalid Input"; + }); + return errors; + }; + const updateAdmin = async(e) => { + e.preventDefault(); + + const validationErrors = validateForm(); + setErrors(validationErrors || {}); + + if (validationErrors) return; + setErrors({}); + + console.log(firstName,lastName,phone,email); + const token =localStorage.getItem('token') + const data = { + firstName, + lastName, + username, + contact:phone, + }; + const formData = new FormData(); + formData.append('firstName', firstName); + formData.append('lastName', lastName); + formData.append('username', username); + formData.append('contact', phone); + formData.append("image", pic); + setLoading(true); + try { + const response = await fetch(`${END_POINT}/admin/${props.adminData._id}/${token}`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${token}`, + }, + body:formData, + }); + + if (!response.ok) { + setToastMessage("Failed to update admin"); + setOpenToast(true); + setSeverity("error"); + throw new Error('Failed to update admin'); + }else{ + setToastMessage("Updated Successfully"); + setOpenToast(true); + setSeverity("success"); + // re-render the Admin Page as it is updated + props.update(); + } + } catch (error) { + console.error('Error updating admin:', error); + setToastMessage("Failed to update admin"); + setOpenToast(true); + setSeverity("error"); + } finally { + setLoading(false); + } + }; + + const onPicChange = (event) => { + const { target } = event; + const { files } = target; + + if (files && files[0]) { + setPic(files[0]); + let reader = new FileReader(); + reader.onload = function (e) { + setPicUrl(e.target.result); + }; + reader.readAsDataURL(files[0]); + } + return; + }; + + const changePic = () => { + return edit && document.getElementById("profile-pic-input")?.click(); + }; useEffect(() => { let firstName = props.adminData.firstName ? props.adminData.firstName : ""; let lastName = props.adminData.lastName ? props.adminData.lastName : ""; let Name = firstName + " " + lastName; - setName(Name); - setEmail(props.adminData.email); - setPhone(props.adminData.contact); - }, props); + setName(Name || "xyz"); + setFirstName(firstName || "xyz"); + setLastName(lastName || ""); + setEmail(props.adminData.email || "xyz@gmail.com"); + setPhone(props.adminData.contact || "+919099999990"); + setUsername(props.adminData.username || "xyzIo"); + }, [props]); return (
+

My Profile

setEdit(!edit)}> @@ -29,28 +162,64 @@ export function Profile(props) { )}
-
+
+ admin_img +
{edit ? ( - <> +
- + setFirstName(e.target.value)} required/> +
+ {errors.firstName && {errors.firstName}} +
+
- + setLastName(e.target.value)}/> +
+ {errors.lastName && {errors.lastName}} +
- + setUsername(e.target.value)}/> +
+ {errors.username && {errors.username}} +
+
+
+ setPhone(e.target.value)} required/> +
+ {errors.phone && {errors.phone}} +
+
+
+
+
+ {loading ? ( + + ) : ( + + )} +
+
- +
) : ( <>
{name}
diff --git a/frontend/src/pages/Admin/Components/Profile/profile.module.scss b/frontend/src/pages/Admin/Components/Profile/profile.module.scss index 1be61c0d..058038b5 100644 --- a/frontend/src/pages/Admin/Components/Profile/profile.module.scss +++ b/frontend/src/pages/Admin/Components/Profile/profile.module.scss @@ -40,16 +40,20 @@ vertical-align: middle; background: linear-gradient(45deg, #ff005a 14.23%, #0a183d 83.75%); box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.25); + overflow: hidden; } .img-admin { - width: 100px; - height: 100px; + width: 100%; + height: 100%; margin: auto; + object-fit: cover; + object-position: center; + border-radius: 50%; } .card-info { - height: 20rem; + height: 32rem; width: 98%; background: #fff; margin: 0 auto; @@ -89,6 +93,12 @@ width: 50%; margin: 1rem auto; } +.input-wrapper-btn { + width: 50%; + margin: 1rem auto; + display: flex; + justify-content: center; +} .input-wrapper input { border: 1px solid #f8f7f7; @@ -98,6 +108,29 @@ padding: 0 1rem; box-shadow: inset 2px 2px 5px #c9c9c9, inset -2px -2px 5px #ffffff; } +.data-loader { + width: 100%; + display: flex; + height: 60px; + justify-content: center; + align-items: center; +} +.edit-icon{ + position: absolute; + top:0; + right:5px; + padding:2px; + color:black; + background-color: white; + border:solid white 1px; + border-radius: 100px; +} +.error-message { + height: 1rem; + color: red; + font-size: 12px; + margin-top: 2px; +} @media (max-width: 600px) { .card-info { diff --git a/frontend/src/pages/Admin/admin.module.scss b/frontend/src/pages/Admin/admin.module.scss index 29b0d000..d04600c4 100644 --- a/frontend/src/pages/Admin/admin.module.scss +++ b/frontend/src/pages/Admin/admin.module.scss @@ -26,7 +26,9 @@ container { .img-admin { width: 50%; + aspect-ratio: 1 / 1; margin: 1em; + border-radius: 50%; } .h1 {