feat(SW-2264): Added campaign overview page

Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-06-19 15:19:56 +00:00
parent 3c4ff0a792
commit 891108791c
42 changed files with 743 additions and 86 deletions

View File

@@ -9,6 +9,10 @@ import {
GetMyPagesBreadcrumbs,
GetMyPagesBreadcrumbsRefs,
} from "@/lib/graphql/Query/Breadcrumbs/AccountPage.graphql"
import {
GetCampaignOverviewPageBreadcrumbs,
GetCampaignOverviewPageBreadcrumbsRefs,
} from "@/lib/graphql/Query/Breadcrumbs/CampaignOverviewPage.graphql"
import {
GetCampaignPageBreadcrumbs,
GetCampaignPageBreadcrumbsRefs,
@@ -158,6 +162,17 @@ export const breadcrumbsQueryRouter = router({
},
variables
)
case PageContentTypeEnum.campaignOverviewPage:
return await getBreadcrumbs<{
campaign_overview_page: RawBreadcrumbsSchema
}>(
{
dataKey: "campaign_overview_page",
refQuery: GetCampaignOverviewPageBreadcrumbsRefs,
query: GetCampaignOverviewPageBreadcrumbs,
},
variables
)
case PageContentTypeEnum.campaignPage:
return await getBreadcrumbs<{
campaign_page: RawBreadcrumbsSchema

View File

@@ -0,0 +1,7 @@
import { mergeRouters } from "@scandic-hotels/trpc"
import { campaignOverviewPageQueryRouter } from "./query"
export const campaignOverviewPageRouter = mergeRouters(
campaignOverviewPageQueryRouter
)

View File

@@ -0,0 +1,57 @@
import { z } from "zod"
import {
linkAndTitleSchema,
linkConnectionRefs,
} from "@/server/routers/contentstack/schemas/linkConnection"
import { systemSchema } from "../schemas/system"
const navigationLinksSchema = z
.array(linkAndTitleSchema)
.nullable()
.transform((data) => {
if (!data) {
return null
}
return data
.filter((item) => !!item.link)
.map((item) => ({
url: item.link!.url,
title: item.title || item.link!.title,
}))
})
export const campaignOverviewPageSchema = z.object({
campaign_overview_page: z.object({
title: z.string(),
header: z.object({
heading: z.string(),
preamble: z.string(),
navigation_links: navigationLinksSchema,
}),
system: systemSchema.merge(
z.object({
created_at: z.string(),
updated_at: z.string(),
})
),
}),
trackingProps: z.object({
url: z.string(),
}),
})
/** REFS */
const campaignOverviewPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
})
export const campaignOverviewPageRefsSchema = z.object({
campaign_overview_page: z.object({
header: campaignOverviewPageHeaderRefs,
system: systemSchema,
}),
})

View File

@@ -0,0 +1,135 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentStackUidWithServiceProcedure } from "@scandic-hotels/trpc/procedures"
import {
GetCampaignOverviewPage,
GetCampaignOverviewPageRefs,
} from "@/lib/graphql/Query/CampaignOverviewPage/CampaignOverviewPage.graphql"
import { request } from "@/lib/graphql/request"
import { generateRefsResponseTag } from "@/utils/generateTag"
import {
campaignOverviewPageRefsSchema,
campaignOverviewPageSchema,
} from "./output"
import { generatePageTags } from "./utils"
import {
TrackingChannelEnum,
type TrackingSDKPageData,
} from "@/types/components/tracking"
import type {
GetCampaignOverviewPageData,
GetCampaignOverviewPageRefsData,
} from "@/types/trpc/routers/contentstack/campaignOverviewPage"
export const campaignOverviewPageQueryRouter = router({
get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getCampaignOverviewPageRefsCounter = createCounter(
"trpc.contentstack",
"campaignOverviewPage.get.refs"
)
const metricsGetCampaignOverviewPageRefs =
getCampaignOverviewPageRefsCounter.init({
lang,
uid,
})
metricsGetCampaignOverviewPageRefs.start()
const refsResponse = await request<GetCampaignOverviewPageRefsData>(
GetCampaignOverviewPageRefs,
{ locale: lang, uid },
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
metricsGetCampaignOverviewPageRefs.noDataError()
throw notFoundError
}
const validatedRefsData = campaignOverviewPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetCampaignOverviewPageRefs.validationError(
validatedRefsData.error
)
return null
}
metricsGetCampaignOverviewPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
const getCampaignOverviewPageCounter = createCounter(
"trpc.contentstack",
"campaignOverviewPage.get"
)
const metricsGetCampaignOverviewPage = getCampaignOverviewPageCounter.init({
lang,
uid,
})
metricsGetCampaignOverviewPage.start()
const response = await request<GetCampaignOverviewPageData>(
GetCampaignOverviewPage,
{
locale: lang,
uid,
},
{
key: tags,
ttl: "max",
}
)
if (!response.data) {
const notFoundError = notFound(response)
metricsGetCampaignOverviewPage.noDataError()
throw notFoundError
}
const validatedResponse = campaignOverviewPageSchema.safeParse(
response.data
)
if (!validatedResponse.success) {
metricsGetCampaignOverviewPage.validationError(validatedResponse.error)
return null
}
const campaignOverviewPage = validatedResponse.data.campaign_overview_page
metricsGetCampaignOverviewPage.success()
const system = campaignOverviewPage.system
const pageName = `campaign-overview-page`
const tracking: TrackingSDKPageData = {
pageId: system.uid,
domainLanguage: system.locale,
publishDate: system.updated_at,
createDate: system.created_at,
channel: TrackingChannelEnum["campaign-overview-page"],
pageType: "campaign-overview-page",
pageName,
siteSections: pageName,
siteVersion: "new-web",
}
return {
campaignOverviewPage,
tracking,
}
}),
})

View File

@@ -0,0 +1,33 @@
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { System } from "@/types/requests/system"
import type { CampaignOverviewPageRefs } from "@/types/trpc/routers/contentstack/campaignOverviewPage"
export function generatePageTags(
validatedData: CampaignOverviewPageRefs,
lang: Lang
): string[] {
const connections = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedData.campaign_overview_page.system.uid),
].flat()
}
export function getConnections({
campaign_overview_page,
}: CampaignOverviewPageRefs) {
const connections: System["system"][] = [campaign_overview_page.system]
if (campaign_overview_page.header.navigation_links) {
campaign_overview_page.header.navigation_links.forEach((link) => {
if (link.link) {
connections.push(link.link)
}
})
}
return connections
}

View File

@@ -3,6 +3,7 @@ import { router } from "@scandic-hotels/trpc"
import { accountPageRouter } from "./accountPage"
import { baseRouter } from "./base"
import { breadcrumbsRouter } from "./breadcrumbs"
import { campaignOverviewPageRouter } from "./campaignOverviewPage"
import { campaignPageRouter } from "./campaignPage"
import { collectionPageRouter } from "./collectionPage"
import { contentPageRouter } from "./contentPage"
@@ -26,6 +27,7 @@ export const contentstackRouter = router({
hotelPage: hotelPageRouter,
languageSwitcher: languageSwitcherRouter,
loyaltyPage: loyaltyPageRouter,
campaignOverviewPage: campaignOverviewPageRouter,
campaignPage: campaignPageRouter,
collectionPage: collectionPageRouter,
contentPage: contentPageRouter,

View File

@@ -7,6 +7,10 @@ import {
GetDaDeEnUrlsAccountPage,
GetFiNoSvUrlsAccountPage,
} from "@/lib/graphql/Query/AccountPage/AccountPage.graphql"
import {
GetDaDeEnUrlsCampaignOverviewPage,
GetFiNoSvUrlsCampaignOverviewPage,
} from "@/lib/graphql/Query/CampaignOverviewPage/CampaignOverviewPage.graphql"
import {
GetDaDeEnUrlsCampaignPage,
GetFiNoSvUrlsCampaignPage,
@@ -100,6 +104,10 @@ export async function getUrlsOfAllLanguages(
daDeEnDocument = GetDaDeEnUrlsCurrentBlocksPage
fiNoSvDocument = GetFiNoSvUrlsCurrentBlocksPage
break
case PageContentTypeEnum.campaignOverviewPage:
daDeEnDocument = GetDaDeEnUrlsCampaignOverviewPage
fiNoSvDocument = GetFiNoSvUrlsCampaignOverviewPage
break
case PageContentTypeEnum.campaignPage:
daDeEnDocument = GetDaDeEnUrlsCampaignPage
fiNoSvDocument = GetFiNoSvUrlsCampaignPage

View File

@@ -7,6 +7,7 @@ import { contentStackUidWithServiceProcedure } from "@scandic-hotels/trpc/proced
import { env } from "@/env/server"
import { GetAccountPageMetadata } from "@/lib/graphql/Query/AccountPage/Metadata.graphql"
import { GetCampaignOverviewPageMetadata } from "@/lib/graphql/Query/CampaignOverviewPage/Metadata.graphql"
import { GetCampaignPageMetadata } from "@/lib/graphql/Query/CampaignPage/Metadata.graphql"
import { GetCollectionPageMetadata } from "@/lib/graphql/Query/CollectionPage/Metadata.graphql"
import { GetContentPageMetadata } from "@/lib/graphql/Query/ContentPage/Metadata.graphql"
@@ -131,6 +132,14 @@ export const metadataQueryRouter = router({
accountPageResponse.account_page
)
break
case PageContentTypeEnum.campaignOverviewPage:
const campaignOverviewPageResponse = await fetchMetadata<{
campaign_overview_page: RawMetadataSchema
}>(GetCampaignOverviewPageMetadata, variables)
metadata = await getTransformedMetadata(
campaignOverviewPageResponse.campaign_overview_page
)
break
case PageContentTypeEnum.campaignPage:
const campaignPageResponse = await fetchMetadata<{
campaign_page: RawMetadataSchema

View File

@@ -5,6 +5,7 @@ import { contentstackBaseProcedure } from "@scandic-hotels/trpc/procedures"
import {
GetAccountPageSettings,
GetCampaignOverviewPageSettings,
GetCampaignPageSettings,
GetCollectionPageSettings,
GetContentPageSettings,
@@ -77,6 +78,7 @@ export const pageSettingsQueryRouter = router({
const graphqlQueriesForContentType: Record<PageContentTypeEnum, any> = {
[PageContentTypeEnum.accountPage]: GetAccountPageSettings,
[PageContentTypeEnum.campaignOverviewPage]: GetCampaignOverviewPageSettings,
[PageContentTypeEnum.campaignPage]: GetCampaignPageSettings,
[PageContentTypeEnum.collectionPage]: GetCollectionPageSettings,
[PageContentTypeEnum.contentPage]: GetContentPageSettings,

View File

@@ -2,6 +2,8 @@ import { z } from "zod"
import {
accountPageSchema,
campaignOverviewPageSchema,
campaignPageSchema,
collectionPageSchema,
contentPageSchema,
destinationCityPageSchema,
@@ -20,6 +22,8 @@ export const contentEmbedsSchema = z
imageContainerSchema,
sysAssetSchema,
accountPageSchema,
campaignOverviewPageSchema,
campaignPageSchema,
collectionPageSchema,
contentPageSchema,
destinationCityPageSchema,

View File

@@ -32,6 +32,18 @@ export const extendedPageLinkSchema = pageLinkSchema.merge(
.default({ original_url: "" }),
})
)
export const campaignOverviewPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.CampaignOverviewPage),
})
.merge(extendedPageLinkSchema)
export const campaignOverviewPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.CampaignOverviewPage),
system: systemSchema,
})
export const collectionPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.CollectionPage),
@@ -133,6 +145,7 @@ export const startPageRefSchema = z.object({
export const linkUnionSchema = z.discriminatedUnion("__typename", [
accountPageSchema,
campaignOverviewPageSchema,
campaignPageSchema,
collectionPageSchema,
contentPageSchema,
@@ -146,6 +159,7 @@ export const linkUnionSchema = z.discriminatedUnion("__typename", [
type Data =
| z.output<typeof accountPageSchema>
| z.output<typeof campaignOverviewPageSchema>
| z.output<typeof campaignPageSchema>
| z.output<typeof collectionPageSchema>
| z.output<typeof contentPageSchema>
@@ -161,6 +175,7 @@ export function transformPageLink(data: Data) {
if (data && "__typename" in data) {
switch (data.__typename) {
case ContentEnum.blocks.AccountPage:
case ContentEnum.blocks.CampaignOverviewPage:
case ContentEnum.blocks.CampaignPage:
case ContentEnum.blocks.DestinationCityPage:
case ContentEnum.blocks.DestinationCountryPage:
@@ -173,7 +188,6 @@ export function transformPageLink(data: Data) {
title: data.title,
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
}
case ContentEnum.blocks.CollectionPage:
case ContentEnum.blocks.ContentPage:
case ContentEnum.blocks.LoyaltyPage:
@@ -195,6 +209,7 @@ export function transformPageLink(data: Data) {
export const linkRefsUnionSchema = z.discriminatedUnion("__typename", [
accountPageRefSchema,
campaignOverviewPageRefSchema,
campaignPageRefSchema,
collectionPageRefSchema,
contentPageRefSchema,
@@ -208,6 +223,7 @@ export const linkRefsUnionSchema = z.discriminatedUnion("__typename", [
type RefData =
| z.output<typeof accountPageRefSchema>
| z.output<typeof campaignOverviewPageRefSchema>
| z.output<typeof campaignPageRefSchema>
| z.output<typeof collectionPageRefSchema>
| z.output<typeof contentPageRefSchema>
@@ -223,6 +239,7 @@ export function transformPageLinkRef(data: RefData) {
if (data && "__typename" in data) {
switch (data.__typename) {
case ContentEnum.blocks.AccountPage:
case ContentEnum.blocks.CampaignOverviewPage:
case ContentEnum.blocks.CampaignPage:
case ContentEnum.blocks.CollectionPage:
case ContentEnum.blocks.ContentPage: