import { z } from "zod" import { Lang } from "@/constants/languages" import { removeMultipleSlashes } from "@/utils/url" import { imageVaultAssetTransformedSchema } from "../schemas/imageVault" import { Image } from "@/types/image" // Help me write this zod schema based on the type ContactConfig export const validateContactConfigSchema = z.object({ all_contact_config: z.object({ items: z.array( z.object({ email: z.object({ name: z.string().nullable(), address: z.string().nullable(), }), email_loyalty: z.object({ name: z.string().nullable(), address: z.string().nullable(), }), mailing_address: z.object({ zip: z.string().nullable(), street: z.string().nullable(), name: z.string().nullable(), city: z.string().nullable(), country: z.string().nullable(), }), phone: z.object({ number: z.string().nullable(), name: z.string().nullable(), }), phone_loyalty: z.object({ number: z.string().nullable(), name: z.string().nullable(), }), visiting_address: z.object({ zip: z.string().nullable(), country: z.string().nullable(), city: z.string().nullable(), street: z.string().nullable(), }), }) ), }), }) export enum ContactFieldGroupsEnum { email = "email", email_loyalty = "email_loyalty", mailing_address = "mailing_address", phone = "phone", phone_loyalty = "phone_loyalty", visiting_address = "visiting_address", } export type ContactFieldGroups = keyof typeof ContactFieldGroupsEnum export type ContactConfigData = z.infer export type ContactConfig = ContactConfigData["all_contact_config"]["items"][0] export type ContactFields = { display_text: string | null contact_field: string footnote: string | null } export const validateCurrentHeaderConfigSchema = z.object({ all_current_header: z.object({ items: z.array( z.object({ frontpage_link_text: z.string(), logoConnection: z.object({ edges: z.array( z.object({ node: z.object({ description: z.string().optional().nullable(), dimension: z.object({ height: z.number(), width: z.number(), }), metadata: z.any().nullable(), system: z.object({ uid: z.string(), }), title: z.string().nullable(), url: z.string().nullable(), }), }) ), }), menu: z.object({ links: z.array( z.object({ href: z.string(), title: z.string(), }) ), }), top_menu: z.object({ links: z.array( z.object({ link: z.object({ href: z.string(), title: z.string(), }), show_on_mobile: z.boolean(), sort_order_mobile: z.number(), }) ), }), }) ), }), }) export type CurrentHeaderDataRaw = z.infer< typeof validateCurrentHeaderConfigSchema > export type CurrentHeaderData = Omit< CurrentHeaderDataRaw["all_current_header"]["items"][0], "logoConnection" > & { logo: Image } const validateCurrentHeaderRefConfigSchema = z.object({ all_current_header: z.object({ items: z.array( z.object({ system: z.object({ content_type_uid: z.string(), uid: z.string(), }), }) ), }), }) export type CurrentHeaderRefDataRaw = z.infer< typeof validateCurrentHeaderRefConfigSchema > const validateAppDownload = z.object({ href: z.string(), imageConnection: z.object({ edges: z.array( z.object({ node: z.object({ description: z.string().optional().nullable(), dimension: z.object({ height: z.number(), width: z.number(), }), metadata: z.any().nullable(), system: z.object({ uid: z.string(), }), title: z.string(), url: z.string(), }), }) ), }), }) const validateNavigationItem = z.object({ links: z.array(z.object({ href: z.string(), title: z.string() })), title: z.string(), }) export type NavigationItem = z.infer export const validateCurrentFooterConfigSchema = z.object({ all_current_footer: z.object({ items: z.array( z.object({ title: z.string(), about: z.object({ title: z.string(), text: z.string(), }), app_downloads: z.object({ title: z.string(), app_store: validateAppDownload, google_play: validateAppDownload, }), logoConnection: z.object({ edges: z.array( z.object({ node: z.object({ description: z.string().optional().nullable(), dimension: z.object({ height: z.number(), width: z.number(), }), metadata: z.any().nullable(), system: z.object({ uid: z.string(), }), title: z.string(), url: z.string(), }), }) ), }), navigation: z.array(validateNavigationItem), social_media: z.object({ title: z.string(), facebook: z.object({ href: z.string(), title: z.string() }), instagram: z.object({ href: z.string(), title: z.string() }), twitter: z.object({ href: z.string(), title: z.string() }), }), trip_advisor: z.object({ title: z.string(), logoConnection: z.object({ edges: z.array( z.object({ node: z.object({ description: z.string().optional().nullable(), dimension: z.object({ height: z.number(), width: z.number(), }), metadata: z.any().nullable(), system: z.object({ uid: z.string(), }), title: z.string(), url: z.string(), }), }) ), }), }), }) ), }), }) export type CurrentFooterDataRaw = z.infer< typeof validateCurrentFooterConfigSchema > export type CurrentFooterData = Omit< CurrentFooterDataRaw["all_current_footer"]["items"][0], "logoConnection" > & { logo: Image } const validateCurrentFooterRefConfigSchema = z.object({ all_current_footer: z.object({ items: z.array( z.object({ system: z.object({ content_type_uid: z.string(), uid: z.string(), }), }) ), }), }) export type CurrentFooterRefDataRaw = z.infer< typeof validateCurrentFooterRefConfigSchema > const validateExternalLink = z .object({ href: z.string(), title: z.string(), }) .optional() const validateInternalLink = z .object({ edges: z .array( z.object({ node: z.object({ system: z.object({ uid: z.string(), locale: z.nativeEnum(Lang), }), url: z.string(), title: z.string(), web: z .object({ original_url: z.string(), }) .optional(), }), }) ) .max(1), }) .transform((data) => { const node = data.edges[0]?.node if (!node) { return null } const url = node.url const originalUrl = node.web?.original_url const lang = node.system.locale return { url: originalUrl ?? removeMultipleSlashes(`/${lang}/${url}`), title: node.title, } }) .optional() export const validateLinkItem = z .object({ title: z.string(), open_in_new_tab: z.boolean(), link: validateExternalLink, pageConnection: validateInternalLink, }) .transform((data) => { return { url: data.pageConnection?.url ?? data.link?.href ?? "", title: data?.title ?? data.link?.title, openInNewTab: data.open_in_new_tab, isExternal: !!data.link?.href, } }) export const validateSecondaryLinks = z.array( z.object({ title: z.string(), links: z.array(validateLinkItem), }) ) export const validateLinksWithType = z.array( z.object({ type: z.string(), href: validateExternalLink, }) ) export const validateFooterConfigSchema = z .object({ all_footer: z.object({ items: z.array( z.object({ main_links: z.array(validateLinkItem), app_downloads: z.object({ title: z.string(), links: validateLinksWithType, }), secondary_links: validateSecondaryLinks, social_media: z.object({ links: validateLinksWithType, }), tertiary_links: z.array(validateLinkItem), }) ), }), }) .transform((data) => { const { main_links, app_downloads, secondary_links, social_media, tertiary_links, } = data.all_footer.items[0] return { mainLinks: main_links, appDownloads: app_downloads, secondaryLinks: secondary_links, socialMedia: social_media, tertiaryLinks: tertiary_links, } }) const pageConnectionRefs = z.object({ edges: z .array( z.object({ node: z.object({ system: z.object({ content_type_uid: z.string(), uid: z.string(), }), }), }) ) .max(1), }) export const validateFooterRefConfigSchema = z.object({ all_footer: 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, }) ), system: z.object({ content_type_uid: z.string(), uid: z.string(), }), }) ) .length(1), }), }) const linkConnectionNodeSchema = z .object({ edges: z .array( z.object({ node: z.object({ system: z.object({ uid: z.string(), locale: z.nativeEnum(Lang), }), url: z.string(), title: z.string(), web: z.object({ original_url: z.string(), }), }), }) ) .max(1), }) .transform((data) => { const node = data.edges[0]?.node if (!node) { return null } const url = node.url const originalUrl = node.web?.original_url const lang = node.system.locale return { href: originalUrl || removeMultipleSlashes(`/${lang}/${url}`), isExternal: !!originalUrl, } }) const linkWithTitleSchema = z .object({ title: z.string(), linkConnection: linkConnectionNodeSchema, }) .transform((rawData) => { return rawData.linkConnection && rawData.title ? { ...rawData.linkConnection, title: rawData.title, } : null }) const cardButtonSchema = z .object({ cta_text: z.string(), external_link: z.object({ href: z.string(), title: z.string(), }), is_contentstack_link: z.boolean(), linkConnection: linkConnectionNodeSchema, open_in_new_tab: z.boolean(), }) .transform((data) => { const linkConnectionData = data.linkConnection const isContentstackLink = data.is_contentstack_link const externalLink = data.external_link const href = isContentstackLink && externalLink.href ? externalLink.href : linkConnectionData?.href ?? "" return { openInNewTab: data.open_in_new_tab, title: data.cta_text, href, isExternal: !isContentstackLink || linkConnectionData?.isExternal, } }) const cardConnectionSchema = z .object({ edges: z .array( z.object({ node: z.object({ heading: z.string(), body_text: z.string(), background_image: imageVaultAssetTransformedSchema, has_primary_button: z.boolean(), has_secondary_button: z.boolean(), scripted_top_title: z.string(), primary_button: cardButtonSchema.nullable(), secondary_button: cardButtonSchema.nullable(), }), }) ) .max(1), }) .transform((data) => { const node = data.edges[0]?.node if (!node) { return null } return { scriptedTopTitle: node.scripted_top_title, heading: node.heading, bodyText: node.body_text, backgroundImage: node.background_image, primaryButton: node.has_primary_button ? node.primary_button : null, secondaryButton: node.has_secondary_button ? node.secondary_button : null, } }) export const menuItemSchema = z .object({ title: z.string(), linkConnection: linkConnectionNodeSchema, submenu: z.array( z.object({ title: z.string(), links: z.array(linkWithTitleSchema), }) ), see_all_link: linkWithTitleSchema, cardConnection: cardConnectionSchema, }) .transform((data) => { const { submenu, linkConnection, cardConnection, see_all_link, title } = data return { title, link: submenu.length ? null : linkConnection, seeAllLink: submenu.length ? see_all_link : null, submenu, card: cardConnection, } }) export const getHeaderSchema = z .object({ all_header: z.object({ items: z .array( z.object({ top_link: linkWithTitleSchema, menu_items: z.array(menuItemSchema), }) ) .length(1), }), }) .transform((data) => { const { top_link, menu_items } = data.all_header.items[0] return { topLink: top_link, menuItems: menu_items, } }) const linkConnectionRefs = z.object({ edges: z .array( z.object({ node: z.object({ system: z.object({ content_type_uid: z.string(), uid: z.string(), }), }), }) ) .max(1), }) const cardConnectionRefs = z.object({ primary_button: z .object({ linkConnection: linkConnectionRefs, }) .nullable(), secondary_button: z .object({ linkConnection: linkConnectionRefs, }) .nullable(), system: z.object({ content_type_uid: z.string(), uid: z.string(), }), }) export const getHeaderRefSchema = z.object({ all_header: z.object({ items: z .array( z.object({ top_link: z .object({ linkConnection: linkConnectionRefs, }) .nullable(), menu_items: z.array( z.object({ linkConnection: linkConnectionRefs, see_all_link: z.object({ linkConnection: linkConnectionRefs, }), cardConnection: z.object({ edges: z .array( z.object({ node: cardConnectionRefs, }) ) .max(1), }), submenu: z.array( z.object({ links: z.array( z.object({ linkConnection: linkConnectionRefs }) ), }) ), }) ), system: z.object({ content_type_uid: z.string(), uid: z.string(), }), }) ) .length(1), }), })