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

View File

@@ -43,13 +43,13 @@ export default async function StartPage() {
) : null}
</header>
<main className={styles.main}>
{blocks ? <Blocks blocks={blocks} /> : null}
<details
style={{ maxWidth: "100vw", overflow: "hidden", padding: "1rem 0" }}
>
<summary>JSON data</summary>
<pre>{JSON.stringify(content, null, 2)}</pre>
</details>
{blocks ? <Blocks blocks={blocks} /> : null}
</main>
<Suspense fallback={null}>
<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/Blocks/CardsGrid.graphql"
#import "../../Fragments/Blocks/FullWidthCampaign.graphql"
query GetStartPage($locale: String!, $uid: String!) {
start_page(uid: $uid, locale: $locale) {
start_page(locale: $locale, uid: $uid) {
title
url
header {
heading
hero_image
}
blocks {
__typename
... on StartPageBlocksFullWidthCampaign {
__typename
full_width_campaign {
full_width_campaignConnection {
edges {
node {
...FullWidthCampaign
}
}
}
}
}
}
system {
...System
created_at
@@ -29,6 +45,17 @@ query GetStartPageRefs($locale: String!, $uid: String!) {
blocks {
__typename
...CardsGrid_StartPageRefs
... on StartPageBlocksFullWidthCampaign {
full_width_campaign {
full_width_campaignConnection {
edges {
node {
...FullWidthCampaignRefs
}
}
}
}
}
}
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,
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
fullWidthCampaignBlockRefsSchema,
fullWidthCampaignBlockSchema,
} from "../schemas/blocks/fullWidthCampaign"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { systemSchema } from "../schemas/system"
import { StartPageEnum } from "@/types/enums/startPage"
export const startPageCards = z
const startPageCards = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.CardsGrid),
})
.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({
start_page: z.object({
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
title: z.string(),
header: z.object({
heading: z.string(),
hero_image: tempImageVaultAssetSchema,
}),
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
system: systemSchema.merge(
z.object({
created_at: z.string(),
@@ -46,8 +59,15 @@ const startPageCardsRefs = z
})
.merge(cardGridRefsSchema)
const startPageFullWidthCampaignRef = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.FullWidthCampaign),
})
.merge(fullWidthCampaignBlockRefsSchema)
const startPageBlockRefsItem = z.discriminatedUnion("__typename", [
startPageCardsRefs,
startPageFullWidthCampaignRef,
])
export const startPageRefsSchema = z.object({

View File

@@ -6,7 +6,7 @@ import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { startPageRefsSchema, startPageSchema } from "./output"
import {
@@ -17,6 +17,7 @@ import {
getStartPageRefsSuccessCounter,
getStartPageSuccessCounter,
} from "./telemetry"
import { getConnections } from "./utils"
import {
TrackingChannelEnum,
@@ -103,6 +104,13 @@ export const startPageQueryRouter = router({
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>(
GetStartPage,
{
@@ -112,10 +120,11 @@ export const startPageQueryRouter = router({
{
cache: "force-cache",
next: {
tags: [generateTag(lang, uid)],
tags,
},
}
)
if (!response.data) {
const notFoundError = notFound(response)
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",
SasTierComparison = "SasTierComparison",
HotelListing = "HotelListing",
FullWidthCampaign = "FullWidthCampaign",
}
}

View File

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

View File

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