diff --git a/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css b/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css index 6d6df6168..be2804939 100644 --- a/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css +++ b/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css @@ -1,2 +1,32 @@ -.htmlContent { +.ul, +.ol { + padding: var(--Spacing-x2) var(--Spacing-x0); + display: grid; + gap: var(--Spacing-x1); + margin-left: var(--Spacing-x2); +} + +.ol > li::marker { + color: var(--Primary-Light-On-Surface-Accent); +} + +.li { + margin-left: var(--Spacing-x3); +} + +.li > p { + display: inline; +} + +@media screen and (min-width: 768px) { + .ol:has(li:nth-last-child(n + 5)), + .ul:has(li:nth-last-child(n + 5)) { + grid-template-columns: 1fr 1fr; + grid-auto-flow: column; + } +} + +.tableContainer { + max-width: 100%; + overflow-x: auto; } diff --git a/components/ContentType/HotelSubpage/HtmlContent/index.tsx b/components/ContentType/HotelSubpage/HtmlContent/index.tsx index 4cd806da6..942009b57 100644 --- a/components/ContentType/HotelSubpage/HtmlContent/index.tsx +++ b/components/ContentType/HotelSubpage/HtmlContent/index.tsx @@ -1,16 +1,156 @@ +import { ElementType } from "domelementtype" +import parse, { type DOMNode, Element, type Text } from "html-react-parser" + +import Link from "@/components/TempDesignSystem/Link" +import Table from "@/components/TempDesignSystem/Table" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" + +import { NodeNames } from "./utils" + import styles from "./htmlContent.module.css" +type Node = Element | Text + interface HtmlContentProps { html: string } export default function HtmlContent({ html }: HtmlContentProps) { - return ( -
- ) + const cleanedHtml = html.replace("

\n", "") + const parsedContent = parse(cleanedHtml, { + replace: (domNode: DOMNode) => { + if (domNode instanceof Element) { + return renderNode(domNode) + } else { + if (domNode.data === "\n") { + return
+ } + } + }, + }) + return
{parsedContent}
+} + +function renderChildren(node: Element) { + return node.children?.map((child) => renderNode(child as Element)) +} + +function renderNode(domNode: Node) { + if (domNode.type === ElementType.Tag) { + switch (domNode.name) { + case NodeNames.h1: + case NodeNames.h2: + case NodeNames.h3: + case NodeNames.h4: + case NodeNames.h5: + return {renderChildren(domNode)} + + case NodeNames.br: + return
+ + case NodeNames.a: + console.log(domNode.attribs.target) + return ( + + {renderChildren(domNode)} + + ) + + case NodeNames.ul: + let numberOfRows: number | undefined + if (domNode.children.length > 4) { + const half = domNode.children.length / 2 + numberOfRows = Math.ceil(half) + } + return ( + + ) + + case NodeNames.ol: + let numberOfOlRows: number | undefined + if (domNode.children.length > 4) { + const half = domNode.children.length / 2 + numberOfOlRows = Math.ceil(half) + } + return ( +
    + {renderChildren(domNode)} +
+ ) + + case NodeNames.li: + return
  • {renderChildren(domNode)}
  • + + case NodeNames.td: + return {renderChildren(domNode)} + + case NodeNames.th: + return ( + + + {renderChildren(domNode)} + + + ) + + case NodeNames.tr: + return {renderChildren(domNode)} + + case NodeNames.tbody: + return {renderChildren(domNode)} + + case NodeNames.table: + return ( +
    + {renderChildren(domNode)}
    +
    + ) + + case NodeNames.p: + return ( + domNode.children.length !== 0 && ( + {renderChildren(domNode)} + ) + ) + + case NodeNames.em: + return {renderChildren(domNode)} + + case NodeNames.strong: + return {renderChildren(domNode)} + + case NodeNames.span: + return {renderChildren(domNode)} + } + } else if (domNode.type === ElementType.Text) { + return domNode.data + } + return null } diff --git a/components/ContentType/HotelSubpage/HtmlContent/utils.ts b/components/ContentType/HotelSubpage/HtmlContent/utils.ts new file mode 100644 index 000000000..507ab1480 --- /dev/null +++ b/components/ContentType/HotelSubpage/HtmlContent/utils.ts @@ -0,0 +1,22 @@ +export enum NodeNames { + a = "a", + h1 = "h1", + h2 = "h2", + h3 = "h3", + h4 = "h4", + h5 = "h5", + li = "li", + ol = "ol", + p = "p", + table = "table", + tbody = "tbody", + td = "td", + text = "text", + th = "th", + tr = "tr", + ul = "ul", + em = "em", + strong = "strong", + br = "br", + span = "span", +} diff --git a/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx b/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx index 737dbb550..666603a56 100644 --- a/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx +++ b/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx @@ -26,13 +26,7 @@ export default async function MeetingsSidebar({ {intl.formatMessage({ id: "Contact us" })}
    - - {phoneNumber} - + {phoneNumber} {country === Country.Finland ? ( {intl.formatMessage({ @@ -40,7 +34,11 @@ export default async function MeetingsSidebar({ })} ) : null} - {email} + {email && ( + + {email} + + )}
    ) diff --git a/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx b/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx index 52fc4902b..659d92eff 100644 --- a/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx +++ b/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx @@ -40,11 +40,7 @@ export default async function ParkingSidebar({ hotel }: HotelSidebarProps) { {intl.formatMessage({ id: "Contact us" })} - + {hotel.contactInformation.phoneNumber} diff --git a/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar/RestaurantSidebar.tsx b/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar/RestaurantSidebar.tsx index d2fe8be1b..99b3c1585 100644 --- a/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar/RestaurantSidebar.tsx +++ b/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar/RestaurantSidebar.tsx @@ -26,46 +26,50 @@ export default async function RestaurantSidebar({ return ( diff --git a/components/ContentType/HotelSubpage/hotelSubpage.module.css b/components/ContentType/HotelSubpage/hotelSubpage.module.css index 1476a78a2..4f08446eb 100644 --- a/components/ContentType/HotelSubpage/hotelSubpage.module.css +++ b/components/ContentType/HotelSubpage/hotelSubpage.module.css @@ -13,6 +13,8 @@ width: 100%; max-width: var(--max-width-content); margin: 0 auto; + display: grid; + gap: var(--Spacing-x4); } .contentContainer { @@ -41,6 +43,12 @@ grid-column: 1; } +.meetingBookingWidget { + padding: var(--Spacing-x4); + background-color: var(--Base-Surface-Primary-dark-Normal); + text-align: center; +} + @media (min-width: 1367px) { .contentContainer { grid-template-columns: var(--max-width-text-block) 1fr; diff --git a/components/ContentType/HotelSubpage/index.tsx b/components/ContentType/HotelSubpage/index.tsx index d2eba9dd6..6d35aaa1d 100644 --- a/components/ContentType/HotelSubpage/index.tsx +++ b/components/ContentType/HotelSubpage/index.tsx @@ -56,6 +56,12 @@ export default async function HotelSubpage({ meetingRooms = await getMeetingRooms({ hotelId: hotelId, language: lang }) } + const meetingBookingWidget = meetingRooms ? ( +
    + Booking Widget Placeholder +
    + ) : null + return ( <>
    @@ -66,11 +72,18 @@ export default async function HotelSubpage({ subpageTitle={pageData.heading} /> - {pageData.heroImage && ( + + {pageData.heroImage || meetingBookingWidget ? (
    - + {meetingBookingWidget} + {pageData.heroImage && ( + + )}
    - )} + ) : null}
    diff --git a/package-lock.json b/package-lock.json index 1f4c22a9e..8ab75ef91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "graphql": "^16.8.1", "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", + "html-react-parser": "^5.2.2", "ics": "^3.8.1", "immer": "10.1.1", "input-otp": "^1.4.2", @@ -11958,11 +11959,11 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", + "dev":true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -14054,6 +14055,16 @@ "react-is": "^16.7.0" } }, + "node_modules/html-dom-parser": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.0.13.tgz", + "integrity": "sha512-B7JonBuAfG32I7fDouUQEogBrz3jK9gAuN1r1AaXpED6dIhtg/JwiSRhjGL7aOJwRz3HU4efowCjQBaoXiREqg==", + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "htmlparser2": "10.0.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -14072,6 +14083,58 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-react-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.2.2.tgz", + "integrity": "sha512-yA5012CJGSFWYZsgYzfr6HXJgDap38/AEP4ra8Cw+WHIi2ZRDXRX/QVYdumRf1P8zKyScKd6YOrWYvVEiPfGKg==", + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "html-dom-parser": "5.0.13", + "react-property": "2.0.2", + "style-to-js": "1.1.16" + }, + "peerDependencies": { + "@types/react": "0.14 || 15 || 16 || 17 || 18 || 19", + "react": "0.14 || 15 || 16 || 17 || 18 || 19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", @@ -14386,6 +14449,12 @@ "node": ">=10" } }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/input-otp": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", @@ -19891,6 +19960,12 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz", + "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", + "license": "MIT" + }, "node_modules/react-remove-scroll": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", @@ -21449,6 +21524,24 @@ "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==", "dev": true }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", diff --git a/package.json b/package.json index 6b31493f4..92c8e8fe9 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "graphql": "^16.8.1", "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", + "html-react-parser": "^5.2.2", "ics": "^3.8.1", "immer": "10.1.1", "input-otp": "^1.4.2",