handle 404 pages, #890

This commit is contained in:
Simon 2025-03-15 16:56:18 +01:00
parent c5f549967b
commit a4824497ef
6 changed files with 94 additions and 23 deletions

View File

@ -55,6 +55,13 @@ const APIClient = async (
throw new Error('Forbidden: Access denied.');
}
if (response.status === 404) {
throw {
status: 404,
message: 'Resource not found',
} as ApiError;
}
let data;
// expected empty response

View File

@ -27,6 +27,7 @@ import ChannelAbout from './pages/ChannelAbout';
import Download from './pages/Download';
import loadUserAccount from './api/loader/loadUserAccount';
import loadAppsettingsConfig from './api/loader/loadAppsettingsConfig';
import NotFound from './pages/404Page';
const router = createBrowserRouter(
[
@ -145,6 +146,10 @@ const router = createBrowserRouter(
element: <Login />,
errorElement: <ErrorPage />,
},
{
path: '*',
element: <NotFound />,
},
],
{ basename: import.meta.env.BASE_URL },
);

View File

@ -0,0 +1,22 @@
import { Link } from 'react-router-dom';
import useColours from '../configuration/colours/useColours';
import Routes from '../configuration/routes/RouteList';
const NotFound = ({ failType = 'page' }) => {
useColours();
return (
<>
<title>404 | Not found</title>
<div id="error-page" style={{ margin: '10%' }}>
<h1>Oops!</h1>
<p>
<i>404</i>
<span>: That {failType} does not exist.</span>
</p>
<Link to={Routes.Home}>Go Home</Link>
</div>
</>
);
};
export default NotFound;

View File

@ -8,6 +8,8 @@ import ChannelBanner from '../components/ChannelBanner';
import loadChannelNav, { ChannelNavResponseType } from '../api/loader/loadChannelNav';
import loadChannelById from '../api/loader/loadChannelById';
import useIsAdmin from '../functions/useIsAdmin';
import { ApiError } from '../functions/APIClient';
import NotFound from './404Page';
type ChannelParams = {
channelId: string;
@ -18,6 +20,7 @@ export type ChannelResponseType = ChannelType;
const ChannelBase = () => {
const { channelId } = useParams() as ChannelParams;
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
const [error, setError] = useState<ApiError | null>(null);
const isAdmin = useIsAdmin();
const [channelResponse, setChannelResponse] = useState<ChannelResponseType>();
@ -29,14 +32,24 @@ const ChannelBase = () => {
useEffect(() => {
(async () => {
setError(null);
try {
const channelResponse = await loadChannelById(channelId);
setChannelResponse(channelResponse);
} catch (err) {
if ((err as ApiError).status === 404) {
setError(err as ApiError);
} else {
console.error('Failed to fetch item:', err);
}
}
const channelNavResponse = await loadChannelNav(channelId);
const channelResponse = await loadChannelById(channelId);
setChannelResponse(channelResponse);
setChannelNav(channelNavResponse);
})();
}, [channelId]);
if (error) return <NotFound failType="channel" />;
if (!channelId) {
return [];
}

View File

@ -23,6 +23,8 @@ import Button from '../components/Button';
import loadVideoListByFilter from '../api/loader/loadVideoListByPage';
import useIsAdmin from '../functions/useIsAdmin';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { ApiError } from '../functions/APIClient';
import NotFound from './404Page';
export type VideoResponseType = {
data?: VideoType[];
@ -31,6 +33,7 @@ export type VideoResponseType = {
const Playlist = () => {
const { playlistId } = useParams();
const [error, setError] = useState<ApiError | null>(null);
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const videoId = searchParams.get('videoId');
@ -66,22 +69,28 @@ const Playlist = () => {
useEffect(() => {
(async () => {
const playlist = await loadPlaylistById(playlistId);
const video = await loadVideoListByFilter({
playlist: playlistId,
page: currentPage,
watch: hideWatched ? 'unwatched' : undefined,
});
const isCustomPlaylist = playlist?.playlist_type === 'custom';
if (!isCustomPlaylist) {
const channel = await loadChannelById(playlist.playlist_channel_id);
setChannelResponse(channel);
setError(null);
try {
const playlist = await loadPlaylistById(playlistId);
const video = await loadVideoListByFilter({
playlist: playlistId,
page: currentPage,
watch: hideWatched ? 'unwatched' : undefined,
});
setPlaylistResponse(playlist);
setVideoResponse(video);
const isCustomPlaylist = playlist?.playlist_type === 'custom';
if (!isCustomPlaylist) {
const channel = await loadChannelById(playlist.playlist_channel_id);
setChannelResponse(channel);
}
} catch (err) {
if ((err as ApiError).status === 404) {
setError(err as ApiError);
} else {
console.error('Failed to fetch item:', err);
}
}
setPlaylistResponse(playlist);
setVideoResponse(video);
setRefresh(false);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -94,9 +103,9 @@ const Playlist = () => {
videoId,
]);
if (!playlistId || !playlist) {
return `Playlist ${playlistId} not found!`;
}
if (error) return <NotFound failType="playlist" />;
if (!playlist || !playlistId) return [];
const isCustomPlaylist = playlist.playlist_type === 'custom';

View File

@ -43,6 +43,8 @@ import { useAppSettingsStore } from '../stores/AppSettingsStore';
import updateDownloadQueueStatusById from '../api/actions/updateDownloadQueueStatusById';
import { FileSizeUnits } from '../api/actions/updateUserConfig';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { ApiError } from '../functions/APIClient';
import NotFound from './404Page';
const isInPlaylist = (videoId: string, playlist: PlaylistType) => {
return playlist.playlist_entries.some(entry => {
@ -116,6 +118,7 @@ const Video = () => {
const { appSettingsConfig } = useAppSettingsStore();
const { userConfig } = useUserConfigStore();
const [error, setError] = useState<ApiError | null>(null);
const [videoEnded, setVideoEnded] = useState(false);
const [playlistAutoplay, setPlaylistAutoplay] = useState(
localStorage.getItem('playlistAutoplay') === 'true',
@ -138,7 +141,18 @@ const Video = () => {
useEffect(() => {
(async () => {
if (refreshVideoList || videoId !== videoResponse?.youtube_id) {
const videoByIdResponse = await loadVideoById(videoId);
setError(null);
try {
const videoByIdResponse = await loadVideoById(videoId);
setVideoResponse(videoByIdResponse);
} catch (err) {
if ((err as ApiError).status === 404) {
setError(err as ApiError);
} else {
console.error('Failed to fetch item:', err);
}
}
const simmilarVideosResponse = await loadSimmilarVideosById(videoId);
const customPlaylistsResponse = await loadPlaylistList({ type: 'custom' });
const videoNavResponse = await loadVideoNav(videoId);
@ -150,7 +164,6 @@ const Video = () => {
console.log('Comments not found', e);
}
setVideoResponse(videoByIdResponse);
setSimmilarVideos(simmilarVideosResponse);
setVideoPlaylistNav(videoNavResponse);
setCustomPlaylistsResponse(customPlaylistsResponse);
@ -191,6 +204,8 @@ const Video = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [videoEnded, playlistAutoplay]);
if (error) return <NotFound failType="video" />;
if (videoResponse === undefined) {
return [];
}