Merged in feat/LOY-419-highlight-promo-campaigns (pull request #3308)

feat(LOY-419): add promo campaigns to Campaign Overview Page

* feat(LOY-419): add promo campaigns as top campaign and all campaigns

* refactor(LOY-419)


Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
Matilda Landström
2025-12-10 12:29:23 +00:00
parent fde77a06ce
commit 5bcbc23732
9 changed files with 181 additions and 47 deletions

View File

@@ -1,4 +1,5 @@
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { ContentEnum } from "@scandic-hotels/trpc/types/content"
import CampaignHotelListing from "@/components/Blocks/CampaignHotelListing" import CampaignHotelListing from "@/components/Blocks/CampaignHotelListing"
import CampaignHero from "@/components/ContentType/CampaignPage/Hero" import CampaignHero from "@/components/ContentType/CampaignPage/Hero"
@@ -13,10 +14,12 @@ interface TopCampaignProps {
topCampaign: CampaignOverviewPageData["topCampaign"] topCampaign: CampaignOverviewPageData["topCampaign"]
} }
export default async function TopCampaign({ topCampaign }: TopCampaignProps) { export async function TopCampaign({ topCampaign }: TopCampaignProps) {
const lang = await getLang() const lang = await getLang()
const intl = await getIntl() const intl = await getIntl()
const { campaign, heading } = topCampaign const { campaign, heading } = topCampaign
const buttonData = { const buttonData = {
cta: intl.formatMessage({ cta: intl.formatMessage({
id: "campaign.exploreTheOffer", id: "campaign.exploreTheOffer",
@@ -25,6 +28,41 @@ export default async function TopCampaign({ topCampaign }: TopCampaignProps) {
url: `/${lang}${campaign.url}`, url: `/${lang}${campaign.url}`,
} }
function CampaignContent() {
switch (campaign.typename) {
case ContentEnum.blocks.CampaignPage:
return (
<>
<CampaignHero {...campaign.hero} button={buttonData} />
<CampaignHotelListing
heading={
campaign.hotel_listing.heading ||
intl.formatMessage({
id: "campaignPage.hotelsIncludedInOffer",
defaultMessage: "Hotels included in this offer",
})
}
hotelIds={campaign.hotel_listing.hotelIds}
visibleCountMobile={3}
visibleCountDesktop={3}
/>
</>
)
case ContentEnum.blocks.PromoCampaignPage:
if (!campaign.hero) return null
return (
<CampaignHero
{...campaign.hero}
button={buttonData}
rate_text={null}
campaign_text={null}
/>
)
default:
return null
}
}
return ( return (
<section className={styles.topCampaign}> <section className={styles.topCampaign}>
{heading ? ( {heading ? (
@@ -32,19 +70,7 @@ export default async function TopCampaign({ topCampaign }: TopCampaignProps) {
<h2 className={styles.heading}>{heading}</h2> <h2 className={styles.heading}>{heading}</h2>
</Typography> </Typography>
) : null} ) : null}
<CampaignHero {...campaign.hero} button={buttonData} /> <CampaignContent />
<CampaignHotelListing
heading={
campaign.hotel_listing.heading ||
intl.formatMessage({
id: "campaignPage.hotelsIncludedInOffer",
defaultMessage: "Hotels included in this offer",
})
}
hotelIds={campaign.hotel_listing.hotelIds}
visibleCountMobile={3}
visibleCountDesktop={3}
/>
</section> </section>
) )
} }

View File

@@ -6,7 +6,7 @@ import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
import { getCampaignOverviewPage } from "@/lib/trpc/memoizedRequests" import { getCampaignOverviewPage } from "@/lib/trpc/memoizedRequests"
import TopCampaign from "@/components/ContentType/CampaignOverviewPage/TopCampaign" import { TopCampaign } from "@/components/ContentType/CampaignOverviewPage/TopCampaign"
import LinkChips from "@/components/TempDesignSystem/LinkChips" import LinkChips from "@/components/TempDesignSystem/LinkChips"
import Blocks from "./Blocks" import Blocks from "./Blocks"
@@ -23,7 +23,6 @@ export default async function CampaignOverviewPage() {
const { campaign_overview_page, tracking } = pageData const { campaign_overview_page, tracking } = pageData
const { header, topCampaign, blocks } = campaign_overview_page const { header, topCampaign, blocks } = campaign_overview_page
return ( return (
<> <>
<Suspense fallback={<CampaignOverviewPageSkeleton />}> <Suspense fallback={<CampaignOverviewPageSkeleton />}>

View File

@@ -10,7 +10,7 @@ const config = {
}, },
}, },
defaultVariants: { defaultVariants: {
theme: "Peach", theme: "Burgundy",
}, },
} as const } as const

View File

@@ -1,6 +1,7 @@
import { gql } from "graphql-tag" import { gql } from "graphql-tag"
import { CampaignPageRef } from "../CampaignPage/Ref.graphql" import { CampaignPageRef } from "../CampaignPage/Ref.graphql"
import { PromoCampaignPageRef } from "../PromoCampaignPage/Ref.graphql"
export const AllCampaigns = gql` export const AllCampaigns = gql`
fragment AllCampaigns on CampaignOverviewPageBlocksAllCampaigns { fragment AllCampaigns on CampaignOverviewPageBlocksAllCampaigns {
@@ -21,6 +22,17 @@ export const AllCampaigns = gql`
locale locale
} }
} }
... on PromoCampaignPage {
url
card_content {
heading
image
text
}
system {
locale
}
}
} }
} }
} }
@@ -36,10 +48,12 @@ export const AllCampaignsRefs = gql`
node { node {
__typename __typename
...CampaignPageRef ...CampaignPageRef
...PromoCampaignPageRef
} }
} }
} }
} }
} }
${CampaignPageRef} ${CampaignPageRef}
${PromoCampaignPageRef}
` `

View File

@@ -30,3 +30,23 @@ export const TopCampaignRef = gql`
} }
${System} ${System}
` `
export const TopPromoCampaign = gql`
fragment TopPromoCampaign on PromoCampaignPage {
url
promo_hero {
image
heading
benefits
}
}
`
export const TopPromoCampaignRef = gql`
fragment TopPromoCampaignRef on PromoCampaignPage {
system {
...System
}
}
${System}
`

View File

@@ -16,6 +16,8 @@ import {
import { import {
TopCampaign, TopCampaign,
TopCampaignRef, TopCampaignRef,
TopPromoCampaign,
TopPromoCampaignRef,
} from "../../Fragments/CampaignOverviewPage/TopCampaign.graphql" } from "../../Fragments/CampaignOverviewPage/TopCampaign.graphql"
import { System } from "../../Fragments/System.graphql" import { System } from "../../Fragments/System.graphql"
@@ -33,7 +35,9 @@ export const GetCampaignOverviewPage = gql`
campaignConnection { campaignConnection {
edges { edges {
node { node {
__typename
...TopCampaign ...TopCampaign
...TopPromoCampaign
} }
} }
} }
@@ -57,6 +61,7 @@ export const GetCampaignOverviewPage = gql`
${System} ${System}
${NavigationLinks_CampaignOverviewPage} ${NavigationLinks_CampaignOverviewPage}
${TopCampaign} ${TopCampaign}
${TopPromoCampaign}
${AllCampaigns} ${AllCampaigns}
${CarouselCards_CampaignOverviewPage} ${CarouselCards_CampaignOverviewPage}
${HotelListing_CampaignOverviewPage} ${HotelListing_CampaignOverviewPage}
@@ -73,6 +78,7 @@ export const GetCampaignOverviewPageRefs = gql`
edges { edges {
node { node {
...TopCampaignRef ...TopCampaignRef
...TopPromoCampaignRef
} }
} }
} }
@@ -90,6 +96,7 @@ export const GetCampaignOverviewPageRefs = gql`
${System} ${System}
${NavigationLinksRef_CampaignOverviewPage} ${NavigationLinksRef_CampaignOverviewPage}
${TopCampaignRef} ${TopCampaignRef}
${TopPromoCampaignRef}
${CarouselCards_CampaignOverviewPageRefs} ${CarouselCards_CampaignOverviewPageRefs}
${AllCampaignsRefs} ${AllCampaignsRefs}
` `

View File

@@ -2,12 +2,14 @@ import { z } from "zod"
import { CampaignOverviewPageEnum } from "../../../types/campaignOverviewPageEnum" import { CampaignOverviewPageEnum } from "../../../types/campaignOverviewPageEnum"
import { CampaignPageEnum } from "../../../types/campaignPage" import { CampaignPageEnum } from "../../../types/campaignPage"
import { ContentEnum } from "../../../types/content"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion" import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import { import {
campaignPageHotelListing, campaignPageHotelListing,
heroSchema, heroSchema,
includedHotelsSchema, includedHotelsSchema,
} from "../campaignPage/output" } from "../campaignPage/output"
import { promoHeroSchema } from "../promoCampaignPage/output"
import { import {
allCampaignsRefsSchema, allCampaignsRefsSchema,
allCampaignsSchema, allCampaignsSchema,
@@ -43,28 +45,69 @@ export const campaignPageBlocksSchema = z.discriminatedUnion("__typename", [
campaignPageHotelListing, campaignPageHotelListing,
]) ])
const topCampaign = z.object({
typename: z
.literal(ContentEnum.blocks.CampaignPage)
.optional()
.default(ContentEnum.blocks.CampaignPage),
hero: heroSchema,
included_hotels: includedHotelsSchema,
blocks: discriminatedUnionArray(campaignPageBlocksSchema.options),
url: z.string(),
})
const topCampaignSchema = z const topCampaignSchema = z
.object({ .object({
hero: heroSchema, __typename: z.literal(ContentEnum.blocks.CampaignPage),
included_hotels: includedHotelsSchema,
blocks: discriminatedUnionArray(campaignPageBlocksSchema.options),
url: z.string(),
}) })
.transform((data) => { .merge(topCampaign)
const { blocks, included_hotels, ...rest } = data
const hotelListingBlock = blocks.find(
(block) =>
block.__typename === CampaignPageEnum.ContentStack.blocks.HotelListing
)
return { export const transformTopCampaign = (data: z.infer<typeof topCampaign>) => {
...rest, const { blocks, included_hotels, ...rest } = data
hotel_listing: { const hotelListingBlock = blocks?.find(
heading: hotelListingBlock?.hotel_listing.heading || "", (block) =>
hotelIds: included_hotels, block.__typename === CampaignPageEnum.ContentStack.blocks.HotelListing
}, )
} return {
...rest,
__typename: ContentEnum.blocks.CampaignPage,
hotel_listing: {
heading: hotelListingBlock?.hotel_listing.heading || "",
hotelIds: included_hotels,
},
}
}
export const transformedTopCampaign =
topCampaignSchema.transform(transformTopCampaign)
const topPromoCampaign = z.object({
typename: z
.literal(ContentEnum.blocks.PromoCampaignPage)
.optional()
.default(ContentEnum.blocks.PromoCampaignPage),
promo_hero: promoHeroSchema.nullish(),
url: z.string(),
})
const topPromoCampaignSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.PromoCampaignPage),
}) })
.merge(topPromoCampaign)
export const transformTopPromoCampaign = (
data: z.infer<typeof topPromoCampaign>
) => {
const { promo_hero, ...rest } = data
return {
...rest,
__typename: ContentEnum.blocks.PromoCampaignPage,
hero: promo_hero,
}
}
export const transformedTopPromoCampaign = topPromoCampaignSchema.transform(
transformTopPromoCampaign
)
const campaignOverviewPageCarouselCards = z const campaignOverviewPageCarouselCards = z
.object({ .object({
@@ -109,9 +152,23 @@ export const campaignOverviewPageSchema = z.object({
heading: z.string().nullish(), heading: z.string().nullish(),
campaignConnection: z.object({ campaignConnection: z.object({
edges: z.array( edges: z.array(
z.object({ z
node: topCampaignSchema, .object({
}) node: z.discriminatedUnion("__typename", [
topCampaignSchema,
topPromoCampaignSchema,
]),
})
.transform((data) => {
if (data.node.__typename === ContentEnum.blocks.CampaignPage) {
return {
node: transformTopCampaign(data.node),
}
}
return {
node: transformTopPromoCampaign(data.node),
}
})
), ),
}), }),
}), }),

View File

@@ -4,7 +4,7 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url" import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { BlocksEnums } from "../../../../types/blocksEnum" import { BlocksEnums } from "../../../../types/blocksEnum"
import { campaignPageRefSchema } from "../pageLinks" import { campaignPageRefSchema, promoCampaignPageRefSchema } from "../pageLinks"
export const campaignPageCardSchema = z.object({ export const campaignPageCardSchema = z.object({
image: transformedImageVaultAssetSchema, image: transformedImageVaultAssetSchema,
@@ -72,7 +72,10 @@ export const allCampaignsRefsSchema = z.object({
campaignsConnection: z.object({ campaignsConnection: z.object({
edges: z.array( edges: z.array(
z.object({ z.object({
node: campaignPageRefSchema, node: z.discriminatedUnion("__typename", [
campaignPageRefSchema,
promoCampaignPageRefSchema,
]),
}) })
), ),
}), }),

View File

@@ -3,19 +3,27 @@ import type { z } from "zod"
import type { import type {
campaignOverviewPageRefsSchema, campaignOverviewPageRefsSchema,
campaignOverviewPageSchema, campaignOverviewPageSchema,
transformedTopCampaign,
transformedTopPromoCampaign,
} from "../routers/contentstack/campaignOverviewPage/output" } from "../routers/contentstack/campaignOverviewPage/output"
export interface GetCampaignOverviewPageData export interface GetCampaignOverviewPageData extends z.input<
extends z.input<typeof campaignOverviewPageSchema> {} typeof campaignOverviewPageSchema
export interface CampaignOverviewPage > {}
extends z.output<typeof campaignOverviewPageSchema> {} export interface CampaignOverviewPage extends z.output<
typeof campaignOverviewPageSchema
> {}
export type CampaignOverviewPageData = export type CampaignOverviewPageData =
CampaignOverviewPage["campaign_overview_page"] CampaignOverviewPage["campaign_overview_page"]
export interface GetCampaignOverviewPageRefsData export type TopCampaign = z.output<typeof transformedTopCampaign>
extends z.input<typeof campaignOverviewPageRefsSchema> {} export type TopPromoCampaign = z.output<typeof transformedTopPromoCampaign>
export interface GetCampaignOverviewPageRefsData extends z.input<
typeof campaignOverviewPageRefsSchema
> {}
export interface CampaignOverviewPageRefs export interface CampaignOverviewPageRefs extends z.output<
extends z.output<typeof campaignOverviewPageRefsSchema> {} typeof campaignOverviewPageRefsSchema
> {}
export type Block = CampaignOverviewPageData["blocks"][number] export type Block = CampaignOverviewPageData["blocks"][number]