diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml
index 9e58d09b..5121838c 100644
--- a/.github/workflows/dev-build.yaml
+++ b/.github/workflows/dev-build.yaml
@@ -6,7 +6,7 @@ concurrency:
on:
push:
- branches: ['upgrade-multer'] # put your current branch to create a build. Core team only.
+ branches: ['enhancement-error-boundary'] # put your current branch to create a build. Core team only.
paths-ignore:
- '**.md'
- 'cloud-deployments/*'
diff --git a/frontend/package.json b/frontend/package.json
index dab34cc4..bfbb9cb5 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -35,6 +35,7 @@
"react-device-detect": "^2.2.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
+ "react-error-boundary": "^6.0.0",
"react-highlight-words": "^0.21.0",
"react-i18next": "^14.1.1",
"react-loading-skeleton": "^3.1.0",
@@ -75,4 +76,4 @@
"tailwindcss": "^3.3.1",
"vite": "^4.3.0"
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 1ae785cc..7e7b58a5 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,5 +1,5 @@
import React, { Suspense } from "react";
-import { Outlet } from "react-router-dom";
+import { Outlet, useLocation } from "react-router-dom";
import { I18nextProvider } from "react-i18next";
import { AuthProvider } from "@/AuthContext";
import { ToastContainer } from "react-toastify";
@@ -12,25 +12,34 @@ import { FullScreenLoader } from "./components/Preloader";
import { ThemeProvider } from "./ThemeContext";
import { PWAModeProvider } from "./PWAContext";
import KeyboardShortcutsHelp from "@/components/KeyboardShortcutsHelp";
+import { ErrorBoundary } from "react-error-boundary";
+import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback";
export default function App() {
+ const location = useLocation();
return (
-
-
- }>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/components/ErrorBoundaryFallback/index.jsx b/frontend/src/components/ErrorBoundaryFallback/index.jsx
new file mode 100644
index 00000000..03297859
--- /dev/null
+++ b/frontend/src/components/ErrorBoundaryFallback/index.jsx
@@ -0,0 +1,93 @@
+import { NavLink } from "react-router-dom";
+import { House, ArrowClockwise, Copy, Check } from "@phosphor-icons/react";
+import { useState } from "react";
+
+export default function ErrorBoundaryFallback({ error, resetErrorBoundary }) {
+ const [copied, setCopied] = useState(false);
+
+ const copyErrorDetails = async () => {
+ const details = {
+ url: window.location.href,
+ error: error?.name || "Unknown Error",
+ message: error?.message || "No message available",
+ stack: error?.stack || "No stack trace available",
+ userAgent: navigator.userAgent,
+ timestamp: new Date().toISOString(),
+ };
+
+ const formattedDetails = `
+Error Report
+============
+Timestamp: ${details.timestamp}
+URL: ${details.url}
+User Agent: ${details.userAgent}
+
+Error: ${details.error}
+Message: ${details.message}
+
+Stack Trace:
+${details.stack}
+ `.trim();
+
+ try {
+ await navigator.clipboard.writeText(formattedDetails);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error("Failed to copy error details:", err);
+ }
+ };
+
+ return (
+
+
+ An error occurred.
+
+
+ {error?.message}
+
+ {import.meta.env.DEV && (
+
+
+
+
+
+ {error?.stack}
+
+
+ )}
+
+
+
+
+ Home
+
+
+
+ );
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 58e496cc..51767edf 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -372,6 +372,14 @@ const router = createBrowserRouter([
return { element: };
},
},
+ // Catch-all route for 404s
+ {
+ path: "*",
+ lazy: async () => {
+ const { default: NotFound } = await import("@/pages/404");
+ return { element: };
+ },
+ },
],
},
]);
diff --git a/frontend/src/pages/404.jsx b/frontend/src/pages/404.jsx
index 6f2ac8e4..79cebbf6 100644
--- a/frontend/src/pages/404.jsx
+++ b/frontend/src/pages/404.jsx
@@ -1,24 +1,25 @@
-import Header from "../components/Header";
-import Footer from "../components/Footer";
+import { NavLink } from "react-router-dom";
+import { House, MagnifyingGlass } from "@phosphor-icons/react";
-export default function Contact() {
+export default function NotFound() {
return (
-
-
-
);
}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 1f709c62..d060ddef 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -183,6 +183,11 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
+"@babel/runtime@^7.12.5":
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326"
+ integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
+
"@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2"
@@ -3206,6 +3211,13 @@ react-dropzone@^14.2.3:
file-selector "^0.6.0"
prop-types "^15.8.1"
+react-error-boundary@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-6.0.0.tgz#a9e552146958fa77d873b587aa6a5e97544ee954"
+ integrity sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+
react-highlight-words@^0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/react-highlight-words/-/react-highlight-words-0.21.0.tgz#a109acdf7dc6fac3ed7db82e9cba94e8d65c281c"
@@ -3583,7 +3595,16 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -3655,7 +3676,14 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -4196,7 +4224,16 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==