diff --git a/package-lock.json b/package-lock.json
index 5ac22f7..53ad00e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"react": "^18.3.1",
"react-datepicker": "^7.3.0",
"react-dom": "^18.3.1",
+ "react-easy-crop": "^5.1.0",
"react-icon": "^1.0.0",
"react-model": "^4.3.1",
"react-scripts": "5.0.1",
@@ -14044,6 +14045,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/normalize-wheel": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
+ "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/npm": {
"version": "10.8.2",
"resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz",
@@ -18991,6 +18998,20 @@
"react": ">= 16.8 || 18.0.0"
}
},
+ "node_modules/react-easy-crop": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.1.0.tgz",
+ "integrity": "sha512-UsYeF/N7zoqtfOSD+2xSt1nRaoBYCI2YLkzmq+hi+aVepS4/bAMhbrLwJtDAP60jsVzWRiQCX7JG+ZtfWcHsiw==",
+ "license": "MIT",
+ "dependencies": {
+ "normalize-wheel": "^1.0.1",
+ "tslib": "^2.0.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.4.0",
+ "react-dom": ">=16.4.0"
+ }
+ },
"node_modules/react-error-overlay": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
diff --git a/package.json b/package.json
index 1b94c06..83b734c 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"react": "^18.3.1",
"react-datepicker": "^7.3.0",
"react-dom": "^18.3.1",
+ "react-easy-crop": "^5.1.0",
"react-icon": "^1.0.0",
"react-model": "^4.3.1",
"react-scripts": "5.0.1",
diff --git a/src/Profile/cropImage.js b/src/Profile/cropImage.js
new file mode 100644
index 0000000..2dd0c91
--- /dev/null
+++ b/src/Profile/cropImage.js
@@ -0,0 +1,45 @@
+const getCroppedImg = (imageSrc, croppedAreaPixels) => {
+ const createImage = (url) =>
+ new Promise((resolve, reject) => {
+ const image = new Image();
+ image.onload = () => resolve(image);
+ image.onerror = (error) => reject(error);
+ image.src = url;
+ });
+
+ const getCroppedImg = async (imageSrc, crop) => {
+ const image = await createImage(imageSrc);
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+
+ canvas.width = crop.width;
+ canvas.height = crop.height;
+
+ ctx.drawImage(
+ image,
+ crop.x,
+ crop.y,
+ crop.width,
+ crop.height,
+ 0,
+ 0,
+ crop.width,
+ crop.height
+ );
+
+ return new Promise((resolve) => {
+ canvas.toBlob((blob) => {
+ if (!blob) {
+ console.error("Canvas is empty");
+ return;
+ }
+ const fileUrl = window.URL.createObjectURL(blob);
+ resolve(fileUrl);
+ }, "image/jpeg");
+ });
+ };
+
+ return getCroppedImg(imageSrc, croppedAreaPixels);
+ };
+
+export default getCroppedImg;
\ No newline at end of file
diff --git a/src/Profile/profile.css b/src/Profile/profile.css
index d75ea17..fc854d1 100644
--- a/src/Profile/profile.css
+++ b/src/Profile/profile.css
@@ -16,6 +16,7 @@
.profile-header {
position: relative;
display: inline-block;
+ margin-bottom: 30px;
}
.profile-image {
@@ -53,7 +54,7 @@
font-size: 24px;
font-weight: bold;
margin-bottom: 5px;
- margin-top: 50px;
+ margin-top: 20px;
}
.profile-mob {
@@ -211,3 +212,70 @@
.profile-edit-password-button:hover {
background-color: #4caf4fba;
}
+
+.profile-cropper
+{
+ position: absolute;
+ top:50%;
+ left:50%;
+ transform: translate(-50%,-50%);
+}
+.profile-crop-button-container
+{
+ position: relative;
+ top: 400px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 50px;
+}
+.profile-cropper-modal button, .profile-image-submit
+{
+ width: 150px;
+ height: 40px;
+ border-radius: 5px;
+ border-color: transparent;
+ font-size: large;
+ font-weight: 500;
+}
+.profile-cropper-modal button:hover, .profile-image-submit:hover
+{
+ cursor: pointer;
+}
+.profile-button-crop
+{
+ background-color: #4CAF50;
+}
+.profile-button-cancel
+{
+ background-color: #c9302c;
+}
+.profile-button-crop:hover
+{
+ background-color: #4caf4f7e;
+}
+.profile-button-cancel:hover
+{
+ background-color: #c9312c8c;
+}
+.profile-cropper-note
+{
+ position: relative;
+ color: red;
+ top: 410px;
+ font-weight: 600;
+}
+.profile-image-submit
+{
+ margin-top: 50px;
+ width: 200px;
+ background-color: #4CAF50;
+}
+.profile-image-submit
+{
+ background-color: #4caf50;
+}
+.profile-image-submit:hover
+{
+ background-color: #4CAF4f7e;
+}
\ No newline at end of file
diff --git a/src/Profile/profile.jsx b/src/Profile/profile.jsx
index c83bf98..70f641e 100644
--- a/src/Profile/profile.jsx
+++ b/src/Profile/profile.jsx
@@ -1,13 +1,15 @@
-import React, { useState, useEffect } from 'react';
-import axios from 'axios';
-import './profile.css';
-import Navbar from '../Navbar/Navbar';
-import { FaPlus, FaEye, FaEyeSlash } from 'react-icons/fa';
-import Modal from 'react-modal';
-import AlertModal from '../AlertModal/AlertModal';
+import React, { useState, useEffect } from "react";
+import axios from "axios";
+import "./profile.css";
+import Navbar from "../Navbar/Navbar";
+import { FaPlus, FaEye, FaEyeSlash } from "react-icons/fa";
+import Modal from "react-modal";
+import AlertModal from "../AlertModal/AlertModal";
import defaultPhoto from "../Assets/profile_photo.png";
+import Cropper from "react-easy-crop";
+import getCroppedImg from "./cropImage"; // Utility to crop the image
-Modal.setAppElement('#root');
+Modal.setAppElement("#root");
const Profile = () => {
const [profileData, setProfileData] = useState({
@@ -16,7 +18,7 @@ const Profile = () => {
email: "",
mobile_no: "",
password: "",
- confirmPassword: ""
+ confirmPassword: "",
});
const [modalIsOpen, setModalIsOpen] = useState(false);
@@ -27,28 +29,41 @@ const Profile = () => {
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [editPassword, setEditPassword] = useState(false);
- const [teacher, setTeacher] = useState();
const [passwordsMatch, setPasswordMatch] = useState(false);
+ const [modalCropIsOpen, setModalCropIsOpen] = useState(false);
+ const [crop, setCrop] = useState({ x: 0, y: 0 });
+ const [zoom, setZoom] = useState(1);
+ const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
+ const [submitDisplay,setSubmitDisplay] = useState(false);
useEffect(() => {
- axios.post('http://localhost:5000/teacher/getteacherDetails', { teacherId: localStorage.getItem("teacherId") })
- .then((response) => {
- setTeacher(response?.data?.teacher);
- if (teacher) {
- setProfileData(prevData => ({
+ // Fetch teacher details only when the component mounts
+ const fetchTeacherDetails = async () => {
+ try {
+ const response = await axios.post("http://localhost:5000/teacher/getteacherDetails", {
+ teacherId: localStorage.getItem("teacherId"),
+ });
+
+ const teacherData = response?.data?.teacher;
+ if (teacherData) {
+ setProfileData((prevData) => ({
...prevData,
- name: teacher?.name,
- email: teacher?.email,
- mobile_no: teacher?.mobileNumber,
- password: teacher?.password,
- confirmPassword: teacher?.password,
+ photo: teacherData.profilePic || defaultPhoto,
+ name: teacherData.name,
+ email: teacherData.email,
+ mobile_no: teacherData.mobileNumber,
+ password: teacherData.password,
+ confirmPassword: teacherData.password,
}));
}
- })
- .catch((error) => {
- console.log(error);
- });
- }, );
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ fetchTeacherDetails();
+ }, []); // Empty dependency array to ensure this effect only runs on mount
+
const openModal = () => {
setNewProfileData({ ...profileData, password: "", confirmPassword: "" });
@@ -70,7 +85,7 @@ const Profile = () => {
const handleSave = () => {
const { email, mobile_no, password, confirmPassword } = newProfileData;
- if (!email.includes('@')) {
+ if (!email.includes("@")) {
openAlertModal("Please enter a valid email address.", true);
return;
}
@@ -82,9 +97,13 @@ const Profile = () => {
}
if (editPassword) {
- const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
+ const passwordRegex =
+ /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
if (!passwordRegex.test(password)) {
- openAlertModal("Password must be at least 8 characters, contain one uppercase letter, one number, and one special character.", true);
+ openAlertModal(
+ "Password must be at least 8 characters, contain one uppercase letter, one number, and one special character.",
+ true
+ );
return;
}
@@ -94,11 +113,11 @@ const Profile = () => {
}
}
- // Merging /edit API call
- axios.post('http://localhost:5000/teacher/edit', {
- teacherId: localStorage.getItem("teacherId"),
- ...newProfileData,
- })
+ axios
+ .post("http://localhost:5000/teacher/edit", {
+ teacherId: localStorage.getItem("teacherId"),
+ ...newProfileData,
+ })
.then((response) => {
setProfileData({
...profileData,
@@ -115,6 +134,10 @@ const Profile = () => {
});
};
+ const onCropComplete = (croppedArea, croppedAreaPixels) => {
+ setCroppedAreaPixels(croppedAreaPixels);
+ };
+
const handleImageChange = (event) => {
const file = event.target.files[0];
if (file) {
@@ -126,18 +149,109 @@ const Profile = () => {
});
};
reader.readAsDataURL(file);
+ setModalCropIsOpen(true);
+ setSubmitDisplay(true);
+ }
+ };
+
+ const saveCroppedImage = async () => {
+ try {
+ const croppedImage = await getCroppedImg(profileData.photo, croppedAreaPixels);
+ setProfileData((prevData) => ({
+ ...prevData,
+ photo: croppedImage,
+ }));
+ setModalCropIsOpen(false);
+ } catch (error) {
+ console.error("Error cropping the image: ", error);
}
};
const togglePasswordVisibility = () => setShowPassword(!showPassword);
- const toggleConfirmPasswordVisibility = () => setShowConfirmPassword(!showConfirmPassword);
+ const toggleConfirmPasswordVisibility = () =>
+ setShowConfirmPassword(!showConfirmPassword);
+
+ const editProfilePicture = async () => {
+ try {
+ const formData = new FormData();
+ formData.append("file", profileData.photo);
+
+ // Upload the image to the server (assuming the endpoint is '/paper/upload')
+ const uploadResponse = await axios.post("http://localhost:5000/paper/upload", formData, {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ });
+
+ const profilePicUrl = uploadResponse.data.url; // Extract the uploaded image URL
+
+ // Update the teacher's profile picture with the new URL
+ const response = await axios.post("http://localhost:5000/teacher/editProfilePicture", {
+ link: profilePicUrl,
+ teacherId: localStorage.getItem("teacherId"),
+ });
+
+
+
+ // Update state with the new profile picture
+ setProfileData((prevData) => ({
+ ...prevData,
+ photo: profilePicUrl, // Set the new profile picture URL
+ }));
+
+ // Show success alert
+ setAlertIsOpen(true);
+ setAlertMessage(response.data.message);
+ } catch (err) {
+ // Handle error
+ setIsError(true);
+ setAlertIsOpen(true);
+ setAlertMessage("Failed to update profile picture. Please try again.");
+ console.error(err);
+ }
+ };
return (
<>
+
Email: {profileData.email}
Mobile: {profileData.mobile_no}