From 0647ef0e3e75ca4ca702f81a95b665107a22a6bc Mon Sep 17 00:00:00 2001 From: Abin Date: Sat, 22 Jul 2023 18:23:17 +0530 Subject: [PATCH 1/4] folders renamed --- client/src/App.tsx | 4 +-- .../{add-course => addCourse}/AddCourse.tsx | 0 .../CourseDescriptionForm.tsx | 0 .../CourseMediaForm.tsx | 0 .../{add-course => addCourse}/FinalPage.tsx | 0 .../AddLessonsForm.tsx | 0 .../ListCourseForIstructors.tsx | 0 .../QuizSwitch.tsx | 0 .../QuizesComponent.tsx | 0 .../ViewLessons.tsx | 0 .../pages/instructors/InsructorProfile.tsx | 0 .../instructors}/InstructorHeader.tsx | 0 .../instructors/InstructorSideNav.tsx} | 26 ++++++++++++------- .../instructors}/ProfileMenuInstructor.tsx | 2 +- .../ChagePasswordForm.tsx | 0 .../{dash => studentDash}/DashHeader.tsx | 0 .../pages/{dash => studentDash}/DashHome.tsx | 0 .../{dash => studentDash}/DashSideNav.tsx | 0 .../{dash => studentDash}/MyCourseCard.tsx | 0 .../pages/{dash => studentDash}/MyCourses.tsx | 0 .../pages/{dash => studentDash}/MyProfile.tsx | 0 .../{dash => studentDash}/ProfileFrom.tsx | 0 .../{dash => studentDash}/UserDashboard.tsx | 0 client/src/routes.tsx | 14 +++++----- 24 files changed, 27 insertions(+), 19 deletions(-) rename client/src/components/pages/{add-course => addCourse}/AddCourse.tsx (100%) rename client/src/components/pages/{add-course => addCourse}/CourseDescriptionForm.tsx (100%) rename client/src/components/pages/{add-course => addCourse}/CourseMediaForm.tsx (100%) rename client/src/components/pages/{add-course => addCourse}/FinalPage.tsx (100%) rename client/src/components/pages/{add-lessons => addLessons}/AddLessonsForm.tsx (100%) rename client/src/components/pages/{add-lessons => addLessons}/ListCourseForIstructors.tsx (100%) rename client/src/components/pages/{add-lessons => addLessons}/QuizSwitch.tsx (100%) rename client/src/components/pages/{add-lessons => addLessons}/QuizesComponent.tsx (100%) rename client/src/components/pages/{add-lessons => addLessons}/ViewLessons.tsx (100%) create mode 100644 client/src/components/pages/instructors/InsructorProfile.tsx rename client/src/components/{partials => pages/instructors}/InstructorHeader.tsx (100%) rename client/src/components/{partials/SideNav.tsx => pages/instructors/InstructorSideNav.tsx} (91%) rename client/src/components/{partials => pages/instructors}/ProfileMenuInstructor.tsx (97%) rename client/src/components/pages/{dash => studentDash}/ChagePasswordForm.tsx (100%) rename client/src/components/pages/{dash => studentDash}/DashHeader.tsx (100%) rename client/src/components/pages/{dash => studentDash}/DashHome.tsx (100%) rename client/src/components/pages/{dash => studentDash}/DashSideNav.tsx (100%) rename client/src/components/pages/{dash => studentDash}/MyCourseCard.tsx (100%) rename client/src/components/pages/{dash => studentDash}/MyCourses.tsx (100%) rename client/src/components/pages/{dash => studentDash}/MyProfile.tsx (100%) rename client/src/components/pages/{dash => studentDash}/ProfileFrom.tsx (100%) rename client/src/components/pages/{dash => studentDash}/UserDashboard.tsx (100%) diff --git a/client/src/App.tsx b/client/src/App.tsx index 2f209c5..ee5536a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -6,9 +6,9 @@ import AdminLoginPage from "./components/pages/admin/AdminLoginPage"; import { Sidenav } from "./components/pages/admin/widgets/layout"; import { routes } from "./components/pages/admin/AdminDashBoardPage"; import { useSelector, useDispatch } from "react-redux"; -import InstructorSideNav from "./components/partials/SideNav"; +import InstructorSideNav from "./components/pages/instructors/InstructorSideNav"; import { selectIsAdminLoggedIn } from "./redux/reducers/adminAuthSlice"; -import InstructorHeader from "./components/partials/InstructorHeader"; +import InstructorHeader from "./components/pages/instructors/InstructorHeader"; import useIsOnline from "./hooks/useOnline"; import YouAreOffline from "./components/common/YouAreOffline"; import StudentFooter from "./components/partials/StudentFooter"; diff --git a/client/src/components/pages/add-course/AddCourse.tsx b/client/src/components/pages/addCourse/AddCourse.tsx similarity index 100% rename from client/src/components/pages/add-course/AddCourse.tsx rename to client/src/components/pages/addCourse/AddCourse.tsx diff --git a/client/src/components/pages/add-course/CourseDescriptionForm.tsx b/client/src/components/pages/addCourse/CourseDescriptionForm.tsx similarity index 100% rename from client/src/components/pages/add-course/CourseDescriptionForm.tsx rename to client/src/components/pages/addCourse/CourseDescriptionForm.tsx diff --git a/client/src/components/pages/add-course/CourseMediaForm.tsx b/client/src/components/pages/addCourse/CourseMediaForm.tsx similarity index 100% rename from client/src/components/pages/add-course/CourseMediaForm.tsx rename to client/src/components/pages/addCourse/CourseMediaForm.tsx diff --git a/client/src/components/pages/add-course/FinalPage.tsx b/client/src/components/pages/addCourse/FinalPage.tsx similarity index 100% rename from client/src/components/pages/add-course/FinalPage.tsx rename to client/src/components/pages/addCourse/FinalPage.tsx diff --git a/client/src/components/pages/add-lessons/AddLessonsForm.tsx b/client/src/components/pages/addLessons/AddLessonsForm.tsx similarity index 100% rename from client/src/components/pages/add-lessons/AddLessonsForm.tsx rename to client/src/components/pages/addLessons/AddLessonsForm.tsx diff --git a/client/src/components/pages/add-lessons/ListCourseForIstructors.tsx b/client/src/components/pages/addLessons/ListCourseForIstructors.tsx similarity index 100% rename from client/src/components/pages/add-lessons/ListCourseForIstructors.tsx rename to client/src/components/pages/addLessons/ListCourseForIstructors.tsx diff --git a/client/src/components/pages/add-lessons/QuizSwitch.tsx b/client/src/components/pages/addLessons/QuizSwitch.tsx similarity index 100% rename from client/src/components/pages/add-lessons/QuizSwitch.tsx rename to client/src/components/pages/addLessons/QuizSwitch.tsx diff --git a/client/src/components/pages/add-lessons/QuizesComponent.tsx b/client/src/components/pages/addLessons/QuizesComponent.tsx similarity index 100% rename from client/src/components/pages/add-lessons/QuizesComponent.tsx rename to client/src/components/pages/addLessons/QuizesComponent.tsx diff --git a/client/src/components/pages/add-lessons/ViewLessons.tsx b/client/src/components/pages/addLessons/ViewLessons.tsx similarity index 100% rename from client/src/components/pages/add-lessons/ViewLessons.tsx rename to client/src/components/pages/addLessons/ViewLessons.tsx diff --git a/client/src/components/pages/instructors/InsructorProfile.tsx b/client/src/components/pages/instructors/InsructorProfile.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/partials/InstructorHeader.tsx b/client/src/components/pages/instructors/InstructorHeader.tsx similarity index 100% rename from client/src/components/partials/InstructorHeader.tsx rename to client/src/components/pages/instructors/InstructorHeader.tsx diff --git a/client/src/components/partials/SideNav.tsx b/client/src/components/pages/instructors/InstructorSideNav.tsx similarity index 91% rename from client/src/components/partials/SideNav.tsx rename to client/src/components/pages/instructors/InstructorSideNav.tsx index 783daff..f854bfa 100644 --- a/client/src/components/partials/SideNav.tsx +++ b/client/src/components/pages/instructors/InstructorSideNav.tsx @@ -22,28 +22,36 @@ const routes = [ path:'/instructors/' }, { - title: "View course", + title: "View courses", icon: , value: "view-course", path:'/instructors/view-course' }, { - title: "Add course", + title: "Add courses", icon: , value: "add-course", path:'/instructors/add-course' - },{ - title:"Channels", - icon:, - value:"view-channels", - path:'/instructors/view-channels' }, { - title:"Students", + title:"My students", + icon:, + value:"view-students", + path:"/instructors/view-students" + }, + { + title:"My Profile", icon:, value:"view-students", path:"/instructors/view-students" - } + }, + { + title:"Channels", + icon:, + value:"view-channels", + path:'/instructors/view-channels' + }, + ]; const InstructorSideNav: React.FC = () => { diff --git a/client/src/components/partials/ProfileMenuInstructor.tsx b/client/src/components/pages/instructors/ProfileMenuInstructor.tsx similarity index 97% rename from client/src/components/partials/ProfileMenuInstructor.tsx rename to client/src/components/pages/instructors/ProfileMenuInstructor.tsx index 7213a4e..97f3923 100644 --- a/client/src/components/partials/ProfileMenuInstructor.tsx +++ b/client/src/components/pages/instructors/ProfileMenuInstructor.tsx @@ -15,7 +15,7 @@ import { LifebuoyIcon, PowerIcon, } from "@heroicons/react/24/outline"; -import { getProfileUrl } from "../../api/endpoints/student"; +import { getProfileUrl } from "../../../api/endpoints/student"; import { toast } from "react-toastify"; import {UserCircleIcon} from "@heroicons/react/24/outline"; diff --git a/client/src/components/pages/dash/ChagePasswordForm.tsx b/client/src/components/pages/studentDash/ChagePasswordForm.tsx similarity index 100% rename from client/src/components/pages/dash/ChagePasswordForm.tsx rename to client/src/components/pages/studentDash/ChagePasswordForm.tsx diff --git a/client/src/components/pages/dash/DashHeader.tsx b/client/src/components/pages/studentDash/DashHeader.tsx similarity index 100% rename from client/src/components/pages/dash/DashHeader.tsx rename to client/src/components/pages/studentDash/DashHeader.tsx diff --git a/client/src/components/pages/dash/DashHome.tsx b/client/src/components/pages/studentDash/DashHome.tsx similarity index 100% rename from client/src/components/pages/dash/DashHome.tsx rename to client/src/components/pages/studentDash/DashHome.tsx diff --git a/client/src/components/pages/dash/DashSideNav.tsx b/client/src/components/pages/studentDash/DashSideNav.tsx similarity index 100% rename from client/src/components/pages/dash/DashSideNav.tsx rename to client/src/components/pages/studentDash/DashSideNav.tsx diff --git a/client/src/components/pages/dash/MyCourseCard.tsx b/client/src/components/pages/studentDash/MyCourseCard.tsx similarity index 100% rename from client/src/components/pages/dash/MyCourseCard.tsx rename to client/src/components/pages/studentDash/MyCourseCard.tsx diff --git a/client/src/components/pages/dash/MyCourses.tsx b/client/src/components/pages/studentDash/MyCourses.tsx similarity index 100% rename from client/src/components/pages/dash/MyCourses.tsx rename to client/src/components/pages/studentDash/MyCourses.tsx diff --git a/client/src/components/pages/dash/MyProfile.tsx b/client/src/components/pages/studentDash/MyProfile.tsx similarity index 100% rename from client/src/components/pages/dash/MyProfile.tsx rename to client/src/components/pages/studentDash/MyProfile.tsx diff --git a/client/src/components/pages/dash/ProfileFrom.tsx b/client/src/components/pages/studentDash/ProfileFrom.tsx similarity index 100% rename from client/src/components/pages/dash/ProfileFrom.tsx rename to client/src/components/pages/studentDash/ProfileFrom.tsx diff --git a/client/src/components/pages/dash/UserDashboard.tsx b/client/src/components/pages/studentDash/UserDashboard.tsx similarity index 100% rename from client/src/components/pages/dash/UserDashboard.tsx rename to client/src/components/pages/studentDash/UserDashboard.tsx diff --git a/client/src/routes.tsx b/client/src/routes.tsx index d2d1206..cbe154e 100644 --- a/client/src/routes.tsx +++ b/client/src/routes.tsx @@ -16,21 +16,21 @@ import { Student, Admin } from "./App"; import ViewMoreInstructorRequest from "./components/pages/InstructorManagement/ViewMoreInstructorRequest"; import { Instructor } from "./App"; import InstructorDashboard from "./components/pages/instructors/InstructorDashboard"; -import AddCourse from "./components/pages/add-course/AddCourse"; +import AddCourse from "./components/pages/addCourse/AddCourse"; import ViewCourseStudent from "./components/pages/Course/ViewCourse"; import WatchLessons from "./components/pages/Course/WatchLessons"; -import ListCourseForInstructors from "./components/pages/add-lessons/ListCourseForIstructors"; -import ViewLessons from "./components/pages/add-lessons/ViewLessons"; +import ListCourseForInstructors from "./components/pages/addLessons/ListCourseForIstructors"; +import ViewLessons from "./components/pages/addLessons/ViewLessons"; import StripeContainer from "./components/pages/payment-stripe/StripeContainer"; import Categories from "./components/pages/categories/Categories"; import AddCategory from "./components/pages/categories/AddCategory"; import EditCategory from "./components/pages/categories/EditCategory"; import ListCategories from "./components/pages/categories/ListCategory"; import ViewInstructor from "./components/pages/instructors/ViewInstructor"; -import UserDashboard from "./components/pages/dash/UserDashboard"; -import MyCourses from "./components/pages/dash/MyCourses"; -import MyProfile from "./components/pages/dash/MyProfile"; -import DashHome from "./components/pages/dash/DashHome"; +import UserDashboard from "./components/pages/studentDash/UserDashboard"; +import MyCourses from "./components/pages/studentDash/MyCourses"; +import MyProfile from "./components/pages/studentDash/MyProfile"; +import DashHome from "./components/pages/studentDash/DashHome"; import ViewStudents from "./components/pages/studentManagement/ViewStudents"; import StudentsTab from "./components/pages/studentManagement/StudentsTab"; From 354e4d86caa61922bd8fb7dd51d9b5df2bdbd0a1 Mon Sep 17 00:00:00 2001 From: Abin Date: Sat, 22 Jul 2023 18:42:54 +0530 Subject: [PATCH 2/4] instructor profile desing complete --- .../pages/instructors/InsructorProfile.tsx | 62 +++++ .../pages/instructors/InstructorSideNav.tsx | 8 +- .../pages/instructors/PasswordForm.tsx | 208 ++++++++++++++ .../pages/instructors/ProfileForm.tsx | 258 ++++++++++++++++++ client/src/routes.tsx | 6 +- 5 files changed, 538 insertions(+), 4 deletions(-) create mode 100644 client/src/components/pages/instructors/PasswordForm.tsx create mode 100644 client/src/components/pages/instructors/ProfileForm.tsx diff --git a/client/src/components/pages/instructors/InsructorProfile.tsx b/client/src/components/pages/instructors/InsructorProfile.tsx index e69de29..7169642 100644 --- a/client/src/components/pages/instructors/InsructorProfile.tsx +++ b/client/src/components/pages/instructors/InsructorProfile.tsx @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from "react"; +import ProfileForm from "./ProfileForm"; +import ChangePasswordForm from "./PasswordForm"; +import { fetchStudentData } from "../../../redux/reducers/studentSlice"; +import { useDispatch } from "react-redux"; +import { FiEdit } from "react-icons/fi"; + +type Props = {}; + +const InstructorProfile: React.FC = (props: Props) => { + const dispatch = useDispatch(); + const [editMode, setEditMode] = useState(false); + + useEffect(() => { + dispatch(fetchStudentData()); + }, [dispatch]); + + const handleEditClick = () => { + setEditMode(true); + }; + + return ( +
+
+
+
+

+ Edit profile info +

+
+
+
+
+
+

+ Account Info +

+
+ +
+
+
+ +
+
+
+

+ Change password +

+
+ +
+
+
+
+
+ ); +}; + +export default InstructorProfile; diff --git a/client/src/components/pages/instructors/InstructorSideNav.tsx b/client/src/components/pages/instructors/InstructorSideNav.tsx index f854bfa..9854ecc 100644 --- a/client/src/components/pages/instructors/InstructorSideNav.tsx +++ b/client/src/components/pages/instructors/InstructorSideNav.tsx @@ -10,6 +10,8 @@ import { Square3Stack3DIcon } from "@heroicons/react/24/outline"; import { useLocation } from "react-router-dom"; import {IoMdChatboxes} from 'react-icons/io' import { FaUserGraduate } from "react-icons/fa"; +import {UserCircleIcon} from "@heroicons/react/24/outline"; + const icon = { className: "w-5 h-5 text-inherit", @@ -41,9 +43,9 @@ const routes = [ }, { title:"My Profile", - icon:, - value:"view-students", - path:"/instructors/view-students" + icon:, + value:"view-profile", + path:"/instructors/view-profile" }, { title:"Channels", diff --git a/client/src/components/pages/instructors/PasswordForm.tsx b/client/src/components/pages/instructors/PasswordForm.tsx new file mode 100644 index 0000000..f6ff8d0 --- /dev/null +++ b/client/src/components/pages/instructors/PasswordForm.tsx @@ -0,0 +1,208 @@ +import { useState } from "react"; +import { useFormik } from "formik"; +import { changePassword } from "../../../api/endpoints/student"; +import { toast } from "react-toastify"; +import { PasswordInfo } from "../../../api/types/student/student"; +import { PasswordValidationSchema } from "../../../validations/student"; +import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai"; +const ChangePasswordForm = () => { + const [showCurrentPassword, setShowCurrentPassword] = useState(false); + const [showNewPassword, setShowNewPassword] = useState(false); + const [showRepeatPassword, setShowRepeatPassword] = useState(false); + + const handleSubmit = async (passwordInfo: PasswordInfo) => { + try { + const response = await changePassword(passwordInfo); + response?.data?.status === "success" && formik.resetForm(); + toast.success(response?.data?.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } catch (error: any) { + toast.error(error?.data?.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + + const formik = useFormik({ + initialValues: { + currentPassword: "", + newPassword: "", + repeatPassword: "", + }, + validationSchema: PasswordValidationSchema, + onSubmit: (values) => { + handleSubmit(values); + }, + }); + + + const togglePasswordVisibility = (field: string) => { + switch (field) { + case "currentPassword": + setShowCurrentPassword(!showCurrentPassword); + break; + case "newPassword": + setShowNewPassword(!showNewPassword); + break; + case "repeatPassword": + setShowRepeatPassword(!showRepeatPassword); + break; + default: + break; + } + }; + + const getPasswordInputType = (field: string) => { + switch (field) { + case "currentPassword": + return showCurrentPassword ? "text" : "password"; + case "newPassword": + return showNewPassword ? "text" : "password"; + case "repeatPassword": + return showRepeatPassword ? "text" : "password"; + default: + return "password"; + } + }; + + + return ( +
+
+ + {formik.touched.currentPassword && formik.errors.currentPassword && ( +
+ {formik.errors.currentPassword} +
+ )} + + +
+
+ + {formik.touched.newPassword && formik.errors.newPassword && ( +
+ {formik.errors.newPassword} +
+ )} + + +
+
+ + {formik.touched.repeatPassword && formik.errors.repeatPassword && ( +
+ {formik.errors.repeatPassword} +
+ )} + + +
+
+ +
+
+ ); +}; + +export default ChangePasswordForm; diff --git a/client/src/components/pages/instructors/ProfileForm.tsx b/client/src/components/pages/instructors/ProfileForm.tsx new file mode 100644 index 0000000..ed0fd1f --- /dev/null +++ b/client/src/components/pages/instructors/ProfileForm.tsx @@ -0,0 +1,258 @@ +import { useState, useEffect } from "react"; +import { useFormik } from "formik"; +import { toast } from "react-toastify"; +import { updateProfile } from "../../../api/endpoints/student"; +import { UpdateProfileInfo } from "../../../api/types/student/student"; +import { Avatar } from "@material-tailwind/react"; +import { useSelector, useDispatch } from "react-redux"; +import { + selectStudent, + selectIsFetchingStudent, + selectStudentError, + fetchStudentData, +} from "../../../redux/reducers/studentSlice"; +import { getProfileUrl } from "../../../api/endpoints/student"; + +interface Props { + editMode:boolean; + setEditMode:(val:boolean)=>void +} +const ProfileForm:React.FC = ({editMode,setEditMode}) => { + const [previewImage, setPreviewImage] = useState(null); + const studentInfo = useSelector(selectStudent)?.studentDetails; + let isFetching = useSelector(selectIsFetchingStudent); + const [loading, setLoading] = useState(false); + const [profileUrl, setProfileUrl] = useState(""); + const error = useSelector(selectStudentError); + const [updated, setUpdated] = useState(false); + const dispatch = useDispatch(); + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setPreviewImage(reader.result as string); + }; + reader.readAsDataURL(file); + formik.setFieldValue("profilePic", file); + } else { + setPreviewImage(null); + formik.setFieldValue("profilePic", null); + } + }; + const fetchUrl = async () => { + try { + setLoading(true); + const response = await getProfileUrl(); + setProfileUrl(response.data); + setLoading(false); + } catch (error: any) { + toast.error(error?.data?.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + useEffect(() => { + fetchUrl(); + dispatch(fetchStudentData()); + }, [updated]); + + useEffect(() => { + if (!studentInfo) { + setLoading(true); + } else { + setLoading(false); + } + }, [studentInfo]); + + const handleSubmit = async (profileInfo: UpdateProfileInfo) => { + try { + const formData = new FormData(); + if (profileInfo.profilePic) { + formData.append("image", profileInfo.profilePic); + } + formData.append("email", profileInfo.email || ""); + formData.append("firstName", profileInfo.firstName || ""); + formData.append("lastName", profileInfo.lastName || ""); + formData.append("mobile", profileInfo.mobile || ""); + + const response = await updateProfile(formData); + // formik.resetForm(); + setPreviewImage(null); + const fileInput = document.getElementById( + "file_input" + ) as HTMLInputElement; + if (fileInput) { + fileInput.value = ""; + } + setUpdated(!updated); + setEditMode(false); + toast.success(response?.data?.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } catch (error: any) { + setUpdated(!updated); + toast.error(error?.data?.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + + const formik = useFormik({ + initialValues: { + email: studentInfo?.email || "", + firstName: studentInfo?.firstName || "", + lastName: studentInfo?.lastName || "", + mobile: studentInfo?.mobile || "", + }, + onSubmit: (values) => { + handleSubmit(values); + }, + }); + + // if (isFetching) { + // return
Loading...
; + // } + if (loading) { + return
Loading...
; + } + + // if (error) { + // return
Error: {error}
; + // } + + // if (!studentInfo) { + // return
loading...
+ // } + + return ( +
+
+
+
+ +
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ {editMode && ( + + )} +
+
+ ); +}; + +export default ProfileForm; diff --git a/client/src/routes.tsx b/client/src/routes.tsx index cbe154e..b5545ac 100644 --- a/client/src/routes.tsx +++ b/client/src/routes.tsx @@ -31,8 +31,8 @@ import UserDashboard from "./components/pages/studentDash/UserDashboard"; import MyCourses from "./components/pages/studentDash/MyCourses"; import MyProfile from "./components/pages/studentDash/MyProfile"; import DashHome from "./components/pages/studentDash/DashHome"; -import ViewStudents from "./components/pages/studentManagement/ViewStudents"; import StudentsTab from "./components/pages/studentManagement/StudentsTab"; +import InstructorProfile from "./components/pages/instructors/InsructorProfile"; const LazyListCourse = lazy( () => import("./components/pages/Course/ListCourse") @@ -199,6 +199,10 @@ const AppRouter = createBrowserRouter([ path: "view-lessons/:courseId", element: , }, + { + path:"view-profile", + element: + } ], }, ]); From 31445024993b9f379be7c7a8ea8fee203fd28f6b Mon Sep 17 00:00:00 2001 From: Abin Date: Sat, 22 Jul 2023 22:56:01 +0530 Subject: [PATCH 3/4] api created for instructor profile --- .../controllers/instructorController.ts | 2 + .../repositories/instructorDbRepository.ts | 11 ++- .../src/app/usecases/auth/instructorAuth.ts | 2 +- server/src/app/usecases/instructor.ts | 72 +++++++++++++++++++ .../management/instructorManagement.ts | 3 + .../repositories/instructorRepoMongoDb.ts | 19 ++++- server/src/types/instructorInterface.ts | 6 +- 7 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 server/src/app/usecases/instructor.ts diff --git a/server/src/adapters/controllers/instructorController.ts b/server/src/adapters/controllers/instructorController.ts index 8963677..884da67 100644 --- a/server/src/adapters/controllers/instructorController.ts +++ b/server/src/adapters/controllers/instructorController.ts @@ -124,6 +124,8 @@ const instructorController = ( }) + + return { getInstructorRequests, verifyInstructor, diff --git a/server/src/app/repositories/instructorDbRepository.ts b/server/src/app/repositories/instructorDbRepository.ts index cf6c2fb..d76cb36 100644 --- a/server/src/app/repositories/instructorDbRepository.ts +++ b/server/src/app/repositories/instructorDbRepository.ts @@ -1,5 +1,6 @@ import { InstructorRepositoryMongoDb } from '@src/frameworks/database/mongodb/repositories/instructorRepoMongoDb'; import { InstructorInterface } from '@src/types/instructorInterface'; +import { SavedInstructorInterface } from '@src/types/instructorInterface'; export const instructorDbRepository = ( repository: ReturnType @@ -38,7 +39,11 @@ export const instructorDbRepository = ( const getInstructorById = async (instructorId: string) => await repository.getInstructorById(instructorId); - const getTotalNumberOfInstructors = async ()=> repository.getTotalNumberOfInstructors() + const getTotalNumberOfInstructors = async ()=> await repository.getTotalNumberOfInstructors() + + const changePassword = async (id:string,password:string)=> await repository.changePassword(id,password) + + const updateProfile = async (id:string,instructorInfo:SavedInstructorInterface)=> await repository.updateProfile(id,instructorInfo) return { addInstructor, @@ -52,7 +57,9 @@ export const instructorDbRepository = ( unblockInstructors, getBlockedInstructors, getInstructorById, - getTotalNumberOfInstructors + getTotalNumberOfInstructors, + changePassword, + updateProfile }; }; diff --git a/server/src/app/usecases/auth/instructorAuth.ts b/server/src/app/usecases/auth/instructorAuth.ts index 091c4e2..c1f8eca 100644 --- a/server/src/app/usecases/auth/instructorAuth.ts +++ b/server/src/app/usecases/auth/instructorAuth.ts @@ -19,7 +19,7 @@ export const instructorRegister = async ( if (files) { files.map((file) => { if (file.originalname === 'profilePic') { - instructor.profilePic = file.path; + instructor.profilePic.url = file.path; } else { const certificate = { name:file.originalname, diff --git a/server/src/app/usecases/instructor.ts b/server/src/app/usecases/instructor.ts new file mode 100644 index 0000000..49f7958 --- /dev/null +++ b/server/src/app/usecases/instructor.ts @@ -0,0 +1,72 @@ +import { InstructorDbInterface } from '../repositories/instructorDbRepository'; +import AppError from '../../utils/appError'; +import HttpStatusCodes from '../../constants/HttpStatusCodes'; +import { AuthServiceInterface } from '../services/authServicesInterface'; +import { SavedInstructorInterface } from '@src/types/instructorInterface'; +import { CloudServiceInterface } from '../services/cloudServiceInterface'; + +export const changePasswordU = async ( + id: string | undefined, + password: { currentPassword: string; newPassword: string }, + authService: ReturnType, + instructorDbRepository: ReturnType +) => { + if (!id) { + throw new AppError('Invalid student', HttpStatusCodes.BAD_REQUEST); + } + if (!password.currentPassword) { + throw new AppError( + 'Please provide current password', + HttpStatusCodes.BAD_REQUEST + ); + } + const instructor:SavedInstructorInterface|null = await instructorDbRepository.getInstructorById( + id + ); + if (!instructor) { + throw new AppError('Unauthorized user', HttpStatusCodes.NOT_FOUND); + } + const isPasswordCorrect = await authService.comparePassword( + password.currentPassword, + instructor?.password + ); + if (!isPasswordCorrect) { + throw new AppError( + 'Sorry, your current password is incorrect.', + HttpStatusCodes.BAD_REQUEST + ); + } + if (!password.newPassword) { + throw new AppError( + 'new password cannot be empty', + HttpStatusCodes.UNAUTHORIZED + ); + } + const hashedPassword = await authService.hashPassword(password.newPassword); + await instructorDbRepository.changePassword(id, hashedPassword); +}; + +export const updateProfileU = async ( + id: string | undefined, + instructorInfo: SavedInstructorInterface, + profilePic: Express.Multer.File, + cloudService: ReturnType, + instructorDbRepository: ReturnType +) => { + if (!id) { + throw new AppError('Invalid instructor', HttpStatusCodes.BAD_REQUEST); + } + if (Object.keys(instructorInfo).length === 0) { + throw new AppError( + 'At least update a single field', + HttpStatusCodes.BAD_REQUEST + ); + } + if (profilePic) { + const response = await cloudService.upload(profilePic); + instructorInfo.profilePic = response; + } + await instructorDbRepository.updateProfile(id, instructorInfo); +}; + + diff --git a/server/src/app/usecases/management/instructorManagement.ts b/server/src/app/usecases/management/instructorManagement.ts index 594da64..40be8d4 100644 --- a/server/src/app/usecases/management/instructorManagement.ts +++ b/server/src/app/usecases/management/instructorManagement.ts @@ -117,5 +117,8 @@ export const getInstructorByIdUseCase = async ( throw new AppError('Invalid instructor id', HttpStatusCodes.BAD_REQUEST); } const instructor = await instructorRepository.getInstructorById(instructorId); + if (instructor) { + instructor.password = 'no password'; + } return instructor; }; diff --git a/server/src/frameworks/database/mongodb/repositories/instructorRepoMongoDb.ts b/server/src/frameworks/database/mongodb/repositories/instructorRepoMongoDb.ts index 4d943b9..b710b56 100644 --- a/server/src/frameworks/database/mongodb/repositories/instructorRepoMongoDb.ts +++ b/server/src/frameworks/database/mongodb/repositories/instructorRepoMongoDb.ts @@ -96,7 +96,7 @@ export const instructorRepoMongoDb = () => { }; const getInstructorById = async (instructorId: string) => { - const instructor = await Instructor.findOne({ + const instructor:SavedInstructorInterface|null = await Instructor.findOne({ _id: new mongoose.Types.ObjectId(instructorId) }); return instructor; @@ -107,6 +107,19 @@ export const instructorRepoMongoDb = () => { return total; }; + const changePassword = async (id: string, password: string) => { + await Instructor.updateOne( + { _id: new mongoose.Types.ObjectId(id) }, + { password } + ); + }; + + const updateProfile = async (id: string, instructorInfo: SavedInstructorInterface) => { + await Instructor.updateOne( + { _id: new mongoose.Types.ObjectId(id) }, + { ...instructorInfo } + ); + }; return { addInstructor, getInstructorByEmail, @@ -119,7 +132,9 @@ export const instructorRepoMongoDb = () => { unblockInstructors, getBlockedInstructors, getInstructorById, - getTotalNumberOfInstructors + getTotalNumberOfInstructors, + changePassword, + updateProfile }; }; diff --git a/server/src/types/instructorInterface.ts b/server/src/types/instructorInterface.ts index 89c59e7..d819a2d 100644 --- a/server/src/types/instructorInterface.ts +++ b/server/src/types/instructorInterface.ts @@ -1,7 +1,11 @@ export interface InstructorInterface { firstName:string; lastName:string; - profilePic:string; + profilePic:{ + name:string; + key?:string; + url?:string; + }; email: string; mobile:number; qualifications:string; From 00723e2e97afcaebaead17f027aa60bd0d38d56f Mon Sep 17 00:00:00 2001 From: Abin Date: Sun, 23 Jul 2023 16:58:36 +0530 Subject: [PATCH 4/4] completed instructor profile --- client/src/api/endpoints/instructor.ts | 20 ++ client/src/api/services/instructor.ts | 28 +++ .../apiResponses/apiResponseInstructors.ts | 3 +- client/src/api/types/instructor/instructor.ts | 18 ++ .../pages/instructors/InsructorProfile.tsx | 8 +- .../pages/instructors/PasswordForm.tsx | 2 +- .../pages/instructors/ProfileForm.tsx | 212 ++++++++++++------ client/src/constants/endpoints.ts | 4 +- .../controllers/instructorController.ts | 117 +++++++--- server/src/app/usecases/instructor.ts | 2 +- .../management/instructorManagement.ts | 6 + .../database/mongodb/models/instructor.ts | 25 ++- server/src/frameworks/services/authService.ts | 2 +- .../frameworks/webserver/routes/instructor.ts | 46 ++-- server/src/types/instructorInterface.ts | 1 + 15 files changed, 379 insertions(+), 115 deletions(-) create mode 100644 client/src/api/endpoints/instructor.ts create mode 100644 client/src/api/services/instructor.ts create mode 100644 client/src/api/types/instructor/instructor.ts diff --git a/client/src/api/endpoints/instructor.ts b/client/src/api/endpoints/instructor.ts new file mode 100644 index 0000000..8e2a73a --- /dev/null +++ b/client/src/api/endpoints/instructor.ts @@ -0,0 +1,20 @@ +import { + updateProfileService, + changePasswordService, +} from "../services/instructor"; +import { PasswordInfo } from "../types/student/student"; +import END_POINTS from "../../constants/endpoints"; + +export const changePassword = (passwordInfo: PasswordInfo) => { + return changePasswordService( + END_POINTS.INSTRUCTOR_CHANGE_PASSWORD, + passwordInfo + ); +}; + +export const updateProfile = (profileInfo: FormData) => { + return updateProfileService( + END_POINTS.INSTRUCTOR_UPDATE_PROFILE, + profileInfo + ); +}; diff --git a/client/src/api/services/instructor.ts b/client/src/api/services/instructor.ts new file mode 100644 index 0000000..3933012 --- /dev/null +++ b/client/src/api/services/instructor.ts @@ -0,0 +1,28 @@ +import CONSTANTS_COMMON from "../../constants/common"; +import api from "../middlewares/protectedInterceptor"; +import { PasswordInfo} from "../types/student/student"; + +export const changePasswordService = async ( + endpoint: string, + passwordInfo: PasswordInfo +) => { + const response = await api.patch( + `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}`, + passwordInfo + ); + return response; +}; + +export const updateProfileService = async ( + endpoint: string, + profileInfo: FormData +) => { + const response = await api.put( + `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}`, + profileInfo + ); + return response; +}; + + + diff --git a/client/src/api/types/apiResponses/apiResponseInstructors.ts b/client/src/api/types/apiResponses/apiResponseInstructors.ts index fddb0ce..49d8d3f 100644 --- a/client/src/api/types/apiResponses/apiResponseInstructors.ts +++ b/client/src/api/types/apiResponses/apiResponseInstructors.ts @@ -19,7 +19,8 @@ export interface InstructorApiResponse { dateJoined: string __v: number rejected: boolean - rejectedReason: string + rejectedReason: string, + profileUrl:string; } interface Certificate { diff --git a/client/src/api/types/instructor/instructor.ts b/client/src/api/types/instructor/instructor.ts new file mode 100644 index 0000000..b34433f --- /dev/null +++ b/client/src/api/types/instructor/instructor.ts @@ -0,0 +1,18 @@ +export interface PasswordInfo { + currentPassword: string; + newPassword: string; + } + export interface UpdateProfileInfo { + firstName?: string; + lastName?: string; + email?: string; + mobile?: string; + profilePic?: File | null; + qualification?:string; + subjects?:string; + experience?:string; + skills?:string; + about?:string; + + } + \ No newline at end of file diff --git a/client/src/components/pages/instructors/InsructorProfile.tsx b/client/src/components/pages/instructors/InsructorProfile.tsx index 7169642..a9741bb 100644 --- a/client/src/components/pages/instructors/InsructorProfile.tsx +++ b/client/src/components/pages/instructors/InsructorProfile.tsx @@ -27,9 +27,9 @@ const InstructorProfile: React.FC = (props: Props) => {

Edit profile info

- + -
+

@@ -54,8 +54,8 @@ const InstructorProfile: React.FC = (props: Props) => {

-
- + + ); }; diff --git a/client/src/components/pages/instructors/PasswordForm.tsx b/client/src/components/pages/instructors/PasswordForm.tsx index f6ff8d0..1e5feb8 100644 --- a/client/src/components/pages/instructors/PasswordForm.tsx +++ b/client/src/components/pages/instructors/PasswordForm.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useFormik } from "formik"; -import { changePassword } from "../../../api/endpoints/student"; +import { changePassword } from "../../../api/endpoints/instructor"; import { toast } from "react-toastify"; import { PasswordInfo } from "../../../api/types/student/student"; import { PasswordValidationSchema } from "../../../validations/student"; diff --git a/client/src/components/pages/instructors/ProfileForm.tsx b/client/src/components/pages/instructors/ProfileForm.tsx index ed0fd1f..73a6980 100644 --- a/client/src/components/pages/instructors/ProfileForm.tsx +++ b/client/src/components/pages/instructors/ProfileForm.tsx @@ -1,31 +1,24 @@ import { useState, useEffect } from "react"; import { useFormik } from "formik"; import { toast } from "react-toastify"; -import { updateProfile } from "../../../api/endpoints/student"; -import { UpdateProfileInfo } from "../../../api/types/student/student"; +import { updateProfile } from "../../../api/endpoints/instructor"; +import { UpdateProfileInfo } from "../../../api/types/instructor/instructor"; import { Avatar } from "@material-tailwind/react"; -import { useSelector, useDispatch } from "react-redux"; -import { - selectStudent, - selectIsFetchingStudent, - selectStudentError, - fetchStudentData, -} from "../../../redux/reducers/studentSlice"; -import { getProfileUrl } from "../../../api/endpoints/student"; +import { getIndividualInstructors } from "../../../api/endpoints/instructorManagement"; +import { InstructorApiResponse } from "../../../api/types/apiResponses/apiResponseInstructors"; interface Props { - editMode:boolean; - setEditMode:(val:boolean)=>void + editMode: boolean; + setEditMode: (val: boolean) => void; } -const ProfileForm:React.FC = ({editMode,setEditMode}) => { +const ProfileForm: React.FC = ({ editMode, setEditMode }) => { const [previewImage, setPreviewImage] = useState(null); - const studentInfo = useSelector(selectStudent)?.studentDetails; - let isFetching = useSelector(selectIsFetchingStudent); - const [loading, setLoading] = useState(false); + const [profileLoading, setProfileLoading] = useState(false); + const [instructor, setInstructor] = useState( + null + ); const [profileUrl, setProfileUrl] = useState(""); - const error = useSelector(selectStudentError); const [updated, setUpdated] = useState(false); - const dispatch = useDispatch(); const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; @@ -41,30 +34,54 @@ const ProfileForm:React.FC = ({editMode,setEditMode}) => { formik.setFieldValue("profilePic", null); } }; - const fetchUrl = async () => { + const fetchInstructor = async () => { try { - setLoading(true); - const response = await getProfileUrl(); - setProfileUrl(response.data); - setLoading(false); - } catch (error: any) { - toast.error(error?.data?.message, { - position: toast.POSITION.BOTTOM_RIGHT, - }); + setProfileLoading(true); + const response = await getIndividualInstructors("fromProfile"); + setInstructor(response?.data?.data); + setProfileLoading(false); + } catch (error) { + setProfileLoading(false); } }; - useEffect(() => { - fetchUrl(); - dispatch(fetchStudentData()); - }, [updated]); useEffect(() => { - if (!studentInfo) { - setLoading(true); - } else { - setLoading(false); + if (instructor) { + formik.setValues({ + email: instructor.email || "", + firstName: instructor.firstName || "", + lastName: instructor.lastName || "", + mobile: instructor.mobile || "", + qualification: instructor?.qualification || "", + // subjects: instructor?.subjects || "", + experience: instructor?.experience || "", + skills: instructor?.skills || "", + about: instructor?.about || "", + }); } - }, [studentInfo]); + setProfileUrl(instructor?.profileUrl ?? ""); + }, [instructor]); + + useEffect(() => { + fetchInstructor(); + }, [updated]); + + const formik = useFormik({ + initialValues: { + email: instructor?.email || "", + firstName: instructor?.firstName || "", + lastName: instructor?.lastName || "", + mobile: instructor?.mobile || "", + qualification: instructor?.qualification || "", + // subjects: instructor?.subjects || "", + experience: instructor?.experience || "", + skills: instructor?.skills || "", + about: instructor?.about || "", + }, + onSubmit: (values) => { + handleSubmit(values); + }, + }); const handleSubmit = async (profileInfo: UpdateProfileInfo) => { try { @@ -76,9 +93,12 @@ const ProfileForm:React.FC = ({editMode,setEditMode}) => { formData.append("firstName", profileInfo.firstName || ""); formData.append("lastName", profileInfo.lastName || ""); formData.append("mobile", profileInfo.mobile || ""); + formData.append("qualification",profileInfo.qualification||"") + formData.append("experience",profileInfo.experience||"") + formData.append("skills",profileInfo.skills||"") const response = await updateProfile(formData); - // formik.resetForm(); + // formik.resetForm(); setPreviewImage(null); const fileInput = document.getElementById( "file_input" @@ -99,37 +119,13 @@ const ProfileForm:React.FC = ({editMode,setEditMode}) => { } }; - const formik = useFormik({ - initialValues: { - email: studentInfo?.email || "", - firstName: studentInfo?.firstName || "", - lastName: studentInfo?.lastName || "", - mobile: studentInfo?.mobile || "", - }, - onSubmit: (values) => { - handleSubmit(values); - }, - }); - - // if (isFetching) { - // return
Loading...
; - // } - if (loading) { + if (profileLoading) { return
Loading...
; } - // if (error) { - // return
Error: {error}
; - // } - - // if (!studentInfo) { - // return
loading...
- // } - return ( -
-
-
+ +
= ({editMode,setEditMode}) => { Mobile
+
+ + +
+ {/*
+ + +
*/} +
+ + +
+
+ + +
{editMode && (