Use graphql-codegen and replace all types from queries

This commit is contained in:
DrMint 2022-03-31 14:59:31 +02:00
parent acf92d6fc1
commit 125421de0f
66 changed files with 11739 additions and 8983 deletions

3
.gitignore vendored
View File

@ -2,6 +2,9 @@
/testing_logs/* /testing_logs/*
# Generated content
src/graphql/generated.ts
# dependencies # dependencies
/node_modules /node_modules
/.pnp /.pnp

View File

@ -25,9 +25,10 @@
#### [Front](https://github.com/Accords-Library/accords-library.com) (this repository) #### [Front](https://github.com/Accords-Library/accords-library.com) (this repository)
- Language: [TypeScript](https://www.typescriptlang.org/) - Language: [TypeScript](https://www.typescriptlang.org/)
- Queries: [GraphQL](https://graphql.org/) - Queries: [GraphQL Code Generator](https://www.graphql-code-generator.com/)
- [GraphQL Code Generator](https://www.graphql-code-generator.com/) to automatically generated types for the operations variables and responses - Fetch the GraphQL schema from the GraphQL back-end endpoint
- The operations are stored in a graphql file and then retrieved and wrap as an actual TypeScript function - Read the operations and fragments stored as graphql files in the `src/graphql` folder
- Automatically generates a typesafe ready to use SDK using [graphql-request](https://www.npmjs.com/package/graphql-request) as the GraphQL client
- Markdown: [markdown-to-jsx](https://www.npmjs.com/package/markdown-to-jsx) - Markdown: [markdown-to-jsx](https://www.npmjs.com/package/markdown-to-jsx)
- Support for Arbitrary React Components and Component Props! - Support for Arbitrary React Components and Component Props!
- Autogenerated multi-level table of content and anchor links for the different headers - Autogenerated multi-level table of content and anchor links for the different headers

21
graphql-codegen.js Normal file
View File

@ -0,0 +1,21 @@
const { loadEnvConfig } = require("@next/env");
loadEnvConfig(process.cwd());
module.exports = {
overwrite: true,
schema: {
[process.env.URL_GRAPHQL]: {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
},
},
documents: ["src/graphql/operations/*.graphql", "src/graphql/fragments/*.graphql"],
generates: {
"src/graphql/generated.ts": {
plugins: [
"typescript",
"typescript-operations",
"typescript-graphql-request",
],
},
},
};

9015
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,8 @@
"build": "next build", "build": "next build",
"postbuild": "next-sitemap", "postbuild": "next-sitemap",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"generate": "graphql-codegen --config graphql-codegen.js"
}, },
"dependencies": { "dependencies": {
"@fontsource/material-icons": "^4.5.4", "@fontsource/material-icons": "^4.5.4",
@ -15,6 +16,7 @@
"@fontsource/vollkorn": "^4.5.6", "@fontsource/vollkorn": "^4.5.6",
"@fontsource/zen-maru-gothic": "^4.5.8", "@fontsource/zen-maru-gothic": "^4.5.8",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"graphql-request": "^4.2.0",
"markdown-to-jsx": "^7.1.7", "markdown-to-jsx": "^7.1.7",
"next": "^12.1.2", "next": "^12.1.2",
"nodemailer": "^6.7.3", "nodemailer": "^6.7.3",
@ -25,6 +27,10 @@
"turndown": "^7.1.1" "turndown": "^7.1.1"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^2.6.2",
"@graphql-codegen/typescript": "2.4.8",
"@graphql-codegen/typescript-graphql-request": "^4.4.4",
"@graphql-codegen/typescript-operations": "^2.3.5",
"@types/node": "17.0.23", "@types/node": "17.0.23",
"@types/nodemailer": "^6.4.4", "@types/nodemailer": "^6.4.4",
"@types/react": "17.0.43", "@types/react": "17.0.43",
@ -34,6 +40,7 @@
"@typescript-eslint/parser": "^5.17.0", "@typescript-eslint/parser": "^5.17.0",
"eslint": "^8.12.0", "eslint": "^8.12.0",
"eslint-config-next": "12.1.2", "eslint-config-next": "12.1.2",
"graphql": "^14.7.0",
"next-sitemap": "^2.5.14", "next-sitemap": "^2.5.14",
"prettier-plugin-organize-imports": "^2.3.4", "prettier-plugin-organize-imports": "^2.3.4",
"tailwindcss": "^3.0.23", "tailwindcss": "^3.0.23",

View File

@ -1 +1,3 @@
npx next build npm run generate
npm run build
npm run postbuild

View File

@ -1,11 +1,16 @@
import Button from "components/Button"; import Button from "components/Button";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { StrapiImage } from "graphql/operations-types"; import { UploadImageFragment } from "graphql/generated";
import { useMediaMobile } from "hooks/useMediaQuery"; import { useMediaMobile } from "hooks/useMediaQuery";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps } from "queries/getAppStaticProps";
import { getOgImage, OgImage, prettyLanguage } from "queries/helpers"; import {
getOgImage,
OgImage,
prettyLanguage,
prettySlug,
} from "queries/helpers";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSwipeable } from "react-swipeable"; import { useSwipeable } from "react-swipeable";
import { ImageQuality } from "./Img"; import { ImageQuality } from "./Img";
@ -18,8 +23,8 @@ interface AppLayoutProps extends AppStaticProps {
subPanelIcon?: string; subPanelIcon?: string;
contentPanel?: React.ReactNode; contentPanel?: React.ReactNode;
title?: string; title?: string;
navTitle: string; navTitle: string | null | undefined;
thumbnail?: StrapiImage; thumbnail?: UploadImageFragment;
description?: string; description?: string;
} }
@ -61,11 +66,10 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
height: 630, height: 630,
alt: "Accord's Library Logo", alt: "Accord's Library Logo",
}; };
const ogTitle = props.title ? props.title : props.navTitle; const ogTitle =
props.title ?? props.navTitle ?? prettySlug(router.asPath.split("/").pop());
const metaDescription = props.description const metaDescription = props.description ?? langui.default_description ?? "";
? props.description
: langui.default_description;
useEffect(() => { useEffect(() => {
document.getElementsByTagName("html")[0].style.fontSize = `${ document.getElementsByTagName("html")[0].style.fontSize = `${
@ -73,9 +77,10 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
}%`; }%`;
}, [appLayout.fontSize]); }, [appLayout.fontSize]);
const currencyOptions = currencies.map( const currencyOptions: string[] = [];
(currency) => currency.attributes.code currencies.map((currency) => {
); if (currency.attributes?.code) currencyOptions.push(currency.attributes.code);
});
const [currencySelect, setCurrencySelect] = useState<number>(-1); const [currencySelect, setCurrencySelect] = useState<number>(-1);
useEffect(() => { useEffect(() => {
@ -127,7 +132,10 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
></meta> ></meta>
<meta name="description" content={metaDescription} /> <meta name="description" content={metaDescription} />
<meta name="twitter:description" content={metaDescription}></meta> <meta
name="twitter:description"
content={metaDescription}
></meta>
<meta property="og:image" content={metaImage.image}></meta> <meta property="og:image" content={metaImage.image}></meta>
<meta property="og:image:secure_url" content={metaImage.image}></meta> <meta property="og:image:secure_url" content={metaImage.image}></meta>
@ -220,12 +228,12 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
</span> </span>
<p <p
className={`font-black font-headers text-center overflow-hidden ${ className={`font-black font-headers text-center overflow-hidden ${
props.navTitle?.length > 30 ogTitle && ogTitle.length > 30
? "text-xl max-h-14" ? "text-xl max-h-14"
: "text-2xl max-h-16" : "text-2xl max-h-16"
}`} }`}
> >
{props.navTitle} {ogTitle}
</p> </p>
<span <span
className="material-icons mt-[.1em] cursor-pointer" className="material-icons mt-[.1em] cursor-pointer"

View File

@ -3,14 +3,17 @@ import ToolTip from "components/ToolTip";
import { import {
Enum_Componenttranslationschronologyitem_Status, Enum_Componenttranslationschronologyitem_Status,
GetChronologyItemsQuery, GetChronologyItemsQuery,
GetWebsiteInterfaceQuery, } from "graphql/generated";
} from "graphql/operations-types"; import { AppStaticProps } from "queries/getAppStaticProps";
import { getStatusDescription } from "queries/helpers"; import { getStatusDescription } from "queries/helpers";
export type ChronologyItemComponentProps = { export type ChronologyItemComponentProps = {
item: GetChronologyItemsQuery["chronologyItems"]["data"][number]; item: Exclude<
GetChronologyItemsQuery["chronologyItems"],
null | undefined
>["data"][number];
displayYear: boolean; displayYear: boolean;
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; langui: AppStaticProps["langui"];
}; };
export default function ChronologyItemComponent( export default function ChronologyItemComponent(
@ -18,22 +21,29 @@ export default function ChronologyItemComponent(
): JSX.Element { ): JSX.Element {
const { langui } = props; const { langui } = props;
function generateAnchor(year: number, month: number, day: number): string { function generateAnchor(
year: number | undefined,
month: number | null | undefined,
day: number | null | undefined
): string {
let result = ""; let result = "";
result += year; if (year) result += year;
if (month) result += `- ${month.toString().padStart(2, "0")}`; if (month) result += `- ${month.toString().padStart(2, "0")}`;
if (day) result += `- ${day.toString().padStart(2, "0")}`; if (day) result += `- ${day.toString().padStart(2, "0")}`;
return result; return result;
} }
function generateYear(displayed_date: string, year: number): string { function generateYear(
if (displayed_date) { displayed_date: string | null | undefined,
return displayed_date; year: number | undefined
} ): string {
return year.toString(); return displayed_date ?? year?.toString() ?? "";
} }
function generateDate(month: number, day: number): string { function generateDate(
month: number | null | undefined,
day: number | null | undefined
): string {
const lut = [ const lut = [
"Jan", "Jan",
"Feb", "Feb",
@ -50,7 +60,7 @@ export default function ChronologyItemComponent(
]; ];
let result = ""; let result = "";
if (month) { if (month && month >= 1 && month <= 12) {
result += lut[month - 1]; result += lut[month - 1];
if (day) { if (day) {
result += ` ${day}`; result += ` ${day}`;
@ -60,78 +70,98 @@ export default function ChronologyItemComponent(
return result; return result;
} }
return ( if (props.item.attributes) {
<div return (
className="grid place-content-start grid-rows-[auto_1fr] grid-cols-[4em] py-4 px-8 rounded-2xl target:bg-mid target:py-8 target:my-4" <div
id={generateAnchor( className="grid place-content-start grid-rows-[auto_1fr] grid-cols-[4em] py-4 px-8 rounded-2xl target:bg-mid target:py-8 target:my-4"
props.item.attributes.year, id={generateAnchor(
props.item.attributes.month, props.item.attributes.year,
props.item.attributes.day props.item.attributes.month,
)} props.item.attributes.day
> )}
{props.displayYear && ( >
<p className="text-lg mt-[-.2em] font-bold"> {props.displayYear && (
{generateYear( <p className="text-lg mt-[-.2em] font-bold">
props.item.attributes.displayed_date, {generateYear(
props.item.attributes.year props.item.attributes.displayed_date,
)} props.item.attributes.year
)}
</p>
)}
<p className="col-start-1 text-dark text-sm">
{generateDate(props.item.attributes.month, props.item.attributes.day)}
</p> </p>
)}
<p className="col-start-1 text-dark text-sm"> <div className="col-start-2 row-start-1 row-span-2 grid gap-4">
{generateDate(props.item.attributes.month, props.item.attributes.day)} {props.item.attributes.events?.map((event) => (
</p> <>
{event && (
<div className="m-0" key={event.id}>
{event.translations?.map((translation) => (
<>
{translation && (
<>
<div className="place-items-start place-content-start grid grid-flow-col gap-2">
{translation.status !==
Enum_Componenttranslationschronologyitem_Status.Done && (
<ToolTip
content={getStatusDescription(
translation.status,
langui
)}
maxWidth={"20rem"}
>
<Chip>{translation.status}</Chip>
</ToolTip>
)}
{translation.title ? (
<h3>{translation.title}</h3>
) : (
""
)}
</div>
<div className="col-start-2 row-start-1 row-span-2 grid gap-4"> {translation.description && (
{props.item.attributes.events.map((event) => ( <p
<div className="m-0" key={event.id}> className={
{event.translations.map((translation) => ( event.translations &&
<> event.translations.length > 1
<div className="place-items-start place-content-start grid grid-flow-col gap-2"> ? "before:content-['-'] before:text-dark before:inline-block before:w-4 before:ml-[-1em] mt-2 whitespace-pre-line"
{translation.status !== : "whitespace-pre-line"
Enum_Componenttranslationschronologyitem_Status.Done && ( }
<ToolTip >
content={getStatusDescription(translation.status, langui)} {translation.description}
maxWidth={"20rem"} </p>
> )}
<Chip>{translation.status}</Chip> {translation.note ? (
</ToolTip> <em>{`Notes: ${translation.note}`}</em>
)} ) : (
{translation.title ? <h3>{translation.title}</h3> : ""} ""
</div> )}
</>
)}
</>
))}
{translation.description && ( <p className="text-dark text-xs grid place-self-start grid-flow-col gap-1 mt-1">
<p {event.source?.data ? (
className={ `(${event.source.data.attributes?.name})`
event.translations.length > 1 ) : (
? "before:content-['-'] before:text-dark before:inline-block before:w-4 before:ml-[-1em] mt-2 whitespace-pre-line" <>
: "whitespace-pre-line" <span className="material-icons !text-sm">warning</span>
} No sources!
> </>
{translation.description} )}
</p> </p>
)} </div>
{translation.note ? (
<em>{`Notes: ${translation.note}`}</em>
) : (
""
)}
</>
))}
<p className="text-dark text-xs grid place-self-start grid-flow-col gap-1 mt-1">
{event.source.data ? (
`(${event.source.data.attributes.name})`
) : (
<>
<span className="material-icons !text-sm">warning</span>No
sources!
</>
)} )}
</p> </>
</div> ))}
))} </div>
</div> </div>
</div> );
); }
return <></>;
} }

View File

@ -1,13 +1,14 @@
import ChronologyItemComponent from "components/Chronology/ChronologyItemComponent"; import ChronologyItemComponent from "components/Chronology/ChronologyItemComponent";
import { import { GetChronologyItemsQuery } from "graphql/generated";
GetChronologyItemsQuery, import { AppStaticProps } from "queries/getAppStaticProps";
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
type ChronologyYearComponentProps = { type ChronologyYearComponentProps = {
year: number; year: number;
items: GetChronologyItemsQuery["chronologyItems"]["data"][number][]; items: Exclude<
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; GetChronologyItemsQuery["chronologyItems"],
null | undefined
>["data"][number][];
langui: AppStaticProps["langui"];
}; };
export default function ChronologyYearComponent( export default function ChronologyYearComponent(

View File

@ -1,21 +1,31 @@
import Chip from "components/Chip"; import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img"; import Img, { ImageQuality } from "components/Img";
import InsetBox from "components/InsetBox"; import InsetBox from "components/InsetBox";
import { import { GetContentQuery, UploadImageFragment } from "graphql/generated";
GetContentQuery, import { AppStaticProps } from "queries/getAppStaticProps";
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers"; import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers";
export type ThumbnailHeaderProps = { export type ThumbnailHeaderProps = {
pre_title?: string; pre_title?: string | null | undefined;
title: string; title: string | null | undefined;
subtitle?: string; subtitle?: string | null | undefined;
description?: string; description?: string | null | undefined;
type?: GetContentQuery["contents"]["data"][number]["attributes"]["type"]; type?: Exclude<
categories?: GetContentQuery["contents"]["data"][number]["attributes"]["categories"]; Exclude<
thumbnail?: GetContentQuery["contents"]["data"][number]["attributes"]["thumbnail"]["data"]["attributes"]; GetContentQuery["contents"],
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; null | undefined
>["data"][number]["attributes"],
null | undefined
>["type"];
categories?: Exclude<
Exclude<
GetContentQuery["contents"],
null | undefined
>["data"][number]["attributes"],
null | undefined
>["categories"];
thumbnail?: UploadImageFragment | null | undefined;
langui: AppStaticProps["langui"];
}; };
export default function ThumbnailHeader( export default function ThumbnailHeader(
@ -60,13 +70,14 @@ export default function ThumbnailHeader(
</div> </div>
<div className="grid grid-flow-col gap-8"> <div className="grid grid-flow-col gap-8">
{type?.data && ( {type?.data?.attributes && (
<div className="flex flex-col place-items-center gap-2"> <div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.type}</h3> <h3 className="text-xl">{langui.type}</h3>
<div className="flex flex-row flex-wrap"> <div className="flex flex-row flex-wrap">
<Chip> <Chip>
{type.data.attributes.titles.length > 0 {type.data.attributes.titles &&
? type.data.attributes.titles[0].title type.data.attributes.titles.length > 0
? type.data.attributes.titles[0]?.title
: prettySlug(type.data.attributes.slug)} : prettySlug(type.data.attributes.slug)}
</Chip> </Chip>
</div> </div>
@ -78,7 +89,7 @@ export default function ThumbnailHeader(
<h3 className="text-xl">{langui.categories}</h3> <h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{categories.data.map((category) => ( {categories.data.map((category) => (
<Chip key={category.id}>{category.attributes.name}</Chip> <Chip key={category.id}>{category.attributes?.name}</Chip>
))} ))}
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { StrapiImage } from "graphql/operations-types"; import { UploadImageFragment } from "graphql/generated";
import Image, { ImageProps } from "next/image"; import Image, { ImageProps } from "next/image";
export enum ImageQuality { export enum ImageQuality {
@ -51,7 +51,7 @@ export function getImgSizesByQuality(
type ImgProps = { type ImgProps = {
className?: string; className?: string;
image?: StrapiImage; image?: UploadImageFragment;
quality?: ImageQuality; quality?: ImageQuality;
alt?: ImageProps["alt"]; alt?: ImageProps["alt"];
layout?: ImageProps["layout"]; layout?: ImageProps["layout"];
@ -61,11 +61,11 @@ type ImgProps = {
}; };
export default function Img(props: ImgProps): JSX.Element { export default function Img(props: ImgProps): JSX.Element {
if (props.image) { if (props.image?.width && props.image?.height) {
const imgSize = getImgSizesByQuality( const imgSize = getImgSizesByQuality(
props.image.width, props.image.width,
props.image.height, props.image.height,
props.quality ? props.quality : ImageQuality.Small props.quality ?? ImageQuality.Small
); );
if (props.rawImg) { if (props.rawImg) {
@ -75,9 +75,9 @@ export default function Img(props: ImgProps): JSX.Element {
className={props.className} className={props.className}
src={getAssetURL( src={getAssetURL(
props.image.url, props.image.url,
props.quality ? props.quality : ImageQuality.Small props.quality ?? ImageQuality.Small
)} )}
alt={props.alt ? props.alt : props.image.alternativeText} alt={props.alt ?? props.image.alternativeText ?? ""}
width={imgSize.width} width={imgSize.width}
height={imgSize.height} height={imgSize.height}
/> />
@ -90,7 +90,7 @@ export default function Img(props: ImgProps): JSX.Element {
props.image.url, props.image.url,
props.quality ? props.quality : ImageQuality.Small props.quality ? props.quality : ImageQuality.Small
)} )}
alt={props.alt ? props.alt : props.image.alternativeText} alt={props.alt ?? props.image.alternativeText ?? ""}
width={props.layout === "fill" ? undefined : imgSize.width} width={props.layout === "fill" ? undefined : imgSize.width}
height={props.layout === "fill" ? undefined : imgSize.height} height={props.layout === "fill" ? undefined : imgSize.height}
layout={props.layout} layout={props.layout}

View File

@ -1,16 +1,13 @@
import {
GetLanguagesQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps } from "queries/getAppStaticProps";
import { prettyLanguage } from "queries/helpers"; import { prettyLanguage } from "queries/helpers";
import Button from "./Button"; import Button from "./Button";
type HorizontalLineProps = { type HorizontalLineProps = {
className?: string; className?: string;
locales: string[]; locales: (string | undefined)[];
languages: GetLanguagesQuery["languages"]["data"]; languages: AppStaticProps["languages"];
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; langui: AppStaticProps["langui"];
href?: string; href?: string;
}; };
@ -26,14 +23,18 @@ export default function HorizontalLine(
<p>{langui.language_switch_message}</p> <p>{langui.language_switch_message}</p>
<div className="flex flex-wrap flex-row gap-2"> <div className="flex flex-wrap flex-row gap-2">
{locales.map((locale, index) => ( {locales.map((locale, index) => (
<Button <>
key={index} {locale && (
active={locale === router.locale} <Button
href={href} key={index}
locale={locale} active={locale === router.locale}
> href={href}
{prettyLanguage(locale, props.languages)} locale={locale}
</Button> >
{prettyLanguage(locale, props.languages)}
</Button>
)}
</>
))} ))}
</div> </div>
</div> </div>

View File

@ -1,16 +1,23 @@
import Button from "components/Button"; import Button from "components/Button";
import Chip from "components/Chip"; import Chip from "components/Chip";
import { import { GetLibraryItemQuery } from "graphql/generated";
GetLibraryItemQuery, import { AppStaticProps } from "queries/getAppStaticProps";
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { prettyinlineTitle, prettySlug } from "queries/helpers"; import { prettyinlineTitle, prettySlug } from "queries/helpers";
import { useState } from "react"; import { useState } from "react";
type ContentTOCLineProps = { type ContentTOCLineProps = {
content: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"]["contents"]["data"][number]; content: Exclude<
Exclude<
Exclude<
GetLibraryItemQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"],
null | undefined
>["contents"],
null | undefined
>["data"][number];
parentSlug: string; parentSlug: string;
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; langui: AppStaticProps["langui"];
}; };
export default function ContentTOCLine( export default function ContentTOCLine(
@ -20,82 +27,91 @@ export default function ContentTOCLine(
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
return ( if (content.attributes) {
<div return (
className={`grid gap-2 px-4 rounded-lg ${
opened && "bg-mid shadow-inner-sm shadow-shade h-auto py-3 my-2"
}`}
>
<div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]">
<a>
<h3 className="cursor-pointer" onClick={() => setOpened(!opened)}>
{content.attributes.content.data &&
content.attributes.content.data.attributes.titles.length > 0
? prettyinlineTitle(
content.attributes.content.data.attributes.titles[0]
.pre_title,
content.attributes.content.data.attributes.titles[0].title,
content.attributes.content.data.attributes.titles[0].subtitle
)
: prettySlug(content.attributes.slug, props.parentSlug)}
</h3>
</a>
<div className="flex flex-row flex-wrap gap-1">
{content.attributes.content.data?.attributes.categories.data.map(
(category) => (
<Chip key={category.id}>{category.attributes.short}</Chip>
)
)}
</div>
<p className="border-b-2 h-4 w-full border-black border-dotted opacity-30"></p>
<p>
{content.attributes.range[0].__typename === "ComponentRangePageRange"
? content.attributes.range[0].starting_page
: ""}
</p>
{content.attributes.content.data && (
<Chip className="justify-self-end thin:hidden">
{content.attributes.content.data.attributes.type.data.attributes
.titles.length > 0
? content.attributes.content.data.attributes.type.data.attributes
.titles[0].title
: prettySlug(
content.attributes.content.data.attributes.type.data
.attributes.slug
)}
</Chip>
)}
</div>
<div <div
className={`grid-flow-col place-content-start place-items-center gap-2 ${ className={`grid gap-2 px-4 rounded-lg ${
opened ? "grid" : "hidden" opened && "bg-mid shadow-inner-sm shadow-shade h-auto py-3 my-2"
}`} }`}
> >
<span className="material-icons text-dark"> <div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]">
subdirectory_arrow_right <a>
</span> <h3 className="cursor-pointer" onClick={() => setOpened(!opened)}>
{content.attributes.content?.data?.attributes?.titles?.[0]
? prettyinlineTitle(
content.attributes.content.data.attributes.titles[0]
?.pre_title,
content.attributes.content.data.attributes.titles[0]?.title,
content.attributes.content.data.attributes.titles[0]
?.subtitle
)
: prettySlug(content.attributes.slug, props.parentSlug)}
</h3>
</a>
<div className="flex flex-row flex-wrap gap-1">
{content.attributes.content?.data?.attributes?.categories?.data.map(
(category) => (
<Chip key={category.id}>{category.attributes?.short}</Chip>
)
)}
</div>
<p className="border-b-2 h-4 w-full border-black border-dotted opacity-30"></p>
<p>
{content.attributes.range[0]?.__typename ===
"ComponentRangePageRange"
? content.attributes.range[0].starting_page
: ""}
</p>
{content.attributes.content?.data?.attributes?.type?.data
?.attributes && (
<Chip className="justify-self-end thin:hidden">
{content.attributes.content.data.attributes.type.data.attributes
.titles &&
content.attributes.content.data.attributes.type.data.attributes
.titles.length > 0
? content.attributes.content.data.attributes.type.data
.attributes.titles[0]?.title
: prettySlug(
content.attributes.content.data.attributes.type.data
.attributes.slug
)}
</Chip>
)}
</div>
<div
className={`grid-flow-col place-content-start place-items-center gap-2 ${
opened ? "grid" : "hidden"
}`}
>
<span className="material-icons text-dark">
subdirectory_arrow_right
</span>
{content.attributes.scan_set.length > 0 && ( {content.attributes.scan_set &&
<Button content.attributes.scan_set.length > 0 && (
href={`/library/${parentSlug}/scans#${content.attributes.slug}`} <Button
> href={`/library/${parentSlug}/scans#${content.attributes.slug}`}
{langui.view_scans} >
</Button> {langui.view_scans}
)} </Button>
)}
{content.attributes.content.data && ( {content.attributes.content?.data && (
<Button <Button
href={`/contents/${content.attributes.content.data.attributes.slug}`} href={`/contents/${content.attributes.content.data.attributes?.slug}`}
> >
{langui.open_content} {langui.open_content}
</Button> </Button>
)} )}
{content.attributes.scan_set.length === 0 && {content.attributes.scan_set &&
!content.attributes.content.data content.attributes.scan_set.length === 0 &&
? "The content is not available" !content.attributes.content?.data
: ""} ? "The content is not available"
: ""}
</div>
</div> </div>
</div> );
); }
return <></>;
} }

View File

@ -1,17 +1,14 @@
import Chip from "components/Chip"; import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img"; import Img, { ImageQuality } from "components/Img";
import { GetContentsQuery } from "graphql/operations-types"; import { GetContentsQuery } from "graphql/generated";
import Link from "next/link"; import Link from "next/link";
import { prettySlug } from "queries/helpers"; import { prettySlug } from "queries/helpers";
export type LibraryContentPreviewProps = { export type LibraryContentPreviewProps = {
item: { item: Exclude<
slug: GetContentsQuery["contents"]["data"][number]["attributes"]["slug"]; GetContentsQuery["contents"],
thumbnail: GetContentsQuery["contents"]["data"][number]["attributes"]["thumbnail"]; null | undefined
titles: GetContentsQuery["contents"]["data"][number]["attributes"]["titles"]; >["data"][number]["attributes"];
categories: GetContentsQuery["contents"]["data"][number]["attributes"]["categories"];
type: GetContentsQuery["contents"]["data"][number]["attributes"]["type"];
};
}; };
export default function LibraryContentPreview( export default function LibraryContentPreview(
@ -20,9 +17,9 @@ export default function LibraryContentPreview(
const { item } = props; const { item } = props;
return ( return (
<Link href={`/contents/${item.slug}`} passHref> <Link href={`/contents/${item?.slug}`} passHref>
<div className="drop-shadow-shade-xl cursor-pointer grid items-end fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform"> <div className="drop-shadow-shade-xl cursor-pointer grid items-end fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform">
{item.thumbnail.data ? ( {item?.thumbnail?.data?.attributes ? (
<Img <Img
className="rounded-md coarse:rounded-b-none" className="rounded-md coarse:rounded-b-none"
image={item.thumbnail.data.attributes} image={item.thumbnail.data.attributes}
@ -33,29 +30,29 @@ export default function LibraryContentPreview(
)} )}
<div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2"> <div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
<div className="grid grid-flow-col gap-1 overflow-hidden place-content-start"> <div className="grid grid-flow-col gap-1 overflow-hidden place-content-start">
{item.type.data && ( {item?.type?.data?.attributes && (
<Chip> <Chip>
{item.type.data.attributes.titles.length > 0 {item.type.data.attributes.titles?.[0]
? item.type.data.attributes.titles[0].title ? item.type.data.attributes.titles[0]?.title
: prettySlug(item.type.data.attributes.slug)} : prettySlug(item.type.data.attributes.slug)}
</Chip> </Chip>
)} )}
</div> </div>
<div> <div>
{item.titles.length > 0 ? ( {item?.titles?.[0] ? (
<> <>
<p>{item.titles[0].pre_title}</p> <p>{item.titles[0].pre_title}</p>
<h1 className="text-lg">{item.titles[0].title}</h1> <h1 className="text-lg">{item.titles[0].title}</h1>
<h2>{item.titles[0].subtitle}</h2> <h2>{item.titles[0].subtitle}</h2>
</> </>
) : ( ) : (
<h1 className="text-lg">{prettySlug(item.slug)}</h1> <h1 className="text-lg">{prettySlug(item?.slug)}</h1>
)} )}
</div> </div>
<div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start"> <div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
{item.categories.data.map((category) => ( {item?.categories?.data.map((category) => (
<Chip key={category.id} className="text-sm"> <Chip key={category.id} className="text-sm">
{category.attributes.short} {category.attributes?.short}
</Chip> </Chip>
))} ))}
</div> </div>

View File

@ -2,25 +2,31 @@ import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img"; import Img, { ImageQuality } from "components/Img";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { import {
GetCurrenciesQuery, GetLibraryItemQuery,
GetLibraryItemsPreviewQuery, GetLibraryItemsPreviewQuery,
} from "graphql/operations-types"; } from "graphql/generated";
import Link from "next/link"; import Link from "next/link";
import { AppStaticProps } from "queries/getAppStaticProps";
import { prettyDate, prettyItemSubType, prettyPrice } from "queries/helpers"; import { prettyDate, prettyItemSubType, prettyPrice } from "queries/helpers";
export type LibraryItemsPreviewProps = { export type LibraryItemsPreviewProps = {
className?: string; className?: string;
item: { item:
slug: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["slug"]; | Exclude<
thumbnail: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["thumbnail"]; Exclude<
title: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["title"]; Exclude<
subtitle: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["subtitle"]; GetLibraryItemQuery["libraryItems"],
price?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"]; null | undefined
categories: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["categories"]; >["data"][number]["attributes"],
release_date?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["release_date"]; null | undefined
metadata?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"]; >["subitems"],
}; null | undefined
currencies?: GetCurrenciesQuery["currencies"]["data"]; >["data"][number]["attributes"]
| Exclude<
GetLibraryItemsPreviewQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"];
currencies: AppStaticProps["currencies"];
}; };
export default function LibraryItemsPreview( export default function LibraryItemsPreview(
@ -30,11 +36,11 @@ export default function LibraryItemsPreview(
const appLayout = useAppLayout(); const appLayout = useAppLayout();
return ( return (
<Link href={`/library/${item.slug}`} passHref> <Link href={`/library/${item?.slug}`} passHref>
<div <div
className={`drop-shadow-shade-xl cursor-pointer grid items-end hover:rounded-3xl fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform ${props.className}`} className={`drop-shadow-shade-xl cursor-pointer grid items-end hover:rounded-3xl fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform ${props.className}`}
> >
{item.thumbnail.data ? ( {item?.thumbnail?.data?.attributes ? (
<Img <Img
image={item.thumbnail.data.attributes} image={item.thumbnail.data.attributes}
quality={ImageQuality.Small} quality={ImageQuality.Small}
@ -44,26 +50,26 @@ export default function LibraryItemsPreview(
)} )}
<div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute place-items-start bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2"> <div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute place-items-start bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
{item.metadata && item.metadata.length > 0 && ( {item?.metadata && item.metadata.length > 0 && item.metadata[0] && (
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-1">
<Chip>{prettyItemSubType(item.metadata[0])}</Chip> <Chip>{prettyItemSubType(item.metadata[0])}</Chip>
</div> </div>
)} )}
<div> <div>
<h2 className="mobile:text-sm text-lg leading-5">{item.title}</h2> <h2 className="mobile:text-sm text-lg leading-5">{item?.title}</h2>
<h3 className="mobile:text-xs leading-3">{item.subtitle}</h3> <h3 className="mobile:text-xs leading-3">{item?.subtitle}</h3>
</div> </div>
<div className="w-full grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:h-0 [scrollbar-width:none] place-content-start"> <div className="w-full grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:h-0 [scrollbar-width:none] place-content-start">
{item.categories.data.map((category) => ( {item?.categories?.data.map((category) => (
<Chip key={category.id} className="text-sm"> <Chip key={category.id} className="text-sm">
{category.attributes.short} {category.attributes?.short}
</Chip> </Chip>
))} ))}
</div> </div>
{(item.release_date || item.price) && ( {(item?.release_date || item?.price) && (
<div className="grid grid-flow-col w-full"> <div className="grid grid-flow-col w-full">
{item.release_date && ( {item.release_date && (
<p className="mobile:text-xs text-sm"> <p className="mobile:text-xs text-sm">
@ -73,7 +79,7 @@ export default function LibraryItemsPreview(
{prettyDate(item.release_date)} {prettyDate(item.release_date)}
</p> </p>
)} )}
{item.price && props.currencies && ( {item.price && (
<p className="mobile:text-xs text-sm justify-self-end"> <p className="mobile:text-xs text-sm justify-self-end">
<span className="material-icons !text-base translate-y-[.15em] mr-1"> <span className="material-icons !text-base translate-y-[.15em] mr-1">
shopping_cart shopping_cart

View File

@ -69,7 +69,7 @@ export type TOC = {
export function getTocFromMarkdawn(text: string, title?: string): TOC { export function getTocFromMarkdawn(text: string, title?: string): TOC {
const toc: TOC = { const toc: TOC = {
title: title ?? "Return to top", title: title ?? "Return to top",
slug: slugify(title) ?? "", slug: slugify(title),
children: [], children: [],
}; };
let h2 = -1; let h2 = -1;

View File

@ -1,26 +1,23 @@
import Chip from "components/Chip"; import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img"; import Img, { ImageQuality } from "components/Img";
import { GetPostsPreviewQuery } from "graphql/operations-types"; import { GetPostsPreviewQuery } from "graphql/generated";
import Link from "next/link"; import Link from "next/link";
import { prettyDate, prettySlug } from "queries/helpers"; import { prettyDate, prettySlug } from "queries/helpers";
export type PostPreviewProps = { export type PostPreviewProps = {
post: { post: Exclude<
slug: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["slug"]; GetPostsPreviewQuery["posts"],
thumbnail: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["thumbnail"]; null | undefined
translations: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["translations"]; >["data"][number]["attributes"];
categories: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["categories"];
date: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["date"];
};
}; };
export default function PostPreview(props: PostPreviewProps): JSX.Element { export default function PostPreview(props: PostPreviewProps): JSX.Element {
const { post } = props; const { post } = props;
return ( return (
<Link href={`/news/${post.slug}`} passHref> <Link href={`/news/${post?.slug}`} passHref>
<div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] transition-transform"> <div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] transition-transform">
{post.thumbnail.data ? ( {post?.thumbnail?.data?.attributes ? (
<Img <Img
className="rounded-md rounded-b-none" className="rounded-md rounded-b-none"
image={post.thumbnail.data.attributes} image={post.thumbnail.data.attributes}
@ -30,30 +27,31 @@ export default function PostPreview(props: PostPreviewProps): JSX.Element {
<div className="w-full aspect-[3/2] bg-light rounded-lg"></div> <div className="w-full aspect-[3/2] bg-light rounded-lg"></div>
)} )}
<div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2"> <div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
<div className="grid grid-flow-col w-full"> {post?.date && (
{post.date && ( <div className="grid grid-flow-col w-full">
<p className="mobile:text-xs text-sm"> <p className="mobile:text-xs text-sm">
<span className="material-icons !text-base translate-y-[.15em] mr-1"> <span className="material-icons !text-base translate-y-[.15em] mr-1">
event event
</span> </span>
{prettyDate(post.date)} {prettyDate(post.date)}
</p> </p>
)} </div>
</div> )}
<div> <div>
{post.translations.length > 0 ? ( {post?.translations?.[0] ? (
<> <>
<h1 className="text-xl">{post.translations[0].title}</h1> <h1 className="text-xl">{post.translations[0].title}</h1>
<p>{post.translations[0].excerpt}</p> <p>{post.translations[0].excerpt}</p>
</> </>
) : ( ) : (
<h1 className="text-lg">{prettySlug(post.slug)}</h1> <h1 className="text-lg">{prettySlug(post?.slug)}</h1>
)} )}
</div> </div>
<div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start"> <div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
{post.categories.data.map((category) => ( {post?.categories?.data.map((category) => (
<Chip key={category.id} className="text-sm"> <Chip key={category.id} className="text-sm">
{category.attributes.short} {category.attributes?.short}
</Chip> </Chip>
))} ))}
</div> </div>

View File

@ -5,8 +5,8 @@ import { MouseEventHandler } from "react";
type NavOptionProps = { type NavOptionProps = {
url: string; url: string;
icon?: string; icon?: string;
title: string; title: string | null | undefined;
subtitle?: string; subtitle?: string | null | undefined;
border?: boolean; border?: boolean;
reduced?: boolean; reduced?: boolean;
onClick?: MouseEventHandler<HTMLDivElement>; onClick?: MouseEventHandler<HTMLDivElement>;

View File

@ -2,8 +2,8 @@ import HorizontalLine from "components/HorizontalLine";
type PanelHeaderProps = { type PanelHeaderProps = {
icon?: string; icon?: string;
title: string; title: string | null | undefined;
description?: string; description?: string | null | undefined;
}; };
export default function PanelHeader(props: PanelHeaderProps): JSX.Element { export default function PanelHeader(props: PanelHeaderProps): JSX.Element {

View File

@ -1,12 +1,12 @@
import Button from "components/Button"; import Button from "components/Button";
import HorizontalLine from "components/HorizontalLine"; import HorizontalLine from "components/HorizontalLine";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { GetWebsiteInterfaceQuery } from "graphql/operations-types"; import { AppStaticProps } from "queries/getAppStaticProps";
type ReturnButtonProps = { type ReturnButtonProps = {
href: string; href: string;
title: string; title: string | null | undefined;
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; langui: AppStaticProps["langui"];
displayOn: ReturnButtonType; displayOn: ReturnButtonType;
horizontalLine?: boolean; horizontalLine?: boolean;
className?: string; className?: string;

View File

@ -3,14 +3,14 @@ import HorizontalLine from "components/HorizontalLine";
import NavOption from "components/PanelComponents/NavOption"; import NavOption from "components/PanelComponents/NavOption";
import ToolTip from "components/ToolTip"; import ToolTip from "components/ToolTip";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { GetWebsiteInterfaceQuery } from "graphql/operations-types";
import { useMediaDesktop } from "hooks/useMediaQuery"; import { useMediaDesktop } from "hooks/useMediaQuery";
import Markdown from "markdown-to-jsx"; import Markdown from "markdown-to-jsx";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps } from "queries/getAppStaticProps";
type MainPanelProps = { type MainPanelProps = {
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; langui: AppStaticProps["langui"];
}; };
export default function MainPanel(props: MainPanelProps): JSX.Element { export default function MainPanel(props: MainPanelProps): JSX.Element {

View File

@ -1,68 +1,67 @@
import Chip from "components/Chip"; import Chip from "components/Chip";
import { import { RecorderChipFragment } from "graphql/generated";
GetContentTextQuery, import { AppStaticProps } from "queries/getAppStaticProps";
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import Button from "./Button"; import Button from "./Button";
import Img, { ImageQuality } from "./Img"; import Img, { ImageQuality } from "./Img";
import ToolTip from "./ToolTip"; import ToolTip from "./ToolTip";
type RecorderChipProps = { type RecorderChipProps = {
className?: string; className?: string;
recorder: GetContentTextQuery["contents"]["data"][number]["attributes"]["text_set"][number]["transcribers"]["data"][number]; recorder: RecorderChipFragment;
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; langui: AppStaticProps["langui"];
}; };
export default function RecorderChip(props: RecorderChipProps): JSX.Element { export default function RecorderChip(props: RecorderChipProps): JSX.Element {
const { recorder, langui } = props; const { recorder, langui } = props;
return ( return (
<ToolTip <ToolTip
content={ content={
<div className="text-left p-2 py-5 grid gap-8"> <div className="text-left p-2 py-5 grid gap-8">
<div className="grid grid-flow-col gap-6 place-items-center place-content-start"> <div className="grid grid-flow-col gap-6 place-items-center place-content-start">
{recorder.attributes.avatar.data && ( {recorder.avatar?.data?.attributes && (
<Img <Img
className="w-20 rounded-full border-4 border-mid" className="w-20 rounded-full border-4 border-mid"
image={recorder.attributes.avatar.data.attributes} image={recorder.avatar?.data.attributes}
quality={ImageQuality.Small} quality={ImageQuality.Small}
rawImg rawImg
/> />
)} )}
<div className="grid gap-2"> <div className="grid gap-2">
<h3 className=" text-2xl">{recorder.attributes.username}</h3> <h3 className=" text-2xl">{recorder.username}</h3>
{recorder.attributes.languages.data.length > 0 && ( {recorder.languages?.data && recorder.languages.data.length > 0 && (
<div className="flex flex-row flex-wrap gap-1"> <div className="flex flex-row flex-wrap gap-1">
<p>{langui.languages}:</p> <p>{langui.languages}:</p>
{recorder.attributes.languages.data.map((language) => ( {recorder.languages.data.map((language) => (
<Chip key={language.attributes.code}> <>
{language.attributes.code.toUpperCase()} {language.attributes && (
</Chip> <Chip key={language.attributes.code}>
{language.attributes.code.toUpperCase()}
</Chip>
)}
</>
))} ))}
</div> </div>
)} )}
{recorder.attributes.pronouns && ( {recorder.pronouns && (
<div className="flex flex-row flex-wrap gap-1"> <div className="flex flex-row flex-wrap gap-1">
<p>{langui.pronouns}:</p> <p>{langui.pronouns}:</p>
<Chip>{recorder.attributes.pronouns}</Chip> <Chip>{recorder.pronouns}</Chip>
</div> </div>
)} )}
</div> </div>
</div> </div>
{recorder.attributes.bio.length > 0 && ( {recorder.bio?.[0] && <p>{recorder.bio[0].bio}</p>}
<p>{recorder.attributes.bio[0].bio}</p>
)}
<Button className="cursor-not-allowed">View profile</Button> <Button className="cursor-not-allowed">View profile</Button>
</div> </div>
} }
placement="top" placement="top"
> >
<Chip key={recorder.id}> <Chip key={recorder.anonymous_code}>
{recorder.attributes.anonymize {recorder.anonymize
? `Recorder#${recorder.attributes.anonymous_code}` ? `Recorder#${recorder.anonymous_code}`
: recorder.attributes.username} : recorder.username}
</Chip> </Chip>
</ToolTip> </ToolTip>
); );

View File

@ -0,0 +1,5 @@
fragment datePicker on ComponentBasicsDatepicker {
year
month
day
}

View File

@ -0,0 +1,12 @@
fragment pricePicker on ComponentBasicsPrice {
amount
currency {
data {
attributes {
symbol
code
rate_to_usd
}
}
}
}

View File

@ -0,0 +1,23 @@
fragment recorderChip on Recorder {
username
anonymize
anonymous_code
pronouns
bio(filters: { language: { code: { eq: $language_code } } }) {
bio
}
languages {
data {
attributes {
code
}
}
}
avatar {
data {
attributes {
...uploadImage
}
}
}
}

View File

@ -0,0 +1,8 @@
fragment uploadImage on UploadFile {
name
alternativeText
caption
width
height
url
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,178 +0,0 @@
import { readFileSync } from "fs";
import {
GetChronologyItemsQuery,
GetChronologyItemsQueryVariables,
GetContentQuery,
GetContentQueryVariables,
GetContentsQuery,
GetContentsQueryVariables,
GetContentsSlugsQuery,
GetContentsSlugsQueryVariables,
GetContentTextQuery,
GetContentTextQueryVariables,
GetCurrenciesQuery,
GetCurrenciesQueryVariables,
GetErasQuery,
GetErasQueryVariables,
GetLanguagesQuery,
GetLanguagesQueryVariables,
GetLibraryItemQuery,
GetLibraryItemQueryVariables,
GetLibraryItemScansQuery,
GetLibraryItemScansQueryVariables,
GetLibraryItemsPreviewQuery,
GetLibraryItemsPreviewQueryVariables,
GetLibraryItemsSlugsQuery,
GetLibraryItemsSlugsQueryVariables,
GetPostQuery,
GetPostQueryVariables,
GetPostsPreviewQuery,
GetPostsPreviewQueryVariables,
GetPostsSlugsQuery,
GetPostsSlugsQueryVariables,
GetWebsiteInterfaceQuery,
GetWebsiteInterfaceQueryVariables,
} from "graphql/operations-types";
async function graphQL(query: string, variables?: string) {
const res = await fetch(`${process.env.URL_GRAPHQL}`, {
method: "POST",
body: JSON.stringify({
query: query,
variables: variables,
}),
headers: {
"content-type": "application/json",
Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
},
});
return (await res.json()).data;
}
function getQueryFromOperations(queryName: string): string {
const operations = readFileSync("./src/graphql/operation.graphql", "utf8");
let startingIndex = -1;
let endingIndex = -1;
const lines = operations.split("\n");
lines.map((line, index) => {
if (startingIndex === -1) {
if (line.startsWith(`query ${queryName}(`)) startingIndex = index;
if (line.startsWith(`query ${queryName} {`)) startingIndex = index;
} else if (endingIndex === -1) {
if (line.startsWith("query")) endingIndex = index;
}
});
return lines.slice(startingIndex, endingIndex).join("\n");
}
export async function getWebsiteInterface(
variables: GetWebsiteInterfaceQueryVariables
): Promise<GetWebsiteInterfaceQuery> {
const query = getQueryFromOperations("getWebsiteInterface");
return await graphQL(query, JSON.stringify(variables));
}
export async function getEras(
variables: GetErasQueryVariables
): Promise<GetErasQuery> {
const query = getQueryFromOperations("getEras");
return await graphQL(query, JSON.stringify(variables));
}
export async function getChronologyItems(
variables: GetChronologyItemsQueryVariables
): Promise<GetChronologyItemsQuery> {
const query = getQueryFromOperations("getChronologyItems");
return await graphQL(query, JSON.stringify(variables));
}
export async function getLibraryItemsPreview(
variables: GetLibraryItemsPreviewQueryVariables
): Promise<GetLibraryItemsPreviewQuery> {
const query = getQueryFromOperations("getLibraryItemsPreview");
return await graphQL(query, JSON.stringify(variables));
}
export async function getLibraryItemsSlugs(
variables: GetLibraryItemsSlugsQueryVariables
): Promise<GetLibraryItemsSlugsQuery> {
const query = getQueryFromOperations("getLibraryItemsSlugs");
return await graphQL(query, JSON.stringify(variables));
}
export async function getLibraryItem(
variables: GetLibraryItemQueryVariables
): Promise<GetLibraryItemQuery> {
const query = getQueryFromOperations("getLibraryItem");
return await graphQL(query, JSON.stringify(variables));
}
export async function getContentsSlugs(
variables: GetContentsSlugsQueryVariables
): Promise<GetContentsSlugsQuery> {
const query = getQueryFromOperations("getContentsSlugs");
return await graphQL(query, JSON.stringify(variables));
}
export async function getContents(
variables: GetContentsQueryVariables
): Promise<GetContentsQuery> {
const query = getQueryFromOperations("getContents");
return await graphQL(query, JSON.stringify(variables));
}
export async function getContent(
variables: GetContentQueryVariables
): Promise<GetContentQuery> {
const query = getQueryFromOperations("getContent");
return await graphQL(query, JSON.stringify(variables));
}
export async function getContentText(
variables: GetContentTextQueryVariables
): Promise<GetContentTextQuery> {
const query = getQueryFromOperations("getContentText");
return await graphQL(query, JSON.stringify(variables));
}
export async function getCurrencies(
variables: GetCurrenciesQueryVariables
): Promise<GetCurrenciesQuery> {
const query = getQueryFromOperations("getCurrencies");
return await graphQL(query, JSON.stringify(variables));
}
export async function getLanguages(
variables: GetLanguagesQueryVariables
): Promise<GetLanguagesQuery> {
const query = getQueryFromOperations("getLanguages");
return await graphQL(query, JSON.stringify(variables));
}
export async function getPost(
variables: GetPostQueryVariables
): Promise<GetPostQuery> {
const query = getQueryFromOperations("getPost");
return await graphQL(query, JSON.stringify(variables));
}
export async function getPostsSlugs(
variables: GetPostsSlugsQueryVariables
): Promise<GetPostsSlugsQuery> {
const query = getQueryFromOperations("getPostsSlugs");
return await graphQL(query, JSON.stringify(variables));
}
export async function getPostsPreview(
variables: GetPostsPreviewQueryVariables
): Promise<GetPostsPreviewQuery> {
const query = getQueryFromOperations("getPostsPreview");
return await graphQL(query, JSON.stringify(variables));
}
export async function getLibraryItemScans(
variables: GetLibraryItemScansQueryVariables
): Promise<GetLibraryItemScansQuery> {
const query = getQueryFromOperations("getLibraryItemScans");
return await graphQL(query, JSON.stringify(variables));
}

View File

@ -0,0 +1,34 @@
query getChronologyItems($language_code: String) {
chronologyItems(
pagination: { limit: -1 }
sort: ["year:asc", "month:asc", "day:asc"]
) {
data {
id
attributes {
year
month
day
displayed_date
events {
id
source {
data {
attributes {
name
}
}
}
translations(
filters: { language: { code: { eq: $language_code } } }
) {
title
description
note
status
}
}
}
}
}
}

View File

@ -0,0 +1,77 @@
query getContent($slug: String, $language_code: String) {
contents(filters: { slug: { eq: $slug } }) {
data {
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
pre_title
title
subtitle
description
}
categories {
data {
id
attributes {
name
short
}
}
}
type {
data {
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
}
}
ranged_contents {
data {
id
attributes {
slug
scan_set {
id
}
library_item {
data {
attributes {
slug
title
subtitle
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
}
}
}
text_set {
id
}
video_set {
id
}
audio_set {
id
}
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
}

View File

@ -0,0 +1,114 @@
query getContentText($slug: String, $language_code: String) {
contents(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
pre_title
title
subtitle
description
}
categories {
data {
id
attributes {
name
short
}
}
}
type {
data {
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
}
}
ranged_contents {
data {
id
attributes {
slug
scan_set {
id
}
library_item {
data {
attributes {
slug
title
subtitle
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
}
}
}
text_set_languages: text_set {
language {
data {
attributes {
code
}
}
}
}
text_set(filters: { language: { code: { eq: $language_code } } }) {
status
text
source_language {
data {
attributes {
code
}
}
}
transcribers {
data {
id
attributes {
...recorderChip
}
}
}
translators {
data {
id
attributes {
...recorderChip
}
}
}
proofreaders {
data {
id
attributes {
...recorderChip
}
}
}
notes
}
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
}

View File

@ -0,0 +1,77 @@
query getContents($language_code: String) {
contents(pagination: { limit: -1 }) {
data {
id
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
pre_title
title
subtitle
}
categories {
data {
id
attributes {
name
short
}
}
}
type {
data {
attributes {
slug
titles(filters: { language: { code: { eq: $language_code } } }) {
title
}
}
}
}
ranged_contents {
data {
id
attributes {
slug
scan_set {
id
}
library_item {
data {
attributes {
slug
title
subtitle
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
}
}
}
text_set {
id
}
video_set {
id
}
audio_set {
id
}
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
}

View File

@ -0,0 +1,9 @@
query getContentsSlugs {
contents(pagination: { limit: -1 }) {
data {
attributes {
slug
}
}
}
}

View File

@ -0,0 +1,13 @@
query getCurrencies {
currencies {
data {
id
attributes {
code
symbol
rate_to_usd
display_decimals
}
}
}
}

View File

@ -0,0 +1,16 @@
query getEras($language_code: String) {
chronologyEras(sort: "starting_year") {
data {
id
attributes {
slug
starting_year
ending_year
title(filters: { language: { code: { eq: $language_code } } }) {
title
description
}
}
}
}
}

View File

@ -0,0 +1,12 @@
query getLanguages {
languages {
data {
id
attributes {
name
code
localized_name
}
}
}
}

View File

@ -0,0 +1,387 @@
query getLibraryItem($slug: String, $language_code: String) {
libraryItems(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
title
subtitle
slug
root_item
primary
digital
thumbnail {
data {
attributes {
...uploadImage
}
}
}
gallery {
data {
id
attributes {
...uploadImage
}
}
}
release_date {
...datePicker
}
price {
...pricePicker
}
categories {
data {
id
attributes {
name
short
}
}
}
size {
width
height
thickness
}
descriptions(filters: { language: { code: { eq: $language_code } } }) {
description
}
metadata {
__typename
... on ComponentMetadataBooks {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
binding_type
page_count
page_order
languages {
data {
attributes {
code
name
}
}
}
}
... on ComponentMetadataVideo {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataGame {
platforms {
data {
id
attributes {
short
}
}
}
audio_languages {
data {
attributes {
code
name
}
}
}
sub_languages {
data {
attributes {
code
name
}
}
}
interface_languages {
data {
attributes {
code
name
}
}
}
}
... on ComponentMetadataAudio {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataGroup {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
subitems_type {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
}
subitem_of {
data {
id
attributes {
title
subtitle
slug
}
}
}
subitems {
data {
id
attributes {
title
subtitle
slug
thumbnail {
data {
attributes {
...uploadImage
}
}
}
release_date {
...datePicker
}
price {
...pricePicker
}
categories {
data {
id
attributes {
name
short
}
}
}
metadata {
__typename
... on ComponentMetadataBooks {
subtype {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
title
}
}
}
}
}
... on ComponentMetadataGame {
platforms {
data {
id
attributes {
short
}
}
}
}
... on ComponentMetadataVideo {
subtype {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
title
}
}
}
}
}
... on ComponentMetadataAudio {
subtype {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
title
}
}
}
}
}
... on ComponentMetadataGroup {
subtype {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
title
}
}
}
}
subitems_type {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
title
}
}
}
}
}
}
}
}
}
submerchs {
data {
id
attributes {
slug
title
subtitle
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
contents(pagination: { limit: -1 }) {
data {
id
attributes {
slug
range {
__typename
... on ComponentRangePageRange {
starting_page
ending_page
}
... on ComponentRangeTimeRange {
starting_time
ending_time
}
}
scan_set {
id
}
content {
data {
attributes {
slug
categories {
data {
id
attributes {
name
short
}
}
}
type {
data {
attributes {
slug
titles(
filters: {
language: { code: { eq: $language_code } }
}
) {
title
}
}
}
}
titles(
filters: { language: { code: { eq: $language_code } } }
) {
pre_title
title
subtitle
}
text_set {
id
}
video_set {
id
}
audio_set {
id
}
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,97 @@
query getLibraryItemScans($slug: String, $language_code: String) {
libraryItems(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
slug
title
subtitle
thumbnail {
data {
attributes {
...uploadImage
}
}
}
contents(pagination: { limit: -1 }) {
data {
id
attributes {
slug
range {
__typename
... on ComponentRangePageRange {
starting_page
ending_page
}
... on ComponentRangeTimeRange {
starting_time
ending_time
}
}
scan_set_languages: scan_set {
language {
data {
attributes {
code
}
}
}
}
scan_set(
filters: {
or: [
{ language: { code: { eq: "xx" } } }
{ language: { code: { eq: $language_code } } }
]
}
) {
status
source_language {
data {
attributes {
code
}
}
}
scanners {
data {
id
attributes {
...recorderChip
}
}
}
cleaners {
data {
id
attributes {
...recorderChip
}
}
}
typesetters {
data {
id
attributes {
...recorderChip
}
}
}
notes
pages(pagination: { limit: -1 }) {
data {
id
attributes {
...uploadImage
}
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,117 @@
query getLibraryItemsPreview($language_code: String) {
libraryItems(pagination: { limit: -1 }) {
data {
id
attributes {
title
subtitle
slug
root_item
primary
thumbnail {
data {
attributes {
...uploadImage
}
}
}
release_date {
...datePicker
}
price {
...pricePicker
}
categories {
data {
id
attributes {
name
short
}
}
}
metadata {
__typename
... on ComponentMetadataBooks {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataGame {
platforms {
data {
id
attributes {
short
}
}
}
}
... on ComponentMetadataVideo {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataAudio {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
... on ComponentMetadataGroup {
subtype {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
subitems_type {
data {
attributes {
slug
titles(
filters: { language: { code: { eq: $language_code } } }
) {
title
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,9 @@
query getLibraryItemsSlugs {
libraryItems(pagination: { limit: -1 }) {
data {
attributes {
slug
}
}
}
}

View File

@ -0,0 +1,61 @@
query getPost($slug: String, $language_code: String) {
posts(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
slug
updatedAt
date {
...datePicker
}
authors {
data {
id
attributes {
...recorderChip
}
}
}
categories {
data {
id
attributes {
name
short
}
}
}
hidden
thumbnail {
data {
attributes {
...uploadImage
}
}
}
translations_languages: translations {
language {
data {
attributes {
code
}
}
}
}
translations(filters: { language: { code: { eq: $language_code } } }) {
status
title
excerpt
thumbnail {
data {
attributes {
...uploadImage
}
}
}
body
}
}
}
}
}

View File

@ -0,0 +1,39 @@
query getPostsPreview($language_code: String) {
posts(filters: { hidden: { eq: false } }) {
data {
id
attributes {
slug
date {
...datePicker
}
categories {
data {
id
attributes {
short
}
}
}
thumbnail {
data {
attributes {
...uploadImage
}
}
}
translations(filters: { language: { code: { eq: $language_code } } }) {
title
excerpt
thumbnail {
data {
attributes {
...uploadImage
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,10 @@
query getPostsSlugs {
posts(filters: { hidden: { eq: false } }) {
data {
id
attributes {
slug
}
}
}
}

View File

@ -0,0 +1,138 @@
query getWebsiteInterface($language_code: String) {
websiteInterfaces(
filters: { ui_language: { code: { eq: $language_code } } }
) {
data {
attributes {
library
contents
wiki
chronicles
library_short_description
contents_short_description
wiki_short_description
chronicles_short_description
news
merch
gallery
archives
about_us
licensing_notice
copyright_notice
contents_description
type
category
categories
size
release_date
release_year
details
price
width
height
thickness
subitem
subitems
subitem_of
variant
variants
variant_of
summary
audio
video
textual
game
other
return_to
left_to_right
right_to_left
page
pages
page_order
binding
type_information
front_matter
back_matter
open_content
read_content
watch_content
listen_content
view_scans
paperback
hardcover
languages
select_language
language
library_description
wiki_description
chronicles_description
news_description
merch_description
gallery_description
archives_description
about_us_description
page_not_found
default_description
name
show_subitems
show_primary_items
show_secondary_items
no_type
no_year
order_by
group_by
select_option_sidebar
group
settings
theme
light
auto
dark
font_size
player_name
currency
font
calculated
status_incomplete
status_draft
status_review
status_done
incomplete
draft
review
done
status
transcribers
translators
proofreaders
transcript_notice
translation_notice
source_language
pronouns
no_category
item
items
content
result
results
language_switch_message
open_settings
change_language
open_search
chronology
accords_handbook
legality
members
sharing_policy
contact_us
email
email_gdpr_notice
message
send
response_invalid_code
response_invalid_email
response_email_success
}
}
}
}

File diff suppressed because it is too large Load Diff

9
src/graphql/sdk.ts Normal file
View File

@ -0,0 +1,9 @@
import { GraphQLClient } from "graphql-request";
import { getSdk, UploadFile } from "graphql/generated";
export function getReadySdk() {
const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
});
return getSdk(client);
}

View File

@ -7,23 +7,27 @@ import ReturnButton, {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import ContentPanel from "components/Panels/ContentPanel"; import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { getPost } from "graphql/operations"; import { GetPostQuery } from "graphql/generated";
import { GetPostQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { getLocalesFromLanguages, prettySlug } from "queries/helpers"; import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
interface AccordsHandbookProps extends AppStaticProps { interface Props extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"]; post: Exclude<
GetPostQuery["posts"],
null | undefined
>["data"][number]["attributes"];
} }
export default function AccordsHandbook( export default function AccordsHandbook(props: Props): JSX.Element {
props: AccordsHandbookProps
): JSX.Element {
const { langui, post } = props; const { langui, post } = props;
const router = useRouter(); const router = useRouter();
const locales = getLocalesFromLanguages(post.translations_languages); const locales = getLocalesFromLanguages(post?.translations_languages);
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
@ -34,12 +38,7 @@ export default function AccordsHandbook(
title={langui.about_us} title={langui.about_us}
horizontalLine horizontalLine
/> />
{post.translations.length > 0 && post.translations[0].body && ( <TOC text={body} title={title} />
<TOC
text={post.translations[0].body}
title={post.translations[0].title}
/>
)}
</SubPanel> </SubPanel>
); );
@ -53,7 +52,7 @@ export default function AccordsHandbook(
className="mb-10" className="mb-10"
/> />
{locales.includes(router.locale ?? "en") ? ( {locales.includes(router.locale ?? "en") ? (
<Markdawn text={post.translations[0].body} /> <Markdawn text={body} />
) : ( ) : (
<LanguageSwitcher <LanguageSwitcher
locales={locales} locales={locales}
@ -66,11 +65,7 @@ export default function AccordsHandbook(
return ( return (
<AppLayout <AppLayout
navTitle={ navTitle={title}
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} {...props}
@ -80,16 +75,17 @@ export default function AccordsHandbook(
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: AccordsHandbookProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = "accords-handbook"; const slug = "accords-handbook";
const props: AccordsHandbookProps = { const post = await sdk.getPost({
slug: slug,
language_code: context.locale ?? "en",
});
if (!post.posts) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
post: ( post: post.posts.data[0].attributes,
await getPost({
slug: slug,
language_code: context.locale ?? "en",
})
).posts.data[0].attributes,
}; };
return { return {
props: props, props: props,

View File

@ -8,31 +8,41 @@ import ReturnButton, {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import ContentPanel from "components/Panels/ContentPanel"; import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { getPost } from "graphql/operations"; import { GetPostQuery } from "graphql/generated";
import { GetPostQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { getLocalesFromLanguages, randomInt } from "queries/helpers"; import {
getLocalesFromLanguages,
prettySlug,
randomInt,
} from "queries/helpers";
import { useState } from "react"; import { useState } from "react";
interface ContactProps extends AppStaticProps { interface Props extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"]; post: Exclude<
GetPostQuery["posts"],
null | undefined
>["data"][number]["attributes"];
} }
export default function AboutUs(props: ContactProps): JSX.Element { export default function AboutUs(props: Props): JSX.Element {
const { langui, post } = props; const { langui, post } = props;
const router = useRouter(); const router = useRouter();
const [formResponse, setFormResponse] = useState(""); const [formResponse, setFormResponse] = useState("");
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">( const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">(
"stale" "stale"
); );
const locales = getLocalesFromLanguages(post.translations_languages); const locales = getLocalesFromLanguages(post?.translations_languages);
const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10)); const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10));
const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10)); const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10));
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
@ -42,12 +52,7 @@ export default function AboutUs(props: ContactProps): JSX.Element {
title={langui.about_us} title={langui.about_us}
horizontalLine horizontalLine
/> />
{post.translations.length > 0 && post.translations[0].body && ( <TOC text={body} title={title} />
<TOC
text={post.translations[0].body}
title={post.translations[0].title}
/>
)}
</SubPanel> </SubPanel>
); );
@ -61,7 +66,7 @@ export default function AboutUs(props: ContactProps): JSX.Element {
className="mb-10" className="mb-10"
/> />
{locales.includes(router.locale ?? "en") ? ( {locales.includes(router.locale ?? "en") ? (
<Markdawn text={post.translations[0].body} /> <Markdawn text={body} />
) : ( ) : (
<LanguageSwitcher <LanguageSwitcher
locales={locales} locales={locales}
@ -110,13 +115,13 @@ export default function AboutUs(props: ContactProps): JSX.Element {
.then((response: ResponseMailProps) => { .then((response: ResponseMailProps) => {
switch (response.code) { switch (response.code) {
case "OKAY": case "OKAY":
setFormResponse(langui.response_email_success); setFormResponse(langui.response_email_success ?? "");
setFormState("completed"); setFormState("completed");
break; break;
case "EENVELOPE": case "EENVELOPE":
setFormResponse(langui.response_invalid_email); setFormResponse(langui.response_invalid_email ?? "");
setFormState("stale"); setFormState("stale");
break; break;
@ -127,7 +132,7 @@ export default function AboutUs(props: ContactProps): JSX.Element {
} }
}); });
} else { } else {
setFormResponse(langui.response_invalid_code); setFormResponse(langui.response_invalid_code ?? "");
setFormState("stale"); setFormState("stale");
setRandomNumber1(randomInt(0, 10)); setRandomNumber1(randomInt(0, 10));
setRandomNumber2(randomInt(0, 10)); setRandomNumber2(randomInt(0, 10));
@ -194,7 +199,7 @@ export default function AboutUs(props: ContactProps): JSX.Element {
<input <input
type="submit" type="submit"
value={langui.send} value={langui.send ?? "Send"}
className="w-min !px-6" className="w-min !px-6"
disabled={formState !== "stale"} disabled={formState !== "stale"}
/> />
@ -224,16 +229,17 @@ export default function AboutUs(props: ContactProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: ContactProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = "contact"; const slug = "contact";
const props: ContactProps = { const post = await sdk.getPost({
slug: slug,
language_code: context.locale ?? "en",
});
if (!post.posts) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
post: ( post: post.posts.data[0].attributes,
await getPost({
slug: slug,
language_code: context.locale ?? "en",
})
).posts.data[0].attributes,
}; };
return { return {
props: props, props: props,

View File

@ -5,9 +5,9 @@ import SubPanel from "components/Panels/SubPanel";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
interface AboutUsProps extends AppStaticProps {} interface Props extends AppStaticProps {}
export default function AboutUs(props: AboutUsProps): JSX.Element { export default function AboutUs(props: Props): JSX.Element {
const { langui } = props; const { langui } = props;
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
@ -38,8 +38,8 @@ export default function AboutUs(props: AboutUsProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: AboutUsProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const props: AboutUsProps = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
}; };
return { return {

View File

@ -7,21 +7,27 @@ import ReturnButton, {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import ContentPanel from "components/Panels/ContentPanel"; import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { getPost } from "graphql/operations"; import { GetPostQuery } from "graphql/generated";
import { GetPostQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { getLocalesFromLanguages, prettySlug } from "queries/helpers"; import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
interface SiteInfoProps extends AppStaticProps { interface Props extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"]; post: Exclude<
GetPostQuery["posts"],
null | undefined
>["data"][number]["attributes"];
} }
export default function SiteInformation(props: SiteInfoProps): JSX.Element { export default function SiteInformation(props: Props): JSX.Element {
const { langui, post } = props; const { langui, post } = props;
const router = useRouter(); const router = useRouter();
const locales = getLocalesFromLanguages(post.translations_languages); const locales = getLocalesFromLanguages(post?.translations_languages);
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
@ -32,12 +38,7 @@ export default function SiteInformation(props: SiteInfoProps): JSX.Element {
title={langui.about_us} title={langui.about_us}
horizontalLine horizontalLine
/> />
{post.translations.length > 0 && post.translations[0].body && ( <TOC text={body} title={title} />
<TOC
text={post.translations[0].body}
title={post.translations[0].title}
/>
)}
</SubPanel> </SubPanel>
); );
@ -51,7 +52,7 @@ export default function SiteInformation(props: SiteInfoProps): JSX.Element {
className="mb-10" className="mb-10"
/> />
{locales.includes(router.locale ?? "en") ? ( {locales.includes(router.locale ?? "en") ? (
<Markdawn text={post.translations[0].body} /> <Markdawn text={body} />
) : ( ) : (
<LanguageSwitcher <LanguageSwitcher
locales={locales} locales={locales}
@ -64,11 +65,7 @@ export default function SiteInformation(props: SiteInfoProps): JSX.Element {
return ( return (
<AppLayout <AppLayout
navTitle={ navTitle={title}
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} {...props}
@ -78,16 +75,17 @@ export default function SiteInformation(props: SiteInfoProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: SiteInfoProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = "legality"; const slug = "legality";
const props: SiteInfoProps = { const post = await sdk.getPost({
slug: slug,
language_code: context.locale ?? "en",
});
if (!post.posts) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
post: ( post: post.posts.data[0].attributes,
await getPost({
slug: slug,
language_code: context.locale ?? "en",
})
).posts.data[0].attributes,
}; };
return { return {
props: props, props: props,

View File

@ -7,22 +7,27 @@ import ReturnButton, {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import ContentPanel from "components/Panels/ContentPanel"; import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { getPost } from "graphql/operations"; import { GetPostQuery } from "graphql/generated";
import { GetPostQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { getLocalesFromLanguages, prettySlug } from "queries/helpers"; import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
interface SharingPolicyProps extends AppStaticProps { interface Props extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"]; post: Exclude<
GetPostQuery["posts"],
null | undefined
>["data"][number]["attributes"];
} }
export default function SharingPolicy(props: Props): JSX.Element {
export default function SharingPolicy(props: SharingPolicyProps): JSX.Element {
const { langui, post } = props; const { langui, post } = props;
const locales = getLocalesFromLanguages(post.translations_languages); const locales = getLocalesFromLanguages(post?.translations_languages);
const router = useRouter(); const router = useRouter();
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
@ -32,12 +37,7 @@ export default function SharingPolicy(props: SharingPolicyProps): JSX.Element {
title={langui.about_us} title={langui.about_us}
horizontalLine horizontalLine
/> />
{post.translations.length > 0 && post.translations[0].body && ( <TOC text={body} title={title} />
<TOC
text={post.translations[0].body}
title={post.translations[0].title}
/>
)}
</SubPanel> </SubPanel>
); );
@ -51,7 +51,7 @@ export default function SharingPolicy(props: SharingPolicyProps): JSX.Element {
className="mb-10" className="mb-10"
/> />
{locales.includes(router.locale ?? "en") ? ( {locales.includes(router.locale ?? "en") ? (
<Markdawn text={post.translations[0].body} /> <Markdawn text={body} />
) : ( ) : (
<LanguageSwitcher <LanguageSwitcher
locales={locales} locales={locales}
@ -64,11 +64,7 @@ export default function SharingPolicy(props: SharingPolicyProps): JSX.Element {
return ( return (
<AppLayout <AppLayout
navTitle={ navTitle={title}
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
subPanel={subPanel} subPanel={subPanel}
contentPanel={contentPanel} contentPanel={contentPanel}
{...props} {...props}
@ -78,16 +74,17 @@ export default function SharingPolicy(props: SharingPolicyProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: SharingPolicyProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = "sharing-policy"; const slug = "sharing-policy";
const props: SharingPolicyProps = { const post = await sdk.getPost({
slug: slug,
language_code: context.locale ?? "en",
});
if (!post.posts) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
post: ( post: post.posts.data[0].attributes,
await getPost({
slug: slug,
language_code: context.locale ?? "en",
})
).posts.data[0].attributes,
}; };
return { return {
props: props, props: props,

View File

@ -64,7 +64,6 @@ export default async function Mail(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<ResponseMailProps> res: NextApiResponse<ResponseMailProps>
) { ) {
console.log(req.body);
const body = req.body as RequestProps; const body = req.body as RequestProps;
const { serverRuntimeConfig } = getConfig(); const { serverRuntimeConfig } = getConfig();

View File

@ -13,8 +13,8 @@ import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import RecorderChip from "components/RecorderChip"; import RecorderChip from "components/RecorderChip";
import ToolTip from "components/ToolTip"; import ToolTip from "components/ToolTip";
import { getContentsSlugs, getContentText } from "graphql/operations"; import { GetContentTextQuery } from "graphql/generated";
import { GetContentTextQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { import {
GetStaticPathsContext, GetStaticPathsContext,
GetStaticPathsResult, GetStaticPathsResult,
@ -33,15 +33,21 @@ import {
} from "queries/helpers"; } from "queries/helpers";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
content: GetContentTextQuery["contents"]["data"][number]["attributes"]; content: Exclude<
contentId: GetContentTextQuery["contents"]["data"][number]["id"]; GetContentTextQuery["contents"],
null | undefined
>["data"][number]["attributes"];
contentId: Exclude<
GetContentTextQuery["contents"],
null | undefined
>["data"][number]["id"];
} }
export default function Content(props: Props): JSX.Element { export default function Content(props: Props): JSX.Element {
useTesting(props); useTesting(props);
const { langui, content, languages } = props; const { langui, content, languages } = props;
const router = useRouter(); const router = useRouter();
const locales = getLocalesFromLanguages(content.text_set_languages); const locales = getLocalesFromLanguages(content?.text_set_languages);
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
@ -53,7 +59,7 @@ export default function Content(props: Props): JSX.Element {
horizontalLine horizontalLine
/> />
{content.text_set.length > 0 && content.text_set[0].source_language.data && ( {content?.text_set?.[0]?.source_language?.data?.attributes && (
<div className="grid gap-5"> <div className="grid gap-5">
<h2 className="text-xl"> <h2 className="text-xl">
{content.text_set[0].source_language.data.attributes.code === {content.text_set[0].source_language.data.attributes.code ===
@ -91,134 +97,157 @@ export default function Content(props: Props): JSX.Element {
</ToolTip> </ToolTip>
</div> </div>
{content.text_set[0].transcribers.data.length > 0 && ( {content.text_set[0].transcribers &&
<div> content.text_set[0].transcribers.data.length > 0 && (
<p className="font-headers">{langui.transcribers}:</p> <div>
<div className="grid place-items-center place-content-center gap-2"> <p className="font-headers">{langui.transcribers}:</p>
{content.text_set[0].transcribers.data.map((recorder) => ( <div className="grid place-items-center place-content-center gap-2">
<RecorderChip {content.text_set[0].transcribers.data.map((recorder) => (
key={recorder.id} <>
langui={langui} {recorder.attributes && (
recorder={recorder} <RecorderChip
/> key={recorder.id}
))} langui={langui}
recorder={recorder.attributes}
/>
)}
</>
))}
</div>
</div> </div>
</div> )}
)}
{content.text_set[0].translators.data.length > 0 && ( {content.text_set[0].translators &&
<div> content.text_set[0].translators.data.length > 0 && (
<p className="font-headers">{langui.translators}:</p> <div>
<div className="grid place-items-center place-content-center gap-2"> <p className="font-headers">{langui.translators}:</p>
{content.text_set[0].translators.data.map((recorder) => ( <div className="grid place-items-center place-content-center gap-2">
<RecorderChip {content.text_set[0].translators.data.map((recorder) => (
key={recorder.id} <>
langui={langui} {recorder.attributes && (
recorder={recorder} <RecorderChip
/> key={recorder.id}
))} langui={langui}
recorder={recorder.attributes}
/>
)}
</>
))}
</div>
</div> </div>
</div> )}
)}
{content.text_set[0].proofreaders.data.length > 0 && ( {content.text_set[0].proofreaders &&
<div> content.text_set[0].proofreaders.data.length > 0 && (
<p className="font-headers">{langui.proofreaders}:</p> <div>
<div className="grid place-items-center place-content-center gap-2"> <p className="font-headers">{langui.proofreaders}:</p>
{content.text_set[0].proofreaders.data.map((recorder) => ( <div className="grid place-items-center place-content-center gap-2">
<RecorderChip {content.text_set[0].proofreaders.data.map((recorder) => (
key={recorder.id} <>
langui={langui} {recorder.attributes && (
recorder={recorder} <RecorderChip
/> key={recorder.id}
))} langui={langui}
recorder={recorder.attributes}
/>
)}
</>
))}
</div>
</div> </div>
</div> )}
)}
</div> </div>
)} )}
{content.text_set.length > 0 && content.text_set[0].text && ( {content?.text_set &&
<> content.text_set.length > 0 &&
<HorizontalLine /> content.text_set[0]?.text && (
<TOC <>
text={content.text_set[0].text} <HorizontalLine />
title={ <TOC
content.titles.length > 0 text={content.text_set[0].text}
? prettyinlineTitle( title={
content.titles[0].pre_title, content.titles && content.titles.length > 0 && content.titles[0]
content.titles[0].title, ? prettyinlineTitle(
content.titles[0].subtitle content.titles[0].pre_title,
) content.titles[0].title,
: prettySlug(content.slug) content.titles[0].subtitle
} )
/> : prettySlug(content.slug)
</> }
)} />
</>
)}
</SubPanel> </SubPanel>
); );
const contentPanel = ( const contentPanel = (
<ContentPanel> <ContentPanel>
<ReturnButton <ReturnButton
href={`/contents/${content.slug}`} href={`/contents/${content?.slug}`}
title={langui.content} title={langui.content}
langui={langui} langui={langui}
displayOn={ReturnButtonType.mobile} displayOn={ReturnButtonType.mobile}
className="mb-10" className="mb-10"
/> />
<div className="grid place-items-center"> {content && (
<ThumbnailHeader <div className="grid place-items-center">
thumbnail={content.thumbnail.data?.attributes} <ThumbnailHeader
pre_title={ thumbnail={content.thumbnail?.data?.attributes}
content.titles.length > 0 ? content.titles[0].pre_title : undefined pre_title={
} content.titles && content.titles.length > 0
title={ ? content.titles[0]?.pre_title
content.titles.length > 0 : undefined
? content.titles[0].title }
: prettySlug(content.slug) title={
} content.titles && content.titles.length > 0
subtitle={ ? content.titles[0]?.title
content.titles.length > 0 ? content.titles[0].subtitle : undefined : prettySlug(content.slug)
} }
description={ subtitle={
content.titles.length > 0 content.titles && content.titles.length > 0
? content.titles[0].description ? content.titles[0]?.subtitle
: undefined : undefined
} }
type={content.type} description={
categories={content.categories} content.titles && content.titles.length > 0
langui={langui} ? content.titles[0]?.description
/> : undefined
}
<HorizontalLine /> type={content.type}
categories={content.categories}
{locales.includes(router.locale ?? "en") ? ( langui={langui}
<Markdawn text={content.text_set[0].text} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/> />
)}
</div> <HorizontalLine />
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={content.text_set?.[0]?.text ?? ""} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
</div>
)}
</ContentPanel> </ContentPanel>
); );
let description = ""; let description = "";
if (content.type.data) { if (content?.type?.data) {
description += `${langui.type}: `; description += `${langui.type}: `;
if (content.type.data.attributes.titles.length > 0) {
description += content.type.data.attributes.titles[0].title; description +=
} else { content.type.data.attributes?.titles?.[0]?.title ??
description += prettySlug(content.type.data.attributes.slug); prettySlug(content.type.data.attributes?.slug);
}
description += "\n"; description += "\n";
} }
if (content.categories.data.length > 0) { if (content?.categories?.data && content.categories.data.length > 0) {
description += `${langui.categories}: `; description += `${langui.categories}: `;
description += content.categories.data description += content.categories.data
.map((category) => category.attributes.short) .map((category) => category.attributes?.short)
.join(" | "); .join(" | ");
description += "\n"; description += "\n";
} }
@ -226,15 +255,15 @@ export default function Content(props: Props): JSX.Element {
return ( return (
<AppLayout <AppLayout
navTitle={ navTitle={
content.titles.length > 0 content?.titles && content.titles.length > 0 && content.titles[0]
? prettyinlineTitle( ? prettyinlineTitle(
content.titles[0].pre_title, content.titles[0].pre_title,
content.titles[0].title, content.titles[0].title,
content.titles[0].subtitle content.titles[0].subtitle
) )
: prettySlug(content.slug) : prettySlug(content?.slug)
} }
thumbnail={content.thumbnail.data?.attributes} thumbnail={content?.thumbnail?.data?.attributes ?? undefined}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
description={description} description={description}
@ -246,18 +275,19 @@ export default function Content(props: Props): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = context.params?.slug?.toString() ?? ""; const slug = context.params?.slug?.toString() ?? "";
const content = ( const content = await sdk.getContentText({
await getContentText({ slug: slug,
slug: slug, language_code: context.locale ?? "en",
language_code: context.locale ?? "en", });
})
).contents.data[0]; if (!content.contents || content.contents.data.length === 0)
if (!content) return { notFound: true }; return { notFound: true };
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
content: content.attributes, content: content.contents.data[0].attributes,
contentId: content.id, contentId: content.contents.data[0].id,
}; };
return { return {
props: props, props: props,
@ -267,11 +297,13 @@ export async function getStaticProps(
export async function getStaticPaths( export async function getStaticPaths(
context: GetStaticPathsContext context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { ): Promise<GetStaticPathsResult> {
const contents = await getContentsSlugs({}); const sdk = getReadySdk();
const contents = await sdk.getContentsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
contents.contents.data.map((item) => { contents.contents?.data.map((item) => {
context.locales?.map((local) => { context.locales?.map((local) => {
paths.push({ params: { slug: item.attributes.slug }, locale: local }); if (item.attributes)
paths.push({ params: { slug: item.attributes.slug }, locale: local });
}); });
}); });
return { return {
@ -287,12 +319,12 @@ function useTesting(props: Props) {
const contentURL = `/admin/content-manager/collectionType/api::content.content/${contentId}`; const contentURL = `/admin/content-manager/collectionType/api::content.content/${contentId}`;
if (router.locale === "en") { if (router.locale === "en") {
if (content.categories.data.length === 0) { if (content?.categories?.data.length === 0) {
prettyTestError(router, "Missing categories", ["content"], contentURL); prettyTestError(router, "Missing categories", ["content"], contentURL);
} }
} }
if (content.ranged_contents.data.length === 0) { if (content?.ranged_contents?.data.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Unconnected to any source", "Unconnected to any source",
@ -301,7 +333,7 @@ function useTesting(props: Props) {
); );
} }
if (content.text_set.length === 0) { if (content?.text_set?.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Has no textset, nor audioset, nor videoset", "Has no textset, nor audioset, nor videoset",
@ -310,7 +342,7 @@ function useTesting(props: Props) {
); );
} }
if (content.text_set.length > 1) { if (content?.text_set && content.text_set.length > 1) {
prettyTestError( prettyTestError(
router, router,
"More than one textset for this language", "More than one textset for this language",
@ -319,10 +351,10 @@ function useTesting(props: Props) {
); );
} }
if (content.text_set.length === 1) { if (content?.text_set?.length === 1) {
const textset = content.text_set[0]; const textset = content.text_set[0];
if (!textset.text) { if (!textset?.text) {
prettyTestError( prettyTestError(
router, router,
"Missing text", "Missing text",
@ -330,16 +362,18 @@ function useTesting(props: Props) {
contentURL contentURL
); );
} }
if (!textset.source_language.data) { if (!textset?.source_language?.data) {
prettyTestError( prettyTestError(
router, router,
"Missing source language", "Missing source language",
["content", "text_set"], ["content", "text_set"],
contentURL contentURL
); );
} else if (textset.source_language.data.attributes.code === router.locale) { } else if (
textset.source_language.data.attributes?.code === router.locale
) {
// This is a transcript // This is a transcript
if (textset.transcribers.data.length === 0) { if (textset.transcribers?.data.length === 0) {
prettyTestError( prettyTestError(
router, router,
"Missing transcribers attribution", "Missing transcribers attribution",
@ -347,7 +381,7 @@ function useTesting(props: Props) {
contentURL contentURL
); );
} }
if (textset.translators.data.length > 0) { if (textset.translators && textset.translators.data.length > 0) {
prettyTestError( prettyTestError(
router, router,
"Transcripts shouldn't have translators", "Transcripts shouldn't have translators",
@ -357,7 +391,7 @@ function useTesting(props: Props) {
} }
} else { } else {
// This is a translation // This is a translation
if (textset.translators.data.length === 0) { if (textset.translators?.data.length === 0) {
prettyTestError( prettyTestError(
router, router,
"Missing translators attribution", "Missing translators attribution",
@ -365,7 +399,7 @@ function useTesting(props: Props) {
contentURL contentURL
); );
} }
if (textset.transcribers.data.length > 0) { if (textset.transcribers && textset.transcribers.data.length > 0) {
prettyTestError( prettyTestError(
router, router,
"Translations shouldn't have transcribers", "Translations shouldn't have transcribers",

View File

@ -1,379 +0,0 @@
import AppLayout from "components/AppLayout";
import Button from "components/Button";
import Chip from "components/Chip";
import ThumbnailHeader from "components/Content/ThumbnailHeader";
import HorizontalLine from "components/HorizontalLine";
import LanguageSwitcher from "components/LanguageSwitcher";
import Markdawn from "components/Markdown/Markdawn";
import TOC from "components/Markdown/TOC";
import ReturnButton, {
ReturnButtonType,
} from "components/PanelComponents/ReturnButton";
import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel";
import RecorderChip from "components/RecorderChip";
import ToolTip from "components/ToolTip";
import { getContentsSlugs, getContentText } from "graphql/operations";
import { GetContentTextQuery } from "graphql/operations-types";
import {
GetStaticPathsContext,
GetStaticPathsResult,
GetStaticPropsContext,
} from "next";
import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import {
getLocalesFromLanguages,
getStatusDescription,
prettyinlineTitle,
prettyLanguage,
prettySlug,
prettyTestError,
prettyTestWarning,
} from "queries/helpers";
interface ContentReadProps extends AppStaticProps {
content: GetContentTextQuery["contents"]["data"][number]["attributes"];
contentId: GetContentTextQuery["contents"]["data"][number]["id"];
}
export default function ContentRead(props: ContentReadProps): JSX.Element {
useTesting(props);
const { langui, content, languages } = props;
const router = useRouter();
const locales = getLocalesFromLanguages(content.text_set_languages);
const subPanel = (
<SubPanel>
<ReturnButton
href={`/contents/${content.slug}`}
title={"Content"}
langui={langui}
displayOn={ReturnButtonType.desktop}
horizontalLine
/>
{content.text_set.length > 0 && content.text_set[0].source_language.data && (
<div className="grid gap-5">
<h2 className="text-xl">
{content.text_set[0].source_language.data.attributes.code ===
router.locale
? langui.transcript_notice
: langui.translation_notice}
</h2>
{content.text_set[0].source_language.data.attributes.code !==
router.locale && (
<div className="grid place-items-center gap-2">
<p className="font-headers">{langui.source_language}:</p>
<Button
href={router.asPath}
locale={
content.text_set[0].source_language.data.attributes.code
}
>
{prettyLanguage(
content.text_set[0].source_language.data.attributes.code,
languages
)}
</Button>
</div>
)}
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
<p className="font-headers">{langui.status}:</p>
<ToolTip
content={getStatusDescription(content.text_set[0].status, langui)}
maxWidth={"20rem"}
>
<Chip>{content.text_set[0].status}</Chip>
</ToolTip>
</div>
{content.text_set[0].transcribers.data.length > 0 && (
<div>
<p className="font-headers">{langui.transcribers}:</p>
<div className="grid place-items-center place-content-center gap-2">
{content.text_set[0].transcribers.data.map((recorder) => (
<RecorderChip
key={recorder.id}
langui={langui}
recorder={recorder}
/>
))}
</div>
</div>
)}
{content.text_set[0].translators.data.length > 0 && (
<div>
<p className="font-headers">{langui.translators}:</p>
<div className="grid place-items-center place-content-center gap-2">
{content.text_set[0].translators.data.map((recorder) => (
<RecorderChip
key={recorder.id}
langui={langui}
recorder={recorder}
/>
))}
</div>
</div>
)}
{content.text_set[0].proofreaders.data.length > 0 && (
<div>
<p className="font-headers">{langui.proofreaders}:</p>
<div className="grid place-items-center place-content-center gap-2">
{content.text_set[0].proofreaders.data.map((recorder) => (
<RecorderChip
key={recorder.id}
langui={langui}
recorder={recorder}
/>
))}
</div>
</div>
)}
</div>
)}
{content.text_set.length > 0 && content.text_set[0].text && (
<>
<HorizontalLine />
<TOC
text={content.text_set[0].text}
title={
content.titles.length > 0
? prettyinlineTitle(
content.titles[0].pre_title,
content.titles[0].title,
content.titles[0].subtitle
)
: prettySlug(content.slug)
}
/>
</>
)}
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
href={`/contents/${content.slug}`}
title={langui.content}
langui={langui}
displayOn={ReturnButtonType.mobile}
className="mb-10"
/>
<div className="grid place-items-center">
<ThumbnailHeader
thumbnail={content.thumbnail.data?.attributes}
pre_title={
content.titles.length > 0 ? content.titles[0].pre_title : undefined
}
title={
content.titles.length > 0
? content.titles[0].title
: prettySlug(content.slug)
}
subtitle={
content.titles.length > 0 ? content.titles[0].subtitle : undefined
}
description={
content.titles.length > 0
? content.titles[0].description
: undefined
}
type={content.type}
categories={content.categories}
langui={langui}
/>
<HorizontalLine />
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={content.text_set[0].text} />
) : (
<LanguageSwitcher
locales={locales}
languages={props.languages}
langui={props.langui}
/>
)}
</div>
</ContentPanel>
);
let description = "";
if (content.type.data) {
description += `${langui.type}: `;
if (content.type.data.attributes.titles.length > 0) {
description += content.type.data.attributes.titles[0].title;
} else {
description += prettySlug(content.type.data.attributes.slug);
}
description += "\n";
}
if (content.categories.data.length > 0) {
description += `${langui.categories}: `;
description += content.categories.data
.map((category) => category.attributes.short)
.join(" | ");
description += "\n";
}
return (
<AppLayout
navTitle="Contents"
title={
content.titles.length > 0
? prettyinlineTitle(
content.titles[0].pre_title,
content.titles[0].title,
content.titles[0].subtitle
)
: prettySlug(content.slug)
}
thumbnail={content.thumbnail.data?.attributes}
contentPanel={contentPanel}
subPanel={subPanel}
description={description}
{...props}
/>
);
}
export async function getStaticProps(
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: ContentReadProps }> {
const slug = context.params?.slug?.toString() ?? "";
const content = (
await getContentText({
slug: slug,
language_code: context.locale ?? "en",
})
).contents.data[0];
if (!content) return { notFound: true };
const props: ContentReadProps = {
...(await getAppStaticProps(context)),
content: content.attributes,
contentId: content.id,
};
return {
props: props,
};
}
export async function getStaticPaths(
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> {
const contents = await getContentsSlugs({});
const paths: GetStaticPathsResult["paths"] = [];
contents.contents.data.map((item) => {
context.locales?.map((local) => {
paths.push({ params: { slug: item.attributes.slug }, locale: local });
});
});
return {
paths,
fallback: "blocking",
};
}
function useTesting(props: ContentReadProps) {
const router = useRouter();
const { content, contentId } = props;
const contentURL = `/admin/content-manager/collectionType/api::content.content/${contentId}`;
if (router.locale === "en") {
if (content.categories.data.length === 0) {
prettyTestError(router, "Missing categories", ["content"], contentURL);
}
}
if (content.ranged_contents.data.length === 0) {
prettyTestWarning(
router,
"Unconnected to any source",
["content"],
contentURL
);
}
if (content.text_set.length === 0) {
prettyTestWarning(
router,
"Has no textset, nor audioset, nor videoset",
["content"],
contentURL
);
}
if (content.text_set.length > 1) {
prettyTestError(
router,
"More than one textset for this language",
["content", "text_set"],
contentURL
);
}
if (content.text_set.length === 1) {
const textset = content.text_set[0];
if (!textset.text) {
prettyTestError(
router,
"Missing text",
["content", "text_set"],
contentURL
);
}
if (!textset.source_language.data) {
prettyTestError(
router,
"Missing source language",
["content", "text_set"],
contentURL
);
} else if (textset.source_language.data.attributes.code === router.locale) {
// This is a transcript
if (textset.transcribers.data.length === 0) {
prettyTestError(
router,
"Missing transcribers attribution",
["content", "text_set"],
contentURL
);
}
if (textset.translators.data.length > 0) {
prettyTestError(
router,
"Transcripts shouldn't have translators",
["content", "text_set"],
contentURL
);
}
} else {
// This is a translation
if (textset.translators.data.length === 0) {
prettyTestError(
router,
"Missing translators attribution",
["content", "text_set"],
contentURL
);
}
if (textset.transcribers.data.length > 0) {
prettyTestError(
router,
"Translations shouldn't have transcribers",
["content", "text_set"],
contentURL
);
}
}
}
}

View File

@ -7,21 +7,18 @@ import ContentPanel, {
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import Select from "components/Select"; import Select from "components/Select";
import { getContents } from "graphql/operations"; import { GetContentsQuery } from "graphql/generated";
import { import { getReadySdk } from "graphql/sdk";
GetContentsQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { prettyinlineTitle, prettySlug } from "queries/helpers"; import { prettyinlineTitle, prettySlug } from "queries/helpers";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
interface ContentsProps extends AppStaticProps { interface ContentsProps extends AppStaticProps {
contents: GetContentsQuery["contents"]["data"]; contents: Exclude<GetContentsQuery["contents"], null | undefined>["data"];
} }
type GroupContentItems = Map<string, GetContentsQuery["contents"]["data"]>; type GroupContentItems = Map<string, ContentsProps["contents"]>;
export default function Contents(props: ContentsProps): JSX.Element { export default function Contents(props: ContentsProps): JSX.Element {
const { langui, contents } = props; const { langui, contents } = props;
@ -48,7 +45,7 @@ export default function Contents(props: ContentsProps): JSX.Element {
<p className="flex-shrink-0">{langui.group_by}:</p> <p className="flex-shrink-0">{langui.group_by}:</p>
<Select <Select
className="w-full" className="w-full"
options={[langui.category, langui.type]} options={[langui.category ?? "", langui.type ?? ""]}
state={groupingMethod} state={groupingMethod}
setState={setGroupingMethod} setState={setGroupingMethod}
allowEmpty allowEmpty
@ -70,8 +67,8 @@ export default function Contents(props: ContentsProps): JSX.Element {
{name} {name}
<Chip>{`${items.length} ${ <Chip>{`${items.length} ${
items.length <= 1 items.length <= 1
? langui.result.toLowerCase() ? langui.result?.toLowerCase() ?? ""
: langui.results.toLowerCase() : langui.results?.toLowerCase() ?? ""
}`}</Chip> }`}</Chip>
</h2> </h2>
)} )}
@ -80,7 +77,14 @@ export default function Contents(props: ContentsProps): JSX.Element {
className="grid gap-8 items-end grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]" className="grid gap-8 items-end grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
> >
{items.map((item) => ( {items.map((item) => (
<LibraryContentPreview key={item.id} item={item.attributes} /> <>
{item.attributes && (
<LibraryContentPreview
key={item.id}
item={item.attributes}
/>
)}
</>
))} ))}
</div> </div>
</> </>
@ -102,35 +106,32 @@ export default function Contents(props: ContentsProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: ContentsProps }> { ): Promise<{ notFound: boolean } | { props: ContentsProps }> {
const contents = ( const sdk = getReadySdk();
await getContents({ const contents = await sdk.getContents({
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
}) });
).contents.data; if (!contents.contents) return { notFound: true };
contents.contents.data.sort((a, b) => {
contents.sort((a, b) => { const titleA = a.attributes?.titles?.[0]
const titleA = ? prettyinlineTitle(
a.attributes.titles.length > 0 a.attributes.titles[0].pre_title,
? prettyinlineTitle( a.attributes.titles[0].title,
a.attributes.titles[0].pre_title, a.attributes.titles[0].subtitle
a.attributes.titles[0].title, )
a.attributes.titles[0].subtitle : a.attributes?.slug ?? "";
) const titleB = b.attributes?.titles?.[0]
: a.attributes.slug; ? prettyinlineTitle(
const titleB = b.attributes.titles[0].pre_title,
b.attributes.titles.length > 0 b.attributes.titles[0].title,
? prettyinlineTitle( b.attributes.titles[0].subtitle
b.attributes.titles[0].pre_title, )
b.attributes.titles[0].title, : b.attributes?.slug ?? "";
b.attributes.titles[0].subtitle
)
: b.attributes.slug;
return titleA.localeCompare(titleB); return titleA.localeCompare(titleB);
}); });
const props: ContentsProps = { const props: ContentsProps = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
contents: contents, contents: contents.contents.data,
}; };
return { return {
props: props, props: props,
@ -138,7 +139,7 @@ export async function getStaticProps(
} }
function getGroups( function getGroups(
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"], langui: AppStaticProps["langui"],
groupByType: number, groupByType: number,
items: ContentsProps["contents"] items: ContentsProps["contents"]
): GroupContentItems { ): GroupContentItems {
@ -165,11 +166,11 @@ function getGroups(
group.set(langui.no_category, []); group.set(langui.no_category, []);
items.map((item) => { items.map((item) => {
if (item.attributes.categories.data.length === 0) { if (item.attributes?.categories?.data.length === 0) {
group.get(langui.no_category)?.push(item); group.get(langui.no_category)?.push(item);
} else { } else {
item.attributes.categories.data.map((category) => { item.attributes?.categories?.data.map((category) => {
group.get(category.attributes.name)?.push(item); group.get(category.attributes?.name)?.push(item);
}); });
} }
}); });
@ -180,10 +181,8 @@ function getGroups(
const group: GroupContentItems = new Map(); const group: GroupContentItems = new Map();
items.map((item) => { items.map((item) => {
const type = const type =
item.attributes.type.data.attributes.titles.length > 0 item.attributes?.type?.data?.attributes?.titles?.[0]?.title ??
? item.attributes.type.data.attributes.titles[0].title prettySlug(item.attributes?.type?.data?.attributes?.slug);
: prettySlug(item.attributes.type.data.attributes.slug);
if (!group.has(type)) group.set(type, []); if (!group.has(type)) group.set(type, []);
group.get(type)?.push(item); group.get(type)?.push(item);
}); });

View File

@ -2,22 +2,28 @@ import AppLayout from "components/AppLayout";
import LanguageSwitcher from "components/LanguageSwitcher"; import LanguageSwitcher from "components/LanguageSwitcher";
import Markdawn from "components/Markdown/Markdawn"; import Markdawn from "components/Markdown/Markdawn";
import ContentPanel from "components/Panels/ContentPanel"; import ContentPanel from "components/Panels/ContentPanel";
import { getPost } from "graphql/operations"; import { GetPostQuery } from "graphql/generated";
import { GetPostQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { getLocalesFromLanguages, prettySlug } from "queries/helpers"; import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
interface HomeProps extends AppStaticProps { interface Props extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"]; post: Exclude<
GetPostQuery["posts"],
null | undefined
>["data"][number]["attributes"];
} }
export default function Home(props: HomeProps): JSX.Element { export default function Home(props: Props): JSX.Element {
const { post } = props; const { post } = props;
const locales = getLocalesFromLanguages(post.translations_languages); const locales = getLocalesFromLanguages(post?.translations_languages);
const router = useRouter(); const router = useRouter();
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const contentPanel = ( const contentPanel = (
<ContentPanel> <ContentPanel>
<div className="grid place-items-center place-content-center w-full gap-5 text-center"> <div className="grid place-items-center place-content-center w-full gap-5 text-center">
@ -28,7 +34,7 @@ export default function Home(props: HomeProps): JSX.Element {
</h2> </h2>
</div> </div>
{locales.includes(router.locale ?? "en") ? ( {locales.includes(router.locale ?? "en") ? (
<Markdawn text={post.translations[0].body} /> <Markdawn text={body} />
) : ( ) : (
<LanguageSwitcher <LanguageSwitcher
locales={locales} locales={locales}
@ -39,31 +45,22 @@ export default function Home(props: HomeProps): JSX.Element {
</ContentPanel> </ContentPanel>
); );
return ( return <AppLayout navTitle={title} contentPanel={contentPanel} {...props} />;
<AppLayout
navTitle={
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
contentPanel={contentPanel}
{...props}
/>
);
} }
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: HomeProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = "home"; const slug = "home";
const props: HomeProps = { const post = await sdk.getPost({
slug: slug,
language_code: context.locale ?? "en",
});
if (!post.posts) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
post: ( post: post.posts.data[0].attributes,
await getPost({
slug: slug,
language_code: context.locale ?? "en",
})
).posts.data[0].attributes,
}; };
return { return {
props: props, props: props,

View File

@ -15,12 +15,12 @@ import ContentPanel, {
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { getLibraryItem, getLibraryItemsSlugs } from "graphql/operations";
import { import {
Enum_Componentmetadatabooks_Binding_Type, Enum_Componentmetadatabooks_Binding_Type,
Enum_Componentmetadatabooks_Page_Order, Enum_Componentmetadatabooks_Page_Order,
GetLibraryItemQuery, GetLibraryItemQuery,
} from "graphql/operations-types"; } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { import {
GetStaticPathsContext, GetStaticPathsContext,
GetStaticPathsResult, GetStaticPathsResult,
@ -41,22 +41,27 @@ import {
} from "queries/helpers"; } from "queries/helpers";
import { useState } from "react"; import { useState } from "react";
interface LibrarySlugProps extends AppStaticProps { interface Props extends AppStaticProps {
item: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"]; item: Exclude<
itemId: GetLibraryItemQuery["libraryItems"]["data"][number]["id"]; GetLibraryItemQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"];
itemId: Exclude<
GetLibraryItemQuery["libraryItems"],
null | undefined
>["data"][number]["id"];
} }
export default function LibrarySlug(props: LibrarySlugProps): JSX.Element { export default function LibrarySlug(props: Props): JSX.Element {
useTesting(props); useTesting(props);
const { item, langui, currencies } = props; const { item, langui, currencies } = props;
const appLayout = useAppLayout(); const appLayout = useAppLayout();
const isVariantSet = const isVariantSet =
item.metadata.length > 0 && item?.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
item.metadata[0].__typename === "ComponentMetadataGroup" && item.metadata[0].subtype?.data?.attributes?.slug === "variant-set";
item.metadata[0].subtype.data.attributes.slug === "variant-set";
sortContent(item.contents); sortContent(item?.contents);
const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxImages, setLightboxImages] = useState([""]); const [lightboxImages, setLightboxImages] = useState([""]);
@ -80,7 +85,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
onClick={() => appLayout.setSubPanelOpen(false)} onClick={() => appLayout.setSubPanelOpen(false)}
/> />
{item.gallery.data.length > 0 && ( {item?.gallery && item.gallery.data.length > 0 && (
<NavOption <NavOption
title={langui.gallery} title={langui.gallery}
url="#gallery" url="#gallery"
@ -96,7 +101,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
onClick={() => appLayout.setSubPanelOpen(false)} onClick={() => appLayout.setSubPanelOpen(false)}
/> />
{item.subitems.data.length > 0 && ( {item?.subitems && item.subitems.data.length > 0 && (
<NavOption <NavOption
title={isVariantSet ? langui.variants : langui.subitems} title={isVariantSet ? langui.variants : langui.subitems}
url={isVariantSet ? "#variants" : "#subitems"} url={isVariantSet ? "#variants" : "#subitems"}
@ -105,7 +110,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
/> />
)} )}
{item.contents.data.length > 0 && ( {item?.contents && item.contents.data.length > 0 && (
<NavOption <NavOption
title={langui.contents} title={langui.contents}
url="#contents" url="#contents"
@ -138,17 +143,19 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
<div <div
className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[60vh] desktop:mb-16 relative cursor-pointer" className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[60vh] desktop:mb-16 relative cursor-pointer"
onClick={() => { onClick={() => {
setLightboxOpen(true); if (item?.thumbnail?.data?.attributes) {
setLightboxImages([ setLightboxOpen(true);
getAssetURL( setLightboxImages([
item.thumbnail.data.attributes.url, getAssetURL(
ImageQuality.Large item.thumbnail.data.attributes.url,
), ImageQuality.Large
]); ),
setLightboxIndex(0); ]);
setLightboxIndex(0);
}
}} }}
> >
{item.thumbnail.data ? ( {item?.thumbnail?.data?.attributes ? (
<Img <Img
image={item.thumbnail.data.attributes} image={item.thumbnail.data.attributes}
quality={ImageQuality.Large} quality={ImageQuality.Large}
@ -163,7 +170,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
<InsetBox id="summary" className="grid place-items-center"> <InsetBox id="summary" className="grid place-items-center">
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8"> <div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8">
{item.subitem_of.data.length > 0 && ( {item?.subitem_of?.data[0]?.attributes && (
<div className="grid place-items-center"> <div className="grid place-items-center">
<p>{langui.subitem_of}</p> <p>{langui.subitem_of}</p>
<Button <Button
@ -178,41 +185,53 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
</div> </div>
)} )}
<div className="grid place-items-center"> <div className="grid place-items-center">
<h1 className="text-3xl">{item.title}</h1> <h1 className="text-3xl">{item?.title}</h1>
{item.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>} {item?.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>}
</div> </div>
{item.descriptions.length > 0 && ( {item?.descriptions?.[0] && (
<p className="text-justify">{item.descriptions[0].description}</p> <p className="text-justify">{item.descriptions[0].description}</p>
)} )}
</div> </div>
</InsetBox> </InsetBox>
{item.gallery.data.length > 0 && ( {item?.gallery && item.gallery.data.length > 0 && (
<div id="gallery" className="grid place-items-center gap-8 w-full"> <div id="gallery" className="grid place-items-center gap-8 w-full">
<h2 className="text-2xl">{langui.gallery}</h2> <h2 className="text-2xl">{langui.gallery}</h2>
<div className="grid w-full gap-8 items-end grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"> <div className="grid w-full gap-8 items-end grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]">
{item.gallery.data.map((galleryItem, index) => ( {item.gallery.data.map((galleryItem, index) => (
<div <>
key={galleryItem.id} {galleryItem.attributes && (
className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer" <div
onClick={() => { key={galleryItem.id}
setLightboxOpen(true); className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer"
setLightboxImages( onClick={() => {
item.gallery.data.map((image) => if (item.gallery?.data) {
getAssetURL(image.attributes.url, ImageQuality.Large) const images: string[] = [];
) item.gallery.data.map((image) => {
); if (image.attributes)
setLightboxIndex(index); images.push(
}} getAssetURL(
> image.attributes.url,
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div> ImageQuality.Large
<Img )
className="rounded-lg" );
image={galleryItem.attributes} });
layout="fill" setLightboxOpen(true);
objectFit="cover" setLightboxImages(images);
/> setLightboxIndex(index);
</div> }
}}
>
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
<Img
className="rounded-lg"
image={galleryItem.attributes}
layout="fill"
objectFit="cover"
/>
</div>
)}
</>
))} ))}
</div> </div>
</div> </div>
@ -222,7 +241,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
<div className="w-[clamp(0px,100%,42rem)] grid place-items gap-8"> <div className="w-[clamp(0px,100%,42rem)] grid place-items gap-8">
<h2 className="text-2xl text-center">{langui.details}</h2> <h2 className="text-2xl text-center">{langui.details}</h2>
<div className="grid grid-flow-col w-full place-content-between"> <div className="grid grid-flow-col w-full place-content-between">
{item.metadata.length > 0 && ( {item?.metadata?.[0] && (
<div className="grid place-items-center place-content-start"> <div className="grid place-items-center place-content-start">
<h3 className="text-xl">{langui.type}</h3> <h3 className="text-xl">{langui.type}</h3>
<div className="grid grid-flow-col gap-1"> <div className="grid grid-flow-col gap-1">
@ -233,24 +252,24 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
</div> </div>
)} )}
{item.release_date && ( {item?.release_date && (
<div className="grid place-items-center place-content-start"> <div className="grid place-items-center place-content-start">
<h3 className="text-xl">{langui.release_date}</h3> <h3 className="text-xl">{langui.release_date}</h3>
<p>{prettyDate(item.release_date)}</p> <p>{prettyDate(item.release_date)}</p>
</div> </div>
)} )}
{item.price && ( {item?.price && (
<div className="grid place-items-center text-center place-content-start"> <div className="grid place-items-center text-center place-content-start">
<h3 className="text-xl">{langui.price}</h3> <h3 className="text-xl">{langui.price}</h3>
<p> <p>
{prettyPrice( {prettyPrice(
item.price, item.price,
currencies, currencies,
item.price.currency.data.attributes.code item.price.currency?.data?.attributes?.code
)} )}
</p> </p>
{item.price.currency.data.attributes.code !== {item.price.currency?.data?.attributes?.code !==
appLayout.currency && ( appLayout.currency && (
<p> <p>
{prettyPrice(item.price, currencies, appLayout.currency)}{" "} {prettyPrice(item.price, currencies, appLayout.currency)}{" "}
@ -261,18 +280,18 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
)} )}
</div> </div>
{item.categories.data.length > 0 && ( {item?.categories && item.categories.data.length > 0 && (
<div className="flex flex-col place-items-center gap-2"> <div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3> <h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{item.categories.data.map((category) => ( {item.categories.data.map((category) => (
<Chip key={category.id}>{category.attributes.name}</Chip> <Chip key={category.id}>{category.attributes?.name}</Chip>
))} ))}
</div> </div>
</div> </div>
)} )}
{item.size && ( {item?.size && (
<> <>
<h3 className="text-xl">{langui.size}</h3> <h3 className="text-xl">{langui.size}</h3>
<div className="grid grid-flow-col w-full place-content-between"> <div className="grid grid-flow-col w-full place-content-between">
@ -303,13 +322,12 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
</> </>
)} )}
{item.metadata.length > 0 && {item?.metadata?.[0]?.__typename !== "ComponentMetadataGroup" &&
item.metadata[0].__typename !== "ComponentMetadataGroup" && item?.metadata?.[0]?.__typename !== "ComponentMetadataOther" && (
item.metadata[0].__typename !== "ComponentMetadataOther" && (
<> <>
<h3 className="text-xl">{langui.type_information}</h3> <h3 className="text-xl">{langui.type_information}</h3>
<div className="grid grid-cols-2 w-full place-content-between"> <div className="grid grid-cols-2 w-full place-content-between">
{item.metadata[0].__typename === {item?.metadata?.[0]?.__typename ===
"ComponentMetadataBooks" && ( "ComponentMetadataBooks" && (
<> <>
<div className="flex flex-row place-content-start gap-4"> <div className="flex flex-row place-content-start gap-4">
@ -336,18 +354,15 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
{item.metadata[0].page_order === {item.metadata[0].page_order ===
Enum_Componentmetadatabooks_Page_Order.LeftToRight Enum_Componentmetadatabooks_Page_Order.LeftToRight
? langui.left_to_right ? langui.left_to_right
: item.metadata[0].page_order === : langui.right_to_left}
Enum_Componentmetadatabooks_Page_Order.RightToLeft
? langui.right_to_left
: ""}
</p> </p>
</div> </div>
<div className="flex flex-row place-content-start gap-4"> <div className="flex flex-row place-content-start gap-4">
<p className="font-bold">{langui.languages}:</p> <p className="font-bold">{langui.languages}:</p>
{item.metadata[0].languages.data.map((lang) => ( {item.metadata[0]?.languages?.data.map((lang) => (
<p key={lang.attributes.code}> <p key={lang.attributes?.code}>
{lang.attributes.name} {lang.attributes?.name}
</p> </p>
))} ))}
</div> </div>
@ -359,7 +374,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
</div> </div>
</InsetBox> </InsetBox>
{item.subitems.data.length > 0 && ( {item?.subitems && item.subitems.data.length > 0 && (
<div <div
id={isVariantSet ? "variants" : "subitems"} id={isVariantSet ? "variants" : "subitems"}
className="grid place-items-center gap-8 w-full" className="grid place-items-center gap-8 w-full"
@ -372,13 +387,14 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
<LibraryItemsPreview <LibraryItemsPreview
key={subitem.id} key={subitem.id}
item={subitem.attributes} item={subitem.attributes}
currencies={props.currencies}
/> />
))} ))}
</div> </div>
</div> </div>
)} )}
{item.contents.data.length > 0 && ( {item?.contents && item.contents.data.length > 0 && (
<div id="contents" className="w-full grid place-items-center gap-8"> <div id="contents" className="w-full grid place-items-center gap-8">
<h2 className="text-2xl">{langui.contents}</h2> <h2 className="text-2xl">{langui.contents}</h2>
<div className="grid gap-4 w-full"> <div className="grid gap-4 w-full">
@ -399,15 +415,11 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
return ( return (
<AppLayout <AppLayout
navTitle={prettyinlineTitle("", item.title, item.subtitle)} navTitle={prettyinlineTitle("", item?.title, item?.subtitle)}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={item.thumbnail.data?.attributes} thumbnail={item?.thumbnail?.data?.attributes ?? undefined}
description={ description={item?.descriptions?.[0]?.description ?? undefined}
item.descriptions.length > 0
? item.descriptions[0].description
: undefined
}
{...props} {...props}
/> />
); );
@ -415,18 +427,17 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: LibrarySlugProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const item = ( const sdk = getReadySdk();
await getLibraryItem({ const item = await sdk.getLibraryItem({
slug: context.params?.slug?.toString() ?? "", slug: context.params?.slug ? context.params.slug.toString() : "",
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
}) });
).libraryItems.data[0]; if (!item.libraryItems) return { notFound: true };
if (!item) return { notFound: true }; const props: Props = {
const props: LibrarySlugProps = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
item: item.attributes, item: item.libraryItems.data[0].attributes,
itemId: item.id, itemId: item.libraryItems.data[0].id,
}; };
return { return {
props: props, props: props,
@ -436,29 +447,32 @@ export async function getStaticProps(
export async function getStaticPaths( export async function getStaticPaths(
context: GetStaticPathsContext context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { ): Promise<GetStaticPathsResult> {
const libraryItems = await getLibraryItemsSlugs({}); const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
libraryItems.libraryItems.data.map((item) => { if (libraryItems.libraryItems) {
context.locales?.map((local) => { libraryItems.libraryItems.data.map((item) => {
paths.push({ params: { slug: item.attributes.slug }, locale: local }); context.locales?.map((local) => {
paths.push({ params: { slug: item.attributes?.slug }, locale: local });
});
}); });
}); }
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",
}; };
} }
function useTesting(props: LibrarySlugProps) { function useTesting(props: Props) {
const { item, itemId } = props; const { item, itemId } = props;
const router = useRouter(); const router = useRouter();
const libraryItemURL = `/admin/content-manager/collectionType/api::library-item.library-item/${itemId}`; const libraryItemURL = `/admin/content-manager/collectionType/api::library-item.library-item/${itemId}`;
sortContent(item.contents); sortContent(item?.contents);
if (router.locale === "en") { if (router.locale === "en") {
if (!item.thumbnail.data) { if (!item?.thumbnail?.data) {
prettyTestError( prettyTestError(
router, router,
"Missing thumbnail", "Missing thumbnail",
@ -466,7 +480,7 @@ function useTesting(props: LibrarySlugProps) {
libraryItemURL libraryItemURL
); );
} }
if (item.metadata.length === 0) { if (item?.metadata?.length === 0) {
prettyTestError( prettyTestError(
router, router,
"Missing metadata", "Missing metadata",
@ -474,9 +488,9 @@ function useTesting(props: LibrarySlugProps) {
libraryItemURL libraryItemURL
); );
} else if ( } else if (
item.metadata[0].__typename === "ComponentMetadataGroup" && item?.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
(item.metadata[0].subtype.data.attributes.slug === "relation-set" || (item.metadata[0].subtype?.data?.attributes?.slug === "relation-set" ||
item.metadata[0].subtype.data.attributes.slug === "variant-set") item.metadata[0].subtype?.data?.attributes?.slug === "variant-set")
) { ) {
// This is a group type item // This is a group type item
if (item.price) { if (item.price) {
@ -503,7 +517,7 @@ function useTesting(props: LibrarySlugProps) {
libraryItemURL libraryItemURL
); );
} }
if (item.contents.data.length > 0) { if (item.contents && item.contents.data.length > 0) {
prettyTestError( prettyTestError(
router, router,
"Group-type items shouldn't have contents", "Group-type items shouldn't have contents",
@ -511,7 +525,7 @@ function useTesting(props: LibrarySlugProps) {
libraryItemURL libraryItemURL
); );
} }
if (item.subitems.data.length === 0) { if (item.subitems && item.subitems.data.length === 0) {
prettyTestError( prettyTestError(
router, router,
"Group-type items should have subitems", "Group-type items should have subitems",
@ -522,8 +536,8 @@ function useTesting(props: LibrarySlugProps) {
} else { } else {
// This is a normal item // This is a normal item
if (item.metadata[0].__typename === "ComponentMetadataGroup") { if (item?.metadata?.[0]?.__typename === "ComponentMetadataGroup") {
if (item.subitems.data.length === 0) { if (item.subitems?.data.length === 0) {
prettyTestError( prettyTestError(
router, router,
"Group-type item should have subitems", "Group-type item should have subitems",
@ -533,7 +547,7 @@ function useTesting(props: LibrarySlugProps) {
} }
} }
if (item.price) { if (item?.price) {
if (!item.price.amount) { if (!item.price.amount) {
prettyTestError( prettyTestError(
router, router,
@ -559,8 +573,8 @@ function useTesting(props: LibrarySlugProps) {
); );
} }
if (!item.digital) { if (!item?.digital) {
if (item.size) { if (item?.size) {
if (!item.size.width) { if (!item.size.width) {
prettyTestWarning( prettyTestWarning(
router, router,
@ -595,7 +609,7 @@ function useTesting(props: LibrarySlugProps) {
} }
} }
if (item.release_date) { if (item?.release_date) {
if (!item.release_date.year) { if (!item.release_date.year) {
prettyTestError( prettyTestError(
router, router,
@ -629,7 +643,7 @@ function useTesting(props: LibrarySlugProps) {
); );
} }
if (item.contents.data.length === 0) { if (item?.contents?.data.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing contents", "Missing contents",
@ -638,26 +652,27 @@ function useTesting(props: LibrarySlugProps) {
); );
} else { } else {
let currentRangePage = 0; let currentRangePage = 0;
item.contents.data.map((content) => { item?.contents?.data.map((content) => {
const contentURL = `/admin/content-manager/collectionType/api::content.content/${content.id}`; const contentURL = `/admin/content-manager/collectionType/api::content.content/${content.id}`;
if (content.attributes.scan_set.length === 0) { if (content.attributes?.scan_set?.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing scan_set", "Missing scan_set",
["libraryItem", "content", content.id], ["libraryItem", "content", content.id ?? ""],
contentURL contentURL
); );
} }
if (content.attributes.range.length === 0) { if (content.attributes?.range.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing range", "Missing range",
["libraryItem", "content", content.id], ["libraryItem", "content", content.id ?? ""],
contentURL contentURL
); );
} else if ( } else if (
content.attributes.range[0].__typename === "ComponentRangePageRange" content.attributes?.range[0]?.__typename ===
"ComponentRangePageRange"
) { ) {
if ( if (
content.attributes.range[0].starting_page < content.attributes.range[0].starting_page <
@ -666,7 +681,7 @@ function useTesting(props: LibrarySlugProps) {
prettyTestError( prettyTestError(
router, router,
`Overlapping pages ${content.attributes.range[0].starting_page} to ${currentRangePage}`, `Overlapping pages ${content.attributes.range[0].starting_page} to ${currentRangePage}`,
["libraryItem", "content", content.id, "range"], ["libraryItem", "content", content.id ?? "", "range"],
libraryItemURL libraryItemURL
); );
} else if ( } else if (
@ -678,16 +693,16 @@ function useTesting(props: LibrarySlugProps) {
`Missing pages ${currentRangePage + 1} to ${ `Missing pages ${currentRangePage + 1} to ${
content.attributes.range[0].starting_page - 1 content.attributes.range[0].starting_page - 1
}`, }`,
["libraryItem", "content", content.id, "range"], ["libraryItem", "content", content.id ?? "", "range"],
libraryItemURL libraryItemURL
); );
} }
if (!content.attributes.content.data) { if (!content.attributes.content?.data) {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing content", "Missing content",
["libraryItem", "content", content.id, "range"], ["libraryItem", "content", content.id ?? "", "range"],
libraryItemURL libraryItemURL
); );
} }
@ -696,26 +711,8 @@ function useTesting(props: LibrarySlugProps) {
} }
}); });
if (item.metadata[0].__typename === "ComponentMetadataBooks") { if (item?.metadata?.[0]?.__typename === "ComponentMetadataBooks") {
if (currentRangePage < item.metadata[0].page_count) { if (item.metadata[0].languages?.data.length === 0) {
prettyTestError(
router,
`Missing pages ${currentRangePage + 1} to ${
item.metadata[0].page_count
}`,
["libraryItem", "content"],
libraryItemURL
);
} else if (currentRangePage > item.metadata[0].page_count) {
prettyTestError(
router,
`Page overflow, content references pages up to ${currentRangePage} when the highest expected was ${item.metadata[0].page_count}`,
["libraryItem", "content"],
libraryItemURL
);
}
if (item.metadata[0].languages.data.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing language", "Missing language",
@ -724,7 +721,25 @@ function useTesting(props: LibrarySlugProps) {
); );
} }
if (!item.metadata[0].page_count) { if (item.metadata[0].page_count) {
if (currentRangePage < item.metadata[0].page_count) {
prettyTestError(
router,
`Missing pages ${currentRangePage + 1} to ${
item.metadata[0].page_count
}`,
["libraryItem", "content"],
libraryItemURL
);
} else if (currentRangePage > item.metadata[0].page_count) {
prettyTestError(
router,
`Page overflow, content references pages up to ${currentRangePage} when the highest expected was ${item.metadata[0].page_count}`,
["libraryItem", "content"],
libraryItemURL
);
}
} else {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing page_count", "Missing page_count",
@ -736,7 +751,7 @@ function useTesting(props: LibrarySlugProps) {
} }
} }
if (!item.root_item && item.subitem_of.data.length === 0) { if (!item?.root_item && item?.subitem_of?.data.length === 0) {
prettyTestError( prettyTestError(
router, router,
"This item is inaccessible (not root item and not subitem of another item)", "This item is inaccessible (not root item and not subitem of another item)",
@ -745,7 +760,7 @@ function useTesting(props: LibrarySlugProps) {
); );
} }
if (item.gallery.data.length === 0) { if (item?.gallery?.data.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing gallery", "Missing gallery",
@ -755,7 +770,7 @@ function useTesting(props: LibrarySlugProps) {
} }
} }
if (item.descriptions.length === 0) { if (item?.descriptions?.length === 0) {
prettyTestWarning( prettyTestWarning(
router, router,
"Missing description", "Missing description",

View File

@ -11,8 +11,8 @@ import ContentPanel, {
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { getLibraryItemScans, getLibraryItemsSlugs } from "graphql/operations"; import { GetLibraryItemScansQuery } from "graphql/generated";
import { GetLibraryItemScansQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { import {
GetStaticPathsContext, GetStaticPathsContext,
GetStaticPathsResult, GetStaticPathsResult,
@ -23,15 +23,21 @@ import { prettyinlineTitle, prettySlug, sortContent } from "queries/helpers";
import { useState } from "react"; import { useState } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
item: GetLibraryItemScansQuery["libraryItems"]["data"][number]["attributes"]; item: Exclude<
itemId: GetLibraryItemScansQuery["libraryItems"]["data"][number]["id"]; GetLibraryItemScansQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"];
itemId: Exclude<
GetLibraryItemScansQuery["libraryItems"],
null | undefined
>["data"][number]["id"];
} }
export default function LibrarySlug(props: Props): JSX.Element { export default function LibrarySlug(props: Props): JSX.Element {
const { item, langui } = props; const { item, langui } = props;
const appLayout = useAppLayout(); const appLayout = useAppLayout();
sortContent(item.contents); sortContent(item?.contents);
const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxImages, setLightboxImages] = useState([""]); const [lightboxImages, setLightboxImages] = useState([""]);
@ -40,21 +46,21 @@ export default function LibrarySlug(props: Props): JSX.Element {
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
<ReturnButton <ReturnButton
href={`/library/${item.slug}`} href={`/library/${item?.slug}`}
title={langui.item} title={langui.item}
langui={langui} langui={langui}
displayOn={ReturnButtonType.desktop} displayOn={ReturnButtonType.desktop}
horizontalLine horizontalLine
/> />
{item.contents.data.map((content) => ( {item?.contents?.data.map((content) => (
<NavOption <NavOption
key={content.id} key={content.id}
url={`#${content.attributes.slug}`} url={`#${content.attributes?.slug}`}
title={prettySlug(content.attributes.slug, item.slug)} title={prettySlug(content.attributes?.slug, item.slug)}
subtitle={ subtitle={
content.attributes.range.length > 0 && content.attributes?.range[0]?.__typename ===
content.attributes.range[0].__typename === "ComponentRangePageRange" "ComponentRangePageRange"
? `${content.attributes.range[0].starting_page}${content.attributes.range[0].ending_page}` ? `${content.attributes.range[0].starting_page}${content.attributes.range[0].ending_page}`
: undefined : undefined
} }
@ -76,55 +82,68 @@ export default function LibrarySlug(props: Props): JSX.Element {
/> />
<ReturnButton <ReturnButton
href={`/library/${item.slug}`} href={`/library/${item?.slug}`}
title={langui.item} title={langui.item}
langui={langui} langui={langui}
displayOn={ReturnButtonType.mobile} displayOn={ReturnButtonType.mobile}
className="mb-10" className="mb-10"
/> />
{item.contents.data.map((content) => ( {item?.contents?.data.map((content) => (
<> <>
<h2 <h2
id={content.attributes.slug} id={content.attributes?.slug}
key={`h2${content.id}`} key={`h2${content.id}`}
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2" className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
> >
{prettySlug(content.attributes.slug, item.slug)} {prettySlug(content.attributes?.slug, item.slug)}
</h2> </h2>
{content.attributes.scan_set.length > 0 ? ( {content.attributes?.scan_set?.[0] ? (
<div <div
key={`items${content.id}`} key={`items${content.id}`}
className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0" className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0"
> >
{content.attributes.scan_set[0].pages.data.map((page, index) => ( {content.attributes.scan_set[0].pages?.data.map((page, index) => (
<div <div
key={page.id} key={page.id}
className="drop-shadow-shade-lg hover:scale-[1.02] cursor-pointer transition-transform" className="drop-shadow-shade-lg hover:scale-[1.02] cursor-pointer transition-transform"
onClick={() => { onClick={() => {
setLightboxOpen(true); setLightboxOpen(true);
setLightboxImages( if (content.attributes?.scan_set?.[0]?.pages) {
content.attributes.scan_set[0].pages.data.map((image) => const images: string[] = [];
getAssetURL(image.attributes.url, ImageQuality.Large) content.attributes.scan_set[0].pages.data.map((image) => {
) if (image.attributes?.url)
); images.push(
getAssetURL(
image.attributes.url,
ImageQuality.Large
)
);
});
setLightboxImages(images);
}
setLightboxIndex(index); setLightboxIndex(index);
}} }}
> >
<Img image={page.attributes} quality={ImageQuality.Small} /> {page.attributes && (
<Img image={page.attributes} quality={ImageQuality.Small} />
)}
</div> </div>
))} ))}
</div> </div>
) : ( ) : (
<div className="pb-12 border-b-[3px] border-dotted last-of-type:border-0"> <div className="pb-12 border-b-[3px] border-dotted last-of-type:border-0">
<LanguageSwitcher {content.attributes?.scan_set_languages && (
locales={content.attributes.scan_set_languages.map( <LanguageSwitcher
(language) => language.language.data.attributes.code locales={content.attributes.scan_set_languages.map(
)} (language) => language?.language?.data?.attributes?.code
languages={props.languages} )}
langui={props.langui} languages={props.languages}
href={`#${content.attributes.slug}`} langui={props.langui}
/> href={`#${content.attributes.slug}`}
/>
)}
</div> </div>
)} )}
</> </>
@ -134,10 +153,10 @@ export default function LibrarySlug(props: Props): JSX.Element {
return ( return (
<AppLayout <AppLayout
navTitle={prettyinlineTitle("", item.title, item.subtitle)} navTitle={prettyinlineTitle("", item?.title, item?.subtitle)}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={item.thumbnail.data?.attributes} thumbnail={item?.thumbnail?.data?.attributes ?? undefined}
{...props} {...props}
/> />
); );
@ -146,17 +165,16 @@ export default function LibrarySlug(props: Props): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const item = ( const sdk = getReadySdk();
await getLibraryItemScans({ const item = await sdk.getLibraryItemScans({
slug: context.params?.slug?.toString() ?? "", slug: context.params?.slug?.toString() ?? "",
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
}) });
).libraryItems.data[0]; if (!item.libraryItems) return { notFound: true };
if (!item) return { notFound: true };
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
item: item.attributes, item: item.libraryItems.data[0].attributes,
itemId: item.id, itemId: item.libraryItems.data[0].id,
}; };
return { return {
props: props, props: props,
@ -166,13 +184,17 @@ export async function getStaticProps(
export async function getStaticPaths( export async function getStaticPaths(
context: GetStaticPathsContext context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { ): Promise<GetStaticPathsResult> {
const libraryItems = await getLibraryItemsSlugs({}); const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs({});
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
libraryItems.libraryItems.data.map((item) => { if (libraryItems.libraryItems) {
context.locales?.map((local) => { libraryItems.libraryItems.data.map((item) => {
paths.push({ params: { slug: item.attributes.slug }, locale: local }); context.locales?.map((local) => {
if (item.attributes)
paths.push({ params: { slug: item.attributes.slug }, locale: local });
});
}); });
}); }
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

View File

@ -8,27 +8,23 @@ import ContentPanel, {
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import Select from "components/Select"; import Select from "components/Select";
import Switch from "components/Switch"; import Switch from "components/Switch";
import { getLibraryItemsPreview } from "graphql/operations"; import { GetLibraryItemsPreviewQuery } from "graphql/generated";
import { import { getReadySdk } from "graphql/sdk";
GetCurrenciesQuery,
GetLibraryItemsPreviewQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { convertPrice, prettyDate, prettyinlineTitle } from "queries/helpers"; import { convertPrice, prettyDate, prettyinlineTitle } from "queries/helpers";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
interface LibraryProps extends AppStaticProps { interface Props extends AppStaticProps {
items: GetLibraryItemsPreviewQuery["libraryItems"]["data"]; items: Exclude<
GetLibraryItemsPreviewQuery["libraryItems"],
null | undefined
>["data"];
} }
type GroupLibraryItems = Map< type GroupLibraryItems = Map<string, Props["items"]>;
string,
GetLibraryItemsPreviewQuery["libraryItems"]["data"]
>;
export default function Library(props: LibraryProps): JSX.Element { export default function Library(props: Props): JSX.Element {
const { langui, items: libraryItems, currencies } = props; const { langui, items: libraryItems, currencies } = props;
const [showSubitems, setShowSubitems] = useState<boolean>(false); const [showSubitems, setShowSubitems] = useState<boolean>(false);
@ -37,7 +33,7 @@ export default function Library(props: LibraryProps): JSX.Element {
const [sortingMethod, setSortingMethod] = useState<number>(0); const [sortingMethod, setSortingMethod] = useState<number>(0);
const [groupingMethod, setGroupingMethod] = useState<number>(-1); const [groupingMethod, setGroupingMethod] = useState<number>(-1);
const [filteredItems, setFilteredItems] = useState<LibraryProps["items"]>( const [filteredItems, setFilteredItems] = useState<Props["items"]>(
filterItems( filterItems(
showSubitems, showSubitems,
showPrimaryItems, showPrimaryItems,
@ -46,7 +42,7 @@ export default function Library(props: LibraryProps): JSX.Element {
) )
); );
const [sortedItems, setSortedItem] = useState<LibraryProps["items"]>( const [sortedItems, setSortedItem] = useState<Props["items"]>(
sortBy(groupingMethod, filteredItems, currencies) sortBy(groupingMethod, filteredItems, currencies)
); );
@ -85,7 +81,11 @@ export default function Library(props: LibraryProps): JSX.Element {
<p className="flex-shrink-0">{langui.group_by}:</p> <p className="flex-shrink-0">{langui.group_by}:</p>
<Select <Select
className="w-full" className="w-full"
options={[langui.category, langui.type, langui.release_year]} options={[
langui.category ?? "Category",
langui.type ?? "Type",
langui.release_year ?? "Year",
]}
state={groupingMethod} state={groupingMethod}
setState={setGroupingMethod} setState={setGroupingMethod}
allowEmpty allowEmpty
@ -96,7 +96,11 @@ export default function Library(props: LibraryProps): JSX.Element {
<p className="flex-shrink-0">{langui.order_by}:</p> <p className="flex-shrink-0">{langui.order_by}:</p>
<Select <Select
className="w-full" className="w-full"
options={[langui.name, langui.price, langui.release_date]} options={[
langui.name ?? "Name",
langui.price ?? "Price",
langui.release_date ?? "Release date",
]}
state={sortingMethod} state={sortingMethod}
setState={setSortingMethod} setState={setSortingMethod}
/> />
@ -132,8 +136,8 @@ export default function Library(props: LibraryProps): JSX.Element {
{name} {name}
<Chip>{`${items.length} ${ <Chip>{`${items.length} ${
items.length <= 1 items.length <= 1
? langui.result.toLowerCase() ? langui.result?.toLowerCase() ?? "result"
: langui.results.toLowerCase() : langui.results?.toLowerCase() ?? "results"
}`}</Chip> }`}</Chip>
</h2> </h2>
)} )}
@ -167,14 +171,15 @@ export default function Library(props: LibraryProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: LibraryProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const props: LibraryProps = { const sdk = getReadySdk();
const items = await sdk.getLibraryItemsPreview({
language_code: context.locale ?? "en",
});
if (!items.libraryItems) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
items: ( items: items.libraryItems.data,
await getLibraryItemsPreview({
language_code: context.locale ?? "en",
})
).libraryItems.data,
}; };
return { return {
props: props, props: props,
@ -182,9 +187,9 @@ export async function getStaticProps(
} }
function getGroups( function getGroups(
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"], langui: AppStaticProps["langui"],
groupByType: number, groupByType: number,
items: LibraryProps["items"] items: Props["items"]
): GroupLibraryItems { ): GroupLibraryItems {
switch (groupByType) { switch (groupByType) {
case 0: { case 0: {
@ -209,11 +214,11 @@ function getGroups(
typeGroup.set(langui.no_category, []); typeGroup.set(langui.no_category, []);
items.map((item) => { items.map((item) => {
if (item.attributes.categories.data.length === 0) { if (item.attributes?.categories?.data.length === 0) {
typeGroup.get(langui.no_category)?.push(item); typeGroup.get(langui.no_category)?.push(item);
} else { } else {
item.attributes.categories.data.map((category) => { item.attributes?.categories?.data.map((category) => {
typeGroup.get(category.attributes.name)?.push(item); typeGroup.get(category.attributes?.name)?.push(item);
}); });
} }
}); });
@ -223,49 +228,50 @@ function getGroups(
case 1: { case 1: {
const group: GroupLibraryItems = new Map(); const group: GroupLibraryItems = new Map();
group.set(langui.audio, []); group.set(langui.audio ?? "Audio", []);
group.set(langui.game, []); group.set(langui.game ?? "Game", []);
group.set(langui.textual, []); group.set(langui.textual ?? "Textual", []);
group.set(langui.video, []); group.set(langui.video ?? "Video", []);
group.set(langui.other, []); group.set(langui.other ?? "Other", []);
group.set(langui.group, []); group.set(langui.group ?? "Group", []);
group.set(langui.no_type, []); group.set(langui.no_type ?? "No type", []);
items.map((item) => { items.map((item) => {
if (item.attributes.metadata.length > 0) { if (item.attributes?.metadata && item.attributes.metadata.length > 0) {
switch (item.attributes.metadata[0].__typename) { switch (item.attributes.metadata[0]?.__typename) {
case "ComponentMetadataAudio": case "ComponentMetadataAudio":
group.get(langui.audio)?.push(item); group.get(langui.audio ?? "Audio")?.push(item);
break; break;
case "ComponentMetadataGame": case "ComponentMetadataGame":
group.get(langui.game)?.push(item); group.get(langui.game ?? "Game")?.push(item);
break; break;
case "ComponentMetadataBooks": case "ComponentMetadataBooks":
group.get(langui.textual)?.push(item); group.get(langui.textual ?? "Textual")?.push(item);
break; break;
case "ComponentMetadataVideo": case "ComponentMetadataVideo":
group.get(langui.video)?.push(item); group.get(langui.video ?? "Video")?.push(item);
break; break;
case "ComponentMetadataOther": case "ComponentMetadataOther":
group.get(langui.other)?.push(item); group.get(langui.other ?? "Other")?.push(item);
break; break;
case "ComponentMetadataGroup": case "ComponentMetadataGroup":
switch ( switch (
item.attributes.metadata[0].subitems_type.data.attributes.slug item.attributes.metadata[0]?.subitems_type?.data?.attributes
?.slug
) { ) {
case "audio": case "audio":
group.get(langui.audio)?.push(item); group.get(langui.audio ?? "Audio")?.push(item);
break; break;
case "video": case "video":
group.get(langui.video)?.push(item); group.get(langui.video ?? "Video")?.push(item);
break; break;
case "game": case "game":
group.get(langui.game)?.push(item); group.get(langui.game ?? "Game")?.push(item);
break; break;
case "textual": case "textual":
group.get(langui.textual)?.push(item); group.get(langui.textual ?? "Textual")?.push(item);
break; break;
case "mixed": case "mixed":
group.get(langui.group)?.push(item); group.get(langui.group ?? "Group")?.push(item);
break; break;
default: { default: {
throw new Error( throw new Error(
@ -279,7 +285,7 @@ function getGroups(
} }
} }
} else { } else {
group.get(langui.no_type)?.push(item); group.get(langui.no_type ?? "No type")?.push(item);
} }
}); });
return group; return group;
@ -288,7 +294,7 @@ function getGroups(
case 2: { case 2: {
const years: number[] = []; const years: number[] = [];
items.map((item) => { items.map((item) => {
if (item.attributes.release_date) { if (item.attributes?.release_date?.year) {
if (!years.includes(item.attributes.release_date.year)) if (!years.includes(item.attributes.release_date.year))
years.push(item.attributes.release_date.year); years.push(item.attributes.release_date.year);
} }
@ -298,12 +304,12 @@ function getGroups(
years.map((year) => { years.map((year) => {
group.set(year.toString(), []); group.set(year.toString(), []);
}); });
group.set(langui.no_year, []); group.set(langui.no_year || "No year", []);
items.map((item) => { items.map((item) => {
if (item.attributes.release_date) { if (item.attributes?.release_date?.year) {
group.get(item.attributes.release_date.year.toString())?.push(item); group.get(item.attributes.release_date.year.toString())?.push(item);
} else { } else {
group.get(langui.no_year)?.push(item); group.get(langui.no_year || "No year")?.push(item);
} }
}); });
@ -322,66 +328,63 @@ function filterItems(
showSubitems: boolean, showSubitems: boolean,
showPrimaryItems: boolean, showPrimaryItems: boolean,
showSecondaryItems: boolean, showSecondaryItems: boolean,
items: LibraryProps["items"] items: Props["items"]
): LibraryProps["items"] { ): Props["items"] {
return [...items].filter((item) => { return [...items].filter((item) => {
if (!showSubitems && !item.attributes.root_item) return false; if (!showSubitems && !item.attributes?.root_item) return false;
if ( if (
showSubitems && showSubitems &&
item.attributes.metadata.length > 0 && item.attributes?.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
item.attributes.metadata[0].__typename === "ComponentMetadataGroup" && (item.attributes.metadata[0].subtype?.data?.attributes?.slug ===
(item.attributes.metadata[0].subtype.data.attributes.slug ===
"variant-set" || "variant-set" ||
item.attributes.metadata[0].subtype.data.attributes.slug === item.attributes.metadata[0].subtype?.data?.attributes?.slug ===
"relation-set") "relation-set")
) )
return false; return false;
if (item.attributes.primary && !showPrimaryItems) return false; if (item.attributes?.primary && !showPrimaryItems) return false;
if (!item.attributes.primary && !showSecondaryItems) return false; if (!item.attributes?.primary && !showSecondaryItems) return false;
return true; return true;
}); });
} }
function sortBy( function sortBy(
orderByType: number, orderByType: number,
items: LibraryProps["items"], items: Props["items"],
currencies: GetCurrenciesQuery["currencies"]["data"] currencies: AppStaticProps["currencies"]
): LibraryProps["items"] { ): Props["items"] {
switch (orderByType) { switch (orderByType) {
case 0: case 0:
return [...items].sort((a, b) => { return [...items].sort((a, b) => {
const titleA = prettyinlineTitle( const titleA = prettyinlineTitle(
"", "",
a.attributes.title, a.attributes?.title,
a.attributes.subtitle a.attributes?.subtitle
); );
const titleB = prettyinlineTitle( const titleB = prettyinlineTitle(
"", "",
b.attributes.title, b.attributes?.title,
b.attributes.subtitle b.attributes?.subtitle
); );
return titleA.localeCompare(titleB); return titleA.localeCompare(titleB);
}); });
case 1: case 1:
return [...items].sort((a, b) => { return [...items].sort((a, b) => {
const priceA = a.attributes.price const priceA = a.attributes?.price
? convertPrice(a.attributes.price, currencies[0]) ? convertPrice(a.attributes.price, currencies[0])
: 99999; : 99999;
const priceB = b.attributes.price const priceB = b.attributes?.price
? convertPrice(b.attributes.price, currencies[0]) ? convertPrice(b.attributes.price, currencies[0])
: 99999; : 99999;
return priceA - priceB; return priceA - priceB;
}); });
case 2: case 2:
return [...items].sort((a, b) => { return [...items].sort((a, b) => {
const dateA = const dateA = a.attributes?.release_date
a.attributes.release_date === null ? prettyDate(a.attributes.release_date)
? "9999" : "9999";
: prettyDate(a.attributes.release_date); const dateB = b.attributes?.release_date
const dateB = ? prettyDate(b.attributes.release_date)
b.attributes.release_date === null : "9999";
? "9999"
: prettyDate(b.attributes.release_date);
return dateA.localeCompare(dateB); return dateA.localeCompare(dateB);
}); });
default: default:

View File

@ -17,7 +17,13 @@ export default function Merch(props: MerchProps): JSX.Element {
</SubPanel> </SubPanel>
); );
return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />; return (
<AppLayout
navTitle={langui.merch}
subPanel={subPanel}
{...props}
/>
);
} }
export async function getStaticProps( export async function getStaticProps(

View File

@ -12,8 +12,8 @@ import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import RecorderChip from "components/RecorderChip"; import RecorderChip from "components/RecorderChip";
import ToolTip from "components/ToolTip"; import ToolTip from "components/ToolTip";
import { getPost, getPostsSlugs } from "graphql/operations"; import { GetPostQuery } from "graphql/generated";
import { GetPostQuery, StrapiImage } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { import {
GetStaticPathsContext, GetStaticPathsContext,
GetStaticPathsResult, GetStaticPathsResult,
@ -28,21 +28,30 @@ import {
} from "queries/helpers"; } from "queries/helpers";
interface PostProps extends AppStaticProps { interface PostProps extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"]; post: Exclude<
postId: GetPostQuery["posts"]["data"][number]["id"]; GetPostQuery["posts"],
null | undefined
>["data"][number]["attributes"];
postId: Exclude<
GetPostQuery["posts"],
null | undefined
>["data"][number]["id"];
} }
export default function LibrarySlug(props: PostProps): JSX.Element { export default function LibrarySlug(props: PostProps): JSX.Element {
const { post, langui } = props; const { post, langui } = props;
const locales = getLocalesFromLanguages(post.translations_languages); const locales = getLocalesFromLanguages(post?.translations_languages);
const router = useRouter(); const router = useRouter();
const thumbnail: StrapiImage | undefined = const thumbnail = post?.translations?.[0]?.thumbnail?.data
post.translations.length > 0 && post.translations[0].thumbnail.data ? post.translations[0].thumbnail.data.attributes
? post.translations[0].thumbnail.data.attributes : post?.thumbnail?.data
: post.thumbnail.data ? post.thumbnail.data.attributes
? post.thumbnail.data.attributes : undefined;
: undefined;
const body = post?.translations?.[0]?.body ?? "";
const title = post?.translations?.[0]?.title ?? prettySlug(post?.slug);
const except = post?.translations?.[0]?.excerpt ?? "";
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
@ -54,7 +63,7 @@ export default function LibrarySlug(props: PostProps): JSX.Element {
horizontalLine horizontalLine
/> />
{post.translations.length > 0 && ( {post?.translations?.[0] && (
<div className="grid grid-flow-col place-items-center place-content-center gap-2"> <div className="grid grid-flow-col place-items-center place-content-center gap-2">
<p className="font-headers">{langui.status}:</p> <p className="font-headers">{langui.status}:</p>
@ -67,12 +76,20 @@ export default function LibrarySlug(props: PostProps): JSX.Element {
</div> </div>
)} )}
{post.authors.data.length > 0 && ( {post?.authors && post.authors.data.length > 0 && (
<div> <div>
<p className="font-headers">{"Authors"}:</p> <p className="font-headers">{"Authors"}:</p>
<div className="grid place-items-center place-content-center gap-2"> <div className="grid place-items-center place-content-center gap-2">
{post.authors.data.map((author) => ( {post.authors.data.map((author) => (
<RecorderChip key={author.id} langui={langui} recorder={author} /> <>
{author.attributes && (
<RecorderChip
key={author.id}
langui={langui}
recorder={author.attributes}
/>
)}
</>
))} ))}
</div> </div>
</div> </div>
@ -80,12 +97,7 @@ export default function LibrarySlug(props: PostProps): JSX.Element {
<HorizontalLine /> <HorizontalLine />
{post.translations.length > 0 && post.translations[0].body && ( <TOC text={body} title={title} />
<TOC
text={post.translations[0].body}
title={post.translations[0].title}
/>
)}
</SubPanel> </SubPanel>
); );
const contentPanel = ( const contentPanel = (
@ -100,24 +112,16 @@ export default function LibrarySlug(props: PostProps): JSX.Element {
<ThumbnailHeader <ThumbnailHeader
thumbnail={thumbnail} thumbnail={thumbnail}
title={ title={title}
post.translations.length > 0 description={except}
? post.translations[0].title
: prettySlug(post.slug)
}
description={
post.translations.length > 0
? post.translations[0].excerpt
: undefined
}
langui={langui} langui={langui}
categories={post.categories} categories={post?.categories}
/> />
<HorizontalLine /> <HorizontalLine />
{locales.includes(router.locale ?? "en") ? ( {locales.includes(router.locale ?? "en") ? (
<Markdawn text={post.translations[0].body} /> <Markdawn text={body} />
) : ( ) : (
<LanguageSwitcher <LanguageSwitcher
locales={locales} locales={locales}
@ -130,14 +134,10 @@ export default function LibrarySlug(props: PostProps): JSX.Element {
return ( return (
<AppLayout <AppLayout
navTitle={ navTitle={title}
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
contentPanel={contentPanel} contentPanel={contentPanel}
subPanel={subPanel} subPanel={subPanel}
thumbnail={thumbnail} thumbnail={thumbnail ?? undefined}
{...props} {...props}
/> />
); );
@ -146,18 +146,17 @@ export default function LibrarySlug(props: PostProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: PostProps }> { ): Promise<{ notFound: boolean } | { props: PostProps }> {
const slug = context.params?.slug?.toString() ?? ""; const sdk = getReadySdk();
const post = ( const slug = context.params?.slug ? context.params.slug.toString() : "";
await getPost({ const post = await sdk.getPost({
slug: slug, slug: slug,
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
}) });
).posts.data[0]; if (!post.posts?.data[0]) return { notFound: true };
if (!post) return { notFound: true };
const props: PostProps = { const props: PostProps = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
post: post.attributes, post: post.posts.data[0].attributes,
postId: post.id, postId: post.posts.data[0].id,
}; };
return { return {
props: props, props: props,
@ -167,13 +166,16 @@ export async function getStaticProps(
export async function getStaticPaths( export async function getStaticPaths(
context: GetStaticPathsContext context: GetStaticPathsContext
): Promise<GetStaticPathsResult> { ): Promise<GetStaticPathsResult> {
const posts = await getPostsSlugs({}); const sdk = getReadySdk();
const posts = await sdk.getPostsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
posts.posts.data.map((item) => { if (posts.posts)
context.locales?.map((local) => { posts.posts.data.map((item) => {
paths.push({ params: { slug: item.attributes.slug }, locale: local }); context.locales?.map((local) => {
if (item.attributes)
paths.push({ params: { slug: item.attributes.slug }, locale: local });
});
}); });
});
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

View File

@ -5,25 +5,23 @@ import ContentPanel, {
ContentPanelWidthSizes, ContentPanelWidthSizes,
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { getPostsPreview } from "graphql/operations"; import { GetPostsPreviewQuery } from "graphql/generated";
import { GetPostsPreviewQuery } from "graphql/operations-types"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { prettyDate } from "queries/helpers"; import { prettyDate } from "queries/helpers";
interface NewsProps extends AppStaticProps { interface Props extends AppStaticProps {
posts: GetPostsPreviewQuery["posts"]["data"]; posts: Exclude<GetPostsPreviewQuery["posts"], null | undefined>["data"];
} }
export default function News(props: NewsProps): JSX.Element { export default function News(props: Props): JSX.Element {
const { langui, posts } = props; const { langui, posts } = props;
posts posts
.sort((a, b) => { .sort((a, b) => {
const dateA = const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999";
a.attributes.date === null ? "9999" : prettyDate(a.attributes.date); const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999";
const dateB =
b.attributes.date === null ? "9999" : prettyDate(b.attributes.date);
return dateA.localeCompare(dateB); return dateA.localeCompare(dateB);
}) })
.reverse(); .reverse();
@ -60,12 +58,15 @@ export default function News(props: NewsProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: NewsProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const props: NewsProps = { const sdk = getReadySdk();
const posts = await sdk.getPostsPreview({
language_code: context.locale ?? "en",
});
if (!posts.posts) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
posts: await ( posts: posts.posts.data,
await getPostsPreview({ language_code: context.locale ?? "en" })
).posts.data,
}; };
return { return {
props: props, props: props,

View File

@ -8,11 +8,8 @@ import ReturnButton, {
import ContentPanel from "components/Panels/ContentPanel"; import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { getChronologyItems, getEras } from "graphql/operations"; import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
import { import { getReadySdk } from "graphql/sdk";
GetChronologyItemsQuery,
GetErasQuery,
} from "graphql/operations-types";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
@ -22,19 +19,24 @@ import {
prettyTestWarning, prettyTestWarning,
} from "queries/helpers"; } from "queries/helpers";
interface ChronologyProps extends AppStaticProps { interface Props extends AppStaticProps {
chronologyItems: GetChronologyItemsQuery["chronologyItems"]["data"]; chronologyItems: Exclude<
chronologyEras: GetErasQuery["chronologyEras"]["data"]; GetChronologyItemsQuery["chronologyItems"],
null | undefined
>["data"];
chronologyEras: Exclude<
GetErasQuery["chronologyEras"],
null | undefined
>["data"];
} }
export default function Chronology(props: ChronologyProps): JSX.Element { export default function Chronology(props: Props): JSX.Element {
useTesting(props); useTesting(props);
const { chronologyItems, chronologyEras, langui } = props; const { chronologyItems, chronologyEras, langui } = props;
const appLayout = useAppLayout(); const appLayout = useAppLayout();
// Group by year the Chronology items // Group by year the Chronology items
const chronologyItemYearGroups: GetChronologyItemsQuery["chronologyItems"]["data"][number][][][] = const chronologyItemYearGroups: Props["chronologyItems"][number][][][] = [];
[];
chronologyEras.map(() => { chronologyEras.map(() => {
chronologyItemYearGroups.push([]); chronologyItemYearGroups.push([]);
@ -42,25 +44,28 @@ export default function Chronology(props: ChronologyProps): JSX.Element {
let currentChronologyEraIndex = 0; let currentChronologyEraIndex = 0;
chronologyItems.map((item) => { chronologyItems.map((item) => {
if ( if (item.attributes) {
item.attributes.year > if (
chronologyEras[currentChronologyEraIndex].attributes.ending_year item.attributes.year >
) { (chronologyEras[currentChronologyEraIndex].attributes?.ending_year ??
currentChronologyEraIndex += 1; 999999)
} ) {
if ( currentChronologyEraIndex += 1;
Object.prototype.hasOwnProperty.call( }
chronologyItemYearGroups[currentChronologyEraIndex], if (
item.attributes.year Object.prototype.hasOwnProperty.call(
) chronologyItemYearGroups[currentChronologyEraIndex],
) { item.attributes.year
chronologyItemYearGroups[currentChronologyEraIndex][ )
item.attributes.year ) {
].push(item); chronologyItemYearGroups[currentChronologyEraIndex][
} else { item.attributes.year
chronologyItemYearGroups[currentChronologyEraIndex][ ].push(item);
item.attributes.year } else {
] = [item]; chronologyItemYearGroups[currentChronologyEraIndex][
item.attributes.year
] = [item];
}
} }
}); });
@ -75,18 +80,24 @@ export default function Chronology(props: ChronologyProps): JSX.Element {
/> />
{chronologyEras.map((era) => ( {chronologyEras.map((era) => (
<NavOption <>
key={era.id} {era.attributes && (
url={`#${era.attributes.slug}`} <NavOption
title={ key={era.id}
era.attributes.title.length > 0 url={`#${era.attributes.slug}`}
? era.attributes.title[0].title title={
: prettySlug(era.attributes.slug) era.attributes.title &&
} era.attributes.title.length > 0 &&
subtitle={`${era.attributes.starting_year}${era.attributes.ending_year}`} era.attributes.title[0]
border ? era.attributes.title[0].title
onClick={() => appLayout.setSubPanelOpen(false)} : prettySlug(era.attributes.slug)
/> }
subtitle={`${era.attributes.starting_year}${era.attributes.ending_year}`}
border
onClick={() => appLayout.setSubPanelOpen(false)}
/>
)}
</>
))} ))}
</SubPanel> </SubPanel>
); );
@ -104,27 +115,31 @@ export default function Chronology(props: ChronologyProps): JSX.Element {
{chronologyItemYearGroups.map((era, eraIndex) => ( {chronologyItemYearGroups.map((era, eraIndex) => (
<> <>
<InsetBox <InsetBox
id={chronologyEras[eraIndex].attributes.slug} id={chronologyEras[eraIndex].attributes?.slug}
className="grid text-center my-8 gap-4" className="grid text-center my-8 gap-4"
> >
<h2 className="text-2xl"> <h2 className="text-2xl">
{chronologyEras[eraIndex].attributes.title.length > 0 {chronologyEras[eraIndex].attributes?.title?.[0]
? chronologyEras[eraIndex].attributes.title[0].title ? chronologyEras[eraIndex].attributes?.title?.[0]?.title
: prettySlug(chronologyEras[eraIndex].attributes.slug)} : prettySlug(chronologyEras[eraIndex].attributes?.slug)}
</h2> </h2>
<p className="whitespace-pre-line "> <p className="whitespace-pre-line ">
{chronologyEras[eraIndex].attributes.title.length > 0 {chronologyEras[eraIndex].attributes?.title?.[0]
? chronologyEras[eraIndex].attributes.title[0].description ? chronologyEras[eraIndex].attributes?.title?.[0]?.description
: ""} : ""}
</p> </p>
</InsetBox> </InsetBox>
{era.map((items, index) => ( {era.map((items, index) => (
<ChronologyYearComponent <>
key={`${eraIndex}-${index}`} {items[0].attributes?.year && (
year={items[0].attributes.year} <ChronologyYearComponent
items={items} key={`${eraIndex}-${index}`}
langui={langui} year={items[0].attributes.year}
/> items={items}
langui={langui}
/>
)}
</>
))} ))}
</> </>
))} ))}
@ -143,36 +158,40 @@ export default function Chronology(props: ChronologyProps): JSX.Element {
export async function getStaticProps( export async function getStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: ChronologyProps }> { ): Promise<{ notFound: boolean } | { props: Props }> {
const props: ChronologyProps = { const sdk = getReadySdk();
const chronologyItems = await sdk.getChronologyItems({
language_code: context.locale ?? "en",
});
const chronologyEras = await sdk.getEras({
language_code: context.locale ?? "en",
});
if (!chronologyItems.chronologyItems || !chronologyEras.chronologyEras)
return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
chronologyItems: ( chronologyItems: chronologyItems.chronologyItems.data,
await getChronologyItems({ chronologyEras: chronologyEras.chronologyEras.data,
language_code: context.locale ?? "en",
})
).chronologyItems.data,
chronologyEras: (await getEras({ language_code: context.locale ?? "en" }))
.chronologyEras.data,
}; };
return { return {
props: props, props: props,
}; };
} }
function useTesting(props: ChronologyProps) { function useTesting(props: Props) {
const router = useRouter(); const router = useRouter();
const { chronologyItems, chronologyEras } = props; const { chronologyItems, chronologyEras } = props;
chronologyEras.map((era) => { chronologyEras.map((era) => {
const chronologyErasURL = `/admin/content-manager/collectionType/api::chronology-era.chronology-era/${chronologyItems[0].id}`; const chronologyErasURL = `/admin/content-manager/collectionType/api::chronology-era.chronology-era/${chronologyItems[0].id}`;
if (era.attributes.title.length === 0) { if (era.attributes?.title?.length === 0) {
prettyTestError( prettyTestError(
router, router,
"Missing translation for title and description, using slug instead", "Missing translation for title and description, using slug instead",
["chronologyEras", era.attributes.slug], ["chronologyEras", era.attributes.slug],
chronologyErasURL chronologyErasURL
); );
} else if (era.attributes.title.length > 1) { } else if (era.attributes?.title && era.attributes.title.length > 1) {
prettyTestError( prettyTestError(
router, router,
"More than one title and description", "More than one title and description",
@ -180,18 +199,18 @@ function useTesting(props: ChronologyProps) {
chronologyErasURL chronologyErasURL
); );
} else { } else {
if (!era.attributes.title[0].title) if (!era.attributes?.title?.[0]?.title)
prettyTestError( prettyTestError(
router, router,
"Missing title, using slug instead", "Missing title, using slug instead",
["chronologyEras", era.attributes.slug], ["chronologyEras", era.attributes?.slug ?? ""],
chronologyErasURL chronologyErasURL
); );
if (!era.attributes.title[0].description) if (!era.attributes?.title?.[0]?.description)
prettyTestError( prettyTestError(
router, router,
"Missing description", "Missing description",
["chronologyEras", era.attributes.slug], ["chronologyEras", era.attributes?.slug ?? ""],
chronologyErasURL chronologyErasURL
); );
} }
@ -200,23 +219,23 @@ function useTesting(props: ChronologyProps) {
chronologyItems.map((item) => { chronologyItems.map((item) => {
const chronologyItemsURL = `/admin/content-manager/collectionType/api::chronology-item.chronology-item/${chronologyItems[0].id}`; const chronologyItemsURL = `/admin/content-manager/collectionType/api::chronology-item.chronology-item/${chronologyItems[0].id}`;
const date = `${item.attributes.year}/${item.attributes.month}/${item.attributes.day}`; const date = `${item.attributes?.year}/${item.attributes?.month}/${item.attributes?.day}`;
if (item.attributes.events.length > 0) { if (item.attributes?.events && item.attributes.events.length > 0) {
item.attributes.events.map((event) => { item.attributes.events.map((event) => {
if (!event.source.data) { if (!event?.source?.data) {
prettyTestError( prettyTestError(
router, router,
"No source for this event", "No source for this event",
["chronologyItems", date, event.id], ["chronologyItems", date, event?.id ?? ""],
chronologyItemsURL chronologyItemsURL
); );
} }
if (!(event.translations.length > 0)) { if (!(event?.translations && event.translations.length > 0)) {
prettyTestWarning( prettyTestWarning(
router, router,
"No translation for this event", "No translation for this event",
["chronologyItems", date, event.id], ["chronologyItems", date, event?.id ?? ""],
chronologyItemsURL chronologyItemsURL
); );
} }

View File

@ -1,39 +1,58 @@
import {
getCurrencies,
getLanguages,
getWebsiteInterface,
} from "graphql/operations";
import { import {
GetCurrenciesQuery, GetCurrenciesQuery,
GetLanguagesQuery, GetLanguagesQuery,
GetWebsiteInterfaceQuery, GetWebsiteInterfaceQuery,
} from "graphql/operations-types"; } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next"; import { GetStaticPropsContext } from "next";
export interface AppStaticProps { export interface AppStaticProps {
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; langui: Exclude<
currencies: GetCurrenciesQuery["currencies"]["data"]; Exclude<
languages: GetLanguagesQuery["languages"]["data"]; GetWebsiteInterfaceQuery["websiteInterfaces"],
null | undefined
>["data"][number]["attributes"],
null | undefined
>;
currencies: Exclude<
GetCurrenciesQuery["currencies"],
null | undefined
>["data"];
languages: Exclude<GetLanguagesQuery["languages"], null | undefined>["data"];
} }
export async function getAppStaticProps( export async function getAppStaticProps(
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<AppStaticProps> { ): Promise<AppStaticProps> {
const languages = (await getLanguages({})).languages.data; const sdk = getReadySdk();
languages.sort((a, b) => const languages = (await sdk.getLanguages()).languages;
a.attributes.localized_name.localeCompare(b.attributes.localized_name)
);
const currencies = (await getCurrencies({})).currencies.data; if (languages?.data) {
currencies.sort((a, b) => a.attributes.code.localeCompare(b.attributes.code)); languages.data.sort((a, b) =>
a.attributes && b.attributes
? a.attributes.localized_name.localeCompare(b.attributes.localized_name)
: 0
);
}
const currencies = (await sdk.getCurrencies()).currencies;
if (currencies?.data) {
currencies.data.sort((a, b) =>
a.attributes && b.attributes
? a.attributes.code.localeCompare(b.attributes.code)
: 0
);
}
const langui = (
await sdk.getWebsiteInterface({
language_code: context.locale ?? "en",
})
).websiteInterfaces?.data[0].attributes;
return { return {
langui: ( langui: langui ?? ({} as AppStaticProps["langui"]),
await getWebsiteInterface({ currencies: currencies?.data ?? ({} as AppStaticProps["currencies"]),
language_code: context.locale ?? "en", languages: languages?.data ?? ({} as AppStaticProps["languages"]),
})
).websiteInterfaces.data[0].attributes,
currencies: currencies,
languages: languages,
}; };
} }

View File

@ -4,33 +4,36 @@ import {
ImageQuality, ImageQuality,
} from "components/Img"; } from "components/Img";
import { import {
DatePickerFragment,
Enum_Componentsetstextset_Status, Enum_Componentsetstextset_Status,
GetCurrenciesQuery, GetCurrenciesQuery,
GetLanguagesQuery,
GetLibraryItemQuery, GetLibraryItemQuery,
GetLibraryItemsPreviewQuery, GetLibraryItemScansQuery,
GetWebsiteInterfaceQuery, PricePickerFragment,
StrapiImage, UploadImageFragment,
} from "graphql/operations-types"; } from "graphql/generated";
import { NextRouter } from "next/router"; import { NextRouter } from "next/router";
import { AppStaticProps } from "./getAppStaticProps";
export function prettyDate( export function prettyDate(datePicker: DatePickerFragment): string {
datePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["release_date"] let result = "";
): string { if (datePicker.year) result += datePicker.year.toString();
return `${datePicker.year}/${datePicker.month if (datePicker.month)
.toString() result += `/${datePicker.month.toString().padStart(2, "0")}`;
.padStart(2, "0")}/${datePicker.day.toString().padStart(2, "0")}`; if (datePicker.day)
result += `/${datePicker.day.toString().padStart(2, "0")}`;
return result;
} }
export function prettyPrice( export function prettyPrice(
pricePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"], pricePicker: PricePickerFragment,
currencies: GetCurrenciesQuery["currencies"]["data"], currencies: AppStaticProps["currencies"],
targetCurrencyCode?: string targetCurrencyCode?: string
): string { ): string {
if (!targetCurrencyCode) return ""; if (!targetCurrencyCode) return "";
let result = ""; let result = "";
currencies.map((currency) => { currencies.map((currency) => {
if (currency.attributes.code === targetCurrencyCode) { if (currency?.attributes?.code === targetCurrencyCode) {
const amountInTargetCurrency = convertPrice(pricePicker, currency); const amountInTargetCurrency = convertPrice(pricePicker, currency);
result = result =
currency.attributes.symbol + currency.attributes.symbol +
@ -44,13 +47,22 @@ export function prettyPrice(
} }
export function convertPrice( export function convertPrice(
pricePicker: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"], pricePicker: PricePickerFragment,
targetCurrency: GetCurrenciesQuery["currencies"]["data"][number] targetCurrency: Exclude<
GetCurrenciesQuery["currencies"],
null | undefined
>["data"][number]
): number { ): number {
return ( if (
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) / pricePicker.amount &&
targetCurrency.attributes.rate_to_usd pricePicker.currency?.data?.attributes &&
); targetCurrency.attributes
)
return (
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) /
targetCurrency.attributes.rate_to_usd
);
return 0;
} }
export function prettySlug(slug?: string, parentSlug?: string): string { export function prettySlug(slug?: string, parentSlug?: string): string {
@ -65,9 +77,9 @@ export function prettySlug(slug?: string, parentSlug?: string): string {
} }
export function prettyinlineTitle( export function prettyinlineTitle(
pretitle: string, pretitle: string | undefined | null,
title: string, title: string | undefined | null,
subtitle: string subtitle: string | undefined | null
): string { ): string {
let result = ""; let result = "";
if (pretitle) result += `${pretitle}: `; if (pretitle) result += `${pretitle}: `;
@ -77,11 +89,9 @@ export function prettyinlineTitle(
} }
export function prettyItemType( export function prettyItemType(
metadata: { metadata: any,
__typename: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"][number]["__typename"]; langui: AppStaticProps["langui"]
}, ): string | undefined | null {
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]
): string {
switch (metadata.__typename) { switch (metadata.__typename) {
case "ComponentMetadataAudio": case "ComponentMetadataAudio":
return langui.audio; return langui.audio;
@ -100,50 +110,132 @@ export function prettyItemType(
} }
} }
export function prettyItemSubType(metadata: { export function prettyItemSubType(
/* eslint-disable @typescript-eslint/no-explicit-any */ metadata:
__typename: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"][number]["__typename"]; | {
subtype?: any; __typename: "ComponentMetadataAudio";
platforms?: any; subtype?: {
subitems_type?: any; data?: {
}): string { attributes?: {
switch (metadata.__typename) { slug: string;
case "ComponentMetadataAudio": titles?: Array<{
case "ComponentMetadataBooks": title: string;
case "ComponentMetadataVideo": } | null> | null;
return metadata.subtype.data.attributes.titles.length > 0 } | null;
? metadata.subtype.data.attributes.titles[0].title } | null;
: prettySlug(metadata.subtype.data.attributes.slug); } | null;
case "ComponentMetadataGame": }
return metadata.platforms.data.length > 0 | {
? metadata.platforms.data[0].attributes.short __typename: "ComponentMetadataBooks";
: ""; subtype?: {
data?: {
case "ComponentMetadataGroup": { attributes?: {
const firstPart = slug: string;
metadata.subtype.data.attributes.titles.length > 0 titles?: Array<{
title: string;
} | null> | null;
} | null;
} | null;
} | null;
}
| {
__typename: "ComponentMetadataGame";
platforms?: {
data: Array<{
id?: string | null;
attributes?: {
short: string;
} | null;
}>;
} | null;
}
| {
__typename: "ComponentMetadataGroup";
subtype?: {
data?: {
attributes?: {
slug: string;
titles?: Array<{
title: string;
} | null> | null;
} | null;
} | null;
} | null;
subitems_type?: {
data?: {
attributes?: {
slug: string;
titles?: Array<{
title: string;
} | null> | null;
} | null;
} | null;
} | null;
}
| { __typename: "ComponentMetadataOther" }
| {
__typename: "ComponentMetadataVideo";
subtype?: {
data?: {
attributes?: {
slug: string;
titles?: Array<{
title: string;
} | null> | null;
} | null;
} | null;
} | null;
}
| { __typename: "Error" }
| null
): string {
if (metadata) {
switch (metadata.__typename) {
case "ComponentMetadataAudio":
case "ComponentMetadataBooks":
case "ComponentMetadataVideo":
return metadata.subtype?.data?.attributes?.titles &&
metadata.subtype?.data?.attributes?.titles.length > 0 &&
metadata.subtype.data.attributes.titles[0]
? metadata.subtype.data.attributes.titles[0].title ? metadata.subtype.data.attributes.titles[0].title
: prettySlug(metadata.subtype.data.attributes.slug); : prettySlug(metadata.subtype?.data?.attributes?.slug);
case "ComponentMetadataGame":
return metadata.platforms?.data &&
metadata.platforms?.data.length > 0 &&
metadata.platforms.data[0].attributes
? metadata.platforms.data[0].attributes.short
: "";
case "ComponentMetadataGroup": {
const firstPart =
metadata.subtype?.data?.attributes?.titles &&
metadata.subtype?.data?.attributes?.titles.length > 0 &&
metadata.subtype.data.attributes.titles[0]
? metadata.subtype.data.attributes.titles[0].title
: prettySlug(metadata.subtype?.data?.attributes?.slug);
const secondPart = const secondPart =
metadata.subitems_type.data.attributes.titles.length > 0 metadata.subitems_type?.data?.attributes?.titles &&
? metadata.subitems_type.data.attributes.titles[0].title metadata.subitems_type?.data?.attributes?.titles.length > 0 &&
: prettySlug(metadata.subitems_type.data.attributes.slug); metadata.subitems_type.data.attributes.titles[0]
return `${secondPart} ${firstPart}`; ? metadata.subitems_type.data.attributes.titles[0].title
: prettySlug(metadata.subitems_type?.data?.attributes?.slug);
return `${secondPart} ${firstPart}`;
}
default:
return "";
} }
default:
return "";
} }
return "";
/* eslint-enable @typescript-eslint/no-explicit-any */ /* eslint-enable @typescript-eslint/no-explicit-any */
} }
export function prettyLanguage( export function prettyLanguage(
code: string, code: string,
languages: GetLanguagesQuery["languages"]["data"] languages: AppStaticProps["languages"]
): string { ): string {
let result = code; let result = code;
languages.forEach((language) => { languages.forEach((language) => {
if (language.attributes.code === code) if (language?.attributes?.code === code)
result = language.attributes.localized_name; result = language.attributes.localized_name;
}); });
return result; return result;
@ -207,8 +299,8 @@ export function capitalizeString(string: string): string {
return words.join(" "); return words.join(" ");
} }
export function convertMmToInch(mm: number): string { export function convertMmToInch(mm: number | null | undefined): string {
return (mm * 0.03937008).toPrecision(3); return mm ? (mm * 0.03937008).toPrecision(3) : "";
} }
export type OgImage = { export type OgImage = {
@ -218,31 +310,44 @@ export type OgImage = {
alt: string; alt: string;
}; };
export function getOgImage(quality: ImageQuality, image: StrapiImage): OgImage { export function getOgImage(
quality: ImageQuality,
image: UploadImageFragment
): OgImage {
const imgSize = getImgSizesByQuality( const imgSize = getImgSizesByQuality(
image.width, image.width ?? 0,
image.height, image.height ?? 0,
quality ? quality : ImageQuality.Small quality ? quality : ImageQuality.Small
); );
return { return {
image: getAssetURL(image.url, quality), image: getAssetURL(image.url, quality),
width: imgSize.width, width: imgSize.width,
height: imgSize.height, height: imgSize.height,
alt: image.alternativeText, alt: image.alternativeText || "",
}; };
} }
export function sortContent(contents: { export function sortContent(
data: { contents:
attributes: { | Exclude<
range: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"]["contents"]["data"][number]["attributes"]["range"]; Exclude<
}; GetLibraryItemQuery["libraryItems"],
}[]; null | undefined
}) { >["data"][number]["attributes"],
contents.data.sort((a, b) => { null | undefined
>["contents"]
| Exclude<
Exclude<
GetLibraryItemScansQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"],
null | undefined
>["contents"]
) {
contents?.data.sort((a, b) => {
if ( if (
a.attributes.range[0].__typename === "ComponentRangePageRange" && a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
b.attributes.range[0].__typename === "ComponentRangePageRange" b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
) { ) {
return ( return (
a.attributes.range[0].starting_page - a.attributes.range[0].starting_page -
@ -255,8 +360,8 @@ export function sortContent(contents: {
export function getStatusDescription( export function getStatusDescription(
status: string, status: string,
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"] langui: AppStaticProps["langui"]
): string { ): string | null | undefined {
switch (status) { switch (status) {
case Enum_Componentsetstextset_Status.Incomplete: case Enum_Componentsetstextset_Status.Incomplete:
return langui.status_incomplete; return langui.status_incomplete;
@ -300,15 +405,15 @@ export function randomInt(min: number, max: number) {
} }
export function getLocalesFromLanguages( export function getLocalesFromLanguages(
languages: { languages?: Array<{
language: { language?: {
data: { data?: {
attributes: { attributes?: { code: string } | null;
code: string; } | null;
}; } | null;
}; } | null> | null
};
}[]
) { ) {
return languages.map((language) => language.language.data.attributes.code); return languages
? languages.map((language) => language?.language?.data?.attributes?.code)
: [];
} }