handle 404 pages, #890
This commit is contained in:
parent
c5f549967b
commit
a4824497ef
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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 [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue