Feature/wrap logging * feat: change all logging to go through our own logger function so that we can control log levels * move packages/trpc to using our own logger * merge Approved-by: Linus Flood
863 lines
20 KiB
TypeScript
863 lines
20 KiB
TypeScript
import { z, ZodError, ZodIssueCode } from "zod"
|
|
|
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
|
import { logger } from "@scandic-hotels/common/logger"
|
|
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
|
|
|
|
import { AlertTypeEnum } from "../../../types/alertType"
|
|
import { discriminatedUnion } from "../../../utils/discriminatedUnion"
|
|
import {
|
|
cardBlockRefsSchema,
|
|
cardBlockSchema,
|
|
transformCardBlock,
|
|
transformCardBlockRefs,
|
|
} from "../schemas/blocks/cardsGrid"
|
|
import {
|
|
linkRefsUnionSchema,
|
|
linkUnionSchema,
|
|
transformPageLink,
|
|
transformPageLinkRef,
|
|
} from "../schemas/pageLinks"
|
|
import { systemSchema } from "../schemas/system"
|
|
|
|
import type { 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(),
|
|
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(),
|
|
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<typeof validateContactConfigSchema>
|
|
|
|
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().optional().default(""),
|
|
url: z.string().optional().default(""),
|
|
}),
|
|
})
|
|
),
|
|
}),
|
|
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(),
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
.transform((data) => {
|
|
if (!data.all_current_header.items.length) {
|
|
return {
|
|
header: null,
|
|
}
|
|
}
|
|
const header = data.all_current_header.items[0]
|
|
return {
|
|
header: {
|
|
frontpageLinkText: header.frontpage_link_text,
|
|
logo: header.logoConnection.edges[0].node,
|
|
menu: header.menu,
|
|
topMenu: header.top_menu,
|
|
},
|
|
}
|
|
})
|
|
|
|
export interface GetCurrentHeaderData
|
|
extends z.input<typeof validateCurrentHeaderConfigSchema> {}
|
|
export type HeaderData = z.output<typeof validateCurrentHeaderConfigSchema>
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const validateCurrentHeaderRefConfigSchema = z.object({
|
|
all_current_header: z.object({
|
|
items: z.array(
|
|
z.object({
|
|
system: systemSchema,
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
|
|
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<typeof validateNavigationItem>
|
|
|
|
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
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const validateCurrentFooterRefConfigSchema = z.object({
|
|
all_current_footer: z.object({
|
|
items: z.array(
|
|
z.object({
|
|
system: systemSchema,
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
|
|
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.pageConnection?.url,
|
|
}
|
|
})
|
|
|
|
const validateLinks = z
|
|
.array(validateLinkItem)
|
|
.transform((data) => data.filter((item) => item.url))
|
|
|
|
export const validateSecondaryLinks = z.array(
|
|
z.object({
|
|
title: z.string(),
|
|
links: validateLinks,
|
|
})
|
|
)
|
|
|
|
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: validateLinks.nullish().transform((val) => val ?? []),
|
|
app_downloads: z.object({
|
|
title: z.string(),
|
|
links: validateLinksWithType
|
|
.nullish()
|
|
.transform((val) => val ?? []),
|
|
}),
|
|
secondary_links: validateSecondaryLinks
|
|
.nullish()
|
|
.transform((val) => val ?? []),
|
|
social_media: z.object({
|
|
links: validateLinksWithType
|
|
.nullish()
|
|
.transform((val) => val ?? []),
|
|
}),
|
|
tertiary_links: validateLinks.nullish().transform((val) => val ?? []),
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
.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: systemSchema,
|
|
}),
|
|
})
|
|
)
|
|
.max(1),
|
|
})
|
|
|
|
export const validateFooterRefConfigSchema = z.object({
|
|
all_footer: z.object({
|
|
items: z
|
|
.array(
|
|
z.object({
|
|
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,
|
|
})
|
|
)
|
|
.length(1),
|
|
}),
|
|
})
|
|
|
|
/**
|
|
* New Header Validation
|
|
*/
|
|
|
|
const linkRefsSchema = z
|
|
.object({
|
|
linkConnection: z.object({
|
|
edges: z.array(
|
|
z.object({
|
|
node: linkRefsUnionSchema,
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
.transform((data) => {
|
|
if (data.linkConnection.edges.length) {
|
|
const link = transformPageLinkRef(data.linkConnection.edges[0].node)
|
|
if (link) {
|
|
return {
|
|
link,
|
|
}
|
|
}
|
|
}
|
|
return { link: null }
|
|
})
|
|
|
|
const menuItemsRefsSchema = z.intersection(
|
|
linkRefsSchema,
|
|
z
|
|
.object({
|
|
cardConnection: z.object({
|
|
edges: z.array(
|
|
z.object({
|
|
node: cardBlockRefsSchema,
|
|
})
|
|
),
|
|
}),
|
|
see_all_link: linkRefsSchema,
|
|
submenu: z.array(
|
|
z.object({
|
|
links: z.array(linkRefsSchema),
|
|
})
|
|
),
|
|
})
|
|
.transform((data) => {
|
|
let card = null
|
|
if (data.cardConnection.edges.length) {
|
|
card = transformCardBlockRefs(data.cardConnection.edges[0].node)
|
|
}
|
|
|
|
return {
|
|
card,
|
|
see_all_link: data.see_all_link,
|
|
submenu: data.submenu,
|
|
}
|
|
})
|
|
)
|
|
|
|
const topLinkRefsSchema = z.object({
|
|
logged_in: linkRefsSchema.nullable(),
|
|
logged_out: linkRefsSchema.nullable(),
|
|
})
|
|
|
|
export const headerRefsSchema = z
|
|
.object({
|
|
all_header: z.object({
|
|
items: z
|
|
.array(
|
|
z.object({
|
|
menu_items: z.array(menuItemsRefsSchema),
|
|
system: systemSchema,
|
|
top_link: topLinkRefsSchema,
|
|
})
|
|
)
|
|
.max(1),
|
|
}),
|
|
})
|
|
.transform((data) => {
|
|
if (!data.all_header.items.length) {
|
|
logger.error(`Zod Error - No header returned in refs request`)
|
|
throw new ZodError([
|
|
{
|
|
code: ZodIssueCode.custom,
|
|
fatal: true,
|
|
message: "No header returned (Refs)",
|
|
path: ["all_header.items"],
|
|
},
|
|
])
|
|
}
|
|
|
|
return {
|
|
header: data.all_header.items[0],
|
|
}
|
|
})
|
|
|
|
const linkSchema = z
|
|
.object({
|
|
linkConnection: z.object({
|
|
edges: z.array(
|
|
z.object({
|
|
node: discriminatedUnion(linkUnionSchema.options),
|
|
})
|
|
),
|
|
}),
|
|
})
|
|
.transform((data) => {
|
|
if (data.linkConnection.edges.length) {
|
|
const linkNode = data.linkConnection.edges[0].node
|
|
if (linkNode) {
|
|
const link = transformPageLink(linkNode)
|
|
if (link) {
|
|
return {
|
|
link,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
link: null,
|
|
}
|
|
})
|
|
|
|
const titleSchema = z.object({
|
|
title: z.string().optional().default(""),
|
|
})
|
|
|
|
/**
|
|
* Intersection has to be used since you are not
|
|
* allowed to merge two schemas where one uses
|
|
* transform
|
|
*/
|
|
const linkAndTitleSchema = z.intersection(linkSchema, titleSchema)
|
|
|
|
/**
|
|
* Same as above 👆
|
|
*/
|
|
export const menuItemSchema = z
|
|
.intersection(
|
|
linkAndTitleSchema,
|
|
z
|
|
.object({
|
|
cardConnection: z.object({
|
|
edges: z.array(
|
|
z.object({
|
|
node: cardBlockSchema,
|
|
})
|
|
),
|
|
}),
|
|
see_all_link: linkAndTitleSchema,
|
|
submenu: z.array(
|
|
z.object({
|
|
links: z.array(linkAndTitleSchema),
|
|
title: z.string().optional().default(""),
|
|
})
|
|
),
|
|
})
|
|
.transform((data) => {
|
|
let card = null
|
|
if (data.cardConnection.edges.length) {
|
|
card = transformCardBlock(data.cardConnection.edges[0].node)
|
|
}
|
|
|
|
return {
|
|
card,
|
|
seeAllLink: data.see_all_link,
|
|
submenu: data.submenu,
|
|
}
|
|
})
|
|
)
|
|
.transform((data) => {
|
|
return {
|
|
...data,
|
|
link: data.submenu.length ? null : data.link,
|
|
seeAllLink: data.submenu.length ? data.seeAllLink : null,
|
|
}
|
|
})
|
|
|
|
// TODO When original IconName enum is moved to common we should use it
|
|
enum IconName {
|
|
Gift = "Gift",
|
|
InfoCircle = "InfoCircle",
|
|
PriceTag = "PriceTag",
|
|
}
|
|
|
|
const topLinkItemSchema = z.intersection(
|
|
linkAndTitleSchema,
|
|
z.object({
|
|
icon: z
|
|
.enum(["loyalty", "info", "offer"])
|
|
.nullable()
|
|
.transform((icon) => {
|
|
switch (icon) {
|
|
case "loyalty":
|
|
return IconName.Gift
|
|
case "info":
|
|
return IconName.InfoCircle
|
|
case "offer":
|
|
return IconName.PriceTag
|
|
default:
|
|
return null
|
|
}
|
|
}),
|
|
})
|
|
)
|
|
|
|
export const topLinkSchema = z.object({
|
|
logged_in: topLinkItemSchema.nullable(),
|
|
logged_out: topLinkItemSchema.nullable(),
|
|
})
|
|
|
|
export const headerSchema = z
|
|
.object({
|
|
all_header: z.object({
|
|
items: z
|
|
.array(
|
|
z.object({
|
|
menu_items: z.array(menuItemSchema),
|
|
top_link: topLinkSchema,
|
|
})
|
|
)
|
|
.max(1),
|
|
}),
|
|
})
|
|
.transform((data) => {
|
|
if (!data.all_header.items.length) {
|
|
logger.error(`Zod Error - No header returned in request`)
|
|
throw new ZodError([
|
|
{
|
|
code: ZodIssueCode.custom,
|
|
fatal: true,
|
|
message: "No header returned",
|
|
path: ["all_header.items"],
|
|
},
|
|
])
|
|
}
|
|
|
|
const header = data.all_header.items[0]
|
|
return {
|
|
header: {
|
|
menuItems: header.menu_items,
|
|
topLink: header.top_link,
|
|
},
|
|
}
|
|
})
|
|
|
|
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: linkUnionSchema.transform((data) => {
|
|
const link = transformPageLink(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: linkRefsUnionSchema,
|
|
})
|
|
),
|
|
}),
|
|
}),
|
|
})
|
|
|
|
const alertConnectionRefSchema = z.object({
|
|
edges: z.array(
|
|
z.object({
|
|
node: z.object({
|
|
link: linkRefsSchema,
|
|
sidepeek_content: sidepeekContentRefSchema,
|
|
system: systemSchema,
|
|
}),
|
|
})
|
|
),
|
|
})
|
|
|
|
export const siteConfigRefSchema = z.object({
|
|
all_site_config: z.object({
|
|
items: z.array(
|
|
z.object({
|
|
sitewide_alert: z.object({
|
|
alertConnection: alertConnectionRefSchema,
|
|
}),
|
|
system: systemSchema,
|
|
})
|
|
),
|
|
}),
|
|
})
|