diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index e89d9dc..1979ef5 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -69,6 +69,7 @@ module.exports = {
"no-unsafe-optional-chaining": "off",
"react/no-unescaped-entities": "off",
'@typescript-eslint/no-explicit-any': 0,
+ "react/prop-types": "off",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
diff --git a/src/__test__/passwordUpdate.test.tsx b/src/__test__/passwordUpdate.test.tsx
new file mode 100644
index 0000000..ec8155b
--- /dev/null
+++ b/src/__test__/passwordUpdate.test.tsx
@@ -0,0 +1,122 @@
+import "@testing-library/jest-dom";
+import {
+ render,
+ screen,
+ fireEvent,
+ act,
+ waitFor,
+} from "@testing-library/react";
+import { Provider } from "react-redux";
+import { BrowserRouter as Router } from "react-router-dom";
+import configureStore from "redux-mock-store";
+import { thunk } from "redux-thunk";
+import { ToastContainer } from "react-toastify";
+
+import UpdatePasswordmod from "../components/password/updateModal";
+// import { updatePassword } from "../redux/api/updatePasswordApiSlice";
+// import updatePasswordApiSlice from "../redux/api/updatePasswordApiSlice";
+
+jest.mock("react-toastify", () => ({
+ toast: {
+ success: jest.fn(),
+ error: jest.fn(),
+ },
+ ToastContainer: () =>
,
+}));
+
+jest.mock("react-redux", () => ({
+ ...jest.requireActual("react-redux"),
+ useDispatch: () => jest.fn(),
+}));
+
+const middlewares = [thunk];
+// @ts-ignore
+const mockStore = configureStore(middlewares);
+const setPasswordModal = jest.fn();
+// @ts-ignore
+const renderComponent = (store) => render(
+
+
+
+
+
+ ,
+);
+
+describe("Update Password Modal", () => {
+ let store;
+ beforeEach(() => {
+ store = mockStore({
+ updatePassword: {
+ loading: false,
+ },
+ });
+ jest.clearAllMocks();
+ });
+
+ it("update Password Modal renders correctly", () => {
+ renderComponent(store);
+ expect(screen.getByPlaceholderText("Old Password")).toBeInTheDocument();
+ expect(screen.getByPlaceholderText("New Password")).toBeInTheDocument();
+ expect(screen.getByPlaceholderText("Confirm Password")).toBeInTheDocument();
+ });
+
+ it("handles input and form submission", async () => {
+ // const mockUpdatePassword = jest.fn();
+ // (useDispatch as unknown as jest.Mock).mockReturnValue(mockUpdatePassword);
+ const mockDispatch = jest.fn();
+ jest.mock("react-redux", () => ({
+ useDispatch: () => mockDispatch,
+ }));
+
+ renderComponent(store);
+ const currentPasswordInput = screen.getByPlaceholderText("Old Password");
+ const newPasswordInput = screen.getByPlaceholderText("New Password");
+ const confirmNewPasswordInput = screen.getByPlaceholderText("Confirm Password");
+ const updateButton = screen.getByRole("button", { name: /Save Changes/i });
+
+ await act(() => {
+ fireEvent.change(currentPasswordInput, { target: { value: "Test@123" } });
+ fireEvent.change(newPasswordInput, { target: { value: "NewTest@123" } });
+ fireEvent.change(confirmNewPasswordInput, {
+ target: { value: "NewTest@123" },
+ });
+ });
+
+ await act(() => {
+ fireEvent.click(updateButton);
+ console.log("updateButton", updateButton.textContent);
+ });
+
+ await waitFor(() => {
+ expect(mockDispatch).toHaveBeenCalledTimes(0);
+ expect(updateButton).toHaveTextContent("Save Changes");
+ expect(setPasswordModal).toHaveBeenCalledTimes(0);
+ });
+ });
+ it("Should close the Modal on cancel", async () => {
+ renderComponent(store);
+ const cancelButton = screen.getByRole("button", { name: /Cancel/i });
+ await act(() => {
+ fireEvent.click(cancelButton);
+ });
+ await waitFor(() => {
+ expect(setPasswordModal).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ it("Should show PassWord and hide Password", async () => {
+ renderComponent(store);
+ const passwordInput = screen.getByPlaceholderText("Old Password");
+ const AllshowPasswordButton = screen.getAllByRole("button", {
+ name: /Show/i,
+ });
+ const showPasswordButton = AllshowPasswordButton[0];
+ await act(() => {
+ fireEvent.click(showPasswordButton);
+ });
+ await waitFor(() => {
+ expect(passwordInput).toHaveAttribute("type", "text");
+ });
+ });
+});
diff --git a/src/__test__/updatePasswordApiSlice.test.tsx b/src/__test__/updatePasswordApiSlice.test.tsx
new file mode 100644
index 0000000..793405c
--- /dev/null
+++ b/src/__test__/updatePasswordApiSlice.test.tsx
@@ -0,0 +1,56 @@
+import { configureStore } from "@reduxjs/toolkit";
+import axios from "axios";
+
+import updatePasswordApiSlice, {
+ updatePassword,
+} from "../redux/api/updatePasswordApiSlice";
+
+jest.mock("axios");
+
+describe("updatePasswordApiSlice", () => {
+ let store;
+
+ beforeEach(() => {
+ store = configureStore({
+ reducer: {
+ updatePassword: updatePasswordApiSlice,
+ },
+ });
+ });
+
+ it("handles successful password update", async () => {
+ const mockResponse = { data: { message: "Password updated successfully" } };
+ // @ts-ignore
+ axios.put.mockResolvedValueOnce(mockResponse);
+
+ await store.dispatch(
+ updatePassword({
+ oldPassword: "Test@123",
+ newPassword: "NewTest@123",
+ confirmPassword: "NewTest@123",
+ }),
+ );
+
+ const state = store.getState();
+ expect(state.updatePassword.loading).toBe(false);
+ expect(state.updatePassword.error).toBe(null);
+ });
+
+ it("handles failed password update", async () => {
+ const mockError = { response: { data: { message: "Update failed" } } };
+ // @ts-ignore
+ axios.put.mockRejectedValueOnce(mockError);
+
+ await store.dispatch(
+ updatePassword({
+ oldPassword: "Test@123",
+ newPassword: "NewTest@123",
+ confirmPassword: "NewTest@123",
+ }),
+ );
+
+ const state = store.getState();
+ expect(state.updatePassword.loading).toBe(false);
+ expect(state.updatePassword.error).toBe(null);
+ });
+});
diff --git a/src/components/common/auth/Button.tsx b/src/components/common/auth/Button.tsx
index 59d634b..94f64ed 100644
--- a/src/components/common/auth/Button.tsx
+++ b/src/components/common/auth/Button.tsx
@@ -5,6 +5,8 @@ interface ButtonProps {
disabled?: boolean;
dataTestId?: string;
backgroundColor?: string;
+ className?: string;
+ onClick?: () => void;
}
const Button: React.FC = ({
@@ -12,12 +14,15 @@ const Button: React.FC = ({
disabled,
dataTestId,
backgroundColor,
+ className,
+ onClick,
}) => (
diff --git a/src/components/common/auth/InputField.tsx b/src/components/common/auth/InputField.tsx
index 1d00197..7a9ebc0 100644
--- a/src/components/common/auth/InputField.tsx
+++ b/src/components/common/auth/InputField.tsx
@@ -8,6 +8,7 @@ interface InputFieldProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
register: UseFormRegister;
error: string | undefined;
+ className?: string;
}
const InputField: React.FC = ({
@@ -16,10 +17,11 @@ const InputField: React.FC = ({
placeholder,
register,
error,
+ className,
}) => (
= ({
+ id,
+ placeholder,
+ register,
+ error,
+}) => {
+ const [showPassword, setShowPassword] = useState(false);
+
+ return (
+
+
+
+ {error &&
{error.message}
}
+
+ );
+};
+
+export default PasswordInput;
diff --git a/src/components/password/updateModal.tsx b/src/components/password/updateModal.tsx
new file mode 100644
index 0000000..9ed4748
--- /dev/null
+++ b/src/components/password/updateModal.tsx
@@ -0,0 +1,102 @@
+import React, { useState } from "react";
+import { useForm } from "react-hook-form";
+import { yupResolver } from "@hookform/resolvers/yup";
+import { toast, ToastContainer } from "react-toastify";
+import { AxiosError } from "axios";
+import { useDispatch } from "react-redux";
+
+import updatePasswordSchema from "../../schemas/updatePasswordSchema";
+import { updatePassword } from "../../redux/api/updatePasswordApiSlice";
+import PasswordInput from "../common/auth/password";
+
+interface UpdatePasswordProps {
+ setPasswordModal: (isOpen: boolean) => void;
+}
+
+const UpdatePasswordmod: React.FC
= ({
+ setPasswordModal,
+}) => {
+ const dispatch = useDispatch();
+ const [loading, setLoading] = useState(false);
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: yupResolver(updatePasswordSchema),
+ });
+
+ const onSubmit = async (data: {
+ oldPassword: string;
+ newPassword: string;
+ confirmPassword: string;
+ }) => {
+ try {
+ setLoading(true);
+ // @ts-ignore
+ const response = await dispatch(updatePassword(data)).unwrap();
+ setLoading(false);
+ toast.success(response.message);
+ setTimeout(() => {
+ setPasswordModal(false);
+ }, 3000);
+ } catch (err) {
+ setLoading(false);
+ const error = err as AxiosError;
+ toast.error(error.message);
+ }
+ };
+
+ return (
+
+
+
+
+ Update Password
+
+
+
+
+ );
+};
+
+export default UpdatePasswordmod;
diff --git a/src/pages/passwordUpdatePage.tsx b/src/pages/passwordUpdatePage.tsx
new file mode 100644
index 0000000..f24412f
--- /dev/null
+++ b/src/pages/passwordUpdatePage.tsx
@@ -0,0 +1,25 @@
+import { useState } from "react";
+
+import UpdatePasswordmod from "../components/password/updateModal";
+
+const UpdatePasswordPage = () => {
+ const [PasswordModal, setPasswordModal] = useState(false);
+ return (
+
+
+ {PasswordModal && (
+
+ )}
+
+ );
+};
+
+export default UpdatePasswordPage;
diff --git a/src/redux/api/updatePasswordApiSlice.ts b/src/redux/api/updatePasswordApiSlice.ts
new file mode 100644
index 0000000..4afe4e1
--- /dev/null
+++ b/src/redux/api/updatePasswordApiSlice.ts
@@ -0,0 +1,69 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import { AxiosError } from "axios";
+
+import axios from "./api";
+
+interface UpdatePasswordState {
+ loading: boolean;
+ error: string | null;
+}
+
+const initialState: UpdatePasswordState = {
+ loading: false,
+ error: null,
+};
+
+interface UpdatePasswordPayload {
+ oldPassword: string;
+ newPassword: string;
+ confirmPassword: string;
+}
+
+interface UpdatePasswordResponse {
+ message: string;
+}
+
+interface UpdatePasswordError {
+ message: string;
+}
+const token = localStorage.getItem("accessToken");
+export const updatePassword = createAsyncThunk<
+UpdatePasswordResponse,
+UpdatePasswordPayload,
+{ rejectValue: UpdatePasswordError }
+>("updatePassword", async (payload, { rejectWithValue }) => {
+ try {
+ const response = await axios.put("/users/passwordupdate", payload, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return response.data;
+ } catch (error) {
+ const axiosError = error as AxiosError;
+ return rejectWithValue(axiosError.response?.data as UpdatePasswordError);
+ }
+});
+
+const updatePasswordApiSlice = createSlice({
+ name: "updatePassword",
+ initialState,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder
+ .addCase(updatePassword.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(updatePassword.fulfilled, (state) => {
+ state.loading = false;
+ state.error = null;
+ })
+ .addCase(updatePassword.rejected, (state, { payload }) => {
+ state.loading = false;
+ state.error = payload?.message || null;
+ });
+ },
+});
+
+export default updatePasswordApiSlice.reducer;
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 3105127..5eaf138 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -6,6 +6,7 @@ import otpVerificationReucer from "./api/otpApiSclice";
import getLinkReducer from "./reducers/getLinkSlice";
import resetReducer from "./reducers/resetPasswordSlice";
import categoriesReducer from "./reducers/categoriesSlice";
+import updatePasswordApiSlice from "./api/updatePasswordApiSlice";
const store = configureStore({
reducer: {
@@ -15,6 +16,7 @@ const store = configureStore({
getLink: getLinkReducer,
reset: resetReducer,
categories: categoriesReducer,
+ updatePassword: updatePasswordApiSlice,
},
});
diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx
index 0e477f9..a768e5d 100644
--- a/src/routes/AppRoutes.tsx
+++ b/src/routes/AppRoutes.tsx
@@ -11,6 +11,7 @@ import GetLinkPage from "../pages/GetLinkPage";
import ResetPassword from "../pages/ResetPassword";
import SellerDashboard from "../dashboard/sellers/Index";
import AddProduct from "../dashboard/sellers/AddProduct";
+import UpdatePasswordPage from "../pages/passwordUpdatePage";
const AppRoutes = () => (
@@ -26,6 +27,7 @@ const AppRoutes = () => (
} />
} />
} />
+ } />
);
diff --git a/src/schemas/updatePasswordSchema.ts b/src/schemas/updatePasswordSchema.ts
new file mode 100644
index 0000000..6bfd0b1
--- /dev/null
+++ b/src/schemas/updatePasswordSchema.ts
@@ -0,0 +1,24 @@
+import { object, string, ref } from "yup";
+
+const updatePasswordSchema = object({
+ oldPassword: string().required("Old password is required"),
+ newPassword: string()
+ .required("New password is required")
+ .min(8, "Password must be at least 8 characters long")
+ .matches(/[a-z]/, "Password must contain at least one lowercase letter")
+ .matches(/[A-Z]/, "Password must contain at least one uppercase letter")
+ .matches(/[0-9]/, "Password must contain at least one number")
+ .matches(
+ /[!@#$%^&*(),.?":{}|<>]/,
+ "Password must contain at least one special character",
+ )
+ .notOneOf(
+ [ref("oldPassword")],
+ "New password must be different from old password",
+ ),
+ confirmPassword: string()
+ .required("Confirm password is required")
+ .oneOf([ref("newPassword")], "Confirm password must match new password"),
+});
+
+export default updatePasswordSchema;