diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 687f4450d..9a87f1e3c 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -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 default: const type: never = params.contentType console.error(`Unsupported content type given: ${type}`) diff --git a/app/[lang]/(live)/middleware-error/[status]/page.tsx b/app/[lang]/(live)/middleware-error/[status]/page.tsx index 89e71843c..5fc9dc1c8 100644 --- a/app/[lang]/(live)/middleware-error/[status]/page.tsx +++ b/app/[lang]/(live)/middleware-error/[status]/page.tsx @@ -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) { setLang(params.lang) - const intl = await getIntl() - return (
- {intl.formatMessage( - { id: "Middleware error {lang}: {status}" }, - { - lang: params.lang, - status: params.status, - } - )} + Middleware error {params.lang}: {params.status}
) } diff --git a/components/ContentType/StartPage/index.tsx b/components/ContentType/StartPage/index.tsx new file mode 100644 index 000000000..c7c817b81 --- /dev/null +++ b/components/ContentType/StartPage/index.tsx @@ -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 ( +

+ {JSON.stringify(content, null, 2)} + + + +

+ ) +} diff --git a/components/TempDesignSystem/Breadcrumbs/variants.ts b/components/TempDesignSystem/Breadcrumbs/variants.ts index 069af703f..35fa1963f 100644 --- a/components/TempDesignSystem/Breadcrumbs/variants.ts +++ b/components/TempDesignSystem/Breadcrumbs/variants.ts @@ -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, }, }, diff --git a/lib/graphql/Query/ResolveEntry.graphql b/lib/graphql/Query/ResolveEntry.graphql index 751291d38..e68014288 100644 --- a/lib/graphql/Query/ResolveEntry.graphql +++ b/lib/graphql/Query/ResolveEntry.graphql @@ -76,4 +76,12 @@ query EntryByUrlBatch2($locale: String!, $url: String!) { } total } + all_start_page(where: { url: $url }, locale: $locale) { + items { + system { + ...System + } + } + total + } } diff --git a/lib/graphql/Query/StartPage/StartPage.graphql b/lib/graphql/Query/StartPage/StartPage.graphql new file mode 100644 index 000000000..4a8beb1e8 --- /dev/null +++ b/lib/graphql/Query/StartPage/StartPage.graphql @@ -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 + } +} diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index b3e37157a..d9c381a11 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -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() +}) diff --git a/server/routers/contentstack/bookingwidget/output.ts b/server/routers/contentstack/bookingwidget/output.ts index 088d51a05..6a74712b6 100644 --- a/server/routers/contentstack/bookingwidget/output.ts +++ b/server/routers/contentstack/bookingwidget/output.ts @@ -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< diff --git a/server/routers/contentstack/index.ts b/server/routers/contentstack/index.ts index 9292f3fdd..29bbbb1a4 100644 --- a/server/routers/contentstack/index.ts +++ b/server/routers/contentstack/index.ts @@ -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, }) diff --git a/server/routers/contentstack/languageSwitcher/query.ts b/server/routers/contentstack/languageSwitcher/query.ts index f852f2ade..15da48696 100644 --- a/server/routers/contentstack/languageSwitcher/query.ts +++ b/server/routers/contentstack/languageSwitcher/query.ts @@ -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`) diff --git a/server/routers/contentstack/startPage/index.ts b/server/routers/contentstack/startPage/index.ts new file mode 100644 index 000000000..c8236f2a6 --- /dev/null +++ b/server/routers/contentstack/startPage/index.ts @@ -0,0 +1,5 @@ +import { mergeRouters } from "@/server/trpc" + +import { startPageQueryRouter } from "./query" + +export const startPageRouter = mergeRouters(startPageQueryRouter) diff --git a/server/routers/contentstack/startPage/output.ts b/server/routers/contentstack/startPage/output.ts new file mode 100644 index 000000000..b63a000b1 --- /dev/null +++ b/server/routers/contentstack/startPage/output.ts @@ -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, + }), +}) diff --git a/server/routers/contentstack/startPage/query.ts b/server/routers/contentstack/startPage/query.ts new file mode 100644 index 000000000..d6cdf1986 --- /dev/null +++ b/server/routers/contentstack/startPage/query.ts @@ -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( + 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( + 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, + } + }), +}) diff --git a/server/routers/contentstack/startPage/telemetry.ts b/server/routers/contentstack/startPage/telemetry.ts new file mode 100644 index 000000000..e5296f3e1 --- /dev/null +++ b/server/routers/contentstack/startPage/telemetry.ts @@ -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" +) diff --git a/types/components/tracking.ts b/types/components/tracking.ts index 2e74204c5..29fe50f06 100644 --- a/types/components/tracking.ts +++ b/types/components/tracking.ts @@ -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 diff --git a/types/params.ts b/types/params.ts index 4c4a9d304..ae2a69fcc 100644 --- a/types/params.ts +++ b/types/params.ts @@ -26,6 +26,7 @@ export type ContentTypeParams = { | PageContentTypeEnum.destinationOverviewPage | PageContentTypeEnum.destinationCountryPage | PageContentTypeEnum.destinationCityPage + | PageContentTypeEnum.startPage } export type ContentTypeWebviewParams = { diff --git a/types/requests/contentType.ts b/types/requests/contentType.ts index d41b3710c..3ab996f27 100644 --- a/types/requests/contentType.ts +++ b/types/requests/contentType.ts @@ -8,4 +8,5 @@ export enum PageContentTypeEnum { destinationCityPage = "destination_city_page", hotelPage = "hotel_page", loyaltyPage = "loyalty_page", + startPage = "start_page", } diff --git a/types/requests/entry.ts b/types/requests/entry.ts index b9acc3fd7..47e17fa8e 100644 --- a/types/requests/entry.ts +++ b/types/requests/entry.ts @@ -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, }) diff --git a/types/trpc/routers/contentstack/startPage.ts b/types/trpc/routers/contentstack/startPage.ts new file mode 100644 index 000000000..8966d1569 --- /dev/null +++ b/types/trpc/routers/contentstack/startPage.ts @@ -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 {} +export interface StartPage extends z.output {} + +export interface GetStartPageRefsSchema + extends z.input {} + +export interface StartPageRefs extends z.output {} diff --git a/utils/entry.ts b/utils/entry.ts index d66cf8ca5..452b90d67 100644 --- a/utils/entry.ts +++ b/utils/entry.ts @@ -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,