Merged in feat/SW-1384-filterable-carousel-cards (pull request #1235)

feat(SW-1384): add CarouselCards block to start page

* feat(SW-1384): add filterable carousel cards block to start page

* fix(SW-1384): remove unnecessary link prop from SectionHeader

* fix(SW-1384): remove uneeded undefined

* fix(SW-1384): better type safety

* feat(SW-1384): Add see all link to filterable carousel cards section header

* refactor(SW-1384): Replace FilterableCarouselCards with CarouselCards block

* fix(SW-1384): Remove CardsEnumType type definition

* fix(SW-1384):Implement code review feedback to CarouselCards

* refactor(SW-1384): Convert CarouselCardFilterEnum to const enum with type


Approved-by: Christian Andolf
This commit is contained in:
Chuma Mcphoy (We Ahead)
2025-02-04 10:59:17 +00:00
parent 8b475e0ca8
commit 2e311be924
21 changed files with 764 additions and 177 deletions

View File

@@ -0,0 +1,44 @@
import { z } from "zod"
import { tempImageVaultAssetSchema } from "../../imageVault"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
import { CardsEnum } from "@/types/enums/cards"
export const contentCardSchema = z.object({
__typename: z.literal(CardsEnum.ContentCard),
title: z.string(),
heading: z.string(),
image: tempImageVaultAssetSchema,
body_text: z.string(),
promo_text: z.string().optional(),
has_primary_button: z.boolean(),
has_secondary_button: z.boolean(),
primary_button: buttonSchema,
secondary_button: buttonSchema,
system: systemSchema,
})
export const contentCardRefSchema = z.object({
__typename: z.literal(CardsEnum.ContentCard),
primary_button: linkConnectionRefsSchema,
secondary_button: linkConnectionRefsSchema,
system: systemSchema,
})
export function transformContentCard(card: typeof contentCardSchema._type) {
return {
__typename: card.__typename,
title: card.title,
heading: card.heading,
image: card.image,
bodyText: card.body_text,
promoText: card.promo_text,
primaryButton: card.has_primary_button ? card.primary_button : undefined,
secondaryButton: card.has_secondary_button
? card.secondary_button
: undefined,
}
}

View File

@@ -0,0 +1,43 @@
import { z } from "zod"
import { tempImageVaultAssetSchema } from "../../imageVault"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
import { CardsEnum } from "@/types/enums/cards"
export const infoCardBlockSchema = z.object({
__typename: z.literal(CardsEnum.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 infoCardBlockRefsSchema = z.object({
__typename: z.literal(CardsEnum.InfoCard),
primary_button: linkConnectionRefsSchema,
secondary_button: linkConnectionRefsSchema,
system: systemSchema,
})

View File

@@ -0,0 +1,25 @@
import { z } from "zod"
import { tempImageVaultAssetSchema } from "../../imageVault"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
import { CardsEnum } from "@/types/enums/cards"
export const loyaltyCardBlockSchema = z.object({
__typename: z.literal(CardsEnum.LoyaltyCard),
body_text: z.string().optional(),
heading: z.string().optional().default(""),
// JSON - ImageVault Image
image: tempImageVaultAssetSchema,
link: buttonSchema,
system: systemSchema,
title: z.string().optional(),
})
export const loyaltyCardBlockRefsSchema = z.object({
__typename: z.literal(CardsEnum.LoyaltyCard),
link: linkConnectionRefsSchema,
system: systemSchema,
})

View File

@@ -0,0 +1,121 @@
import { z } from "zod"
import { tempImageVaultAssetSchema } from "../../imageVault"
import {
accountPageSchema,
collectionPageSchema,
contentPageSchema,
destinationCityPageSchema,
destinationCountryPageSchema,
destinationOverviewPageSchema,
hotelPageSchema,
loyaltyPageSchema,
startPageSchema,
transformPageLink,
} from "../../pageLinks"
import { systemSchema } from "../../system"
import { imageSchema } from "../image"
import { imageContainerSchema } from "../imageContainer"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
import { CardsEnum } from "@/types/enums/cards"
export const teaserCardBlockSchema = z.object({
__typename: z.literal(CardsEnum.TeaserCard),
heading: z.string().default(""),
body_text: z.string().default(""),
image: tempImageVaultAssetSchema,
primary_button: buttonSchema,
secondary_button: buttonSchema,
has_primary_button: z.boolean().default(false),
has_secondary_button: z.boolean().default(false),
has_sidepeek_button: z.boolean().default(false),
sidepeek_button: z
.object({
call_to_action_text: z.string().optional().default(""),
})
.optional(),
sidepeek_content: z
.object({
heading: z.string(),
content: z.object({
json: z.any(),
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: z
.discriminatedUnion("__typename", [
imageContainerSchema,
imageSchema,
accountPageSchema,
collectionPageSchema,
contentPageSchema,
destinationCityPageSchema,
destinationCountryPageSchema,
destinationOverviewPageSchema,
hotelPageSchema,
loyaltyPageSchema,
startPageSchema,
])
.transform((data) => {
const link = transformPageLink(data)
if (link) {
return link
}
return data
}),
})
),
}),
}),
has_primary_button: z.boolean().default(false),
primary_button: buttonSchema,
has_secondary_button: z.boolean().default(false),
secondary_button: buttonSchema,
})
.optional()
.transform((data) => {
if (!data) {
return
}
return {
...data,
primary_button: data.has_primary_button
? data.primary_button
: undefined,
secondary_button: data.has_secondary_button
? data.secondary_button
: undefined,
}
}),
system: systemSchema,
})
export function transformTeaserCardBlock(
card: typeof teaserCardBlockSchema._type
) {
return {
__typename: card.__typename,
body_text: card.body_text,
heading: card.heading,
primaryButton: card.has_primary_button ? card.primary_button : undefined,
secondaryButton: card.has_secondary_button
? card.secondary_button
: undefined,
sidePeekButton: card.has_sidepeek_button ? card.sidepeek_button : undefined,
sidePeekContent: card.has_sidepeek_button
? card.sidepeek_content
: undefined,
image: card.image,
system: card.system,
}
}
export const teaserCardBlockRefsSchema = z.object({
__typename: z.literal(CardsEnum.TeaserCard),
primary_button: linkConnectionRefsSchema,
secondary_button: linkConnectionRefsSchema,
system: systemSchema,
})

View File

@@ -1,23 +1,23 @@
import { z } from "zod"
import { tempImageVaultAssetSchema } from "../imageVault"
import {
accountPageSchema,
collectionPageSchema,
contentPageSchema,
destinationCityPageSchema,
destinationCountryPageSchema,
destinationOverviewPageSchema,
hotelPageSchema,
loyaltyPageSchema,
startPageSchema,
transformPageLink,
} from "../pageLinks"
import { systemSchema } from "../system"
import {
infoCardBlockRefsSchema,
infoCardBlockSchema,
transformInfoCardBlock,
} from "./cards/infoCard"
import {
loyaltyCardBlockRefsSchema,
loyaltyCardBlockSchema,
} from "./cards/loyaltyCard"
import {
teaserCardBlockRefsSchema,
teaserCardBlockSchema,
transformTeaserCardBlock,
} from "./cards/teaserCard"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "./utils/linkConnection"
import { imageSchema } from "./image"
import { imageContainerSchema } from "./imageContainer"
import { BlocksEnums } from "@/types/enums/blocks"
import { CardsGridEnum, CardsGridLayoutEnum } from "@/types/enums/cardsGrid"
@@ -54,137 +54,6 @@ export function transformCardBlock(card: typeof cardBlockSchema._type) {
}
}
export const teaserCardBlockSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.TeaserCard),
heading: z.string().default(""),
body_text: z.string().default(""),
image: tempImageVaultAssetSchema,
primary_button: buttonSchema,
secondary_button: buttonSchema,
has_primary_button: z.boolean().default(false),
has_secondary_button: z.boolean().default(false),
has_sidepeek_button: z.boolean().default(false),
sidepeek_button: z
.object({
call_to_action_text: z.string().optional().default(""),
})
.optional(),
sidepeek_content: z
.object({
heading: z.string(),
content: z.object({
json: z.any(),
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: z
.discriminatedUnion("__typename", [
imageContainerSchema,
imageSchema,
accountPageSchema,
collectionPageSchema,
contentPageSchema,
destinationCityPageSchema,
destinationCountryPageSchema,
destinationOverviewPageSchema,
hotelPageSchema,
loyaltyPageSchema,
startPageSchema,
])
.transform((data) => {
const link = transformPageLink(data)
if (link) {
return link
}
return data
}),
})
),
}),
}),
has_primary_button: z.boolean().default(false),
primary_button: buttonSchema,
has_secondary_button: z.boolean().default(false),
secondary_button: buttonSchema,
})
.optional()
.transform((data) => {
if (!data) {
return undefined
}
return {
...data,
primary_button: data.has_primary_button
? data.primary_button
: undefined,
secondary_button: data.has_secondary_button
? data.secondary_button
: undefined,
}
}),
system: systemSchema,
})
export function transformTeaserCardBlock(
card: typeof teaserCardBlockSchema._type
) {
return {
__typename: card.__typename,
body_text: card.body_text,
heading: card.heading,
primaryButton: card.has_primary_button ? card.primary_button : undefined,
secondaryButton: card.has_secondary_button
? card.secondary_button
: undefined,
sidePeekButton: card.has_sidepeek_button ? card.sidepeek_button : undefined,
sidePeekContent: card.has_sidepeek_button
? card.sidepeek_content
: undefined,
image: card.image,
system: card.system,
}
}
const loyaltyCardBlockSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.LoyaltyCard),
body_text: z.string().optional(),
heading: z.string().optional().default(""),
// JSON - ImageVault Image
image: tempImageVaultAssetSchema,
link: buttonSchema,
system: systemSchema,
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)
@@ -261,26 +130,6 @@ export function transformCardBlockRefs(
return cards
}
const loyaltyCardBlockRefsSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.LoyaltyCard),
link: linkConnectionRefsSchema,
system: systemSchema,
})
export const teaserCardBlockRefsSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.TeaserCard),
primary_button: linkConnectionRefsSchema,
secondary_button: linkConnectionRefsSchema,
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({

View File

@@ -0,0 +1,137 @@
import { z } from "zod"
import {
contentCardRefSchema,
contentCardSchema,
transformContentCard,
} from "./cards/contentCard"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "./utils/linkConnection"
import { BlocksEnums } from "@/types/enums/blocks"
import {
type CarouselCardFilter,
CarouselCardFilterEnum,
} from "@/types/enums/carouselCards"
const commonFields = {
heading: z.string().optional(),
link: buttonSchema.optional(),
} as const
const carouselCardsWithFilters = z.object({
...commonFields,
enable_filters: z.literal(true),
card_groups: z.array(
z.object({
filter_category: z.object({
filter_identifier: z.nativeEnum(CarouselCardFilterEnum),
filter_label: z.string(),
}),
cardConnection: z.object({
edges: z.array(z.object({ node: contentCardSchema })),
}),
})
),
default_filter: z.nativeEnum(CarouselCardFilterEnum),
})
const carouselCardsWithoutFilters = z.object({
...commonFields,
enable_filters: z.literal(false),
card_groups: z.array(
z.object({
filter_category: z.object({
filter_identifier: z.null(),
filter_label: z.string(),
}),
cardConnection: z.object({
edges: z.array(z.object({ node: contentCardSchema })),
}),
})
),
default_filter: z.null(),
})
export const carouselCardsSchema = z.object({
typename: z
.literal(BlocksEnums.block.CarouselCards)
.optional()
.default(BlocksEnums.block.CarouselCards),
carousel_cards: z
.discriminatedUnion("enable_filters", [
carouselCardsWithFilters,
carouselCardsWithoutFilters,
])
.transform((data) => {
if (!data.enable_filters) {
return {
heading: data.heading,
enableFilters: false,
filterCategories: [],
cards: data.card_groups.flatMap((group) =>
group.cardConnection.edges.map((edge) =>
transformContentCard(edge.node)
)
),
defaultFilter: null,
link: data.link
? { href: data.link.href, text: data.link.title }
: undefined,
}
}
const filterCategories = data.card_groups.reduce<
Array<{
identifier: CarouselCardFilter
label: string
}>
>((acc, group) => {
const identifier = group.filter_category.filter_identifier
if (!acc.some((category) => category.identifier === identifier)) {
acc.push({
identifier,
label: group.filter_category.filter_label,
})
}
return acc
}, [])
return {
heading: data.heading,
enableFilters: true,
filterCategories,
cards: data.card_groups.flatMap((group) =>
group.cardConnection.edges.map((edge) => ({
...transformContentCard(edge.node),
filterId: group.filter_category.filter_identifier,
}))
),
defaultFilter: data.default_filter,
link: data.link
? { href: data.link.href, text: data.link.title }
: undefined,
}
}),
})
export const carouselCardsRefsSchema = z.object({
typename: z
.literal(BlocksEnums.block.CarouselCards)
.optional()
.default(BlocksEnums.block.CarouselCards),
carousel_cards: z.object({
card_groups: z.array(
z.object({
cardConnection: z.object({
edges: z.array(
z.object({
node: contentCardRefSchema,
})
),
}),
})
),
link: linkConnectionRefsSchema.optional(),
}),
})

View File

@@ -3,9 +3,9 @@ import { z } from "zod"
import {
teaserCardBlockRefsSchema,
teaserCardBlockSchema,
transformCardBlockRefs,
transformTeaserCardBlock,
} from "../blocks/cardsGrid"
} from "../blocks/cards/teaserCard"
import { transformCardBlockRefs } from "../blocks/cardsGrid"
import { SidebarEnums } from "@/types/enums/sidebar"

View File

@@ -6,6 +6,10 @@ import {
cardGridRefsSchema,
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
carouselCardsRefsSchema,
carouselCardsSchema,
} from "../schemas/blocks/carouselCards"
import {
fullWidthCampaignBlockRefsSchema,
fullWidthCampaignBlockSchema,
@@ -21,6 +25,12 @@ const startPageCards = z
})
.merge(cardsGridSchema)
const startPageCarouselCards = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.CarouselCards),
})
.merge(carouselCardsSchema)
const startPageFullWidthCampaign = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.FullWidthCampaign),
@@ -30,6 +40,7 @@ const startPageFullWidthCampaign = z
export const blocksSchema = z.discriminatedUnion("__typename", [
startPageCards,
startPageFullWidthCampaign,
startPageCarouselCards,
])
export const startPageSchema = z.object({
@@ -65,9 +76,16 @@ const startPageFullWidthCampaignRef = z
})
.merge(fullWidthCampaignBlockRefsSchema)
const startPageCarouselCardsRef = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.CarouselCards),
})
.merge(carouselCardsRefsSchema)
const startPageBlockRefsItem = z.discriminatedUnion("__typename", [
startPageCardsRefs,
startPageFullWidthCampaignRef,
startPageCarouselCardsRef,
])
export const startPageRefsSchema = z.object({