Skip to content

Commit

Permalink
Added: Feature Axios Interceptors
Browse files Browse the repository at this point in the history
  • Loading branch information
gisubizo Jovan authored and gisubizo Jovan committed Jul 8, 2024
1 parent 9652708 commit ce7c781
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 18 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = {
"react/jsx-props-no-spreading": "off",
"react/require-default-props": "off",
"import/no-extraneous-dependencies": "off",
"import/no-cycle": "off",
"@typescript-eslint/no-shadow": "off",
"no-unsafe-optional-chaining": "off",
"react/no-unused-prop-types": "off",
Expand Down
12 changes: 12 additions & 0 deletions src/__test__/updatePasswordApiSlice.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ import updatePasswordApiSlice, {
} from "../redux/api/updatePasswordApiSlice";

jest.mock("axios");
jest.mock("../redux/api/api", () => ({
api: {
interceptors: {
response: {
use: jest.fn(),
},
request: {
use: jest.fn(),
},
},
},
}));

describe("updatePasswordApiSlice", () => {
let store;
Expand Down
32 changes: 32 additions & 0 deletions src/components/common/errors/networtErrorPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";
import { useSelector, useDispatch } from "react-redux";

import { clearNetworkError } from "../../../redux/reducers/NetworkErrorSlice";

const Popup = () => {
const dispatch = useDispatch();
// @ts-ignore
const showPopup = useSelector((state) => state.networkError.showPopup);

if (!showPopup) return null;

return (
<div className="fixed inset-0 z-40 flex items-center justify-center bg-[#D0D0D0] bg-opacity-50">
<div className="relative bg-white rounded-lg p-10 w-[90%] md:w-[65%] lg:w-[55%] xl:w-[50%] duration-75 animate-fadeIn">
<p>
📵
<b>Network Error </b>
<br />
Please check your internet connection.
</p>
<button
onClick={() => dispatch(clearNetworkError())}
className="mt-10 bg-transparent text-primary border border-[#DB4444] px-4 py-2 rounded"
>
Close
</button>
</div>
</div>
);
};
export default Popup;
2 changes: 2 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import store from "./redux/store";
import App from "./App";
import Popup from "./components/common/errors/networtErrorPopup";

const client = new QueryClient({});

Expand All @@ -15,6 +16,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
<QueryClientProvider client={client}>
<Provider store={store}>
<Router>
<Popup />
<App />
</Router>
</Provider>
Expand Down
34 changes: 34 additions & 0 deletions src/redux/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
import axios from "axios";

import store from "../store";
import { setNetworkError } from "../reducers/NetworkErrorSlice";

const api = axios.create({
baseURL: process.env.VITE_BASE_URL,
headers: {
"Content-Type": "application/json",
},
});
let navigateFunction = null;

export const setNavigateFunction = (navigate) => {
navigateFunction = navigate;
};
const redirectToLogin = (navigate) => {
setTimeout(() => {
navigate("/login");
}, 2000);
};

const redirectToLanding = (navigate) => {
setTimeout(() => {
navigate("/");
}, 3000);
};

api.interceptors.response.use(
(response) => response,
(error) => {
// const navigate = useNavigate();
if (error.response && error.response.status === 401) {
if (navigateFunction) {
redirectToLogin(navigateFunction);
}
} else if (!error.response) {
store.dispatch(setNetworkError());
}
return Promise.reject(error);
},
);

export default api;
18 changes: 18 additions & 0 deletions src/redux/reducers/NetworkErrorSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createSlice } from "@reduxjs/toolkit";

export const networkErrorSlice = createSlice({
name: "networkError",
initialState: { showPopup: false },
reducers: {
setNetworkError: (state) => {
state.showPopup = true;
},
clearNetworkError: (state) => {
state.showPopup = false;
},
},
});

export const { setNetworkError, clearNetworkError } = networkErrorSlice.actions;

export default networkErrorSlice.reducer;
2 changes: 2 additions & 0 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import getLinkReducer from "./reducers/getLinkSlice";
import resetReducer from "./reducers/resetPasswordSlice";
import categoriesReducer from "./reducers/categoriesSlice";
import updatePasswordApiSlice from "./api/updatePasswordApiSlice";
import NetworkErrorSlice from "./reducers/NetworkErrorSlice";

const store = configureStore({
reducer: {
Expand All @@ -17,6 +18,7 @@ const store = configureStore({
reset: resetReducer,
categories: categoriesReducer,
updatePassword: updatePasswordApiSlice,
networkError: NetworkErrorSlice,
},
});

Expand Down
64 changes: 46 additions & 18 deletions src/routes/AppRoutes.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Route, Routes } from "react-router-dom";
import { Route, Routes, useNavigate } from "react-router-dom";
import { useEffect } from "react";

import RootLayout from "../components/layouts/RootLayout";
import Homepage from "../pages/Homepage";
Expand All @@ -12,23 +13,50 @@ import ResetPassword from "../pages/ResetPassword";
import SellerDashboard from "../dashboard/sellers/Index";
import AddProduct from "../dashboard/sellers/AddProduct";
import UpdatePasswordPage from "../pages/passwordUpdatePage";
import { setNavigateFunction } from "../redux/api/api";

const AppRoutes = () => (
<Routes>
<Route path="/" element={<RootLayout />}>
<Route index element={<Homepage />} />
<Route path="products" element={<ProductPage />} />
<Route path="products/:id" element={<ProductDetails />} />
</Route>
<Route path="/password-reset-link" element={<GetLinkPage />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/register" element={<RegisterUser />} />
<Route path="/login" element={<Login />} />
<Route path="2fa-verify" element={<OtpVerificationForm />} />
<Route path="/dashboard" element={<SellerDashboard />} />
<Route path="/dashboard/addproduct" element={<AddProduct />} />
<Route path="/update-password" element={<UpdatePasswordPage />} />
</Routes>
);
const AppRoutes = () => {
const navigate = useNavigate();
useEffect(() => {
setNavigateFunction(navigate);
}, [navigate]);

const AlreadyLogged = ({ children }) => {
const navigate = useNavigate();
const token = localStorage.getItem("accessToken");
useEffect(() => {
if (token) {
navigate("/");
}
}, [token, navigate]);

return token ? null : children;
};

return (
<Routes>
<Route path="/" element={<RootLayout />}>
<Route index element={<Homepage />} />
<Route path="products" element={<ProductPage />} />
<Route path="products/:id" element={<ProductDetails />} />
</Route>
<Route path="/password-reset-link" element={<GetLinkPage />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/register" element={<RegisterUser />} />
<Route
path="/login"
element={(
<AlreadyLogged>
<Login />
</AlreadyLogged>
)}
/>
<Route path="2fa-verify" element={<OtpVerificationForm />} />
<Route path="/dashboard" element={<SellerDashboard />} />
<Route path="/dashboard/addproduct" element={<AddProduct />} />
<Route path="/update-password" element={<UpdatePasswordPage />} />
</Routes>
);
};

export default AppRoutes;

0 comments on commit ce7c781

Please sign in to comment.