diff --git a/src/apps/experimental/routes/routes.tsx b/src/apps/experimental/routes/routes.tsx index 046f447d65..57161a9891 100644 --- a/src/apps/experimental/routes/routes.tsx +++ b/src/apps/experimental/routes/routes.tsx @@ -5,6 +5,7 @@ import ConnectionRequired from 'components/ConnectionRequired'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; import ErrorBoundary from 'components/router/ErrorBoundary'; +import FallbackRoute from 'components/router/FallbackRoute'; import { ASYNC_USER_ROUTES } from './asyncRoutes'; import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes'; @@ -33,7 +34,13 @@ export const EXPERIMENTAL_APP_ROUTES: RouteObject[] = [ /* Public routes */ { index: true, element: }, - ...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute) + ...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute), + + /* Fallback route for invalid paths */ + { + path: '*', + Component: FallbackRoute + } ] } ]; diff --git a/src/apps/stable/routes/routes.tsx b/src/apps/stable/routes/routes.tsx index 787d89ac7f..08bd56634e 100644 --- a/src/apps/stable/routes/routes.tsx +++ b/src/apps/stable/routes/routes.tsx @@ -5,6 +5,7 @@ import ConnectionRequired from 'components/ConnectionRequired'; import { toAsyncPageRoute } from 'components/router/AsyncRoute'; import { toViewManagerPageRoute } from 'components/router/LegacyRoute'; import ErrorBoundary from 'components/router/ErrorBoundary'; +import FallbackRoute from 'components/router/FallbackRoute'; import AppLayout from '../AppLayout'; @@ -28,7 +29,13 @@ export const STABLE_APP_ROUTES: RouteObject[] = [ /* Public routes */ { index: true, element: }, - ...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute) + ...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute), + + /* Fallback route for invalid paths */ + { + path: '*', + Component: FallbackRoute + } ] } ]; diff --git a/src/components/router/FallbackRoute.tsx b/src/components/router/FallbackRoute.tsx new file mode 100644 index 0000000000..f438d730ad --- /dev/null +++ b/src/components/router/FallbackRoute.tsx @@ -0,0 +1,57 @@ +import React, { useEffect, useMemo } from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; + +import loading from 'components/loading/loading'; +import Page from 'components/Page'; +import globalize from 'lib/globalize'; +import LinkButton from 'elements/emby-button/LinkButton'; + +const FallbackRoute = () => { + const location = useLocation(); + + useEffect(() => { + loading.hide(); + }, []); + + // Check if the requested path should be redirected + const to = useMemo(() => { + const _to = { + search: location.search, + hash: location.hash + }; + + // If a path ends in ".html", redirect to the path with it removed + if (location.pathname.endsWith('.html')) { + return { ..._to, pathname: location.pathname.slice(0, -5) }; + } + }, [ location ]); + + if (to) { + console.warn('[FallbackRoute] You are using a deprecated URL format. This will stop working in a future Jellyfin update.'); + + return ( + + ); + } + + return ( + +

{globalize.translate('HeaderPageNotFound')}

+

{globalize.translate('PageNotFound')}

+ + {globalize.translate('GoHome')} + +
+ ); +}; + +export default FallbackRoute; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 71de76a2ab..bb5f9cf80a 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -473,6 +473,7 @@ "HeaderNoLyrics": "No lyrics found", "HeaderOnNow": "On Now", "HeaderOtherItems": "Other Items", + "HeaderPageNotFound": "Page not found", "HeaderParentalRatings": "Parental Ratings", "HeaderPassword": "Password", "HeaderPasswordReset": "Password Reset", @@ -1315,6 +1316,7 @@ "PackageInstallCancelled": "{0} (version {1}) installation cancelled.", "PackageInstallCompleted": "{0} (version {1}) installation completed.", "PackageInstallFailed": "{0} (version {1}) installation failed.", + "PageNotFound": "This is not the page you are looking for.", "ParentalRating": "Parental rating", "PasswordMatchError": "Password and password confirmation must match.", "PasswordRequiredForAdmin": "A password is required for admin accounts.",