diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 974ea7735..687f4450d 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -5,6 +5,8 @@ import { isSignupPage } from "@/constants/routes/signup" import { env } from "@/env/server" import { getHotelPage } from "@/lib/trpc/memoizedRequests" +import DestinationCityPage from "@/components/ContentType/DestinationCityPage" +import DestinationCountryPage from "@/components/ContentType/DestinationCountryPage" import DestinationOverviewPage from "@/components/ContentType/DestinationOverviewPage" import HotelPage from "@/components/ContentType/HotelPage" import LoyaltyPage from "@/components/ContentType/LoyaltyPage" @@ -58,6 +60,10 @@ export default async function ContentTypePage({ return case PageContentTypeEnum.destinationOverviewPage: return + case PageContentTypeEnum.destinationCountryPage: + return + case PageContentTypeEnum.destinationCityPage: + return case PageContentTypeEnum.hotelPage: if (env.HIDE_FOR_NEXT_RELEASE) { return notFound() diff --git a/components/ContentType/DestinationCityPage/destinationCityPage.module.css b/components/ContentType/DestinationCityPage/destinationCityPage.module.css new file mode 100644 index 000000000..c58807d98 --- /dev/null +++ b/components/ContentType/DestinationCityPage/destinationCityPage.module.css @@ -0,0 +1,10 @@ +.pageContainer { + display: grid; + max-width: var(--max-width); +} + +@media screen and (min-width: 768px) { + .pageContainer { + margin: 0 auto; + } +} diff --git a/components/ContentType/DestinationCityPage/index.tsx b/components/ContentType/DestinationCityPage/index.tsx new file mode 100644 index 000000000..16dbedb62 --- /dev/null +++ b/components/ContentType/DestinationCityPage/index.tsx @@ -0,0 +1,28 @@ +import { Suspense } from "react" + +import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests" + +import TrackingSDK from "@/components/TrackingSDK" + +import styles from "./destinationCityPage.module.css" + +export default async function DestinationCityPage() { + const pageData = await getDestinationCityPage() + + if (!pageData) { + return null + } + + const { tracking, destinationCityPage } = pageData + + return ( + <> +
+

Destination City Page

+
+ + + + + ) +} diff --git a/components/ContentType/DestinationCountryPage/destinationCountryPage.module.css b/components/ContentType/DestinationCountryPage/destinationCountryPage.module.css new file mode 100644 index 000000000..c58807d98 --- /dev/null +++ b/components/ContentType/DestinationCountryPage/destinationCountryPage.module.css @@ -0,0 +1,10 @@ +.pageContainer { + display: grid; + max-width: var(--max-width); +} + +@media screen and (min-width: 768px) { + .pageContainer { + margin: 0 auto; + } +} diff --git a/components/ContentType/DestinationCountryPage/index.tsx b/components/ContentType/DestinationCountryPage/index.tsx new file mode 100644 index 000000000..c49f1886e --- /dev/null +++ b/components/ContentType/DestinationCountryPage/index.tsx @@ -0,0 +1,28 @@ +import { Suspense } from "react" + +import { getDestinationCountryPage } from "@/lib/trpc/memoizedRequests" + +import TrackingSDK from "@/components/TrackingSDK" + +import styles from "./destinationCountryPage.module.css" + +export default async function DestinationCountryPage() { + const pageData = await getDestinationCountryPage() + + if (!pageData) { + return null + } + + const { tracking, destinationCountryPage } = pageData + + return ( + <> +
+

Destination Country Page

+
+ + + + + ) +} diff --git a/components/TempDesignSystem/Breadcrumbs/variants.ts b/components/TempDesignSystem/Breadcrumbs/variants.ts index 28ff76517..069af703f 100644 --- a/components/TempDesignSystem/Breadcrumbs/variants.ts +++ b/components/TempDesignSystem/Breadcrumbs/variants.ts @@ -11,6 +11,8 @@ export const breadcrumbsVariants = cva(styles.breadcrumbs, { [PageContentTypeEnum.contentPage]: styles.contentWidth, [PageContentTypeEnum.collectionPage]: styles.contentWidth, [PageContentTypeEnum.destinationOverviewPage]: styles.contentWidth, + [PageContentTypeEnum.destinationCountryPage]: styles.contentWidth, + [PageContentTypeEnum.destinationCityPage]: styles.contentWidth, [PageContentTypeEnum.hotelPage]: styles.hotelHeaderWidth, [PageContentTypeEnum.loyaltyPage]: styles.fullWidth, default: styles.fullWidth, diff --git a/lib/graphql/Fragments/Breadcrumbs/Breadcrumbs.graphql b/lib/graphql/Fragments/Breadcrumbs/Breadcrumbs.graphql index fdc5d2730..010dcf4ac 100644 --- a/lib/graphql/Fragments/Breadcrumbs/Breadcrumbs.graphql +++ b/lib/graphql/Fragments/Breadcrumbs/Breadcrumbs.graphql @@ -1,6 +1,9 @@ #import "./AccountPage.graphql" #import "./CollectionPage.graphql" #import "./ContentPage.graphql" +#import "./DestinationOverviewPage.graphql" +#import "./DestinationCountryPage.graphql" +#import "./DestinationCityPage.graphql" #import "./HotelPage.graphql" #import "./LoyaltyPage.graphql" @@ -13,6 +16,9 @@ fragment Breadcrumbs on Breadcrumbs { ...AccountPageBreadcrumb ...CollectionPageBreadcrumb ...ContentPageBreadcrumb + ...DestinationOverviewPageBreadcrumb + ...DestinationCountryPageBreadcrumb + ...DestinationCityPageBreadcrumb ...HotelPageBreadcrumb ...LoyaltyPageBreadcrumb } @@ -29,6 +35,9 @@ fragment BreadcrumbsRefs on Breadcrumbs { ...AccountPageBreadcrumbRef ...CollectionPageBreadcrumbRef ...ContentPageBreadcrumbRef + ...DestinationOverviewPageBreadcrumbRef + ...DestinationCountryPageBreadcrumbRef + ...DestinationCityPageBreadcrumbRef ...HotelPageBreadcrumbRef ...LoyaltyPageBreadcrumbRef } diff --git a/lib/graphql/Fragments/Breadcrumbs/DestinationCityPage.graphql b/lib/graphql/Fragments/Breadcrumbs/DestinationCityPage.graphql new file mode 100644 index 000000000..67cd5d764 --- /dev/null +++ b/lib/graphql/Fragments/Breadcrumbs/DestinationCityPage.graphql @@ -0,0 +1,24 @@ +#import "../System.graphql" + +fragment DestinationCityPageBreadcrumb on DestinationCityPage { + web { + breadcrumbs { + title + } + } + system { + ...System + } + url +} + +fragment DestinationCityPageBreadcrumbRef on DestinationCityPage { + web { + breadcrumbs { + title + } + } + system { + ...System + } +} diff --git a/lib/graphql/Fragments/Breadcrumbs/DestinationCountryPage.graphql b/lib/graphql/Fragments/Breadcrumbs/DestinationCountryPage.graphql new file mode 100644 index 000000000..1a856cf09 --- /dev/null +++ b/lib/graphql/Fragments/Breadcrumbs/DestinationCountryPage.graphql @@ -0,0 +1,24 @@ +#import "../System.graphql" + +fragment DestinationCountryPageBreadcrumb on DestinationCountryPage { + web { + breadcrumbs { + title + } + } + system { + ...System + } + url +} + +fragment DestinationCountryPageBreadcrumbRef on DestinationCountryPage { + web { + breadcrumbs { + title + } + } + system { + ...System + } +} diff --git a/lib/graphql/Fragments/Breadcrumbs/DestinationOverviewPage.graphql b/lib/graphql/Fragments/Breadcrumbs/DestinationOverviewPage.graphql new file mode 100644 index 000000000..21dd61dbb --- /dev/null +++ b/lib/graphql/Fragments/Breadcrumbs/DestinationOverviewPage.graphql @@ -0,0 +1,24 @@ +#import "../System.graphql" + +fragment DestinationOverviewPageBreadcrumb on DestinationOverviewPage { + web { + breadcrumbs { + title + } + } + system { + ...System + } + url +} + +fragment DestinationOverviewPageBreadcrumbRef on DestinationOverviewPage { + web { + breadcrumbs { + title + } + } + system { + ...System + } +} diff --git a/lib/graphql/Query/BookingWidgetToggle.graphql b/lib/graphql/Query/BookingWidgetToggle.graphql index 412e0a5cc..d42cf7463 100644 --- a/lib/graphql/Query/BookingWidgetToggle.graphql +++ b/lib/graphql/Query/BookingWidgetToggle.graphql @@ -36,6 +36,20 @@ query GetDestinationOverviewPageSettings($uid: String!, $locale: String!) { } } } +query GetDestinationCountryPageSettings($uid: String!, $locale: String!) { + destination_country_page(uid: $uid, locale: $locale) { + page_settings { + hide_booking_widget + } + } +} +query GetDestinationCityPageSettings($uid: String!, $locale: String!) { + destination_city_page(uid: $uid, locale: $locale) { + page_settings { + hide_booking_widget + } + } +} query GetHotelPageSettings($uid: String!, $locale: String!) { hotel_page(uid: $uid, locale: $locale) { diff --git a/lib/graphql/Query/Breadcrumbs/DestinationCityPage.graphql b/lib/graphql/Query/Breadcrumbs/DestinationCityPage.graphql new file mode 100644 index 000000000..13f0c6a1d --- /dev/null +++ b/lib/graphql/Query/Breadcrumbs/DestinationCityPage.graphql @@ -0,0 +1,28 @@ +#import "../../Fragments/Breadcrumbs/Breadcrumbs.graphql" +#import "../../Fragments/System.graphql" + +query GetDestinationCityPageBreadcrumbs($locale: String!, $uid: String!) { + destination_city_page(locale: $locale, uid: $uid) { + web { + breadcrumbs { + ...Breadcrumbs + } + } + system { + ...System + } + } +} + +query GetDestinationCityPageBreadcrumbsRefs($locale: String!, $uid: String!) { + destination_city_page(locale: $locale, uid: $uid) { + web { + breadcrumbs { + ...BreadcrumbsRefs + } + } + system { + ...System + } + } +} diff --git a/lib/graphql/Query/Breadcrumbs/DestinationCountryPage.graphql b/lib/graphql/Query/Breadcrumbs/DestinationCountryPage.graphql new file mode 100644 index 000000000..d0215bc8b --- /dev/null +++ b/lib/graphql/Query/Breadcrumbs/DestinationCountryPage.graphql @@ -0,0 +1,31 @@ +#import "../../Fragments/Breadcrumbs/Breadcrumbs.graphql" +#import "../../Fragments/System.graphql" + +query GetDestinationCountryPageBreadcrumbs($locale: String!, $uid: String!) { + destination_country_page(locale: $locale, uid: $uid) { + web { + breadcrumbs { + ...Breadcrumbs + } + } + system { + ...System + } + } +} + +query GetDestinationCountryPageBreadcrumbsRefs( + $locale: String! + $uid: String! +) { + destination_country_page(locale: $locale, uid: $uid) { + web { + breadcrumbs { + ...BreadcrumbsRefs + } + } + system { + ...System + } + } +} diff --git a/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql b/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql new file mode 100644 index 000000000..111d72e0e --- /dev/null +++ b/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql @@ -0,0 +1,47 @@ +#import "../../Fragments/System.graphql" + +query GetDestinationCityPage($locale: String!, $uid: String!) { + destination_city_page(uid: $uid, locale: $locale) { + title + system { + ...System + created_at + updated_at + } + } + trackingProps: destination_city_page(locale: "en", uid: $uid) { + url + } +} + +query GetDestinationCityPageRefs($locale: String!, $uid: String!) { + destination_city_page(locale: $locale, uid: $uid) { + system { + ...System + } + } +} + +query GetDaDeEnUrlsDestinationCityPage($uid: String!) { + de: destination_city_page(locale: "de", uid: $uid) { + url + } + en: destination_city_page(locale: "en", uid: $uid) { + url + } + da: destination_city_page(locale: "da", uid: $uid) { + url + } +} + +query GetFiNoSvUrlsDestinationCityPage($uid: String!) { + fi: destination_city_page(locale: "fi", uid: $uid) { + url + } + no: destination_city_page(locale: "no", uid: $uid) { + url + } + sv: destination_city_page(locale: "sv", uid: $uid) { + url + } +} diff --git a/lib/graphql/Query/DestinationCityPage/Metadata.graphql b/lib/graphql/Query/DestinationCityPage/Metadata.graphql new file mode 100644 index 000000000..e0b1074f9 --- /dev/null +++ b/lib/graphql/Query/DestinationCityPage/Metadata.graphql @@ -0,0 +1,18 @@ +#import "../../Fragments/Metadata.graphql" +#import "../../Fragments/System.graphql" + +query GetDestinationCityPageMetadata($locale: String!, $uid: String!) { + destination_city_page(locale: $locale, uid: $uid) { + web { + breadcrumbs { + title + } + seo_metadata { + ...Metadata + } + } + system { + ...System + } + } +} diff --git a/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql b/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql new file mode 100644 index 000000000..dd628e279 --- /dev/null +++ b/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql @@ -0,0 +1,47 @@ +#import "../../Fragments/System.graphql" + +query GetDestinationCountryPage($locale: String!, $uid: String!) { + destination_country_page(uid: $uid, locale: $locale) { + title + system { + ...System + created_at + updated_at + } + } + trackingProps: destination_country_page(locale: "en", uid: $uid) { + url + } +} + +query GetDestinationCountryPageRefs($locale: String!, $uid: String!) { + destination_country_page(locale: $locale, uid: $uid) { + system { + ...System + } + } +} + +query GetDaDeEnUrlsDestinationCountryPage($uid: String!) { + de: destination_country_page(locale: "de", uid: $uid) { + url + } + en: destination_country_page(locale: "en", uid: $uid) { + url + } + da: destination_country_page(locale: "da", uid: $uid) { + url + } +} + +query GetFiNoSvUrlsDestinationCountryPage($uid: String!) { + fi: destination_country_page(locale: "fi", uid: $uid) { + url + } + no: destination_country_page(locale: "no", uid: $uid) { + url + } + sv: destination_country_page(locale: "sv", uid: $uid) { + url + } +} diff --git a/lib/graphql/Query/DestinationCountryPage/Metadata.graphql b/lib/graphql/Query/DestinationCountryPage/Metadata.graphql new file mode 100644 index 000000000..771e21255 --- /dev/null +++ b/lib/graphql/Query/DestinationCountryPage/Metadata.graphql @@ -0,0 +1,18 @@ +#import "../../Fragments/Metadata.graphql" +#import "../../Fragments/System.graphql" + +query GetDestinationCountryPageMetadata($locale: String!, $uid: String!) { + destination_country_page(locale: $locale, uid: $uid) { + web { + breadcrumbs { + title + } + seo_metadata { + ...Metadata + } + } + system { + ...System + } + } +} diff --git a/lib/graphql/Query/ResolveEntry.graphql b/lib/graphql/Query/ResolveEntry.graphql index 29b0cb993..751291d38 100644 --- a/lib/graphql/Query/ResolveEntry.graphql +++ b/lib/graphql/Query/ResolveEntry.graphql @@ -60,4 +60,20 @@ query EntryByUrlBatch2($locale: String!, $url: String!) { } total } + all_destination_country_page(where: { url: $url }, locale: $locale) { + items { + system { + ...System + } + } + total + } + all_destination_city_page(where: { url: $url }, locale: $locale) { + items { + system { + ...System + } + } + total + } } diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index 5e1903a18..b3e37157a 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -176,3 +176,13 @@ export const getDestinationOverviewPage = cache( return serverClient().contentstack.destinationOverviewPage.get() } ) +export const getDestinationCountryPage = cache( + async function getMemoizedDestinationCountryPage() { + return serverClient().contentstack.destinationCountryPage.get() + } +) +export const getDestinationCityPage = cache( + async function getMemoizedDestinationCityPage() { + return serverClient().contentstack.destinationCityPage.get() + } +) diff --git a/server/routers/contentstack/bookingwidget/output.ts b/server/routers/contentstack/bookingwidget/output.ts index b88d4270c..088d51a05 100644 --- a/server/routers/contentstack/bookingwidget/output.ts +++ b/server/routers/contentstack/bookingwidget/output.ts @@ -14,6 +14,8 @@ export const validateBookingWidgetToggleSchema = z.object({ content_page: bookingWidgetToggleSchema, current_blocks_page: bookingWidgetToggleSchema, destination_overview_page: bookingWidgetToggleSchema, + destination_country_page: bookingWidgetToggleSchema, + destination_city_page: bookingWidgetToggleSchema, hotel_page: bookingWidgetToggleSchema, loyalty_page: bookingWidgetToggleSchema, }) diff --git a/server/routers/contentstack/bookingwidget/query.ts b/server/routers/contentstack/bookingwidget/query.ts index ed244011a..3a138c7f5 100644 --- a/server/routers/contentstack/bookingwidget/query.ts +++ b/server/routers/contentstack/bookingwidget/query.ts @@ -3,6 +3,8 @@ import { GetCollectionPageSettings, GetContentPageSettings, GetCurrentBlocksPageSettings, + GetDestinationCityPageSettings, + GetDestinationCountryPageSettings, GetDestinationOverviewPageSettings, GetHotelPageSettings, GetLoyaltyPageSettings, @@ -54,6 +56,12 @@ export const bookingwidgetQueryRouter = router({ case PageContentTypeEnum.destinationOverviewPage: GetPageSettings = GetDestinationOverviewPageSettings break + case PageContentTypeEnum.destinationCountryPage: + GetPageSettings = GetDestinationCountryPageSettings + break + case PageContentTypeEnum.destinationCityPage: + GetPageSettings = GetDestinationCityPageSettings + break case PageContentTypeEnum.hotelPage: GetPageSettings = GetHotelPageSettings break diff --git a/server/routers/contentstack/breadcrumbs/query.ts b/server/routers/contentstack/breadcrumbs/query.ts index 5f9676ef3..c96bf59e9 100644 --- a/server/routers/contentstack/breadcrumbs/query.ts +++ b/server/routers/contentstack/breadcrumbs/query.ts @@ -13,6 +13,14 @@ import { GetContentPageBreadcrumbs, GetContentPageBreadcrumbsRefs, } from "@/lib/graphql/Query/Breadcrumbs/ContentPage.graphql" +import { + GetDestinationCityPageBreadcrumbs, + GetDestinationCityPageBreadcrumbsRefs, +} from "@/lib/graphql/Query/Breadcrumbs/DestinationCityPage.graphql" +import { + GetDestinationCountryPageBreadcrumbs, + GetDestinationCountryPageBreadcrumbsRefs, +} from "@/lib/graphql/Query/Breadcrumbs/DestinationCountryPage.graphql" import { GetDestinationOverviewPageBreadcrumbs, GetDestinationOverviewPageBreadcrumbsRefs, @@ -218,6 +226,28 @@ export const breadcrumbsQueryRouter = router({ }, variables ) + case PageContentTypeEnum.destinationCountryPage: + return await getBreadcrumbs<{ + destination_country_page: RawBreadcrumbsSchema + }>( + { + dataKey: "destination_country_page", + refQuery: GetDestinationCountryPageBreadcrumbsRefs, + query: GetDestinationCountryPageBreadcrumbs, + }, + variables + ) + case PageContentTypeEnum.destinationCityPage: + return await getBreadcrumbs<{ + destination_city_page: RawBreadcrumbsSchema + }>( + { + dataKey: "destination_city_page", + refQuery: GetDestinationCityPageBreadcrumbsRefs, + query: GetDestinationCityPageBreadcrumbs, + }, + variables + ) case PageContentTypeEnum.hotelPage: return await getBreadcrumbs<{ hotel_page: RawBreadcrumbsSchema diff --git a/server/routers/contentstack/destinationCityPage/index.ts b/server/routers/contentstack/destinationCityPage/index.ts new file mode 100644 index 000000000..81765282f --- /dev/null +++ b/server/routers/contentstack/destinationCityPage/index.ts @@ -0,0 +1,7 @@ +import { mergeRouters } from "@/server/trpc" + +import { destinationCityPageQueryRouter } from "./query" + +export const destinationCityPageRouter = mergeRouters( + destinationCityPageQueryRouter +) diff --git a/server/routers/contentstack/destinationCityPage/output.ts b/server/routers/contentstack/destinationCityPage/output.ts new file mode 100644 index 000000000..54ec7d334 --- /dev/null +++ b/server/routers/contentstack/destinationCityPage/output.ts @@ -0,0 +1,25 @@ +import { z } from "zod" + +import { systemSchema } from "../schemas/system" + +export const destinationCityPageSchema = z.object({ + destination_city_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 destinationCityPageRefsSchema = z.object({ + destination_city_page: z.object({ + system: systemSchema, + }), +}) diff --git a/server/routers/contentstack/destinationCityPage/query.ts b/server/routers/contentstack/destinationCityPage/query.ts new file mode 100644 index 000000000..de6f6014e --- /dev/null +++ b/server/routers/contentstack/destinationCityPage/query.ts @@ -0,0 +1,179 @@ +import { + GetDestinationCityPage, + GetDestinationCityPageRefs, +} from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPage.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 { + destinationCityPageRefsSchema, + destinationCityPageSchema, +} from "./output" +import { + getDestinationCityPageCounter, + getDestinationCityPageFailCounter, + getDestinationCityPageRefsCounter, + getDestinationCityPageRefsFailCounter, + getDestinationCityPageRefsSuccessCounter, + getDestinationCityPageSuccessCounter, +} from "./telemetry" + +import { + TrackingChannelEnum, + type TrackingSDKPageData, +} from "@/types/components/tracking" +import type { + GetDestinationCityPageData, + GetDestinationCityPageRefsSchema, +} from "@/types/trpc/routers/contentstack/destinationCityPage" + +export const destinationCityPageQueryRouter = router({ + get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { + const { lang, uid } = ctx + + getDestinationCityPageRefsCounter.add(1, { lang, uid }) + console.info( + "contentstack.destinationCityPage.refs start", + JSON.stringify({ query: { lang, uid } }) + ) + + const refsResponse = await request( + GetDestinationCityPageRefs, + { locale: lang, uid }, + { + cache: "force-cache", + next: { + tags: [generateTag(lang, uid)], + }, + } + ) + + if (!refsResponse.data) { + const notFoundError = notFound(refsResponse) + getDestinationCityPageRefsFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "not_found", + error: JSON.stringify({ code: notFoundError.code }), + }) + console.error( + "contentstack.destinationCityPage.refs not found error", + JSON.stringify({ + query: { lang, uid }, + error: { code: notFoundError.code }, + }) + ) + throw notFoundError + } + + const validatedRefsData = destinationCityPageRefsSchema.safeParse( + refsResponse.data + ) + if (!validatedRefsData.success) { + getDestinationCityPageRefsFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "validation_error", + error: JSON.stringify(validatedRefsData.error), + }) + console.error( + "contentstack.destinationCityPage.refs validation error", + JSON.stringify({ query: { lang, uid }, error: validatedRefsData.error }) + ) + return null + } + getDestinationCityPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` }) + console.info( + "contentstack.destinationCityPage.refs success", + JSON.stringify({ query: { lang, uid } }) + ) + + getDestinationCityPageCounter.add(1, { lang, uid: `${uid}` }) + console.info( + "contentstack.destinationCityPage start", + JSON.stringify({ + query: { lang, uid }, + }) + ) + const response = await request( + GetDestinationCityPage, + { + locale: lang, + uid, + }, + { + cache: "force-cache", + next: { + tags: [generateTag(lang, uid)], + }, + } + ) + if (!response.data) { + const notFoundError = notFound(response) + getDestinationCityPageFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "not_found", + error: JSON.stringify({ code: notFoundError.code }), + }) + console.error( + "contentstack.destinationCityPage not found error", + JSON.stringify({ + query: { lang, uid }, + error: { code: notFoundError.code }, + }) + ) + throw notFoundError + } + + const destinationCityPage = destinationCityPageSchema.safeParse( + response.data + ) + + if (!destinationCityPage.success) { + getDestinationCityPageFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "validation_error", + error: JSON.stringify(destinationCityPage.error), + }) + console.error( + "contentstack.destinationCityPage validation error", + JSON.stringify({ + query: { lang, uid }, + error: destinationCityPage.error, + }) + ) + return null + } + + getDestinationCityPageSuccessCounter.add(1, { lang, uid: `${uid}` }) + console.info( + "contentstack.destinationCityPage success", + JSON.stringify({ + query: { lang, uid }, + }) + ) + + const system = destinationCityPage.data.destination_city_page.system + const tracking: TrackingSDKPageData = { + pageId: system.uid, + domainLanguage: lang, + publishDate: system.updated_at, + createDate: system.created_at, + channel: TrackingChannelEnum["destination-page"], + pageType: "staticcontentpage", + pageName: destinationCityPage.data.trackingProps.url, + siteSections: destinationCityPage.data.trackingProps.url, + siteVersion: "new-web", + } + + return { + destinationCityPage: destinationCityPage.data.destination_city_page, + tracking, + } + }), +}) diff --git a/server/routers/contentstack/destinationCityPage/telemetry.ts b/server/routers/contentstack/destinationCityPage/telemetry.ts new file mode 100644 index 000000000..92440d1ce --- /dev/null +++ b/server/routers/contentstack/destinationCityPage/telemetry.ts @@ -0,0 +1,23 @@ +import { metrics } from "@opentelemetry/api" + +const meter = metrics.getMeter("trpc.contentstack.destinationCityPage") + +export const getDestinationCityPageRefsCounter = meter.createCounter( + "trpc.contentstack.destinationCityPage.get" +) +export const getDestinationCityPageRefsFailCounter = meter.createCounter( + "trpc.contentstack.destinationCityPage.get-fail" +) +export const getDestinationCityPageRefsSuccessCounter = meter.createCounter( + "trpc.contentstack.destinationCityPage.get-success" +) + +export const getDestinationCityPageCounter = meter.createCounter( + "trpc.contentstack.destinationCityPage.get" +) +export const getDestinationCityPageSuccessCounter = meter.createCounter( + "trpc.contentstack.destinationCityPage.get-success" +) +export const getDestinationCityPageFailCounter = meter.createCounter( + "trpc.contentstack.destinationCityPage.get-fail" +) diff --git a/server/routers/contentstack/destinationCountryPage/index.ts b/server/routers/contentstack/destinationCountryPage/index.ts new file mode 100644 index 000000000..76da88a62 --- /dev/null +++ b/server/routers/contentstack/destinationCountryPage/index.ts @@ -0,0 +1,7 @@ +import { mergeRouters } from "@/server/trpc" + +import { destinationCountryPageQueryRouter } from "./query" + +export const destinationCountryPageRouter = mergeRouters( + destinationCountryPageQueryRouter +) diff --git a/server/routers/contentstack/destinationCountryPage/output.ts b/server/routers/contentstack/destinationCountryPage/output.ts new file mode 100644 index 000000000..07d01ad7d --- /dev/null +++ b/server/routers/contentstack/destinationCountryPage/output.ts @@ -0,0 +1,25 @@ +import { z } from "zod" + +import { systemSchema } from "../schemas/system" + +export const destinationCountryPageSchema = z.object({ + destination_country_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 destinationCountryPageRefsSchema = z.object({ + destination_country_page: z.object({ + system: systemSchema, + }), +}) diff --git a/server/routers/contentstack/destinationCountryPage/query.ts b/server/routers/contentstack/destinationCountryPage/query.ts new file mode 100644 index 000000000..48ede62e9 --- /dev/null +++ b/server/routers/contentstack/destinationCountryPage/query.ts @@ -0,0 +1,180 @@ +import { + GetDestinationCountryPage, + GetDestinationCountryPageRefs, +} from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.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 { + destinationCountryPageRefsSchema, + destinationCountryPageSchema, +} from "./output" +import { + getDestinationCountryPageCounter, + getDestinationCountryPageFailCounter, + getDestinationCountryPageRefsCounter, + getDestinationCountryPageRefsFailCounter, + getDestinationCountryPageRefsSuccessCounter, + getDestinationCountryPageSuccessCounter, +} from "./telemetry" + +import { + TrackingChannelEnum, + type TrackingSDKPageData, +} from "@/types/components/tracking" +import type { + GetDestinationCountryPageData, + GetDestinationCountryPageRefsSchema, +} from "@/types/trpc/routers/contentstack/destinationCountryPage" + +export const destinationCountryPageQueryRouter = router({ + get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { + const { lang, uid } = ctx + + getDestinationCountryPageRefsCounter.add(1, { lang, uid }) + console.info( + "contentstack.destinationCountryPage.refs start", + JSON.stringify({ query: { lang, uid } }) + ) + + const refsResponse = await request( + GetDestinationCountryPageRefs, + { locale: lang, uid }, + { + cache: "force-cache", + next: { + tags: [generateTag(lang, uid)], + }, + } + ) + + if (!refsResponse.data) { + const notFoundError = notFound(refsResponse) + getDestinationCountryPageRefsFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "not_found", + error: JSON.stringify({ code: notFoundError.code }), + }) + console.error( + "contentstack.destinationCountryPage.refs not found error", + JSON.stringify({ + query: { lang, uid }, + error: { code: notFoundError.code }, + }) + ) + throw notFoundError + } + + const validatedRefsData = destinationCountryPageRefsSchema.safeParse( + refsResponse.data + ) + if (!validatedRefsData.success) { + getDestinationCountryPageRefsFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "validation_error", + error: JSON.stringify(validatedRefsData.error), + }) + console.error( + "contentstack.destinationCountryPage.refs validation error", + JSON.stringify({ query: { lang, uid }, error: validatedRefsData.error }) + ) + return null + } + getDestinationCountryPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` }) + console.info( + "contentstack.destinationCountryPage.refs success", + JSON.stringify({ query: { lang, uid } }) + ) + + getDestinationCountryPageCounter.add(1, { lang, uid: `${uid}` }) + console.info( + "contentstack.destinationCountryPage start", + JSON.stringify({ + query: { lang, uid }, + }) + ) + const response = await request( + GetDestinationCountryPage, + { + locale: lang, + uid, + }, + { + cache: "force-cache", + next: { + tags: [generateTag(lang, uid)], + }, + } + ) + if (!response.data) { + const notFoundError = notFound(response) + getDestinationCountryPageFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "not_found", + error: JSON.stringify({ code: notFoundError.code }), + }) + console.error( + "contentstack.destinationCountryPage not found error", + JSON.stringify({ + query: { lang, uid }, + error: { code: notFoundError.code }, + }) + ) + throw notFoundError + } + + const destinationCountryPage = destinationCountryPageSchema.safeParse( + response.data + ) + + if (!destinationCountryPage.success) { + getDestinationCountryPageFailCounter.add(1, { + lang, + uid: `${uid}`, + error_type: "validation_error", + error: JSON.stringify(destinationCountryPage.error), + }) + console.error( + "contentstack.destinationCountryPage validation error", + JSON.stringify({ + query: { lang, uid }, + error: destinationCountryPage.error, + }) + ) + return null + } + + getDestinationCountryPageSuccessCounter.add(1, { lang, uid: `${uid}` }) + console.info( + "contentstack.destinationCountryPage success", + JSON.stringify({ + query: { lang, uid }, + }) + ) + + const system = destinationCountryPage.data.destination_country_page.system + const tracking: TrackingSDKPageData = { + pageId: system.uid, + domainLanguage: lang, + publishDate: system.updated_at, + createDate: system.created_at, + channel: TrackingChannelEnum["destination-page"], + pageType: "staticcontentpage", + pageName: destinationCountryPage.data.trackingProps.url, + siteSections: destinationCountryPage.data.trackingProps.url, + siteVersion: "new-web", + } + + return { + destinationCountryPage: + destinationCountryPage.data.destination_country_page, + tracking, + } + }), +}) diff --git a/server/routers/contentstack/destinationCountryPage/telemetry.ts b/server/routers/contentstack/destinationCountryPage/telemetry.ts new file mode 100644 index 000000000..5ba3445b3 --- /dev/null +++ b/server/routers/contentstack/destinationCountryPage/telemetry.ts @@ -0,0 +1,23 @@ +import { metrics } from "@opentelemetry/api" + +const meter = metrics.getMeter("trpc.contentstack.destinationCountryPage") + +export const getDestinationCountryPageRefsCounter = meter.createCounter( + "trpc.contentstack.destinationCountryPage.get" +) +export const getDestinationCountryPageRefsFailCounter = meter.createCounter( + "trpc.contentstack.destinationCountryPage.get-fail" +) +export const getDestinationCountryPageRefsSuccessCounter = meter.createCounter( + "trpc.contentstack.destinationCountryPage.get-success" +) + +export const getDestinationCountryPageCounter = meter.createCounter( + "trpc.contentstack.destinationCountryPage.get" +) +export const getDestinationCountryPageSuccessCounter = meter.createCounter( + "trpc.contentstack.destinationCountryPage.get-success" +) +export const getDestinationCountryPageFailCounter = meter.createCounter( + "trpc.contentstack.destinationCountryPage.get-fail" +) diff --git a/server/routers/contentstack/index.ts b/server/routers/contentstack/index.ts index c1c192bc5..9292f3fdd 100644 --- a/server/routers/contentstack/index.ts +++ b/server/routers/contentstack/index.ts @@ -6,6 +6,8 @@ import { bookingwidgetRouter } from "./bookingwidget" import { breadcrumbsRouter } from "./breadcrumbs" import { collectionPageRouter } from "./collectionPage" import { contentPageRouter } from "./contentPage" +import { destinationCityPageRouter } from "./destinationCityPage" +import { destinationCountryPageRouter } from "./destinationCountryPage" import { destinationOverviewPageRouter } from "./destinationOverviewPage" import { hotelPageRouter } from "./hotelPage" import { languageSwitcherRouter } from "./languageSwitcher" @@ -26,6 +28,8 @@ export const contentstackRouter = router({ collectionPage: collectionPageRouter, contentPage: contentPageRouter, destinationOverviewPage: destinationOverviewPageRouter, + destinationCountryPage: destinationCountryPageRouter, + destinationCityPage: destinationCityPageRouter, myPages: myPagesRouter, metadata: metadataRouter, rewards: rewardRouter, diff --git a/server/routers/contentstack/languageSwitcher/query.ts b/server/routers/contentstack/languageSwitcher/query.ts index 88e9c32be..f852f2ade 100644 --- a/server/routers/contentstack/languageSwitcher/query.ts +++ b/server/routers/contentstack/languageSwitcher/query.ts @@ -19,6 +19,14 @@ import { GetDaDeEnUrlsCurrentBlocksPage, GetFiNoSvUrlsCurrentBlocksPage, } from "@/lib/graphql/Query/Current/LanguageSwitcher.graphql" +import { + GetDaDeEnUrlsDestinationCityPage, + GetFiNoSvUrlsDestinationCityPage, +} from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql" +import { + GetDaDeEnUrlsDestinationCountryPage, + GetFiNoSvUrlsDestinationCountryPage, +} from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql" import { GetDaDeEnUrlsDestinationOverviewPage, GetFiNoSvUrlsDestinationOverviewPage, @@ -104,6 +112,14 @@ async function getLanguageSwitcher(options: LanguageSwitcherVariables) { daDeEnDocument = GetDaDeEnUrlsDestinationOverviewPage fiNoSvDocument = GetFiNoSvUrlsDestinationOverviewPage break + case PageContentTypeEnum.destinationCountryPage: + daDeEnDocument = GetDaDeEnUrlsDestinationCountryPage + fiNoSvDocument = GetFiNoSvUrlsDestinationCountryPage + break + case PageContentTypeEnum.destinationCityPage: + daDeEnDocument = GetDaDeEnUrlsDestinationCityPage + fiNoSvDocument = GetFiNoSvUrlsDestinationCityPage + 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/metadata/query.ts b/server/routers/contentstack/metadata/query.ts index 92ae60ef0..11c5b30b0 100644 --- a/server/routers/contentstack/metadata/query.ts +++ b/server/routers/contentstack/metadata/query.ts @@ -4,6 +4,8 @@ import { cache } from "react" import { GetAccountPageMetadata } from "@/lib/graphql/Query/AccountPage/Metadata.graphql" import { GetCollectionPageMetadata } from "@/lib/graphql/Query/CollectionPage/Metadata.graphql" import { GetContentPageMetadata } from "@/lib/graphql/Query/ContentPage/Metadata.graphql" +import { GetDestinationCityPageMetadata } from "@/lib/graphql/Query/DestinationCityPage/Metadata.graphql" +import { GetDestinationCountryPageMetadata } from "@/lib/graphql/Query/DestinationCountryPage/Metadata.graphql" import { GetDestinationOverviewPageMetadata } from "@/lib/graphql/Query/DestinationOverviewPage/Metadata.graphql" import { GetHotelPageMetadata } from "@/lib/graphql/Query/HotelPage/Metadata.graphql" import { GetLoyaltyPageMetadata } from "@/lib/graphql/Query/LoyaltyPage/Metadata.graphql" @@ -144,6 +146,20 @@ export const metadataQueryRouter = router({ return getTransformedMetadata( destinationOverviewPageResponse.destination_overview_page ) + case PageContentTypeEnum.destinationCountryPage: + const destinationCountryPageResponse = await fetchMetadata<{ + destination_country_page: RawMetadataSchema + }>(GetDestinationCountryPageMetadata, variables) + return getTransformedMetadata( + destinationCountryPageResponse.destination_country_page + ) + case PageContentTypeEnum.destinationCityPage: + const destinationCityPageResponse = await fetchMetadata<{ + destination_city_page: RawMetadataSchema + }>(GetDestinationCityPageMetadata, variables) + return getTransformedMetadata( + destinationCityPageResponse.destination_city_page + ) case PageContentTypeEnum.loyaltyPage: const loyaltyPageResponse = await fetchMetadata<{ loyalty_page: RawMetadataSchema diff --git a/types/components/tracking.ts b/types/components/tracking.ts index f39eada37..2e74204c5 100644 --- a/types/components/tracking.ts +++ b/types/components/tracking.ts @@ -7,6 +7,7 @@ export enum TrackingChannelEnum { "hotelreservation" = "hotelreservation", "collection-page" = "collection-page", "destination-overview-page" = "destination-overview-page", + "destination-page" = "destination-page", "hotels" = "hotels", } diff --git a/types/params.ts b/types/params.ts index 24c59b915..4c4a9d304 100644 --- a/types/params.ts +++ b/types/params.ts @@ -24,6 +24,8 @@ export type ContentTypeParams = { | PageContentTypeEnum.hotelPage | PageContentTypeEnum.collectionPage | PageContentTypeEnum.destinationOverviewPage + | PageContentTypeEnum.destinationCountryPage + | PageContentTypeEnum.destinationCityPage } export type ContentTypeWebviewParams = { diff --git a/types/requests/contentType.ts b/types/requests/contentType.ts index a081e0538..d41b3710c 100644 --- a/types/requests/contentType.ts +++ b/types/requests/contentType.ts @@ -4,6 +4,8 @@ export enum PageContentTypeEnum { contentPage = "content_page", currentBlocksPage = "current_blocks_page", destinationOverviewPage = "destination_overview_page", + destinationCountryPage = "destination_country_page", + destinationCityPage = "destination_city_page", hotelPage = "hotel_page", loyaltyPage = "loyalty_page", } diff --git a/types/requests/entry.ts b/types/requests/entry.ts index 475473c70..b9acc3fd7 100644 --- a/types/requests/entry.ts +++ b/types/requests/entry.ts @@ -20,4 +20,6 @@ export const validateEntryResolveSchema = z.object({ all_current_blocks_page: entryResolveSchema, all_hotel_page: entryResolveSchema, all_destination_overview_page: entryResolveSchema, + all_destination_country_page: entryResolveSchema, + all_destination_city_page: entryResolveSchema, }) diff --git a/types/trpc/routers/contentstack/destinationCityPage.ts b/types/trpc/routers/contentstack/destinationCityPage.ts new file mode 100644 index 000000000..352a18a24 --- /dev/null +++ b/types/trpc/routers/contentstack/destinationCityPage.ts @@ -0,0 +1,17 @@ +import type { z } from "zod" + +import type { + destinationCityPageRefsSchema, + destinationCityPageSchema, +} from "@/server/routers/contentstack/destinationCityPage/output" + +export interface GetDestinationCityPageData + extends z.input {} +export interface DestinationCityPage + extends z.output {} + +export interface GetDestinationCityPageRefsSchema + extends z.input {} + +export interface DestinationCityPageRefs + extends z.output {} diff --git a/types/trpc/routers/contentstack/destinationCountryPage.ts b/types/trpc/routers/contentstack/destinationCountryPage.ts new file mode 100644 index 000000000..c6b5b1688 --- /dev/null +++ b/types/trpc/routers/contentstack/destinationCountryPage.ts @@ -0,0 +1,17 @@ +import type { z } from "zod" + +import type { + destinationCountryPageRefsSchema, + destinationCountryPageSchema, +} from "@/server/routers/contentstack/destinationCountryPage/output" + +export interface GetDestinationCountryPageData + extends z.input {} +export interface DestinationCountryPage + extends z.output {} + +export interface GetDestinationCountryPageRefsSchema + extends z.input {} + +export interface DestinationCountryPageRefs + extends z.output {} diff --git a/types/trpc/routers/contentstack/destinationOverviewPage.ts b/types/trpc/routers/contentstack/destinationOverviewPage.ts index 02a4b7efa..b541697b9 100644 --- a/types/trpc/routers/contentstack/destinationOverviewPage.ts +++ b/types/trpc/routers/contentstack/destinationOverviewPage.ts @@ -7,7 +7,7 @@ import type { export interface GetDestinationOverviewPageData extends z.input {} -export interface DestinationPage +export interface DestinationOverviewPage extends z.output {} export interface GetDestinationOverviewPageRefsSchema