import { Fragment, useCallback, useMemo } from "react"; import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { useRouter } from "next/router"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Chip } from "components/Chip"; import { Img } from "components/Img"; import { Button } from "components/Inputs/Button"; import { Switch } from "components/Inputs/Switch"; import { InsetBox } from "components/InsetBox"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; import { NavOption } from "components/PanelComponents/NavOption"; import { ReturnButton, ReturnButtonType, } from "components/PanelComponents/ReturnButton"; import { ContentPanel, ContentPanelWidthSizes, } from "components/Panels/ContentPanel"; import { SubPanel } from "components/Panels/SubPanel"; import { PreviewCard } from "components/PreviewCard"; import { useAppLayout } from "contexts/AppLayoutContext"; import { Enum_Componentmetadatabooks_Binding_Type, Enum_Componentmetadatabooks_Page_Order, GetLibraryItemQuery, } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyDate, prettyInlineTitle, prettyItemSubType, prettyItemType, prettyPrice, prettySlug, prettyURL, } from "helpers/formatters"; import { getAssetURL, ImageQuality } from "helpers/img"; import { convertMmToInch } from "helpers/numbers"; import { filterDefined, filterHasAttributes, isDefined, isDefinedAndNotEmpty, sortRangedContent, } from "helpers/others"; import { useLightBox } from "hooks/useLightBox"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { isUntangibleGroupItem } from "helpers/libraryItem"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { WithLabel } from "components/Inputs/WithLabel"; import { Ico, Icon } from "components/Ico"; import { cJoin, cIf } from "helpers/className"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useBoolean } from "hooks/useBoolean"; import { getOpenGraph } from "helpers/openGraph"; import { getDescription } from "helpers/description"; /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── */ interface Props extends AppStaticProps, AppLayoutRequired { item: NonNullable< NonNullable< GetLibraryItemQuery["libraryItems"] >["data"][number]["attributes"] >; itemId: NonNullable< GetLibraryItemQuery["libraryItems"] >["data"][number]["id"]; } const LibrarySlug = ({ item, itemId, langui, currencies, languages, ...otherProps }: Props): JSX.Element => { const { currency } = useAppLayout(); const hoverable = useMediaHoverable(); const router = useRouter(); const [openLightBox, LightBox] = useLightBox(); const { state: keepInfoVisible, toggleState: toggleKeepInfoVisible } = useBoolean(false); useScrollTopOnChange(AnchorIds.ContentPanel, [item]); const isVariantSet = useMemo( () => item.metadata?.[0]?.__typename === "ComponentMetadataGroup" && item.metadata[0].subtype?.data?.attributes?.slug === "variant-set", [item.metadata] ); const displayOpenScans = useMemo( () => item.contents?.data.some( (content) => content.attributes?.scan_set && content.attributes.scan_set.length > 0 ), [item.contents?.data] ); const subPanel = useMemo( () => (
{item.gallery && item.gallery.data.length > 0 && ( )} {item.subitems && item.subitems.data.length > 0 && ( )} {item.contents && item.contents.data.length > 0 && ( )}
), [isVariantSet, item.contents, item.gallery, item.subitems, langui] ); const contentPanel = useMemo( () => (
{ if (item.thumbnail?.data?.attributes) { openLightBox([ getAssetURL( item.thumbnail.data.attributes.url, ImageQuality.Large ), ]); } }} > {item.thumbnail?.data?.attributes ? ( ) : (
)}
{item.subitem_of?.data[0]?.attributes && (

{langui.subitem_of}

)}

{item.title}

{isDefinedAndNotEmpty(item.subtitle) && (

{item.subtitle}

)}
{!isUntangibleGroupItem(item.metadata?.[0]) && isDefinedAndNotEmpty(itemId) && ( )} {item.descriptions?.[0] && (

{item.descriptions[0].description}

)} {!( item.metadata && item.metadata[0]?.__typename === "ComponentMetadataGroup" && (item.metadata[0].subtype?.data?.attributes?.slug === "variant-set" || item.metadata[0].subtype?.data?.attributes?.slug === "relation-set") ) && ( <> {item.urls?.length ? (

{langui.available_at}

{filterHasAttributes(item.urls, ["url"] as const).map( (url, index) => (
) : (

{langui.item_not_available}

)} )}
{item.gallery && item.gallery.data.length > 0 && ( )}

{langui.details}

{item.metadata?.[0] && (

{langui.type}

{"›"}
)} {item.release_date && (

{langui.release_date}

{prettyDate(item.release_date, router.locale)}

)} {item.price && (

{langui.price}

{prettyPrice( item.price, currencies, item.price.currency?.data?.attributes?.code )}

{item.price.currency?.data?.attributes?.code !== currency && (

{prettyPrice(item.price, currencies, currency)}
( {langui.calculated?.toLowerCase()})

)}
)}
{item.categories && item.categories.data.length > 0 && (

{langui.categories}

{filterHasAttributes(item.categories.data, [ "attributes", ] as const).map((category) => ( ))}
)} {item.size && (

{langui.size}

{langui.width}:

{item.size.width} mm

{convertMmToInch(item.size.width)} in

{langui.height}:

{item.size.height} mm

{convertMmToInch(item.size.height)} in

{isDefined(item.size.thickness) && (

{langui.thickness}:

{item.size.thickness} mm

{convertMmToInch(item.size.thickness)} in

)}
)} {item.metadata?.[0]?.__typename !== "ComponentMetadataGroup" && item.metadata?.[0]?.__typename !== "ComponentMetadataOther" && ( <>

{langui.type_information}

{item.metadata?.[0]?.__typename === "ComponentMetadataBooks" && ( <>

{langui.pages}:

{item.metadata[0].page_count}

{langui.binding}:

{item.metadata[0].binding_type === Enum_Componentmetadatabooks_Binding_Type.Paperback ? langui.paperback : item.metadata[0].binding_type === Enum_Componentmetadatabooks_Binding_Type.Hardcover ? langui.hardcover : ""}

{langui.page_order}:

{item.metadata[0].page_order === Enum_Componentmetadatabooks_Page_Order.LeftToRight ? langui.left_to_right : langui.right_to_left}

{langui.languages}:

{item.metadata[0]?.languages?.data.map((lang) => (

{lang.attributes?.name}

))}
)}
)}
{item.subitems && item.subitems.data.length > 0 && (

{isVariantSet ? langui.variants : langui.subitems}

{hoverable && ( } /> )}
{filterHasAttributes(item.subitems.data, [ "id", "attributes", ] as const).map((subitem) => ( 0 && subitem.attributes.metadata[0] ? [prettyItemSubType(subitem.attributes.metadata[0])] : [] } bottomChips={subitem.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" )} metadata={{ currencies: currencies, releaseDate: subitem.attributes.release_date, price: subitem.attributes.price, position: "Bottom", }} infoAppend={ !isUntangibleGroupItem( subitem.attributes.metadata?.[0] ) && } /> ))}
)} {item.contents && item.contents.data.length > 0 && (

{langui.contents}

{displayOpenScans && (
)}
), [ LightBox, langui, item.thumbnail?.data?.attributes, item.subitem_of?.data, item.title, item.subtitle, item.metadata, item.descriptions, item.urls, item.gallery, item.release_date, item.price, item.categories, item.size, item.subitems, item.contents, item.slug, itemId, router.locale, currencies, currency, isVariantSet, hoverable, toggleKeepInfoVisible, keepInfoVisible, displayOpenScans, openLightBox, languages, ] ); return ( ); }; export default LibrarySlug; /* * ╭──────────────────────╮ * ───────────────────────────────────╯ NEXT DATA FETCHING ╰────────────────────────────────────── */ export const getStaticProps: GetStaticProps = async (context) => { const sdk = getReadySdk(); const item = await sdk.getLibraryItem({ slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "", language_code: context.locale ?? "en", }); if (!item.libraryItems?.data[0]?.attributes) return { notFound: true }; sortRangedContent(item.libraryItems.data[0].attributes.contents); const appStaticProps = await getAppStaticProps(context); const { title, thumbnail } = item.libraryItems.data[0].attributes; const description = getDescription( item.libraryItems.data[0].attributes.descriptions?.[0]?.description, { [appStaticProps.langui.categories ?? "Categories"]: filterHasAttributes( item.libraryItems.data[0].attributes.categories?.data, ["attributes.short"] ).map((category) => category.attributes.short), [appStaticProps.langui.type ?? "Type"]: item.libraryItems.data[0] .attributes.metadata?.[0] ? [prettyItemSubType(item.libraryItems.data[0].attributes.metadata[0])] : [], [appStaticProps.langui.release_date ?? "Release date"]: [ item.libraryItems.data[0].attributes.release_date ? prettyDate( item.libraryItems.data[0].attributes.release_date, context.locale ) : undefined, ], } ); const props: Props = { ...appStaticProps, item: item.libraryItems.data[0].attributes, itemId: item.libraryItems.data[0].id, openGraph: getOpenGraph( appStaticProps.langui, title, description, thumbnail?.data?.attributes ), }; return { props: props, }; }; // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ export const getStaticPaths: GetStaticPaths = async (context) => { const sdk = getReadySdk(); const libraryItems = await sdk.getLibraryItemsSlugs(); const paths: GetStaticPathsResult["paths"] = []; filterHasAttributes(libraryItems.libraryItems?.data, [ "attributes", ] as const).map((item) => { context.locales?.map((local) => paths.push({ params: { slug: item.attributes.slug }, locale: local }) ); }); return { paths, fallback: "blocking", }; }; /* * ╭──────────────────────╮ * ───────────────────────────────────╯ PRIVATE COMPONENTS ╰────────────────────────────────────── */ interface ContentLineProps { content?: { translations: { pre_title: string | null | undefined; title: string; subtitle: string | null | undefined; language: string | undefined; }[]; categories?: string[]; type?: string; slug: string; }; rangeStart: string; parentSlug: string; slug: string; langui: AppStaticProps["langui"]; languages: AppStaticProps["languages"]; hasScanSet: boolean; } const ContentLine = ({ rangeStart, content, langui, languages, hasScanSet, slug, parentSlug, }: ContentLineProps): JSX.Element => { const { state: isOpened, toggleState: toggleOpened } = useBoolean(false); const [selectedTranslation] = useSmartLanguage({ items: content?.translations ?? [], languages: languages, languageExtractor: useCallback( ( item: NonNullable["translations"][number] ) => item.language, [] ), }); return (

{selectedTranslation ? prettyInlineTitle( selectedTranslation.pre_title, selectedTranslation.title, selectedTranslation.subtitle ) : content ? prettySlug(content.slug, parentSlug) : prettySlug(slug, parentSlug)}

{content?.categories?.map((category, index) => ( ))}

{rangeStart}

{content?.type && ( )}
{hasScanSet || isDefined(content) ? ( <> {hasScanSet && (
); };