mirror of
https://git.vectorsigma.ru/public/tubearchivist.git
synced 2026-08-04 19:09:29 +00:00
handle 404 pages, #890
This commit is contained in:
@@ -55,6 +55,13 @@ const APIClient = async (
|
|||||||
throw new Error('Forbidden: Access denied.');
|
throw new Error('Forbidden: Access denied.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw {
|
||||||
|
status: 404,
|
||||||
|
message: 'Resource not found',
|
||||||
|
} as ApiError;
|
||||||
|
}
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
// expected empty response
|
// expected empty response
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import ChannelAbout from './pages/ChannelAbout';
|
|||||||
import Download from './pages/Download';
|
import Download from './pages/Download';
|
||||||
import loadUserAccount from './api/loader/loadUserAccount';
|
import loadUserAccount from './api/loader/loadUserAccount';
|
||||||
import loadAppsettingsConfig from './api/loader/loadAppsettingsConfig';
|
import loadAppsettingsConfig from './api/loader/loadAppsettingsConfig';
|
||||||
|
import NotFound from './pages/404Page';
|
||||||
|
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
[
|
[
|
||||||
@@ -145,6 +146,10 @@ const router = createBrowserRouter(
|
|||||||
element: <Login />,
|
element: <Login />,
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
element: <NotFound />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
{ basename: import.meta.env.BASE_URL },
|
{ basename: import.meta.env.BASE_URL },
|
||||||
);
|
);
|
||||||
|
|||||||
22
frontend/src/pages/404Page.tsx
Normal file
22
frontend/src/pages/404Page.tsx
Normal 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;
|
||||||
@@ -8,6 +8,8 @@ import ChannelBanner from '../components/ChannelBanner';
|
|||||||
import loadChannelNav, { ChannelNavResponseType } from '../api/loader/loadChannelNav';
|
import loadChannelNav, { ChannelNavResponseType } from '../api/loader/loadChannelNav';
|
||||||
import loadChannelById from '../api/loader/loadChannelById';
|
import loadChannelById from '../api/loader/loadChannelById';
|
||||||
import useIsAdmin from '../functions/useIsAdmin';
|
import useIsAdmin from '../functions/useIsAdmin';
|
||||||
|
import { ApiError } from '../functions/APIClient';
|
||||||
|
import NotFound from './404Page';
|
||||||
|
|
||||||
type ChannelParams = {
|
type ChannelParams = {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
@@ -18,6 +20,7 @@ export type ChannelResponseType = ChannelType;
|
|||||||
const ChannelBase = () => {
|
const ChannelBase = () => {
|
||||||
const { channelId } = useParams() as ChannelParams;
|
const { channelId } = useParams() as ChannelParams;
|
||||||
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
|
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
|
||||||
|
const [error, setError] = useState<ApiError | null>(null);
|
||||||
const isAdmin = useIsAdmin();
|
const isAdmin = useIsAdmin();
|
||||||
|
|
||||||
const [channelResponse, setChannelResponse] = useState<ChannelResponseType>();
|
const [channelResponse, setChannelResponse] = useState<ChannelResponseType>();
|
||||||
@@ -29,14 +32,24 @@ const ChannelBase = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(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 channelNavResponse = await loadChannelNav(channelId);
|
||||||
const channelResponse = await loadChannelById(channelId);
|
|
||||||
|
|
||||||
setChannelResponse(channelResponse);
|
|
||||||
setChannelNav(channelNavResponse);
|
setChannelNav(channelNavResponse);
|
||||||
})();
|
})();
|
||||||
}, [channelId]);
|
}, [channelId]);
|
||||||
|
|
||||||
|
if (error) return <NotFound failType="channel" />;
|
||||||
|
|
||||||
if (!channelId) {
|
if (!channelId) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import Button from '../components/Button';
|
|||||||
import loadVideoListByFilter from '../api/loader/loadVideoListByPage';
|
import loadVideoListByFilter from '../api/loader/loadVideoListByPage';
|
||||||
import useIsAdmin from '../functions/useIsAdmin';
|
import useIsAdmin from '../functions/useIsAdmin';
|
||||||
import { useUserConfigStore } from '../stores/UserConfigStore';
|
import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||||
|
import { ApiError } from '../functions/APIClient';
|
||||||
|
import NotFound from './404Page';
|
||||||
|
|
||||||
export type VideoResponseType = {
|
export type VideoResponseType = {
|
||||||
data?: VideoType[];
|
data?: VideoType[];
|
||||||
@@ -31,6 +33,7 @@ export type VideoResponseType = {
|
|||||||
|
|
||||||
const Playlist = () => {
|
const Playlist = () => {
|
||||||
const { playlistId } = useParams();
|
const { playlistId } = useParams();
|
||||||
|
const [error, setError] = useState<ApiError | null>(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const videoId = searchParams.get('videoId');
|
const videoId = searchParams.get('videoId');
|
||||||
@@ -66,22 +69,28 @@ const Playlist = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const playlist = await loadPlaylistById(playlistId);
|
setError(null);
|
||||||
const video = await loadVideoListByFilter({
|
try {
|
||||||
playlist: playlistId,
|
const playlist = await loadPlaylistById(playlistId);
|
||||||
page: currentPage,
|
const video = await loadVideoListByFilter({
|
||||||
watch: hideWatched ? 'unwatched' : undefined,
|
playlist: playlistId,
|
||||||
});
|
page: currentPage,
|
||||||
|
watch: hideWatched ? 'unwatched' : undefined,
|
||||||
const isCustomPlaylist = playlist?.playlist_type === 'custom';
|
});
|
||||||
if (!isCustomPlaylist) {
|
setPlaylistResponse(playlist);
|
||||||
const channel = await loadChannelById(playlist.playlist_channel_id);
|
setVideoResponse(video);
|
||||||
|
const isCustomPlaylist = playlist?.playlist_type === 'custom';
|
||||||
setChannelResponse(channel);
|
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);
|
setRefresh(false);
|
||||||
})();
|
})();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -94,9 +103,9 @@ const Playlist = () => {
|
|||||||
videoId,
|
videoId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!playlistId || !playlist) {
|
if (error) return <NotFound failType="playlist" />;
|
||||||
return `Playlist ${playlistId} not found!`;
|
|
||||||
}
|
if (!playlist || !playlistId) return [];
|
||||||
|
|
||||||
const isCustomPlaylist = playlist.playlist_type === 'custom';
|
const isCustomPlaylist = playlist.playlist_type === 'custom';
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ import { useAppSettingsStore } from '../stores/AppSettingsStore';
|
|||||||
import updateDownloadQueueStatusById from '../api/actions/updateDownloadQueueStatusById';
|
import updateDownloadQueueStatusById from '../api/actions/updateDownloadQueueStatusById';
|
||||||
import { FileSizeUnits } from '../api/actions/updateUserConfig';
|
import { FileSizeUnits } from '../api/actions/updateUserConfig';
|
||||||
import { useUserConfigStore } from '../stores/UserConfigStore';
|
import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||||
|
import { ApiError } from '../functions/APIClient';
|
||||||
|
import NotFound from './404Page';
|
||||||
|
|
||||||
const isInPlaylist = (videoId: string, playlist: PlaylistType) => {
|
const isInPlaylist = (videoId: string, playlist: PlaylistType) => {
|
||||||
return playlist.playlist_entries.some(entry => {
|
return playlist.playlist_entries.some(entry => {
|
||||||
@@ -116,6 +118,7 @@ const Video = () => {
|
|||||||
const { appSettingsConfig } = useAppSettingsStore();
|
const { appSettingsConfig } = useAppSettingsStore();
|
||||||
const { userConfig } = useUserConfigStore();
|
const { userConfig } = useUserConfigStore();
|
||||||
|
|
||||||
|
const [error, setError] = useState<ApiError | null>(null);
|
||||||
const [videoEnded, setVideoEnded] = useState(false);
|
const [videoEnded, setVideoEnded] = useState(false);
|
||||||
const [playlistAutoplay, setPlaylistAutoplay] = useState(
|
const [playlistAutoplay, setPlaylistAutoplay] = useState(
|
||||||
localStorage.getItem('playlistAutoplay') === 'true',
|
localStorage.getItem('playlistAutoplay') === 'true',
|
||||||
@@ -138,7 +141,18 @@ const Video = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (refreshVideoList || videoId !== videoResponse?.youtube_id) {
|
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 simmilarVideosResponse = await loadSimmilarVideosById(videoId);
|
||||||
const customPlaylistsResponse = await loadPlaylistList({ type: 'custom' });
|
const customPlaylistsResponse = await loadPlaylistList({ type: 'custom' });
|
||||||
const videoNavResponse = await loadVideoNav(videoId);
|
const videoNavResponse = await loadVideoNav(videoId);
|
||||||
@@ -150,7 +164,6 @@ const Video = () => {
|
|||||||
console.log('Comments not found', e);
|
console.log('Comments not found', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoResponse(videoByIdResponse);
|
|
||||||
setSimmilarVideos(simmilarVideosResponse);
|
setSimmilarVideos(simmilarVideosResponse);
|
||||||
setVideoPlaylistNav(videoNavResponse);
|
setVideoPlaylistNav(videoNavResponse);
|
||||||
setCustomPlaylistsResponse(customPlaylistsResponse);
|
setCustomPlaylistsResponse(customPlaylistsResponse);
|
||||||
@@ -191,6 +204,8 @@ const Video = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [videoEnded, playlistAutoplay]);
|
}, [videoEnded, playlistAutoplay]);
|
||||||
|
|
||||||
|
if (error) return <NotFound failType="video" />;
|
||||||
|
|
||||||
if (videoResponse === undefined) {
|
if (videoResponse === undefined) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user