261 lines
10 KiB
TypeScript
261 lines
10 KiB
TypeScript
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
|
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/asserts";
|
|
import { GetContentsFolderQuery, ParentFolderPreviewFragment } from "graphql/generated";
|
|
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
|
import { prettySlug } from "helpers/formatters";
|
|
import { Button } 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 { atoms } from "contexts/atoms";
|
|
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
|
|
import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
|
|
import { useFormat } from "hooks/useFormat";
|
|
import { getFormat } from "helpers/i18n";
|
|
import { Chip } from "components/Chip";
|
|
import { FolderPath } from "components/Contents/FolderPath";
|
|
|
|
/*
|
|
* ╭────────╮
|
|
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
|
*/
|
|
|
|
interface Props extends AppLayoutRequired {
|
|
folder: NonNullable<
|
|
NonNullable<GetContentsFolderQuery["contentsFolders"]>["data"][number]["attributes"]
|
|
>;
|
|
path: ParentFolderPreviewFragment[];
|
|
}
|
|
|
|
const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.Element => {
|
|
const { format, formatCategory, formatContentType } = useFormat();
|
|
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
|
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
|
|
|
const subPanel = (
|
|
<SubPanel>
|
|
<PanelHeader
|
|
icon="workspaces"
|
|
title={format("contents")}
|
|
description={format("contents_description")}
|
|
/>
|
|
|
|
<HorizontalLine />
|
|
|
|
<Button
|
|
href="/contents/all"
|
|
text={format("switch_to_grid_view")}
|
|
icon="apps"
|
|
onClick={() => setSubPanelOpened(false)}
|
|
/>
|
|
</SubPanel>
|
|
);
|
|
|
|
const contentPanel = (
|
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
|
<FolderPath path={path} />
|
|
|
|
{folder.subfolders?.data && folder.subfolders.data.length > 0 && (
|
|
<div className="mb-8">
|
|
<div className="mb-2 flex place-items-center gap-2">
|
|
<h2 className="text-2xl">{format("folders")}</h2>
|
|
<Chip text={format("x_results", { x: folder.subfolders.data.length })} />
|
|
</div>
|
|
<div
|
|
className={cJoin(
|
|
"grid items-start pb-12",
|
|
cIf(
|
|
isContentPanelAtLeast4xl,
|
|
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
|
|
"grid-cols-2 gap-4"
|
|
)
|
|
)}>
|
|
{filterHasAttributes(folder.subfolders.data, ["id", "attributes"]).map((subfolder) => (
|
|
<TranslatedPreviewFolder
|
|
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>
|
|
)}
|
|
|
|
{folder.contents?.data && folder.contents.data.length > 0 && (
|
|
<div className="mb-8">
|
|
<div className="mb-2 flex place-items-center gap-2">
|
|
<h2 className="text-2xl">{format("contents")}</h2>
|
|
<Chip text={format("x_results", { x: folder.contents.data.length })} />
|
|
</div>
|
|
<div
|
|
className={cJoin(
|
|
"grid items-start pb-12",
|
|
cIf(
|
|
isContentPanelAtLeast4xl,
|
|
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
|
|
"grid-cols-2 gap-4"
|
|
)
|
|
)}>
|
|
{filterHasAttributes(folder.contents.data, ["id", "attributes"]).map((item) => (
|
|
<TranslatedPreviewCard
|
|
key={item.id}
|
|
href={`/contents/${item.attributes.slug}`}
|
|
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"
|
|
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
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
|
|
<NoContentNorFolderMessage />
|
|
)}
|
|
</ContentPanel>
|
|
);
|
|
|
|
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 { format } = getFormat(context.locale);
|
|
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
|
const contentsFolder = await sdk.getContentsFolder({ slug: slug });
|
|
if (!contentsFolder.contentsFolders?.data[0]?.attributes) {
|
|
return { notFound: true };
|
|
}
|
|
|
|
const folder = contentsFolder.contentsFolders.data[0].attributes;
|
|
|
|
const title = (() => {
|
|
if (slug === "root") {
|
|
return format("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(format, title),
|
|
folder,
|
|
path: getRecursiveParentFolderPreview(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"]).map((item) => {
|
|
context.locales?.map((local) => {
|
|
paths.push({
|
|
params: { slug: item.attributes.slug },
|
|
locale: local,
|
|
});
|
|
});
|
|
});
|
|
return {
|
|
paths,
|
|
fallback: "blocking",
|
|
};
|
|
};
|
|
|
|
/*
|
|
* ╭──────────────────────╮
|
|
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
|
*/
|
|
|
|
const NoContentNorFolderMessage = () => {
|
|
const { format } = useFormat();
|
|
return (
|
|
<div className="grid place-content-center">
|
|
<div
|
|
className="mt-12 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">{format("empty_folder_message")}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/*
|
|
* ╭───────────────────╮
|
|
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
|
*/
|
|
|
|
type ParentFolderWithParentFolder = ParentFolderPreviewFragment & {
|
|
parent_folder?: {
|
|
data?: {
|
|
attributes?: ParentFolderPreviewFragment | ParentFolderWithParentFolder | null;
|
|
} | null;
|
|
} | null;
|
|
};
|
|
|
|
const getRecursiveParentFolderPreview = (
|
|
parentFolder: ParentFolderWithParentFolder
|
|
): ParentFolderPreviewFragment[] => [
|
|
...(parentFolder.parent_folder?.data?.attributes
|
|
? getRecursiveParentFolderPreview(parentFolder.parent_folder.data.attributes)
|
|
: []),
|
|
{ slug: parentFolder.slug, titles: parentFolder.titles },
|
|
];
|