diff --git a/src/constants/Api.ts b/src/constants/Api.ts deleted file mode 100644 index 82dca5cf..00000000 --- a/src/constants/Api.ts +++ /dev/null @@ -1 +0,0 @@ -export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://localhost:3002"; \ No newline at end of file diff --git a/src/pages/Authentication/ForgotPassword.tsx b/src/pages/Authentication/ForgotPassword.tsx index 4681d3d7..c90538c4 100644 --- a/src/pages/Authentication/ForgotPassword.tsx +++ b/src/pages/Authentication/ForgotPassword.tsx @@ -2,11 +2,11 @@ import React from "react"; import { Button, Col, Container } from "react-bootstrap"; import { Form, Formik, FormikHelpers } from "formik"; import FormInput from "../../components/Form/FormInput"; -import axios, { AxiosError } from "axios"; import { alertActions } from "../../store/slices/alertSlice"; import { useDispatch } from "react-redux"; -import { API_BASE_URL } from "../../constants/Api"; import * as Yup from "yup"; +import { AxiosError } from "axios"; +import axiosClient from "../../utils/axios_client"; interface IForgotPasswordFormValues { email: string; @@ -24,7 +24,7 @@ const ForgotPassword = () => { submitProps: FormikHelpers ) => { try { - await axios.post(`${API_BASE_URL}/password_resets`, { email: values.email }); + await axiosClient.post(`/password_resets`, { email: values.email }); dispatch( alertActions.showAlert({ variant: "success", diff --git a/src/pages/Authentication/ResetPassword.tsx b/src/pages/Authentication/ResetPassword.tsx index 4ed478c0..9d91754e 100644 --- a/src/pages/Authentication/ResetPassword.tsx +++ b/src/pages/Authentication/ResetPassword.tsx @@ -3,11 +3,11 @@ import { Button, Col, Container } from "react-bootstrap"; import { Form, Formik, FormikHelpers } from "formik"; import FormInput from "../../components/Form/FormInput"; import { useLocation, useNavigate } from "react-router-dom"; -import axios, { AxiosError } from "axios"; import { alertActions } from "../../store/slices/alertSlice"; import { useDispatch } from "react-redux"; -import { API_BASE_URL } from "../../constants/Api"; import * as Yup from "yup"; +import { AxiosError } from "axios"; +import axiosClient from "../../utils/axios_client"; interface IResetPasswordFormValues { password: string; @@ -49,7 +49,7 @@ const ResetPassword = () => { ) => { try { // Send password reset request to the backend - await axios.put(`${API_BASE_URL}/password_resets/${token}`, { + await axiosClient.put(`/password_resets/${token}`, { user: { password: values.password }, }); dispatch( diff --git a/src/pages/Authentication/__tests__/ForgotPassword.test.tsx b/src/pages/Authentication/__tests__/ForgotPassword.test.tsx index a049c235..76b72d7b 100644 --- a/src/pages/Authentication/__tests__/ForgotPassword.test.tsx +++ b/src/pages/Authentication/__tests__/ForgotPassword.test.tsx @@ -6,10 +6,10 @@ import { Provider } from "react-redux"; import { configureStore } from "@reduxjs/toolkit"; import alertReducer from "store/slices/alertSlice"; import { vi } from "vitest"; -import axios from "axios"; import { AxiosError } from "axios"; +import axiosClient from "../../../utils/axios_client"; -vi.mock('axios'); +vi.mock("../../../utils/axios_client"); beforeEach(() => { vi.clearAllMocks(); @@ -66,7 +66,7 @@ describe('Test Forgot Password Form Validations', () => { await user.tab(); await user.click(submitButton); - expect(axios.post).not.toHaveBeenCalled(); + expect(axiosClient.post).not.toHaveBeenCalled(); expect(screen.getByText(/required/i)).toBeInTheDocument(); }); @@ -90,14 +90,14 @@ describe('Test Forgot Password Form Validations', () => { expect(screen.getByText(/invalid email address/i)).toBeInTheDocument(); }); expect(submitButton).toBeDisabled(); - expect(axios.post).not.toHaveBeenCalled(); + expect(axiosClient.post).not.toHaveBeenCalled(); }); }); describe('Test Forgot Password Api Error', () => { it('Handles API unavailable', async () => { const user = userEvent.setup(); - (axios.post as any).mockRejectedValue( + (axiosClient.post as any).mockRejectedValue( new AxiosError("Network Error", 'ERR_NETWORK') ); @@ -120,7 +120,7 @@ describe('Test Forgot Password Api Error', () => { expect(state.alert.variant).toBe('danger'); }); - expect(axios.post).toHaveBeenCalledWith( + expect(axiosClient.post).toHaveBeenCalledWith( expect.stringContaining('/password_resets'), {email: validEmail} ); }); @@ -129,7 +129,7 @@ describe('Test Forgot Password Api Error', () => { describe('Test Successful Password Reset Request', () => { it('submit form successfully', async () => { const user = userEvent.setup(); - (axios.post as any).mockResolvedValue({ + (axiosClient.post as any).mockResolvedValue({ status: 200, data: { message: 'If the email exists, a reset link has been sent.'}, }); @@ -139,14 +139,14 @@ describe('Test Successful Password Reset Request', () => { ); - + let emailInput = screen.getByRole('textbox'); let submitButton = screen.getByRole('button', {name: submitText}); await user.type(emailInput, validEmail); await user.click(submitButton); - expect(axios.post).toHaveBeenCalledWith( + expect(axiosClient.post).toHaveBeenCalledWith( expect.stringContaining('/password_resets'), {email: validEmail} ); diff --git a/src/pages/Authentication/__tests__/ResetPassword.test.tsx b/src/pages/Authentication/__tests__/ResetPassword.test.tsx index 8d916761..f48130c4 100644 --- a/src/pages/Authentication/__tests__/ResetPassword.test.tsx +++ b/src/pages/Authentication/__tests__/ResetPassword.test.tsx @@ -7,10 +7,10 @@ import { configureStore } from "@reduxjs/toolkit"; import alertReducer from "store/slices/alertSlice"; import { MemoryRouter, Route, Routes } from "react-router-dom"; import { vi } from "vitest"; -import axios from "axios"; import { AxiosError } from "axios"; +import axiosClient from "../../../utils/axios_client"; -vi.mock("axios"); +vi.mock("../../../utils/axios_client"); beforeEach(() => { vi.clearAllMocks(); @@ -104,7 +104,7 @@ describe("Test Reset Password Form Validations", () => { expect(screen.getByText(/password must be at least 6 characters/i)).toBeInTheDocument(); }); expect(submitButton).toBeDisabled(); - expect(axios.put).not.toHaveBeenCalled(); + expect(axiosClient.put).not.toHaveBeenCalled(); }); it("does not submit form when passwords do not match", async () => { @@ -123,14 +123,14 @@ describe("Test Reset Password Form Validations", () => { expect(screen.getByText(/passwords do not match/i)).toBeInTheDocument(); }); expect(submitButton).toBeDisabled(); - expect(axios.put).not.toHaveBeenCalled(); + expect(axiosClient.put).not.toHaveBeenCalled(); }); }); describe("Test Successful Password Reset", () => { it("submits form successfully", async () => { const user = userEvent.setup(); - (axios.put as any).mockResolvedValue({ + (axiosClient.put as any).mockResolvedValue({ status: 200, data: { message: "Password Successfully Updated" }, }); @@ -147,7 +147,7 @@ describe("Test Successful Password Reset", () => { await user.click(submitButton); await waitFor(() => { - expect(axios.put).toHaveBeenCalledWith( + expect(axiosClient.put).toHaveBeenCalledWith( expect.stringContaining("/password_resets/valid-token"), { user: { password: validPassword } } ); @@ -161,7 +161,7 @@ describe("Test Successful Password Reset", () => { describe("Test Reset Password Api Error", () => { it("handles API unavailable", async () => { const user = userEvent.setup(); - (axios.put as any).mockRejectedValue( + (axiosClient.put as any).mockRejectedValue( new AxiosError("Network Error", "ERR_NETWORK") ); @@ -182,7 +182,7 @@ describe("Test Reset Password Api Error", () => { expect(state.alert.variant).toBe("danger"); }); - expect(axios.put).toHaveBeenCalledWith( + expect(axiosClient.put).toHaveBeenCalledWith( expect.stringContaining("/password_resets/valid-token"), { user: { password: validPassword } } ); @@ -198,7 +198,7 @@ describe("Test Reset Password Api Error", () => { headers: {}, config: {} as any, }; - (axios.put as any).mockRejectedValue(serverError); + (axiosClient.put as any).mockRejectedValue(serverError); const store = renderComponent(); diff --git a/src/utils/axios_client.ts b/src/utils/axios_client.ts index 77a4ba16..e7b6c3a9 100644 --- a/src/utils/axios_client.ts +++ b/src/utils/axios_client.ts @@ -14,14 +14,22 @@ const axiosClient = axios.create({ }, }); -axiosClient.interceptors.request.use((config) => { - const token = getAuthToken(); - if (token && token !== "EXPIRED") { - config.headers["Authorization"] = `Bearer ${token}`; +axiosClient.interceptors.request.use( + (config) => { + const token = getAuthToken(); + + // Attach the token only if it exists and is valid + if (token && token !== "EXPIRED") { + config.headers["Authorization"] = `Bearer ${token}`; + } + + // Always return the config, not all routes need an Authorization header. return config; + }, + (error) => { + return Promise.reject(error); } - return Promise.reject("Authentication token not found! Please login again."); -}); +); // Add response interceptor for debugging axiosClient.interceptors.response.use(