diff --git a/frontend/src/layouts/AppLayout/ErrorBoundary.tsx b/frontend/src/layouts/AppLayout/ErrorBoundary.tsx new file mode 100644 index 000000000..55acf3433 --- /dev/null +++ b/frontend/src/layouts/AppLayout/ErrorBoundary.tsx @@ -0,0 +1,117 @@ +import React, { ErrorInfo, ReactNode, useEffect } from "react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { faBugs, faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Button } from "@app/components/v2"; + +interface ErrorBoundaryProps { + children: ReactNode; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + +const ErrorPage = ({ error }: { error: Error | null }) => { + const [orgId, setOrgId] = React.useState(null); + const router = useRouter(); + const currentUrl = router?.asPath?.split("?")?.[0]; + + // Workaround: Fixes localStorage not being available in the error boundary until the next render. + useEffect(() => { + const savedOrgId = localStorage.getItem("orgData.id"); + + if (savedOrgId) { + setOrgId(savedOrgId); + } + }, []); + + return ( +
+
+ +

+ Something went wrong. Please contact{" "} + + support@infisical.com + + , or{" "} + + + join our Slack community + + {" "} + if the issue persists. +

+ + {orgId && ( + + )} + + {error?.message && ( + <> +
+

+ + {currentUrl}, {error.message} + +

+ + )} +
+
+ ); +}; + +class ErrorBoundary extends React.Component { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + console.error("Error caught by ErrorBoundary:", error, errorInfo); + } + + render(): ReactNode { + const { hasError, error } = this.state; + const { children } = this.props; + + if (hasError) { + return ; + } + return children; + } +} + +const ErrorBoundaryWrapper = ({ children }: ErrorBoundaryProps) => { + return {children}; +}; + +export default ErrorBoundaryWrapper; diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 7de2e92ab..493e302e0 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -27,6 +27,7 @@ import { WorkspaceProvider } from "@app/context"; import { AppLayout } from "@app/layouts"; +import ErrorBoundaryWrapper from "@app/layouts/AppLayout/ErrorBoundary"; import { queryClient } from "@app/reactQuery"; import "nprogress/nprogress.css"; @@ -85,46 +86,50 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element => !Component.requireAuth ) { return ( - - - - - - - - - - + + + + + + + + + + + + ); } const Layout = Component?.layout || AppLayout; return ( - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + ); };