121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
import { Text } from '@radix-ui/themes';
|
|
import { AxiosError } from 'axios';
|
|
import { useLocation, useNavigate } from 'react-router';
|
|
import { toast } from 'react-toastify/unstyled';
|
|
import { SearchParamKeys } from '../lib/constants';
|
|
import { useQueryMessage } from './useQueryMessage';
|
|
import { QueryMessageCode, QueryMessageType } from '../lib/QueryMessages';
|
|
import { useCallback } from 'react';
|
|
|
|
export enum ResponseErrorToastId {
|
|
NetworkError = 'network-error',
|
|
}
|
|
|
|
export type DefaultResponseErrorHandlerOptions = {
|
|
disableUnauthorizedHandling?: boolean;
|
|
disableHandleUnexpectedErrors?: boolean;
|
|
disableIgnoreCanceledRequests?: boolean;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param err error value
|
|
* @returns {boolean} true if the error was handled, false otherwise
|
|
*/
|
|
|
|
export function useResponseErrorHandler(): {
|
|
defaultResponseErrorHandler: typeof defaultResponseErrorHandler;
|
|
} {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { toSearchParamQueryMessage } = useQueryMessage();
|
|
|
|
const defaultResponseErrorHandler = useCallback(
|
|
(err: unknown, options?: DefaultResponseErrorHandlerOptions): boolean => {
|
|
if (!(err instanceof AxiosError) && !options?.disableHandleUnexpectedErrors) {
|
|
toast.error(
|
|
<div>
|
|
<Text weight="bold">Unexpected Error:</Text>
|
|
<br /> An unexpected error occurred. Please try again later.
|
|
</div>,
|
|
{
|
|
position: 'top-center',
|
|
autoClose: false,
|
|
hideProgressBar: false,
|
|
closeOnClick: true,
|
|
pauseOnHover: true,
|
|
draggable: false,
|
|
progress: undefined,
|
|
theme: 'colored',
|
|
}
|
|
);
|
|
return true;
|
|
}
|
|
|
|
if (!(err instanceof AxiosError)) return false;
|
|
|
|
if (err.message === 'canceled') {
|
|
// request was aborted, ignore but return true to indicate it was handled
|
|
return !options?.disableIgnoreCanceledRequests;
|
|
}
|
|
|
|
if (err.message === 'Network Error') {
|
|
toast.error(
|
|
<div>
|
|
<Text weight="bold">Network Error:</Text>
|
|
<br /> Unable to reach the server. Please check your internet connection and try again.
|
|
</div>,
|
|
{
|
|
toastId: ResponseErrorToastId.NetworkError,
|
|
position: 'top-center',
|
|
autoClose: false,
|
|
hideProgressBar: false,
|
|
closeOnClick: true,
|
|
pauseOnHover: true,
|
|
draggable: false,
|
|
progress: undefined,
|
|
theme: 'colored',
|
|
}
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// handle 401 Unauthorized globally
|
|
if (err.status === 401 && !options?.disableUnauthorizedHandling) {
|
|
// store current path for redirect after login
|
|
const currentPath = location.pathname + location.search;
|
|
const searchParam = new URLSearchParams();
|
|
searchParam.set(SearchParamKeys.Redirect, currentPath);
|
|
searchParam.set(SearchParamKeys.Message, toSearchParamQueryMessage(QueryMessageCode.SessionExpired, QueryMessageType.Info));
|
|
navigate(`/login?${searchParam.toString()}`);
|
|
return true;
|
|
}
|
|
|
|
if (err.status === 403) {
|
|
toast.error(
|
|
<div>
|
|
<Text weight="bold">Forbidden:</Text>
|
|
<br /> You do not have permission to perform this action.
|
|
</div>,
|
|
{
|
|
position: 'top-center',
|
|
autoClose: 5000,
|
|
hideProgressBar: false,
|
|
closeOnClick: true,
|
|
pauseOnHover: true,
|
|
draggable: false,
|
|
progress: undefined,
|
|
theme: 'colored',
|
|
}
|
|
);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
[location, navigate, toSearchParamQueryMessage]
|
|
);
|
|
|
|
return { defaultResponseErrorHandler };
|
|
}
|