Merged in feat/SW-1381-new-startpage-page (pull request #1197)

feat(SW-1381): add initial start page

* feat(SW-1381): add initial start page

* fix: remove unused startpage template

remove translation key for middleware error page

* fix(SW-1381): add tracking SDK and feature flag to hide start page


Approved-by: Erik Tiekstra
Approved-by: Matilda Landström
Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
Christian Andolf
2025-01-23 10:18:21 +00:00
parent cf8a8a2810
commit 127bb6a0a7
20 changed files with 356 additions and 11 deletions

View File

@@ -10,6 +10,7 @@ import DestinationCountryPage from "@/components/ContentType/DestinationCountryP
import DestinationOverviewPage from "@/components/ContentType/DestinationOverviewPage"
import HotelPage from "@/components/ContentType/HotelPage"
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
import StartPage from "@/components/ContentType/StartPage"
import CollectionPage from "@/components/ContentType/StaticPages/CollectionPage"
import ContentPage from "@/components/ContentType/StaticPages/ContentPage"
import { setLang } from "@/i18n/serverContext"
@@ -74,6 +75,11 @@ export default async function ContentTypePage({
) : (
notFound()
)
case PageContentTypeEnum.startPage:
if (env.HIDE_FOR_NEXT_RELEASE) {
return notFound()
}
return <StartPage />
default:
const type: never = params.contentType
console.error(`Unsupported content type given: ${type}`)

View File

@@ -1,4 +1,3 @@
import { getIntl } from "@/i18n"
import { setLang } from "@/i18n/serverContext"
import styles from "./page.module.css"
@@ -10,17 +9,9 @@ export default async function MiddlewareError({
}: LayoutArgs<LangParams & StatusParams>) {
setLang(params.lang)
const intl = await getIntl()
return (
<div className={styles.layout}>
{intl.formatMessage(
{ id: "Middleware error {lang}: {status}" },
{
lang: params.lang,
status: params.status,
}
)}
Middleware error {params.lang}: {params.status}
</div>
)
}

View File

@@ -0,0 +1,21 @@
import { Suspense } from "react"
import { getStartPage } from "@/lib/trpc/memoizedRequests"
import TrackingSDK from "@/components/TrackingSDK"
export default async function StartPage() {
const content = await getStartPage()
if (!content) {
return null
}
return (
<p>
<code>{JSON.stringify(content, null, 2)}</code>
<Suspense fallback={null}>
<TrackingSDK pageData={content.tracking} />
</Suspense>
</p>
)
}

View File

@@ -15,6 +15,7 @@ export const breadcrumbsVariants = cva(styles.breadcrumbs, {
[PageContentTypeEnum.destinationCityPage]: styles.contentWidth,
[PageContentTypeEnum.hotelPage]: styles.hotelHeaderWidth,
[PageContentTypeEnum.loyaltyPage]: styles.fullWidth,
[PageContentTypeEnum.startPage]: styles.contentWidth,
default: styles.fullWidth,
},
},

View File

@@ -76,4 +76,12 @@ query EntryByUrlBatch2($locale: String!, $url: String!) {
}
total
}
all_start_page(where: { url: $url }, locale: $locale) {
items {
system {
...System
}
}
total
}
}

View File

@@ -0,0 +1,47 @@
#import "../../Fragments/System.graphql"
query GetStartPage($locale: String!, $uid: String!) {
start_page(uid: $uid, locale: $locale) {
title
url
system {
...System
created_at
updated_at
}
}
trackingProps: start_page(locale: "en", uid: $uid) {
url
}
}
query GetStartPageRefs($locale: String!, $uid: String!) {
start_page(locale: $locale, uid: $uid) {
system {
...System
}
}
}
query GetDaDeEnUrlsStartPage($uid: String!) {
de: start_page(locale: "de", uid: $uid) {
url
}
en: start_page(locale: "en", uid: $uid) {
url
}
da: start_page(locale: "da", uid: $uid) {
url
}
}
query GetFiNoSvUrlsStartPage($uid: String!) {
fi: start_page(locale: "fi", uid: $uid) {
url
}
no: start_page(locale: "no", uid: $uid) {
url
}
sv: start_page(locale: "sv", uid: $uid) {
url
}
}

View File

@@ -186,3 +186,7 @@ export const getDestinationCityPage = cache(
return serverClient().contentstack.destinationCityPage.get()
}
)
export const getStartPage = cache(async function getMemoizedStartPage() {
return serverClient().contentstack.startPage.get()
})

View File

@@ -18,6 +18,7 @@ export const validateBookingWidgetToggleSchema = z.object({
destination_city_page: bookingWidgetToggleSchema,
hotel_page: bookingWidgetToggleSchema,
loyalty_page: bookingWidgetToggleSchema,
start_page: bookingWidgetToggleSchema,
})
export type ValidateBookingWidgetToggleType = z.infer<

View File

@@ -16,6 +16,7 @@ import { loyaltyPageRouter } from "./loyaltyPage"
import { metadataRouter } from "./metadata"
import { myPagesRouter } from "./myPages"
import { rewardRouter } from "./reward"
import { startPageRouter } from "./startPage"
export const contentstackRouter = router({
accountPage: accountPageRouter,
@@ -34,4 +35,5 @@ export const contentstackRouter = router({
metadata: metadataRouter,
rewards: rewardRouter,
loyaltyLevels: loyaltyLevelRouter,
startPage: startPageRouter,
})

View File

@@ -39,6 +39,10 @@ import {
GetDaDeEnUrlsLoyaltyPage,
GetFiNoSvUrlsLoyaltyPage,
} from "@/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql"
import {
GetDaDeEnUrlsStartPage,
GetFiNoSvUrlsStartPage,
} from "@/lib/graphql/Query/StartPage/StartPage.graphql"
import { internalServerError } from "@/server/errors/trpc"
import { publicProcedure, router } from "@/server/trpc"
@@ -120,6 +124,10 @@ async function getLanguageSwitcher(options: LanguageSwitcherVariables) {
daDeEnDocument = GetDaDeEnUrlsDestinationCityPage
fiNoSvDocument = GetFiNoSvUrlsDestinationCityPage
break
case PageContentTypeEnum.startPage:
daDeEnDocument = GetDaDeEnUrlsStartPage
fiNoSvDocument = GetFiNoSvUrlsStartPage
break
default:
console.error(`type: [${options.contentType}]`)
console.error(`Trying to get a content type that is not supported`)

View File

@@ -0,0 +1,5 @@
import { mergeRouters } from "@/server/trpc"
import { startPageQueryRouter } from "./query"
export const startPageRouter = mergeRouters(startPageQueryRouter)

View File

@@ -0,0 +1,25 @@
import { z } from "zod"
import { systemSchema } from "../schemas/system"
export const startPageSchema = z.object({
start_page: z.object({
title: z.string(),
system: systemSchema.merge(
z.object({
created_at: z.string(),
updated_at: z.string(),
})
),
}),
trackingProps: z.object({
url: z.string(),
}),
})
/** REFS */
export const startPageRefsSchema = z.object({
start_page: z.object({
system: systemSchema,
}),
})

View File

@@ -0,0 +1,182 @@
import {
GetStartPage,
GetStartPageRefs,
} from "@/lib/graphql/Query/StartPage/StartPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import { startPageRefsSchema, startPageSchema } from "./output"
import {
getStartPageCounter,
getStartPageFailCounter,
getStartPageRefsCounter,
getStartPageRefsFailCounter,
getStartPageRefsSuccessCounter,
getStartPageSuccessCounter,
} from "./telemetry"
import {
TrackingChannelEnum,
type TrackingSDKPageData,
} from "@/types/components/tracking"
import type {
GetStartPageData,
GetStartPageRefsSchema,
} from "@/types/trpc/routers/contentstack/startPage"
export const startPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
getStartPageRefsCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage.refs start",
JSON.stringify({
query: { lang, uid },
})
)
const refsResponse = await request<GetStartPageRefsSchema>(
GetStartPageRefs,
{
locale: lang,
uid,
},
{
cache: "force-cache",
next: {
tags: [generateTag(lang, uid)],
},
}
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getStartPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.startPage.refs not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const validatedRefsData = startPageRefsSchema.safeParse(refsResponse.data)
if (!validatedRefsData.success) {
getStartPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
"contentstack.startPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedRefsData.error,
})
)
return null
}
getStartPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage.refs success",
JSON.stringify({
query: { lang, uid },
})
)
getStartPageCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage start",
JSON.stringify({
query: { lang, uid },
})
)
const response = await request<GetStartPageData>(
GetStartPage,
{
locale: lang,
uid,
},
{
cache: "force-cache",
next: {
tags: [generateTag(lang, uid)],
},
}
)
if (!response.data) {
const notFoundError = notFound(response)
getStartPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.startPage not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const startPage = startPageSchema.safeParse(response.data)
if (!startPage.success) {
getStartPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(startPage.error),
})
console.error(
"contentstack.startPage validation error",
JSON.stringify({
query: { lang, uid },
error: startPage.error,
})
)
return null
}
getStartPageSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage success",
JSON.stringify({
query: { lang, uid },
})
)
const system = startPage.data.start_page.system
const tracking: TrackingSDKPageData = {
pageId: system.uid,
domainLanguage: lang,
publishDate: system.updated_at,
createDate: system.created_at,
channel: TrackingChannelEnum["start-page"],
pageType: "start-page",
pageName: startPage.data.trackingProps.url,
siteSections: startPage.data.trackingProps.url,
siteVersion: "new-web",
}
return {
startPage: startPage.data.start_page,
tracking,
}
}),
})

View File

@@ -0,0 +1,23 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.startPage")
export const getStartPageRefsCounter = meter.createCounter(
"trpc.contentstack.startPage.get"
)
export const getStartPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.startPage.get-fail"
)
export const getStartPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.startPage.get-success"
)
export const getStartPageCounter = meter.createCounter(
"trpc.contentstack.startPage.get"
)
export const getStartPageSuccessCounter = meter.createCounter(
"trpc.contentstack.startPage.get-success"
)
export const getStartPageFailCounter = meter.createCounter(
"trpc.contentstack.startPage.get-fail"
)

View File

@@ -9,6 +9,7 @@ export enum TrackingChannelEnum {
"destination-overview-page" = "destination-overview-page",
"destination-page" = "destination-page",
"hotels" = "hotels",
"start-page" = "start-page",
}
export type TrackingChannel = keyof typeof TrackingChannelEnum

View File

@@ -26,6 +26,7 @@ export type ContentTypeParams = {
| PageContentTypeEnum.destinationOverviewPage
| PageContentTypeEnum.destinationCountryPage
| PageContentTypeEnum.destinationCityPage
| PageContentTypeEnum.startPage
}
export type ContentTypeWebviewParams = {

View File

@@ -8,4 +8,5 @@ export enum PageContentTypeEnum {
destinationCityPage = "destination_city_page",
hotelPage = "hotel_page",
loyaltyPage = "loyalty_page",
startPage = "start_page",
}

View File

@@ -22,4 +22,5 @@ export const validateEntryResolveSchema = z.object({
all_destination_overview_page: entryResolveSchema,
all_destination_country_page: entryResolveSchema,
all_destination_city_page: entryResolveSchema,
all_start_page: entryResolveSchema,
})

View File

@@ -0,0 +1,14 @@
import type { z } from "zod"
import type {
startPageRefsSchema,
startPageSchema,
} from "@/server/routers/contentstack/startPage/output"
export interface GetStartPageData extends z.input<typeof startPageSchema> {}
export interface StartPage extends z.output<typeof startPageSchema> {}
export interface GetStartPageRefsSchema
extends z.input<typeof startPageRefsSchema> {}
export interface StartPageRefs extends z.output<typeof startPageRefsSchema> {}

View File

@@ -9,7 +9,10 @@ import { internalServerError } from "@/server/errors/next"
import { validateEntryResolveSchema } from "@/types/requests/entry"
export async function resolve(url: string, lang = Lang.en) {
const variables = { locale: lang, url }
const variables = { locale: lang, url: url || "/" }
// The maximum amount of content types you can query is 6, therefor more
// than that is being batched
const response = await batchEdgeRequest([
{
document: EntryByUrlBatch1,