From b57174647fef8d0d81e4930363fea3fbeca11880 Mon Sep 17 00:00:00 2001 From: "Chuma Mcphoy (We Ahead)" Date: Wed, 29 Jan 2025 10:21:33 +0000 Subject: [PATCH] Merged in SW-1382-start-page-offers-section (pull request #1221) SW-1382 start page offers section * feat(SW-1382): Add InfoCard component to CardsGrid and StartPage * feat(SW-1382): Add dynamic image positioning for InfoCard in CardsGrid * refactor(SW-1382): Update InfoCard data transformation and prop naming * fix(SW-1382): Add flex display to InfoCard image container Approved-by: Christian Andolf Approved-by: Erik Tiekstra --- components/Blocks/CardsGrid.tsx | 16 +++- .../ContentType/StartPage/InfoCard/index.tsx | 46 ++++++++++++ .../StartPage/InfoCard/infoCard.module.css | 47 ++++++++++++ components/ContentType/StartPage/index.tsx | 18 +++-- .../StartPage/startPage.module.css | 9 +++ .../Fragments/Blocks/CardsGrid.graphql | 38 ++++++++++ lib/graphql/Fragments/Blocks/InfoCard.graphql | 73 +++++++++++++++++++ .../Fragments/Blocks/Refs/InfoCard.graphql | 51 +++++++++++++ lib/graphql/Query/StartPage/StartPage.graphql | 9 +++ .../contentstack/schemas/blocks/cardsGrid.ts | 43 ++++++++++- .../contentstack/schemas/imageVault.ts | 1 + .../routers/contentstack/startPage/output.ts | 28 +++++++ types/components/blocks/index.ts | 2 + types/components/blocks/infoCard.ts | 22 ++++++ types/enums/cardsGrid.ts | 1 + types/enums/startPage.ts | 7 ++ types/trpc/routers/contentstack/startPage.ts | 3 + 17 files changed, 405 insertions(+), 9 deletions(-) create mode 100644 components/ContentType/StartPage/InfoCard/index.tsx create mode 100644 components/ContentType/StartPage/InfoCard/infoCard.module.css create mode 100644 lib/graphql/Fragments/Blocks/InfoCard.graphql create mode 100644 lib/graphql/Fragments/Blocks/Refs/InfoCard.graphql create mode 100644 types/components/blocks/infoCard.ts create mode 100644 types/enums/startPage.ts diff --git a/components/Blocks/CardsGrid.tsx b/components/Blocks/CardsGrid.tsx index aaebd9df9..9cc21873e 100644 --- a/components/Blocks/CardsGrid.tsx +++ b/components/Blocks/CardsGrid.tsx @@ -1,3 +1,4 @@ +import InfoCard from "@/components/ContentType/StartPage/InfoCard" import SectionContainer from "@/components/Section/Container" import SectionHeader from "@/components/Section/Header" import Card from "@/components/TempDesignSystem/Card" @@ -37,7 +38,7 @@ export default function CardsGrid({ topTitle={firstItem} /> - {cards_grid.cards.map((card) => { + {cards_grid.cards.map((card, index) => { switch (card.__typename) { case CardsGridEnum.cards.Card: return ( @@ -55,6 +56,19 @@ export default function CardsGrid({ imageGradient /> ) + case CardsGridEnum.cards.InfoCard: + return ( + + ) case CardsGridEnum.cards.TeaserCard: return ( + {image ? ( +
+ {image.title} +
+ ) : null} + + + ) +} diff --git a/components/ContentType/StartPage/InfoCard/infoCard.module.css b/components/ContentType/StartPage/InfoCard/infoCard.module.css new file mode 100644 index 000000000..18057a933 --- /dev/null +++ b/components/ContentType/StartPage/InfoCard/infoCard.module.css @@ -0,0 +1,47 @@ +.container { + display: grid; + gap: var(--Spacing-x-quarter); +} + +.image { + object-fit: cover; + overflow: hidden; + width: 100%; + height: 179px; /* Exact mobile height from Figma */ + border-radius: var(--Corner-radius-Medium); +} + +.imageContainer { + display: flex; +} + +@media (min-width: 768px) { + .container:has(.imageContainer) { + gap: var(--Spacing-x2); + align-items: center; + } + + .container:not(:has(.imageContainer)) { + grid-template-columns: 1fr; + } + + .container:has(.image-right) { + grid-template-columns: 456px 1fr; + } + + .container:has(.image-left) { + grid-template-columns: 1fr 456px; + } + + .image-right { + order: 2; + } + + .image-left { + order: 0; + } + + .image { + height: 320px; /* Desktop height from Figma */ + } +} diff --git a/components/ContentType/StartPage/index.tsx b/components/ContentType/StartPage/index.tsx index 670552c4c..33972541f 100644 --- a/components/ContentType/StartPage/index.tsx +++ b/components/ContentType/StartPage/index.tsx @@ -2,6 +2,7 @@ import { Suspense } from "react" import { getStartPage } from "@/lib/trpc/memoizedRequests" +import Blocks from "@/components/Blocks" import Image from "@/components/Image" import PageContainer from "@/components/PageContainer" import Title from "@/components/TempDesignSystem/Text/Title" @@ -15,7 +16,7 @@ export default async function StartPage() { return null } - const { header } = content.startPage + const { header, blocks } = content.startPage return ( @@ -41,12 +42,15 @@ export default async function StartPage() { /> ) : null} -
- JSON data -
{JSON.stringify(content, null, 2)}
-
+
+
+ JSON data +
{JSON.stringify(content, null, 2)}
+
+ {blocks ? : null} +
diff --git a/components/ContentType/StartPage/startPage.module.css b/components/ContentType/StartPage/startPage.module.css index 00caa35d9..378d16845 100644 --- a/components/ContentType/StartPage/startPage.module.css +++ b/components/ContentType/StartPage/startPage.module.css @@ -37,3 +37,12 @@ .topImage { max-width: 100%; } + +.main { + display: grid; + width: 100%; + gap: var(--Spacing-x6); + margin: 0 auto; + max-width: var(--max-width-content); + padding: var(--Spacing-x4) 0; +} diff --git a/lib/graphql/Fragments/Blocks/CardsGrid.graphql b/lib/graphql/Fragments/Blocks/CardsGrid.graphql index 2011d8491..dcde71205 100644 --- a/lib/graphql/Fragments/Blocks/CardsGrid.graphql +++ b/lib/graphql/Fragments/Blocks/CardsGrid.graphql @@ -1,8 +1,10 @@ #import "./Card.graphql" +#import "./InfoCard.graphql" #import "./LoyaltyCard.graphql" #import "./TeaserCard.graphql" #import "./Refs/Card.graphql" +#import "./Refs/InfoCard.graphql" #import "./Refs/LoyaltyCard.graphql" #import "./Refs/TeaserCard.graphql" @@ -17,6 +19,7 @@ fragment CardsGrid_ContentPage on ContentPageBlocksCardsGrid { node { __typename ...CardBlock + ...InfoCardBlock ...LoyaltyCardBlock ...TeaserCardBlock } @@ -32,6 +35,7 @@ fragment CardsGrid_ContentPageRefs on ContentPageBlocksCardsGrid { node { __typename ...CardBlockRef + ...InfoCardBlockRef ...LoyaltyCardBlockRef ...TeaserCardBlockRef } @@ -103,3 +107,37 @@ fragment CardsGrid_LoyaltyPageRefs on LoyaltyPageBlocksCardsGrid { } } } + +fragment CardsGrid_StartPage on StartPageBlocksCardsGrid { + cards_grid { + layout + preamble + theme + title + cardConnection(limit: 10) { + edges { + node { + __typename + ...CardBlock + ...TeaserCardBlock + ...InfoCardBlock + } + } + } + } +} + +fragment CardsGrid_StartPageRefs on StartPageBlocksCardsGrid { + cards_grid { + cardConnection(limit: 10) { + edges { + node { + __typename + ...CardBlockRef + ...TeaserCardBlockRef + ...InfoCardBlockRef + } + } + } + } +} diff --git a/lib/graphql/Fragments/Blocks/InfoCard.graphql b/lib/graphql/Fragments/Blocks/InfoCard.graphql new file mode 100644 index 000000000..dd348fe68 --- /dev/null +++ b/lib/graphql/Fragments/Blocks/InfoCard.graphql @@ -0,0 +1,73 @@ +#import "../System.graphql" + +#import "../PageLink/AccountPageLink.graphql" +#import "../PageLink/CollectionPageLink.graphql" +#import "../PageLink/ContentPageLink.graphql" +#import "../PageLink/DestinationCityPageLink.graphql" +#import "../PageLink/DestinationCountryPageLink.graphql" +#import "../PageLink/DestinationOverviewPageLink.graphql" +#import "../PageLink/HotelPageLink.graphql" +#import "../PageLink/LoyaltyPageLink.graphql" +#import "../PageLink/StartPageLink.graphql" + +fragment InfoCardBlock on InfoCard { + scripted_top_title + heading + body_text + image + title + + primary_button { + is_contentstack_link + cta_text + open_in_new_tab + external_link { + title + href + } + linkConnection { + edges { + node { + __typename + ...AccountPageLink + ...CollectionPageLink + ...ContentPageLink + ...DestinationCityPageLink + ...DestinationCountryPageLink + ...DestinationOverviewPageLink + ...HotelPageLink + ...LoyaltyPageLink + ...StartPageLink + } + } + } + } + secondary_button { + is_contentstack_link + cta_text + open_in_new_tab + external_link { + title + href + } + linkConnection { + edges { + node { + __typename + ...AccountPageLink + ...CollectionPageLink + ...ContentPageLink + ...DestinationCityPageLink + ...DestinationCountryPageLink + ...DestinationOverviewPageLink + ...HotelPageLink + ...LoyaltyPageLink + ...StartPageLink + } + } + } + } + system { + ...System + } +} diff --git a/lib/graphql/Fragments/Blocks/Refs/InfoCard.graphql b/lib/graphql/Fragments/Blocks/Refs/InfoCard.graphql new file mode 100644 index 000000000..945eeca6a --- /dev/null +++ b/lib/graphql/Fragments/Blocks/Refs/InfoCard.graphql @@ -0,0 +1,51 @@ +#import "../../AccountPage/Ref.graphql" +#import "../../CollectionPage/Ref.graphql" +#import "../../ContentPage/Ref.graphql" +#import "../../DestinationCityPage/Ref.graphql" +#import "../../DestinationCountryPage/Ref.graphql" +#import "../../DestinationOverviewPage/Ref.graphql" +#import "../../HotelPage/Ref.graphql" +#import "../../LoyaltyPage/Ref.graphql" +#import "../../StartPage/Ref.graphql" + +fragment InfoCardBlockRef on InfoCard { + secondary_button { + linkConnection { + edges { + node { + __typename + ...AccountPageRef + ...CollectionPageRef + ...ContentPageRef + ...DestinationCityPageRef + ...DestinationCountryPageRef + ...DestinationOverviewPageRef + ...HotelPageRef + ...LoyaltyPageRef + ...StartPageRef + } + } + } + } + primary_button { + linkConnection { + edges { + node { + __typename + ...AccountPageRef + ...CollectionPageRef + ...ContentPageRef + ...DestinationCityPageRef + ...DestinationCountryPageRef + ...DestinationOverviewPageRef + ...HotelPageRef + ...LoyaltyPageRef + ...StartPageRef + } + } + } + } + system { + ...System + } +} diff --git a/lib/graphql/Query/StartPage/StartPage.graphql b/lib/graphql/Query/StartPage/StartPage.graphql index de058a81f..a3bcfea4e 100644 --- a/lib/graphql/Query/StartPage/StartPage.graphql +++ b/lib/graphql/Query/StartPage/StartPage.graphql @@ -1,4 +1,5 @@ #import "../../Fragments/System.graphql" +#import "../../Fragments/Blocks/CardsGrid.graphql" query GetStartPage($locale: String!, $uid: String!) { start_page(uid: $uid, locale: $locale) { @@ -13,6 +14,10 @@ query GetStartPage($locale: String!, $uid: String!) { created_at updated_at } + blocks { + __typename + ...CardsGrid_StartPage + } } trackingProps: start_page(locale: "en", uid: $uid) { url @@ -21,6 +26,10 @@ query GetStartPage($locale: String!, $uid: String!) { query GetStartPageRefs($locale: String!, $uid: String!) { start_page(locale: $locale, uid: $uid) { + blocks { + __typename + ...CardsGrid_StartPageRefs + } system { ...System } diff --git a/server/routers/contentstack/schemas/blocks/cardsGrid.ts b/server/routers/contentstack/schemas/blocks/cardsGrid.ts index 44c22c06e..be543d124 100644 --- a/server/routers/contentstack/schemas/blocks/cardsGrid.ts +++ b/server/routers/contentstack/schemas/blocks/cardsGrid.ts @@ -157,6 +157,34 @@ const loyaltyCardBlockSchema = z.object({ title: z.string().optional(), }) +export const infoCardBlockSchema = z.object({ + __typename: z.literal(CardsGridEnum.cards.InfoCard), + scripted_top_title: z.string().optional(), + heading: z.string().optional().default(""), + body_text: z.string().optional().default(""), + image: tempImageVaultAssetSchema, + title: z.string().optional(), + primary_button: buttonSchema.optional().nullable(), + secondary_button: buttonSchema.optional().nullable(), + system: systemSchema, +}) + +export function transformInfoCardBlock(card: typeof infoCardBlockSchema._type) { + return { + __typename: card.__typename, + scriptedTopTitle: card.scripted_top_title, + heading: card.heading, + bodyText: card.body_text, + image: card.image, + title: card.title, + primaryButton: card.primary_button?.href ? card.primary_button : undefined, + secondaryButton: card.secondary_button?.href + ? card.secondary_button + : undefined, + system: card.system, + } +} + export const cardsGridSchema = z.object({ typename: z .literal(BlocksEnums.block.CardsGrid) @@ -171,6 +199,7 @@ export const cardsGridSchema = z.object({ cardBlockSchema, loyaltyCardBlockSchema, teaserCardBlockSchema, + infoCardBlockSchema, ]), }) ), @@ -191,6 +220,8 @@ export const cardsGridSchema = z.object({ return transformCardBlock(card.node) } else if (card.node.__typename === CardsGridEnum.cards.TeaserCard) { return transformTeaserCardBlock(card.node) + } else if (card.node.__typename === CardsGridEnum.cards.InfoCard) { + return transformInfoCardBlock(card.node) } else { return { __typename: card.node.__typename, @@ -218,6 +249,7 @@ export function transformCardBlockRefs( card: | typeof cardBlockRefsSchema._type | typeof teaserCardBlockRefsSchema._type + | typeof infoCardBlockRefsSchema._type ) { const cards = [card.system] if (card.primary_button) { @@ -242,6 +274,13 @@ export const teaserCardBlockRefsSchema = z.object({ system: systemSchema, }) +export const infoCardBlockRefsSchema = z.object({ + __typename: z.literal(CardsGridEnum.cards.InfoCard), + primary_button: linkConnectionRefsSchema, + secondary_button: linkConnectionRefsSchema, + system: systemSchema, +}) + export const cardGridRefsSchema = z.object({ cards_grid: z .object({ @@ -252,6 +291,7 @@ export const cardGridRefsSchema = z.object({ cardBlockRefsSchema, loyaltyCardBlockRefsSchema, teaserCardBlockRefsSchema, + infoCardBlockRefsSchema, ]), }) ), @@ -262,7 +302,8 @@ export const cardGridRefsSchema = z.object({ .map(({ node }) => { if ( node.__typename === CardsGridEnum.cards.Card || - node.__typename === CardsGridEnum.cards.TeaserCard + node.__typename === CardsGridEnum.cards.TeaserCard || + node.__typename === CardsGridEnum.cards.InfoCard ) { return transformCardBlockRefs(node) } else { diff --git a/server/routers/contentstack/schemas/imageVault.ts b/server/routers/contentstack/schemas/imageVault.ts index 35cfaadd6..244992d12 100644 --- a/server/routers/contentstack/schemas/imageVault.ts +++ b/server/routers/contentstack/schemas/imageVault.ts @@ -136,6 +136,7 @@ export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform( ) export const tempImageVaultAssetSchema = imageVaultAssetSchema + .nullable() .optional() .or( // Temp since there is a bug in Contentstack diff --git a/server/routers/contentstack/startPage/output.ts b/server/routers/contentstack/startPage/output.ts index e9247a5f5..f52c911eb 100644 --- a/server/routers/contentstack/startPage/output.ts +++ b/server/routers/contentstack/startPage/output.ts @@ -1,10 +1,27 @@ import { z } from "zod" +import { discriminatedUnionArray } from "@/lib/discriminatedUnion" + +import { + cardGridRefsSchema, + cardsGridSchema, +} from "../schemas/blocks/cardsGrid" import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { systemSchema } from "../schemas/system" +import { StartPageEnum } from "@/types/enums/startPage" + +export const startPageCards = z + .object({ + __typename: z.literal(StartPageEnum.ContentStack.blocks.CardsGrid), + }) + .merge(cardsGridSchema) + +export const blocksSchema = z.discriminatedUnion("__typename", [startPageCards]) + export const startPageSchema = z.object({ start_page: z.object({ + blocks: discriminatedUnionArray(blocksSchema.options).nullable(), title: z.string(), header: z.object({ heading: z.string(), @@ -23,8 +40,19 @@ export const startPageSchema = z.object({ }) /** REFS */ +const startPageCardsRefs = z + .object({ + __typename: z.literal(StartPageEnum.ContentStack.blocks.CardsGrid), + }) + .merge(cardGridRefsSchema) + +const startPageBlockRefsItem = z.discriminatedUnion("__typename", [ + startPageCardsRefs, +]) + export const startPageRefsSchema = z.object({ start_page: z.object({ + blocks: discriminatedUnionArray(startPageBlockRefsItem.options).nullable(), system: systemSchema, }), }) diff --git a/types/components/blocks/index.ts b/types/components/blocks/index.ts index 501e8dfc9..9ecad9a70 100644 --- a/types/components/blocks/index.ts +++ b/types/components/blocks/index.ts @@ -2,12 +2,14 @@ import type { Block as AccountPageBlock } from "@/types/trpc/routers/contentstac import type { Block as CollectionPageBlock } from "@/types/trpc/routers/contentstack/collectionPage" import type { Block as ContentPageBlock } from "@/types/trpc/routers/contentstack/contentPage" import type { Block as LoyaltyPageBlock } from "@/types/trpc/routers/contentstack/loyaltyPage" +import type { Block as StartPageBlock } from "@/types/trpc/routers/contentstack/startPage" export type Blocks = | AccountPageBlock | CollectionPageBlock | ContentPageBlock | LoyaltyPageBlock + | StartPageBlock export interface BlocksProps { blocks: Blocks[] diff --git a/types/components/blocks/infoCard.ts b/types/components/blocks/infoCard.ts new file mode 100644 index 000000000..a4dc42c7b --- /dev/null +++ b/types/components/blocks/infoCard.ts @@ -0,0 +1,22 @@ +import type { VariantProps } from "class-variance-authority" + +import type { ImageVaultAsset } from "@/types/components/imageVault" +import type { CardProps } from "@/components/TempDesignSystem/Card/card" +import type { cardVariants } from "@/components/TempDesignSystem/Card/variants" + +type CardTheme = Exclude< + NonNullable["theme"]>, + "image" +> + +export interface InfoCardProps { + scriptedTopTitle?: string + heading: string + bodyText: string + image?: ImageVaultAsset + imagePosition?: "left" | "right" + primaryButton?: CardProps["primaryButton"] + secondaryButton?: CardProps["secondaryButton"] + theme?: CardTheme + className?: string +} diff --git a/types/enums/cardsGrid.ts b/types/enums/cardsGrid.ts index 314a26926..b38a51e2f 100644 --- a/types/enums/cardsGrid.ts +++ b/types/enums/cardsGrid.ts @@ -3,6 +3,7 @@ export namespace CardsGridEnum { Card = "Card", LoyaltyCard = "LoyaltyCard", TeaserCard = "TeaserCard", + InfoCard = "InfoCard", } } diff --git a/types/enums/startPage.ts b/types/enums/startPage.ts new file mode 100644 index 000000000..fff235db4 --- /dev/null +++ b/types/enums/startPage.ts @@ -0,0 +1,7 @@ +export namespace StartPageEnum { + export namespace ContentStack { + export const enum blocks { + CardsGrid = "StartPageBlocksCardsGrid", + } + } +} diff --git a/types/trpc/routers/contentstack/startPage.ts b/types/trpc/routers/contentstack/startPage.ts index 8966d1569..61219679e 100644 --- a/types/trpc/routers/contentstack/startPage.ts +++ b/types/trpc/routers/contentstack/startPage.ts @@ -1,6 +1,7 @@ import type { z } from "zod" import type { + blocksSchema, startPageRefsSchema, startPageSchema, } from "@/server/routers/contentstack/startPage/output" @@ -12,3 +13,5 @@ export interface GetStartPageRefsSchema extends z.input {} export interface StartPageRefs extends z.output {} + +export type Block = z.output