diff --git a/components/Blocks/UspGrid/index.tsx b/components/Blocks/UspGrid/index.tsx new file mode 100644 index 000000000..26110c622 --- /dev/null +++ b/components/Blocks/UspGrid/index.tsx @@ -0,0 +1,35 @@ +import { getIconByIconName } from "@/components/Icons/get-icon-by-icon-name" +import JsonToHtml from "@/components/JsonToHtml" + +import { renderOptions } from "./renderOptions" +import { getUspIconName } from "./utils" + +import styles from "./uspgrid.module.css" + +import type { UspGridProps, UspIcon } from "@/types/components/blocks/uspGrid" + +function UspIcon({ icon }: { icon: UspIcon }) { + const iconName = getUspIconName(icon) + const Icon = iconName ? getIconByIconName(iconName) : null + return Icon ? : null +} + +export default function UspGrid({ usp_grid }: UspGridProps) { + return ( +
+ {usp_grid.usp_card.map( + (usp) => + usp.text.json && ( +
+ + +
+ ) + )} +
+ ) +} diff --git a/components/Blocks/UspGrid/renderOptions.tsx b/components/Blocks/UspGrid/renderOptions.tsx new file mode 100644 index 000000000..a4033fec5 --- /dev/null +++ b/components/Blocks/UspGrid/renderOptions.tsx @@ -0,0 +1,73 @@ +import Link from "@/components/TempDesignSystem/Link" +import { removeMultipleSlashes } from "@/utils/url" + +import styles from "./uspgrid.module.css" + +import { EmbedEnum } from "@/types/requests/utils/embeds" +import type { EmbedByUid } from "@/types/transitionTypes/jsontohtml" +import { RTEItemTypeEnum, RTETypeEnum } from "@/types/transitionTypes/rte/enums" +import type { + RTEDefaultNode, + RTENext, + RTENode, + RTERegularNode, +} from "@/types/transitionTypes/rte/node" +import type { RenderOptions } from "@/types/transitionTypes/rte/option" + +export const renderOptions: RenderOptions = { + [RTETypeEnum.p]: ( + node: RTEDefaultNode, + embeds: EmbedByUid, + next: RTENext, + fullRenderOptions: RenderOptions + ) => { + return ( +

+ {next(node.children, embeds, fullRenderOptions)} +

+ ) + }, + [RTETypeEnum.a]: ( + node: RTERegularNode, + embeds: EmbedByUid, + next: RTENext, + fullRenderOptions: RenderOptions + ) => { + if (node.attrs.url) { + return ( + + {next(node.children, embeds, fullRenderOptions)} + + ) + } + return null + }, + [RTETypeEnum.reference]: ( + node: RTENode, + embeds: EmbedByUid, + next: RTENext, + fullRenderOptions: RenderOptions + ) => { + if ("attrs" in node) { + const type = node.attrs.type + if (type !== RTEItemTypeEnum.asset) { + const href = node.attrs?.locale + ? `/${node.attrs.locale}${node.attrs.href}` + : node.attrs.href + + return ( + + {next(node.children, embeds, fullRenderOptions)} + + ) + } + + return null + } + }, +} diff --git a/components/Blocks/UspGrid/uspgrid.module.css b/components/Blocks/UspGrid/uspgrid.module.css new file mode 100644 index 000000000..042acd9b3 --- /dev/null +++ b/components/Blocks/UspGrid/uspgrid.module.css @@ -0,0 +1,28 @@ +.grid { + display: grid; + gap: var(--Spacing-x3); + padding: var(--Spacing-x3) var(--Spacing-x4); +} +@media screen and (min-width: 767px) { + .grid { + grid-template-columns: repeat(3, 1fr); + } + .grid:has(.usp:nth-child(4)) { + grid-template-columns: repeat(2, 1fr); + } +} +.usp { + display: flex; + flex-direction: column; + gap: var(--Spacing-x3); +} +.p { + margin: 0; + font-size: var(--typography-Caption-Regular-fontSize); + color: var(--UI-Text-Medium-contrast); + line-height: 21px; /* Caption variable for line-height is 139.9999976158142%, but it set to 21px in design */ +} +.a { + font-size: var(--typography-Caption-Regular-fontSize); + color: var(--Base-Text-High-contrast); +} diff --git a/components/Blocks/UspGrid/utils.ts b/components/Blocks/UspGrid/utils.ts new file mode 100644 index 000000000..700482909 --- /dev/null +++ b/components/Blocks/UspGrid/utils.ts @@ -0,0 +1,11 @@ +import { UspIcon } from "@/types/components/blocks/uspGrid" +import { IconName } from "@/types/components/icon" + +export function getUspIconName(icon?: UspIcon | null) { + switch (icon) { + case "Snowflake": + return IconName.Snowflake + default: + return IconName.Snowflake + } +} diff --git a/components/Blocks/index.tsx b/components/Blocks/index.tsx index 9946a6fa0..1c6d0bcbc 100644 --- a/components/Blocks/index.tsx +++ b/components/Blocks/index.tsx @@ -2,6 +2,7 @@ import CardsGrid from "@/components/Blocks/CardsGrid" import DynamicContent from "@/components/Blocks/DynamicContent" import Shortcuts from "@/components/Blocks/Shortcuts" import TextCols from "@/components/Blocks/TextCols" +import UspGrid from "@/components/Blocks/UspGrid" import JsonToHtml from "@/components/JsonToHtml" import type { BlocksProps } from "@/types/components/blocks" @@ -57,6 +58,8 @@ export default function Blocks({ blocks }: BlocksProps) { /> ) + case BlocksEnums.block.UspGrid: + return default: return null } diff --git a/components/Icons/Snowflake.tsx b/components/Icons/Snowflake.tsx new file mode 100644 index 000000000..06389343f --- /dev/null +++ b/components/Icons/Snowflake.tsx @@ -0,0 +1,27 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function SnowflakeIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + ) +} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index 117971b3c..92d674299 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -49,6 +49,7 @@ import { SearchIcon, ServiceIcon, ShoppingIcon, + SnowflakeIcon, StarFilledIcon, TrainIcon, TshirtWashIcon, @@ -154,6 +155,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return ServiceIcon case IconName.Shopping: return ShoppingIcon + case IconName.Snowflake: + return SnowflakeIcon case IconName.StarFilled: return StarFilledIcon case IconName.Train: diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index c1806693d..433ef0c89 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -48,6 +48,7 @@ export { default as ScandicLogoIcon } from "./ScandicLogo" export { default as SearchIcon } from "./Search" export { default as ServiceIcon } from "./Service" export { default as ShoppingIcon } from "./Shopping" +export { default as SnowflakeIcon } from "./Snowflake" export { default as StarFilledIcon } from "./StarFilled" export { default as TrainIcon } from "./Train" export { default as TshirtWashIcon } from "./TshirtWash" diff --git a/lib/graphql/Fragments/Blocks/UspGrid.graphql b/lib/graphql/Fragments/Blocks/UspGrid.graphql index 6ccc2a587..ae2df5aa8 100644 --- a/lib/graphql/Fragments/Blocks/UspGrid.graphql +++ b/lib/graphql/Fragments/Blocks/UspGrid.graphql @@ -16,6 +16,7 @@ fragment UspGrid_ContentPage on ContentPageBlocksUspGrid { node { ... on UspGrid { usp_card { + __typename icon text { embedded_itemsConnection { @@ -54,7 +55,7 @@ fragment UspGrid_ContentPageRefs on ContentPageBlocksUspGrid { __typename ...AccountPageRef ...ContentPageRef - ...ImageContainerRef + ...HotelPageRef ...LoyaltyPageRef } } diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts index bb2728e63..8048faee6 100644 --- a/server/routers/contentstack/contentPage/output.ts +++ b/server/routers/contentstack/contentPage/output.ts @@ -19,7 +19,7 @@ import { shortcutsSchema, } from "../schemas/blocks/shortcuts" import { textColsRefsSchema, textColsSchema } from "../schemas/blocks/textCols" -import { uspGridSchema } from "../schemas/blocks/uspGrid" +import { uspGridRefsSchema, uspGridSchema } from "../schemas/blocks/uspGrid" import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { contentRefsSchema as sidebarContentRefsSchema, @@ -157,12 +157,19 @@ const contentPageTextColsRefs = z }) .merge(textColsRefsSchema) +const contentPageUspGridRefs = z + .object({ + __typename: z.literal(ContentPageEnum.ContentStack.blocks.UspGrid), + }) + .merge(uspGridRefsSchema) + const contentPageBlockRefsItem = z.discriminatedUnion("__typename", [ contentPageBlockContentRefs, contentPageShortcutsRefs, contentPageCardsRefs, contentPageDynamicContentRefs, contentPageTextColsRefs, + contentPageUspGridRefs, ]) const contentPageSidebarContentRef = z diff --git a/server/routers/contentstack/contentPage/utils.ts b/server/routers/contentstack/contentPage/utils.ts index c4cc9c5c5..78f5a9d7f 100644 --- a/server/routers/contentstack/contentPage/utils.ts +++ b/server/routers/contentstack/contentPage/utils.ts @@ -147,6 +147,12 @@ export function getConnections({ content_page }: ContentPageRefs) { } break } + case ContentPageEnum.ContentStack.blocks.UspGrid: { + if (block.usp_grid.length) { + connections.push(...block.usp_grid) + } + break + } } }) } diff --git a/server/routers/contentstack/schemas/blocks/uspGrid.ts b/server/routers/contentstack/schemas/blocks/uspGrid.ts index d65a5d0f2..277f84968 100644 --- a/server/routers/contentstack/schemas/blocks/uspGrid.ts +++ b/server/routers/contentstack/schemas/blocks/uspGrid.ts @@ -5,41 +5,57 @@ import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks" import { BlocksEnums } from "@/types/enums/blocks" import { UspGridEnum } from "@/types/enums/uspGrid" +const uspCardSchema = z.object({ + icon: UspGridEnum.uspIcons, + text: z.object({ + json: z.any(), // JSON + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: z + .discriminatedUnion("__typename", [ + pageLinks.accountPageSchema, + pageLinks.contentPageSchema, + pageLinks.hotelPageSchema, + pageLinks.loyaltyPageSchema, + ]) + .transform((data) => { + const link = pageLinks.transform(data) + if (link) { + return link + } + return data + }), + }) + ), + }), + }), +}) + export const uspGridSchema = z.object({ typename: z .literal(BlocksEnums.block.UspGrid) .optional() .default(BlocksEnums.block.UspGrid), - usp_grid: z.object({ - usp_card: z.array( - z.object({ - icon: UspGridEnum.uspIcons, - text: z.object({ - json: z.any(), // JSON - embedded_itemsConnection: z.object({ - edges: z.array( - z.object({ - node: z - .discriminatedUnion("__typename", [ - pageLinks.accountPageSchema, - pageLinks.contentPageSchema, - pageLinks.hotelPageSchema, - pageLinks.loyaltyPageSchema, - ]) - .transform((data) => { - const link = pageLinks.transform(data) - if (link) { - return link - } - return data - }), - }) - ), - }), - }), - }) - ), - }), + usp_grid: z + .object({ + cardsConnection: z.object({ + edges: z.array( + z.object({ + node: z.object({ + usp_card: z.array(uspCardSchema), + }), + }) + ), + }), + }) + .transform((data) => { + return { + usp_card: data.cardsConnection.edges.flatMap( + (edge) => edge.node.usp_card + ), + } + }), }) const actualRefs = z.discriminatedUnion("__typename", [ @@ -49,30 +65,40 @@ const actualRefs = z.discriminatedUnion("__typename", [ pageLinks.loyaltyPageRefSchema, ]) -type Refs = { - node: z.TypeOf -} - export const uspGridRefsSchema = z.object({ usp_grid: z .object({ - usp_card: z.array( - z.object({ - text: z.object({ - embedded_itemsConnection: z.object({ - edges: z.array( + cardsConnection: z.object({ + edges: z.array( + z.object({ + node: z.object({ + usp_card: z.array( z.object({ - node: z.discriminatedUnion("__typename", [ - ...actualRefs.options, - ]), + text: z.object({ + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: z.discriminatedUnion("__typename", [ + ...actualRefs.options, + ]), + }) + ), + }), + }), }) ), }), - }), - }) - ), + }) + ), + }), }) .transform((data) => { - return data.usp_card.flat() + return data.cardsConnection.edges.flatMap(({ node }) => + node.usp_card.flatMap((card) => + card.text.embedded_itemsConnection.edges.map( + ({ node }) => node.system + ) + ) + ) }), }) diff --git a/types/components/blocks/uspGrid.ts b/types/components/blocks/uspGrid.ts new file mode 100644 index 000000000..ea6218541 --- /dev/null +++ b/types/components/blocks/uspGrid.ts @@ -0,0 +1,4 @@ +import type { UspGrid } from "@/types/trpc/routers/contentstack/blocks" + +export interface UspGridProps extends Pick {} +export type UspIcon = UspGrid["usp_grid"]["usp_card"][number]["icon"] diff --git a/types/components/icon.ts b/types/components/icon.ts index 8096ef0f7..f9f61a525 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -54,6 +54,7 @@ export enum IconName { Search = "Search", Service = "Service", Shopping = "Shopping", + Snowflake = "Snowflake", StarFilled = "StarFilled", Train = "Train", Tripadvisor = "Tripadvisor", diff --git a/types/requests/utils/embeds.ts b/types/requests/utils/embeds.ts index 527564882..0fe39ddbd 100644 --- a/types/requests/utils/embeds.ts +++ b/types/requests/utils/embeds.ts @@ -5,6 +5,7 @@ export enum EmbedEnum { LoyaltyPage = "LoyaltyPage", AccountPage = "AccountPage", ContentPage = "ContentPage", + HotelPage = "HotelPage", } export type Embed = keyof typeof EmbedEnum diff --git a/types/trpc/routers/contentstack/blocks.ts b/types/trpc/routers/contentstack/blocks.ts index 8d8e449ab..6bffdfc90 100644 --- a/types/trpc/routers/contentstack/blocks.ts +++ b/types/trpc/routers/contentstack/blocks.ts @@ -4,13 +4,13 @@ import { cardsGridSchema } from "@/server/routers/contentstack/schemas/blocks/ca import { contentSchema } from "@/server/routers/contentstack/schemas/blocks/content" import { dynamicContentSchema } from "@/server/routers/contentstack/schemas/blocks/dynamicContent" import { shortcutsSchema } from "@/server/routers/contentstack/schemas/blocks/shortcuts" - import { textColsSchema } from "@/server/routers/contentstack/schemas/blocks/textCols" +import { uspGridSchema } from "@/server/routers/contentstack/schemas/blocks/uspGrid" -export interface CardsGrid extends z.output { } -export interface Content extends z.output { } -export interface DynamicContent extends z.output { } -export interface Shortcuts extends z.output { } +export interface CardsGrid extends z.output {} +export interface Content extends z.output {} +export interface DynamicContent extends z.output {} +export interface Shortcuts extends z.output {} export type Shortcut = Shortcuts["shortcuts"] -export interface TextCols extends z.output { } - +export interface TextCols extends z.output {} +export interface UspGrid extends z.output {} diff --git a/types/trpc/routers/contentstack/contentPage.ts b/types/trpc/routers/contentstack/contentPage.ts index d95521319..6bc343278 100644 --- a/types/trpc/routers/contentstack/contentPage.ts +++ b/types/trpc/routers/contentstack/contentPage.ts @@ -8,15 +8,15 @@ import { } from "@/server/routers/contentstack/contentPage/output" export interface GetContentPageRefsSchema - extends z.input { } + extends z.input {} export interface ContentPageRefs - extends z.output { } + extends z.output {} export interface GetContentPageSchema - extends z.input { } + extends z.input {} -export interface ContentPage extends z.output { } +export interface ContentPage extends z.output {} export type Block = z.output