diff --git a/frontend/src/components/AuthCommon/password-change/PasswordChange.jsx b/frontend/src/components/AuthCommon/password-change/PasswordChange.jsx
new file mode 100644
index 0000000..e9323f1
--- /dev/null
+++ b/frontend/src/components/AuthCommon/password-change/PasswordChange.jsx
@@ -0,0 +1,275 @@
+/* eslint-disable react/prop-types */
+import React, { useEffect, useState } from "react";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import TextField from "@mui/material/TextField";
+import { isTokenExpired } from "../../../utils/auth/tokenExpiryCheck";
+import { jwtDecode } from "jwt-decode";
+import Typography from "@mui/material/Typography";
+import { updatePasswordFromProfile } from "../../../utils/api/ResetPassword/resetPassword";
+
+import { useNavigation, Form, useActionData } from "react-router-dom";
+
+function PasswordChange(props) {
+ const [passwordError, setPasswordError] = useState(false);
+ const [confirmPasswordError, setConfirmPasswordError] = useState(false);
+ const [passwordResetSuccess, setPasswordResetSuccess] = useState(false);
+ const [errorText, setErrorText] = useState("");
+
+ const actionData = useActionData();
+
+ useEffect(() => {
+ if (actionData) {
+ if (actionData.status == 200) {
+ //password reset successfully
+ setPasswordError(false);
+ setConfirmPasswordError(false);
+ setPasswordResetSuccess(true);
+ //re route to login can be done here directly but success message?
+ } else {
+ // error
+ setPasswordError(true);
+ setConfirmPasswordError(true);
+ setErrorText(actionData.message);
+ }
+ }
+ }, [actionData]);
+
+ const navigation = useNavigation();
+
+ return (
+
+ {/* display success message if reset is success */}
+ <>
+ {passwordResetSuccess ? (
+
+ Password Updated Successfully
+
+ ) : null}
+
+ {/* password reset component */}
+
+ Change Your Password
+
+
+
+
+ >
+
+ );
+}
+
+export default PasswordChange;
+
+export async function action({ request }) {
+ const formData = await request.formData();
+
+ const current_password = formData.get("currentPassword");
+ const new_password = formData.get("newPassword");
+ const confirm_password = formData.get("confirmPassword");
+
+ const accessToken = localStorage.getItem("access");
+ const tokenExpired = isTokenExpired(accessToken);
+
+ //token expired or any issue in token
+ if (tokenExpired) {
+ const response = {
+ status: 500,
+ message: "Please login again to update password.",
+ };
+ return response;
+ }
+
+ //password match check
+ if (new_password != confirm_password) {
+ const response = {
+ status: 400,
+ message: "New Password & Current Password didn't match",
+ };
+ return response;
+ } else {
+ //password matched and all rules followed backend call
+ //pass email also
+ const decoded = jwtDecode(accessToken);
+ const response = await updatePasswordFromProfile(
+ decoded.email,
+ current_password,
+ new_password
+ );
+ return response;
+ }
+}
diff --git a/frontend/src/components/admin/leftNavbar/LeftNavbar.jsx b/frontend/src/components/admin/leftNavbar/LeftNavbar.jsx
index d0eb350..ecad132 100644
--- a/frontend/src/components/admin/leftNavbar/LeftNavbar.jsx
+++ b/frontend/src/components/admin/leftNavbar/LeftNavbar.jsx
@@ -27,6 +27,7 @@ import ExpandMore from "@mui/icons-material/ExpandMore";
import StarBorder from "@mui/icons-material/StarBorder";
import "./styles.css";
import { navItemsManager } from "../../../constants/navbar";
+import { Link } from "react-router-dom";
const drawerWidth = 280;
const openedMixin = (theme) => ({
@@ -278,23 +279,25 @@ export default function MiniDrawer(props) {
component="li"
sx={{ backgroundColor: "#ffffff", opacity: "15%" }}
/>
-
-
- {obj.icon ? (
- obj.icon
- ) : (
-
- )}
-
-
+
- {obj.text}
-
-
+
+ {obj.icon ? (
+ obj.icon
+ ) : (
+
+ )}
+
+
+ {obj.text}
+
+
+
{
if (data) {
//extracting skill names from skill ids
- const filteredSkills = skills.filter((obj) =>
- data.id.skills.includes(obj.id)
- );
+ let filteredSkills = [];
+ let filteredDisciplines = [];
+ if (data.id.skills) {
+ filteredSkills = skills.filter((obj) =>
+ data.id.skills.includes(obj.id)
+ );
+ //extracting discipline names from discipline ids
+ filteredDisciplines = disciplines.filter((obj) =>
+ data.id.skills.includes(obj.id)
+ );
+ }
const skillNames = filteredSkills.map((skill) => skill.name);
-
- //extracting discipline names from discipline ids
- const filteredDisciplines = disciplines.filter((obj) =>
- data.id.skills.includes(obj.id)
- );
+
const disciplineNames = filteredDisciplines.map((skill) => skill.name);
//application date
- const dateString = data.id.last_app_date;
- const parts = dateString.split(" ");
- const monthIndex =
- new Date(Date.parse(parts[1] + " 1, 2000")).getMonth() + 1;
+ let lastDate = null;
+ if (data.id.last_app_date) {
+ const dateString = data.id.last_app_date;
+ const parts = dateString.split(" ");
+ const monthIndex =
+ new Date(Date.parse(parts[1] + " 1, 2000")).getMonth() + 1;
- const formattedDate = `${parts[2]}-${monthIndex
- .toString()
- .padStart(2, "0")}-${parts[0].padStart(2, "0")}`;
- const lastDate = dayjs(formattedDate);
+ const formattedDate = `${parts[2]}-${monthIndex
+ .toString()
+ .padStart(2, "0")}-${parts[0].padStart(2, "0")}`;
+ lastDate = dayjs(formattedDate);
+ }
// Update state values with data from the 'data' prop
setJobDesignation(data.id.designation || "");
setMinSalary(data.id.salary_range_min || "");
setMaxSalary(data.id.salary_range_max || "");
- setJobDomain(data.id.domain.name || "");
- setJobType(data.id.job_type.jobtype || "");
- setGender(data.id.gender || "");
- setOfficeState(data.id.job_state || "");
- setOfficeCity(data.id.city_state || "");
+ setJobDomain(data.id.domain ? data.id.domain.name : "");
+ setJobType(data.id.job_type ? data.id.job_type.jobtype : "");
+ setGender(data.id.gender ? data.id.gender : "");
+ setOfficeState(data.id.job_state ? data.id.job_state : "");
+ setOfficeCity(data.id.city_state ? data.id.city_state : "");
setSkillName(skillNames || []);
- setJobDescription(data.id.jobDescription || "");
- setKeyResponsibilities(data.id.key_job_responsibilities || "");
- setQualification(data.id.requirements || "");
- setApplicationDate(lastDate || null);
- setGradYears(data.id.gradYears || []);
- setMandatorySkills(data.id.mandatorySkills || []);
- setOptionalSkills(data.id.optionalSkills || []);
- setDegree(data.id.degree || []);
+ setJobDescription(data.id.jobDescription ? data.id.jobDescription : "");
+ setKeyResponsibilities(
+ data.id.key_job_responsibilities ? data.id.key_job_responsibilities : ""
+ );
+ setQualification(data.id.requirements ? data.id.requirements : "");
+ setApplicationDate(lastDate ? lastDate : null);
+ setGradYears(data.id.gradYears ? data.id.gradYears : []);
+ setMandatorySkills(
+ data.id.mandatorySkills ? data.id.mandatorySkills : []
+ );
+ setOptionalSkills(data.id.optionalSkills ? data.id.optionalSkills : []);
+ setDegree(data.id.degree ? data.id.degree : []);
setDiscipline(disciplineNames || []);
- setStudentLocation(data.id.studentLocation || []);
+ setStudentLocation(
+ data.id.studentLocation ? data.id.studentLocation : []
+ );
}
- }, [data]);
+ }, [data]);
const handleJobDesignationChange = (event) => {
setJobDesignation(event.target.value);
diff --git a/frontend/src/components/company/company-job-profile/companyJobProfile.jsx b/frontend/src/components/company/company-job-profile/companyJobProfile.jsx
index b4fa1c2..0104c69 100644
--- a/frontend/src/components/company/company-job-profile/companyJobProfile.jsx
+++ b/frontend/src/components/company/company-job-profile/companyJobProfile.jsx
@@ -4,7 +4,7 @@ import { Box, Typography } from "@mui/material";
import Divider from "@mui/material/Divider";
import JobListTable from "./jobListTable";
import { Link, defer, useLoaderData, Await } from "react-router-dom";
-import { getJobsByUserId } from "../../../utils/api/company/jobs";
+import { getJobList } from "../../../utils/api/company/jobs";
import { Suspense } from "react";
import Spinner from "../../common/Spinner";
@@ -75,5 +75,5 @@ function CompanyJobProfile() {
export default CompanyJobProfile;
export function loader() {
const token = localStorage.getItem("access");
- return defer({ jobListData: getJobsByUserId(token) });
+ return defer({ jobListData: getJobList(token) });
}
diff --git a/frontend/src/components/login/ForgotPassword/SendPasswordResetLink.jsx b/frontend/src/components/login/ForgotPassword/SendPasswordResetLink.jsx
index 77ec44f..192b779 100644
--- a/frontend/src/components/login/ForgotPassword/SendPasswordResetLink.jsx
+++ b/frontend/src/components/login/ForgotPassword/SendPasswordResetLink.jsx
@@ -57,29 +57,31 @@ export default function SendPasswordResetLink({ linkSent, linkSentError }) {
{/* email input */}
-
+ "& label.Mui-focused": {
+ color: "#002648",
+ },
+ mt: "1rem",
+ }}
+ />
+ ) : null}
diff --git a/frontend/src/constants/navbar.js b/frontend/src/constants/navbar.js
index fcbf807..5b4cf57 100644
--- a/frontend/src/constants/navbar.js
+++ b/frontend/src/constants/navbar.js
@@ -10,6 +10,7 @@ import FilterAltOutlinedIcon from "@mui/icons-material/FilterAltOutlined";
import ForwardToInboxOutlinedIcon from "@mui/icons-material/ForwardToInboxOutlined";
import PendingOutlinedIcon from "@mui/icons-material/PendingOutlined";
import CategoryOutlinedIcon from "@mui/icons-material/CategoryOutlined";
+import SettingsIcon from "@mui/icons-material/Settings";
import ImageOutlinedIcon from "@mui/icons-material/ImageOutlined";
import VignetteOutlinedIcon from "@mui/icons-material/VignetteOutlined";
import PageviewOutlinedIcon from "@mui/icons-material/PageviewOutlined";
@@ -33,6 +34,7 @@ const navItemsStudent = [
{ text: "Dashboard", url: "dashboard" },
{ text: "Jobs", url: "jobs" },
{ text: "Profile", url: "profile" },
+ { text: "Settings", url: "settings" },
];
//manager dashboard
@@ -165,6 +167,12 @@ const navItemsManager = [
url: "",
icon: ,
},
+ {
+ nested: false,
+ text: "Settings",
+ url: "settings",
+ icon: ,
+ },
];
//employer dashboard
@@ -172,6 +180,7 @@ const navItemsEmployer = [
{ text: "Dashboard", url: "dashboard" },
{ text: "Jobs", url: "jobs" },
{ text: "Profile", url: "profile" },
+ { text: "Settings", url: "settings" },
];
export { navItemsHomepage, navItemsEmployer, navItemsManager, navItemsStudent };
diff --git a/frontend/src/route/router.js b/frontend/src/route/router.js
index 1e94e03..e0c2433 100644
--- a/frontend/src/route/router.js
+++ b/frontend/src/route/router.js
@@ -29,6 +29,11 @@ import NewPasswordInput, {
action as NewPasswordInputAction,
} from "../components/login/ForgotPassword/NewPasswordInput";
+//auth common
+import PasswordChange, {
+ action as passwordChangeAction,
+} from "../components/AuthCommon/password-change/PasswordChange";
+
//student
import StudentLayout, {
action as StudentLogoutAction,
@@ -120,6 +125,11 @@ const router = createBrowserRouter([
{ path: "dashboard", element: },
{ path: "profile", element: },
{ path: "jobs", element: },
+ {
+ path: "settings",
+ element: ,
+ action: passwordChangeAction,
+ },
],
},
@@ -132,7 +142,14 @@ const router = createBrowserRouter([
),
action: ManagerLogoutAction,
- children: [{ path: "", element: }],
+ children: [
+ { path: "", element: },
+ {
+ path: "settings",
+ element: ,
+ action: passwordChangeAction,
+ },
+ ],
},
{
@@ -147,7 +164,15 @@ const router = createBrowserRouter([
children: [
{ path: "dashboard", element: },
{ path: "jobs", element: , loader: jobListLoader },
- { path: "profile", element: },
+ {
+ path: "profile",
+ element: ,
+ },
+ {
+ path: "settings",
+ element: ,
+ action: passwordChangeAction,
+ },
],
},
{
diff --git a/frontend/src/utils/api/ResetPassword/resetPassword.js b/frontend/src/utils/api/ResetPassword/resetPassword.js
index 47a6835..fb23a06 100644
--- a/frontend/src/utils/api/ResetPassword/resetPassword.js
+++ b/frontend/src/utils/api/ResetPassword/resetPassword.js
@@ -39,7 +39,7 @@ export async function resetPassword(email) {
}
}
-//to set new password
+//to set new password from reset link
export async function setNewPassword(password, token) {
const url =
process.env.REACT_APP_API_LINK + "/api/reset-password/" + token + "/";
@@ -76,3 +76,45 @@ export async function setNewPassword(password, token) {
};
}
}
+
+//to set new password from user's profile
+export async function updatePasswordFromProfile(
+ email,
+ currentPassword,
+ newPassword
+) {
+ const accessToken = localStorage.getItem("access");
+ const url = process.env.REACT_APP_API_LINK + "/api/change-password/";
+ const requestOptions = {
+ method: "POST",
+ headers: {
+ Authorization: "Bearer " + accessToken,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ email: email,
+ current_password: currentPassword,
+ new_password: newPassword,
+ }),
+ };
+
+ try {
+ const response = await fetch(url, requestOptions);
+ if (!response.ok) {
+ const errorData = await response.json();
+ return {
+ message: errorData.detail || errorData.error,
+ status: response.status,
+ };
+ }
+ return {
+ message: "Password updated successfully",
+ status: response.status,
+ };
+ } catch (error) {
+ return {
+ message: "Failed to update password",
+ status: 500,
+ };
+ }
+}
diff --git a/frontend/src/utils/api/company/jobs.js b/frontend/src/utils/api/company/jobs.js
index 3902803..b265cdb 100644
--- a/frontend/src/utils/api/company/jobs.js
+++ b/frontend/src/utils/api/company/jobs.js
@@ -1,26 +1,26 @@
/* eslint-disable no-undef */
//to get job list
-export async function getJobsByUserId(token) {
- const headers = {
- 'Authorization' : 'Bearer ' + token,
- 'Content-Type': 'application/json',
- }
+export async function getJobList(token) {
try {
- const options = {
- method: 'GET',
- headers: headers
- }
- const apiUrl = `${process.env.REACT_APP_API_LINK}/api/company/manager/jobs`
- const response = await fetch(apiUrl, options);
+ const url = process.env.REACT_APP_API_LINK + "/api/company/manager/jobs/";
+
+ const requestOptions = {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: "Bearer " + token,
+ },
+ };
+ const response = await fetch(url, requestOptions);
if (!response.ok) {
- throw { message: "Failed to fetchs jobs", status: response.status };
+ return { message: response.detail, status: response.status };
+
}
const jobsData = await response.json();
return jobsData;
} catch (error) {
- console.error("Error fetching jobs:", error);
- throw error;
+ return { message: "Error fetching jobs:", error };
}
}