diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fdce412f9..3fd41e4ec 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -40,6 +40,7 @@ "material-ui-popup-state": "^5.0.10", "moment": "^2.30.1", "mui-daterange-picker-plus": "^1.0.4", + "notistack": "^3.0.1", "papaparse": "^5.4.1", "pretty-bytes": "^6.1.1", "quill": "^2.0.2", @@ -13052,6 +13053,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -16449,6 +16458,36 @@ "node": ">=0.10.0" } }, + "node_modules/notistack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz", + "integrity": "sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==", + "license": "MIT", + "dependencies": { + "clsx": "^1.1.0", + "goober": "^2.0.33" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/notistack" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/notistack/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6e8783097..d6bec5c8f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -62,6 +62,7 @@ "material-ui-popup-state": "^5.0.10", "moment": "^2.30.1", "mui-daterange-picker-plus": "^1.0.4", + "notistack": "^3.0.1", "papaparse": "^5.4.1", "pretty-bytes": "^6.1.1", "quill": "^2.0.2", diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index acb541131..509ac3e8e 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -8,6 +8,7 @@ import { KeycloakProvider } from '@/components/KeycloakProvider' import { getKeycloak } from '@/utils/keycloak' import { LocalizationProvider } from '@mui/x-date-pickers' import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3' +import { SnackbarProvider } from 'notistack' const queryClient = new QueryClient() const keycloak = getKeycloak() @@ -20,8 +21,10 @@ if (root) { - - + + + + diff --git a/frontend/src/services/useApiService.js b/frontend/src/services/useApiService.js index a8525e2ae..7b4fdca4b 100644 --- a/frontend/src/services/useApiService.js +++ b/frontend/src/services/useApiService.js @@ -2,9 +2,11 @@ import { useMemo } from 'react' import axios from 'axios' import { useKeycloak } from '@react-keycloak/web' import { CONFIG } from '@/constants/config' +import { useSnackbar } from 'notistack' export const useApiService = (opts = {}) => { const { keycloak } = useKeycloak() + const { enqueueSnackbar } = useSnackbar() // useMemo to memoize the apiService instance const apiService = useMemo(() => { @@ -25,6 +27,24 @@ export const useApiService = (opts = {}) => { } ) + // Add response interceptor + instance.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status >= 400) { + console.error( + 'API Error:', + error.response.status, + error.response.data + ) + enqueueSnackbar(`${error.response.status} error`, { + autoHideDuration: 5000, + variant: 'error' + }) + } + } + ) + // Download method instance.download = async (url, params = {}) => { try {