diff --git a/components/ContentType/LoyaltyPage/loyaltyPage.module.css b/components/ContentType/LoyaltyPage/loyaltyPage.module.css
index 203e19793..b20fa33a6 100644
--- a/components/ContentType/LoyaltyPage/loyaltyPage.module.css
+++ b/components/ContentType/LoyaltyPage/loyaltyPage.module.css
@@ -4,6 +4,7 @@
padding-left: var(--Spacing-x0);
padding-right: var(--Spacing-x0);
position: relative;
+ justify-content: center;
}
.blocks {
diff --git a/components/Current/Aside/Puff/index.tsx b/components/Current/Aside/Puff/index.tsx
index f435c442d..bfb8f0843 100644
--- a/components/Current/Aside/Puff/index.tsx
+++ b/components/Current/Aside/Puff/index.tsx
@@ -3,6 +3,7 @@ import { useRouter } from "next/navigation"
import { Button } from "@scandic-hotels/design-system/current"
+import { renderOptions as currentRenderOptions } from "@/components/Current/currentRenderOptions"
import Image from "@/components/Image"
import JsonToHtml from "@/components/JsonToHtml"
@@ -44,7 +45,7 @@ export default function Puff({
{link.title || title}
@@ -73,7 +74,7 @@ export default function Puff({
diff --git a/components/Current/Blocks/Text.tsx b/components/Current/Blocks/Text.tsx
index d79bf1518..4d20bd1fc 100644
--- a/components/Current/Blocks/Text.tsx
+++ b/components/Current/Blocks/Text.tsx
@@ -1,5 +1,7 @@
import JsonToHtml from "@/components/JsonToHtml"
+import { renderOptions } from "./../currentRenderOptions"
+
import type { TextProps } from "@/types/components/current/blocks/text"
export default function Text({ text }: TextProps) {
@@ -7,6 +9,7 @@ export default function Text({ text }: TextProps) {
)
}
diff --git a/components/Current/Preamble/index.tsx b/components/Current/Preamble/index.tsx
index d615a6da6..bd0c7adc4 100644
--- a/components/Current/Preamble/index.tsx
+++ b/components/Current/Preamble/index.tsx
@@ -1,5 +1,6 @@
import JsonToHtml from "@/components/JsonToHtml"
+import { renderOptions as currentRenderOptions } from "./../currentRenderOptions"
import Breadcrumbs from "./Breadcrumbs"
import { renderOptions } from "./renderOptions"
@@ -27,7 +28,7 @@ export default function Preamble({
) : null}
diff --git a/components/Current/currentRenderOptions.module.css b/components/Current/currentRenderOptions.module.css
new file mode 100644
index 000000000..2e564ffac
--- /dev/null
+++ b/components/Current/currentRenderOptions.module.css
@@ -0,0 +1,6 @@
+.image {
+ height: auto;
+ margin-bottom: var(--Spacing-x2);
+ max-width: 100%;
+ object-fit: cover;
+}
diff --git a/components/Current/currentRenderOptions.tsx b/components/Current/currentRenderOptions.tsx
new file mode 100644
index 000000000..ae8c465be
--- /dev/null
+++ b/components/Current/currentRenderOptions.tsx
@@ -0,0 +1,474 @@
+import Image from "@/components/Image"
+import Link from "@/components/TempDesignSystem/Link"
+
+import styles from "./currentRenderOptions.module.css"
+
+import type { EmbedByUid } from "@/types/components/jsontohtml"
+import { EmbedEnum } from "@/types/requests/utils/embeds"
+import type { Attributes } from "@/types/rte/attrs"
+import { RTEItemTypeEnum, RTETypeEnum } from "@/types/rte/enums"
+import type {
+ RTEDefaultNode,
+ RTENext,
+ RTENode,
+ RTERegularNode,
+} from "@/types/rte/node"
+import { RTEMarkType } from "@/types/rte/node"
+import type { RenderOptions } from "@/types/rte/option"
+
+function extractPossibleAttributes(attrs: Attributes | undefined) {
+ if (!attrs) return {}
+ const props: Record
= {}
+ if (attrs.id) {
+ props.id = attrs.id
+ }
+
+ if (attrs.class) {
+ props.className = attrs.class
+ } else if (attrs["class-name"]) {
+ props.className = attrs["class-name"]
+ } else if (attrs.classname) {
+ props.className = attrs.classname
+ } else if (attrs?.style?.["text-align"]) {
+ props.style = {
+ textAlign: attrs?.style?.["text-align"],
+ }
+ }
+
+ return props
+}
+
+export const renderOptions: RenderOptions = {
+ [RTETypeEnum.a]: (
+ node: RTERegularNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ if (node.attrs.url) {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ }
+ return null
+ },
+
+ [RTETypeEnum.blockquote]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.code]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.embed]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ if (node.attrs.src) {
+ props.src = node.attrs.src
+ }
+ if (node.attrs.url) {
+ props.src = node.attrs.url
+ }
+ if (!props.src) {
+ return null
+ }
+ return (
+
+ )
+ },
+
+ [RTETypeEnum.h1]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.h2]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.h3]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.h4]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.h5]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.h6]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.hr]: () => {
+ return
+ },
+
+ [RTETypeEnum.li]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.ol]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.p]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.reference]: (
+ node: RTENode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ if ("attrs" in node) {
+ const type = node.attrs.type
+ if (type === RTEItemTypeEnum.asset) {
+ const image = embeds?.[node?.attrs?.["asset-uid"]]
+ if (image?.node.__typename === EmbedEnum.SysAsset) {
+ const alt = image?.node?.title ?? node.attrs.alt
+ const alignment = node.attrs?.style?.["text-align"]
+ ? {
+ alignSelf: node.attrs?.style?.["text-align"],
+ }
+ : {}
+ return (
+
+ )
+ }
+ } else {
+ const props = extractPossibleAttributes(node.attrs)
+ const href = node.attrs?.locale
+ ? `/${node.attrs.locale}${node.attrs.href}`
+ : node.attrs.href
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ }
+ }
+
+ return null
+ },
+
+ [RTETypeEnum.table]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.thead]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.tbody]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.tfoot]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.tr]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.th]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.td]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ [RTETypeEnum.ul]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ const props = extractPossibleAttributes(node.attrs)
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ },
+
+ /** TextNode wrappers */
+ [RTEMarkType.bold]: (children: React.ReactNode) => {
+ return {children}
+ },
+
+ [RTEMarkType.italic]: (children: React.ReactNode) => {
+ return {children}
+ },
+
+ [RTEMarkType.underline]: (children: React.ReactNode) => {
+ return {children}
+ },
+
+ [RTEMarkType.strikethrough]: (children: React.ReactNode) => {
+ return {children}
+ },
+
+ [RTEMarkType.inlineCode]: (children: React.ReactNode) => {
+ return {children}
+ },
+
+ [RTEMarkType.subscript]: (children: React.ReactNode) => {
+ return {children}
+ },
+
+ [RTEMarkType.superscript]: (children: React.ReactNode) => {
+ return {children}
+ },
+
+ [RTEMarkType.break]: (children: React.ReactNode) => {
+ return (
+ <>
+
+ {children}
+ >
+ )
+ },
+
+ [RTEMarkType.classnameOrId]: (
+ children: React.ReactNode,
+ className?: string,
+ id?: string
+ ) => {
+ let props = {
+ className,
+ id,
+ }
+ if (!className) {
+ delete props.className
+ }
+ if (!id) {
+ delete props.id
+ }
+ return (
+
+ {children}
+
+ )
+ },
+
+ /**
+ * Contentstack can return something called `default` as seen here in their
+ * own SDK (https://github.com/contentstack/contentstack-utils-javascript/blob/master/src/options/default-node-options.ts#L89)
+ */
+ default: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ return next(node.children, embeds, fullRenderOptions)
+ },
+}
diff --git a/components/ImageContainer/imageContainer.module.css b/components/ImageContainer/imageContainer.module.css
new file mode 100644
index 000000000..3f61b2506
--- /dev/null
+++ b/components/ImageContainer/imageContainer.module.css
@@ -0,0 +1,24 @@
+.container {
+ display: grid;
+ gap: var(--Spacing-x2);
+ width: 100%;
+ grid-template-columns: auto;
+}
+
+.image {
+ max-width: 100%;
+ width: 100%;
+ height: 365px;
+ object-fit: cover;
+ border-radius: var(--Corner-radius-Medium);
+}
+
+@media screen and (min-width: 768px) {
+ .container {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .image {
+ margin: var(--Spacing-x1) var(--Spacing-x0);
+ }
+}
diff --git a/components/ImageContainer/index.tsx b/components/ImageContainer/index.tsx
new file mode 100644
index 000000000..c7e23d621
--- /dev/null
+++ b/components/ImageContainer/index.tsx
@@ -0,0 +1,36 @@
+import Image from "../Image"
+import Caption from "../TempDesignSystem/Text/Caption"
+
+import styles from "./imageContainer.module.css"
+
+import type { ImageContainerProps } from "@/types/components/imageContainer"
+
+export default function ImageContainer({
+ leftImage,
+ rightImage,
+}: ImageContainerProps) {
+ return (
+
+
+
+ {leftImage.meta.caption}
+
+
+
+ {leftImage.meta.caption}
+
+
+ )
+}
diff --git a/components/JsonToHtml/index.tsx b/components/JsonToHtml/index.tsx
index 9beebccae..501e394e0 100644
--- a/components/JsonToHtml/index.tsx
+++ b/components/JsonToHtml/index.tsx
@@ -1,5 +1,7 @@
import { nodesToHtml } from "./utils"
+import styles from "./jsontohtml.module.css"
+
import type { JsonToHtmlProps } from "@/types/components/jsontohtml"
export default function JsonToHtml({
@@ -10,5 +12,9 @@ export default function JsonToHtml({
if (!Array.isArray(nodes) || !nodes.length) {
return null
}
- return <>{nodesToHtml(nodes, embeds, renderOptions).filter(Boolean)}>
+ return (
+
+ {nodesToHtml(nodes, embeds, renderOptions).filter(Boolean)}
+
+ )
}
diff --git a/components/JsonToHtml/jsontohtml.module.css b/components/JsonToHtml/jsontohtml.module.css
index 2e564ffac..700de51bb 100644
--- a/components/JsonToHtml/jsontohtml.module.css
+++ b/components/JsonToHtml/jsontohtml.module.css
@@ -1,6 +1,60 @@
.image {
- height: auto;
- margin-bottom: var(--Spacing-x2);
max-width: 100%;
+ width: 100%;
+ height: 365px;
object-fit: cover;
+ border-radius: var(--Corner-radius-Medium);
+ margin: var(--Spacing-x1) var(--Spacing-x0);
+}
+
+.ul,
+.ol {
+ padding: var(--Spacing-x2) var(--Spacing-x0);
+ display: grid;
+ gap: var(--Spacing-x1);
+}
+
+.ol:has(li:nth-last-child(n + 4)),
+.ul:has(li:nth-last-child(n + 4)) {
+ grid-template-columns: 1fr 1fr;
+ grid-auto-flow: column;
+}
+
+.ol > li::marker {
+ color: var(--Primary-Light-On-Surface-Accent);
+}
+
+.ul:has(.heart),
+.ul:has(.check) {
+ list-style: none;
+}
+.li:has(.heart),
+.li:has(.check) {
+ display: flex;
+}
+
+.li:not(:has(.heart), :has(.check)) {
+ margin-left: var(--Spacing-x2);
+}
+
+.li:has(.heart):before {
+ content: url("/_static/icons/heart.svg");
+ position: relative;
+ margin-right: var(--Spacing-x1);
+ height: 8px;
+ top: 3px;
+}
+
+.li:has(.check)::before {
+ content: url("/_static/icons/check-ring.svg");
+ position: relative;
+ margin-right: var(--Spacing-x1);
+ height: 8px;
+ top: 3px;
+}
+
+.container {
+ display: "grid";
+ gap: var(--Spacing-x3);
+ max-width: 1197px;
}
diff --git a/components/JsonToHtml/renderOptions.tsx b/components/JsonToHtml/renderOptions.tsx
index 1b745815e..f585dc768 100644
--- a/components/JsonToHtml/renderOptions.tsx
+++ b/components/JsonToHtml/renderOptions.tsx
@@ -1,17 +1,34 @@
import Image from "@/components/Image"
import Link from "@/components/TempDesignSystem/Link"
+import { insertResponseToImageVaultAsset } from "@/utils/imageVault"
+
+import ImageContainer from "../ImageContainer"
+import Divider from "../TempDesignSystem/Divider"
+import BiroScript from "../TempDesignSystem/Text/BiroScript"
+import Body from "../TempDesignSystem/Text/Body"
+import Caption from "../TempDesignSystem/Text/Caption"
+import Footnote from "../TempDesignSystem/Text/Footnote"
+import Subtitle from "../TempDesignSystem/Text/Subtitle"
+import Title from "../TempDesignSystem/Text/Title"
+import { hasAvailableParagraphFormat, hasAvailableULFormat } from "./utils"
import styles from "./jsontohtml.module.css"
import type { EmbedByUid } from "@/types/components/jsontohtml"
import { EmbedEnum } from "@/types/requests/utils/embeds"
-import type { Attributes } from "@/types/rte/attrs"
-import { RTEItemTypeEnum, RTETypeEnum } from "@/types/rte/enums"
+import type { Attributes, RTEImageVaultAttrs } from "@/types/rte/attrs"
+import {
+ AvailableParagraphFormatEnum,
+ RTEItemTypeEnum,
+ RTETypeEnum,
+} from "@/types/rte/enums"
import type {
RTEDefaultNode,
+ RTEImageNode,
RTENext,
RTENode,
RTERegularNode,
+ RTETextNode,
} from "@/types/rte/node"
import { RTEMarkType } from "@/types/rte/node"
import type { RenderOptions } from "@/types/rte/option"
@@ -48,14 +65,16 @@ export const renderOptions: RenderOptions = {
if (node.attrs.url) {
const props = extractPossibleAttributes(node.attrs)
return (
-
{next(node.children, embeds, fullRenderOptions)}
-
+
)
}
return null
@@ -69,9 +88,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
-
+
)
},
@@ -120,9 +139,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
-
+
)
},
@@ -134,9 +153,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
-
+
)
},
@@ -148,9 +167,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
-
+
)
},
@@ -162,9 +181,9 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
-
+
)
},
@@ -176,28 +195,14 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
-
- )
- },
-
- [RTETypeEnum.h6]: (
- node: RTEDefaultNode,
- embeds: EmbedByUid,
- next: RTENext,
- fullRenderOptions: RenderOptions
- ) => {
- const props = extractPossibleAttributes(node.attrs)
- return (
-
- {next(node.children, embeds, fullRenderOptions)}
-
+
)
},
[RTETypeEnum.hr]: () => {
- return
+ return
},
[RTETypeEnum.li]: (
@@ -208,7 +213,7 @@ export const renderOptions: RenderOptions = {
) => {
const props = extractPossibleAttributes(node.attrs)
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
)
@@ -221,8 +226,26 @@ export const renderOptions: RenderOptions = {
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
+
+ // Set the number of rows dynamically to create even rows for each column. We want the li:s
+ // to flow with the column, so therefore this is needed.
+ let numberOfRows: number | undefined
+ if (node.children.length > 4) {
+ const half = node.children.length / 2
+ numberOfRows = Math.ceil(half)
+ }
+
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
)
@@ -235,10 +258,21 @@ export const renderOptions: RenderOptions = {
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
+
+ const hasFormat = node.children.some((item) =>
+ hasAvailableParagraphFormat((item as RTETextNode)?.classname)
+ )
+
+ // If a child node has an available format as className, we wrap it in a
+ // span and render the children with the correct component
+ if (hasFormat) {
+ return next(node.children, embeds, fullRenderOptions)
+ }
+
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
-
+
)
},
@@ -254,32 +288,78 @@ export const renderOptions: RenderOptions = {
const image = embeds?.[node?.attrs?.["asset-uid"]]
if (image?.node.__typename === EmbedEnum.SysAsset) {
const alt = image?.node?.title ?? node.attrs.alt
- const alignment = node.attrs?.style?.["text-align"]
- ? {
- alignSelf: node.attrs?.style?.["text-align"],
- }
- : {}
+ const props = extractPossibleAttributes(node.attrs)
+ props.className = styles.image
return (
)
}
- } else {
- const props = extractPossibleAttributes(node.attrs)
- const href = node.attrs?.locale
- ? `/${node.attrs.locale}${node.attrs.href}`
- : node.attrs.href
+ } else if (type === RTEItemTypeEnum.entry) {
+ const entry = embeds?.[node?.attrs?.["entry-uid"]]
+
+ if (entry?.node.__typename === EmbedEnum.ImageContainer) {
+ const leftImage = insertResponseToImageVaultAsset(
+ entry.node.image_left
+ )
+ const rightImage = insertResponseToImageVaultAsset(
+ entry.node.image_right
+ )
+ return (
+
+ )
+ } else {
+ // If entry is not an ImageContainer, it is a page and we return it as a link
+ const props = extractPossibleAttributes(node.attrs)
+ const href = node.attrs?.locale
+ ? `/${node.attrs.locale}${node.attrs.href}`
+ : node.attrs.href
+ return (
+
+ {next(node.children, embeds, fullRenderOptions)}
+
+ )
+ }
+ }
+ }
+
+ return null
+ },
+
+ [RTETypeEnum.ImageVault]: (node: RTEImageNode) => {
+ if ("attrs" in node) {
+ const type = node.type
+ if (type === RTETypeEnum.ImageVault) {
+ const attrs = node.attrs as RTEImageVaultAttrs
+ const image = insertResponseToImageVaultAsset(attrs)
+ const alt = image.meta.alt ?? image.title
+
+ const width = parseInt(attrs.width.replaceAll("px", ""))
+ const props = extractPossibleAttributes(attrs)
return (
-
- {next(node.children, embeds, fullRenderOptions)}
-
+
+
+ {image.meta.caption}
+
)
}
}
@@ -343,6 +423,15 @@ export const renderOptions: RenderOptions = {
)
},
+ [RTETypeEnum.fragment]: (
+ node: RTEDefaultNode,
+ embeds: EmbedByUid,
+ next: RTENext,
+ fullRenderOptions: RenderOptions
+ ) => {
+ return <>{next(node.children, embeds, fullRenderOptions)}>
+ },
+
[RTETypeEnum.tr]: (
node: RTEDefaultNode,
embeds: EmbedByUid,
@@ -392,8 +481,26 @@ export const renderOptions: RenderOptions = {
fullRenderOptions: RenderOptions
) => {
const props = extractPossibleAttributes(node.attrs)
+
+ // Set the number of rows dynamically to create even rows for each column. We want the li:s
+ // to flow with the column, so therefore this is needed.
+ let numberOfRows: number | undefined
+ if (node.children.length > 4) {
+ const half = node.children.length / 2
+ numberOfRows = Math.ceil(half)
+ }
+
return (
-
+
{next(node.children, embeds, fullRenderOptions)}
)
@@ -452,6 +559,62 @@ export const renderOptions: RenderOptions = {
if (!id) {
delete props.id
}
+
+ if (className) {
+ if (hasAvailableULFormat(className)) {
+ // @ts-ignore: We want to set css modules classNames even if it does not correspond
+ // to an existing class in the module style sheet. Due to our css modules plugin for
+ // typescript, we cannot do this without the ts-ignore
+ props.className = styles[className]
+ }
+ }
+
+ if (className === AvailableParagraphFormatEnum.footnote) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (className === AvailableParagraphFormatEnum.caption) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (className === AvailableParagraphFormatEnum["script-1"]) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (className === AvailableParagraphFormatEnum["script-2"]) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (className === AvailableParagraphFormatEnum["subtitle-1"]) {
+ return (
+
+ {children}
+
+ )
+ }
+ if (className === AvailableParagraphFormatEnum["subtitle-2"]) {
+ return (
+
+ {children}
+
+ )
+ }
return (
{children}
diff --git a/components/JsonToHtml/utils.tsx b/components/JsonToHtml/utils.tsx
index f368e380c..496b9f9e4 100644
--- a/components/JsonToHtml/utils.tsx
+++ b/components/JsonToHtml/utils.tsx
@@ -3,7 +3,11 @@ import { renderOptions } from "./renderOptions"
import type { EmbedByUid } from "@/types/components/jsontohtml"
import type { Embeds } from "@/types/requests/embeds"
import type { Node } from "@/types/requests/utils/edges"
-import { RTETypeEnum } from "@/types/rte/enums"
+import {
+ AvailableParagraphFormatEnum,
+ AvailableULFormatEnum,
+ RTETypeEnum,
+} from "@/types/rte/enums"
import type {
RTENode,
RTERenderOptionComponent,
@@ -74,7 +78,6 @@ export function textNodeToHtml(
if (node.bold) {
text = (fullRenderOptions[RTEMarkType.bold] as RTERenderMark)(text)
}
-
return text
}
@@ -86,6 +89,20 @@ function next(
return nodeChildrenToHtml(nodes, embeds, fullRenderOptions)
}
+export function hasAvailableParagraphFormat(className?: string) {
+ if (!className) {
+ return false
+ }
+ return Object.keys(AvailableParagraphFormatEnum).includes(className)
+}
+
+export function hasAvailableULFormat(className?: string) {
+ if (!className) {
+ return false
+ }
+ return Object.keys(AvailableULFormatEnum).includes(className)
+}
+
export function nodeToHtml(
node: RTENode,
embeds: EmbedByUid,
diff --git a/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/index.tsx b/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/index.tsx
index 827153aa1..ee9f95ac1 100644
--- a/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/index.tsx
+++ b/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/index.tsx
@@ -44,7 +44,7 @@ export default async function ContactRow({ contact }: ContactRowProps) {
diff --git a/components/Section/Link/index.tsx b/components/Section/Link/index.tsx
index 924e81620..c8b27aedd 100644
--- a/components/Section/Link/index.tsx
+++ b/components/Section/Link/index.tsx
@@ -18,7 +18,7 @@ export default function SectionLink({ link, variant }: SectionLinkProps) {
className={classNames}
color="burgundy"
href={link.href}
- variant="myPage"
+ variant="underscored"
>
{link.text}
diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css
index bcf90ec33..94a3c22ec 100644
--- a/components/TempDesignSystem/Link/link.module.css
+++ b/components/TempDesignSystem/Link/link.module.css
@@ -20,7 +20,7 @@
gap: var(--Spacing-x-half);
}
-.myPage {
+.underscored {
font-family: var(--typography-Body-Underlined-fontFamily);
font-size: var(--typography-Body-Underlined-fontSize);
font-weight: var(--typography-Body-Underlined-fontWeight);
diff --git a/components/TempDesignSystem/Link/variants.ts b/components/TempDesignSystem/Link/variants.ts
index cffb3dda4..df9d2c52f 100644
--- a/components/TempDesignSystem/Link/variants.ts
+++ b/components/TempDesignSystem/Link/variants.ts
@@ -23,7 +23,7 @@ export const linkVariants = cva(styles.link, {
breadcrumb: styles.breadcrumb,
default: styles.default,
icon: styles.icon,
- myPage: styles.myPage,
+ underscored: styles.underscored,
myPageMobileDropdown: styles.myPageMobileDropdown,
shortcut: styles.shortcut,
sidebar: styles.sidebar,
diff --git a/components/TempDesignSystem/LoyaltyCard/index.tsx b/components/TempDesignSystem/LoyaltyCard/index.tsx
index db060ef42..b6dbd0008 100644
--- a/components/TempDesignSystem/LoyaltyCard/index.tsx
+++ b/components/TempDesignSystem/LoyaltyCard/index.tsx
@@ -49,7 +49,7 @@ export default function LoyaltyCard({
color="burgundy"
href={link.href}
target={link.openInNewTab ? "_blank" : undefined}
- variant="myPage"
+ variant="underscored"
>
+
+
+
+
+
+
+
diff --git a/public/_static/icons/heart.svg b/public/_static/icons/heart.svg
new file mode 100644
index 000000000..e46e94905
--- /dev/null
+++ b/public/_static/icons/heart.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts
index 0f2196b76..8c1374ff2 100644
--- a/server/routers/contentstack/loyaltyPage/output.ts
+++ b/server/routers/contentstack/loyaltyPage/output.ts
@@ -13,6 +13,7 @@ import {
} from "@/types/components/loyalty/enums"
import { Embeds } from "@/types/requests/embeds"
import { PageLinkEnum } from "@/types/requests/pageLinks"
+import { RTEEmbedsEnum } from "@/types/requests/rte"
import { EdgesWithTotalCount } from "@/types/requests/utils/edges"
import { RTEDocument } from "@/types/rte/node"
@@ -272,6 +273,20 @@ const pageConnectionRefs = z.object({
),
})
+const rteConnectionRefs = z.object({
+ edges: z.array(
+ z.object({
+ node: z.object({
+ __typename: z.nativeEnum(RTEEmbedsEnum),
+ system: z.object({
+ content_type_uid: z.string(),
+ uid: z.string(),
+ }),
+ }),
+ })
+ ),
+})
+
const cardBlockRefs = z.object({
__typename: z.literal(LoyaltyCardsGridEnum.Card),
primary_button: z
@@ -349,7 +364,7 @@ const loyaltyPageBlockTextContentRefs = z.object({
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent),
content: z.object({
content: z.object({
- embedded_itemsConnection: pageConnectionRefs,
+ embedded_itemsConnection: rteConnectionRefs,
}),
}),
})
@@ -365,7 +380,7 @@ const loyaltyPageSidebarTextContentRef = z.object({
__typename: z.literal(SidebarTypenameEnum.LoyaltyPageSidebarContent),
content: z.object({
content: z.object({
- embedded_itemsConnection: pageConnectionRefs,
+ embedded_itemsConnection: rteConnectionRefs,
}),
}),
})
diff --git a/types/components/imageContainer.ts b/types/components/imageContainer.ts
new file mode 100644
index 000000000..6f8e75393
--- /dev/null
+++ b/types/components/imageContainer.ts
@@ -0,0 +1,6 @@
+import type { ImageVaultAsset } from "./imageVaultImage"
+
+export type ImageContainerProps = {
+ leftImage: ImageVaultAsset
+ rightImage: ImageVaultAsset
+}
diff --git a/types/requests/embeds.ts b/types/requests/embeds.ts
index 11e863fd5..714c8746f 100644
--- a/types/requests/embeds.ts
+++ b/types/requests/embeds.ts
@@ -1,3 +1,4 @@
+import type { ImageContainer } from "./imageContainer"
import type { SysAsset } from "./utils/asset"
-export type Embeds = SysAsset
+export type Embeds = SysAsset | ImageContainer
diff --git a/types/requests/imageContainer.ts b/types/requests/imageContainer.ts
new file mode 100644
index 000000000..3eb85023c
--- /dev/null
+++ b/types/requests/imageContainer.ts
@@ -0,0 +1,14 @@
+import { InsertResponse } from "../components/imageVaultImage"
+import { EmbedEnum } from "./utils/embeds"
+import { Typename } from "./utils/typename"
+
+export type ImageContainer = Typename<
+ {
+ image_left: InsertResponse
+ image_right: InsertResponse
+ system: {
+ uid: string
+ }
+ },
+ EmbedEnum.ImageContainer
+>
diff --git a/types/requests/rte.ts b/types/requests/rte.ts
new file mode 100644
index 000000000..f6e18b41a
--- /dev/null
+++ b/types/requests/rte.ts
@@ -0,0 +1,6 @@
+export enum RTEEmbedsEnum {
+ AccountPage = "AccountPage",
+ ContentPage = "ContentPage",
+ LoyaltyPage = "LoyaltyPage",
+ ImageContainer = "ImageContainer",
+}
diff --git a/types/requests/utils/embeds.ts b/types/requests/utils/embeds.ts
index 8cdb3fcaa..527564882 100644
--- a/types/requests/utils/embeds.ts
+++ b/types/requests/utils/embeds.ts
@@ -1,6 +1,10 @@
export enum EmbedEnum {
CurrentBlocksPage = "CurrentBlocksPage",
SysAsset = "SysAsset",
+ ImageContainer = "ImageContainer",
+ LoyaltyPage = "LoyaltyPage",
+ AccountPage = "AccountPage",
+ ContentPage = "ContentPage",
}
export type Embed = keyof typeof EmbedEnum
diff --git a/types/rte/attrs.ts b/types/rte/attrs.ts
index f8bb36e30..dd658872d 100644
--- a/types/rte/attrs.ts
+++ b/types/rte/attrs.ts
@@ -1,6 +1,8 @@
+import { InsertResponse } from "../components/imageVaultImage"
import { RTEItemTypeEnum } from "./enums"
-import type { EmbedTypesEnum, RTEItemType } from "./enums"
+
import type { Lang } from "@/constants/languages"
+import type { EmbedTypesEnum, RTEItemType } from "./enums"
export interface Attributes {
[key: string]: any
@@ -36,3 +38,9 @@ export interface RTELinkAttrs extends Attributes {
target: HTMLAnchorElement["target"]
type: RTEItemTypeEnum.entry
}
+
+export interface RTEImageVaultAttrs extends Attributes, InsertResponse {
+ height: string
+ width: string
+ style: string[]
+}
diff --git a/types/rte/enums.ts b/types/rte/enums.ts
index b5eb2a6a1..f4d6bd86c 100644
--- a/types/rte/enums.ts
+++ b/types/rte/enums.ts
@@ -36,6 +36,8 @@ export enum RTETypeEnum {
thead = "thead",
tr = "tr",
ul = "ul",
+ ImageVault = "ImageVault",
+ fragment = "fragment",
}
export type RTEType = keyof typeof RTETypeEnum
@@ -46,3 +48,17 @@ export enum RTEItemTypeEnum {
}
export type RTEItemType = keyof typeof RTEItemTypeEnum
+
+export enum AvailableParagraphFormatEnum {
+ "script-1" = "script-1",
+ "script-2" = "script-2",
+ "footnote" = "footnote",
+ "caption" = "caption",
+ "subtitle-1" = "subtitle-1",
+ "subtitle-2" = "subtitle-2",
+}
+
+export enum AvailableULFormatEnum {
+ "heart" = "heart",
+ "check" = "check",
+}
diff --git a/types/rte/node.ts b/types/rte/node.ts
index c762f33bb..dca0f9281 100644
--- a/types/rte/node.ts
+++ b/types/rte/node.ts
@@ -5,6 +5,7 @@ import type {
Attributes,
RTEAnchorAttrs,
RTEAssetAttrs,
+ RTEImageVaultAttrs,
RTELinkAttrs,
} from "./attrs"
import type { RenderOptions } from "./option"
@@ -36,6 +37,11 @@ export interface RTEReferenceLinkNode extends RTEDefaultNode {
attrs: RTELinkAttrs
}
+export interface RTEImageVaultNode extends RTEDefaultNode {
+ attrs: RTEImageVaultAttrs
+ type: RTETypeEnum.ImageVault
+}
+
export enum RTEMarkType {
bold = "bold",
break = "break",
@@ -58,9 +64,11 @@ export type RTETextNode = RTETextNodeOptionalKeys & {
text: string
}
-export type RTERegularNode = RTEDefaultNode | RTEAnchorNode
+export type RTERegularNode = RTEDefaultNode | RTEAnchorNode | RTEImageVaultNode
-export type RTEReferenceNode = RTEDefaultNode | RTEAnchorNode
+export type RTEImageNode = RTEDefaultNode | RTEImageVaultNode
+
+export type RTEReferenceNode = RTEAnchorNode
export type RTENode = RTERegularNode | RTEReferenceNode | RTETextNode