From 7c8381450df77113ba84b0ee15fb1ad6f6dba44b Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Tue, 26 May 2026 01:52:15 +0530 Subject: [PATCH 01/14] feat: add employee ID column with search functionality to profile list --- src/components/Profile/List/index.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/Profile/List/index.jsx b/src/components/Profile/List/index.jsx index f0a8c5b..55843d1 100644 --- a/src/components/Profile/List/index.jsx +++ b/src/components/Profile/List/index.jsx @@ -248,6 +248,13 @@ const ListProfiles = () => { ), }, + { + title: "Employee ID", + dataIndex: "employee_id", + key: "employee_id", + width: "6%", + ...getColumnSearchProps("employee_id"), + }, { title: "Email", dataIndex: "email", From 9e444e4b6863c22b527a2d28ed203d477beff45d Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Tue, 26 May 2026 12:26:02 +0530 Subject: [PATCH 02/14] feat: add employee_id field to BasicInfo form --- src/components/Builder/BasicInfo/index.jsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/Builder/BasicInfo/index.jsx b/src/components/Builder/BasicInfo/index.jsx index a40aa42..5e8abb7 100644 --- a/src/components/Builder/BasicInfo/index.jsx +++ b/src/components/Builder/BasicInfo/index.jsx @@ -88,7 +88,7 @@ const BasicInfo = ({ profileData }) => { initialValues={profileData || { description: PROFILE_DETAILS }} > - + { - + { + + + + + @@ -302,6 +310,10 @@ BasicInfo.propTypes = { primary_skills: PropTypes.array, secondary_skills: PropTypes.array, career_objectives: PropTypes.string, + employee_id: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), }), josh_joining_date: PropTypes.oneOfType([ PropTypes.string, From 4287a20755899591f7a5946527d8d644a9c97c09 Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 28 May 2026 10:05:00 +0530 Subject: [PATCH 03/14] feat: implement intranet synchronization flow to pre-fill profile data via employee ID lookup --- src/Constants.js | 1 + src/api/profileApi.jsx | 8 + src/components/Builder/BasicInfo/index.jsx | 69 ++++- .../Profile/IntranetSyncModal/index.jsx | 244 ++++++++++++++++++ src/components/Profile/List/index.jsx | 27 +- 5 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 src/components/Profile/IntranetSyncModal/index.jsx diff --git a/src/Constants.js b/src/Constants.js index fb10744..badd125 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -54,6 +54,7 @@ export const UPDATE_EXPERIENCE_ENDPOINT = export const UPDATE_CERTIFICATE_ENDPOINT = "/api/profiles/:profile_id/certificates/:certificate_id"; export const UPDATE_SEQUENCE_ENDPOINT = "/api/updateSequence"; +export const INTRANET_EMPLOYEE_ENDPOINT = "/api/intranet/employees/:employee_id"; export const DELETE_PROFILE_ENDPOINT = "/api/profiles/:profile_id"; export const DELETE_ACHIEVEMENT_ENDPOINT = diff --git a/src/api/profileApi.jsx b/src/api/profileApi.jsx index 9c52474..63c8545 100644 --- a/src/api/profileApi.jsx +++ b/src/api/profileApi.jsx @@ -3,6 +3,7 @@ import { CREATE_PROFILE_ENDPOINT, DELETE_PROFILE_ENDPOINT, HTTP_METHODS, + INTRANET_EMPLOYEE_ENDPOINT, PROFILE_COMPLETE_ENDPOINT, PROFILE_GET_ENDPOINT, PROFILE_LIST_ENDPOINT, @@ -86,6 +87,12 @@ export const profileApi = createApi({ invalidatesTags: ["profile"], transformResponse: (response) => response.data, }), + getIntranetEmployee: builder.query({ + query: (employeeId) => ({ + url: INTRANET_EMPLOYEE_ENDPOINT.replace(":employee_id", employeeId), + }), + transformResponse: (response) => response.data, + }), }), }); @@ -98,4 +105,5 @@ export const { useUpdateSequenceMutation, useUpdateProfileStatusMutation, useCompleteProfileMutation, + useLazyGetIntranetEmployeeQuery, } = profileApi; diff --git a/src/components/Builder/BasicInfo/index.jsx b/src/components/Builder/BasicInfo/index.jsx index 5e8abb7..eed9ff8 100644 --- a/src/components/Builder/BasicInfo/index.jsx +++ b/src/components/Builder/BasicInfo/index.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import toast from "react-hot-toast"; -import { useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import { Button, Col, DatePicker, Form, Input, Row, Select, Space } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import dayjs from "dayjs"; @@ -24,8 +24,11 @@ const BasicInfo = ({ profileData }) => { const [updateProfileService, { isLoading: isUpdating }] = useUpdateProfileMutation(); const [formChange, setFormChange] = useState(false); + const [showIntranetBanner, setShowIntranetBanner] = useState(false); const navigate = useNavigate(); + const location = useLocation(); const [form] = Form.useForm(); + const intranetData = location.state?.intranetData; useEffect(() => { if (profileData) { @@ -37,6 +40,30 @@ const BasicInfo = ({ profileData }) => { } }, [profileData, form]); + // Pre-fill from Intranet data when coming from the sync flow + useEffect(() => { + if (intranetData && !profileData) { + form.setFieldsValue({ + name: intranetData.name, + email: intranetData.email, + employee_id: intranetData.employeeId, + mobile: intranetData.mobileNumber, + gender: intranetData.gender, + years_of_experience: intranetData.yearsOfExperience, + designation: intranetData.designation, + linkedin_link: intranetData.linkedinUrl, + github_link: intranetData.githubUrl, + primary_skills: intranetData.primarySkills ?? [], + secondary_skills: intranetData.secondarySkills ?? [], + josh_joining_date: intranetData.joshJoiningDate + ? dayjs(intranetData.joshJoiningDate) + : undefined, + }); + setShowIntranetBanner(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [intranetData]); + const onFinish = async (values) => { try { if (values.years_of_experience || values.josh_joining_date) { @@ -87,6 +114,46 @@ const BasicInfo = ({ profileData }) => { onValuesChange={() => setFormChange(true)} initialValues={profileData || { description: PROFILE_DETAILS }} > + {/* Intranet pre-fill info banner */} + {showIntranetBanner && ( +
+ ℹ️ + + Form pre-filled from Intranet data. Please review and complete the + remaining fields. + + +
+ )} { + const navigate = useNavigate(); + const [step, setStep] = useState(STEP_SELECT); + const [employeeId, setEmployeeId] = useState(""); + const [fetchedEmployee, setFetchedEmployee] = useState(null); + const [errorMsg, setErrorMsg] = useState(""); + const [isFetching, setIsFetching] = useState(false); + + const resetState = () => { + setStep(STEP_SELECT); + setEmployeeId(""); + setFetchedEmployee(null); + setErrorMsg(""); + }; + + const handleClose = () => { + resetState(); + onClose(); + }; + + const handleManualCreate = () => { + resetState(); + onManualCreate(); + }; + + const handleFetch = async () => { + if (!employeeId.trim()) { + setErrorMsg("Please enter an Employee ID."); + return; + } + setErrorMsg(""); + setFetchedEmployee(null); + setIsFetching(true); + try { + const response = await axiosInstance.get( + INTRANET_EMPLOYEE_ENDPOINT.replace(":employee_id", employeeId.trim()), + ); + setFetchedEmployee(response.data?.data); + } catch (error) { + if (error.response?.status === 404) { + setErrorMsg( + "No employee found with this ID. Please check the ID or create manually.", + ); + } else { + setErrorMsg( + "Could not reach the Intranet service. Please try again or create manually.", + ); + } + } finally { + setIsFetching(false); + } + }; + + const handleProceed = () => { + navigate(EDITOR_ROUTE, { state: { intranetData: fetchedEmployee } }); + handleClose(); + }; + + const stepSelectContent = ( + + + Choose how you'd like to get started + + + setStep(STEP_LOOKUP)} + style={{ + flex: 1, + cursor: "pointer", + borderColor: "#4361ee", + background: "#f0f3ff", + textAlign: "center", + }} + > + + + Sync from Intranet + + + Auto-fill form from the employee database + + + + + + + Create Manually + + + Start with a blank form + + + + + ); + + const stepLookupContent = ( + + + +
+
+ Enter Employee ID +
+ { + setEmployeeId(e.target.value); + setErrorMsg(""); + setFetchedEmployee(null); + }} + onSearch={handleFetch} + enterButton={ + + } + size="large" + autoFocus + /> +
+ + {errorMsg && } + + {fetchedEmployee && !errorMsg && ( + + + {fetchedEmployee.name} + +  ·  {fetchedEmployee.designation}  · {" "} + {fetchedEmployee.employeeId} + +
+ } + type="success" + showIcon={false} + /> + )} + + ); + + return ( + + + Create New Employee Profile + + } + footer={ + step === STEP_LOOKUP + ? [ + , + , + ] + : null + } + width={520} + destroyOnClose + > + {step === STEP_SELECT ? stepSelectContent : stepLookupContent} + + ); +}; + +IntranetSyncModal.propTypes = { + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onManualCreate: PropTypes.func.isRequired, +}; + +export default IntranetSyncModal; diff --git a/src/components/Profile/List/index.jsx b/src/components/Profile/List/index.jsx index 55843d1..8d3bac4 100644 --- a/src/components/Profile/List/index.jsx +++ b/src/components/Profile/List/index.jsx @@ -1,7 +1,7 @@ import React, { useRef, useState } from "react"; import Highlighter from "react-highlight-words"; import toast from "react-hot-toast"; -import { Link, useNavigate } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Button, Input, @@ -41,12 +41,14 @@ import { showConfirm, } from "../../../helpers"; import Navbar from "../../Navbar"; +import IntranetSyncModal from "../IntranetSyncModal"; import styles from "./ListProfiles.module.css"; const ListProfiles = () => { const [searchText, setSearchText] = useState(""); const [searchedColumn, setSearchedColumn] = useState(""); const [activeStatus, setActiveStatus] = useState(true); + const [showIntranetModal, setShowIntranetModal] = useState(false); const searchInput = useRef(null); const navigate = useNavigate(); const { data, isFetching, refetch } = useGetProfileListQuery(); @@ -402,13 +404,24 @@ const ListProfiles = () => { Active Inactive - - - -
+
+ + setShowIntranetModal(false)} + onManualCreate={() => { + setShowIntranetModal(false); + navigate(EDITOR_ROUTE); + }} + /> Date: Thu, 28 May 2026 12:43:49 +0530 Subject: [PATCH 04/14] feat: restrict employee ID input to admin users only --- src/components/Builder/BasicInfo/index.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Builder/BasicInfo/index.jsx b/src/components/Builder/BasicInfo/index.jsx index eed9ff8..0bd6ec0 100644 --- a/src/components/Builder/BasicInfo/index.jsx +++ b/src/components/Builder/BasicInfo/index.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import toast from "react-hot-toast"; +import { useSelector } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import { Button, Col, DatePicker, Form, Input, Row, Select, Space } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; @@ -10,6 +11,7 @@ import { useUpdateProfileMutation, } from "../../../api/profileApi"; import { + ADMIN, EDITOR_PROFILE_ROUTE, GENDER, PROFILE_DETAILS, @@ -19,6 +21,7 @@ import { import { parseDate } from "../../../helpers"; const BasicInfo = ({ profileData }) => { + const role = useSelector((state) => state.auth.role); const [createProfileService, { isLoading: isCreating }] = useCreateProfileMutation(); const [updateProfileService, { isLoading: isUpdating }] = @@ -184,7 +187,10 @@ const BasicInfo = ({ profileData }) => { name="employee_id" label="Employee ID" > - + From 9d640428aab833ceff33c5f87fa791448baa3206 Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 28 May 2026 17:44:02 +0530 Subject: [PATCH 05/14] refactor: solved linting issue --- src/components/Resume/index.js | 2 +- src/helpers.js | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/Resume/index.js b/src/components/Resume/index.js index 64d82f3..c7dc726 100644 --- a/src/components/Resume/index.js +++ b/src/components/Resume/index.js @@ -1,4 +1,3 @@ -import dayjs from "dayjs"; import React, { forwardRef, useEffect, useRef, useState } from "react"; import toast from "react-hot-toast"; import { useDispatch, useSelector } from "react-redux"; @@ -15,6 +14,7 @@ import { MailOutlined, MobileOutlined, } from "@ant-design/icons"; +import dayjs from "dayjs"; import PropTypes from "prop-types"; import { useLogoutMutation } from "../../api/loginApi"; import { useCompleteProfileMutation } from "../../api/profileApi"; diff --git a/src/helpers.js b/src/helpers.js index ac9cd68..b1980d7 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -104,12 +104,21 @@ export const showConfirm = ({ onOk, onCancel, message }) => { }; export const parseDate = (date) => { - if (!date) return null; - if (dayjs.isDayjs(date)) return date; - if (typeof date === "string") return dayjs(date); - if (typeof date === "object" && date.String && date.Valid) + if (!date) { + return null; + } + if (dayjs.isDayjs(date)) { + return date; + } + if (typeof date === "string") { + return dayjs(date); + } + if (typeof date === "object" && date.String && date.Valid) { return dayjs(date.String); - if (date instanceof Date) return dayjs(date); + } + if (date instanceof Date) { + return dayjs(date); + } return null; }; From dd28ad5865898553df3d3a7633ce4b9c8b052b7e Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 28 May 2026 18:03:41 +0530 Subject: [PATCH 06/14] feat: add admin and employee mock login buttons to the authentication page --- src/components/Login/Login.module.css | 52 +++++++++++++++++++++++++++ src/components/Login/index.js | 40 +++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/components/Login/Login.module.css b/src/components/Login/Login.module.css index d3b7383..c6b05a7 100644 --- a/src/components/Login/Login.module.css +++ b/src/components/Login/Login.module.css @@ -59,3 +59,55 @@ background-image: linear-gradient(to right, #239ce2, #1369b9); color: transparent; } + +.mockButtonsContainer { + display: flex; + gap: 15px; + margin-top: 15px; +} + +.mockButtonAdmin { + background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%) !important; + color: white !important; + border: none !important; + outline: none !important; + padding: 10px 20px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + border-radius: 8px; + height: 46px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.mockButtonAdmin:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} + +.mockButtonEmployee { + background: linear-gradient(135deg, #1abc9c 0%, #16a085 100%) !important; + color: white !important; + border: none !important; + outline: none !important; + padding: 10px 20px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + border-radius: 8px; + height: 46px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.mockButtonEmployee:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} diff --git a/src/components/Login/index.js b/src/components/Login/index.js index af23f74..1d2cef8 100644 --- a/src/components/Login/index.js +++ b/src/components/Login/index.js @@ -58,6 +58,32 @@ const Login = () => { }, }); + const handleMockLogin = async (mockToken) => { + try { + const response = await loginService(mockToken); + if (response?.data) { + const { token, role, profile_id, name, email, message } = response.data; + toast.success(message || "Mock Login Successful!"); + if (token && role) { + dispatch(loginAction({ token, role, profile_id, name, email })); + setLocalStorage(profile_id, name, email, role, token); + + if (role.toLowerCase() === ADMIN) { + navigate(PROFILE_LIST_ROUTE); + } else if (role.toLowerCase() === EMPLOYEE) { + navigate(EDITOR_PROFILE_ROUTE.replace(":profile_id", profile_id)); + } else { + navigate(ROOT_ROUTE); + } + } + } else if (response?.error) { + console.error("Mock login error:", response.error); + } + } catch (error) { + toast.error("Mock Login failed."); + } + }; + return ( <>
@@ -79,6 +105,20 @@ const Login = () => { /> Sign in With Google +
+ + +
); From 75682c5906d2e1605a14b018df63f6dcd037c74c Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 28 May 2026 23:35:51 +0530 Subject: [PATCH 07/14] feat: add admin invitation functionality with dedicated UI modal and API endpoint --- src/Constants.js | 1 + src/api/emailApi.jsx | 11 ++- src/components/Profile/List/index.jsx | 105 ++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/Constants.js b/src/Constants.js index fb10744..8d55390 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -32,6 +32,7 @@ export const EMPLOYEE_INVITE_ENDPOINT = "/api/profiles/:profile_id/employee_invite"; export const PROFILE_COMPLETE_ENDPOINT = "/api/profiles/:profile_id/profile_complete"; +export const ADMIN_INVITE_ENDPOINT = "/api/admin_invite"; export const PROFILE_GET_ENDPOINT = "/api/profiles/:profile_id"; export const PROJECT_LIST_ENDPOINT = "/api/profiles/:profile_id/projects"; diff --git a/src/api/emailApi.jsx b/src/api/emailApi.jsx index 64b12ca..8e3b978 100644 --- a/src/api/emailApi.jsx +++ b/src/api/emailApi.jsx @@ -1,5 +1,6 @@ import { createApi } from "@reduxjs/toolkit/query/react"; import { + ADMIN_INVITE_ENDPOINT, EMPLOYEE_INVITE_ENDPOINT, HTTP_METHODS, USER_EMAIL_REDUCER_PATH, @@ -18,7 +19,15 @@ export const userEmailApi = createApi({ invalidatesTags: ["user_email"], transformResponse: (response) => response.data, }), + adminInvite: builder.mutation({ + query: ({ name, email }) => ({ + url: ADMIN_INVITE_ENDPOINT, + method: HTTP_METHODS.POST, + data: { name, email }, + }), + transformResponse: (response) => response.data, + }), }), }); -export const { useUserEmailMutation } = userEmailApi; +export const { useUserEmailMutation, useAdminInviteMutation } = userEmailApi; diff --git a/src/components/Profile/List/index.jsx b/src/components/Profile/List/index.jsx index f0a8c5b..06ff879 100644 --- a/src/components/Profile/List/index.jsx +++ b/src/components/Profile/List/index.jsx @@ -4,7 +4,9 @@ import toast from "react-hot-toast"; import { Link, useNavigate } from "react-router-dom"; import { Button, + Form, Input, + Modal, Radio, Row, Space, @@ -20,10 +22,16 @@ import { CloseOutlined, DeleteOutlined, EditOutlined, + MailOutlined, SearchOutlined, + UserAddOutlined, + UserOutlined, } from "@ant-design/icons"; import dayjs from "dayjs"; -import { useUserEmailMutation } from "../../../api/emailApi"; +import { + useAdminInviteMutation, + useUserEmailMutation, +} from "../../../api/emailApi"; import { useDeleteProfileMutation, useGetProfileListQuery, @@ -47,6 +55,8 @@ const ListProfiles = () => { const [searchText, setSearchText] = useState(""); const [searchedColumn, setSearchedColumn] = useState(""); const [activeStatus, setActiveStatus] = useState(true); + const [inviteAdminModalOpen, setInviteAdminModalOpen] = useState(false); + const [adminInviteForm] = Form.useForm(); const searchInput = useRef(null); const navigate = useNavigate(); const { data, isFetching, refetch } = useGetProfileListQuery(); @@ -55,6 +65,8 @@ const ListProfiles = () => { const [updateProfileStatusService, { isLoading: profileStatusUpdating }] = useUpdateProfileStatusMutation(); const [sendInvitationService, { isLoading }] = useUserEmailMutation(); + const [adminInviteService, { isLoading: invitingAdmin }] = + useAdminInviteMutation(); const showActiveInactiveModal = (profile_id, isActive) => { showConfirm({ @@ -140,6 +152,26 @@ const ListProfiles = () => { }); }; + const handleAdminInviteSubmit = async () => { + try { + const values = await adminInviteForm.validateFields(); + const response = await adminInviteService(values); + if (response?.data) { + toast.success("Admin invited successfully!"); + setInviteAdminModalOpen(false); + adminInviteForm.resetFields(); + } + } catch (validationError) { + // adminInviteForm.validateFields() rejects when fields are empty/invalid. + // Ant Design automatically shows inline errors on the fields — nothing extra needed here. + } + }; + + const handleAdminInviteCancel = () => { + setInviteAdminModalOpen(false); + adminInviteForm.resetFields(); + }; + const handleClick = (id, is_josh_employee) => { navigate(EDITOR_PROFILE_ROUTE.replace(":profile_id", id), { state: { is_josh_employee }, @@ -391,16 +423,23 @@ const ListProfiles = () => { setActiveStatus(e.target.value === "active")} + style={{ margin: "0 auto" }} > Active Inactive - - - + + + +
{ loading={isFetching} /> + + + + Invite Admin + + } + open={inviteAdminModalOpen} + onCancel={handleAdminInviteCancel} + onOk={handleAdminInviteSubmit} + okText="Send Invitation" + cancelText="Cancel" + confirmLoading={invitingAdmin} + destroyOnClose + width={440} + > +
+ + } + placeholder="e.g. John Doe" + size="large" + autoComplete="off" + /> + + + } + placeholder="e.g. john@example.com" + size="large" + autoComplete="off" + /> + + +
); }; From 41781655332946e8b3834de0f1c56b298ad91343 Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Sat, 30 May 2026 13:22:00 +0530 Subject: [PATCH 08/14] fix: handle multiple toast messages issue --- src/services/axios.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/axios.js b/src/services/axios.js index ca64467..146609a 100644 --- a/src/services/axios.js +++ b/src/services/axios.js @@ -33,8 +33,10 @@ axiosInstance.interceptors.response.use( if (error.response && error.response.status === 401) { store.dispatch(logout()); window.localStorage.clear(); - toast.error("Unauthorized Access"); - history.push(ROOT_ROUTE); + toast.error("Unauthorized Access", { id: "unauthorized" }); + setTimeout(() => { + window.location.href = ROOT_ROUTE; + }, 500); return Promise.reject(error); } error.response?.data?.error_code From 07a6f8859ef2b0a7b4d7609764bbbc29faa486a7 Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Mon, 8 Jun 2026 13:26:15 +0530 Subject: [PATCH 09/14] fix: handle error when syncing existing employee profiles --- src/components/Profile/IntranetSyncModal/index.jsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/Profile/IntranetSyncModal/index.jsx b/src/components/Profile/IntranetSyncModal/index.jsx index 3237579..7b5848e 100644 --- a/src/components/Profile/IntranetSyncModal/index.jsx +++ b/src/components/Profile/IntranetSyncModal/index.jsx @@ -64,14 +64,12 @@ const IntranetSyncModal = ({ open, onClose, onManualCreate }) => { ); setFetchedEmployee(response.data?.data); } catch (error) { - if (error.response?.status === 404) { - setErrorMsg( - "No employee found with this ID. Please check the ID or create manually.", - ); + if (error.response?.status === 409) { + setErrorMsg("Profile already exists for this Employee ID."); + } else if (error.response?.status === 404) { + setErrorMsg("No employee found with this ID. Please check the ID or create manually."); } else { - setErrorMsg( - "Could not reach the Intranet service. Please try again or create manually.", - ); + setErrorMsg("Could not reach the Intranet service. Please try again or create manually."); } } finally { setIsFetching(false); From b755380710eea1c2e4c9246df010eabde8875247 Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Mon, 8 Jun 2026 19:58:03 +0530 Subject: [PATCH 10/14] feat: add support for suppressing specific error toast messages via axios configuration --- src/components/Profile/IntranetSyncModal/index.jsx | 6 +++++- src/services/axios.js | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Profile/IntranetSyncModal/index.jsx b/src/components/Profile/IntranetSyncModal/index.jsx index 7b5848e..ccb4c60 100644 --- a/src/components/Profile/IntranetSyncModal/index.jsx +++ b/src/components/Profile/IntranetSyncModal/index.jsx @@ -61,11 +61,15 @@ const IntranetSyncModal = ({ open, onClose, onManualCreate }) => { try { const response = await axiosInstance.get( INTRANET_EMPLOYEE_ENDPOINT.replace(":employee_id", employeeId.trim()), + { _suppressToastForStatuses: [409] }, ); setFetchedEmployee(response.data?.data); } catch (error) { if (error.response?.status === 409) { - setErrorMsg("Profile already exists for this Employee ID."); + setErrorMsg( + error.response?.data?.error_message || + "Profile already exists for this Employee ID." + ); } else if (error.response?.status === 404) { setErrorMsg("No employee found with this ID. Please check the ID or create manually."); } else { diff --git a/src/services/axios.js b/src/services/axios.js index 146609a..404ccb9 100644 --- a/src/services/axios.js +++ b/src/services/axios.js @@ -39,6 +39,10 @@ axiosInstance.interceptors.response.use( }, 500); return Promise.reject(error); } + const suppressFor = error.config?._suppressToastForStatuses; + if (Array.isArray(suppressFor) && suppressFor.includes(error.response?.status)) { + return Promise.reject(error); + } error.response?.data?.error_code ? toast.error(error.response?.data?.error_message) : toast.error(NETWORK_ERROR); From 8d3e7dfd02bf7927b2115cbd654ff9eeec653a2c Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 11 Jun 2026 01:17:26 +0530 Subject: [PATCH 11/14] refactor: standardize RTK Query error handling, replace manual alert with Ant Design component, and update toast configurations --- src/api/axiosBaseQuery/service.jsx | 13 +++-- src/api/profileApi.jsx | 2 + src/components/Builder/BasicInfo/index.jsx | 47 ++++--------------- .../Profile/IntranetSyncModal/index.jsx | 24 ++++------ src/components/Profile/List/index.jsx | 3 +- 5 files changed, 32 insertions(+), 57 deletions(-) diff --git a/src/api/axiosBaseQuery/service.jsx b/src/api/axiosBaseQuery/service.jsx index 44eba21..587042a 100644 --- a/src/api/axiosBaseQuery/service.jsx +++ b/src/api/axiosBaseQuery/service.jsx @@ -2,7 +2,7 @@ import axiosInstance from "../../services/axios"; const axiosBaseQuery = () => - async ({ url, method, data, params, headers, body }) => { + async ({ url, method, data, params, headers, body, ...rest }) => { try { const result = await axiosInstance({ url: url, @@ -11,10 +11,17 @@ const axiosBaseQuery = params, headers, body, + ...rest, }); - return Promise.resolve(result); + return { data: result.data }; } catch (axiosError) { - return Promise.reject(axiosError?.response?.data); + return { + error: { + status: axiosError?.response?.status, + data: axiosError?.response?.data, + message: axiosError?.message, + }, + }; } }; diff --git a/src/api/profileApi.jsx b/src/api/profileApi.jsx index 63c8545..7003050 100644 --- a/src/api/profileApi.jsx +++ b/src/api/profileApi.jsx @@ -90,6 +90,8 @@ export const profileApi = createApi({ getIntranetEmployee: builder.query({ query: (employeeId) => ({ url: INTRANET_EMPLOYEE_ENDPOINT.replace(":employee_id", employeeId), + method: HTTP_METHODS.GET, + _suppressToastForStatuses: [409], }), transformResponse: (response) => response.data, }), diff --git a/src/components/Builder/BasicInfo/index.jsx b/src/components/Builder/BasicInfo/index.jsx index 0bd6ec0..2b87eef 100644 --- a/src/components/Builder/BasicInfo/index.jsx +++ b/src/components/Builder/BasicInfo/index.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import toast from "react-hot-toast"; import { useSelector } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; -import { Button, Col, DatePicker, Form, Input, Row, Select, Space } from "antd"; +import { Alert, Button, Col, DatePicker, Form, Input, Row, Select, Space } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import dayjs from "dayjs"; import PropTypes from "prop-types"; @@ -119,43 +119,14 @@ const BasicInfo = ({ profileData }) => { > {/* Intranet pre-fill info banner */} {showIntranetBanner && ( -
- ℹ️ - - Form pre-filled from Intranet data. Please review and complete the - remaining fields. - - -
+ setShowIntranetBanner(false)} + style={{ marginBottom: "20px" }} + /> )}
diff --git a/src/components/Profile/IntranetSyncModal/index.jsx b/src/components/Profile/IntranetSyncModal/index.jsx index ccb4c60..083584c 100644 --- a/src/components/Profile/IntranetSyncModal/index.jsx +++ b/src/components/Profile/IntranetSyncModal/index.jsx @@ -17,8 +17,8 @@ import { UserOutlined, } from "@ant-design/icons"; import PropTypes from "prop-types"; -import { EDITOR_ROUTE, INTRANET_EMPLOYEE_ENDPOINT } from "../../../Constants"; -import axiosInstance from "../../../services/axios"; +import { EDITOR_ROUTE } from "../../../Constants"; +import { useLazyGetIntranetEmployeeQuery } from "../../../api/profileApi"; const { Title, Text, Paragraph } = Typography; @@ -31,7 +31,7 @@ const IntranetSyncModal = ({ open, onClose, onManualCreate }) => { const [employeeId, setEmployeeId] = useState(""); const [fetchedEmployee, setFetchedEmployee] = useState(null); const [errorMsg, setErrorMsg] = useState(""); - const [isFetching, setIsFetching] = useState(false); + const [fetchIntranetEmployee, { isFetching }] = useLazyGetIntranetEmployeeQuery(); const resetState = () => { setStep(STEP_SELECT); @@ -57,26 +57,20 @@ const IntranetSyncModal = ({ open, onClose, onManualCreate }) => { } setErrorMsg(""); setFetchedEmployee(null); - setIsFetching(true); try { - const response = await axiosInstance.get( - INTRANET_EMPLOYEE_ENDPOINT.replace(":employee_id", employeeId.trim()), - { _suppressToastForStatuses: [409] }, - ); - setFetchedEmployee(response.data?.data); + const employee = await fetchIntranetEmployee(employeeId.trim()).unwrap(); + setFetchedEmployee(employee); } catch (error) { - if (error.response?.status === 409) { + if (error.status === 409) { setErrorMsg( - error.response?.data?.error_message || + error.data?.error_message || "Profile already exists for this Employee ID." ); - } else if (error.response?.status === 404) { + } else if (error.status === 404) { setErrorMsg("No employee found with this ID. Please check the ID or create manually."); } else { setErrorMsg("Could not reach the Intranet service. Please try again or create manually."); } - } finally { - setIsFetching(false); } }; @@ -156,7 +150,7 @@ const IntranetSyncModal = ({ open, onClose, onManualCreate }) => { { setEmployeeId(e.target.value); diff --git a/src/components/Profile/List/index.jsx b/src/components/Profile/List/index.jsx index 4ace545..9de5c4c 100644 --- a/src/components/Profile/List/index.jsx +++ b/src/components/Profile/List/index.jsx @@ -42,6 +42,7 @@ import { EDITOR_ROUTE, LOADING_SPIN, SPIN_SIZE, + SUCCESS_TOASTER, } from "../../../Constants"; import { calculateTotalExperience, @@ -159,7 +160,7 @@ const ListProfiles = () => { const values = await adminInviteForm.validateFields(); const response = await adminInviteService(values); if (response?.data) { - toast.success("Admin invited successfully!"); + toast.success("Admin invited successfully!", SUCCESS_TOASTER); setInviteAdminModalOpen(false); adminInviteForm.resetFields(); } From bf243ebc97cc3a3ddc1e971b980cd074cdb26c28 Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 11 Jun 2026 01:29:37 +0530 Subject: [PATCH 12/14] fix: sanitize employee_id inputs by trimming whitespace --- src/components/Builder/BasicInfo/index.jsx | 5 +++++ src/components/Profile/IntranetSyncModal/index.jsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Builder/BasicInfo/index.jsx b/src/components/Builder/BasicInfo/index.jsx index 2b87eef..6ad431b 100644 --- a/src/components/Builder/BasicInfo/index.jsx +++ b/src/components/Builder/BasicInfo/index.jsx @@ -79,6 +79,11 @@ const BasicInfo = ({ profileData }) => { values.josh_joining_date.format("MMM-YYYY"); } } + + if (values.employee_id && typeof values.employee_id === "string") { + values.employee_id = values.employee_id.trim(); + } + let response; if (profileData) { if (formChange) { diff --git a/src/components/Profile/IntranetSyncModal/index.jsx b/src/components/Profile/IntranetSyncModal/index.jsx index 083584c..2228c17 100644 --- a/src/components/Profile/IntranetSyncModal/index.jsx +++ b/src/components/Profile/IntranetSyncModal/index.jsx @@ -153,7 +153,7 @@ const IntranetSyncModal = ({ open, onClose, onManualCreate }) => { placeholder="e.g. 1001 or JIN1001" value={employeeId} onChange={(e) => { - setEmployeeId(e.target.value); + setEmployeeId(e.target.value.trim()); setErrorMsg(""); setFetchedEmployee(null); }} From 8a935609ea73315d4e92938ba43c70eedd4880e8 Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 11 Jun 2026 01:47:03 +0530 Subject: [PATCH 13/14] style: update employee ID column width and rename active status column header --- src/components/Profile/List/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Profile/List/index.jsx b/src/components/Profile/List/index.jsx index 9de5c4c..d27bc2c 100644 --- a/src/components/Profile/List/index.jsx +++ b/src/components/Profile/List/index.jsx @@ -287,7 +287,7 @@ const ListProfiles = () => { title: "Employee ID", dataIndex: "employee_id", key: "employee_id", - width: "6%", + width: "8%", ...getColumnSearchProps("employee_id"), }, { @@ -347,7 +347,7 @@ const ListProfiles = () => { render: (date) => formatDate(date), }, { - title: "Is Josh Employee", + title: "Active Status", dataIndex: "is_josh_employee", key: "is_josh_employee", render: (_, record) => ( From 8560db47bfb157a352fa27ec73ebd0a9b287d31e Mon Sep 17 00:00:00 2001 From: irfanmohammad01 Date: Thu, 11 Jun 2026 20:49:16 +0530 Subject: [PATCH 14/14] refactor: remove mock login functionality and fix eslint issue --- src/components/Login/Login.module.css | 52 ------------------- src/components/Login/index.js | 40 -------------- .../Profile/IntranetSyncModal/index.jsx | 2 +- 3 files changed, 1 insertion(+), 93 deletions(-) diff --git a/src/components/Login/Login.module.css b/src/components/Login/Login.module.css index c6b05a7..d3b7383 100644 --- a/src/components/Login/Login.module.css +++ b/src/components/Login/Login.module.css @@ -59,55 +59,3 @@ background-image: linear-gradient(to right, #239ce2, #1369b9); color: transparent; } - -.mockButtonsContainer { - display: flex; - gap: 15px; - margin-top: 15px; -} - -.mockButtonAdmin { - background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%) !important; - color: white !important; - border: none !important; - outline: none !important; - padding: 10px 20px; - font-size: 16px; - font-weight: 500; - cursor: pointer; - border-radius: 8px; - height: 46px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); -} - -.mockButtonAdmin:hover { - transform: translateY(-2px); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); -} - -.mockButtonEmployee { - background: linear-gradient(135deg, #1abc9c 0%, #16a085 100%) !important; - color: white !important; - border: none !important; - outline: none !important; - padding: 10px 20px; - font-size: 16px; - font-weight: 500; - cursor: pointer; - border-radius: 8px; - height: 46px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); -} - -.mockButtonEmployee:hover { - transform: translateY(-2px); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); -} diff --git a/src/components/Login/index.js b/src/components/Login/index.js index 1d2cef8..af23f74 100644 --- a/src/components/Login/index.js +++ b/src/components/Login/index.js @@ -58,32 +58,6 @@ const Login = () => { }, }); - const handleMockLogin = async (mockToken) => { - try { - const response = await loginService(mockToken); - if (response?.data) { - const { token, role, profile_id, name, email, message } = response.data; - toast.success(message || "Mock Login Successful!"); - if (token && role) { - dispatch(loginAction({ token, role, profile_id, name, email })); - setLocalStorage(profile_id, name, email, role, token); - - if (role.toLowerCase() === ADMIN) { - navigate(PROFILE_LIST_ROUTE); - } else if (role.toLowerCase() === EMPLOYEE) { - navigate(EDITOR_PROFILE_ROUTE.replace(":profile_id", profile_id)); - } else { - navigate(ROOT_ROUTE); - } - } - } else if (response?.error) { - console.error("Mock login error:", response.error); - } - } catch (error) { - toast.error("Mock Login failed."); - } - }; - return ( <>
@@ -105,20 +79,6 @@ const Login = () => { /> Sign in With Google -
- - -
); diff --git a/src/components/Profile/IntranetSyncModal/index.jsx b/src/components/Profile/IntranetSyncModal/index.jsx index 2228c17..2740826 100644 --- a/src/components/Profile/IntranetSyncModal/index.jsx +++ b/src/components/Profile/IntranetSyncModal/index.jsx @@ -17,8 +17,8 @@ import { UserOutlined, } from "@ant-design/icons"; import PropTypes from "prop-types"; -import { EDITOR_ROUTE } from "../../../Constants"; import { useLazyGetIntranetEmployeeQuery } from "../../../api/profileApi"; +import { EDITOR_ROUTE } from "../../../Constants"; const { Title, Text, Paragraph } = Typography;