Skip to content
1 change: 0 additions & 1 deletion src/constants/Api.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/pages/Authentication/ForgotPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,7 +24,7 @@ const ForgotPassword = () => {
submitProps: FormikHelpers<IForgotPasswordFormValues>
) => {
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",
Expand Down
6 changes: 3 additions & 3 deletions src/pages/Authentication/ResetPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
18 changes: 9 additions & 9 deletions src/pages/Authentication/__tests__/ForgotPassword.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
});
Expand All @@ -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')
);

Expand All @@ -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}
);
});
Expand All @@ -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.'},
});
Expand All @@ -139,14 +139,14 @@ describe('Test Successful Password Reset Request', () => {
<ForgotPassword />
</Provider>
);

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}
);

Expand Down
18 changes: 9 additions & 9 deletions src/pages/Authentication/__tests__/ResetPassword.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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" },
});
Expand All @@ -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 } }
);
Expand All @@ -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")
);

Expand All @@ -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 } }
);
Expand All @@ -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();

Expand Down
20 changes: 14 additions & 6 deletions src/utils/axios_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
johnmweisz marked this conversation as resolved.
},
(error) => {
return Promise.reject(error);
}
return Promise.reject("Authentication token not found! Please login again.");
});
);

// Add response interceptor for debugging
axiosClient.interceptors.response.use(
Expand Down