diff --git a/src/components/FeaturedArticle.tsx b/src/components/FeaturedArticle.tsx index 9c8021e..fc047ce 100644 --- a/src/components/FeaturedArticle.tsx +++ b/src/components/FeaturedArticle.tsx @@ -1,25 +1,32 @@ import React from "react"; -import FeaturedContent from "./FeaturedContent"; +import FeaturedComponentBase from "./FeaturedComponentBase"; import { Article } from "../model"; +import { Replace } from "../utils/types"; +import RenderElement from "./RenderElement"; type FeaturedArticleProps = Readonly<{ - article: Partial
; + article: Replace }>; }>; const FeaturedArticle: React.FC = ({ article }) => { - const shouldRender = article.elements?.title.value || article.elements?.introduction.value - || article.elements?.publish_date.value || article.elements?.image.value.length; + const shouldRender = Object.entries(article.elements).length > 0; - return ( - - {shouldRender && ( - <> -
+ return shouldRender && ( + + <> +
+

- {article.elements?.title.value} + {article.elements.title?.value}

+
+

- {article.elements?.publish_date.value + {article.elements.publish_date?.value && `Published on ${ new Date(article.elements.publish_date.value!).toLocaleDateString("en-US", { month: "short", @@ -28,18 +35,22 @@ const FeaturedArticle: React.FC = ({ article }) => { }) }`}

+
+

- {article.elements?.introduction.value} + {article.elements.introduction?.value}

-
- {article.elements?.introduction.value && ( - - Read more - - )} - - )} - + +
+ + Read more + + + ); }; diff --git a/src/components/FeaturedComponentBase.tsx b/src/components/FeaturedComponentBase.tsx new file mode 100644 index 0000000..9eca9ee --- /dev/null +++ b/src/components/FeaturedComponentBase.tsx @@ -0,0 +1,41 @@ +import { Elements } from "@kontent-ai/delivery-sdk"; +import { FC, PropsWithChildren } from "react"; +import RenderElement from "./RenderElement"; + +type FeaturedContentProps = PropsWithChildren< + Readonly<{ + type: "article" | "event"; + image?: Elements.AssetsElement; + }> +>; + +const FeaturedComponentBase: FC = ({ type, image, children }) => { + const img = image?.value[0]; + return ( +
+
+ + {img && ( + <> + + {type === "event" ? "FEATURED EVENT" : "FEATURED ARTICLE"} + + {image.value[0].description + + )} + +
+
+ {children} +
+
+ ); +}; + +export default FeaturedComponentBase; diff --git a/src/components/FeaturedContent.tsx b/src/components/FeaturedContent.tsx index 540cf6d..8f0c232 100644 --- a/src/components/FeaturedContent.tsx +++ b/src/components/FeaturedContent.tsx @@ -1,37 +1,36 @@ -import { Elements } from "@kontent-ai/delivery-sdk"; -import { FC, PropsWithChildren } from "react"; +import { FC } from "react"; +import { isArticle, isEvent, LandingPage } from "../model"; +import PageSection from "./PageSection"; +import FeaturedArticle from "./FeaturedArticle"; +import FeaturedEvent from "./FeaturedEvent"; +import Divider from "./Divider"; -type FeaturedContentProps = PropsWithChildren< - Readonly<{ - type: "article" | "event"; - image?: Elements.AssetsElement; - }> ->; +type FeaturedContentProps = { + featuredContent: LandingPage["elements"]["featured_content"]; +}; + +const FeaturedContent: FC = ({ featuredContent }) => { + const featuredArticle = featuredContent.linkedItems.find(isArticle); + const featuredEvent = featuredContent.linkedItems.find(isEvent); -const FeaturedContent: FC = ({ type, image, children }) => { - const img = image?.value[0]; return ( -
-
- {img && ( - <> - - {type === "event" ? "FEATURED EVENT" : "FEATURED ARTICLE"} - - {image.value[0].description - + <> + {featuredArticle + && ( + + + + )} + + {featuredArticle && featuredEvent && } + + {featuredEvent + && ( + + + )} -
-
- {children} -
-
+ ); }; diff --git a/src/components/FeaturedEvent.tsx b/src/components/FeaturedEvent.tsx index a1f1d35..f9c7f15 100644 --- a/src/components/FeaturedEvent.tsx +++ b/src/components/FeaturedEvent.tsx @@ -1,67 +1,79 @@ import { FC } from "react"; -import FeaturedContent from "./FeaturedContent"; +import FeaturedComponentBase from "./FeaturedComponentBase"; import { Event } from "../model"; import { formatDate } from "../utils/date"; import { browserParse, transformToPortableText } from "@kontent-ai/rich-text-resolver"; import { PortableText } from "@portabletext/react"; import { defaultPortableRichTextComponents } from "../utils/richtext"; +import { Replace } from "../utils/types"; +import RenderElement from "./RenderElement"; type FeaturedEventProps = Readonly<{ - event: Partial; + event: Replace }>; }>; const FeaturedEvent: FC = ({ event }) => { - const descriptionPortableText = transformToPortableText(browserParse(event.elements?.description.value ?? "")); - const createTag = (text: string) => (

{text}

); - const shouldRender = event.elements?.name.value || event.elements?.description.value !== "


" - || event.elements?.start_date.value - || event.elements?.end_date.value || event.elements?.image.value.length || event.elements?.event_topic.value.length - || event.elements?.event_type.value.length; + const shouldRender = Object.entries(event.elements).length > 0; - return ( - - {shouldRender - ? ( - <> -
+ return shouldRender + ? ( + + <> +
+

- {event.elements?.name.value} + {event.elements.name?.value}

+
+

{`${ - event.elements?.start_date.value !== null - ? formatDate(event.elements?.start_date.value as string) + event.elements.start_date?.value !== null + ? formatDate(event.elements.start_date?.value as string) : "" }${ - event.elements?.end_date.value !== null - ? ` - ${formatDate(event.elements?.end_date.value as string)}` + event.elements.end_date?.value !== null + ? ` - ${formatDate(event.elements.end_date?.value as string)}` : "" }`}

-
- {event.elements?.event_type.value.map(t => createTag(t.name.toUpperCase()))} - {event.elements?.event_topic.value.map(t => createTag(t.name.toUpperCase()))} -
+
+
+ {event.elements.event_type?.value.map(t => createTag(t.name.toUpperCase()))} + {event.elements.event_topic?.value.map(t => createTag(t.name.toUpperCase()))} +
+
- +

"))} + components={defaultPortableRichTextComponents} + />
-
- {event.elements?.description.value !== "


" && ( - - Read more - - )} - - ) - : <>} - - ); + +
+ {event.elements.description?.value !== "


" && ( + + Read more + + )} + + + ) + : <>; }; export default FeaturedEvent; diff --git a/src/components/HeroImage.tsx b/src/components/HeroImage.tsx index b1a89af..2176315 100644 --- a/src/components/HeroImage.tsx +++ b/src/components/HeroImage.tsx @@ -1,11 +1,12 @@ import { Elements } from "@kontent-ai/delivery-sdk"; import { FC } from "react"; +import RenderElement from "./RenderElement"; type HeroImageProps = Readonly<{ data: { - headline?: string; - subheadline?: string; - heroImage: Elements.AssetsElement; + headline?: Elements.TextElement; + subheadline?: Elements.TextElement; + heroImage?: Elements.AssetsElement; }; }>; @@ -13,24 +14,30 @@ const HeroImage: FC = ({ data }) => { return (
-

- {data.headline} -

-

- {data.subheadline} -

+ +

+ {data.headline?.value} +

+
+ +

{data.subheadline?.value}

+
- {data.heroImage.value[0] && ( - {data.heroImage.value[0].description - - )} + + {data.heroImage?.value[0] + ? ( + {data.heroImage.value[0].description + + ) + : <>} +
); diff --git a/src/components/PageContent.tsx b/src/components/PageContent.tsx index 55af68c..dd1e9fd 100644 --- a/src/components/PageContent.tsx +++ b/src/components/PageContent.tsx @@ -1,13 +1,13 @@ import { FC } from "react"; import { PortableText, PortableTextReactComponents, PortableTextTypeComponentProps } from "@portabletext/react"; import { Elements } from "@kontent-ai/delivery-sdk"; -import { Video as VideoElement } from "../model"; +import { LandingPage, Video as VideoElement } from "../model"; import Video from "./Video"; import { browserParse, PortableTextComponent, transformToPortableText } from "@kontent-ai/rich-text-resolver"; import { defaultPortableRichTextComponents } from "../utils/richtext"; type PageContentProps = { - body: Elements.RichTextElement; + body: LandingPage["elements"]["body_copy"]; }; const PageContent: FC = ({ body }) => { diff --git a/src/components/RenderElement.tsx b/src/components/RenderElement.tsx new file mode 100644 index 0000000..54377c1 --- /dev/null +++ b/src/components/RenderElement.tsx @@ -0,0 +1,67 @@ +import { Elements } from "@kontent-ai/delivery-sdk"; +import type { PropsWithChildren } from "react"; +import { ElementCodenames } from "../model"; + +type AllElementsUnion = + | Elements.TextElement + | Elements.LinkedItemsElement + | Elements.MultipleChoiceElement + | Elements.DateTimeElement + | Elements.RichTextElement + | Elements.NumberElement + | Elements.AssetsElement + | Elements.UrlSlugElement + | Elements.TaxonomyElement + | Elements.CustomElement; + +type ElementTypeUnion = + | "text" + | "number" + | "modular_content" + | "asset" + | "date_time" + | "rich_text" + | "multiple_choice" + | "url_slug" + | "taxonomy" + | "custom"; + +type RenderElementProps = PropsWithChildren<{ + element?: AllElementsUnion; + elementCodename: ElementCodenames; + requiredElementType: ElementTypeUnion; + errorMessageClassName?: string; +}>; + +const RenderElement = (props: RenderElementProps) => { + if (!props.element) { + return ( +

+ Missing or invalid element codename{" "} + + {props.elementCodename} + . To learn more about codenames refer to the{" "} + + Kontent.ai documentation + . +

+ ); + } + + if (props.element.type !== props.requiredElementType) { + return ( +

+ Invalid type of element with codename{" "} + + {props.elementCodename} + + {". "} + Required type of element: {props.requiredElementType}. Actual type: {props.element.type}. +

+ ); + } + + return <>{props.children}; +}; + +export default RenderElement; diff --git a/src/components/Video.tsx b/src/components/Video.tsx index f2adf96..83f4b73 100644 --- a/src/components/Video.tsx +++ b/src/components/Video.tsx @@ -1,37 +1,49 @@ import { FC } from "react"; -import { Video as VideoElement } from "../model"; +import { Video as VideoType } from "../model"; +import { Replace } from "../utils/types"; +import RenderElement from "./RenderElement"; type VideoProps = { - video: Partial; + video: Replace }>; }; const Video: FC = ({ video }) => { return (
-

- {video.elements?.headline.value} -

-

- {video.elements?.description.value} -

- {video.elements?.video_link?.value && ( -
-