Merge branch 'develop' into feature/tracking

This commit is contained in:
Linus Flood
2024-10-24 12:39:34 +02:00
221 changed files with 5789 additions and 1491 deletions

View File

@@ -14,7 +14,8 @@ import { removeMultipleSlashes } from "@/utils/url"
import { systemSchema } from "../schemas/system"
import { Image } from "@/types/image"
import { AlertTypeEnum } from "@/types/enums/alert"
import type { Image } from "@/types/image"
// Help me write this zod schema based on the type ContactConfig
export const validateContactConfigSchema = z.object({
@@ -39,10 +40,12 @@ export const validateContactConfigSchema = z.object({
phone: z.object({
number: z.string().nullable(),
name: z.string().nullable(),
footnote: z.string().nullable(),
}),
phone_loyalty: z.object({
number: z.string().nullable(),
name: z.string().nullable(),
footnote: z.string().nullable(),
}),
visiting_address: z.object({
zip: z.string().nullable(),
@@ -366,16 +369,16 @@ export const validateFooterConfigSchema = z
all_footer: z.object({
items: z.array(
z.object({
main_links: validateLinks,
main_links: validateLinks.default([]),
app_downloads: z.object({
title: z.string(),
links: validateLinksWithType,
links: validateLinksWithType.default([]),
}),
secondary_links: validateSecondaryLinks,
secondary_links: validateSecondaryLinks.default([]),
social_media: z.object({
links: validateLinksWithType,
links: validateLinksWithType.default([]),
}),
tertiary_links: validateLinks,
tertiary_links: validateLinks.default([]),
})
),
}),
@@ -415,25 +418,31 @@ export const validateFooterRefConfigSchema = z.object({
items: z
.array(
z.object({
main_links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
secondary_links: z.array(
z.object({
links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
})
),
tertiary_links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
main_links: z
.array(
z.object({
pageConnection: pageConnectionRefs,
})
)
.nullable(),
secondary_links: z
.array(
z.object({
links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
})
)
.nullable(),
tertiary_links: z
.array(
z.object({
pageConnection: pageConnectionRefs,
})
)
.nullable(),
system: systemSchema,
})
)
@@ -538,6 +547,7 @@ export const headerRefsSchema = z
})
const linkUnionSchema = z.discriminatedUnion("__typename", [
pageLinks.accountPageSchema,
pageLinks.contentPageSchema,
pageLinks.hotelPageSchema,
pageLinks.loyaltyPageSchema,
@@ -660,3 +670,166 @@ export const headerSchema = z
},
}
})
export const alertSchema = z
.object({
type: z.nativeEnum(AlertTypeEnum),
text: z.string(),
heading: z.string(),
phone_contact: z.object({
display_text: z.string(),
phone_number: z.string().nullable(),
footnote: z.string().nullable(),
}),
has_link: z.boolean(),
link: linkAndTitleSchema,
has_sidepeek_button: z.boolean(),
sidepeek_button: z.object({
cta_text: z.string(),
}),
sidepeek_content: z.object({
heading: z.string(),
content: z.object({
json: z.any(),
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: z
.discriminatedUnion("__typename", [
pageLinks.accountPageSchema,
pageLinks.contentPageSchema,
pageLinks.hotelPageSchema,
pageLinks.loyaltyPageSchema,
])
.transform((data) => {
const link = pageLinks.transform(data)
if (link) {
return link
}
return data
}),
})
),
}),
}),
}),
})
.transform(
({
type,
heading,
text,
phone_contact,
has_link,
link,
has_sidepeek_button,
sidepeek_button,
sidepeek_content,
}) => {
const hasLink = has_link && link.link
return {
type,
text,
heading,
phoneContact:
phone_contact.display_text && phone_contact.phone_number
? {
displayText: phone_contact.display_text,
phoneNumber: phone_contact.phone_number,
footnote: phone_contact.footnote,
}
: null,
hasSidepeekButton: !!has_sidepeek_button,
link: hasLink
? {
url: link.link.url,
title: link.title,
}
: null,
sidepeekButton:
!hasLink && has_sidepeek_button ? sidepeek_button : null,
sidepeekContent:
!hasLink && has_sidepeek_button ? sidepeek_content : null,
}
}
)
export const siteConfigSchema = z
.object({
all_site_config: z.object({
items: z
.array(
z.object({
sitewide_alert: z.object({
booking_widget_disabled: z.boolean(),
alertConnection: z.object({
edges: z
.array(
z.object({
node: alertSchema,
})
)
.max(1),
}),
}),
})
)
.max(1),
}),
})
.transform((data) => {
if (!data.all_site_config.items.length) {
return {
sitewideAlert: null,
bookingWidgetDisabled: false,
}
}
const { sitewide_alert } = data.all_site_config.items[0]
return {
sitewideAlert: sitewide_alert.alertConnection.edges[0]?.node || null,
bookingWidgetDisabled: sitewide_alert.booking_widget_disabled,
}
})
const sidepeekContentRefSchema = z.object({
content: z.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
pageLinks.accountPageRefSchema,
pageLinks.contentPageRefSchema,
pageLinks.hotelPageRefSchema,
pageLinks.loyaltyPageRefSchema,
]),
})
),
}),
}),
})
const alertConnectionRefSchema = z.object({
edges: z.array(
z.object({
node: z.object({
link: linkRefsSchema,
sidepeek_content: sidepeekContentRefSchema,
}),
})
),
})
export const siteConfigRefSchema = z.object({
all_site_config: z.object({
items: z.array(
z.object({
sitewide_alert: z.object({
alertConnection: alertConnectionRefSchema,
}),
system: systemSchema,
})
),
}),
})

View File

@@ -1,5 +1,4 @@
import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages"
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
import {
GetCurrentFooter,
@@ -11,6 +10,10 @@ import {
} from "@/lib/graphql/Query/Current/Header.graphql"
import { GetFooter, GetFooterRef } from "@/lib/graphql/Query/Footer.graphql"
import { GetHeader, GetHeaderRef } from "@/lib/graphql/Query/Header.graphql"
import {
GetSiteConfig,
GetSiteConfigRef,
} from "@/lib/graphql/Query/SiteConfig.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc"
@@ -31,13 +34,50 @@ import {
type GetCurrentHeaderData,
headerRefsSchema,
headerSchema,
siteConfigRefSchema,
siteConfigSchema,
validateContactConfigSchema,
validateCurrentFooterConfigSchema,
validateCurrentHeaderConfigSchema,
validateFooterConfigSchema,
validateFooterRefConfigSchema,
} from "./output"
import { getConnections, getFooterConnections } from "./utils"
import {
getContactConfigCounter,
getContactConfigFailCounter,
getContactConfigSuccessCounter,
getCurrentFooterCounter,
getCurrentFooterFailCounter,
getCurrentFooterRefCounter,
getCurrentFooterSuccessCounter,
getCurrentHeaderCounter,
getCurrentHeaderFailCounter,
getCurrentHeaderRefCounter,
getCurrentHeaderSuccessCounter,
getFooterCounter,
getFooterFailCounter,
getFooterRefCounter,
getFooterRefFailCounter,
getFooterRefSuccessCounter,
getFooterSuccessCounter,
getHeaderCounter,
getHeaderFailCounter,
getHeaderRefsCounter,
getHeaderRefsFailCounter,
getHeaderRefsSuccessCounter,
getHeaderSuccessCounter,
getSiteConfigCounter,
getSiteConfigFailCounter,
getSiteConfigRefCounter,
getSiteConfigRefFailCounter,
getSiteConfigRefSuccessCounter,
getSiteConfigSuccessCounter,
} from "./telemetry"
import {
getAlertPhoneContactData,
getConnections,
getFooterConnections,
} from "./utils"
import type {
FooterDataRaw,
@@ -47,157 +87,77 @@ import type {
GetHeader as GetHeaderData,
GetHeaderRefs,
} from "@/types/trpc/routers/contentstack/header"
import type {
GetSiteConfigData,
GetSiteConfigRefData,
} from "@/types/trpc/routers/contentstack/siteConfig"
const meter = metrics.getMeter("trpc.contentstack.base")
// OpenTelemetry metrics: ContactConfig
const getContactConfigCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get"
)
const getContactConfigSuccessCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get-success"
)
const getContactConfigFailCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get-fail"
)
// OpenTelemetry metrics: CurrentHeader
const getCurrentHeaderRefCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get"
)
const getCurrentHeaderRefSuccessCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get-success"
)
const getCurrentHeaderRefFailCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get-fail"
)
const getCurrentHeaderCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get"
)
const getCurrentHeaderSuccessCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get-success"
)
const getCurrentHeaderFailCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get-fail"
)
async function getContactConfig(lang: Lang) {
getContactConfigCounter.add(1, { lang })
console.info(
"contentstack.contactConfig start",
JSON.stringify({ query: { lang } })
)
const response = await request<ContactConfigData>(
GetContactConfig,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [`${lang}:contact`],
},
}
)
// OpenTelemetry metrics: Header
const getHeaderRefsCounter = meter.createCounter(
"trpc.contentstack.header.ref.get"
)
const getHeaderRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-success"
)
const getHeaderRefsFailCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-fail"
)
const getHeaderCounter = meter.createCounter("trpc.contentstack.header.get")
const getHeaderSuccessCounter = meter.createCounter(
"trpc.contentstack.header.get-success"
)
const getHeaderFailCounter = meter.createCounter(
"trpc.contentstack.header.get-fail"
)
if (!response.data) {
const notFoundError = notFound(response)
// OpenTelemetry metrics: CurrentHeader
const getCurrentFooterRefCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get"
)
const getCurrentFooterRefSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-success"
)
const getCurrentFooterRefFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-fail"
)
const getCurrentFooterCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get"
)
const getCurrentFooterSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-success"
)
const getCurrentFooterFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-fail"
)
getContactConfigFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
// OpenTelemetry metrics: Footer
const getFooterRefCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get"
)
const getFooterRefSuccessCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get-success"
)
const getFooterRefFailCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get-fail"
)
const getFooterCounter = meter.createCounter("trpc.contentstack.footer.get")
const getFooterSuccessCounter = meter.createCounter(
"trpc.contentstack.footer.get-success"
)
const getFooterFailCounter = meter.createCounter(
"trpc.contentstack.footer.get-fail"
)
console.error(
"contentstack.config not found error",
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
)
throw notFoundError
}
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
response.data
)
if (!validatedContactConfigConfig.success) {
getContactConfigFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedContactConfigConfig.error),
})
console.error(
"contentstack.contactConfig validation error",
JSON.stringify({
query: { lang },
error: validatedContactConfigConfig.error,
})
)
return null
}
getContactConfigSuccessCounter.add(1, { lang })
console.info(
"contentstack.contactConfig success",
JSON.stringify({ query: { lang } })
)
return validatedContactConfigConfig.data.all_contact_config.items[0]
}
export const baseQueryRouter = router({
contact: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
getContactConfigCounter.add(1, { lang })
console.info(
"contentstack.contactConfig start",
JSON.stringify({ query: { lang } })
)
const response = await request<ContactConfigData>(
GetContactConfig,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [`${lang}:contact`],
},
}
)
if (!response.data) {
const notFoundError = notFound(response)
getContactConfigFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.config not found error",
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
)
throw notFoundError
}
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
response.data
)
if (!validatedContactConfigConfig.success) {
getContactConfigFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedContactConfigConfig.error),
})
console.error(
"contentstack.contactConfig validation error",
JSON.stringify({
query: { lang },
error: validatedContactConfigConfig.error,
})
)
return null
}
getContactConfigSuccessCounter.add(1, { lang })
console.info(
"contentstack.contactConfig success",
JSON.stringify({ query: { lang } })
)
return validatedContactConfigConfig.data.all_contact_config.items[0]
return await getContactConfig(ctx.lang)
}),
header: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
@@ -652,4 +612,149 @@ export const baseQueryRouter = router({
return validatedFooterConfig.data
}),
siteConfig: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
getSiteConfigRefCounter.add(1, { lang })
console.info(
"contentstack.siteConfig.ref start",
JSON.stringify({ query: { lang } })
)
const responseRef = await request<GetSiteConfigRefData>(
GetSiteConfigRef,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [generateRefsResponseTag(lang, "siteConfig")],
},
}
)
if (!responseRef.data) {
const notFoundError = notFound(responseRef)
getSiteConfigRefFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.siteConfig.refs not found error",
JSON.stringify({
query: {
lang,
},
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const validatedSiteConfigRef = siteConfigRefSchema.safeParse(
responseRef.data
)
if (!validatedSiteConfigRef.success) {
getSiteConfigRefFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedSiteConfigRef.error),
})
console.error(
"contentstack.siteConfig.refs validation error",
JSON.stringify({
query: {
lang,
},
error: validatedSiteConfigRef.error,
})
)
return null
}
getSiteConfigRefSuccessCounter.add(1, { lang })
console.info(
"contentstack.siteConfig.refs success",
JSON.stringify({ query: { lang } })
)
getSiteConfigCounter.add(1, { lang })
console.info(
"contentstack.siteConfig start",
JSON.stringify({ query: { lang } })
)
const [siteConfigResponse, contactConfig] = await Promise.all([
request<GetSiteConfigData>(
GetSiteConfig,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [`${lang}:siteConfig`],
},
}
),
getContactConfig(lang),
])
if (!siteConfigResponse.data) {
const notFoundError = notFound(siteConfigResponse)
getSiteConfigFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.siteConfig not found error",
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
)
throw notFoundError
}
const validatedSiteConfig = siteConfigSchema.safeParse(
siteConfigResponse.data
)
if (!validatedSiteConfig.success) {
getSiteConfigFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedSiteConfig.error),
})
console.error(
"contentstack.siteConfig validation error",
JSON.stringify({
query: { lang },
error: validatedSiteConfig.error,
})
)
return null
}
getSiteConfigSuccessCounter.add(1, { lang })
console.info(
"contentstack.siteConfig success",
JSON.stringify({ query: { lang } })
)
const { sitewideAlert } = validatedSiteConfig.data
return {
...validatedSiteConfig.data,
sitewideAlert: sitewideAlert
? {
...sitewideAlert,
phoneContact: contactConfig
? getAlertPhoneContactData(sitewideAlert, contactConfig)
: null,
}
: null,
}
}),
})

View File

@@ -0,0 +1,112 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.base")
// OpenTelemetry metrics: ContactConfig
export const getContactConfigCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get"
)
export const getContactConfigSuccessCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get-success"
)
export const getContactConfigFailCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get-fail"
)
// OpenTelemetry metrics: CurrentHeader
export const getCurrentHeaderRefCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get"
)
export const getCurrentHeaderRefSuccessCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get-success"
)
export const getCurrentHeaderRefFailCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get-fail"
)
export const getCurrentHeaderCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get"
)
export const getCurrentHeaderSuccessCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get-success"
)
export const getCurrentHeaderFailCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get-fail"
)
// OpenTelemetry metrics: Header
export const getHeaderRefsCounter = meter.createCounter(
"trpc.contentstack.header.ref.get"
)
export const getHeaderRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-success"
)
export const getHeaderRefsFailCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-fail"
)
export const getHeaderCounter = meter.createCounter(
"trpc.contentstack.header.get"
)
export const getHeaderSuccessCounter = meter.createCounter(
"trpc.contentstack.header.get-success"
)
export const getHeaderFailCounter = meter.createCounter(
"trpc.contentstack.header.get-fail"
)
// OpenTelemetry metrics: CurrentFooter
export const getCurrentFooterRefCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get"
)
export const getCurrentFooterRefSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-success"
)
export const getCurrentFooterRefFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-fail"
)
export const getCurrentFooterCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get"
)
export const getCurrentFooterSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-success"
)
export const getCurrentFooterFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-fail"
)
// OpenTelemetry metrics: Footer
export const getFooterRefCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get"
)
export const getFooterRefSuccessCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get-success"
)
export const getFooterRefFailCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get-fail"
)
export const getFooterCounter = meter.createCounter(
"trpc.contentstack.footer.get"
)
export const getFooterSuccessCounter = meter.createCounter(
"trpc.contentstack.footer.get-success"
)
export const getFooterFailCounter = meter.createCounter(
"trpc.contentstack.footer.get-fail"
)
// OpenTelemetry metrics: SiteConfig
export const getSiteConfigRefCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.ref.get"
)
export const getSiteConfigRefSuccessCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.ref.get-success"
)
export const getSiteConfigRefFailCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.ref.get-fail"
)
export const getSiteConfigCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.get"
)
export const getSiteConfigSuccessCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.get-success"
)
export const getSiteConfigFailCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.get-fail"
)

View File

@@ -1,11 +1,12 @@
import type {
FooterLinkItem,
FooterRefDataRaw,
} from "@/types/components/footer/footer"
import { System } from "@/types/requests/system"
import { Edges } from "@/types/requests/utils/edges"
import { NodeRefs } from "@/types/requests/utils/refs"
import { getValueFromContactConfig } from "@/utils/contactConfig"
import type { FooterRefDataRaw } from "@/types/components/footer/footer"
import type { System } from "@/types/requests/system"
import type { Edges } from "@/types/requests/utils/edges"
import type { NodeRefs } from "@/types/requests/utils/refs"
import type { HeaderRefs } from "@/types/trpc/routers/contentstack/header"
import type { Alert } from "@/types/trpc/routers/contentstack/siteConfig"
import type { ContactConfig } from "./output"
export function getConnections({ header }: HeaderRefs) {
const connections: System["system"][] = [header.system]
@@ -68,3 +69,21 @@ export function getFooterConnections(refs: FooterRefDataRaw) {
return connections
}
export function getAlertPhoneContactData(
alert: Alert,
contactConfig: ContactConfig
) {
if (alert.phoneContact) {
const { displayText, phoneNumber, footnote } = alert.phoneContact
return {
displayText,
phoneNumber: getValueFromContactConfig(phoneNumber, contactConfig),
footnote: footnote
? getValueFromContactConfig(footnote, contactConfig)
: null,
}
}
return null
}

View File

@@ -39,6 +39,18 @@ import {
joinLoyaltyContactRefsSchema,
joinLoyaltyContactSchema,
} from "../schemas/sidebar/joinLoyaltyContact"
import {
quickLinksRefschema,
quickLinksSchema,
} from "../schemas/sidebar/quickLinks"
import {
scriptedCardRefschema,
scriptedCardsSchema,
} from "../schemas/sidebar/scriptedCard"
import {
teaserCardRefschema,
teaserCardsSchema,
} from "../schemas/sidebar/teaserCard"
import { systemSchema } from "../schemas/system"
import { ContentPageEnum } from "@/types/enums/contentPage"
@@ -122,10 +134,31 @@ export const contentPageJoinLoyaltyContact = z
})
.merge(joinLoyaltyContactSchema)
export const contentPageSidebarScriptedCard = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.ScriptedCard),
})
.merge(scriptedCardsSchema)
export const contentPageSidebarTeaserCard = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.TeaserCard),
})
.merge(teaserCardsSchema)
export const contentPageSidebarQuicklinks = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.QuickLinks),
})
.merge(quickLinksSchema)
export const sidebarSchema = z.discriminatedUnion("__typename", [
contentPageSidebarContent,
contentPageSidebarDynamicContent,
contentPageJoinLoyaltyContact,
contentPageSidebarScriptedCard,
contentPageSidebarTeaserCard,
contentPageSidebarQuicklinks,
])
const navigationLinksSchema = z
@@ -241,9 +274,30 @@ const contentPageSidebarJoinLoyaltyContactRef = z
})
.merge(joinLoyaltyContactRefsSchema)
const contentPageSidebarScriptedCardRef = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.ScriptedCard),
})
.merge(scriptedCardRefschema)
const contentPageSidebarTeaserCardRef = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.TeaserCard),
})
.merge(teaserCardRefschema)
const contentPageSidebarQuickLinksRef = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.QuickLinks),
})
.merge(quickLinksRefschema)
const contentPageSidebarRefsItem = z.discriminatedUnion("__typename", [
contentPageSidebarContentRef,
contentPageSidebarJoinLoyaltyContactRef,
contentPageSidebarScriptedCardRef,
contentPageSidebarTeaserCardRef,
contentPageSidebarQuickLinksRef,
])
const contentPageHeaderRefs = z.object({

View File

@@ -1,7 +1,10 @@
import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages"
import { GetContentPageRefs } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import {
GetContentPageBlocksRefs,
GetContentPageRefs,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
@@ -41,18 +44,30 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
query: { lang, uid },
})
)
const refsResponse = await request<GetContentPageRefsSchema>(
GetContentPageRefs,
{ locale: lang, uid },
{
cache: "force-cache",
next: {
tags: [generateTag(lang, uid)],
},
}
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
const [mainRefsResponse, blockRefsResponse] = await Promise.all([
request<GetContentPageRefsSchema>(
GetContentPageRefs,
{ locale: lang, uid },
{
cache: "force-cache",
next: {
tags: [generateTag(lang, uid)],
},
}
),
request<GetContentPageRefsSchema>(
GetContentPageBlocksRefs,
{ locale: lang, uid },
{
cache: "force-cache",
next: {
tags: [generateTag(lang, uid)],
},
}
),
])
if (!mainRefsResponse.data) {
const notFoundError = notFound(mainRefsResponse)
getContentPageRefsFailCounter.add(1, {
lang,
uid,
@@ -73,8 +88,14 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
)
throw notFoundError
}
return refsResponse.data
const responseData = {
...mainRefsResponse.data,
content_page: {
...mainRefsResponse.data.content_page,
blocks: blockRefsResponse.data.content_page.blocks,
},
}
return responseData
}
export function validateContentPageRefs(
@@ -171,17 +192,30 @@ export function getConnections({ content_page }: ContentPageRefs) {
connections.push(...block.content)
}
break
case ContentPageEnum.ContentStack.sidebar.JoinLoyaltyContact:
if (block.join_loyalty_contact?.button) {
connections.push(block.join_loyalty_contact.button)
}
break
case ContentPageEnum.ContentStack.sidebar.ScriptedCard:
if (block.scripted_card?.length) {
connections.push(...block.scripted_card)
}
break
case ContentPageEnum.ContentStack.sidebar.TeaserCard:
if (block.teaser_card?.length) {
connections.push(...block.teaser_card)
}
break
case ContentPageEnum.ContentStack.sidebar.QuickLinks:
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts)
}
break
default:
break
}
})
}
return connections
}

View File

@@ -31,7 +31,8 @@ export const hotelFaqSchema = z
.object({
questions: accordionItemsSchema,
})
.optional(),
.optional()
.nullable(),
})
.transform((data) => {
const array = []
@@ -51,7 +52,7 @@ export const hotelFaqRefsSchema = z
.optional()
.default(HotelPageEnum.ContentStack.blocks.Faq),
global_faqConnection: globalAccordionConnectionRefs.optional(),
specific_faq: specificAccordionConnectionRefs.optional(),
specific_faq: specificAccordionConnectionRefs.optional().nullable(),
})
.transform((data) => {
const array = []

View File

@@ -4,11 +4,7 @@ import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
import { BlocksEnums } from "@/types/enums/blocks"
export const shortcutsSchema = z.object({
typename: z
.literal(BlocksEnums.block.Shortcuts)
.optional()
.default(BlocksEnums.block.Shortcuts),
export const shortcutsBlockSchema = z.object({
shortcuts: z
.object({
subtitle: z.string().nullable(),
@@ -62,6 +58,15 @@ export const shortcutsSchema = z.object({
}),
})
export const shortcutsSchema = z
.object({
typename: z
.literal(BlocksEnums.block.Shortcuts)
.optional()
.default(BlocksEnums.block.Shortcuts),
})
.merge(shortcutsBlockSchema)
export const shortcutsRefsSchema = z.object({
shortcuts: z.object({
shortcuts: z

View File

@@ -11,6 +11,11 @@ const metaData = z.object({
Value: z.string().nullable(),
})
export const focalPointSchema = z.object({
x: z.number(),
y: z.number(),
})
/**
* Defines a media asset, original or conversion
*/
@@ -94,6 +99,7 @@ export const imageVaultAssetSchema = z.object({
* Name of the user that added the asset to ImageVault
*/
AddedBy: z.string(),
FocalPoint: focalPointSchema.optional(),
})
export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform(
@@ -124,6 +130,7 @@ export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform(
height: mediaConversion.Height,
aspectRatio,
},
focalPoint: rawData.FocalPoint || { x: 50, y: 50 },
}
}
)

View File

@@ -10,7 +10,6 @@ import {
import { ContentEnum } from "@/types/enums/content"
import { SidebarEnums } from "@/types/enums/sidebar"
import { System } from "@/types/requests/system"
export const contentSchema = z.object({
typename: z

View File

@@ -0,0 +1,16 @@
import { z } from "zod"
import { shortcutsBlockSchema, shortcutsRefsSchema } from "../blocks/shortcuts"
import { SidebarEnums } from "@/types/enums/sidebar"
export const quickLinksSchema = z
.object({
typename: z
.literal(SidebarEnums.blocks.QuickLinks)
.optional()
.default(SidebarEnums.blocks.QuickLinks),
})
.merge(shortcutsBlockSchema)
export const quickLinksRefschema = shortcutsRefsSchema

View File

@@ -0,0 +1,66 @@
import { z } from "zod"
import {
cardBlockRefsSchema,
cardBlockSchema,
transformCardBlock,
transformCardBlockRefs,
} from "../blocks/cardsGrid"
import { SidebarEnums } from "@/types/enums/sidebar"
export const scriptedCardsSchema = z.object({
typename: z
.literal(SidebarEnums.blocks.ScriptedCard)
.optional()
.default(SidebarEnums.blocks.ScriptedCard),
scripted_card: z
.object({
theme: z
.enum([
"one",
"two",
"three",
"primaryDim",
"primaryDark",
"primaryInverted",
"primaryStrong",
])
.nullable(),
scripted_cardConnection: z.object({
edges: z.array(
z.object({
node: cardBlockSchema,
})
),
}),
})
.transform((data) => {
return {
theme: data.theme,
...transformCardBlock(data.scripted_cardConnection.edges[0].node),
}
}),
})
export const scriptedCardRefschema = z.object({
scripted_card: z
.object({
scripted_cardConnection: z.object({
edges: z.array(
z.object({
node: cardBlockRefsSchema,
})
),
}),
})
.transform((data) => {
let card = null
if (data.scripted_cardConnection.edges.length) {
card = transformCardBlockRefs(
data.scripted_cardConnection.edges[0].node
)
}
return card
}),
})

View File

@@ -0,0 +1,54 @@
import { z } from "zod"
import {
teaserCardBlockRefsSchema,
teaserCardBlockSchema,
transformCardBlockRefs,
transformTeaserCardBlock,
} from "../blocks/cardsGrid"
import { SidebarEnums } from "@/types/enums/sidebar"
export const teaserCardsSchema = z.object({
typename: z
.literal(SidebarEnums.blocks.TeaserCard)
.optional()
.default(SidebarEnums.blocks.TeaserCard),
teaser_card: z
.object({
theme: z.enum(["featured", "default"]).nullable().default("default"),
teaser_cardConnection: z.object({
edges: z.array(
z.object({
node: teaserCardBlockSchema,
})
),
}),
})
.transform((data) => {
return {
...transformTeaserCardBlock(data.teaser_cardConnection.edges[0].node),
theme: data.theme,
}
}),
})
export const teaserCardRefschema = z.object({
teaser_card: z
.object({
teaser_cardConnection: z.object({
edges: z.array(
z.object({
node: teaserCardBlockRefsSchema,
})
),
}),
})
.transform((data) => {
let card = null
if (data.teaser_cardConnection.edges.length) {
card = transformCardBlockRefs(data.teaser_cardConnection.edges[0].node)
}
return card
}),
})