feat(SW-2541): Adjust for ImageVault custom field return types changes

Approved-by: Bianca Widstam
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-09-10 08:57:49 +00:00
parent ddd4b7c531
commit 15711cb3a4
47 changed files with 388 additions and 458 deletions

View File

@@ -12,7 +12,7 @@ import ContentCard from "@/components/ContentCard"
import styles from "./allCampaigns.module.css"
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
interface AllCampaignsProps {
heading: string

View File

@@ -6,7 +6,7 @@ import Subtitle from "@scandic-hotels/design-system/Subtitle"
import styles from "./contentCard.module.css"
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
interface ContentCardProps {
link?: {

View File

@@ -11,7 +11,7 @@ import { mapImageVaultImagesToGalleryImages } from "@/utils/imageGallery"
import styles from "./topImages.module.css"
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
interface TopImageProps {
images: ImageVaultAsset[]

View File

@@ -1,3 +1,9 @@
import {
type DeprecatedImageVaultAssetResponse,
type ImageVaultAssetResponse,
mapImageVaultAssetResponseToImageVaultAsset,
mapInsertResponseToImageVaultAsset,
} from "@scandic-hotels/common/utils/imageVault"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import Body from "@scandic-hotels/design-system/Body"
import Caption from "@scandic-hotels/design-system/Caption"
@@ -14,18 +20,15 @@ import {
RTEItemTypeEnum,
RTETypeEnum,
} from "@scandic-hotels/trpc/types/RTEenums"
import { insertResponseToImageVaultAsset } from "@scandic-hotels/trpc/utils/imageVault"
import BiroScript from "../TempDesignSystem/Text/BiroScript"
import { hasAvailableParagraphFormat, hasAvailableULFormat } from "./utils"
import styles from "./jsontohtml.module.css"
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { EmbedByUid } from "@/types/components/deprecatedjsontohtml"
import { EmbedEnum } from "@/types/requests/utils/embeds"
import type { Attributes, RTEImageVaultAttrs } from "@/types/rte/attrs"
import type { Attributes } from "@/types/rte/attrs"
import {
type RTEDefaultNode,
type RTEImageNode,
@@ -309,30 +312,13 @@ export const renderOptions: RenderOptions = {
if (entry?.node.__typename === EmbedEnum.ImageContainer) {
if (entry.node.image_left && entry.node.image_right) {
if ("dimensions" in entry.node.image_left) {
// Only temp until all ImageVaultAssets are
// handled in schema.transform()
return (
<ImageContainer
leftImage={
entry.node.image_left as unknown as ImageVaultAsset
}
rightImage={
entry.node.image_right as unknown as ImageVaultAsset
}
leftImage={entry.node.image_left}
rightImage={entry.node.image_right}
/>
)
}
const leftImage = insertResponseToImageVaultAsset(
entry.node.image_left
)
const rightImage = insertResponseToImageVaultAsset(
entry.node.image_right
)
return (
<ImageContainer leftImage={leftImage} rightImage={rightImage} />
)
}
return null
} else if (
entry?.node.__typename === EmbedEnum.LoyaltyPage ||
@@ -372,30 +358,38 @@ export const renderOptions: RenderOptions = {
},
[RTETypeEnum.ImageVault]: (node: RTEImageNode) => {
if ("attrs" in node) {
const type = node.type
if (type === RTETypeEnum.ImageVault) {
const attrs = node.attrs as RTEImageVaultAttrs
if (!("attrs" in node) || type !== RTETypeEnum.ImageVault) {
return null
}
let image = undefined
if ("dimensions" in attrs) {
image = attrs
} else {
image = insertResponseToImageVaultAsset(attrs)
if ("imageVaultId" in node.attrs && "fileName" in node.attrs) {
image = mapImageVaultAssetResponseToImageVaultAsset(
node.attrs as unknown as ImageVaultAssetResponse
)
}
if ("Name" in node.attrs && "Id" in node.attrs) {
image = mapInsertResponseToImageVaultAsset(
node.attrs as unknown as DeprecatedImageVaultAssetResponse
)
}
if (!image) {
return null
}
const alt = image.meta.alt ?? image.title
const props = extractPossibleAttributes(node.attrs)
const width = attrs.width
? parseInt(attrs.width.replaceAll("px", ""))
: image.dimensions.width
const props = extractPossibleAttributes(attrs)
return (
<section key={node.uid}>
<Image
alt={alt}
className={styles.image}
height={365}
fill
sizes="(min-width: 1367px) 800px, (max-width: 1366px) and (min-width: 1200px) 1200px, 100vw"
src={image.url}
width={width}
focalPoint={image.focalPoint}
dimensions={image.dimensions}
{...props}
@@ -403,8 +397,6 @@ export const renderOptions: RenderOptions = {
<Caption>{image.meta.caption}</Caption>
</section>
)
}
}
return null
},

View File

@@ -1,4 +1,5 @@
import type { FocalPoint, Image } from "@scandic-hotels/trpc/types/image"
import type { FocalPoint } from "@scandic-hotels/common/utils/imageVault"
import type { Image } from "@scandic-hotels/trpc/types/image"
export interface HeroProps {
alt: string

View File

@@ -1,4 +1,4 @@
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { VariantProps } from "class-variance-authority"
import type { ApiImage } from "@/types/components/image"

View File

@@ -1,4 +1,4 @@
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { VariantProps } from "class-variance-authority"
import type { loyaltyCardVariants } from "./variants"

View File

@@ -1,4 +1,4 @@
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { VariantProps } from "class-variance-authority"
import type { CardProps } from "@/components/TempDesignSystem/Card/card"

View File

@@ -1,4 +1,4 @@
import type { FocalPoint } from "@scandic-hotels/trpc/types/image"
import type { FocalPoint } from "@scandic-hotels/common/utils/imageVault"
interface Dimensions {
width: number

View File

@@ -1,5 +1,5 @@
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { TeaserCard } from "@scandic-hotels/trpc/types/blocks"
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { VariantProps } from "class-variance-authority"
import type { CardProps } from "@/components/TempDesignSystem/Card/card"

View File

@@ -1,9 +1,8 @@
import { EmbedEnum } from "./utils/embeds"
import { PageLink, PageLinkWithOriginalUrl } from "./utils/pageLink"
import { TypenameInterface } from "./utils/typename"
import type { ImageContainer } from "./imageContainer"
import type { SysAsset } from "./utils/asset"
import type { EmbedEnum } from "./utils/embeds"
import type { PageLink, PageLinkWithOriginalUrl } from "./utils/pageLink"
import type { TypenameInterface } from "./utils/typename"
interface AccountPage
extends TypenameInterface<EmbedEnum.AccountPage>,

View File

@@ -1,5 +1,5 @@
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { System } from "@scandic-hotels/trpc/routers/contentstack/schemas/system"
import type { ImageVaultAssetResponse } from "@scandic-hotels/trpc/types/imageVault"
import type { EmbedEnum } from "./utils/embeds"
import type { TypenameInterface } from "./utils/typename"
@@ -7,6 +7,6 @@ import type { TypenameInterface } from "./utils/typename"
export interface ImageContainer
extends TypenameInterface<EmbedEnum.ImageContainer>,
System {
image_left?: ImageVaultAssetResponse
image_right?: ImageVaultAssetResponse
image_left?: ImageVaultAsset
image_right?: ImageVaultAsset
}

View File

@@ -2,7 +2,7 @@ import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
ImageVaultAsset,
ImageVaultAssetResponse,
} from "@scandic-hotels/trpc/types/imageVault"
} from "@scandic-hotels/common/utils/imageVault"
import type {
EmbedTypesEnum,
RTEItemType,

View File

@@ -1,5 +1,5 @@
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { EmbedTypesEnum, RTEItemType, RTEItemTypeEnum } from "./enums"

View File

@@ -1,5 +1,5 @@
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { ApiImage } from "@scandic-hotels/trpc/types/hotel"
import type { ImageVaultAsset } from "@scandic-hotels/trpc/types/imageVault"
import type { GalleryImage } from "@/types/components/imageGallery"

View File

@@ -45,6 +45,7 @@
"./utils/chunk": "./utils/chunk.ts",
"./utils/dateFormatting": "./utils/dateFormatting.ts",
"./utils/debounce": "./utils/debounce.ts",
"./utils/imageVault": "./utils/imageVault.ts",
"./utils/isDefined": "./utils/isDefined.ts",
"./utils/isEdge": "./utils/isEdge.ts",
"./utils/isValidJson": "./utils/isValidJson.ts",

View File

@@ -0,0 +1,212 @@
import { z } from "zod"
const focalPointSchema = z.object({
x: z.number(),
y: z.number(),
})
const deprecatedMetaDataSchema = z.object({
DefinitionType: z.number().nullish(),
Description: z.string().nullable(),
LanguageId: z.number().nullable(),
MetadataDefinitionId: z.number(),
Name: z.string(),
Value: z.string().nullable(),
})
/**
* Defines a media asset, original or conversion
*/
const deprecatedMediaConversionSchema = z.object({
/**
* Aspect ratio of the conversion
*/
AspectRatio: z.number(),
/**
* Content type of the conversion
*/
ContentType: z.string(),
/**
* Aspect ratio of the selected/requested format
*/
FormatAspectRatio: z.number(),
/**
* Height of the selected/requested format
*/
FormatHeight: z.number(),
/**
* Width of the selected/requested format
*/
FormatWidth: z.number(),
/**
* Height, in pixels, of the conversion
*/
Height: z.number(),
/**
* Html representing the conversion
*/
Html: z.string(),
/**
* Id of the selected media format
*/
MediaFormatId: z.number(),
/**
* Name of the media format
*/
MediaFormatName: z.string(),
/**
* Name of the conversion
*/
Name: z.string(),
/**
* The url to the conversion
*/
Url: z.string(),
/**
* Width, in pixels, of the conversion
*/
Width: z.number(),
})
/**
* The response from ImageVault when inserting an asset
*/
export const deprecatedImageVaultAssetSchema = z.object({
/**
* The media item id of the asset
*/
Id: z.number(),
/**
* The id of the vault where the asset resides
*/
VaultId: z.number(),
/**
* The name of the asset
*/
Name: z.string(),
/**
* The conversion selected by the user. Is an array but will only contain one object
*/
MediaConversions: z.array(deprecatedMediaConversionSchema),
Metadata: z.array(deprecatedMetaDataSchema),
/**
* Date when the asset was added to ImageVault
*/
DateAdded: z.string(),
/**
* Name of the user that added the asset to ImageVault
*/
AddedBy: z.string(),
FocalPoint: focalPointSchema.optional(),
})
export const imageVaultAssetSchema = z.object({
imageVaultId: z.number(),
fileName: z.string(),
url: z.string(),
dimensions: z.object({
width: z.number(),
height: z.number(),
aspectRatio: z.number(),
}),
focalPoint: focalPointSchema,
meta: z.object({ alt: z.string(), caption: z.string() }),
})
export const transformedImageVaultAssetSchema = imageVaultAssetSchema
.or(deprecatedImageVaultAssetSchema)
.nullish()
.or(
// Temp since there is a bug in Contentstack
// sending empty objects when there has been an
// image selected previously but has since been
// deleted
z.object({})
)
.transform((data) => {
if (!data || Object.keys(data).length === 0) {
return undefined
}
if ("imageVaultId" in data && "fileName" in data) {
return mapImageVaultAssetResponseToImageVaultAsset(data)
}
if ("Name" in data && "Id" in data) {
return mapInsertResponseToImageVaultAsset(data)
}
})
export type FocalPoint = z.infer<typeof focalPointSchema>
export type DeprecatedImageVaultAssetResponse = z.infer<
typeof deprecatedImageVaultAssetSchema
>
export type ImageVaultAssetResponse = z.infer<typeof imageVaultAssetSchema>
export type ImageVaultAsset = {
id: number
title: string
url: string
dimensions: {
width: number
height: number
aspectRatio: number
}
meta: { alt: string | undefined | null; caption: string | undefined | null }
focalPoint: FocalPoint
}
export function mapInsertResponseToImageVaultAsset(
response: DeprecatedImageVaultAssetResponse
): ImageVaultAsset {
const alt = response.Metadata?.find((meta) =>
meta.Name.includes("AltText_")
)?.Value
const caption = response.Metadata?.find((meta) =>
meta.Name.includes("Title_")
)?.Value
const mediaConversion = response.MediaConversions[0]
const aspectRatio =
mediaConversion.FormatAspectRatio ||
mediaConversion.AspectRatio ||
mediaConversion.Width / mediaConversion.Height
return {
id: response.Id,
title: response.Name,
url: mediaConversion.Url,
dimensions: {
width: mediaConversion.Width,
height: mediaConversion.Height,
aspectRatio,
},
meta: {
alt,
caption,
},
focalPoint: response.FocalPoint || { x: 50, y: 50 },
}
}
export function mapImageVaultAssetResponseToImageVaultAsset(
response: ImageVaultAssetResponse
): ImageVaultAsset {
return {
id: response.imageVaultId,
title: response.fileName,
url: response.url,
dimensions: {
width: response.dimensions.width,
height: response.dimensions.height,
aspectRatio: response.dimensions.aspectRatio,
},
meta: {
alt: response.meta.alt,
caption: response.meta.caption,
},
focalPoint: response.focalPoint,
}
}

View File

@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { JsonToHtml } from './JsonToHtml'
import { RTEImageVaultNode, RTENode } from './types/rte/node'
import { RTETypeEnum } from './types/rte/enums'
import { expect } from 'storybook/test'
import { JsonToHtml } from './JsonToHtml'
import { RTETypeEnum } from './types/rte/enums'
import { RTEImageVaultNode, RTENode } from './types/rte/node'
const meta: Meta<typeof JsonToHtml> = {
title: 'Components/JsonToHtml',
@@ -192,7 +192,8 @@ const image: RTEImageVaultNode = {
url: 'https://imagevault.scandichotels.com/publishedmedia/77obkq3g4harjm8wyua9/Scandic_Family_Breakfast_2.jpg',
height: '200px',
width: '200px',
id: 1,
fileName: 'Scandic_Family_Breakfast_2.jpg',
imageVaultId: 1,
title: 'Image Title',
dimensions: { width: 200, height: 200, aspectRatio: 1.5 },
meta: { alt: 'Image Alt', caption: 'Image Caption' },

View File

@@ -4,31 +4,15 @@ import { nodesToHtml } from './utils'
import styles from './jsontohtml.module.css'
import { ImageVaultAsset } from '@scandic-hotels/common/utils/imageVault'
import { ContentBlockType } from './types/rte/enums'
import type { RTENode } from './types/rte/node'
import type { RenderOptions } from './types/rte/option'
import { ContentBlockType } from './types/rte/enums'
export type Node<T> = {
node: T
}
type Image = {
id: number
title: string
url: string
focalPoint: {
x: number
y: number
}
dimensions?: {
width: number
height: number
}
meta: {
alt?: string | null
caption?: string | null
}
}
export type Embeds =
| {
__typename: Exclude<ContentBlockType, 'ImageContainer'>
@@ -38,11 +22,11 @@ export type Embeds =
}
| {
__typename: 'ImageContainer'
system?: { uid: string } | undefined | null
url?: string | undefined | null
title?: string | undefined | null
image_left?: Image | undefined
image_right?: Image | undefined
system?: { uid: string } | null
url?: string | null
title?: string | null
image_left?: ImageVaultAsset
image_right?: ImageVaultAsset
}
export type EmbedByUid = Record<string, Node<Embeds>>

View File

@@ -1,60 +0,0 @@
export type ImageVaultAsset = {
id: number
title: string
url: string
dimensions: {
width: number
height: number
aspectRatio: number
}
meta: { alt: string | undefined | null; caption: string | undefined | null }
focalPoint: { x: number; y: number }
}
type ImageVaultAssetResponse = {
Id: number
Name: string
FocalPoint: { x: number; y: number }
Metadata: Array<{ Name: string; Value: string }>
MediaConversions: Array<{
Url: string
Width: number
Height: number
AspectRatio: number
FormatAspectRatio: number
}>
}
export function insertResponseToImageVaultAsset(
response: ImageVaultAssetResponse
): ImageVaultAsset {
const alt = response.Metadata?.find((meta) =>
meta.Name.includes('AltText_')
)?.Value
const caption = response.Metadata?.find((meta) =>
meta.Name.includes('Title_')
)?.Value
const mediaConversion = response.MediaConversions[0]
const aspectRatio =
mediaConversion.FormatAspectRatio ||
mediaConversion.AspectRatio ||
mediaConversion.Width / mediaConversion.Height
return {
url: mediaConversion.Url,
id: response.Id,
meta: {
alt,
caption,
},
title: response.Name,
dimensions: {
width: mediaConversion.Width,
height: mediaConversion.Height,
aspectRatio,
},
focalPoint: response.FocalPoint || { x: 50, y: 50 },
}
}

View File

@@ -15,9 +15,14 @@ import {
import styles from './jsontohtml.module.css'
import { insertResponseToImageVaultAsset } from './insertResponseToImageVaultAsset'
import {
DeprecatedImageVaultAssetResponse,
ImageVaultAssetResponse,
mapImageVaultAssetResponseToImageVaultAsset,
mapInsertResponseToImageVaultAsset,
} from '@scandic-hotels/common/utils/imageVault'
import type { EmbedByUid } from './JsonToHtml'
import type { Attributes, RTEImageVaultAttrs } from './types/rte/attrs'
import type { Attributes } from './types/rte/attrs'
import {
AvailableParagraphFormatEnum,
RTEItemTypeEnum,
@@ -478,13 +483,26 @@ export const renderOptions: RenderOptions = {
return null
}
const attrs = node.attrs as RTEImageVaultAttrs
const image =
'dimensions' in attrs ? attrs : insertResponseToImageVaultAsset(attrs)
let image = undefined
if ('imageVaultId' in node.attrs && 'fileName' in node.attrs) {
image = mapImageVaultAssetResponseToImageVaultAsset(
node.attrs as unknown as ImageVaultAssetResponse
)
}
if ('Name' in node.attrs && 'Id' in node.attrs) {
image = mapInsertResponseToImageVaultAsset(
node.attrs as unknown as DeprecatedImageVaultAssetResponse
)
}
if (!image) {
return null
}
const alt = image.meta.alt ?? image.title
const caption = image.meta.caption
const { className, ...props } = extractPossibleAttributes(attrs)
const { className, ...props } = extractPossibleAttributes(node.attrs)
return (
<div key={node.uid} className={styles.imageWithCaption}>
<div className={styles.imageWrapper}>

View File

@@ -1,11 +1,14 @@
import type { JSX } from 'react'
import {
DeprecatedImageVaultAssetResponse,
ImageVaultAssetResponse,
} from '@scandic-hotels/common/utils/imageVault'
import type { EmbedByUid } from '../../JsonToHtml'
import type {
Attributes,
RTEAnchorAttrs,
RTEAssetAttrs,
RTEImageVaultAttrs,
RTELinkAttrs,
} from './attrs'
import type { RTETypeEnum } from './enums'
@@ -39,7 +42,8 @@ export interface RTEReferenceLinkNode extends RTEDefaultNode {
}
export interface RTEImageVaultNode extends RTEDefaultNode {
attrs: RTEImageVaultAttrs
attrs: Attributes &
(DeprecatedImageVaultAssetResponse | ImageVaultAssetResponse)
type: RTETypeEnum.ImageVault
}

View File

@@ -60,6 +60,7 @@ fragment Content_ContentPageRefs on ContentPageBlocksContent {
edges {
node {
__typename
...ImageContainerRef
...AccountPageRef
...CampaignOverviewPageRef
...CampaignPageRef

View File

@@ -1,5 +1,7 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { AccountPageEnum } from "../../../types/accountPageEnum"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
@@ -15,7 +17,6 @@ import {
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import { textContentSchema } from "../schemas/blocks/textContent"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { systemSchema } from "../schemas/system"
const accountPageAccordions = z
@@ -54,7 +55,7 @@ export const accountPageSchema = z.object({
content: discriminatedUnionArray(blocksSchema.options),
heading: z.string().nullable(),
preamble: z.string().nullable(),
hero_image: tempImageVaultAssetSchema,
hero_image: transformedImageVaultAssetSchema,
hero_image_active: z
.boolean()
.nullable()

View File

@@ -1,5 +1,6 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { CampaignPageEnum } from "../../../types/campaignPage"
@@ -14,7 +15,6 @@ import {
} from "../schemas/blocks/carouselCards"
import { essentialsBlockSchema } from "../schemas/blocks/essentials"
import { campaignPageHotelListingSchema } from "../schemas/blocks/hotelListing"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import {
linkConnectionRefs,
linkConnectionSchema,
@@ -22,7 +22,7 @@ import {
import { systemSchema } from "../schemas/system"
import { getCarouselCardsBlockWithBookingCodeLinks } from "./utils"
import type { ImageVaultAsset } from "../../../types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
const campaignPageEssentials = z
.object({
@@ -56,7 +56,7 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
])
export const heroSchema = z.object({
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
heading: z.string(),
theme: z.enum(["Peach", "Burgundy"]).default("Peach"),
benefits: z
@@ -206,7 +206,7 @@ export const campaignPagesByHotelUidSchema = z
sort_order: z.number().nullish(),
card_content: z
.object({
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
heading: z.string().nullish(),
text: z.string().nullish(),
})

View File

@@ -1,5 +1,7 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { CollectionPageEnum } from "../../../types/collectionPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
@@ -15,7 +17,6 @@ import {
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import { uspGridRefsSchema, uspGridSchema } from "../schemas/blocks/uspGrid"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import {
linkAndTitleSchema,
linkConnectionRefs,
@@ -87,7 +88,7 @@ const topPrimaryButtonSchema = linkAndTitleSchema
// Content Page Schema and types
export const collectionPageSchema = z.object({
collection_page: z.object({
hero_image: tempImageVaultAssetSchema,
hero_image: transformedImageVaultAssetSchema,
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
title: z.string(),
header: z.object({

View File

@@ -1,5 +1,7 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { ContentPageEnum } from "../../../types/contentPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
@@ -30,7 +32,6 @@ import {
dynamicContentRefsSchema as headerDynamicContentRefsSchema,
dynamicContentSchema as headerDynamicContentSchema,
} from "../schemas/headers/dynamicContent"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import {
linkAndTitleSchema,
linkConnectionRefs,
@@ -203,7 +204,7 @@ const topPrimaryButtonSchema = linkAndTitleSchema
// Content Page Schema and types
export const contentPageSchema = z.object({
content_page: z.object({
hero_image: tempImageVaultAssetSchema,
hero_image: transformedImageVaultAssetSchema,
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
sidebar: discriminatedUnionArray(sidebarSchema.options).nullable(),
title: z.string(),

View File

@@ -1,5 +1,6 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { DestinationCityPageEnum } from "../../../types/destinationCityPage"
@@ -10,7 +11,6 @@ import {
accordionSchema,
} from "../schemas/blocks/accordion"
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { mapLocationSchema } from "../schemas/mapLocation"
import {
linkRefsUnionSchema,
@@ -19,7 +19,7 @@ import {
} from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
import type { ImageVaultAsset } from "../../../types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
export const destinationCityPageDestinationSettingsSchema = z
.object({
@@ -73,7 +73,7 @@ export const destinationCityListDataSchema = z
)
.nullish(),
images: z
.array(z.object({ image: tempImageVaultAssetSchema }))
.array(z.object({ image: transformedImageVaultAssetSchema }))
.transform((images) =>
images
.map((image) => image.image)
@@ -128,7 +128,7 @@ export const destinationCityPageSchema = z.object({
.nullish()
.transform((experiences) => experiences?.destination_experiences ?? []),
images: z
.array(z.object({ image: tempImageVaultAssetSchema }))
.array(z.object({ image: transformedImageVaultAssetSchema }))
.transform((images) =>
images
.map((image) => image.image)

View File

@@ -1,5 +1,6 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { Country } from "../../../types/country"
@@ -10,7 +11,6 @@ import {
accordionSchema,
} from "../schemas/blocks/accordion"
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { mapLocationSchema } from "../schemas/mapLocation"
import {
linkRefsUnionSchema,
@@ -19,7 +19,7 @@ import {
} from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
import type { ImageVaultAsset } from "../../../types/imageVault"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
export const destinationCountryPageContent = z
.object({
@@ -57,7 +57,7 @@ export const destinationCountryPageSchema = z.object({
})
.transform(({ destination_experiences }) => destination_experiences),
images: z
.array(z.object({ image: tempImageVaultAssetSchema }))
.array(z.object({ image: transformedImageVaultAssetSchema }))
.transform((images) =>
images
.map((image) => image.image)

View File

@@ -1,5 +1,7 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { LoyaltyPageEnum } from "../../../enums/loyaltyPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
@@ -18,7 +20,6 @@ import {
shortcutsRefsSchema,
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import {
contentRefsSchema as sidebarContentRefsSchema,
contentSchema as sidebarContentSchema,
@@ -166,7 +167,7 @@ export const loyaltyPageSchema = z.object({
.object({
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
heading: z.string().optional(),
hero_image: tempImageVaultAssetSchema,
hero_image: transformedImageVaultAssetSchema,
preamble: z.string().optional(),
sidebar: discriminatedUnionArray(sidebarSchema.options).nullable(),
title: z.string().optional(),

View File

@@ -2,18 +2,18 @@ import { z } from "zod"
import { findMyBooking } from "@scandic-hotels/common/constants/routes/findMyBooking"
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { attributesSchema as hotelAttributesSchema } from "../../../routers/hotels/schemas/hotel"
import { Country } from "../../../types/country"
import { RTETypeEnum } from "../../../types/RTEenums"
import { additionalDataAttributesSchema } from "../../hotels/schemas/hotel/include/additionalData"
import { imageSchema } from "../../hotels/schemas/image"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { systemSchema } from "../schemas/system"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
import type { ImageVaultAsset } from "../../../types/imageVault"
import type { LanguageSwitcherData } from "../../../types/languageSwitcher"
const metaDataJsonSchema = z.object({
@@ -56,7 +56,7 @@ export const rawMetadataSchema = z.object({
title: z.string().nullish(),
description: z.string().nullish(),
noindex: z.boolean().nullish(),
seo_image: tempImageVaultAssetSchema.nullable(),
seo_image: transformedImageVaultAssetSchema,
})
.nullish(),
breadcrumbs: z
@@ -97,12 +97,12 @@ export const rawMetadataSchema = z.object({
.object({
heading: z.string().nullish(),
preamble: z.string().nullish(),
hero_image: tempImageVaultAssetSchema.nullable(),
hero_image: transformedImageVaultAssetSchema,
})
.nullish(),
hero_image: tempImageVaultAssetSchema.nullable(),
hero_image: transformedImageVaultAssetSchema,
images: z
.array(z.object({ image: tempImageVaultAssetSchema }).nullish())
.array(z.object({ image: transformedImageVaultAssetSchema }).nullish())
.transform((images) =>
images
.map((image) => image?.image)

View File

@@ -1,9 +1,9 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { HotelPageEnum } from "../../../../types/hotelPageEnum"
import { tempImageVaultAssetSchema } from "../imageVault"
import {
collectionPageRefSchema,
collectionPageSchema,
@@ -18,7 +18,7 @@ export const activitiesCardSchema = z.object({
.default(HotelPageEnum.ContentStack.blocks.ActivitiesCard),
upcoming_activities_card: z
.object({
background_image: tempImageVaultAssetSchema,
background_image: transformedImageVaultAssetSchema,
body_text: z.string(),
cta_text: z.string(),
sidepeek_cta_text: z.string(),

View File

@@ -1,13 +1,13 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { tempImageVaultAssetSchema } from "../imageVault"
import { campaignPageRefSchema } from "../pageLinks"
export const campaignPageCardSchema = z.object({
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
heading: z.string(),
text: z.string(),
})
@@ -29,7 +29,7 @@ export const allCampaignsSchema = z.object({
url: z.string(),
card_content: z
.object({
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
heading: z.string().nullish(),
text: z.string().nullish(),
})

View File

@@ -1,7 +1,8 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { CardsEnum } from "../../../../../types/cardsEnum"
import { tempImageVaultAssetSchema } from "../../imageVault"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
@@ -10,7 +11,7 @@ export const contentCardSchema = z.object({
__typename: z.literal(CardsEnum.ContentCard),
title: z.string(),
heading: z.string(),
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
body_text: z.string(),
promo_text: z.string().optional(),
has_card_link: z.boolean(),

View File

@@ -1,7 +1,8 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { CardsEnum } from "../../../../../types/cardsEnum"
import { tempImageVaultAssetSchema } from "../../imageVault"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
@@ -19,7 +20,7 @@ export const infoCardBlockSchema = z.object({
scripted_top_title: z.string().optional(),
heading: z.string().optional().default(""),
body_text: z.string().optional().default(""),
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
theme: z.enum(INFO_CARD_THEMES).nullable(),
title: z.string().optional(),
primary_button: buttonSchema.optional().nullable(),

View File

@@ -1,7 +1,8 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { CardsEnum } from "../../../../../types/cardsEnum"
import { tempImageVaultAssetSchema } from "../../imageVault"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
@@ -11,7 +12,7 @@ export const loyaltyCardBlockSchema = z.object({
body_text: z.string().optional(),
heading: z.string().optional().default(""),
// JSON - ImageVault Image
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
link: buttonSchema,
system: systemSchema,
title: z.string().optional(),

View File

@@ -1,7 +1,8 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { CardsEnum } from "../../../../../types/cardsEnum"
import { tempImageVaultAssetSchema } from "../../imageVault"
import {
accountPageSchema,
campaignOverviewPageSchema,
@@ -26,7 +27,7 @@ export const teaserCardBlockSchema = z.object({
__typename: z.literal(CardsEnum.TeaserCard),
heading: z.string().default(""),
body_text: z.string().default(""),
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
primary_button: buttonSchema,
secondary_button: buttonSchema,
has_primary_button: z.boolean().default(false),

View File

@@ -1,12 +1,13 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { scriptedCardThemeEnum } from "../../../../enums/scriptedCard"
import { BlocksEnums } from "../../../../types/blocksEnum"
import {
CardsGridEnum,
CardsGridLayoutEnum,
} from "../../../../types/cardsGridEnum"
import { tempImageVaultAssetSchema } from "../imageVault"
import { systemSchema } from "../system"
import {
infoCardBlockRefsSchema,
@@ -28,7 +29,7 @@ import { linkConnectionRefsSchema } from "./utils/linkConnection"
export const cardBlockSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.Card),
// JSON - ImageVault Image
background_image: tempImageVaultAssetSchema,
background_image: transformedImageVaultAssetSchema,
body_text: z.string().optional().default(""),
has_primary_button: z.boolean().default(false),
has_secondary_button: z.boolean().default(false),

View File

@@ -1,8 +1,9 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import * as pageLinks from "../../../../routers/contentstack/schemas/pageLinks"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { tempImageVaultAssetSchema } from "../imageVault"
import { systemSchema } from "../system"
import { buttonSchema } from "./utils/buttonLinkSchema"
@@ -13,7 +14,7 @@ export const fullWidthCampaignSchema = z.object({
edges: z.array(
z.object({
node: z.object({
background_image: tempImageVaultAssetSchema,
background_image: transformedImageVaultAssetSchema,
heading: z.string().optional(),
body_text: z.string().optional(),
scripted_top_title: z.string().optional(),

View File

@@ -1,15 +1,14 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { ContentEnum } from "../../../../types/content"
import { tempImageVaultAssetSchema } from "../imageVault"
import { systemSchema } from "../system"
export const imageContainerSchema = z.object({
__typename: z.literal(ContentEnum.blocks.ImageContainer),
// JSON - ImageVault Image
image_left: tempImageVaultAssetSchema,
// JSON - ImageVault Image
image_right: tempImageVaultAssetSchema,
image_left: transformedImageVaultAssetSchema,
image_right: transformedImageVaultAssetSchema,
system: systemSchema,
title: z.string().optional(),
})

View File

@@ -1,7 +1,8 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { tempImageVaultAssetSchema } from "../imageVault"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "./utils/linkConnection"
@@ -11,7 +12,7 @@ export const joinScandicFriendsSchema = z.object({
scripted_top_title: z.string(),
title: z.string(),
preamble: z.string(),
image: tempImageVaultAssetSchema,
image: transformedImageVaultAssetSchema,
show_usp: z.boolean().default(false),
usp: z.array(z.string()),
has_primary_button: z.boolean().default(false),

View File

@@ -1,163 +0,0 @@
import { z } from "zod"
import { insertResponseToImageVaultAsset } from "../../../utils/imageVault"
import type { ImageVaultAssetResponse } from "../../../types/imageVault"
const metaData = z.object({
DefinitionType: z.number().nullable().optional(),
Description: z.string().nullable(),
LanguageId: z.number().nullable(),
MetadataDefinitionId: z.number(),
Name: z.string(),
Value: z.string().nullable(),
})
export const focalPointSchema = z.object({
x: z.number(),
y: z.number(),
})
/**
* Defines a media asset, original or conversion
*/
const mediaConversion = z.object({
/**
* Aspect ratio of the conversion
*/
AspectRatio: z.number(),
/**
* Content type of the conversion
*/
ContentType: z.string(),
/**
* Aspect ratio of the selected/requested format
*/
FormatAspectRatio: z.number(),
/**
* Height of the selected/requested format
*/
FormatHeight: z.number(),
/**
* Width of the selected/requested format
*/
FormatWidth: z.number(),
/**
* Height, in pixels, of the conversion
*/
Height: z.number(),
/**
* Html representing the conversion
*/
Html: z.string(),
/**
* Id of the selected media format
*/
MediaFormatId: z.number(),
/**
* Name of the media format
*/
MediaFormatName: z.string(),
/**
* Name of the conversion
*/
Name: z.string(),
/**
* The url to the conversion
*/
Url: z.string(),
/**
* Width, in pixels, of the conversion
*/
Width: z.number(),
})
/**
* The response from ImageVault when inserting an asset
*/
export const imageVaultAssetSchema = z.object({
/**
* The media item id of the asset
*/
Id: z.number(),
/**
* The id of the vault where the asset resides
*/
VaultId: z.number(),
/**
* The name of the asset
*/
Name: z.string(),
/**
* The conversion selected by the user. Is an array but will only contain one object
*/
MediaConversions: z.array(mediaConversion),
Metadata: z.array(metaData),
/**
* Date when the asset was added to ImageVault
*/
DateAdded: z.string(),
/**
* Name of the user that added the asset to ImageVault
*/
AddedBy: z.string(),
FocalPoint: focalPointSchema.optional(),
})
export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform(
(rawData) => {
const alt = rawData.Metadata?.find((meta) =>
meta.Name.includes("AltText_")
)?.Value
const caption = rawData.Metadata?.find((meta) =>
meta.Name.includes("Title_")
)?.Value
const mediaConversion = rawData.MediaConversions[0]
const aspectRatio =
mediaConversion.FormatAspectRatio ||
mediaConversion.AspectRatio ||
mediaConversion.Width / mediaConversion.Height
return {
url: mediaConversion.Url,
id: rawData.Id,
meta: {
alt,
caption,
},
title: rawData.Name,
dimensions: {
width: mediaConversion.Width,
height: mediaConversion.Height,
aspectRatio,
},
focalPoint: rawData.FocalPoint || { x: 50, y: 50 },
}
}
)
export const tempImageVaultAssetSchema = imageVaultAssetSchema
.nullable()
.optional()
.or(
// Temp since there is a bug in Contentstack
// sending empty objects when there has been an
// image selected previously but has since been
// deleted
z.object({})
)
.transform((data) => {
if (data) {
if ("Name" in data) {
return makeImageVaultImage(data)
}
}
return undefined
})
function makeImageVaultImage(image: any) {
return image && !!Object.keys(image).length
? insertResponseToImageVaultAsset(image as ImageVaultAssetResponse)
: undefined
}

View File

@@ -1,5 +1,7 @@
import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
cardGridRefsSchema,
@@ -17,7 +19,6 @@ import {
joinScandicFriendsBlockRefsSchema,
joinScandicFriendsBlockSchema,
} from "../schemas/blocks/joinScandicFriends"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { systemSchema } from "../schemas/system"
import { StartPageEnum } from "./utils"
@@ -57,7 +58,7 @@ export const startPageSchema = z.object({
title: z.string(),
header: z.object({
heading: z.string(),
hero_image: tempImageVaultAssetSchema,
hero_image: transformedImageVaultAssetSchema,
}),
blocks: discriminatedUnionArray(blocksSchema.options)
.nullable()

View File

@@ -1,8 +1,3 @@
export interface FocalPoint {
x: number
y: number
}
export interface Image {
description?: string | null
dimension: {

View File

@@ -1,28 +0,0 @@
/**
* @file TypeScript typings for ImageVault
*
* The types in this file are based on the source maps of the downloaded
* distribution at https://clientscript.imagevault.se/Installation/ImageVaultInsertMedia
*
* They have been clean up and amended to.
*/
import type { z } from "zod"
import type { imageVaultAssetSchema } from "../routers/contentstack/schemas/imageVault"
import type { FocalPoint } from "./image"
export type ImageVaultAssetResponse = z.infer<typeof imageVaultAssetSchema>
export type ImageVaultAsset = {
id: number
title: string
url: string
dimensions: {
width: number
height: number
aspectRatio: number
}
meta: { alt: string | undefined | null; caption: string | undefined | null }
focalPoint: FocalPoint
}

View File

@@ -1,38 +0,0 @@
import type {
ImageVaultAsset,
ImageVaultAssetResponse,
} from "../types/imageVault"
export function insertResponseToImageVaultAsset(
response: ImageVaultAssetResponse
): ImageVaultAsset {
const alt = response.Metadata?.find((meta) =>
meta.Name.includes("AltText_")
)?.Value
const caption = response.Metadata?.find((meta) =>
meta.Name.includes("Title_")
)?.Value
const mediaConversion = response.MediaConversions[0]
const aspectRatio =
mediaConversion.FormatAspectRatio ||
mediaConversion.AspectRatio ||
mediaConversion.Width / mediaConversion.Height
return {
url: mediaConversion.Url,
id: response.Id,
meta: {
alt,
caption,
},
title: response.Name,
dimensions: {
width: mediaConversion.Width,
height: mediaConversion.Height,
aspectRatio,
},
focalPoint: response.FocalPoint || { x: 50, y: 50 },
}
}