diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/index.tsx
index 0738b7645..480a5f06b 100644
--- a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/index.tsx
+++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/index.tsx
@@ -1,18 +1,12 @@
import { parkingSubPage } from "@/constants/routes/hotelSubpages"
-import { OpenInNewIcon } from "@/components/Icons"
+import ParkingInformation from "@/components/ParkingInformation"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Button from "@/components/TempDesignSystem/Button"
-import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
-import Body from "@/components/TempDesignSystem/Text/Body"
-import Caption from "@/components/TempDesignSystem/Text/Caption"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
-import ParkingList from "./ParkingList"
-import ParkingPrices from "./ParkingPrices"
-
import styles from "./parkingAmenity.module.css"
import type { ParkingAmenityProps } from "@/types/components/hotelPage/sidepeek/parking"
@@ -36,58 +30,7 @@ export default async function ParkingAmenity({
{parkingElevatorPitch}
{parking.map((data) => (
-
-
-
-
- {intl.formatMessage({ id: "Prices" })}
-
-
-
- {intl.formatMessage({ id: "Weekday prices" })}
-
-
-
-
-
-
- {intl.formatMessage({ id: "Weekend prices" })}
-
-
-
-
-
- {data.externalParkingUrl && (
-
-
- {intl.formatMessage({ id: "Book parking" })}
-
-
-
- )}
-
+
))}
{hasExtraParkingPage && (
+ {parking.map((data) => (
+
+ ))}
+
+ )
+}
diff --git a/components/ContentType/HotelSubpage/AdditionalContent/additionalContent.module.css b/components/ContentType/HotelSubpage/AdditionalContent/additionalContent.module.css
index e69de29bb..3011afbbc 100644
--- a/components/ContentType/HotelSubpage/AdditionalContent/additionalContent.module.css
+++ b/components/ContentType/HotelSubpage/AdditionalContent/additionalContent.module.css
@@ -0,0 +1,4 @@
+.additionalContent {
+ display: grid;
+ gap: var(--Spacing-x4);
+}
diff --git a/components/ContentType/HotelSubpage/AdditionalContent/index.tsx b/components/ContentType/HotelSubpage/AdditionalContent/index.tsx
index e1d859d67..ca86c1a2b 100644
--- a/components/ContentType/HotelSubpage/AdditionalContent/index.tsx
+++ b/components/ContentType/HotelSubpage/AdditionalContent/index.tsx
@@ -1,7 +1,12 @@
-import { wellnessSubPage } from "@/constants/routes/hotelSubpages"
+import {
+ parkingSubPage,
+ wellnessSubPage,
+} from "@/constants/routes/hotelSubpages"
import { getLang } from "@/i18n/serverContext"
+import ParkingAdditionalContent from "./ParkingAdditionalContent"
+
import type { Hotel } from "@/types/hotel"
interface HotelSubpageAdditionalContentProps {
@@ -16,6 +21,8 @@ export default function HotelSubpageAdditionalContent({
const lang = getLang()
switch (subpage) {
+ case parkingSubPage[lang]:
+ return
case wellnessSubPage[lang]:
return null
default:
diff --git a/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx b/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx
new file mode 100644
index 000000000..52fc4902b
--- /dev/null
+++ b/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx
@@ -0,0 +1,64 @@
+import NextLink from "next/link"
+
+import { OpenInNewIcon } from "@/components/Icons"
+import Button from "@/components/TempDesignSystem/Button"
+import Link from "@/components/TempDesignSystem/Link"
+import Body from "@/components/TempDesignSystem/Text/Body"
+import Title from "@/components/TempDesignSystem/Text/Title"
+import { getIntl } from "@/i18n"
+
+import styles from "./sidebar.module.css"
+
+import type { Hotel } from "@/types/hotel"
+
+interface HotelSidebarProps {
+ hotel: Hotel
+}
+
+export default async function ParkingSidebar({ hotel }: HotelSidebarProps) {
+ const intl = await getIntl()
+
+ const parking = hotel.parking
+ .map((parking) => ({ url: parking.externalParkingUrl, type: parking.type }))
+ .filter(
+ (parking): parking is { type: string; url: string } => !!parking.url
+ )
+
+ return (
+
+
+ {intl.formatMessage({ id: "Address" })}
+
+
+ {hotel.address.streetAddress}
+
+ {hotel.address.zipCode} {hotel.address.city}
+
+ {hotel.address.country}
+
+
+
+ {intl.formatMessage({ id: "Contact us" })}
+
+
+ {hotel.contactInformation.phoneNumber}
+
+
+ {parking.map(({ url, type }) => (
+
+
+ {intl.formatMessage(
+ { id: "Book {type} parking" },
+ { type: type.toLowerCase() }
+ )}
+
+
+
+ ))}
+
+ )
+}
diff --git a/components/ContentType/HotelSubpage/Sidebar/index.tsx b/components/ContentType/HotelSubpage/Sidebar/index.tsx
index 7608eeefc..acbbd9865 100644
--- a/components/ContentType/HotelSubpage/Sidebar/index.tsx
+++ b/components/ContentType/HotelSubpage/Sidebar/index.tsx
@@ -1,7 +1,11 @@
-import { wellnessSubPage } from "@/constants/routes/hotelSubpages"
+import {
+ parkingSubPage,
+ wellnessSubPage,
+} from "@/constants/routes/hotelSubpages"
import { getLang } from "@/i18n/serverContext"
+import ParkingSidebar from "./ParkingSidebar"
import WellnessSidebar from "./WellnessSidebar"
import type { Hotel } from "@/types/hotel"
@@ -17,6 +21,8 @@ export default function HotelSubpageSidebar({
}: HotelSubpageSidebarProps) {
const lang = getLang()
switch (subpage) {
+ case parkingSubPage[lang]:
+ return
case wellnessSubPage[lang]:
return
default:
diff --git a/components/ContentType/HotelSubpage/utils.ts b/components/ContentType/HotelSubpage/utils.ts
index 7bda855f4..30ffc4d75 100644
--- a/components/ContentType/HotelSubpage/utils.ts
+++ b/components/ContentType/HotelSubpage/utils.ts
@@ -1,4 +1,7 @@
-import { wellnessSubPage } from "@/constants/routes/hotelSubpages"
+import {
+ parkingSubPage,
+ wellnessSubPage,
+} from "@/constants/routes/hotelSubpages"
import { getLang } from "@/i18n/serverContext"
@@ -15,17 +18,29 @@ export function getSubpageData(
const additionalData = hotelData.additionalData
const hotel = hotelData.hotel
switch (subpage) {
+ case parkingSubPage[lang]:
+ const parkingImage = additionalData.parkingImages?.heroImages[0]
+ return {
+ ...additionalData.hotelParking,
+ heading: intl.formatMessage({ id: "Parking" }),
+ heroImage: parkingImage
+ ? {
+ src: parkingImage.imageSizes.medium,
+ alt: parkingImage.metaData.altText || "",
+ }
+ : null,
+ }
case wellnessSubPage[lang]:
- const heroImage = hotel.healthFacilities.find(
+ const wellnessImage = hotel.healthFacilities.find(
(fac) => fac.content.images.length
)?.content.images[0]
return {
...additionalData.healthAndFitness,
heading: intl.formatMessage({ id: "Wellness & Exercise" }),
- heroImage: heroImage
+ heroImage: wellnessImage
? {
- src: heroImage.imageSizes.medium,
- alt: heroImage.metaData.altText || "",
+ src: wellnessImage.imageSizes.medium,
+ alt: wellnessImage.metaData.altText || "",
}
: null,
}
diff --git a/components/ParkingInformation/ParkingList/index.tsx b/components/ParkingInformation/ParkingList/index.tsx
new file mode 100644
index 000000000..9bc1a30cf
--- /dev/null
+++ b/components/ParkingInformation/ParkingList/index.tsx
@@ -0,0 +1,64 @@
+import Body from "@/components/TempDesignSystem/Text/Body"
+import { getIntl } from "@/i18n"
+
+import styles from "./parkingList.module.css"
+
+import type { ParkingListProps } from "@/types/components/hotelPage/sidepeek/parking"
+
+export default async function ParkingList({
+ numberOfChargingSpaces,
+ canMakeReservation,
+ numberOfParkingSpots,
+ distanceToHotel,
+ address,
+}: ParkingListProps) {
+ const intl = await getIntl()
+
+ const canMakeReservationYesMsg = intl.formatMessage({
+ id: "Parking can be reserved in advance: Yes",
+ })
+ const canMakeReservationNoMsg = intl.formatMessage({
+ id: "Parking can be reserved in advance: No",
+ })
+
+ return (
+
+
+ {numberOfChargingSpaces ? (
+
+ {intl.formatMessage(
+ { id: "Number of charging points for electric cars: {number}" },
+ { number: numberOfChargingSpaces }
+ )}
+
+ ) : null}
+
+ {canMakeReservation
+ ? canMakeReservationYesMsg
+ : canMakeReservationNoMsg}
+
+ {numberOfParkingSpots ? (
+
+ {intl.formatMessage(
+ { id: "Number of parking spots: {number}" },
+ { number: numberOfParkingSpots }
+ )}
+
+ ) : null}
+ {distanceToHotel ? (
+
+ {intl.formatMessage(
+ { id: "Distance to hotel: {distanceInM} m" },
+ { distanceInM: distanceToHotel }
+ )}
+
+ ) : null}
+ {address ? (
+
+ {intl.formatMessage({ id: "Address: {address}" }, { address })}
+
+ ) : null}
+
+
+ )
+}
diff --git a/components/ParkingInformation/ParkingList/parkingList.module.css b/components/ParkingInformation/ParkingList/parkingList.module.css
new file mode 100644
index 000000000..6e837a4fd
--- /dev/null
+++ b/components/ParkingInformation/ParkingList/parkingList.module.css
@@ -0,0 +1,11 @@
+.listStyling {
+ list-style-type: none;
+}
+
+.listStyling > li::before {
+ content: url("/_static/icons/heart.svg");
+ position: relative;
+ height: 8px;
+ top: 3px;
+ margin-right: var(--Spacing-x1);
+}
diff --git a/components/ParkingInformation/ParkingPrices/index.tsx b/components/ParkingInformation/ParkingPrices/index.tsx
new file mode 100644
index 000000000..0302efdb0
--- /dev/null
+++ b/components/ParkingInformation/ParkingPrices/index.tsx
@@ -0,0 +1,69 @@
+import Body from "@/components/TempDesignSystem/Text/Body"
+import { getIntl } from "@/i18n"
+import { formatPrice } from "@/utils/numberFormatting"
+
+import styles from "./parkingPrices.module.css"
+
+import {
+ type ParkingPricesProps,
+ Periods,
+} from "@/types/components/hotelPage/sidepeek/parking"
+
+export default async function ParkingPrices({
+ currency = "",
+ freeParking,
+ pricing,
+}: ParkingPricesProps) {
+ const intl = await getIntl()
+ const day = intl.formatMessage({ id: "Price per day" })
+ const night = intl.formatMessage({ id: "Price per night" })
+ const allDay = intl.formatMessage({ id: "Price per 24 hours" })
+
+ function getPeriod(period?: string) {
+ switch (period) {
+ case Periods.day:
+ return day
+ case Periods.night:
+ return night
+ case Periods.allDay:
+ return allDay
+ default:
+ return period
+ }
+ }
+
+ const filteredPeriods = pricing?.filter((filter) => filter.period !== "Hour")
+
+ return (
+
+ {filteredPeriods?.map((parking) => (
+
+
+
+ {getPeriod(parking.period)}
+
+
+ {parking.amount
+ ? freeParking
+ ? intl.formatMessage({ id: "Free parking" })
+ : formatPrice(intl, parking.amount, currency)
+ : intl.formatMessage({ id: "N/A" })}
+
+
+ {parking.startTime &&
+ parking.endTime &&
+ parking.period !== Periods.allDay && (
+
+
+ {intl.formatMessage({ id: "From" })}
+
+
+ {`${parking.startTime}-${parking.endTime}`}
+
+
+ )}
+
+ ))}
+
+ )
+}
diff --git a/components/ParkingInformation/ParkingPrices/parkingPrices.module.css b/components/ParkingInformation/ParkingPrices/parkingPrices.module.css
new file mode 100644
index 000000000..436e883ec
--- /dev/null
+++ b/components/ParkingInformation/ParkingPrices/parkingPrices.module.css
@@ -0,0 +1,13 @@
+.wrapper {
+ display: grid;
+ row-gap: var(--Spacing-x1);
+}
+
+.period {
+ display: flex;
+ gap: var(--Spacing-x5);
+}
+
+.information {
+ flex: 1;
+}
diff --git a/components/ParkingInformation/index.tsx b/components/ParkingInformation/index.tsx
new file mode 100644
index 000000000..8f7b2a37e
--- /dev/null
+++ b/components/ParkingInformation/index.tsx
@@ -0,0 +1,78 @@
+import Link from "next/link"
+
+import { getIntl } from "@/i18n"
+
+import { OpenInNewIcon } from "../Icons"
+import Button from "../TempDesignSystem/Button"
+import Divider from "../TempDesignSystem/Divider"
+import Caption from "../TempDesignSystem/Text/Caption"
+import Subtitle from "../TempDesignSystem/Text/Subtitle"
+import ParkingList from "./ParkingList"
+import ParkingPrices from "./ParkingPrices"
+
+import styles from "./parkingInformation.module.css"
+
+import type { Parking } from "@/types/hotel"
+
+interface ParkingInformationProps {
+ parking: Parking
+ showExternalParkingButton?: boolean
+}
+
+export default async function ParkingInformation({
+ parking,
+ showExternalParkingButton = true,
+}: ParkingInformationProps) {
+ const intl = await getIntl()
+ return (
+
+
+
+
+ {intl.formatMessage({ id: "Prices" })}
+
+
+
+ {intl.formatMessage({ id: "Weekday prices" })}
+
+
+
+
+
+
+ {intl.formatMessage({ id: "Weekend prices" })}
+
+
+
+
+
+ {parking.externalParkingUrl && showExternalParkingButton && (
+
+
+ {intl.formatMessage({ id: "Book parking" })}
+
+
+
+ )}
+
+ )
+}
diff --git a/components/ParkingInformation/parkingInformation.module.css b/components/ParkingInformation/parkingInformation.module.css
new file mode 100644
index 000000000..a6549552a
--- /dev/null
+++ b/components/ParkingInformation/parkingInformation.module.css
@@ -0,0 +1,18 @@
+.parkingInformation {
+ display: grid;
+ gap: var(--Spacing-x3);
+}
+
+.list,
+.prices {
+ display: grid;
+ gap: var(--Spacing-x-one-and-half);
+}
+
+.priceWrapper {
+ background-color: var(--Base-Surface-Subtle-Normal);
+ border-radius: var(--Corner-radius-Medium);
+ padding: var(--Spacing-x2) var(--Spacing-x3);
+ display: grid;
+ gap: var(--Spacing-x1);
+}
diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json
index 306f30c13..6e7cb554e 100644
--- a/i18n/dictionaries/da.json
+++ b/i18n/dictionaries/da.json
@@ -60,6 +60,7 @@
"Book a table online": "Book et bord online",
"Book parking": "Book parkering",
"Book reward night": "Book bonusnat",
+ "Book {type} parking": "Book {type} parkering",
"Booking confirmation": "Booking bekræftelse",
"Booking number": "Bookingnummer",
"Breakfast": "Morgenmad",
diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json
index ed0f928e2..421909a98 100644
--- a/i18n/dictionaries/de.json
+++ b/i18n/dictionaries/de.json
@@ -60,6 +60,7 @@
"Book a table online": "Tisch online buchen",
"Book parking": "Parkplatz buchen",
"Book reward night": "Bonusnacht buchen",
+ "Book {type} parking": "Buchen Sie {type} Parkplatz",
"Booking confirmation": "Buchungsbestätigung",
"Booking number": "Buchungsnummer",
"Breakfast": "Frühstück",
diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json
index f40723212..8d85eecf2 100644
--- a/i18n/dictionaries/en.json
+++ b/i18n/dictionaries/en.json
@@ -63,6 +63,7 @@
"Book parking": "Book parking",
"Book reward night": "Book reward night",
"Book your next stay": "Book your next stay",
+ "Book {type} parking": "Book {type} parking",
"Booking": "Booking",
"Booking confirmation": "Booking confirmation",
"Booking number": "Booking number",
diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json
index 74dd19e36..476968158 100644
--- a/i18n/dictionaries/fi.json
+++ b/i18n/dictionaries/fi.json
@@ -60,6 +60,7 @@
"Book a table online": "Varaa pöytä verkossa",
"Book parking": "Varaa pysäköinti",
"Book reward night": "Kirjapalkinto-ilta",
+ "Book {type} parking": "Varaa {type} pysäköinti",
"Booking confirmation": "Varausvahvistus",
"Booking number": "Varausnumero",
"Breakfast": "Aamiainen",
diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json
index 4ac8c63c4..248fdf18d 100644
--- a/i18n/dictionaries/no.json
+++ b/i18n/dictionaries/no.json
@@ -60,6 +60,7 @@
"Book a table online": "Bestill bord online",
"Book parking": "Bestill parkering",
"Book reward night": "Bestill belønningskveld",
+ "Book {type} parking": "Book {type} parkering",
"Booking confirmation": "Bestillingsbekreftelse",
"Booking number": "Bestillingsnummer",
"Breakfast": "Frokost",
diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json
index 4c214b8c6..db89a7482 100644
--- a/i18n/dictionaries/sv.json
+++ b/i18n/dictionaries/sv.json
@@ -60,6 +60,7 @@
"Book a table online": "Boka ett bord online",
"Book parking": "Boka parkering",
"Book reward night": "Boka frinatt",
+ "Book {type} parking": "Boka {type} parkering",
"Booking confirmation": "Bokningsbekräftelse",
"Booking number": "Bokningsnummer",
"Breakfast": "Frukost",
diff --git a/server/routers/hotels/schemas/additionalData.ts b/server/routers/hotels/schemas/additionalData.ts
index 28602b9fe..b2b3ecc53 100644
--- a/server/routers/hotels/schemas/additionalData.ts
+++ b/server/routers/hotels/schemas/additionalData.ts
@@ -49,6 +49,7 @@ export const additionalDataSchema = z.object({
conferencesAndMeetings: facilitySchema.optional(),
healthAndWellness: facilitySchema.optional(),
restaurantImages: facilitySchema.optional(),
+ parkingImages: facilitySchema.optional(),
restaurantsOverviewPage: restaurantsOverviewPageSchema,
meetingRooms: extraPageSchema,
healthAndFitness: extraPageSchema,