diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx
index 0e8e98a05..378d52bbf 100644
--- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx
+++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx
@@ -1,6 +1,6 @@
import { notFound } from "next/navigation"
-import ContentPage from "@/components/ContentType/ContentPage"
+import ContentPage from "@/components/ContentType/ContentPage/ContentPage"
import HotelPage from "@/components/ContentType/HotelPage/HotelPage"
import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage"
import { setLang } from "@/i18n/serverContext"
@@ -19,7 +19,7 @@ export default async function ContentTypePage({
switch (params.contentType) {
case "content-page":
- return
+ return
case "loyalty-page":
return
case "hotel-page":
diff --git a/components/ContentType/ContentPage.tsx b/components/ContentType/ContentPage.tsx
deleted file mode 100644
index bf2443621..000000000
--- a/components/ContentType/ContentPage.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default async function ContentPage() {
- return null
-}
diff --git a/components/ContentType/ContentPage/ContentPage.tsx b/components/ContentType/ContentPage/ContentPage.tsx
new file mode 100644
index 000000000..546bcec0d
--- /dev/null
+++ b/components/ContentType/ContentPage/ContentPage.tsx
@@ -0,0 +1,47 @@
+import { serverClient } from "@/lib/trpc/server"
+
+import Hero from "@/components/TempDesignSystem/Hero"
+import Preamble from "@/components/TempDesignSystem/Text/Preamble"
+import Title from "@/components/TempDesignSystem/Text/Title"
+import TrackingSDK from "@/components/TrackingSDK"
+
+import Intro from "./Intro"
+
+import styles from "./contentPage.module.css"
+
+import type { LangParams } from "@/types/params"
+
+export default async function ContentPage({ lang }: LangParams) {
+ const contentPageRes = await serverClient().contentstack.contentPage.get()
+
+ if (!contentPageRes) {
+ return null
+ }
+
+ const { tracking, contentPage } = contentPageRes
+ const heroImage = contentPage.hero_image
+
+ return (
+ <>
+
+
+
+ {contentPage.header.heading}
+ {contentPage.header.preamble}
+
+
+
+ {heroImage ? (
+
+
+
+ ) : null}
+
+
+
+ >
+ )
+}
diff --git a/components/ContentType/ContentPage/Intro/index.tsx b/components/ContentType/ContentPage/Intro/index.tsx
new file mode 100644
index 000000000..a5401164d
--- /dev/null
+++ b/components/ContentType/ContentPage/Intro/index.tsx
@@ -0,0 +1,22 @@
+import { PropsWithChildren } from "react"
+
+import MaxWidth from "@/components/MaxWidth"
+
+import styles from "./intro.module.css"
+
+export default async function Intro({ children }: PropsWithChildren) {
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/components/ContentType/ContentPage/Intro/intro.module.css b/components/ContentType/ContentPage/Intro/intro.module.css
new file mode 100644
index 000000000..8ae34a4ad
--- /dev/null
+++ b/components/ContentType/ContentPage/Intro/intro.module.css
@@ -0,0 +1,8 @@
+.intro {
+ padding: var(--Spacing-x4) var(--Spacing-x2);
+}
+
+.content {
+ display: grid;
+ gap: var(--Spacing-x3);
+}
diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css
new file mode 100644
index 000000000..df5938084
--- /dev/null
+++ b/components/ContentType/ContentPage/contentPage.module.css
@@ -0,0 +1,17 @@
+.content {
+ display: grid;
+ padding-bottom: var(--Spacing-x9);
+ position: relative;
+ justify-content: center;
+ align-items: flex-start;
+}
+
+.header {
+ background-color: var(--Base-Surface-Subtle-Normal);
+}
+
+.hero {
+ display: grid;
+ justify-content: center;
+ padding: var(--Spacing-x4) var(--Spacing-x2);
+}
diff --git a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx
index 484117c47..b6b90bf47 100644
--- a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx
+++ b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx
@@ -9,7 +9,6 @@ import TrackingSDK from "@/components/TrackingSDK"
import styles from "./loyaltyPage.module.css"
-import { ImageVaultAsset } from "@/types/components/imageVaultImage"
import type { LangParams } from "@/types/params"
export default async function LoyaltyPage({ lang }: LangParams) {
@@ -20,7 +19,7 @@ export default async function LoyaltyPage({ lang }: LangParams) {
}
const { tracking, loyaltyPage } = loyaltyPageRes
- const heroImage: ImageVaultAsset = loyaltyPage.header?.hero_image
+ const heroImage = loyaltyPage.hero_image
return (
<>
diff --git a/components/MaxWidth/index.tsx b/components/MaxWidth/index.tsx
index 495447a76..0047d0dd2 100644
--- a/components/MaxWidth/index.tsx
+++ b/components/MaxWidth/index.tsx
@@ -1,17 +1,21 @@
"use client"
-import { cva } from "class-variance-authority"
-import styles from "./maxWidth.module.css"
+import { maxWidthVariants } from "./variants"
import type { MaxWidthProps } from "@/types/components/max-width"
-const maxWidthVariants = cva(styles.container)
-
export default function MaxWidth({
className,
tag = "section",
+ variant,
+ align,
...props
}: MaxWidthProps) {
const Cmp = tag
- return
+ return (
+
+ )
}
diff --git a/components/MaxWidth/maxWidth.module.css b/components/MaxWidth/maxWidth.module.css
index 563ae5721..9197d79dc 100644
--- a/components/MaxWidth/maxWidth.module.css
+++ b/components/MaxWidth/maxWidth.module.css
@@ -1,4 +1,20 @@
.container {
- max-width: var(--max-width, 1140px);
position: relative;
}
+
+.container.default {
+ max-width: var(--max-width, 1140px);
+}
+
+.container.text {
+ max-width: 49.5rem;
+}
+
+.container.content {
+ max-width: 74.75rem;
+}
+
+.container.center {
+ margin: 0 auto;
+ width: 100vh;
+}
diff --git a/components/MaxWidth/variants.ts b/components/MaxWidth/variants.ts
new file mode 100644
index 000000000..f7c917b5f
--- /dev/null
+++ b/components/MaxWidth/variants.ts
@@ -0,0 +1,21 @@
+import { cva } from "class-variance-authority"
+
+import styles from "./maxWidth.module.css"
+
+export const maxWidthVariants = cva(styles.container, {
+ variants: {
+ variant: {
+ text: styles.text,
+ content: styles.content,
+ default: styles.default,
+ },
+ align: {
+ center: styles.center,
+ left: styles.left,
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ align: "center",
+ },
+})
diff --git a/lib/graphql/Fragments/ContentPage/Breadcrumbs.graphql b/lib/graphql/Fragments/ContentPage/Breadcrumbs.graphql
new file mode 100644
index 000000000..0e9dc795c
--- /dev/null
+++ b/lib/graphql/Fragments/ContentPage/Breadcrumbs.graphql
@@ -0,0 +1,25 @@
+fragment ContentPageBreadcrumbs on ContentPage {
+ web {
+ breadcrumbs {
+ title
+ parentsConnection {
+ edges {
+ node {
+ ... on ContentPage {
+ web {
+ breadcrumbs {
+ title
+ }
+ }
+ system {
+ locale
+ uid
+ }
+ url
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lib/graphql/Query/ContentPage.graphql b/lib/graphql/Query/ContentPage.graphql
new file mode 100644
index 000000000..ff7c0d24d
--- /dev/null
+++ b/lib/graphql/Query/ContentPage.graphql
@@ -0,0 +1,19 @@
+#import "../Fragments/ContentPage/Breadcrumbs.graphql"
+
+query GetContentPage($locale: String!, $uid: String!) {
+ content_page(uid: $uid, locale: $locale) {
+ title
+ header {
+ heading
+ preamble
+ }
+ hero_image
+ ...ContentPageBreadcrumbs
+ system {
+ uid
+ created_at
+ updated_at
+ locale
+ }
+ }
+}
diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql
index 6ed01ef83..7026fe288 100644
--- a/lib/graphql/Query/LoyaltyPage.graphql
+++ b/lib/graphql/Query/LoyaltyPage.graphql
@@ -107,9 +107,8 @@ query GetLoyaltyPage($locale: String!, $uid: String!) {
}
title
heading
- header {
- hero_image
- }
+ preamble
+ hero_image
sidebar {
__typename
... on LoyaltyPageSidebarDynamicContent {
diff --git a/server/routers/contentstack/contentPage/index.ts b/server/routers/contentstack/contentPage/index.ts
new file mode 100644
index 000000000..52130d7b4
--- /dev/null
+++ b/server/routers/contentstack/contentPage/index.ts
@@ -0,0 +1,5 @@
+import { mergeRouters } from "@/server/trpc"
+
+import { contentPageQueryRouter } from "./query"
+
+export const contentPageRouter = mergeRouters(contentPageQueryRouter)
diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts
new file mode 100644
index 000000000..32fe3ec08
--- /dev/null
+++ b/server/routers/contentstack/contentPage/output.ts
@@ -0,0 +1,30 @@
+import { z } from "zod"
+
+import { Lang } from "@/constants/languages"
+
+import { ImageVaultAsset } from "@/types/components/imageVaultImage"
+
+export const validateContentPageSchema = z.object({
+ content_page: z.object({
+ title: z.string(),
+ header: z.object({
+ heading: z.string(),
+ preamble: z.string(),
+ }),
+ hero_image: z.any().nullable(),
+ system: z.object({
+ uid: z.string(),
+ locale: z.nativeEnum(Lang),
+ created_at: z.string(),
+ updated_at: z.string(),
+ }),
+ }),
+})
+
+export type ContentPageDataRaw = z.infer
+
+type ContentPageRaw = ContentPageDataRaw["content_page"]
+
+export type ContentPage = Omit & {
+ hero_image?: ImageVaultAsset
+}
diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts
new file mode 100644
index 000000000..845dd89e3
--- /dev/null
+++ b/server/routers/contentstack/contentPage/query.ts
@@ -0,0 +1,64 @@
+import { Lang } from "@/constants/languages"
+import { GetContentPage } from "@/lib/graphql/Query/ContentPage.graphql"
+import { request } from "@/lib/graphql/request"
+import { notFound } from "@/server/errors/trpc"
+import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
+
+import {
+ ContentPage,
+ ContentPageDataRaw,
+ validateContentPageSchema,
+} from "./output"
+import { makeImageVaultImage } from "./utils"
+
+import {
+ TrackingChannelEnum,
+ TrackingSDKPageData,
+} from "@/types/components/tracking"
+
+export const contentPageQueryRouter = router({
+ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
+ const { lang, uid } = ctx
+
+ const response = await request(GetContentPage, {
+ locale: lang,
+ uid,
+ })
+
+ if (!response.data) {
+ throw notFound(response)
+ }
+
+ const validatedContentPage = validateContentPageSchema.safeParse(
+ response.data
+ )
+
+ if (!validatedContentPage.success) {
+ console.error(
+ `Failed to validate Contentpage Data - (lang: ${lang}, uid: ${uid})`
+ )
+ console.error(validatedContentPage.error)
+ return null
+ }
+
+ const contentPageData = validatedContentPage.data.content_page
+ const contentPage: ContentPage = {
+ ...contentPageData,
+ hero_image: makeImageVaultImage(contentPageData.hero_image),
+ }
+
+ const tracking: TrackingSDKPageData = {
+ pageId: contentPageData.system.uid,
+ lang: contentPageData.system.locale as Lang,
+ publishedDate: contentPageData.system.updated_at,
+ createdDate: contentPageData.system.created_at,
+ channel: TrackingChannelEnum["static-content-page"],
+ pageType: "staticcontentpage",
+ }
+
+ return {
+ contentPage,
+ tracking,
+ }
+ }),
+})
diff --git a/server/routers/contentstack/contentPage/utils.ts b/server/routers/contentstack/contentPage/utils.ts
new file mode 100644
index 000000000..d2f748d93
--- /dev/null
+++ b/server/routers/contentstack/contentPage/utils.ts
@@ -0,0 +1,9 @@
+import { insertResponseToImageVaultAsset } from "@/utils/imageVault"
+
+import { InsertResponse } from "@/types/components/imageVaultImage"
+
+export function makeImageVaultImage(image: any) {
+ return image && !!Object.keys(image).length
+ ? insertResponseToImageVaultAsset(image as InsertResponse)
+ : undefined
+}
diff --git a/server/routers/contentstack/index.ts b/server/routers/contentstack/index.ts
index be1ef1ed3..127673eb0 100644
--- a/server/routers/contentstack/index.ts
+++ b/server/routers/contentstack/index.ts
@@ -3,6 +3,7 @@ import { router } from "@/server/trpc"
import { accountPageRouter } from "./accountPage"
import { baseRouter } from "./base"
import { breadcrumbsRouter } from "./breadcrumbs"
+import { contentPageRouter } from "./contentPage"
import { hotelPageRouter } from "./hotelPage"
import { languageSwitcherRouter } from "./languageSwitcher"
import { loyaltyPageRouter } from "./loyaltyPage"
@@ -15,5 +16,6 @@ export const contentstackRouter = router({
hotelPage: hotelPageRouter,
languageSwitcher: languageSwitcherRouter,
loyaltyPage: loyaltyPageRouter,
+ contentPage: contentPageRouter,
myPages: myPagesRouter,
})
diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts
index 20a8e3179..f89796ad9 100644
--- a/server/routers/contentstack/loyaltyPage/output.ts
+++ b/server/routers/contentstack/loyaltyPage/output.ts
@@ -191,11 +191,8 @@ const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [
export const validateLoyaltyPageSchema = z.object({
heading: z.string().nullable(),
- header: z
- .object({
- hero_image: z.any(),
- })
- .nullable(),
+ preamble: z.string().nullable(),
+ hero_image: z.any().nullable(),
blocks: z.array(loyaltyPageBlockItem).nullable(),
sidebar: z.array(loyaltyPageSidebarItem).nullable(),
system: z.object({
@@ -263,7 +260,11 @@ export type Sidebar =
| SideBarDynamicContent
type LoyaltyPageDataRaw = z.infer
-export type LoyaltyPage = Omit & {
+export type LoyaltyPage = Omit<
+ LoyaltyPageDataRaw,
+ "blocks" | "sidebar" | "hero_image"
+> & {
+ hero_image?: ImageVaultAsset
blocks: Block[]
sidebar: Sidebar[]
}
diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts
index 0a0b99142..5fea2a8dd 100644
--- a/server/routers/contentstack/loyaltyPage/query.ts
+++ b/server/routers/contentstack/loyaltyPage/query.ts
@@ -12,7 +12,6 @@ import {
generateTag,
generateTags,
} from "@/utils/generateTag"
-import { insertResponseToImageVaultAsset } from "@/utils/imageVault"
import { removeMultipleSlashes } from "@/utils/url"
import { removeEmptyObjects } from "../../utils"
@@ -22,9 +21,8 @@ import {
validateLoyaltyPageRefsSchema,
validateLoyaltyPageSchema,
} from "./output"
-import { getConnections } from "./utils"
+import { getConnections, makeButtonObject, makeImageVaultImage } from "./utils"
-import { InsertResponse } from "@/types/components/imageVaultImage"
import {
LoyaltyBlocksTypenameEnum,
LoyaltyCardsGridEnum,
@@ -35,35 +33,6 @@ import {
TrackingSDKPageData,
} from "@/types/components/tracking"
-function makeImageVaultImage(image: any) {
- return image && !!Object.keys(image).length
- ? insertResponseToImageVaultAsset(image as InsertResponse)
- : undefined
-}
-
-function makeButtonObject(button: any) {
- if (!button) return null
-
- const isContenstackLink =
- button?.is_contentstack_link || button.linkConnection?.edges?.length
-
- return {
- openInNewTab: button?.open_in_new_tab,
- title:
- button.cta_text ||
- (isContenstackLink
- ? button.linkConnection.edges[0].node.title
- : button.external_link.title),
- href: isContenstackLink
- ? button.linkConnection.edges[0].node.web?.original_url ||
- removeMultipleSlashes(
- `/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}`
- )
- : button.external_link.href,
- isExternal: !isContenstackLink,
- }
-}
-
export const loyaltyPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
@@ -208,17 +177,10 @@ export const loyaltyPageQueryRouter = router({
})
: null
- const header = response.data.loyalty_page.header
- ? {
- hero_image: makeImageVaultImage(
- response.data.loyalty_page.header.hero_image
- ),
- }
- : null
-
const loyaltyPage = {
heading: response.data.loyalty_page.heading,
- header,
+ preamble: response.data.loyalty_page.preamble,
+ hero_image: makeImageVaultImage(response.data.loyalty_page.hero_image),
system: response.data.loyalty_page.system,
blocks,
sidebar,
diff --git a/server/routers/contentstack/loyaltyPage/utils.ts b/server/routers/contentstack/loyaltyPage/utils.ts
index 95e251075..99e66571d 100644
--- a/server/routers/contentstack/loyaltyPage/utils.ts
+++ b/server/routers/contentstack/loyaltyPage/utils.ts
@@ -1,5 +1,9 @@
+import { insertResponseToImageVaultAsset } from "@/utils/imageVault"
+import { removeMultipleSlashes } from "@/utils/url"
+
import { LoyaltyPageRefsDataRaw } from "./output"
+import { InsertResponse } from "@/types/components/imageVaultImage"
import {
LoyaltyBlocksTypenameEnum,
LoyaltyCardsGridEnum,
@@ -77,3 +81,32 @@ export function getConnections(refs: LoyaltyPageRefsDataRaw) {
return connections
}
+
+export function makeImageVaultImage(image: any) {
+ return image && !!Object.keys(image).length
+ ? insertResponseToImageVaultAsset(image as InsertResponse)
+ : undefined
+}
+
+export function makeButtonObject(button: any) {
+ if (!button) return null
+
+ const isContenstackLink =
+ button?.is_contentstack_link || button.linkConnection?.edges?.length
+
+ return {
+ openInNewTab: button?.open_in_new_tab,
+ title:
+ button.cta_text ||
+ (isContenstackLink
+ ? button.linkConnection.edges[0].node.title
+ : button.external_link.title),
+ href: isContenstackLink
+ ? button.linkConnection.edges[0].node.web?.original_url ||
+ removeMultipleSlashes(
+ `/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}`
+ )
+ : button.external_link.href,
+ isExternal: !isContenstackLink,
+ }
+}
diff --git a/types/components/max-width.ts b/types/components/max-width.ts
index e1327b581..4a99b2d99 100644
--- a/types/components/max-width.ts
+++ b/types/components/max-width.ts
@@ -1,3 +1,9 @@
-export interface MaxWidthProps extends React.HTMLAttributes {
+import { VariantProps } from "class-variance-authority"
+
+import { maxWidthVariants } from "@/components/MaxWidth/variants"
+
+export interface MaxWidthProps
+ extends React.HTMLAttributes,
+ VariantProps {
tag?: "article" | "div" | "main" | "section"
}
diff --git a/types/components/tracking.ts b/types/components/tracking.ts
index 354212d8d..92128d939 100644
--- a/types/components/tracking.ts
+++ b/types/components/tracking.ts
@@ -4,6 +4,7 @@ import type { Lang } from "@/constants/languages"
export enum TrackingChannelEnum {
"scandic-friends" = "scandic-friends",
+ "static-content-page" = "static-content-page",
}
export type TrackingChannel = keyof typeof TrackingChannelEnum