mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Fix skeletons and errors on show page
This commit is contained in:
parent
6cd976e057
commit
f95d8b565e
@ -5,6 +5,10 @@
|
|||||||
"studio": "Studio",
|
"studio": "Studio",
|
||||||
"genre": "Genres",
|
"genre": "Genres",
|
||||||
"genre-none": "No genres",
|
"genre-none": "No genres",
|
||||||
"staff": "Staff"
|
"staff": "Staff",
|
||||||
|
"staff-none": "The staff is unknown",
|
||||||
|
"noOverview": "No overview available",
|
||||||
|
"episode-none": "There is no episodes in this season",
|
||||||
|
"episodeNoMetadata": "No metadata available"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
"studio": "Studio",
|
"studio": "Studio",
|
||||||
"genre": "Genres",
|
"genre": "Genres",
|
||||||
"genre-none": "Aucun genres",
|
"genre-none": "Aucun genres",
|
||||||
"staff": "Staff"
|
"staff": "Staff",
|
||||||
|
"staff-none": "Aucun membre du staff connu",
|
||||||
|
"noOverview": "Aucune description disponible",
|
||||||
|
"episode-none": "Il n'y a pas d'episodes dans cette saison",
|
||||||
|
"episodeNoMetadata": "Aucune metadonnée disponible"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
"next-translate": "^1.5.0",
|
"next-translate": "^1.5.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-intersection-observer": "^9.4.0",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-query": "^4.0.0-beta.23",
|
"react-query": "^4.0.0-beta.23",
|
||||||
"superjson": "^1.9.1",
|
"superjson": "^1.9.1",
|
||||||
"zod": "^3.18.0"
|
"zod": "^3.18.0"
|
||||||
|
@ -20,14 +20,22 @@
|
|||||||
|
|
||||||
import { styled, experimental_sx as sx } from "@mui/system";
|
import { styled, experimental_sx as sx } from "@mui/system";
|
||||||
|
|
||||||
export const Container = styled("div")(sx({
|
export const Container = styled("div")(
|
||||||
|
sx({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
pl: "15px",
|
px: "15px",
|
||||||
pr: "15px",
|
|
||||||
mx: "auto",
|
mx: "auto",
|
||||||
width: {
|
width: {
|
||||||
sm: "540px",
|
sm: "540px",
|
||||||
md: "880px",
|
md: "880px",
|
||||||
lg: "1170px",
|
lg: "1170px",
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const containerPadding = {
|
||||||
|
xs: "15px",
|
||||||
|
sm: "calc((100vw - 540px) / 2)",
|
||||||
|
md: "calc((100vw - 880px) / 2)",
|
||||||
|
lg: "calc((100vw - 1170px) / 2)",
|
||||||
|
};
|
||||||
|
@ -19,12 +19,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Box, Divider, Skeleton, SxProps, Typography } from "@mui/material";
|
import { Box, Divider, Skeleton, SxProps, Typography } from "@mui/material";
|
||||||
|
import useTranslation from "next-translate/useTranslation";
|
||||||
import { Episode } from "~/models";
|
import { Episode } from "~/models";
|
||||||
import { Link } from "~/utils/link";
|
import { Link } from "~/utils/link";
|
||||||
import { Image } from "./poster";
|
import { Image } from "./poster";
|
||||||
|
|
||||||
const displayNumber = (episode: Episode) => {
|
const displayNumber = (episode: Episode) => {
|
||||||
if (episode.seasonNumber && episode.episodeNumber)
|
if (typeof episode.seasonNumber === "number" && typeof episode.episodeNumber === "number")
|
||||||
return `S${episode.seasonNumber}:E${episode.episodeNumber}`;
|
return `S${episode.seasonNumber}:E${episode.episodeNumber}`;
|
||||||
if (episode.absoluteNumber) return episode.absoluteNumber.toString();
|
if (episode.absoluteNumber) return episode.absoluteNumber.toString();
|
||||||
return "???";
|
return "???";
|
||||||
@ -41,6 +42,8 @@ export const EpisodeBox = ({ episode, sx }: { episode?: Episode; sx: SxProps })
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const EpisodeLine = ({ episode, sx }: { episode?: Episode; sx?: SxProps }) => {
|
export const EpisodeLine = ({ episode, sx }: { episode?: Episode; sx?: SxProps }) => {
|
||||||
|
const { t } = useTranslation("browse");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
@ -59,10 +62,17 @@ export const EpisodeLine = ({ episode, sx }: { episode?: Episode; sx?: SxProps }
|
|||||||
{episode ? displayNumber(episode) : <Skeleton />}
|
{episode ? displayNumber(episode) : <Skeleton />}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Image img={episode?.thumbnail} width="18%" aspectRatio="16/9" sx={{ flexShrink: 0 }} />
|
<Image img={episode?.thumbnail} width="18%" aspectRatio="16/9" sx={{ flexShrink: 0 }} />
|
||||||
<Box>
|
{episode ? (
|
||||||
<Typography variant="h6">{episode?.name ?? <Skeleton />}</Typography>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Typography variant="body2">{episode?.overview ?? <Skeleton />}</Typography>
|
<Typography variant="h6">{episode.name ?? t("show.episodeNoMetadata")}</Typography>
|
||||||
|
{episode.overview && <Typography variant="body2">{episode.overview}</Typography>}
|
||||||
</Box>
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
<Typography variant="h6">{<Skeleton />}</Typography>
|
||||||
|
<Typography variant="body2">{<Skeleton />}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Divider />
|
<Divider />
|
||||||
</>
|
</>
|
||||||
|
@ -18,20 +18,38 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Alert, Snackbar, SnackbarCloseReason, Typography } from "@mui/material";
|
import { Alert, Box, Snackbar, SnackbarCloseReason, Typography, SxProps } from "@mui/material";
|
||||||
import { SyntheticEvent, useState } from "react";
|
import { SyntheticEvent, useState } from "react";
|
||||||
import { KyooErrors } from "~/models";
|
import { KyooErrors } from "~/models";
|
||||||
|
|
||||||
export const ErrorPage = ({ errors }: { errors: string[] }) => {
|
export const ErrorComponent = ({ errors, sx }: { errors: string[], sx?: SxProps }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<Box
|
||||||
<Typography variant="h1">Error</Typography>
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
backgroundColor: "error.light",
|
||||||
|
...sx
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h1" component="h1" sx={{ fontWeight: 500 }}>Error</Typography>
|
||||||
{errors.map((x, i) => (
|
{errors.map((x, i) => (
|
||||||
<Typography variant="h2" key={i}>
|
<Typography variant="h2" component="h2" key={i}>
|
||||||
{x}
|
{x}
|
||||||
</Typography>
|
</Typography>
|
||||||
))}
|
))}
|
||||||
</>
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ErrorPage = ({ errors }: { errors: string[] }) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: "100vh" }}>
|
||||||
|
<ErrorComponent errors={errors} sx={{ backgroundColor: "unset" }} />
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export const PersonAvatar = ({ person, sx }: { person?: Person; sx?: SxProps })
|
|||||||
if (!person) {
|
if (!person) {
|
||||||
return (
|
return (
|
||||||
<Box sx={sx}>
|
<Box sx={sx}>
|
||||||
<Skeleton variant="circular" sx={{ width: "100%", aspectRatio: "1/1" }}/>
|
<Skeleton variant="circular" sx={{ width: "100%", aspectRatio: "1/1", height: "unset" }}/>
|
||||||
<Typography align="center"><Skeleton/></Typography>
|
<Typography align="center"><Skeleton/></Typography>
|
||||||
<Typography variant="body2" align="center"><Skeleton/></Typography>
|
<Typography variant="body2" align="center"><Skeleton/></Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -62,8 +62,9 @@ const _Image = ({
|
|||||||
|
|
||||||
// This allow the loading bool to be false with SSR but still be on client-side
|
// This allow the loading bool to be false with SSR but still be on client-side
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!imgRef.current?.complete) setLoading(true);
|
if (!imgRef.current?.complete && img) setLoading(true);
|
||||||
}, []);
|
if (!img && !loading) setLoading(false);
|
||||||
|
}, [img, loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -73,7 +74,7 @@ const _Image = ({
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
backgroundColor: "primary.dark",
|
backgroundColor: "grey.300",
|
||||||
"& > *": { width: "100%", height: "100%" },
|
"& > *": { width: "100%", height: "100%" },
|
||||||
}}
|
}}
|
||||||
{...others}
|
{...others}
|
||||||
@ -100,9 +101,9 @@ const _Image = ({
|
|||||||
export const Image = styled(_Image)({});
|
export const Image = styled(_Image)({});
|
||||||
|
|
||||||
// eslint-disable-next-line jsx-a11y/alt-text
|
// eslint-disable-next-line jsx-a11y/alt-text
|
||||||
const _Poster = (
|
const _Poster = (props: ImagePropsWithLoading & { width?: Width; height?: Height }) => (
|
||||||
props: ImagePropsWithLoading & { width?: Width; height?: Height },
|
<_Image aspectRatio="2 / 3" {...props} />
|
||||||
) => <_Image aspectRatio="2 / 3" {...props} />;
|
);
|
||||||
|
|
||||||
declare module "@mui/material/styles" {
|
declare module "@mui/material/styles" {
|
||||||
interface ComponentsPropsList {
|
interface ComponentsPropsList {
|
||||||
|
@ -47,17 +47,17 @@ export const EpisodeP = z.preprocess(
|
|||||||
/**
|
/**
|
||||||
* The title of this episode.
|
* The title of this episode.
|
||||||
*/
|
*/
|
||||||
name: z.string(),
|
name: z.string().nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The overview of this episode.
|
* The overview of this episode.
|
||||||
*/
|
*/
|
||||||
overview: z.string(),
|
overview: z.string().nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The release date of this episode. It can be null if unknown.
|
* The release date of this episode. It can be null if unknown.
|
||||||
*/
|
*/
|
||||||
releaseDate: zdate(),
|
releaseDate: zdate().nullable(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export const SeasonP = z.preprocess(
|
|||||||
/**
|
/**
|
||||||
* A quick overview of this season.
|
* A quick overview of this season.
|
||||||
*/
|
*/
|
||||||
overview: z.string(),
|
overview: z.string().nullable(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The starting air date of this season.
|
* The starting air date of this season.
|
||||||
|
@ -37,7 +37,9 @@ export enum Status {
|
|||||||
|
|
||||||
export const ShowP = z.preprocess(
|
export const ShowP = z.preprocess(
|
||||||
(x: any) => {
|
(x: any) => {
|
||||||
|
// Waiting for the API to be updaded
|
||||||
x.name = x.title;
|
x.name = x.title;
|
||||||
|
if (x.aliases === null) x.aliases = [];
|
||||||
return x;
|
return x;
|
||||||
},
|
},
|
||||||
ResourceP.merge(ImagesP).extend({
|
ResourceP.merge(ImagesP).extend({
|
||||||
@ -52,7 +54,7 @@ export const ShowP = z.preprocess(
|
|||||||
/**
|
/**
|
||||||
* The summary of this show.
|
* The summary of this show.
|
||||||
*/
|
*/
|
||||||
overview: z.string(),
|
overview: z.string().nullable(),
|
||||||
/**
|
/**
|
||||||
* Is this show airing, not aired yet or finished?
|
* Is this show airing, not aired yet or finished?
|
||||||
*/
|
*/
|
||||||
@ -72,7 +74,7 @@ export const ShowP = z.preprocess(
|
|||||||
/**
|
/**
|
||||||
* The studio that made this show.
|
* The studio that made this show.
|
||||||
*/
|
*/
|
||||||
studio: StudioP.optional(),
|
studio: StudioP.optional().nullable(),
|
||||||
/**
|
/**
|
||||||
* The list of seasons of this show.
|
* The list of seasons of this show.
|
||||||
*/
|
*/
|
||||||
|
@ -42,14 +42,13 @@ import { QueryIdentifier, QueryPage, useFetch, useInfiniteFetch } from "~/utils/
|
|||||||
import { getDisplayDate } from "~/models/utils";
|
import { getDisplayDate } from "~/models/utils";
|
||||||
import { useScroll } from "~/utils/hooks/use-scroll";
|
import { useScroll } from "~/utils/hooks/use-scroll";
|
||||||
import { withRoute } from "~/utils/router";
|
import { withRoute } from "~/utils/router";
|
||||||
import { Container } from "~/components/container";
|
import { Container, containerPadding } from "~/components/container";
|
||||||
import { makeTitle } from "~/utils/utils";
|
import { makeTitle } from "~/utils/utils";
|
||||||
import { Link } from "~/utils/link";
|
import { Link } from "~/utils/link";
|
||||||
import { Studio } from "~/models/resources/studio";
|
import { Studio } from "~/models/resources/studio";
|
||||||
import { Paged, Person, PersonP } from "~/models";
|
import { Paged, Person, PersonP } from "~/models";
|
||||||
import { PersonAvatar } from "~/components/person";
|
import { PersonAvatar } from "~/components/person";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { ErrorComponent, ErrorPage } from "~/components/errors";
|
||||||
import { ErrorPage } from "~/components/errors";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { EpisodeBox, EpisodeLine } from "~/components/episode";
|
import { EpisodeBox, EpisodeLine } from "~/components/episode";
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ const StudioText = ({
|
|||||||
loading = false,
|
loading = false,
|
||||||
sx,
|
sx,
|
||||||
}: {
|
}: {
|
||||||
studio?: Studio;
|
studio?: Studio | null;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
}) => {
|
}) => {
|
||||||
@ -196,7 +195,7 @@ const ShowHeader = ({ data }: { data?: Show }) => {
|
|||||||
{": "}
|
{": "}
|
||||||
{!data ? (
|
{!data ? (
|
||||||
<Skeleton width="10rem" sx={{ display: "inline-flex" }} />
|
<Skeleton width="10rem" sx={{ display: "inline-flex" }} />
|
||||||
) : data?.genres ? (
|
) : data?.genres && data.genres.length ? (
|
||||||
data.genres.map((genre, i) => [
|
data.genres.map((genre, i) => [
|
||||||
i > 0 && ", ",
|
i > 0 && ", ",
|
||||||
<Link key={genre.id} href={`/genres/${genre.slug}`}>
|
<Link key={genre.id} href={`/genres/${genre.slug}`}>
|
||||||
@ -211,7 +210,9 @@ const ShowHeader = ({ data }: { data?: Show }) => {
|
|||||||
|
|
||||||
<Container sx={{ pt: 2 }}>
|
<Container sx={{ pt: 2 }}>
|
||||||
<Typography align="justify" sx={{ flexBasis: 0, flexGrow: 1, pt: { sm: 2 } }}>
|
<Typography align="justify" sx={{ flexBasis: 0, flexGrow: 1, pt: { sm: 2 } }}>
|
||||||
{data?.overview ?? [...Array(4)].map((_, i) => <Skeleton key={i} />)}
|
{data
|
||||||
|
? data.overview ?? t("show.noOverview")
|
||||||
|
: [...Array(4)].map((_, i) => <Skeleton key={i} />)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
@ -229,7 +230,7 @@ const ShowHeader = ({ data }: { data?: Show }) => {
|
|||||||
<Typography variant="h4" component="h2">
|
<Typography variant="h4" component="h2">
|
||||||
{t("show.genre")}
|
{t("show.genre")}
|
||||||
</Typography>
|
</Typography>
|
||||||
{!data || data.genres ? (
|
{!data || data.genres?.length ? (
|
||||||
<ul>
|
<ul>
|
||||||
{(data ? data.genres! : [...Array(3)]).map((genre, i) => (
|
{(data ? data.genres! : [...Array(3)]).map((genre, i) => (
|
||||||
<li key={genre?.id ?? i}>
|
<li key={genre?.id ?? i}>
|
||||||
@ -262,19 +263,17 @@ const ShowStaff = ({ slug }: { slug: string }) => {
|
|||||||
const { data, isError, error, isFetching, hasNextPage, fetchNextPage } = useInfiniteFetch(
|
const { data, isError, error, isFetching, hasNextPage, fetchNextPage } = useInfiniteFetch(
|
||||||
staffQuery(slug),
|
staffQuery(slug),
|
||||||
);
|
);
|
||||||
const { ref } = useInView({
|
|
||||||
onChange: () => !isFetching && hasNextPage && fetchNextPage(),
|
|
||||||
});
|
|
||||||
const { t } = useTranslation("browse");
|
const { t } = useTranslation("browse");
|
||||||
|
|
||||||
// TODO: Unsure that the fetchNextPage is only used when needed (currently called way too mutch)
|
// TODO: Unsure that the fetchNextPage is only used when needed (currently called way too mutch)
|
||||||
// TODO: Handle errors
|
|
||||||
|
|
||||||
/* if (isError) return null; */
|
if (isError) return <ErrorComponent {...error} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container sx={{ display: "flex", flexDirection: "row", justifyContent: "space-between", py: 3 }}>
|
<Container
|
||||||
|
sx={{ display: "flex", flexDirection: "row", justifyContent: "space-between", py: 3 }}
|
||||||
|
>
|
||||||
<Typography variant="h4" component="h2">
|
<Typography variant="h4" component="h2">
|
||||||
{t("show.staff")}
|
{t("show.staff")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -287,16 +286,31 @@ const ShowStaff = ({ slug }: { slug: string }) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", maxWidth: "100%", overflowY: "auto", py: 1, }}>
|
{data && data?.pages.at(0)?.count === 0 ? (
|
||||||
{(data ? data.pages.flatMap((x) => x.items) : [...Array(6)]).map((x, i) => (
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Typography sx={{ py: 3 }}>{t("show.staff-none")}</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Container
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
maxWidth: "100%",
|
||||||
|
overflowY: "auto",
|
||||||
|
pt: 1,
|
||||||
|
pb: 2,
|
||||||
|
overflowX: "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(data ? data.pages.flatMap((x) => x.items) : [...Array(20)]).map((x, i) => (
|
||||||
<PersonAvatar
|
<PersonAvatar
|
||||||
key={x ? x.id : i}
|
key={x ? x.id : i}
|
||||||
person={x}
|
person={x}
|
||||||
sx={{ width: { xs: "7rem", lg: "10rem" }, flexShrink: 0, px: 2 }}
|
sx={{ width: { xs: "7rem", lg: "10rem" }, flexShrink: 0, px: 2 }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div ref={ref} />
|
</Container>
|
||||||
</Box>
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -314,14 +328,19 @@ const EpisodeGrid = ({ slug, season }: { slug: string; season: number }) => {
|
|||||||
const { data, isError, error, isFetching, hasNextPage, fetchNextPage } = useInfiniteFetch(
|
const { data, isError, error, isFetching, hasNextPage, fetchNextPage } = useInfiniteFetch(
|
||||||
episodesQuery(slug, season),
|
episodesQuery(slug, season),
|
||||||
);
|
);
|
||||||
const { ref } = useInView({
|
const { t } = useTranslation("browse");
|
||||||
onChange: () => !isFetching && hasNextPage && fetchNextPage(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Unsure that the fetchNextPage is only used when needed (currently called way too mutch)
|
// TODO: Unsure that the fetchNextPage is only used when needed (currently called way too mutch)
|
||||||
// TODO: Handle errors
|
|
||||||
|
|
||||||
/* if (isError) return null; */
|
if (isError) return <ErrorComponent {...error} />;
|
||||||
|
|
||||||
|
if (data && data.pages.at(0)?.count === 0) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Typography sx={{ py: 3 }}>{t("show.episode-none")}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", backgroundColor: "background.paper" }}>
|
<Box sx={{ display: "flex", flexDirection: "column", backgroundColor: "background.paper" }}>
|
||||||
@ -339,14 +358,12 @@ const SeasonTab = ({ slug, seasons, sx }: { slug: string; seasons?: Season[]; sx
|
|||||||
// TODO: handle absolute number only shows (without seasons)
|
// TODO: handle absolute number only shows (without seasons)
|
||||||
return (
|
return (
|
||||||
<Container sx={sx}>
|
<Container sx={sx}>
|
||||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
<Box sx={{ borderBottom: 1, borderColor: "divider", width: "100%" }}>
|
||||||
<Tabs value={season} onChange={(_, i) => setSeason(i)} aria-label="List of seasons">
|
<Tabs value={season} onChange={(_, i) => setSeason(i)} aria-label="List of seasons">
|
||||||
{seasons
|
{seasons
|
||||||
? seasons.map((x) => <Tab key={x.seasonNumber} label={x.name} value={x.seasonNumber} />)
|
? seasons.map((x) => <Tab key={x.seasonNumber} label={x.name} value={x.seasonNumber} />)
|
||||||
: [...Array(3)].map((_, i) => (
|
: [...Array(3)].map((_, i) => (
|
||||||
<Typography key={i} variant="button">
|
<Tab key={i} label={<Skeleton width="5rem" />} value={i + 1} disabled />
|
||||||
<Skeleton />
|
|
||||||
</Typography>
|
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<EpisodeGrid slug={slug} season={season} />
|
<EpisodeGrid slug={slug} season={season} />
|
||||||
@ -372,7 +389,7 @@ const ShowDetails: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{makeTitle(data?.name)}</title>
|
<title>{makeTitle(data?.name)}</title>
|
||||||
<meta name="description" content={data?.overview} />
|
<meta name="description" content={data?.overview!} />
|
||||||
</Head>
|
</Head>
|
||||||
<ShowHeader data={data} />
|
<ShowHeader data={data} />
|
||||||
<ShowStaff slug={slug} />
|
<ShowStaff slug={slug} />
|
||||||
|
@ -2191,10 +2191,12 @@ react-dom@18.2.0:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.0"
|
scheduler "^0.23.0"
|
||||||
|
|
||||||
react-intersection-observer@^9.4.0:
|
react-infinite-scroll-component@^6.1.0:
|
||||||
version "9.4.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.0.tgz#f6b6e616e625f9bf255857c5cba9dbf7b1825ec7"
|
resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz#7e511e7aa0f728ac3e51f64a38a6079ac522407f"
|
||||||
integrity sha512-v0403CmomOVlzhqFXlzOxg0ziLcVq8mfbP0AwAcEQWgZmR2OulOT79Ikznw4UlB3N+jlUYqLMe4SDHUOyp0t2A==
|
integrity sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==
|
||||||
|
dependencies:
|
||||||
|
throttle-debounce "^2.1.0"
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
@ -2457,6 +2459,11 @@ text-table@^0.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||||
|
|
||||||
|
throttle-debounce@^2.1.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2"
|
||||||
|
integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==
|
||||||
|
|
||||||
to-fast-properties@^2.0.0:
|
to-fast-properties@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user