diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx
index bba0ce99d..fb17cb2a5 100644
--- a/components/HotelReservation/HotelCardListing/index.tsx
+++ b/components/HotelReservation/HotelCardListing/index.tsx
@@ -1,5 +1,3 @@
-"use client"
-
import Title from "@/components/TempDesignSystem/Text/Title"
import HotelCard from "../HotelCard"
diff --git a/components/HotelReservation/ReadMore/Contact/contact.module.css b/components/HotelReservation/ReadMore/Contact/contact.module.css
new file mode 100644
index 000000000..53f6e01f3
--- /dev/null
+++ b/components/HotelReservation/ReadMore/Contact/contact.module.css
@@ -0,0 +1,48 @@
+.wrapper {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: auto;
+ gap: var(--Spacing-x2);
+ font-family: var(--typography-Body-Regular-fontFamily);
+}
+
+.address,
+.contactInfo {
+ display: grid;
+ grid-template-columns: subgrid;
+ grid-template-rows: subgrid;
+ grid-column: 1 / 3;
+ grid-row: 1 / 4;
+}
+
+.contactInfo > li {
+ font-style: normal;
+ list-style-type: none;
+ display: flex;
+ flex-direction: column;
+}
+
+.heading {
+ font-weight: 500;
+}
+
+.soMeIcons {
+ display: flex;
+ gap: var(--Spacing-x-one-and-half);
+}
+
+.ecoLabel {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: var(--Spacing-x-one-and-half);
+ grid-column: 2 / 3;
+ grid-row: 3 / 4;
+ font-size: var(--typography-Footnote-Regular-fontSize);
+ line-height: ();
+}
+
+.ecoLabelText {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
diff --git a/components/HotelReservation/ReadMore/Contact/index.tsx b/components/HotelReservation/ReadMore/Contact/index.tsx
new file mode 100644
index 000000000..46491fb3d
--- /dev/null
+++ b/components/HotelReservation/ReadMore/Contact/index.tsx
@@ -0,0 +1,85 @@
+"use client"
+
+import { useIntl } from "react-intl"
+
+import FacebookIcon from "@/components/Icons/Facebook"
+import InstagramIcon from "@/components/Icons/Instagram"
+import Image from "@/components/Image"
+import Link from "@/components/TempDesignSystem/Link"
+import useLang from "@/hooks/useLang"
+
+import styles from "./contact.module.css"
+
+import { ContactProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
+
+export default function Contact({ hotel }: ContactProps) {
+ const lang = useLang()
+ const intl = useIntl()
+
+ return (
+
+
+
+ -
+
+ {intl.formatMessage({ id: "Address" })}
+
+ {hotel.address.streetAddress}
+ {hotel.address.city}
+
+ -
+
+ {intl.formatMessage({ id: "Driving directions" })}
+
+ {intl.formatMessage({ id: "Google Maps" })}
+
+ -
+
+ {intl.formatMessage({ id: "Email" })}
+
+
+ {hotel.contactInformation.email}
+
+
+ -
+
+ {intl.formatMessage({ id: "Contact us" })}
+
+
+ {hotel.contactInformation.phoneNumber}
+
+
+ -
+
+ {intl.formatMessage({ id: "Follow us" })}
+
+
+
+
+
+
+
+
+
+
+
+
+ {hotel.hotelFacts.ecoLabels.nordicEcoLabel ? (
+
+
+
+ {intl.formatMessage({ id: "Nordic Swan Ecolabel" })}
+
+ {hotel.hotelFacts.ecoLabels.svanenEcoLabelCertificateNumber}
+
+
+
+ ) : null}
+
+ )
+}
diff --git a/components/HotelReservation/ReadMore/index.tsx b/components/HotelReservation/ReadMore/index.tsx
new file mode 100644
index 000000000..e988ec40d
--- /dev/null
+++ b/components/HotelReservation/ReadMore/index.tsx
@@ -0,0 +1,119 @@
+"use client"
+
+import { useState } from "react"
+import { useIntl } from "react-intl"
+
+import { ChevronRightIcon } from "@/components/Icons"
+import Accordion from "@/components/TempDesignSystem/Accordion"
+import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
+import Button from "@/components/TempDesignSystem/Button"
+import SidePeek from "@/components/TempDesignSystem/SidePeek"
+import Body from "@/components/TempDesignSystem/Text/Body"
+import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
+
+import Contact from "./Contact"
+
+import styles from "./readMore.module.css"
+
+import {
+ DetailedAmenity,
+ ParkingProps,
+ ReadMoreProps,
+} from "@/types/components/hotelReservation/selectHotel/selectHotel"
+import { Hotel } from "@/types/hotel"
+
+function getAmenitiesList(hotel: Hotel) {
+ const detailedAmenities: DetailedAmenity[] = Object.entries(
+ hotel.hotelFacts.hotelFacilityDetail
+ ).map(([key, value]) => ({ name: key, ...value }))
+
+ // Remove Parking facilities since parking accordion is based on hotel.parking
+ const simpleAmenities = hotel.detailedFacilities.filter(
+ (facility) => !facility.name.startsWith("Parking")
+ )
+ return [...detailedAmenities, ...simpleAmenities]
+}
+
+export default function ReadMore({ hotel, hotelId }: ReadMoreProps) {
+ const intl = useIntl()
+
+ const [sidePeekOpen, setSidePeekOpen] = useState(false)
+
+ const amenitiesList = getAmenitiesList(hotel)
+ return (
+ <>
+
+
{
+ setSidePeekOpen(false)
+ }}
+ >
+
+
+ {intl.formatMessage({ id: "Practical information" })}
+
+
+
+ {/* parking */}
+ {hotel.parking.length ? (
+
+ {hotel.parking.map((p) => (
+
+ ))}
+
+ ) : null}
+
+ TODO: What content should be in the accessibility section?
+
+ {amenitiesList.map((amenity) => {
+ return "description" in amenity ? (
+
+ {amenity.description}
+
+ ) : (
+
+ {amenity.name}
+
+ )
+ })}
+
+ {/* TODO: handle linking to Hotel Page */}
+
+
+
+ >
+ )
+}
+
+function Parking({ parking }: ParkingProps) {
+ const intl = useIntl()
+ return (
+
+ {`${intl.formatMessage({ id: parking.type })} (${parking.name})`}
+
+ -
+ {`${intl.formatMessage({
+ id: "Number of charging points for electric cars",
+ })}: ${parking.numberOfChargingSpaces}`}
+
+ - {`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${parking.canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}
+ - {`${intl.formatMessage({ id: "Number of parking spots" })}: ${parking.numberOfParkingSpots}`}
+ - {`${intl.formatMessage({ id: "Distance to hotel" })}: ${parking.distanceToHotel}`}
+ - {`${intl.formatMessage({ id: "Address" })}: ${parking.address}`}
+
+
+ )
+}
diff --git a/components/HotelReservation/ReadMore/readMore.module.css b/components/HotelReservation/ReadMore/readMore.module.css
new file mode 100644
index 000000000..f7859a8fc
--- /dev/null
+++ b/components/HotelReservation/ReadMore/readMore.module.css
@@ -0,0 +1,25 @@
+.detailsButton {
+ align-self: start;
+ border-radius: 0;
+ height: auto;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.content {
+ display: grid;
+ gap: var(--Spacing-x2);
+}
+
+.amenity {
+ font-family: var(--typography-Body-Regular-fontFamily);
+ border-bottom: 1px solid var(--Base-Border-Subtle);
+ /* padding set to align with AccordionItem which has a different composition */
+ padding: var(--Spacing-x2)
+ calc(var(--Spacing-x1) + var(--Spacing-x-one-and-half));
+}
+
+.list {
+ font-family: var(--typography-Body-Regular-fontFamily);
+ list-style: inside;
+}
diff --git a/components/TempDesignSystem/Link/index.tsx b/components/TempDesignSystem/Link/index.tsx
index 2301ce932..771c60e82 100644
--- a/components/TempDesignSystem/Link/index.tsx
+++ b/components/TempDesignSystem/Link/index.tsx
@@ -82,6 +82,11 @@ export default function Link({
// track navigation nor start a router transition.
return
}
+ if (href.startsWith("tel:") || href.startsWith("mailto:")) {
+ // If href contains tel or mailto protocols we don't want to
+ // track navigation nor start a router transition.
+ return
+ }
e.preventDefault()
trackPageViewStart()
startTransition(() => {
diff --git a/components/TempDesignSystem/SidePeek/index.tsx b/components/TempDesignSystem/SidePeek/index.tsx
index df0b8dc44..6df535b54 100644
--- a/components/TempDesignSystem/SidePeek/index.tsx
+++ b/components/TempDesignSystem/SidePeek/index.tsx
@@ -1,7 +1,7 @@
"use client"
import { useIsSSR } from "@react-aria/ssr"
-import { useContext } from "react"
+import { useContext, useState } from "react"
import {
Dialog,
DialogTrigger,
@@ -29,6 +29,15 @@ function SidePeek({
}: React.PropsWithChildren
) {
const isSSR = useIsSSR()
const intl = useIntl()
+
+ const [rootDiv, setRootDiv] = useState(undefined)
+
+ function setRef(node: HTMLDivElement | null) {
+ if (node) {
+ setRootDiv(node)
+ }
+ }
+
const context = useContext(SidePeekContext)
function onClose() {
const closeHandler = handleClose || context?.handleClose
@@ -44,42 +53,45 @@ function SidePeek({
)
}
return (
-
-
-
-
-
-
-
+
+
+
+ {children}
+
+
+
+
+
+
)
}
diff --git a/components/TempDesignSystem/SidePeek/sidePeek.module.css b/components/TempDesignSystem/SidePeek/sidePeek.module.css
index 2d6de4f00..65ad886b1 100644
--- a/components/TempDesignSystem/SidePeek/sidePeek.module.css
+++ b/components/TempDesignSystem/SidePeek/sidePeek.module.css
@@ -78,6 +78,7 @@
.sidePeekContent {
padding: var(--Spacing-x4);
+ overflow-y: auto;
}
@media screen and (min-width: 1367px) {
.modal {
@@ -94,8 +95,4 @@
.modal[data-exiting] {
animation: slide-in 250ms reverse;
}
-
- .overlay {
- top: 0;
- }
}
diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts
index 7f57ba6cd..d0d95903a 100644
--- a/server/routers/hotels/output.ts
+++ b/server/routers/hotels/output.ts
@@ -298,7 +298,7 @@ const parkingPricingSchema = z.object({
.optional(),
})
-const parkingSchema = z.object({
+export const parkingSchema = z.object({
type: z.string(),
name: z.string(),
address: z.string().optional(),
diff --git a/types/components/hotelReservation/selectHotel/selectHotel.ts b/types/components/hotelReservation/selectHotel/selectHotel.ts
index d4c40ac56..6d1cecfca 100644
--- a/types/components/hotelReservation/selectHotel/selectHotel.ts
+++ b/types/components/hotelReservation/selectHotel/selectHotel.ts
@@ -1,4 +1,25 @@
+import { Hotel, ParkingData } from "@/types/hotel"
+
export enum AvailabilityEnum {
Available = "Available",
NotAvailable = "NotAvailable",
}
+
+export interface DetailedAmenity {
+ name: string
+ heading: string
+ description: string
+}
+
+export interface ReadMoreProps {
+ hotelId: string
+ hotel: Hotel
+}
+
+export interface ContactProps {
+ hotel: Hotel
+}
+
+export interface ParkingProps {
+ parking: ParkingData
+}
diff --git a/types/hotel.ts b/types/hotel.ts
index 54d9c8125..f4436fb6c 100644
--- a/types/hotel.ts
+++ b/types/hotel.ts
@@ -2,6 +2,7 @@ import { z } from "zod"
import {
getHotelDataSchema,
+ parkingSchema,
pointOfInterestSchema,
roomSchema,
} from "@/server/routers/hotels/output"
@@ -49,3 +50,5 @@ export enum PointOfInterestGroupEnum {
PARKING = "Parking",
SHOPPING_DINING = "Shopping & Dining",
}
+
+export type ParkingData = z.infer