Simplified some DTO

This commit is contained in:
DrMint 2023-08-17 12:52:40 +02:00
parent 7efa43a630
commit da916f898a
3 changed files with 150 additions and 85 deletions

View File

@ -22,7 +22,6 @@ import { useTypedRouter } from "hooks/useTypedRouter";
import { Select } from "components/Inputs/Select"; import { Select } from "components/Inputs/Select";
import { sendAnalytics } from "helpers/analytics"; import { sendAnalytics } from "helpers/analytics";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { GetVideoChannelQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { Paginator } from "components/Containers/Paginator"; import { Paginator } from "components/Containers/Paginator";
import { useFormat } from "hooks/useFormat"; import { useFormat } from "hooks/useFormat";
@ -56,9 +55,11 @@ const queryParamSchema = z.object({
*/ */
interface Props extends AppLayoutRequired { interface Props extends AppLayoutRequired {
channel: NonNullable< channel: {
NonNullable<GetVideoChannelQuery["videoChannels"]>["data"][number]["attributes"] uid: string;
>; title: string;
subscribers: number;
};
} }
const Channel = ({ channel, ...otherProps }: Props): JSX.Element => { const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
@ -282,14 +283,23 @@ export default Channel;
export const getStaticProps: GetStaticProps = async (context) => { export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const { format } = getFormat(context.locale); const { format } = getFormat(context.locale);
const channel = await sdk.getVideoChannel({ const videoChannel = (
await sdk.getVideoChannel({
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "", channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
}); })
if (!channel.videoChannels?.data[0]?.attributes) return { notFound: true }; ).videoChannels?.data[0]?.attributes;
if (!videoChannel) return { notFound: true };
const channel: Props["channel"] = {
uid: videoChannel.uid,
subscribers: videoChannel.subscribers,
title: videoChannel.title,
};
const props: Props = { const props: Props = {
channel: channel.videoChannels.data[0].attributes, channel,
openGraph: getOpenGraph(format, channel.videoChannels.data[0].attributes.title), openGraph: getOpenGraph(format, channel.title),
}; };
return { return {
props: props, props: props,

View File

@ -9,10 +9,10 @@ import { NavOption } from "components/PanelComponents/NavOption";
import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ReturnButton } from "components/PanelComponents/ReturnButton";
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
import { SubPanel } from "components/Containers/SubPanel"; import { SubPanel } from "components/Containers/SubPanel";
import { GetVideoQuery } from "graphql/generated"; import { Enum_Video_Source } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyShortenNumber } from "helpers/formatters"; import { prettyShortenNumber } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/asserts"; import { filterHasAttributes, isDefined, isUndefined } from "helpers/asserts";
import { getVideoFile, getVideoThumbnailURL } from "helpers/videos"; import { getVideoFile, getVideoThumbnailURL } from "helpers/videos";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { atoms } from "contexts/atoms"; import { atoms } from "contexts/atoms";
@ -29,15 +29,29 @@ import { Markdown } from "components/Markdown/Markdown";
*/ */
interface Props extends AppLayoutRequired { interface Props extends AppLayoutRequired {
video: NonNullable<NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]>; video: {
isGone: boolean;
uid: string;
title: string;
description: string;
publishedDate: string;
views: number;
likes: number;
source?: Enum_Video_Source;
};
channel?: {
title: string;
href: string;
subscribers: number;
};
} }
const Video = ({ video, ...otherProps }: Props): JSX.Element => { const Video = ({ video, channel, ...otherProps }: Props): JSX.Element => {
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl); const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout); const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened); const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]); const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
const { format, formatDate } = useFormat(); const { format } = useFormat();
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
@ -62,7 +76,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
<div className="grid place-items-center gap-12"> <div className="grid place-items-center gap-12">
<div id="video" className="w-full overflow-hidden rounded-xl shadow-xl shadow-shade/80"> <div id="video" className="w-full overflow-hidden rounded-xl shadow-xl shadow-shade/80">
{video.gone ? ( {video.isGone ? (
<VideoPlayer className="w-full" src={getVideoFile(video.uid)} rounded={false} /> <VideoPlayer className="w-full" src={getVideoFile(video.uid)} rounded={false} />
) : ( ) : (
<iframe <iframe
@ -80,7 +94,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
<div className="flex w-full flex-row flex-wrap place-items-center gap-x-6"> <div className="flex w-full flex-row flex-wrap place-items-center gap-x-6">
<p> <p>
<Ico icon="event" className="mr-1 translate-y-[.15em] !text-base" /> <Ico icon="event" className="mr-1 translate-y-[.15em] !text-base" />
{formatDate(video.published_date)} {video.publishedDate}
</p> </p>
<p> <p>
<Ico icon="visibility" className="mr-1 translate-y-[.15em] !text-base" /> <Ico icon="visibility" className="mr-1 translate-y-[.15em] !text-base" />
@ -88,7 +102,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
? video.views.toLocaleString() ? video.views.toLocaleString()
: prettyShortenNumber(video.views)} : prettyShortenNumber(video.views)}
</p> </p>
{video.channel?.data?.attributes && ( {video.likes > 0 && (
<p> <p>
<Ico icon="thumb_up" className="mr-1 translate-y-[.15em] !text-base" /> <Ico icon="thumb_up" className="mr-1 translate-y-[.15em] !text-base" />
{isContentPanelAtLeast4xl {isContentPanelAtLeast4xl
@ -108,18 +122,14 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
</div> </div>
</div> </div>
{video.channel?.data?.attributes && ( {channel && (
<InsetBox id="channel" className="grid place-items-center"> <InsetBox id="channel" className="grid place-items-center">
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-4 text-center"> <div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-4 text-center">
<h2 className="text-2xl">{format("channel")}</h2> <h2 className="text-2xl">{format("channel")}</h2>
<div> <div>
<Button <Button href={channel.href} text={channel.title} />
href={`/archives/videos/c/${video.channel.data.attributes.uid}\
?page=1&query=&sort=1&gone=`}
text={video.channel.data.attributes.title}
/>
<p> <p>
{`${video.channel.data.attributes.subscribers.toLocaleString()} {`${channel.subscribers.toLocaleString()}
${format("subscribers").toLowerCase()}`} ${format("subscribers").toLowerCase()}`}
</p> </p>
</div> </div>
@ -148,29 +158,48 @@ export default Video;
export const getStaticProps: GetStaticProps = async (context) => { export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const { format } = getFormat(context.locale); const { format, formatDate } = getFormat(context.locale);
const videos = await sdk.getVideo({ const videos = await sdk.getVideo({
uid: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "", uid: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
}); });
if (!videos.videos?.data[0]?.attributes) return { notFound: true }; const rawVideo = videos.videos?.data[0]?.attributes;
if (isUndefined(rawVideo)) return { notFound: true };
const channel: Props["channel"] = rawVideo.channel?.data?.attributes
? {
href: `/archives/videos/c/${rawVideo.channel.data.attributes.uid}`,
subscribers: rawVideo.channel.data.attributes.subscribers,
title: rawVideo.channel.data.attributes.title,
}
: undefined;
const video: Props["video"] = {
uid: rawVideo.uid,
isGone: rawVideo.gone,
description: rawVideo.description,
likes: rawVideo.likes,
source: rawVideo.source ?? undefined,
publishedDate: formatDate(rawVideo.published_date),
title: rawVideo.title,
views: rawVideo.views,
};
const props: Props = { const props: Props = {
video: videos.videos.data[0].attributes, video,
channel,
openGraph: getOpenGraph( openGraph: getOpenGraph(
format, format,
videos.videos.data[0].attributes.title, rawVideo.title,
getDescription(videos.videos.data[0].attributes.description, { getDescription(rawVideo.description, {
[format("channel")]: [videos.videos.data[0].attributes.channel?.data?.attributes?.title], [format("channel")]: [rawVideo.channel?.data?.attributes?.title],
}), }),
getVideoThumbnailURL(videos.videos.data[0].attributes.uid), getVideoThumbnailURL(rawVideo.uid),
undefined, undefined,
videos.videos.data[0].attributes.gone rawVideo.gone ? getVideoFile(rawVideo.uid) : undefined
? getVideoFile(videos.videos.data[0].attributes.uid)
: undefined
), ),
}; };
return { return {
props: props, props: JSON.parse(JSON.stringify(props)),
}; };
}; };

View File

@ -4,7 +4,7 @@ import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/Cont
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes } from "helpers/asserts"; import { filterHasAttributes } from "helpers/asserts";
import { GetContentsFolderQuery, ParentFolderPreviewFragment } from "graphql/generated"; import { ParentFolderPreviewFragment, UploadImageFragment } from "graphql/generated";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
@ -27,14 +27,32 @@ import { FolderPath } from "components/Contents/FolderPath";
*/ */
interface Props extends AppLayoutRequired { interface Props extends AppLayoutRequired {
folder: NonNullable< subfolders: {
NonNullable<GetContentsFolderQuery["contentsFolders"]>["data"][number]["attributes"] slug: string;
>; href: string;
translations: { language: string; title: string }[];
fallback: { title: string };
}[];
contents: {
slug: string;
href: string;
translations: { language: string; pre_title?: string; title: string; subtitle?: string }[];
fallback: { title: string };
thumbnail?: UploadImageFragment;
topChips?: string[];
bottomChips?: string[];
}[];
path: ParentFolderPreviewFragment[]; path: ParentFolderPreviewFragment[];
} }
const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.Element => { const ContentsFolder = ({
const { format, formatCategory, formatContentType } = useFormat(); openGraph,
path,
contents,
subfolders,
...otherProps
}: Props): JSX.Element => {
const { format } = useFormat();
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened); const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl); const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
@ -61,11 +79,11 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<FolderPath path={path} /> <FolderPath path={path} />
{folder.subfolders?.data && folder.subfolders.data.length > 0 && ( {subfolders.length > 0 && (
<div className="mb-8"> <div className="mb-8">
<div className="mb-2 flex place-items-center gap-2"> <div className="mb-2 flex place-items-center gap-2">
<h2 className="text-2xl">{format("folders")}</h2> <h2 className="text-2xl">{format("folders")}</h2>
<Chip text={format("x_results", { x: folder.subfolders.data.length })} /> <Chip text={format("x_results", { x: subfolders.length })} />
</div> </div>
<div <div
className={cJoin( className={cJoin(
@ -76,28 +94,18 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
"grid-cols-2 gap-4" "grid-cols-2 gap-4"
) )
)}> )}>
{filterHasAttributes(folder.subfolders.data, ["id", "attributes"]).map((subfolder) => ( {subfolders.map((subfolder) => (
<TranslatedPreviewFolder <TranslatedPreviewFolder key={subfolder.slug} {...subfolder} />
key={subfolder.id}
href={`/contents/folder/${subfolder.attributes.slug}`}
translations={filterHasAttributes(subfolder.attributes.titles, [
"language.data.attributes.code",
]).map((title) => ({
title: title.title,
language: title.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(subfolder.attributes.slug) }}
/>
))} ))}
</div> </div>
</div> </div>
)} )}
{folder.contents?.data && folder.contents.data.length > 0 && ( {contents.length > 0 && (
<div className="mb-8"> <div className="mb-8">
<div className="mb-2 flex place-items-center gap-2"> <div className="mb-2 flex place-items-center gap-2">
<h2 className="text-2xl">{format("contents")}</h2> <h2 className="text-2xl">{format("contents")}</h2>
<Chip text={format("x_results", { x: folder.contents.data.length })} /> <Chip text={format("x_results", { x: contents.length })} />
</div> </div>
<div <div
className={cJoin( className={cJoin(
@ -108,30 +116,12 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
"grid-cols-2 gap-4" "grid-cols-2 gap-4"
) )
)}> )}>
{filterHasAttributes(folder.contents.data, ["id", "attributes"]).map((item) => ( {contents.map((item) => (
<TranslatedPreviewCard <TranslatedPreviewCard
key={item.id} key={item.slug}
href={`/contents/${item.attributes.slug}`} {...item}
translations={filterHasAttributes(item.attributes.translations, [
"language.data.attributes.code",
]).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(item.attributes.slug) }}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="3/2" thumbnailAspectRatio="3/2"
thumbnailForceAspectRatio thumbnailForceAspectRatio
topChips={
item.attributes.type?.data?.attributes
? [formatContentType(item.attributes.type.data.attributes.slug)]
: undefined
}
bottomChips={filterHasAttributes(item.attributes.categories?.data, [
"attributes",
]).map((category) => formatCategory(category.attributes.slug))}
keepInfoVisible keepInfoVisible
/> />
))} ))}
@ -139,9 +129,7 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
</div> </div>
)} )}
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && ( {contents.length === 0 && subfolders.length === 0 && <NoContentNorFolderMessage />}
<NoContentNorFolderMessage />
)}
</ContentPanel> </ContentPanel>
); );
@ -163,7 +151,7 @@ export default ContentsFolder;
export const getStaticProps: GetStaticProps = async (context) => { export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const { format } = getFormat(context.locale); const { format, formatContentType, formatCategory } = getFormat(context.locale);
const slug = context.params?.slug ? context.params.slug.toString() : ""; const slug = context.params?.slug ? context.params.slug.toString() : "";
const contentsFolder = await sdk.getContentsFolder({ slug: slug }); const contentsFolder = await sdk.getContentsFolder({ slug: slug });
if (!contentsFolder.contentsFolders?.data[0]?.attributes) { if (!contentsFolder.contentsFolders?.data[0]?.attributes) {
@ -189,13 +177,51 @@ export const getStaticProps: GetStaticProps = async (context) => {
return prettySlug(folder.slug); return prettySlug(folder.slug);
})(); })();
const subfolders: Props["subfolders"] = filterHasAttributes(folder.subfolders?.data, [
"attributes",
]).map(({ attributes }) => ({
slug: attributes.slug,
href: `/contents/folder/${attributes.slug}`,
translations: filterHasAttributes(attributes.titles, ["language.data.attributes.code"]).map(
(translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
})
),
fallback: { title: prettySlug(attributes.slug) },
}));
const contents: Props["contents"] = filterHasAttributes(folder.contents?.data, [
"attributes",
]).map(({ attributes }) => ({
slug: attributes.slug,
href: `/contents/${attributes.slug}`,
translations: filterHasAttributes(attributes.translations, [
"language.data.attributes.code",
]).map((translation) => ({
pre_title: translation.pre_title ?? undefined,
title: translation.title,
subtitle: translation.subtitle ?? undefined,
language: translation.language.data.attributes.code,
})),
fallback: { title: prettySlug(attributes.slug) },
thumbnail: attributes.thumbnail?.data?.attributes ?? undefined,
topChips: attributes.type?.data?.attributes
? [formatContentType(attributes.type.data.attributes.slug)]
: undefined,
bottomChips: filterHasAttributes(attributes.categories?.data, ["attributes"]).map((category) =>
formatCategory(category.attributes.slug)
),
}));
const props: Props = { const props: Props = {
openGraph: getOpenGraph(format, title), openGraph: getOpenGraph(format, title),
folder, subfolders,
contents,
path: getRecursiveParentFolderPreview(folder), path: getRecursiveParentFolderPreview(folder),
}; };
return { return {
props: props, props: JSON.parse(JSON.stringify(props)),
}; };
}; };