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/*
# Generated content
src/graphql/generated.ts
# dependencies
/node_modules
/.pnp

View File

@ -25,9 +25,10 @@
#### [Front](https://github.com/Accords-Library/accords-library.com) (this repository)
- Language: [TypeScript](https://www.typescriptlang.org/)
- Queries: [GraphQL](https://graphql.org/)
- [GraphQL Code Generator](https://www.graphql-code-generator.com/) to automatically generated types for the operations variables and responses
- The operations are stored in a graphql file and then retrieved and wrap as an actual TypeScript function
- Queries: [GraphQL Code Generator](https://www.graphql-code-generator.com/)
- Fetch the GraphQL schema from the GraphQL back-end endpoint
- 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)
- Support for Arbitrary React Components and Component Props!
- 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",
"postbuild": "next-sitemap",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"generate": "graphql-codegen --config graphql-codegen.js"
},
"dependencies": {
"@fontsource/material-icons": "^4.5.4",
@ -15,6 +16,7 @@
"@fontsource/vollkorn": "^4.5.6",
"@fontsource/zen-maru-gothic": "^4.5.8",
"@tippyjs/react": "^4.2.6",
"graphql-request": "^4.2.0",
"markdown-to-jsx": "^7.1.7",
"next": "^12.1.2",
"nodemailer": "^6.7.3",
@ -25,6 +27,10 @@
"turndown": "^7.1.1"
},
"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/nodemailer": "^6.4.4",
"@types/react": "17.0.43",
@ -34,6 +40,7 @@
"@typescript-eslint/parser": "^5.17.0",
"eslint": "^8.12.0",
"eslint-config-next": "12.1.2",
"graphql": "^14.7.0",
"next-sitemap": "^2.5.14",
"prettier-plugin-organize-imports": "^2.3.4",
"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 { useAppLayout } from "contexts/AppLayoutContext";
import { StrapiImage } from "graphql/operations-types";
import { UploadImageFragment } from "graphql/generated";
import { useMediaMobile } from "hooks/useMediaQuery";
import Head from "next/head";
import { useRouter } from "next/router";
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 { useSwipeable } from "react-swipeable";
import { ImageQuality } from "./Img";
@ -18,8 +23,8 @@ interface AppLayoutProps extends AppStaticProps {
subPanelIcon?: string;
contentPanel?: React.ReactNode;
title?: string;
navTitle: string;
thumbnail?: StrapiImage;
navTitle: string | null | undefined;
thumbnail?: UploadImageFragment;
description?: string;
}
@ -61,11 +66,10 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
height: 630,
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
? props.description
: langui.default_description;
const metaDescription = props.description ?? langui.default_description ?? "";
useEffect(() => {
document.getElementsByTagName("html")[0].style.fontSize = `${
@ -73,9 +77,10 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
}%`;
}, [appLayout.fontSize]);
const currencyOptions = currencies.map(
(currency) => currency.attributes.code
);
const currencyOptions: string[] = [];
currencies.map((currency) => {
if (currency.attributes?.code) currencyOptions.push(currency.attributes.code);
});
const [currencySelect, setCurrencySelect] = useState<number>(-1);
useEffect(() => {
@ -127,7 +132,10 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
></meta>
<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:secure_url" content={metaImage.image}></meta>
@ -220,12 +228,12 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
</span>
<p
className={`font-black font-headers text-center overflow-hidden ${
props.navTitle?.length > 30
ogTitle && ogTitle.length > 30
? "text-xl max-h-14"
: "text-2xl max-h-16"
}`}
>
{props.navTitle}
{ogTitle}
</p>
<span
className="material-icons mt-[.1em] cursor-pointer"

View File

@ -3,14 +3,17 @@ import ToolTip from "components/ToolTip";
import {
Enum_Componenttranslationschronologyitem_Status,
GetChronologyItemsQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
} from "graphql/generated";
import { AppStaticProps } from "queries/getAppStaticProps";
import { getStatusDescription } from "queries/helpers";
export type ChronologyItemComponentProps = {
item: GetChronologyItemsQuery["chronologyItems"]["data"][number];
item: Exclude<
GetChronologyItemsQuery["chronologyItems"],
null | undefined
>["data"][number];
displayYear: boolean;
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
langui: AppStaticProps["langui"];
};
export default function ChronologyItemComponent(
@ -18,22 +21,29 @@ export default function ChronologyItemComponent(
): JSX.Element {
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 = "";
result += year;
if (year) result += year;
if (month) result += `- ${month.toString().padStart(2, "0")}`;
if (day) result += `- ${day.toString().padStart(2, "0")}`;
return result;
}
function generateYear(displayed_date: string, year: number): string {
if (displayed_date) {
return displayed_date;
}
return year.toString();
function generateYear(
displayed_date: string | null | undefined,
year: number | undefined
): string {
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 = [
"Jan",
"Feb",
@ -50,7 +60,7 @@ export default function ChronologyItemComponent(
];
let result = "";
if (month) {
if (month && month >= 1 && month <= 12) {
result += lut[month - 1];
if (day) {
result += ` ${day}`;
@ -60,6 +70,7 @@ export default function ChronologyItemComponent(
return result;
}
if (props.item.attributes) {
return (
<div
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"
@ -83,26 +94,38 @@ export default function ChronologyItemComponent(
</p>
<div className="col-start-2 row-start-1 row-span-2 grid gap-4">
{props.item.attributes.events.map((event) => (
{props.item.attributes.events?.map((event) => (
<>
{event && (
<div className="m-0" key={event.id}>
{event.translations.map((translation) => (
{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)}
content={getStatusDescription(
translation.status,
langui
)}
maxWidth={"20rem"}
>
<Chip>{translation.status}</Chip>
</ToolTip>
)}
{translation.title ? <h3>{translation.title}</h3> : ""}
{translation.title ? (
<h3>{translation.title}</h3>
) : (
""
)}
</div>
{translation.description && (
<p
className={
event.translations &&
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"
@ -117,21 +140,28 @@ export default function ChronologyItemComponent(
""
)}
</>
)}
</>
))}
<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})`
{event.source?.data ? (
`(${event.source.data.attributes?.name})`
) : (
<>
<span className="material-icons !text-sm">warning</span>No
sources!
<span className="material-icons !text-sm">warning</span>
No sources!
</>
)}
</p>
</div>
)}
</>
))}
</div>
</div>
);
}
return <></>;
}

View File

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

View File

@ -1,21 +1,31 @@
import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img";
import InsetBox from "components/InsetBox";
import {
GetContentQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { GetContentQuery, UploadImageFragment } from "graphql/generated";
import { AppStaticProps } from "queries/getAppStaticProps";
import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers";
export type ThumbnailHeaderProps = {
pre_title?: string;
title: string;
subtitle?: string;
description?: string;
type?: GetContentQuery["contents"]["data"][number]["attributes"]["type"];
categories?: GetContentQuery["contents"]["data"][number]["attributes"]["categories"];
thumbnail?: GetContentQuery["contents"]["data"][number]["attributes"]["thumbnail"]["data"]["attributes"];
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
pre_title?: string | null | undefined;
title: string | null | undefined;
subtitle?: string | null | undefined;
description?: string | null | undefined;
type?: Exclude<
Exclude<
GetContentQuery["contents"],
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(
@ -60,13 +70,14 @@ export default function ThumbnailHeader(
</div>
<div className="grid grid-flow-col gap-8">
{type?.data && (
{type?.data?.attributes && (
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.type}</h3>
<div className="flex flex-row flex-wrap">
<Chip>
{type.data.attributes.titles.length > 0
? type.data.attributes.titles[0].title
{type.data.attributes.titles &&
type.data.attributes.titles.length > 0
? type.data.attributes.titles[0]?.title
: prettySlug(type.data.attributes.slug)}
</Chip>
</div>
@ -78,7 +89,7 @@ export default function ThumbnailHeader(
<h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{categories.data.map((category) => (
<Chip key={category.id}>{category.attributes.name}</Chip>
<Chip key={category.id}>{category.attributes?.name}</Chip>
))}
</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";
export enum ImageQuality {
@ -51,7 +51,7 @@ export function getImgSizesByQuality(
type ImgProps = {
className?: string;
image?: StrapiImage;
image?: UploadImageFragment;
quality?: ImageQuality;
alt?: ImageProps["alt"];
layout?: ImageProps["layout"];
@ -61,11 +61,11 @@ type ImgProps = {
};
export default function Img(props: ImgProps): JSX.Element {
if (props.image) {
if (props.image?.width && props.image?.height) {
const imgSize = getImgSizesByQuality(
props.image.width,
props.image.height,
props.quality ? props.quality : ImageQuality.Small
props.quality ?? ImageQuality.Small
);
if (props.rawImg) {
@ -75,9 +75,9 @@ export default function Img(props: ImgProps): JSX.Element {
className={props.className}
src={getAssetURL(
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}
height={imgSize.height}
/>
@ -90,7 +90,7 @@ export default function Img(props: ImgProps): JSX.Element {
props.image.url,
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}
height={props.layout === "fill" ? undefined : imgSize.height}
layout={props.layout}

View File

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

View File

@ -1,16 +1,23 @@
import Button from "components/Button";
import Chip from "components/Chip";
import {
GetLibraryItemQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { GetLibraryItemQuery } from "graphql/generated";
import { AppStaticProps } from "queries/getAppStaticProps";
import { prettyinlineTitle, prettySlug } from "queries/helpers";
import { useState } from "react";
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;
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
langui: AppStaticProps["langui"];
};
export default function ContentTOCLine(
@ -20,6 +27,7 @@ export default function ContentTOCLine(
const [opened, setOpened] = useState(false);
if (content.attributes) {
return (
<div
className={`grid gap-2 px-4 rounded-lg ${
@ -29,36 +37,40 @@ export default function ContentTOCLine(
<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
{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
?.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(
{content.attributes.content?.data?.attributes?.categories?.data.map(
(category) => (
<Chip key={category.id}>{category.attributes.short}</Chip>
<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]?.__typename ===
"ComponentRangePageRange"
? content.attributes.range[0].starting_page
: ""}
</p>
{content.attributes.content.data && (
{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
? content.attributes.content.data.attributes.type.data
.attributes.titles[0]?.title
: prettySlug(
content.attributes.content.data.attributes.type.data
.attributes.slug
@ -75,7 +87,8 @@ export default function ContentTOCLine(
subdirectory_arrow_right
</span>
{content.attributes.scan_set.length > 0 && (
{content.attributes.scan_set &&
content.attributes.scan_set.length > 0 && (
<Button
href={`/library/${parentSlug}/scans#${content.attributes.slug}`}
>
@ -83,19 +96,22 @@ export default function ContentTOCLine(
</Button>
)}
{content.attributes.content.data && (
{content.attributes.content?.data && (
<Button
href={`/contents/${content.attributes.content.data.attributes.slug}`}
href={`/contents/${content.attributes.content.data.attributes?.slug}`}
>
{langui.open_content}
</Button>
)}
{content.attributes.scan_set.length === 0 &&
!content.attributes.content.data
{content.attributes.scan_set &&
content.attributes.scan_set.length === 0 &&
!content.attributes.content?.data
? "The content is not available"
: ""}
</div>
</div>
);
}
return <></>;
}

View File

@ -1,17 +1,14 @@
import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img";
import { GetContentsQuery } from "graphql/operations-types";
import { GetContentsQuery } from "graphql/generated";
import Link from "next/link";
import { prettySlug } from "queries/helpers";
export type LibraryContentPreviewProps = {
item: {
slug: GetContentsQuery["contents"]["data"][number]["attributes"]["slug"];
thumbnail: GetContentsQuery["contents"]["data"][number]["attributes"]["thumbnail"];
titles: GetContentsQuery["contents"]["data"][number]["attributes"]["titles"];
categories: GetContentsQuery["contents"]["data"][number]["attributes"]["categories"];
type: GetContentsQuery["contents"]["data"][number]["attributes"]["type"];
};
item: Exclude<
GetContentsQuery["contents"],
null | undefined
>["data"][number]["attributes"];
};
export default function LibraryContentPreview(
@ -20,9 +17,9 @@ export default function LibraryContentPreview(
const { item } = props;
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">
{item.thumbnail.data ? (
{item?.thumbnail?.data?.attributes ? (
<Img
className="rounded-md coarse:rounded-b-none"
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="grid grid-flow-col gap-1 overflow-hidden place-content-start">
{item.type.data && (
{item?.type?.data?.attributes && (
<Chip>
{item.type.data.attributes.titles.length > 0
? item.type.data.attributes.titles[0].title
{item.type.data.attributes.titles?.[0]
? item.type.data.attributes.titles[0]?.title
: prettySlug(item.type.data.attributes.slug)}
</Chip>
)}
</div>
<div>
{item.titles.length > 0 ? (
{item?.titles?.[0] ? (
<>
<p>{item.titles[0].pre_title}</p>
<h1 className="text-lg">{item.titles[0].title}</h1>
<h2>{item.titles[0].subtitle}</h2>
</>
) : (
<h1 className="text-lg">{prettySlug(item.slug)}</h1>
<h1 className="text-lg">{prettySlug(item?.slug)}</h1>
)}
</div>
<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">
{category.attributes.short}
{category.attributes?.short}
</Chip>
))}
</div>

View File

@ -2,25 +2,31 @@ import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img";
import { useAppLayout } from "contexts/AppLayoutContext";
import {
GetCurrenciesQuery,
GetLibraryItemQuery,
GetLibraryItemsPreviewQuery,
} from "graphql/operations-types";
} from "graphql/generated";
import Link from "next/link";
import { AppStaticProps } from "queries/getAppStaticProps";
import { prettyDate, prettyItemSubType, prettyPrice } from "queries/helpers";
export type LibraryItemsPreviewProps = {
className?: string;
item: {
slug: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["slug"];
thumbnail: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["thumbnail"];
title: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["title"];
subtitle: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["subtitle"];
price?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["price"];
categories: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["categories"];
release_date?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["release_date"];
metadata?: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"];
};
currencies?: GetCurrenciesQuery["currencies"]["data"];
item:
| Exclude<
Exclude<
Exclude<
GetLibraryItemQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"],
null | undefined
>["subitems"],
null | undefined
>["data"][number]["attributes"]
| Exclude<
GetLibraryItemsPreviewQuery["libraryItems"],
null | undefined
>["data"][number]["attributes"];
currencies: AppStaticProps["currencies"];
};
export default function LibraryItemsPreview(
@ -30,11 +36,11 @@ export default function LibraryItemsPreview(
const appLayout = useAppLayout();
return (
<Link href={`/library/${item.slug}`} passHref>
<Link href={`/library/${item?.slug}`} passHref>
<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}`}
>
{item.thumbnail.data ? (
{item?.thumbnail?.data?.attributes ? (
<Img
image={item.thumbnail.data.attributes}
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">
{item.metadata && item.metadata.length > 0 && (
{item?.metadata && item.metadata.length > 0 && item.metadata[0] && (
<div className="flex flex-row gap-1">
<Chip>{prettyItemSubType(item.metadata[0])}</Chip>
</div>
)}
<div>
<h2 className="mobile:text-sm text-lg leading-5">{item.title}</h2>
<h3 className="mobile:text-xs leading-3">{item.subtitle}</h3>
<h2 className="mobile:text-sm text-lg leading-5">{item?.title}</h2>
<h3 className="mobile:text-xs leading-3">{item?.subtitle}</h3>
</div>
<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">
{category.attributes.short}
{category.attributes?.short}
</Chip>
))}
</div>
{(item.release_date || item.price) && (
{(item?.release_date || item?.price) && (
<div className="grid grid-flow-col w-full">
{item.release_date && (
<p className="mobile:text-xs text-sm">
@ -73,7 +79,7 @@ export default function LibraryItemsPreview(
{prettyDate(item.release_date)}
</p>
)}
{item.price && props.currencies && (
{item.price && (
<p className="mobile:text-xs text-sm justify-self-end">
<span className="material-icons !text-base translate-y-[.15em] mr-1">
shopping_cart

View File

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

View File

@ -1,26 +1,23 @@
import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img";
import { GetPostsPreviewQuery } from "graphql/operations-types";
import { GetPostsPreviewQuery } from "graphql/generated";
import Link from "next/link";
import { prettyDate, prettySlug } from "queries/helpers";
export type PostPreviewProps = {
post: {
slug: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["slug"];
thumbnail: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["thumbnail"];
translations: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["translations"];
categories: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["categories"];
date: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["date"];
};
post: Exclude<
GetPostsPreviewQuery["posts"],
null | undefined
>["data"][number]["attributes"];
};
export default function PostPreview(props: PostPreviewProps): JSX.Element {
const { post } = props;
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">
{post.thumbnail.data ? (
{post?.thumbnail?.data?.attributes ? (
<Img
className="rounded-md rounded-b-none"
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="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
{post?.date && (
<div className="grid grid-flow-col w-full">
{post.date && (
<p className="mobile:text-xs text-sm">
<span className="material-icons !text-base translate-y-[.15em] mr-1">
event
</span>
{prettyDate(post.date)}
</p>
)}
</div>
)}
<div>
{post.translations.length > 0 ? (
{post?.translations?.[0] ? (
<>
<h1 className="text-xl">{post.translations[0].title}</h1>
<p>{post.translations[0].excerpt}</p>
</>
) : (
<h1 className="text-lg">{prettySlug(post.slug)}</h1>
<h1 className="text-lg">{prettySlug(post?.slug)}</h1>
)}
</div>
<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">
{category.attributes.short}
{category.attributes?.short}
</Chip>
))}
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,68 +1,67 @@
import Chip from "components/Chip";
import {
GetContentTextQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { RecorderChipFragment } from "graphql/generated";
import { AppStaticProps } from "queries/getAppStaticProps";
import Button from "./Button";
import Img, { ImageQuality } from "./Img";
import ToolTip from "./ToolTip";
type RecorderChipProps = {
className?: string;
recorder: GetContentTextQuery["contents"]["data"][number]["attributes"]["text_set"][number]["transcribers"]["data"][number];
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
recorder: RecorderChipFragment;
langui: AppStaticProps["langui"];
};
export default function RecorderChip(props: RecorderChipProps): JSX.Element {
const { recorder, langui } = props;
return (
<ToolTip
content={
<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">
{recorder.attributes.avatar.data && (
{recorder.avatar?.data?.attributes && (
<Img
className="w-20 rounded-full border-4 border-mid"
image={recorder.attributes.avatar.data.attributes}
image={recorder.avatar?.data.attributes}
quality={ImageQuality.Small}
rawImg
/>
)}
<div className="grid gap-2">
<h3 className=" text-2xl">{recorder.attributes.username}</h3>
{recorder.attributes.languages.data.length > 0 && (
<h3 className=" text-2xl">{recorder.username}</h3>
{recorder.languages?.data && recorder.languages.data.length > 0 && (
<div className="flex flex-row flex-wrap gap-1">
<p>{langui.languages}:</p>
{recorder.attributes.languages.data.map((language) => (
{recorder.languages.data.map((language) => (
<>
{language.attributes && (
<Chip key={language.attributes.code}>
{language.attributes.code.toUpperCase()}
</Chip>
)}
</>
))}
</div>
)}
{recorder.attributes.pronouns && (
{recorder.pronouns && (
<div className="flex flex-row flex-wrap gap-1">
<p>{langui.pronouns}:</p>
<Chip>{recorder.attributes.pronouns}</Chip>
<Chip>{recorder.pronouns}</Chip>
</div>
)}
</div>
</div>
{recorder.attributes.bio.length > 0 && (
<p>{recorder.attributes.bio[0].bio}</p>
)}
{recorder.bio?.[0] && <p>{recorder.bio[0].bio}</p>}
<Button className="cursor-not-allowed">View profile</Button>
</div>
}
placement="top"
>
<Chip key={recorder.id}>
{recorder.attributes.anonymize
? `Recorder#${recorder.attributes.anonymous_code}`
: recorder.attributes.username}
<Chip key={recorder.anonymous_code}>
{recorder.anonymize
? `Recorder#${recorder.anonymous_code}`
: recorder.username}
</Chip>
</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";
import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel";
import { getPost } from "graphql/operations";
import { GetPostQuery } from "graphql/operations-types";
import { GetPostQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { getLocalesFromLanguages, prettySlug } from "queries/helpers";
interface AccordsHandbookProps extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"];
interface Props extends AppStaticProps {
post: Exclude<
GetPostQuery["posts"],
null | undefined
>["data"][number]["attributes"];
}
export default function AccordsHandbook(
props: AccordsHandbookProps
): JSX.Element {
export default function AccordsHandbook(props: Props): JSX.Element {
const { langui, post } = props;
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 = (
<SubPanel>
@ -34,12 +38,7 @@ export default function AccordsHandbook(
title={langui.about_us}
horizontalLine
/>
{post.translations.length > 0 && post.translations[0].body && (
<TOC
text={post.translations[0].body}
title={post.translations[0].title}
/>
)}
<TOC text={body} title={title} />
</SubPanel>
);
@ -53,7 +52,7 @@ export default function AccordsHandbook(
className="mb-10"
/>
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={post.translations[0].body} />
<Markdawn text={body} />
) : (
<LanguageSwitcher
locales={locales}
@ -66,11 +65,7 @@ export default function AccordsHandbook(
return (
<AppLayout
navTitle={
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
navTitle={title}
subPanel={subPanel}
contentPanel={contentPanel}
{...props}
@ -80,16 +75,17 @@ export default function AccordsHandbook(
export async function getStaticProps(
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: AccordsHandbookProps }> {
): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = "accords-handbook";
const props: AccordsHandbookProps = {
...(await getAppStaticProps(context)),
post: (
await getPost({
const post = await sdk.getPost({
slug: slug,
language_code: context.locale ?? "en",
})
).posts.data[0].attributes,
});
if (!post.posts) return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)),
post: post.posts.data[0].attributes,
};
return {
props: props,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,8 +13,8 @@ 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 { GetContentTextQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import {
GetStaticPathsContext,
GetStaticPathsResult,
@ -33,15 +33,21 @@ import {
} from "queries/helpers";
interface Props extends AppStaticProps {
content: GetContentTextQuery["contents"]["data"][number]["attributes"];
contentId: GetContentTextQuery["contents"]["data"][number]["id"];
content: Exclude<
GetContentTextQuery["contents"],
null | undefined
>["data"][number]["attributes"];
contentId: Exclude<
GetContentTextQuery["contents"],
null | undefined
>["data"][number]["id"];
}
export default function Content(props: Props): JSX.Element {
useTesting(props);
const { langui, content, languages } = props;
const router = useRouter();
const locales = getLocalesFromLanguages(content.text_set_languages);
const locales = getLocalesFromLanguages(content?.text_set_languages);
const subPanel = (
<SubPanel>
@ -53,7 +59,7 @@ export default function Content(props: Props): JSX.Element {
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">
<h2 className="text-xl">
{content.text_set[0].source_language.data.attributes.code ===
@ -91,46 +97,61 @@ export default function Content(props: Props): JSX.Element {
</ToolTip>
</div>
{content.text_set[0].transcribers.data.length > 0 && (
{content.text_set[0].transcribers &&
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) => (
<>
{recorder.attributes && (
<RecorderChip
key={recorder.id}
langui={langui}
recorder={recorder}
recorder={recorder.attributes}
/>
)}
</>
))}
</div>
</div>
)}
{content.text_set[0].translators.data.length > 0 && (
{content.text_set[0].translators &&
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) => (
<>
{recorder.attributes && (
<RecorderChip
key={recorder.id}
langui={langui}
recorder={recorder}
recorder={recorder.attributes}
/>
)}
</>
))}
</div>
</div>
)}
{content.text_set[0].proofreaders.data.length > 0 && (
{content.text_set[0].proofreaders &&
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) => (
<>
{recorder.attributes && (
<RecorderChip
key={recorder.id}
langui={langui}
recorder={recorder}
recorder={recorder.attributes}
/>
)}
</>
))}
</div>
</div>
@ -138,13 +159,15 @@ export default function Content(props: Props): JSX.Element {
</div>
)}
{content.text_set.length > 0 && content.text_set[0].text && (
{content?.text_set &&
content.text_set.length > 0 &&
content.text_set[0]?.text && (
<>
<HorizontalLine />
<TOC
text={content.text_set[0].text}
title={
content.titles.length > 0
content.titles && content.titles.length > 0 && content.titles[0]
? prettyinlineTitle(
content.titles[0].pre_title,
content.titles[0].title,
@ -160,29 +183,34 @@ export default function Content(props: Props): JSX.Element {
const contentPanel = (
<ContentPanel>
<ReturnButton
href={`/contents/${content.slug}`}
href={`/contents/${content?.slug}`}
title={langui.content}
langui={langui}
displayOn={ReturnButtonType.mobile}
className="mb-10"
/>
{content && (
<div className="grid place-items-center">
<ThumbnailHeader
thumbnail={content.thumbnail.data?.attributes}
thumbnail={content.thumbnail?.data?.attributes}
pre_title={
content.titles.length > 0 ? content.titles[0].pre_title : undefined
content.titles && content.titles.length > 0
? content.titles[0]?.pre_title
: undefined
}
title={
content.titles.length > 0
? content.titles[0].title
content.titles && content.titles.length > 0
? content.titles[0]?.title
: prettySlug(content.slug)
}
subtitle={
content.titles.length > 0 ? content.titles[0].subtitle : undefined
content.titles && content.titles.length > 0
? content.titles[0]?.subtitle
: undefined
}
description={
content.titles.length > 0
? content.titles[0].description
content.titles && content.titles.length > 0
? content.titles[0]?.description
: undefined
}
type={content.type}
@ -193,7 +221,7 @@ export default function Content(props: Props): JSX.Element {
<HorizontalLine />
{locales.includes(router.locale ?? "en") ? (
<Markdawn text={content.text_set[0].text} />
<Markdawn text={content.text_set?.[0]?.text ?? ""} />
) : (
<LanguageSwitcher
locales={locales}
@ -202,23 +230,24 @@ export default function Content(props: Props): JSX.Element {
/>
)}
</div>
)}
</ContentPanel>
);
let description = "";
if (content.type.data) {
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 +=
content.type.data.attributes?.titles?.[0]?.title ??
prettySlug(content.type.data.attributes?.slug);
description += "\n";
}
if (content.categories.data.length > 0) {
if (content?.categories?.data && content.categories.data.length > 0) {
description += `${langui.categories}: `;
description += content.categories.data
.map((category) => category.attributes.short)
.map((category) => category.attributes?.short)
.join(" | ");
description += "\n";
}
@ -226,15 +255,15 @@ export default function Content(props: Props): JSX.Element {
return (
<AppLayout
navTitle={
content.titles.length > 0
content?.titles && content.titles.length > 0 && content.titles[0]
? prettyinlineTitle(
content.titles[0].pre_title,
content.titles[0].title,
content.titles[0].subtitle
)
: prettySlug(content.slug)
: prettySlug(content?.slug)
}
thumbnail={content.thumbnail.data?.attributes}
thumbnail={content?.thumbnail?.data?.attributes ?? undefined}
contentPanel={contentPanel}
subPanel={subPanel}
description={description}
@ -246,18 +275,19 @@ export default function Content(props: Props): JSX.Element {
export async function getStaticProps(
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: Props }> {
const sdk = getReadySdk();
const slug = context.params?.slug?.toString() ?? "";
const content = (
await getContentText({
const content = await sdk.getContentText({
slug: slug,
language_code: context.locale ?? "en",
})
).contents.data[0];
if (!content) return { notFound: true };
});
if (!content.contents || content.contents.data.length === 0)
return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)),
content: content.attributes,
contentId: content.id,
content: content.contents.data[0].attributes,
contentId: content.contents.data[0].id,
};
return {
props: props,
@ -267,10 +297,12 @@ export async function getStaticProps(
export async function getStaticPaths(
context: GetStaticPathsContext
): Promise<GetStaticPathsResult> {
const contents = await getContentsSlugs({});
const sdk = getReadySdk();
const contents = await sdk.getContentsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
contents.contents.data.map((item) => {
contents.contents?.data.map((item) => {
context.locales?.map((local) => {
if (item.attributes)
paths.push({ params: { slug: item.attributes.slug }, locale: local });
});
});
@ -287,12 +319,12 @@ function useTesting(props: Props) {
const contentURL = `/admin/content-manager/collectionType/api::content.content/${contentId}`;
if (router.locale === "en") {
if (content.categories.data.length === 0) {
if (content?.categories?.data.length === 0) {
prettyTestError(router, "Missing categories", ["content"], contentURL);
}
}
if (content.ranged_contents.data.length === 0) {
if (content?.ranged_contents?.data.length === 0) {
prettyTestWarning(
router,
"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(
router,
"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(
router,
"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];
if (!textset.text) {
if (!textset?.text) {
prettyTestError(
router,
"Missing text",
@ -330,16 +362,18 @@ function useTesting(props: Props) {
contentURL
);
}
if (!textset.source_language.data) {
if (!textset?.source_language?.data) {
prettyTestError(
router,
"Missing source language",
["content", "text_set"],
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
if (textset.transcribers.data.length === 0) {
if (textset.transcribers?.data.length === 0) {
prettyTestError(
router,
"Missing transcribers attribution",
@ -347,7 +381,7 @@ function useTesting(props: Props) {
contentURL
);
}
if (textset.translators.data.length > 0) {
if (textset.translators && textset.translators.data.length > 0) {
prettyTestError(
router,
"Transcripts shouldn't have translators",
@ -357,7 +391,7 @@ function useTesting(props: Props) {
}
} else {
// This is a translation
if (textset.translators.data.length === 0) {
if (textset.translators?.data.length === 0) {
prettyTestError(
router,
"Missing translators attribution",
@ -365,7 +399,7 @@ function useTesting(props: Props) {
contentURL
);
}
if (textset.transcribers.data.length > 0) {
if (textset.transcribers && textset.transcribers.data.length > 0) {
prettyTestError(
router,
"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";
import SubPanel from "components/Panels/SubPanel";
import Select from "components/Select";
import { getContents } from "graphql/operations";
import {
GetContentsQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { GetContentsQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { prettyinlineTitle, prettySlug } from "queries/helpers";
import { useEffect, useState } from "react";
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 {
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>
<Select
className="w-full"
options={[langui.category, langui.type]}
options={[langui.category ?? "", langui.type ?? ""]}
state={groupingMethod}
setState={setGroupingMethod}
allowEmpty
@ -70,8 +67,8 @@ export default function Contents(props: ContentsProps): JSX.Element {
{name}
<Chip>{`${items.length} ${
items.length <= 1
? langui.result.toLowerCase()
: langui.results.toLowerCase()
? langui.result?.toLowerCase() ?? ""
: langui.results?.toLowerCase() ?? ""
}`}</Chip>
</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))]"
>
{items.map((item) => (
<LibraryContentPreview key={item.id} item={item.attributes} />
<>
{item.attributes && (
<LibraryContentPreview
key={item.id}
item={item.attributes}
/>
)}
</>
))}
</div>
</>
@ -102,35 +106,32 @@ export default function Contents(props: ContentsProps): JSX.Element {
export async function getStaticProps(
context: GetStaticPropsContext
): Promise<{ notFound: boolean } | { props: ContentsProps }> {
const contents = (
await getContents({
const sdk = getReadySdk();
const contents = await sdk.getContents({
language_code: context.locale ?? "en",
})
).contents.data;
contents.sort((a, b) => {
const titleA =
a.attributes.titles.length > 0
});
if (!contents.contents) return { notFound: true };
contents.contents.data.sort((a, b) => {
const titleA = a.attributes?.titles?.[0]
? prettyinlineTitle(
a.attributes.titles[0].pre_title,
a.attributes.titles[0].title,
a.attributes.titles[0].subtitle
)
: a.attributes.slug;
const titleB =
b.attributes.titles.length > 0
: a.attributes?.slug ?? "";
const titleB = b.attributes?.titles?.[0]
? prettyinlineTitle(
b.attributes.titles[0].pre_title,
b.attributes.titles[0].title,
b.attributes.titles[0].subtitle
)
: b.attributes.slug;
: b.attributes?.slug ?? "";
return titleA.localeCompare(titleB);
});
const props: ContentsProps = {
...(await getAppStaticProps(context)),
contents: contents,
contents: contents.contents.data,
};
return {
props: props,
@ -138,7 +139,7 @@ export async function getStaticProps(
}
function getGroups(
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"],
langui: AppStaticProps["langui"],
groupByType: number,
items: ContentsProps["contents"]
): GroupContentItems {
@ -165,11 +166,11 @@ function getGroups(
group.set(langui.no_category, []);
items.map((item) => {
if (item.attributes.categories.data.length === 0) {
if (item.attributes?.categories?.data.length === 0) {
group.get(langui.no_category)?.push(item);
} else {
item.attributes.categories.data.map((category) => {
group.get(category.attributes.name)?.push(item);
item.attributes?.categories?.data.map((category) => {
group.get(category.attributes?.name)?.push(item);
});
}
});
@ -180,10 +181,8 @@ function getGroups(
const group: GroupContentItems = new Map();
items.map((item) => {
const type =
item.attributes.type.data.attributes.titles.length > 0
? item.attributes.type.data.attributes.titles[0].title
: prettySlug(item.attributes.type.data.attributes.slug);
item.attributes?.type?.data?.attributes?.titles?.[0]?.title ??
prettySlug(item.attributes?.type?.data?.attributes?.slug);
if (!group.has(type)) group.set(type, []);
group.get(type)?.push(item);
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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