diff --git a/client/app/features/layout/Root.tsx b/client/app/features/layout/Root.tsx index b4435ee7..456036eb 100644 --- a/client/app/features/layout/Root.tsx +++ b/client/app/features/layout/Root.tsx @@ -23,7 +23,7 @@ export const rootLoader: LoaderFunction = async () => { return query .unwrap() - .catch((_err) => console.error('Error fetching orgs')) + .catch((_err) => null) .finally(() => query.unsubscribe()); }; diff --git a/client/app/features/siteDetails/SiteDetails.tsx b/client/app/features/siteDetails/SiteDetails.tsx index e790f8a2..4ba69b1e 100644 --- a/client/app/features/siteDetails/SiteDetails.tsx +++ b/client/app/features/siteDetails/SiteDetails.tsx @@ -1,5 +1,5 @@ import { ReactElement } from 'react'; -import { LoaderFunction, redirect, useNavigate, useParams } from 'react-router-dom'; +import { LoaderFunction, useNavigate, useParams } from 'react-router-dom'; import { RcraSiteDetails } from '~/components/RcraSite/RcraSiteDetails'; import { Button, Card, CardContent, CardHeader, Spinner } from '~/components/ui'; import { rootStore as store, useGetUserHaztrakSiteQuery } from '~/store'; @@ -13,8 +13,7 @@ export const siteDetailsLoader: LoaderFunction = async ({ params }) => { try { return await p.unwrap(); } catch (_error) { - console.error('Error fetching orgs'); - return redirect('/login'); + return null; } finally { p.unsubscribe(); } diff --git a/client/app/services/APIs/htApi.spec.tsx b/client/app/services/APIs/htApi.spec.tsx new file mode 100644 index 00000000..284c38dc --- /dev/null +++ b/client/app/services/APIs/htApi.spec.tsx @@ -0,0 +1,25 @@ +import { describe, expect, it, vi } from 'vitest'; +import { AxiosResponse } from 'axios'; +import { returnOnSuccess } from '~/services/APIs/htApi'; +import { undefined } from 'zod'; + +vi.mock('react-router-dom', () => ({ + redirect: vi.fn(), +})); + +const createMockResponse = (status: number): AxiosResponse => ({ + // @ts-expect-error - ok for test + config: {}, + data: undefined, + headers: {}, + status, + statusText: '', +}); + +describe('htApi response interceptor', () => { + it('returns same response', async () => { + const response = createMockResponse(200); + const returnedResponse = returnOnSuccess(response); + expect(returnedResponse).toBe(response); + }); +}); diff --git a/client/app/services/APIs/htApi.ts b/client/app/services/APIs/htApi.ts index 7922ea2a..f7162aee 100644 --- a/client/app/services/APIs/htApi.ts +++ b/client/app/services/APIs/htApi.ts @@ -1,6 +1,7 @@ /**htApi.ts - service for making requests to the Haztrak API*/ -import axios, { InternalAxiosRequestConfig } from 'axios'; +import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import { rootStore } from '~/store'; +import { redirect } from 'react-router-dom'; /** An Axios instance with an interceptor to automatically apply authentication headers*/ export const htApi = axios.create({ @@ -28,3 +29,14 @@ htApi.interceptors.request.use( return Promise.reject(error); } ); + +export const returnOnSuccess = (response: AxiosResponse) => response; + +export const redirectOnUnauthorized = (error: AxiosError) => { + if (error.response?.status === 401) { + redirect('/login'); + } + return Promise.reject(error); +}; + +htApi.interceptors.response.use(returnOnSuccess, redirectOnUnauthorized); diff --git a/client/package.json b/client/package.json index 046910d0..75136e58 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "haztrak", - "version": "0.7.2", + "version": "0.8.0", "private": true, "type": "module", "scripts": { diff --git a/server/pyproject.toml b/server/pyproject.toml index aac0ef1a..93173174 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "haztrak" -version = "0.7.1" +version = "0.8.0" description = "An open-source web app illustrating how waste management software can interface with RCRAInfo to track hazardous waste" readme = "README.md" authors = [