From a32f76720ae8a2e587752a4443d90bc298f6820f Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 23 Nov 2025 14:12:00 -0800 Subject: [PATCH] refactor app error boundaries --- package.json | 2 +- pnpm-lock.yaml | 35 +++++-- .../components/route-error-boundary.tsx | 87 ----------------- .../routes/action-required-route.tsx | 11 ++- .../action-required/routes/invalid-route.tsx | 11 ++- .../albums/routes/album-detail-route.tsx | 11 ++- .../albums/routes/album-list-route.tsx | 11 ++- .../routes/dummy-album-detail-route.tsx | 11 ++- .../routes/album-artist-detail-route.tsx | 11 ++- ...bum-artist-detail-top-songs-list-route.tsx | 11 ++- .../routes/album-artist-list-route.tsx | 11 ++- .../artists/routes/artist-list-route.tsx | 11 ++- .../genres/routes/genre-list-route.tsx | 11 ++- .../features/home/routes/home-route.tsx | 13 ++- .../features/login/routes/login-route.tsx | 11 ++- .../now-playing/routes/now-playing-route.tsx | 11 ++- .../playlist-detail-song-list-route.tsx | 11 ++- .../playlists/routes/playlist-list-route.tsx | 11 ++- .../features/search/routes/search-route.tsx | 11 ++- .../shared/components/page-error-boundary.tsx | 97 +++++++++++++++++++ .../components}/router-error-boundary.tsx | 22 ++--- .../features/songs/routes/song-list-route.tsx | 13 ++- src/renderer/router/app-router.tsx | 67 ++++--------- 23 files changed, 323 insertions(+), 178 deletions(-) delete mode 100644 src/renderer/features/action-required/components/route-error-boundary.tsx create mode 100644 src/renderer/features/shared/components/page-error-boundary.tsx rename src/renderer/{components/error-boundary => features/shared/components}/router-error-boundary.tsx (87%) diff --git a/package.json b/package.json index e699ed6c5..9f150e561 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "react-image": "^4.1.0", "react-loading-skeleton": "^3.5.0", "react-player": "^2.11.0", - "react-router": "^7.9.4", + "react-router": "^7.9.6", "react-virtualized-auto-sizer": "^1.0.26", "react-window": "1.8.11", "react-window-v2": "npm:react-window@^2.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af70ea5d0..5097c6b26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 8.3.8(@mantine/core@8.3.8(@mantine/hooks@8.3.8(react@19.1.0))(@types/react@19.2.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.3.8(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@offlegacy/nuqs-hash-router': specifier: ^0.1.1 - version: 0.1.1(nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 0.1.1(nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0))(react@19.1.0) '@radix-ui/react-context-menu': specifier: ^2.2.16 version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -151,7 +151,7 @@ importers: version: https://codeload.github.com/jeffvli/Node-MPV/tar.gz/32b4d64395289ad710c41d481d2707a7acfc228f nuqs: specifier: ^2.7.1 - version: 2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) overlayscrollbars: specifier: ^2.11.1 version: 2.11.3 @@ -189,8 +189,8 @@ importers: specifier: ^2.11.0 version: 2.16.0(react@19.1.0) react-router: - specifier: ^7.9.4 - version: 7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^7.9.6 + version: 7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-virtualized-auto-sizer: specifier: ^1.0.26 version: 1.0.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -4610,6 +4610,16 @@ packages: react-dom: optional: true + react-router@7.9.6: + resolution: {integrity: sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -7101,9 +7111,9 @@ snapshots: mkdirp: 1.0.4 rimraf: 3.0.2 - '@offlegacy/nuqs-hash-router@0.1.1(nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0))(react@19.1.0)': + '@offlegacy/nuqs-hash-router@0.1.1(nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0))(react@19.1.0)': dependencies: - nuqs: 2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + nuqs: 2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) react: 19.1.0 '@pkgjs/parseargs@0.11.0': @@ -10148,12 +10158,12 @@ snapshots: dependencies: boolbase: 1.0.0 - nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): + nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): dependencies: '@standard-schema/spec': 1.0.0 react: 19.1.0 optionalDependencies: - react-router: 7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: 7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-router-dom: 7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) object-assign@4.1.1: {} @@ -10571,6 +10581,15 @@ snapshots: set-cookie-parser: 2.7.1 optionalDependencies: react-dom: 19.1.0(react@19.1.0) + optional: true + + react-router@7.9.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) react-style-singleton@2.2.3(@types/react@19.2.5)(react@19.1.0): dependencies: diff --git a/src/renderer/features/action-required/components/route-error-boundary.tsx b/src/renderer/features/action-required/components/route-error-boundary.tsx deleted file mode 100644 index 5e610be1f..000000000 --- a/src/renderer/features/action-required/components/route-error-boundary.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useNavigate, useRouteError } from 'react-router'; - -import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu'; -import { AppRoute } from '/@/renderer/router/routes'; -import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; -import { Button } from '/@/shared/components/button/button'; -import { Center } from '/@/shared/components/center/center'; -import { Divider } from '/@/shared/components/divider/divider'; -import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu'; -import { Group } from '/@/shared/components/group/group'; -import { Icon } from '/@/shared/components/icon/icon'; -import { Stack } from '/@/shared/components/stack/stack'; -import { Text } from '/@/shared/components/text/text'; - -const RouteErrorBoundary = () => { - const { t } = useTranslation(); - const navigate = useNavigate(); - const error = useRouteError() as any; - console.error('error', error); - - const handleReload = () => { - navigate(0); - }; - - const handleReturn = () => { - navigate(-1); - }; - - const handleHome = () => { - navigate(AppRoute.HOME); - }; - - return ( -
-
- - - - - {t('error.genericError')} - - - {error?.message} - - - - - - - - - - - - - - - -
-
- ); -}; - -export default RouteErrorBoundary; diff --git a/src/renderer/features/action-required/routes/action-required-route.tsx b/src/renderer/features/action-required/routes/action-required-route.tsx index 087f986a5..0e511d7fa 100644 --- a/src/renderer/features/action-required/routes/action-required-route.tsx +++ b/src/renderer/features/action-required/routes/action-required-route.tsx @@ -9,6 +9,7 @@ import { ServerCredentialRequired } from '/@/renderer/features/action-required/c import { ServerRequired } from '/@/renderer/features/action-required/components/server-required'; import { ServerList } from '/@/renderer/features/servers/components/server-list'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServerWithCredential } from '/@/renderer/store'; import { Button } from '/@/shared/components/button/button'; @@ -84,4 +85,12 @@ const ActionRequiredRoute = () => { ); }; -export default ActionRequiredRoute; +const ActionRequiredRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default ActionRequiredRouteWithBoundary; diff --git a/src/renderer/features/action-required/routes/invalid-route.tsx b/src/renderer/features/action-required/routes/invalid-route.tsx index c986dbae6..1b6e09d1f 100644 --- a/src/renderer/features/action-required/routes/invalid-route.tsx +++ b/src/renderer/features/action-required/routes/invalid-route.tsx @@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Center } from '/@/shared/components/center/center'; import { Group } from '/@/shared/components/group/group'; @@ -32,4 +33,12 @@ const InvalidRoute = () => { ); }; -export default InvalidRoute; +const InvalidRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default InvalidRouteWithBoundary; diff --git a/src/renderer/features/albums/routes/album-detail-route.tsx b/src/renderer/features/albums/routes/album-detail-route.tsx index e04adfbce..0776620f7 100644 --- a/src/renderer/features/albums/routes/album-detail-route.tsx +++ b/src/renderer/features/albums/routes/album-detail-route.tsx @@ -13,6 +13,7 @@ import { } from '/@/renderer/features/shared/components/library-background-overlay'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { useFastAverageColor } from '/@/renderer/hooks'; import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; import { LibraryItem } from '/@/shared/types/domain-types'; @@ -83,4 +84,12 @@ const AlbumDetailRoute = () => { ); }; -export default AlbumDetailRoute; +const AlbumDetailRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default AlbumDetailRouteWithBoundary; diff --git a/src/renderer/features/albums/routes/album-list-route.tsx b/src/renderer/features/albums/routes/album-list-route.tsx index f3d2dce72..15ea87f02 100644 --- a/src/renderer/features/albums/routes/album-list-route.tsx +++ b/src/renderer/features/albums/routes/album-list-route.tsx @@ -6,6 +6,7 @@ import { AlbumListContent } from '/@/renderer/features/albums/components/album-l import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; const AlbumListRoute = () => { const { albumArtistId, genreId } = useParams(); @@ -34,4 +35,12 @@ const AlbumListRoute = () => { ); }; -export default AlbumListRoute; +const AlbumListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default AlbumListRouteWithBoundary; diff --git a/src/renderer/features/albums/routes/dummy-album-detail-route.tsx b/src/renderer/features/albums/routes/dummy-album-detail-route.tsx index 844a86530..61bac9c59 100644 --- a/src/renderer/features/albums/routes/dummy-album-detail-route.tsx +++ b/src/renderer/features/albums/routes/dummy-album-detail-route.tsx @@ -11,6 +11,7 @@ import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; import { LibraryHeader } from '/@/renderer/features/shared/components/library-header'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { PlayButton } from '/@/renderer/features/shared/components/play-button'; import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; @@ -228,4 +229,12 @@ const DummyAlbumDetailRoute = () => { ); }; -export default DummyAlbumDetailRoute; +const DummyAlbumDetailRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default DummyAlbumDetailRouteWithBoundary; diff --git a/src/renderer/features/artists/routes/album-artist-detail-route.tsx b/src/renderer/features/artists/routes/album-artist-detail-route.tsx index bee8a4525..38676ee19 100644 --- a/src/renderer/features/artists/routes/album-artist-detail-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-detail-route.tsx @@ -13,6 +13,7 @@ import { } from '/@/renderer/features/shared/components/library-background-overlay'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { useFastAverageColor } from '/@/renderer/hooks'; import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; import { LibraryItem } from '/@/shared/types/domain-types'; @@ -89,4 +90,12 @@ const AlbumArtistDetailRoute = () => { ); }; -export default AlbumArtistDetailRoute; +const AlbumArtistDetailRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default AlbumArtistDetailRouteWithBoundary; diff --git a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx index 47e649135..1219ccf68 100644 --- a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx @@ -8,6 +8,7 @@ import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { useCurrentServer } from '/@/renderer/store/auth.store'; import { LibraryItem } from '/@/shared/types/domain-types'; @@ -64,4 +65,12 @@ const AlbumArtistDetailTopSongsListRoute = () => { ); }; -export default AlbumArtistDetailTopSongsListRoute; +const AlbumArtistDetailTopSongsListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default AlbumArtistDetailTopSongsListRouteWithBoundary; diff --git a/src/renderer/features/artists/routes/album-artist-list-route.tsx b/src/renderer/features/artists/routes/album-artist-list-route.tsx index 0a3f5bba2..ffad2da0b 100644 --- a/src/renderer/features/artists/routes/album-artist-list-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-list-route.tsx @@ -5,6 +5,7 @@ import { AlbumArtistListContent } from '/@/renderer/features/artists/components/ import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { ItemListKey } from '/@/shared/types/types'; const AlbumArtistListRoute = () => { @@ -33,4 +34,12 @@ const AlbumArtistListRoute = () => { ); }; -export default AlbumArtistListRoute; +const AlbumArtistListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default AlbumArtistListRouteWithBoundary; diff --git a/src/renderer/features/artists/routes/artist-list-route.tsx b/src/renderer/features/artists/routes/artist-list-route.tsx index dbace88ab..23f1b41bd 100644 --- a/src/renderer/features/artists/routes/artist-list-route.tsx +++ b/src/renderer/features/artists/routes/artist-list-route.tsx @@ -5,6 +5,7 @@ import { ArtistListContent } from '/@/renderer/features/artists/components/artis import { ArtistListHeader } from '/@/renderer/features/artists/components/artist-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { ItemListKey } from '/@/shared/types/types'; const ArtistListRoute = () => { @@ -33,4 +34,12 @@ const ArtistListRoute = () => { ); }; -export default ArtistListRoute; +const ArtistListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default ArtistListRouteWithBoundary; diff --git a/src/renderer/features/genres/routes/genre-list-route.tsx b/src/renderer/features/genres/routes/genre-list-route.tsx index f918bc890..439bd5e49 100644 --- a/src/renderer/features/genres/routes/genre-list-route.tsx +++ b/src/renderer/features/genres/routes/genre-list-route.tsx @@ -5,6 +5,7 @@ import { GenreListContent } from '/@/renderer/features/genres/components/genre-l import { GenreListHeader } from '/@/renderer/features/genres/components/genre-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { ItemListKey } from '/@/shared/types/types'; const GenreListRoute = () => { @@ -33,4 +34,12 @@ const GenreListRoute = () => { ); }; -export default GenreListRoute; +const GenreListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default GenreListRouteWithBoundary; diff --git a/src/renderer/features/home/routes/home-route.tsx b/src/renderer/features/home/routes/home-route.tsx index 0540c1342..596622f45 100644 --- a/src/renderer/features/home/routes/home-route.tsx +++ b/src/renderer/features/home/routes/home-route.tsx @@ -10,6 +10,7 @@ import { FeaturedGenres } from '/@/renderer/features/home/components/featured-ge import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { HomeItem, useCurrentServer, @@ -154,12 +155,14 @@ const HomeRoute = () => { ); }; -const SuspensedHomeRoute = () => { +const HomeRouteWithBoundary = () => { return ( - }> - - + + }> + + + ); }; -export default SuspensedHomeRoute; +export default HomeRouteWithBoundary; diff --git a/src/renderer/features/login/routes/login-route.tsx b/src/renderer/features/login/routes/login-route.tsx index 622dd369e..7a5b33500 100644 --- a/src/renderer/features/login/routes/login-route.tsx +++ b/src/renderer/features/login/routes/login-route.tsx @@ -11,6 +11,7 @@ import JellyfinIcon from '/@/renderer/features/servers/assets/jellyfin.png'; import NavidromeIcon from '/@/renderer/features/servers/assets/navidrome.png'; import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { AppRoute } from '/@/renderer/router/routes'; import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store'; import { Button } from '/@/shared/components/button/button'; @@ -200,4 +201,12 @@ const LoginRoute = () => { ); }; -export default LoginRoute; +const LoginRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default LoginRouteWithBoundary; diff --git a/src/renderer/features/now-playing/routes/now-playing-route.tsx b/src/renderer/features/now-playing/routes/now-playing-route.tsx index 74489bdd8..d0c2c318d 100644 --- a/src/renderer/features/now-playing/routes/now-playing-route.tsx +++ b/src/renderer/features/now-playing/routes/now-playing-route.tsx @@ -6,6 +6,7 @@ import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queu import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { ItemListKey } from '/@/shared/types/types'; const NowPlayingRoute = () => { @@ -28,4 +29,12 @@ const NowPlayingRoute = () => { ); }; -export default NowPlayingRoute; +const NowPlayingRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default NowPlayingRouteWithBoundary; diff --git a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx index 59a71a873..b6b017fb1 100644 --- a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx @@ -14,6 +14,7 @@ import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/crea import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; @@ -233,4 +234,12 @@ const PlaylistDetailSongListRoute = () => { ); }; -export default PlaylistDetailSongListRoute; +const PlaylistDetailSongListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default PlaylistDetailSongListRouteWithBoundary; diff --git a/src/renderer/features/playlists/routes/playlist-list-route.tsx b/src/renderer/features/playlists/routes/playlist-list-route.tsx index 4b8692b71..043bc6f6e 100644 --- a/src/renderer/features/playlists/routes/playlist-list-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-list-route.tsx @@ -6,6 +6,7 @@ import { PlaylistListContent } from '/@/renderer/features/playlists/components/p import { PlaylistListHeader } from '/@/renderer/features/playlists/components/playlist-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { ItemListKey } from '/@/shared/types/types'; const PlaylistListRoute = () => { @@ -35,4 +36,12 @@ const PlaylistListRoute = () => { ); }; -export default PlaylistListRoute; +const PlaylistListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default PlaylistListRouteWithBoundary; diff --git a/src/renderer/features/search/routes/search-route.tsx b/src/renderer/features/search/routes/search-route.tsx index 882425943..6b19ee3a0 100644 --- a/src/renderer/features/search/routes/search-route.tsx +++ b/src/renderer/features/search/routes/search-route.tsx @@ -5,6 +5,7 @@ import { SearchContent } from '/@/renderer/features/search/components/search-con import { SearchHeader } from '/@/renderer/features/search/components/search-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; const SearchRoute = () => { const { state: locationState } = useLocation(); @@ -22,4 +23,12 @@ const SearchRoute = () => { ); }; -export default SearchRoute; +const SearchRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default SearchRouteWithBoundary; diff --git a/src/renderer/features/shared/components/page-error-boundary.tsx b/src/renderer/features/shared/components/page-error-boundary.tsx new file mode 100644 index 000000000..3385c985d --- /dev/null +++ b/src/renderer/features/shared/components/page-error-boundary.tsx @@ -0,0 +1,97 @@ +import { ErrorBoundary } from 'react-error-boundary'; +import { useTranslation } from 'react-i18next'; + +import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector'; +import { Box } from '/@/shared/components/box/box'; +import { Button } from '/@/shared/components/button/button'; +import { Center } from '/@/shared/components/center/center'; +import { Code } from '/@/shared/components/code/code'; +import { Group } from '/@/shared/components/group/group'; +import { Icon } from '/@/shared/components/icon/icon'; +import { Stack } from '/@/shared/components/stack/stack'; +import { TextTitle } from '/@/shared/components/text-title/text-title'; +import { Text } from '/@/shared/components/text/text'; + +interface PageErrorFallbackProps { + error: Error; + resetErrorBoundary: () => void; +} + +const PageErrorFallback = ({ error, resetErrorBoundary }: PageErrorFallbackProps) => { + const { t } = useTranslation(); + + const handleRefresh = () => { + window.location.reload(); + }; + + return ( + + + + +
+ + + + + {t('error.genericError', { postProcess: 'sentenceCase' })} + + + + {error?.message || t('error.genericError', { postProcess: 'sentenceCase' })} + + {process.env.NODE_ENV === 'development' && error?.stack && ( + + {error.stack} + + )} + + + + + +
+
+ ); +}; + +interface PageErrorBoundaryProps { + children: React.ReactNode; +} + +export const PageErrorBoundary = ({ children }: PageErrorBoundaryProps) => { + return ( + { + if (process.env.NODE_ENV === 'development') { + console.error('Page error boundary caught an error:', error, errorInfo); + } + }} + onReset={() => {}} + > + {children} + + ); +}; diff --git a/src/renderer/components/error-boundary/router-error-boundary.tsx b/src/renderer/features/shared/components/router-error-boundary.tsx similarity index 87% rename from src/renderer/components/error-boundary/router-error-boundary.tsx rename to src/renderer/features/shared/components/router-error-boundary.tsx index 52dea1674..770b8259a 100644 --- a/src/renderer/components/error-boundary/router-error-boundary.tsx +++ b/src/renderer/features/shared/components/router-error-boundary.tsx @@ -5,9 +5,11 @@ import { ServerSelector } from '/@/renderer/features/sidebar/components/server-s import { Box } from '/@/shared/components/box/box'; import { Button } from '/@/shared/components/button/button'; import { Center } from '/@/shared/components/center/center'; +import { Code } from '/@/shared/components/code/code'; import { Group } from '/@/shared/components/group/group'; import { Icon } from '/@/shared/components/icon/icon'; import { Stack } from '/@/shared/components/stack/stack'; +import { TextTitle } from '/@/shared/components/text-title/text-title'; import { Text } from '/@/shared/components/text/text'; interface RouterErrorFallbackProps { @@ -41,32 +43,30 @@ const RouterErrorFallback = ({ error, resetErrorBoundary }: RouterErrorFallbackP > -
- +
+ - + {t('error.genericError', { postProcess: 'sentenceCase' })} - + - + {error?.message || t('error.genericError', { postProcess: 'sentenceCase' })} {process.env.NODE_ENV === 'development' && error?.stack && ( - {error.stack} - + )}