diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index d22ca467..b3aba5b8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -32,6 +32,7 @@
"color": "^4.2.3",
"lib0": "^0.2.88",
"moment": "^2.30.1",
+ "notistack": "^2.0.8",
"randomcolor": "^0.6.2",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
@@ -5312,6 +5313,42 @@
"resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
"integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
},
+ "node_modules/notistack": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/notistack/-/notistack-2.0.8.tgz",
+ "integrity": "sha512-/IY14wkFp5qjPgKNvAdfL5Jp6q90+MjgKTPh4c81r/lW70KeuX6b9pE/4f8L4FG31cNudbN9siiFS5ql1aSLRw==",
+ "dependencies": {
+ "clsx": "^1.1.0",
+ "hoist-non-react-statics": "^3.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/notistack"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "@mui/material": "^5.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "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/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 77a48e7a..9330e10e 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,6 +36,7 @@
"color": "^4.2.3",
"lib0": "^0.2.88",
"moment": "^2.30.1",
+ "notistack": "^2.0.8",
"randomcolor": "^0.6.2",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index feb453f5..78aab66e 100755
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -10,6 +10,9 @@ import { useMemo } from "react";
import { selectConfig } from "./store/configSlice";
import axios from "axios";
import { routes } from "./routes";
+import { QueryCache, QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import AuthProvider from "./providers/AuthProvider";
+import { useErrorHandler } from "./hooks/useErrorHandler";
const router = createBrowserRouter(routes);
@@ -32,14 +35,31 @@ function App() {
},
});
}, [config.theme, prefersDarkMode]);
+ const handleError = useErrorHandler();
+ const queryClient = useMemo(() => {
+ return new QueryClient({
+ queryCache: new QueryCache({
+ onError: handleError,
+ }),
+ defaultOptions: {
+ mutations: {
+ onError: handleError,
+ },
+ },
+ });
+ }, [handleError]);
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/components/common/CodePairError.tsx b/frontend/src/components/common/CodePairError.tsx
new file mode 100644
index 00000000..773f12bd
--- /dev/null
+++ b/frontend/src/components/common/CodePairError.tsx
@@ -0,0 +1,19 @@
+import { Stack, Typography } from "@mui/material";
+import { isRouteErrorResponse, useRouteError } from "react-router-dom";
+
+function CodePairError() {
+ const error = useRouteError();
+
+ return (
+
+
+ Something went wrong
+ {isRouteErrorResponse(error) && (
+ Status Code: {error.status}
+ )}
+
+
+ );
+}
+
+export default CodePairError;
diff --git a/frontend/src/hooks/useErrorHandler.ts b/frontend/src/hooks/useErrorHandler.ts
new file mode 100644
index 00000000..eb6b553f
--- /dev/null
+++ b/frontend/src/hooks/useErrorHandler.ts
@@ -0,0 +1,14 @@
+import { useSnackbar } from "notistack";
+import { useCallback } from "react";
+
+export function useErrorHandler() {
+ const { enqueueSnackbar } = useSnackbar();
+ const handleError = useCallback(
+ (error: Error) => {
+ enqueueSnackbar(error.message || "Something went wrong...", { variant: "error" });
+ },
+ [enqueueSnackbar]
+ );
+
+ return handleError;
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index a793cbfb..489fad22 100755
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -6,21 +6,17 @@ import { store } from "./store/store";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import AuthProvider from "./providers/AuthProvider";
+import { SnackbarProvider } from "notistack";
const persistor = persistStore(store);
-const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")!).render(
-
-
-
-
-
+
+
+
diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx
index 99acf738..9d7ce6ac 100644
--- a/frontend/src/routes.tsx
+++ b/frontend/src/routes.tsx
@@ -7,6 +7,18 @@ import WorkspaceLayout from "./components/layouts/WorkspaceLayout";
import GuestRoute from "./components/common/GuestRoute";
import PrivateRoute from "./components/common/PrivateRoute";
import WorkspaceIndex from "./pages/workspace/Index";
+import CodePairError from "./components/common/CodePairError";
+
+interface CodePairRoute {
+ path: string;
+ accessType: AccessType;
+ element: JSX.Element;
+ errorElement?: JSX.Element;
+ children?: {
+ path: string;
+ element: JSX.Element;
+ }[];
+}
const enum AccessType {
PRIVATE, // Authroized user can access only
@@ -14,7 +26,7 @@ const enum AccessType {
GUEST, // Not authorized user can access only
}
-const codePairRoutes = [
+const codePairRoutes: Array = [
{
path: "",
accessType: AccessType.GUEST,
@@ -63,6 +75,8 @@ const injectProtectedRoute = (routes: typeof codePairRoutes) => {
route.element = {route.element};
}
+ route.errorElement = ;
+
return route;
});
};