diff --git a/components/Blocks/FullWidthCampaign/fullWidthCampaign.module.css b/components/Blocks/FullWidthCampaign/fullWidthCampaign.module.css new file mode 100644 index 000000000..efdee8666 --- /dev/null +++ b/components/Blocks/FullWidthCampaign/fullWidthCampaign.module.css @@ -0,0 +1,41 @@ +.container { + position: relative; + overflow: hidden; + height: 640px; +} + +@media screen and (min-width: 768px) { + .container { + height: 880px; + } +} + +.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: 100%; +} diff --git a/components/Blocks/FullWidthCampaign/index.tsx b/components/Blocks/FullWidthCampaign/index.tsx new file mode 100644 index 000000000..3a7a0a1ce --- /dev/null +++ b/components/Blocks/FullWidthCampaign/index.tsx @@ -0,0 +1,76 @@ +import Image from "@/components/Image" +import Button from "@/components/TempDesignSystem/Button" +import Link from "@/components/TempDesignSystem/Link" +import BiroScript from "@/components/TempDesignSystem/Text/BiroScript" +import Preamble from "@/components/TempDesignSystem/Text/Preamble" +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) { + const { background_image, primary_button, secondary_button } = content + + return ( +
+ {background_image ? ( + {background_image.meta.alt + ) : null} +
+ + {content.scripted_top_title} + +
+ + {content.heading} + + + {content.body_text} + +
+ {content.has_primary_button ? ( + + ) : null} + {content.has_secondary_button ? ( + + ) : null} +
+
+
+
+ ) +} diff --git a/components/Blocks/index.tsx b/components/Blocks/index.tsx index a67a3880e..87283a5a9 100644 --- a/components/Blocks/index.tsx +++ b/components/Blocks/index.tsx @@ -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 default: return null } diff --git a/components/ContentType/StartPage/index.tsx b/components/ContentType/StartPage/index.tsx index 33972541f..d94a7d2ff 100644 --- a/components/ContentType/StartPage/index.tsx +++ b/components/ContentType/StartPage/index.tsx @@ -1,8 +1,10 @@ +import { assertNullableType } from "graphql" import { Suspense } from "react" import { getStartPage } from "@/lib/trpc/memoizedRequests" import Blocks from "@/components/Blocks" +import FullWidthCampaign from "@/components/Blocks/FullWidthCampaign" import Image from "@/components/Image" import PageContainer from "@/components/PageContainer" import Title from "@/components/TempDesignSystem/Text/Title" @@ -10,6 +12,8 @@ import TrackingSDK from "@/components/TrackingSDK" import styles from "./startPage.module.css" +import { BlocksEnums } from "@/types/enums/blocks" + export default async function StartPage() { const content = await getStartPage() if (!content) { @@ -43,13 +47,24 @@ export default async function StartPage() { ) : null}
-
- JSON data -
{JSON.stringify(content, null, 2)}
-
- {blocks ? : null} + {blocks + ? blocks.map((block) => { + if (block.typename === BlocksEnums.block.FullWidthCampaign) { + return ( + + ) + } + + return ( +
+ +
+ ) + }) + : null}
diff --git a/components/ContentType/StartPage/startPage.module.css b/components/ContentType/StartPage/startPage.module.css index 378d16845..d69fef887 100644 --- a/components/ContentType/StartPage/startPage.module.css +++ b/components/ContentType/StartPage/startPage.module.css @@ -40,9 +40,19 @@ .main { display: grid; - width: 100%; gap: var(--Spacing-x6); - margin: 0 auto; - max-width: var(--max-width-content); - padding: var(--Spacing-x4) 0; + padding: calc(var(--Spacing-x5) * 2) 0 calc(var(--Spacing-x5) * 4); +} + +@media screen and (min-width: 768px) { + .main { + gap: calc(var(--Spacing-x5) * 3); + } +} + +.section { + margin-left: auto; + margin-right: auto; + max-width: var(--max-width-content); + width: 100%; } diff --git a/components/TempDesignSystem/Text/Preamble/preamble.module.css b/components/TempDesignSystem/Text/Preamble/preamble.module.css index 007cfe56f..bfb796746 100644 --- a/components/TempDesignSystem/Text/Preamble/preamble.module.css +++ b/components/TempDesignSystem/Text/Preamble/preamble.module.css @@ -33,6 +33,10 @@ color: var(--Base-Text-UI-Medium-contrast); } +.baseText { + color: var(--Base-Text-Inverted); +} + .center { text-align: center; } diff --git a/components/TempDesignSystem/Text/Preamble/variants.ts b/components/TempDesignSystem/Text/Preamble/variants.ts index 0eebf4d17..d007d985b 100644 --- a/components/TempDesignSystem/Text/Preamble/variants.ts +++ b/components/TempDesignSystem/Text/Preamble/variants.ts @@ -9,6 +9,7 @@ const config = { burgundy: styles.burgundy, pale: styles.pale, textMediumContrast: styles.textMediumContrast, + baseText: styles.baseText, }, textAlign: { center: styles.center, diff --git a/lib/graphql/Fragments/Blocks/FullWidthCampaign.graphql b/lib/graphql/Fragments/Blocks/FullWidthCampaign.graphql new file mode 100644 index 000000000..2b5886ce2 --- /dev/null +++ b/lib/graphql/Fragments/Blocks/FullWidthCampaign.graphql @@ -0,0 +1,117 @@ +#import "../PageLink/AccountPageLink.graphql" +#import "../PageLink/ContentPageLink.graphql" +#import "../PageLink/LoyaltyPageLink.graphql" +#import "../PageLink/HotelPageLink.graphql" +#import "../PageLink/CollectionPageLink.graphql" +#import "../PageLink/DestinationCityPageLink.graphql" +#import "../PageLink/DestinationCountryPageLink.graphql" +#import "../PageLink/DestinationOverviewPageLink.graphql" + +#import "../AccountPage/Ref.graphql" +#import "../ContentPage/Ref.graphql" +#import "../HotelPage/Ref.graphql" +#import "../LoyaltyPage/Ref.graphql" +#import "../CollectionPage/Ref.graphql" +#import "../DestinationCityPage/Ref.graphql" +#import "../DestinationCountryPage/Ref.graphql" +#import "../DestinationOverviewPage/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 + ...DestinationCityPageLink + ...DestinationCountryPageLink + ...DestinationOverviewPageLink + } + } + } + } + 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 + ...DestinationCityPageLink + ...DestinationCountryPageLink + ...DestinationOverviewPageLink + } + } + } + } + system { + ...System + } +} + +fragment FullWidthCampaignRefs on FullWidthCampaign { + primary_button { + linkConnection { + edges { + node { + __typename + ...AccountPageRef + ...ContentPageRef + ...HotelPageRef + ...LoyaltyPageRef + ...CollectionPageRef + ...DestinationCityPageRef + ...DestinationCountryPageRef + ...DestinationOverviewPageRef + } + } + } + } + secondary_button { + linkConnection { + edges { + node { + __typename + ...AccountPageRef + ...ContentPageRef + ...HotelPageRef + ...LoyaltyPageRef + ...CollectionPageRef + ...DestinationCityPageRef + ...DestinationCountryPageRef + ...DestinationOverviewPageRef + } + } + } + } + system { + ...System + } +} diff --git a/lib/graphql/Query/StartPage/StartPage.graphql b/lib/graphql/Query/StartPage/StartPage.graphql index a3bcfea4e..4e118ceba 100644 --- a/lib/graphql/Query/StartPage/StartPage.graphql +++ b/lib/graphql/Query/StartPage/StartPage.graphql @@ -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 diff --git a/server/routers/contentstack/schemas/blocks/fullWidthCampaign.ts b/server/routers/contentstack/schemas/blocks/fullWidthCampaign.ts new file mode 100644 index 000000000..20a9c0962 --- /dev/null +++ b/server/routers/contentstack/schemas/blocks/fullWidthCampaign.ts @@ -0,0 +1,65 @@ +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, + }), + }) + ), + }), + }) + .transform((data) => { + return data.full_width_campaignConnection.edges[0]?.node || null + }), +}) + +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, + pageLinks.destinationCityPageRefSchema, + pageLinks.destinationCountryPageRefSchema, + pageLinks.destinationOverviewPageRefSchema, + ]), + }) + ), + }), + }), +}) diff --git a/server/routers/contentstack/startPage/output.ts b/server/routers/contentstack/startPage/output.ts index f52c911eb..69c6dc02b 100644 --- a/server/routers/contentstack/startPage/output.ts +++ b/server/routers/contentstack/startPage/output.ts @@ -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({ diff --git a/server/routers/contentstack/startPage/query.ts b/server/routers/contentstack/startPage/query.ts index d6cdf1986..5ce2c5ace 100644 --- a/server/routers/contentstack/startPage/query.ts +++ b/server/routers/contentstack/startPage/query.ts @@ -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( 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, { diff --git a/server/routers/contentstack/startPage/utils.ts b/server/routers/contentstack/startPage/utils.ts new file mode 100644 index 000000000..9edb0720e --- /dev/null +++ b/server/routers/contentstack/startPage/utils.ts @@ -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 +} diff --git a/types/enums/blocks.ts b/types/enums/blocks.ts index baf9a4a9c..4fd753abf 100644 --- a/types/enums/blocks.ts +++ b/types/enums/blocks.ts @@ -11,5 +11,6 @@ export namespace BlocksEnums { UspGrid = "UspGrid", SasTierComparison = "SasTierComparison", HotelListing = "HotelListing", + FullWidthCampaign = "FullWidthCampaign", } } diff --git a/types/enums/startPage.ts b/types/enums/startPage.ts index fff235db4..339e5f26e 100644 --- a/types/enums/startPage.ts +++ b/types/enums/startPage.ts @@ -2,6 +2,7 @@ export namespace StartPageEnum { export namespace ContentStack { export const enum blocks { CardsGrid = "StartPageBlocksCardsGrid", + FullWidthCampaign = "StartPageBlocksFullWidthCampaign", } } } diff --git a/types/trpc/routers/contentstack/startPage.ts b/types/trpc/routers/contentstack/startPage.ts index 61219679e..ae93ef46a 100644 --- a/types/trpc/routers/contentstack/startPage.ts +++ b/types/trpc/routers/contentstack/startPage.ts @@ -1,5 +1,6 @@ import type { z } from "zod" +import type { fullWidthCampaignSchema } from "@/server/routers/contentstack/schemas/blocks/fullWidthCampaign" import type { blocksSchema, startPageRefsSchema, @@ -15,3 +16,7 @@ export interface GetStartPageRefsSchema export interface StartPageRefs extends z.output {} export type Block = z.output + +export type FullWidthCampaign = z.output< + typeof fullWidthCampaignSchema +>["full_width_campaign"]