diff --git a/images/shoko_mascot.png b/images/shoko_mascot.png new file mode 100644 index 000000000..4dcd18a33 Binary files /dev/null and b/images/shoko_mascot.png differ diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 000000000..5b34e3967 --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,104 @@ +import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useNavigate, useRouteError } from 'react-router-dom'; + +import ShokoMascot from '@/../images/shoko_mascot.png'; +import Button from '@/components/Input/Button'; +import { useGetInitVersionQuery } from '@/core/rtkQuery/splitV3Api/initApi'; +import { usePostWebuiUpdateMutation } from '@/core/rtkQuery/splitV3Api/webuiApi'; +import { unsetDetails } from '@/core/slices/apiSession'; + +const ErrorBoundary = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const error = useRouteError() as Error; // There is no type definition provided. + + const version = useGetInitVersionQuery(); + const [webuiUpdateTrigger, webuiUpdateResult] = usePostWebuiUpdateMutation(); + + const [updateChannel, setUpdateChannel] = useState<'Stable' | 'Dev'>('Stable'); + + const handleLogout = () => { + dispatch(unsetDetails()); + navigate('/webui/login'); + }; + + const handleWebUiUpdate = (channel: 'Stable' | 'Dev') => { + setUpdateChannel(channel); + webuiUpdateTrigger(channel).unwrap().then(() => handleLogout(), err => console.error(err)); + }; + + return ( +
+
+
Congratulations!
+
You Broke The Web UI!
+
+          The information below is absolutely (maybe) useless!
+          
+
+ {error.stack} +
+
+
Lets get you back into the Web UI.
+
+ If you are seeing this page after updating, select  + Force Update to Stable Web UI + , otherwise select  + Logout of Web UI +  to clear local storage. +
+
+ Still need help? Hop on our  + + Discord + +  server and provide the above error. +
+
+
+ + + {version.data?.Server.ReleaseChannel !== 'Stable' && ( + + )} + + +
+
+ + mascot +
+ ); +}; + +export default ErrorBoundary; diff --git a/src/core/app.tsx b/src/core/app.tsx index 1c8451500..350562a38 100644 --- a/src/core/app.tsx +++ b/src/core/app.tsx @@ -1,16 +1,15 @@ import React from 'react'; import { Provider } from 'react-redux'; - -import ErrorBoundary from '@/pages/error/ErrorPage'; +import * as Sentry from '@sentry/react'; import Router from './router'; import store from './store'; const App = () => ( - + - + ); diff --git a/src/core/router/index.tsx b/src/core/router/index.tsx index aaf024de1..9a8df885e 100644 --- a/src/core/router/index.tsx +++ b/src/core/router/index.tsx @@ -3,7 +3,9 @@ import React, { useEffect, useRef } from 'react'; import { useSelector } from 'react-redux'; import { Navigate, Route } from 'react-router'; import { RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom'; +import * as Sentry from '@sentry/react'; +import ErrorBoundary from '@/components/ErrorBoundary'; import { useGetSettingsQuery } from '@/core/rtkQuery/splitV3Api/settingsApi'; import Collection from '@/pages/collection/Collection'; import Series from '@/pages/collection/Series'; @@ -25,7 +27,6 @@ import StartServer from '@/pages/firstrun/StartServer'; import LoginPage from '@/pages/login/LoginPage'; import LogsPage from '@/pages/logs/LogsPage'; import MainPage from '@/pages/main/MainPage'; -import NoMatchPage from '@/pages/nomatch'; import SettingsPage, { initialSettings } from '@/pages/settings/SettingsPage'; import AniDBSettings from '@/pages/settings/tabs/AniDBSettings'; import GeneralSettings from '@/pages/settings/tabs/GeneralSettings'; @@ -44,9 +45,11 @@ import AuthenticatedRoute from './AuthenticatedRoute'; import type { RootState } from '@/core/store'; -const router = createBrowserRouter( +const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouter(createBrowserRouter); + +const router = sentryCreateBrowserRouter( createRoutesFromElements( - }> + }> } /> } /> diff --git a/src/main.tsx b/src/main.tsx index e40a7b5de..130bbb494 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from 'react-router-dom'; import * as Sentry from '@sentry/react'; import { createRoot } from 'react-dom/client'; @@ -12,7 +13,15 @@ if (!isDebug()) { environment: 'production', release: uiVersion(), integrations: [ - new Sentry.BrowserTracing(), + new Sentry.BrowserTracing({ + routingInstrumentation: Sentry.reactRouterV6Instrumentation( + React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + ), + }), new Sentry.Replay({ maskAllText: true, blockAllMedia: false, diff --git a/src/pages/error/ErrorPage.tsx b/src/pages/error/ErrorPage.tsx deleted file mode 100644 index 854a58206..000000000 --- a/src/pages/error/ErrorPage.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import * as Sentry from '@sentry/react'; - -type Props = { - children?: React.ReactNode; -}; - -const Fallback = ({ componentStack, error }) => { - const navigate = useNavigate(); - - return ( -
-
-

ERROR

-

You broke the Web UI, congratulations.

-

Hopefully useful information:

-

{error.toString()}

-

- Trace: -

{componentStack}
-

-

- navigate(-1)}>Go back - , or head over to the  - home page -  to choose a new direction. -

-
-
- ); -}; - -const ErrorBoundary = ({ children }: Props) => ( - - {children} - -); - -export default ErrorBoundary;