2022-10-29 16:22:49 +02:00

287 lines
11 KiB
TypeScript

import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useMemo } from "react";
import naturalCompare from "string-natural-compare";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
import { getOpenGraph } from "helpers/openGraph";
import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes } from "helpers/others";
import { GetContentsFolderQuery } from "graphql/generated";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { prettySlug } from "helpers/formatters";
import { SmartList } from "components/SmartList";
import { Ico, Icon } from "components/Ico";
import { Button, TranslatedButton } from "components/Inputs/Button";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Containers/SubPanel";
import { TranslatedPreviewCard } from "components/PreviewCard";
import { HorizontalLine } from "components/HorizontalLine";
import { cJoin, cIf } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData";
import { useLocalData } from "contexts/LocalDataContext";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
/*
* ╭────────╮
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
*/
interface Props extends AppLayoutRequired {
folder: NonNullable<
NonNullable<GetContentsFolderQuery["contentsFolders"]>["data"][number]["attributes"]
>;
}
const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => {
const { langui } = useLocalData();
const { isContentPanelAtLeast4xl } = useContainerQueries();
const subPanel = useMemo(
() => (
<SubPanel>
<PanelHeader
icon={Icon.Workspaces}
title={langui.contents}
description={langui.contents_description}
/>
<HorizontalLine />
<Button href="/contents/all" text={langui.switch_to_grid_view} icon={Icon.Apps} />
</SubPanel>
),
[langui.contents, langui.contents_description, langui.switch_to_grid_view]
);
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<div className="mb-10 grid grid-flow-col place-items-center justify-start gap-x-2">
{folder.parent_folder?.data?.attributes && (
<>
{folder.parent_folder.data.attributes.slug === "root" ? (
<Button href="/contents" icon={Icon.Home} />
) : (
<TranslatedButton
href={`/contents/folder/${folder.parent_folder.data.attributes.slug}`}
translations={filterHasAttributes(folder.parent_folder.data.attributes.titles, [
"language.data.attributes.code",
] as const).map((title) => ({
language: title.language.data.attributes.code,
text: title.title,
}))}
fallback={{
text: prettySlug(folder.parent_folder.data.attributes.slug),
}}
/>
)}
<Ico icon={Icon.ChevronRight} />
</>
)}
{folder.slug === "root" ? (
<Button href="/contents" icon={Icon.Home} active />
) : (
<TranslatedButton
translations={filterHasAttributes(folder.titles, [
"language.data.attributes.code",
] as const).map((title) => ({
language: title.language.data.attributes.code,
text: title.title,
}))}
fallback={{
text: prettySlug(folder.slug),
}}
active
/>
)}
</div>
<SmartList
items={filterHasAttributes(folder.subfolders?.data, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<TranslatedPreviewFolder
href={`/contents/folder/${item.attributes.slug}`}
translations={filterHasAttributes(item.attributes.titles, [
"language.data.attributes.code",
] as const).map((title) => ({
title: title.title,
language: title.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(item.attributes.slug) }}
/>
)}
className={cJoin(
"items-end",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-4"
)
)}
renderWhenEmpty={() => <></>}
groupingFunction={() => [langui.folders ?? "Folders"]}
/>
<SmartList
items={filterHasAttributes(folder.contents?.data, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
<TranslatedPreviewCard
href={`/contents/${item.attributes.slug}`}
translations={filterHasAttributes(item.attributes.translations, [
"language.data.attributes.code",
] as const).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"
thumbnailForceAspectRatio
topChips={
item.attributes.type?.data?.attributes
? [
item.attributes.type.data.attributes.titles?.[0]
? item.attributes.type.data.attributes.titles[0]?.title
: prettySlug(item.attributes.type.data.attributes.slug),
]
: undefined
}
bottomChips={item.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
keepInfoVisible
/>
)}
className={cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-x-3 gap-y-5"
)}
renderWhenEmpty={() => <></>}
groupingFunction={() => [langui.contents ?? "Contents"]}
/>
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
<NoContentNorFolderMessage />
)}
</ContentPanel>
),
[
folder.contents?.data,
folder.parent_folder?.data?.attributes,
folder.slug,
folder.subfolders?.data,
folder.titles,
isContentPanelAtLeast4xl,
langui,
]
);
return (
<AppLayout
subPanel={subPanel}
contentPanel={contentPanel}
openGraph={openGraph}
{...otherProps}
/>
);
};
export default ContentsFolder;
/*
* ╭──────────────────────╮
* ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
*/
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const langui = getLangui(context.locale);
const slug = context.params?.slug ? context.params.slug.toString() : "";
const contentsFolder = await sdk.getContentsFolder({
slug: slug,
language_code: context.locale ?? "en",
});
if (!contentsFolder.contentsFolders?.data[0]?.attributes) {
return { notFound: true };
}
const folder = contentsFolder.contentsFolders.data[0].attributes;
folder.subfolders?.data.sort((a, b) =>
a.attributes && b.attributes ? naturalCompare(a.attributes.slug, b.attributes.slug) : 0
);
folder.contents?.data.sort((a, b) =>
a.attributes && b.attributes ? naturalCompare(a.attributes.slug, b.attributes.slug) : 0
);
const title = (() => {
if (slug === "root") {
return langui.contents ?? "Contents";
}
if (context.locale && context.locales) {
const selectedTranslation = staticSmartLanguage({
items: folder.titles,
languageExtractor: (item) => item.language?.data?.attributes?.code,
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
});
if (selectedTranslation) {
return selectedTranslation.title;
}
}
return prettySlug(folder.slug);
})();
const props: Props = {
openGraph: getOpenGraph(langui, title),
folder,
};
return {
props: props,
};
};
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const contents = await sdk.getContentsFoldersSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.contentsFolders?.data, ["attributes"] as const).map((item) => {
context.locales?.map((local) => {
paths.push({
params: { slug: item.attributes.slug },
locale: local,
});
});
});
return {
paths,
fallback: "blocking",
};
};
/*
* ╭──────────────────────╮
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
*/
const NoContentNorFolderMessage = () => {
const { langui } = useLocalData();
return (
<div className="grid place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
border-dark p-8 text-dark opacity-40">
<p className="max-w-xs text-2xl">{langui.empty_folder_message}</p>
</div>
</div>
);
};