feat(SW-1386): add full width campaign to start page

This commit is contained in:
Christian Andolf
2025-01-21 15:13:34 +01:00
parent b57174647f
commit b0c24d8945
13 changed files with 349 additions and 7 deletions

View File

@@ -0,0 +1,41 @@
.container {
position: relative;
height: 640px;
overflow: hidden;
}
.content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 800px;
margin: 0 auto;
gap: var(--Spacing-x1);
padding: var(--Spacing-x4) var(--Spacing-x3);
}
.mainContent {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--Spacing-x2);
}
.buttons {
display: flex;
gap: var(--Spacing-x1);
}
.image {
max-width: 100%;
height: 600px;
}
@media screen and (min-width: 768px) {
.image {
height: 880px;
}
}

View File

@@ -0,0 +1,57 @@
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./fullWidthCampaign.module.css"
import type { FullWidthCampaign } from "@/types/trpc/routers/contentstack/startPage"
interface FullWidthCampaignProps {
content: FullWidthCampaign
}
export default function FullWidthCampaign({ content }: FullWidthCampaignProps) {
return content.full_width_campaignConnection.edges.map(({ node }) => (
<div key={node.system.uid} className={styles.container}>
{node.background_image ? (
<Image
className={styles.image}
alt={
node.background_image.meta.alt ||
node.background_image.meta.caption ||
""
}
src={node.background_image.url}
focalPoint={node.background_image.focalPoint}
width={1512}
height={880}
/>
) : null}
<div className={styles.content}>
<BiroScript color="baseText">{node.scripted_top_title}</BiroScript>
<div className={styles.mainContent}>
<Title color="baseText" textAlign="center">
{node.heading}
</Title>
<Body color="baseText" textAlign="center">
{node.body_text}
</Body>
<div className={styles.buttons}>
{node.has_primary_button ? (
<Button intent="inverted" size="small" theme="base">
{node.primary_button.title}
</Button>
) : null}
{node.has_secondary_button ? (
<Button intent="secondary" size="small" theme="primaryStrong">
{node.secondary_button.title}
</Button>
) : null}
</div>
</div>
</div>
</div>
))
}

View File

@@ -7,6 +7,7 @@ import JsonToHtml from "@/components/JsonToHtml"
import { SasTierComparison } from "@/components/SasTierComparison" import { SasTierComparison } from "@/components/SasTierComparison"
import AccordionSection from "./Accordion" import AccordionSection from "./Accordion"
import FullWidthCampaign from "./FullWidthCampaign"
import HotelListing from "./HotelListing" import HotelListing from "./HotelListing"
import Table from "./Table" import Table from "./Table"
@@ -98,6 +99,8 @@ export default function Blocks({ blocks }: BlocksProps) {
firstItem={firstItem} firstItem={firstItem}
/> />
) )
case BlocksEnums.block.FullWidthCampaign:
return <FullWidthCampaign content={block.full_width_campaign} />
default: default:
return null return null
} }

View File

@@ -43,13 +43,13 @@ export default async function StartPage() {
) : null} ) : null}
</header> </header>
<main className={styles.main}> <main className={styles.main}>
{blocks ? <Blocks blocks={blocks} /> : null}
<details <details
style={{ maxWidth: "100vw", overflow: "hidden", padding: "1rem 0" }} style={{ maxWidth: "100vw", overflow: "hidden", padding: "1rem 0" }}
> >
<summary>JSON data</summary> <summary>JSON data</summary>
<pre>{JSON.stringify(content, null, 2)}</pre> <pre>{JSON.stringify(content, null, 2)}</pre>
</details> </details>
{blocks ? <Blocks blocks={blocks} /> : null}
</main> </main>
<Suspense fallback={null}> <Suspense fallback={null}>
<TrackingSDK pageData={content.tracking} /> <TrackingSDK pageData={content.tracking} />

View File

@@ -0,0 +1,99 @@
#import "../PageLink/AccountPageLink.graphql"
#import "../PageLink/ContentPageLink.graphql"
#import "../PageLink/LoyaltyPageLink.graphql"
#import "../PageLink/HotelPageLink.graphql"
#import "../PageLink/CollectionPageLink.graphql"
#import "../AccountPage/Ref.graphql"
#import "../ContentPage/Ref.graphql"
#import "../HotelPage/Ref.graphql"
#import "../LoyaltyPage/Ref.graphql"
#import "../CollectionPage/Ref.graphql"
fragment FullWidthCampaign on FullWidthCampaign {
background_image
scripted_top_title
heading
body_text
has_primary_button
primary_button {
cta_text
open_in_new_tab
is_contentstack_link
external_link {
href
title
}
linkConnection {
edges {
node {
__typename
...AccountPageLink
...ContentPageLink
...LoyaltyPageLink
...HotelPageLink
...CollectionPageLink
}
}
}
}
has_secondary_button
secondary_button {
cta_text
open_in_new_tab
is_contentstack_link
external_link {
href
title
}
linkConnection {
edges {
node {
__typename
...AccountPageLink
...ContentPageLink
...LoyaltyPageLink
...HotelPageLink
...CollectionPageLink
}
}
}
}
system {
...System
}
}
fragment FullWidthCampaignRefs on FullWidthCampaign {
primary_button {
linkConnection {
edges {
node {
__typename
...AccountPageRef
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
...CollectionPageRef
}
}
}
}
secondary_button {
linkConnection {
edges {
node {
__typename
...AccountPageRef
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
...CollectionPageRef
}
}
}
}
system {
...System
}
}

View File

@@ -1,14 +1,30 @@
#import "../../Fragments/System.graphql" #import "../../Fragments/System.graphql"
#import "../../Fragments/Blocks/CardsGrid.graphql" #import "../../Fragments/Blocks/CardsGrid.graphql"
#import "../../Fragments/Blocks/FullWidthCampaign.graphql"
query GetStartPage($locale: String!, $uid: String!) { query GetStartPage($locale: String!, $uid: String!) {
start_page(uid: $uid, locale: $locale) { start_page(locale: $locale, uid: $uid) {
title title
url url
header { header {
heading heading
hero_image hero_image
} }
blocks {
__typename
... on StartPageBlocksFullWidthCampaign {
__typename
full_width_campaign {
full_width_campaignConnection {
edges {
node {
...FullWidthCampaign
}
}
}
}
}
}
system { system {
...System ...System
created_at created_at
@@ -29,6 +45,17 @@ query GetStartPageRefs($locale: String!, $uid: String!) {
blocks { blocks {
__typename __typename
...CardsGrid_StartPageRefs ...CardsGrid_StartPageRefs
... on StartPageBlocksFullWidthCampaign {
full_width_campaign {
full_width_campaignConnection {
edges {
node {
...FullWidthCampaignRefs
}
}
}
}
}
} }
system { system {
...System ...System

View File

@@ -0,0 +1,58 @@
import { z } from "zod"
import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
import { tempImageVaultAssetSchema } from "../imageVault"
import { systemSchema } from "../system"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { BlocksEnums } from "@/types/enums/blocks"
export const fullWidthCampaignSchema = z.object({
full_width_campaign: z.object({
full_width_campaignConnection: z.object({
edges: z.array(
z.object({
node: z.object({
background_image: tempImageVaultAssetSchema,
heading: z.string().optional(),
body_text: z.string().optional(),
scripted_top_title: z.string().optional(),
has_primary_button: z.boolean().default(false),
primary_button: buttonSchema,
has_secondary_button: z.boolean().default(false),
secondary_button: buttonSchema,
system: systemSchema,
}),
})
),
}),
}),
})
export const fullWidthCampaignBlockSchema = z
.object({
typename: z
.literal(BlocksEnums.block.FullWidthCampaign)
.optional()
.default(BlocksEnums.block.FullWidthCampaign),
})
.merge(fullWidthCampaignSchema)
export const fullWidthCampaignBlockRefsSchema = z.object({
full_width_campaign: z.object({
full_width_campaignConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
pageLinks.accountPageRefSchema,
pageLinks.contentPageRefSchema,
pageLinks.loyaltyPageRefSchema,
pageLinks.collectionPageRefSchema,
pageLinks.hotelPageRefSchema,
]),
})
),
}),
}),
})

View File

@@ -6,27 +6,40 @@ import {
cardGridRefsSchema, cardGridRefsSchema,
cardsGridSchema, cardsGridSchema,
} from "../schemas/blocks/cardsGrid" } from "../schemas/blocks/cardsGrid"
import {
fullWidthCampaignBlockRefsSchema,
fullWidthCampaignBlockSchema,
} from "../schemas/blocks/fullWidthCampaign"
import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { systemSchema } from "../schemas/system" import { systemSchema } from "../schemas/system"
import { StartPageEnum } from "@/types/enums/startPage" import { StartPageEnum } from "@/types/enums/startPage"
export const startPageCards = z const startPageCards = z
.object({ .object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.CardsGrid), __typename: z.literal(StartPageEnum.ContentStack.blocks.CardsGrid),
}) })
.merge(cardsGridSchema) .merge(cardsGridSchema)
export const blocksSchema = z.discriminatedUnion("__typename", [startPageCards]) const startPageFullWidthCampaign = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.FullWidthCampaign),
})
.merge(fullWidthCampaignBlockSchema)
export const blocksSchema = z.discriminatedUnion("__typename", [
startPageCards,
startPageFullWidthCampaign,
])
export const startPageSchema = z.object({ export const startPageSchema = z.object({
start_page: z.object({ start_page: z.object({
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
title: z.string(), title: z.string(),
header: z.object({ header: z.object({
heading: z.string(), heading: z.string(),
hero_image: tempImageVaultAssetSchema, hero_image: tempImageVaultAssetSchema,
}), }),
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
system: systemSchema.merge( system: systemSchema.merge(
z.object({ z.object({
created_at: z.string(), created_at: z.string(),
@@ -46,8 +59,15 @@ const startPageCardsRefs = z
}) })
.merge(cardGridRefsSchema) .merge(cardGridRefsSchema)
const startPageFullWidthCampaignRef = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.FullWidthCampaign),
})
.merge(fullWidthCampaignBlockRefsSchema)
const startPageBlockRefsItem = z.discriminatedUnion("__typename", [ const startPageBlockRefsItem = z.discriminatedUnion("__typename", [
startPageCardsRefs, startPageCardsRefs,
startPageFullWidthCampaignRef,
]) ])
export const startPageRefsSchema = z.object({ export const startPageRefsSchema = z.object({

View File

@@ -6,7 +6,7 @@ import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc" import { notFound } from "@/server/errors/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag" import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { startPageRefsSchema, startPageSchema } from "./output" import { startPageRefsSchema, startPageSchema } from "./output"
import { import {
@@ -17,6 +17,7 @@ import {
getStartPageRefsSuccessCounter, getStartPageRefsSuccessCounter,
getStartPageSuccessCounter, getStartPageSuccessCounter,
} from "./telemetry" } from "./telemetry"
import { getConnections } from "./utils"
import { import {
TrackingChannelEnum, TrackingChannelEnum,
@@ -103,6 +104,13 @@ export const startPageQueryRouter = router({
query: { lang, uid }, query: { lang, uid },
}) })
) )
const connections = getConnections(validatedRefsData.data)
const tags = [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedRefsData.data.start_page.system.uid),
].flat()
const response = await request<GetStartPageData>( const response = await request<GetStartPageData>(
GetStartPage, GetStartPage,
{ {
@@ -112,10 +120,11 @@ export const startPageQueryRouter = router({
{ {
cache: "force-cache", cache: "force-cache",
next: { next: {
tags: [generateTag(lang, uid)], tags,
}, },
} }
) )
if (!response.data) { if (!response.data) {
const notFoundError = notFound(response) const notFoundError = notFound(response)
getStartPageFailCounter.add(1, { getStartPageFailCounter.add(1, {

View File

@@ -0,0 +1,24 @@
import { StartPageEnum } from "@/types/enums/startPage"
import type { System } from "@/types/requests/system"
import type { StartPageRefs } from "@/types/trpc/routers/contentstack/startPage"
export function getConnections({ start_page }: StartPageRefs) {
const connections: System["system"][] = [start_page.system]
if (start_page.blocks) {
start_page.blocks.forEach((block) => {
switch (block.__typename) {
case StartPageEnum.ContentStack.blocks.FullWidthCampaign: {
block.full_width_campaign.full_width_campaignConnection.edges.forEach(
({ node }) => {
connections.push(node.system)
}
)
break
}
}
})
}
return connections
}

View File

@@ -11,5 +11,6 @@ export namespace BlocksEnums {
UspGrid = "UspGrid", UspGrid = "UspGrid",
SasTierComparison = "SasTierComparison", SasTierComparison = "SasTierComparison",
HotelListing = "HotelListing", HotelListing = "HotelListing",
FullWidthCampaign = "FullWidthCampaign",
} }
} }

View File

@@ -2,6 +2,7 @@ export namespace StartPageEnum {
export namespace ContentStack { export namespace ContentStack {
export const enum blocks { export const enum blocks {
CardsGrid = "StartPageBlocksCardsGrid", CardsGrid = "StartPageBlocksCardsGrid",
FullWidthCampaign = "StartPageBlocksFullWidthCampaign",
} }
} }
} }

View File

@@ -15,3 +15,5 @@ export interface GetStartPageRefsSchema
export interface StartPageRefs extends z.output<typeof startPageRefsSchema> {} export interface StartPageRefs extends z.output<typeof startPageRefsSchema> {}
export type Block = z.output<typeof blocksSchema> export type Block = z.output<typeof blocksSchema>
export type FullWidthCampaign = Block["full_width_campaign"]