diff --git a/public/locales/en.json b/public/locales/en.json index c2d9102..cee7495 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -84,6 +84,12 @@ }, "error": "Error", + "errorBoundary": { + "title": "Critical error", + "description": "A critical error has occurred in KristWeb, so this page was terminated. See console for details.", + "sentryNote": "This error was automatically reported." + }, + "loading": "Loading...", "copy": "Copy to clipboard", diff --git a/src/App.tsx b/src/App.tsx index 7935a67..30640c7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ // FIXME: Apparently the import order of my CSS is important. Who knew! import "./App.less"; +import { ErrorBoundary } from "@global/ErrorBoundary"; import { AppLoading } from "@global/AppLoading"; import { AppServices } from "@global/AppServices"; import { WebsocketProvider } from "@global/ws/WebsocketProvider"; @@ -34,22 +35,24 @@ store = initStore(); } - return }> - - - - - - + return + }> + + + + + + - {/* Services, etc. */} - - - - - - - ; + {/* Services, etc. */} + + + + + + + + ; } export default App; diff --git a/src/components/results/SmallResult.tsx b/src/components/results/SmallResult.tsx index 5e2f80f..acd4b16 100644 --- a/src/components/results/SmallResult.tsx +++ b/src/components/results/SmallResult.tsx @@ -5,7 +5,7 @@ // This is ant-design's Result component, but without importing 54 kB of images // that we don't even use. // -// This file is based off of hte following source code from ant-design, which is +// This file is based off of the following source code from ant-design, which is // licensed under the MIT license: // // https://github.com/ant-design/ant-design/blob/077443696ba0fb708f2af81f5eb665b908d8be66/components/result/index.tsx diff --git a/src/global/AppRouter.tsx b/src/global/AppRouter.tsx index 396b627..c6a840f 100644 --- a/src/global/AppRouter.tsx +++ b/src/global/AppRouter.tsx @@ -1,9 +1,8 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { Alert } from "antd"; - import { Switch, Route, Redirect } from "react-router-dom"; +import { ErrorBoundary } from "@global/ErrorBoundary"; import { DashboardPage } from "@pages/dashboard/DashboardPage"; import { WalletsPage } from "@pages/wallets/WalletsPage"; @@ -100,9 +99,9 @@ component && ( {/* Try to catch errors on a route without crashing everything */} - + {component} - + ) ))} diff --git a/src/global/ErrorBoundary.tsx b/src/global/ErrorBoundary.tsx new file mode 100644 index 0000000..66f079c --- /dev/null +++ b/src/global/ErrorBoundary.tsx @@ -0,0 +1,44 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under AGPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { FC } from "react"; +import { Alert } from "antd"; + +import { useTFns } from "@utils/i18n"; + +import * as Sentry from "@sentry/react"; + +interface Props { + name: string; +} + +export const ErrorBoundary: FC = ({ name, children }) => { + return } + onError={console.error} + + // Add the boundary name to the scope + beforeCapture={scope => { + scope.setTag("error-boundary", name); + }} + > + {children} + ; +}; + +function ErrorFallback(): JSX.Element { + const { tStr } = useTFns("errorBoundary."); + + return +

{tStr("description")}

+ + {/* TODO: Hide this if Sentry is disabled */} +

{tStr("sentryNote")}

+ } + />; +} diff --git a/src/pages/dev/DevPage.tsx b/src/pages/dev/DevPage.tsx index 5ae7424..bfaa040 100644 --- a/src/pages/dev/DevPage.tsx +++ b/src/pages/dev/DevPage.tsx @@ -1,7 +1,8 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { Button } from "antd"; +import { useState } from "react"; +import { Button, Space } from "antd"; import { PageLayout } from "@layout/PageLayout"; import { useWallets, deleteWallet } from "@wallets"; @@ -12,34 +13,39 @@ export function DevPage(): JSX.Element { const { wallets } = useWallets(); + const [forceError, setForceError] = useState(false); + if (forceError) throw new Error("Whoops!"); + return - {/* Delete all wallets with zero balance */} - - toDelete.forEach(deleteWallet); - }}> - Delete all wallets with zero balance - + {/* Delete all wallets */} + -    + {/* Clear local storage */} + - {/* Delete all wallets */} - - -    - - {/* Clear local storage */} - + {/* Cause an error */} + + ; }