fix(SW-1241): Adjusted amenities sidepeek on hotel pages and booking flow

Approved-by: Michael Zetterberg
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-04-23 08:41:04 +00:00
parent c23a32cd10
commit 8152aea649
46 changed files with 654 additions and 731 deletions

View File

@@ -1,5 +0,0 @@
.wrapper {
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
}

View File

@@ -1,46 +0,0 @@
import { IconName } from "@/components/Icons/iconName"
import OpeningHours from "@/components/OpeningHours"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Body from "@/components/TempDesignSystem/Text/Body"
import { getIntl } from "@/i18n"
import type { BreakfastAmenityProps } from "@/types/components/hotelPage/sidepeek/amenities"
import { HotelTypeEnum } from "@/types/enums/hotelType"
export default async function BreakfastAmenity({
openingHours,
alternateOpeningHours,
hotelType,
}: BreakfastAmenityProps) {
const intl = await getIntl()
const accordionContent =
hotelType === HotelTypeEnum.ScandicGo ? (
<Body>
{intl.formatMessage({
defaultMessage: "All-day breakfast",
})}
</Body>
) : (
<OpeningHours
openingHours={openingHours!}
alternateOpeningHours={alternateOpeningHours!}
heading={intl.formatMessage({
defaultMessage: "Opening hours",
})}
/>
)
return (
<AccordionItem
title={intl.formatMessage({
defaultMessage: "Breakfast",
})}
iconName={IconName.CoffeeAlt}
variant="sidepeek"
trackingId="amenities:breakfast"
>
{accordionContent}
</AccordionItem>
)
}

View File

@@ -1,47 +0,0 @@
import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Body from "@/components/TempDesignSystem/Text/Body"
import { getIntl } from "@/i18n"
import type { CheckInAmenityProps } from "@/types/components/hotelPage/sidepeek/checkIn"
export default async function CheckInAmenity({
checkInInformation,
}: CheckInAmenityProps) {
const intl = await getIntl()
const { checkInTime, checkOutTime } = checkInInformation
return (
<AccordionItem
title={`${intl.formatMessage({
defaultMessage: "Check-in",
})}/${intl.formatMessage({
defaultMessage: "Check-out",
})}`}
iconName={IconName.Business}
variant="sidepeek"
trackingId="amenities:check-in"
>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Times",
})}
</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage(
{
defaultMessage: "Check in from: {checkInTime}",
},
{ checkInTime }
)}
</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage(
{
defaultMessage: "Check out at latest: {checkOutTime}",
},
{ checkOutTime }
)}
</Body>
</AccordionItem>
)
}

View File

@@ -1,4 +0,0 @@
.wrapper {
display: grid;
gap: var(--Spacing-x3);
}

View File

@@ -1,4 +0,0 @@
export { default as AccessibilityAmenity } from "./Accessibility"
export { default as BreakfastAmenity } from "./Breakfast"
export { default as CheckInAmenity } from "./CheckIn"
export { default as ParkingAmenity } from "./Parking"

View File

@@ -1,44 +0,0 @@
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { FacilityToIcon } from "../../../data"
import styles from "./filteredAmenities.module.css"
import type { FilteredAmenitiesProps } from "@/types/components/hotelPage/sidepeek/amenities"
export default function FilteredAmenities({
filteredAmenities,
}: FilteredAmenitiesProps) {
return (
<>
{filteredAmenities?.map((amenity) => {
const Icon = (
<FacilityToIcon
id={amenity.id}
color="Icon/Interactive/Default"
size={24}
/>
)
return (
<li key={amenity.name} className={styles.wrapper}>
<div className={styles.amenity}>
{Icon ? (
Icon
) : (
<MaterialIcon
icon="favorite"
color="Icon/Interactive/Default"
/>
)}
<Subtitle color="burgundy" type="two">
{amenity.name}
</Subtitle>
</div>
</li>
)
})}
</>
)
}

View File

@@ -1,19 +1,14 @@
import AccessibilityAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/Accessibility"
import BreakfastAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/Breakfast"
import CheckInCheckOutAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/CheckInCheckOut"
import ParkingAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/Parking"
import AdditionalAmenities from "@/components/SidePeeks/AmenitiesSidepeekContent/AdditionalAmenities"
import Accordion from "@/components/TempDesignSystem/Accordion" import Accordion from "@/components/TempDesignSystem/Accordion"
import SidePeek from "@/components/TempDesignSystem/SidePeek" import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import {
AccessibilityAmenity,
BreakfastAmenity,
CheckInAmenity,
ParkingAmenity,
} from "./AccordionAmenities"
import FilteredAmenities from "./FilteredAmenities"
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage" import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage"
import type { AmenitiesSidePeekProps } from "@/types/components/hotelPage/sidepeek/amenities" import type { AmenitiesSidePeekProps } from "@/types/components/hotelPage/sidepeek/amenities"
import { FacilityEnum } from "@/types/enums/facilities"
import { HotelTypeEnum } from "@/types/enums/hotelType"
export default async function AmenitiesSidePeek({ export default async function AmenitiesSidePeek({
amenitiesList, amenitiesList,
@@ -25,35 +20,6 @@ export default async function AmenitiesSidePeek({
}: AmenitiesSidePeekProps) { }: AmenitiesSidePeekProps) {
const intl = await getIntl() const intl = await getIntl()
const amenitiesToRemove = [
FacilityEnum.ParkingAdditionalCost,
FacilityEnum.ParkingElectricCharging,
FacilityEnum.ParkingFreeParking,
FacilityEnum.ParkingGarage,
FacilityEnum.ParkingOutdoor,
FacilityEnum.MeetingArea,
FacilityEnum.ServesBreakfastAlwaysIncluded,
FacilityEnum.LateCheckOutUntil1400Guaranteed,
]
const filteredAmenities = amenitiesList.filter(
(amenity) => !amenitiesToRemove.includes(amenity.id)
)
const breakfastOpeningHours = restaurants
?.map((restaurant) => {
const breakfastDetail = restaurant.openingDetails.find(
(details) =>
details.openingHours.name === "Breakfast" ||
details.openingHours.name ===
intl.formatMessage({
defaultMessage: "Breakfast",
})
)
return breakfastDetail
})
.filter(Boolean)[0]
return ( return (
<SidePeek <SidePeek
contentKey={SidepeekSlugs.amenities} contentKey={SidepeekSlugs.amenities}
@@ -62,27 +28,21 @@ export default async function AmenitiesSidePeek({
})} })}
> >
<Accordion> <Accordion>
<ParkingAmenity <ParkingAccordionItem
parking={parking.parking} parking={parking.parking}
parkingElevatorPitch={parking.parkingElevatorPitch} elevatorPitch={parking.parkingElevatorPitch}
parkingPageUrl={parking.parkingPageUrl} parkingPageUrl={parking.parkingPageUrl}
/> />
{(breakfastOpeningHours || hotelType === HotelTypeEnum.ScandicGo) && ( <BreakfastAccordionItem
<BreakfastAmenity restaurants={restaurants}
openingHours={breakfastOpeningHours?.openingHours} hotelType={hotelType}
alternateOpeningHours={breakfastOpeningHours?.alternateOpeningHours} />
hotelType={hotelType} <CheckInCheckOutAccordionItem checkInData={checkInInformation} />
/> <AccessibilityAccordionItem
)} accessibilityPageUrl={accessibility.accessibilityPageUrl}
<CheckInAmenity checkInInformation={checkInInformation} /> elevatorPitch={accessibility.elevatorPitch}
{(accessibility.elevatorPitch || />
accessibility.accessibilityPageUrl) && ( <AdditionalAmenities amenities={amenitiesList} />
<AccessibilityAmenity
accessibilityPageUrl={accessibility.accessibilityPageUrl}
elevatorPitch={accessibility.elevatorPitch}
/>
)}
<FilteredAmenities filteredAmenities={filteredAmenities} />
</Accordion> </Accordion>
</SidePeek> </SidePeek>
) )

View File

@@ -38,6 +38,7 @@ import styles from "./hotelCard.module.css"
import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps" import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps"
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
import { RateTypeEnum } from "@/types/enums/rateType" import { RateTypeEnum } from "@/types/enums/rateType"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
@@ -173,6 +174,7 @@ function HotelCard({
hotelId={hotel.operaId} hotelId={hotel.operaId}
hotel={hotel} hotel={hotel}
showCTA={true} showCTA={true}
sidePeekKey={SidePeekEnum.hotelDetails}
/> />
</section> </section>
<div className={styles.prices}> <div className={styles.prices}>

View File

@@ -1,30 +1,29 @@
"use client" "use client"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import useSidePeekStore from "@/stores/sidepeek" import useSidePeekStore from "@/stores/sidepeek"
import Button from "@/components/TempDesignSystem/Button"
import styles from "./readMore.module.css"
import type { ReadMoreProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" import type { ReadMoreProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
export default function ReadMore({ label, hotelId, showCTA }: ReadMoreProps) { export default function ReadMore({
label,
hotelId,
showCTA,
sidePeekKey,
}: ReadMoreProps) {
const openSidePeek = useSidePeekStore((state) => state.openSidePeek) const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
return ( return (
<Button <Button
onPress={() => { onPress={() => {
openSidePeek({ key: SidePeekEnum.hotelDetails, hotelId, showCTA }) openSidePeek({ key: sidePeekKey, hotelId, showCTA })
}} }}
intent="text" variant="Text"
theme="base" typography="Body/Paragraph/mdBold"
wrapping
className={styles.detailsButton}
> >
{label} {label}
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" /> <MaterialIcon icon="chevron_right" size={24} color="CurrentColor" />
</Button> </Button>
) )
} }

View File

@@ -1,25 +0,0 @@
.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;
}

View File

@@ -16,6 +16,7 @@ import TripAdvisorChip from "../../TripAdvisorChip"
import styles from "./hotelInfoCard.module.css" import styles from "./hotelInfoCard.module.css"
import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCard" import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCard"
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
export default async function HotelInfoCard({ hotel }: HotelInfoCardProps) { export default async function HotelInfoCard({ hotel }: HotelInfoCardProps) {
const intl = await getIntl() const intl = await getIntl()
@@ -88,6 +89,7 @@ export default async function HotelInfoCard({ hotel }: HotelInfoCardProps) {
hotelId={hotel.operaId} hotelId={hotel.operaId}
hotel={hotel} hotel={hotel}
showCTA={false} showCTA={false}
sidePeekKey={SidePeekEnum.amenities}
/> />
</div> </div>
</div> </div>

View File

@@ -3,6 +3,7 @@
import { trpc } from "@/lib/trpc/client" import { trpc } from "@/lib/trpc/client"
import useSidePeekStore from "@/stores/sidepeek" import useSidePeekStore from "@/stores/sidepeek"
import AmenitiesSidePeek from "@/components/SidePeeks/AmenitiesSidePeek"
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek" import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
import HotelSidePeek from "@/components/SidePeeks/HotelSidePeek" import HotelSidePeek from "@/components/SidePeeks/HotelSidePeek"
import RoomSidePeek from "@/components/SidePeeks/RoomSidePeek" import RoomSidePeek from "@/components/SidePeeks/RoomSidePeek"
@@ -39,13 +40,23 @@ export default function HotelReservationSidePeek() {
return ( return (
<> <>
{hotelData && ( {hotelData && (
<HotelSidePeek <>
additionalHotelData={hotelData.additionalData} <HotelSidePeek
hotel={hotelData.hotel} additionalHotelData={hotelData.additionalData}
activeSidePeek={activeSidePeek} hotel={hotelData.hotel}
close={close} restaurants={hotelData.restaurants}
showCTA={showCTA} activeSidePeek={activeSidePeek}
/> close={close}
showCTA={showCTA}
/>
<AmenitiesSidePeek
hotel={hotelData.hotel}
restaurants={hotelData.restaurants}
additionalHotelData={hotelData.additionalData}
activeSidePeek={activeSidePeek}
close={close}
/>
</>
)} )}
{selectedRoom && ( {selectedRoom && (
<RoomSidePeek <RoomSidePeek

View File

@@ -1,7 +1,10 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import Divider from "@/components/TempDesignSystem/Divider" import Divider from "@/components/TempDesignSystem/Divider"
import { getIntl } from "@/i18n"
import { getGroupedOpeningHours } from "../utils" import { getGroupedOpeningHours } from "../utils"
@@ -13,10 +16,10 @@ interface AlternateOpeningHoursProps {
alternateOpeningHours: RestaurantOpeningHours alternateOpeningHours: RestaurantOpeningHours
} }
export default async function AlternateOpeningHours({ export default function AlternateOpeningHours({
alternateOpeningHours, alternateOpeningHours,
}: AlternateOpeningHoursProps) { }: AlternateOpeningHoursProps) {
const intl = await getIntl() const intl = useIntl()
const groupedAlternateOpeningHours = alternateOpeningHours const groupedAlternateOpeningHours = alternateOpeningHours
? getGroupedOpeningHours(alternateOpeningHours, intl) ? getGroupedOpeningHours(alternateOpeningHours, intl)
: null : null

View File

@@ -1,6 +1,8 @@
import { Typography } from "@scandic-hotels/design-system/Typography" "use client"
import { getIntl } from "@/i18n" import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import Divider from "../TempDesignSystem/Divider" import Divider from "../TempDesignSystem/Divider"
import AlternateOpeningHours from "./AlternateOpeningHours" import AlternateOpeningHours from "./AlternateOpeningHours"
@@ -8,14 +10,20 @@ import { getGroupedOpeningHours } from "./utils"
import styles from "./openingHours.module.css" import styles from "./openingHours.module.css"
import type { OpeningHoursProps } from "@/types/components/hotelPage/sidepeek/openingHours" import type { RestaurantOpeningHours } from "@/types/hotel"
export default async function OpeningHours({ interface OpeningHoursProps {
openingHours: RestaurantOpeningHours
alternateOpeningHours?: RestaurantOpeningHours
heading?: string
}
export default function OpeningHours({
openingHours, openingHours,
alternateOpeningHours, alternateOpeningHours,
heading, heading,
}: OpeningHoursProps) { }: OpeningHoursProps) {
const intl = await getIntl() const intl = useIntl()
const groupedOpeningHours = getGroupedOpeningHours(openingHours, intl) const groupedOpeningHours = getGroupedOpeningHours(openingHours, intl)
return ( return (

View File

@@ -1,18 +1,21 @@
import Body from "@/components/TempDesignSystem/Text/Body" "use client"
import { getIntl } from "@/i18n"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./parkingList.module.css" import styles from "./parkingList.module.css"
import type { ParkingListProps } from "@/types/components/hotelPage/sidepeek/parking" import type { ParkingListProps } from "@/types/components/hotelPage/sidepeek/parking"
export default async function ParkingList({ export default function ParkingList({
numberOfChargingSpaces, numberOfChargingSpaces,
canMakeReservation, canMakeReservation,
numberOfParkingSpots, numberOfParkingSpots,
distanceToHotel, distanceToHotel,
address, address,
}: ParkingListProps) { }: ParkingListProps) {
const intl = await getIntl() const intl = useIntl()
const canMakeReservationYesMsg = intl.formatMessage({ const canMakeReservationYesMsg = intl.formatMessage({
defaultMessage: "Parking can be reserved in advance: Yes", defaultMessage: "Parking can be reserved in advance: Yes",
@@ -22,7 +25,7 @@ export default async function ParkingList({
}) })
return ( return (
<Body color="uiTextHighContrast" asChild> <Typography variant="Body/Paragraph/mdRegular">
<ul className={styles.listStyling}> <ul className={styles.listStyling}>
{numberOfChargingSpaces ? ( {numberOfChargingSpaces ? (
<li> <li>
@@ -71,6 +74,6 @@ export default async function ParkingList({
</li> </li>
) : null} ) : null}
</ul> </ul>
</Body> </Typography>
) )
} }

View File

@@ -1,5 +1,9 @@
import Body from "@/components/TempDesignSystem/Text/Body" "use client"
import { getIntl } from "@/i18n"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { formatPrice } from "@/utils/numberFormatting" import { formatPrice } from "@/utils/numberFormatting"
import { getPeriod } from "./utils" import { getPeriod } from "./utils"
@@ -11,28 +15,28 @@ import {
Periods, Periods,
} from "@/types/components/hotelPage/sidepeek/parking" } from "@/types/components/hotelPage/sidepeek/parking"
export default async function ParkingPrices({ export default function ParkingPrices({
currency = "", currency = "",
freeParking, freeParking,
pricing, pricing,
}: ParkingPricesProps) { }: ParkingPricesProps) {
const intl = await getIntl() const intl = useIntl()
return freeParking ? ( return freeParking ? (
<Body textTransform="bold" color="uiTextHighContrast"> <Typography variant="Body/Paragraph/mdBold">
{intl.formatMessage({ <p className={styles.wrapper}>
defaultMessage: "Free parking", {intl.formatMessage({ defaultMessage: "Free parking" })}
})} </p>
</Body> </Typography>
) : ( ) : (
<dl className={styles.wrapper}> <dl className={styles.wrapper}>
{pricing?.map((parking) => ( {pricing?.map((parking) => (
<div key={parking.period} className={styles.period}> <div key={parking.period} className={styles.period}>
<div className={styles.information}> <div className={styles.information}>
<Body textTransform="bold" color="uiTextHighContrast" asChild> <Typography variant="Body/Paragraph/mdBold">
<dt>{getPeriod(intl, parking.period)}</dt> <dt>{getPeriod(intl, parking.period)}</dt>
</Body> </Typography>
<Body color="uiTextHighContrast" asChild> <Typography variant="Body/Paragraph/mdRegular">
<dd> <dd>
{parking.amount {parking.amount
? formatPrice(intl, parking.amount, currency) ? formatPrice(intl, parking.amount, currency)
@@ -40,24 +44,17 @@ export default async function ParkingPrices({
defaultMessage: "At a cost", defaultMessage: "At a cost",
})} })}
</dd> </dd>
</Body> </Typography>
</div> </div>
{parking.startTime && {parking.startTime &&
parking.endTime && parking.endTime &&
parking.period !== Periods.allDay && ( parking.period !== Periods.allDay && (
<div className={styles.information}> <Typography variant="Body/Paragraph/mdRegular">
<Body color="uiTextHighContrast" asChild> <div className={styles.information}>
<dt> <dt>{intl.formatMessage({ defaultMessage: "From" })}</dt>
{intl.formatMessage({
defaultMessage: "From",
})}
</dt>
</Body>
<Body color="uiTextHighContrast" asChild>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<dd>{`${parking.startTime}-${parking.endTime}`}</dd> <dd>{`${parking.startTime}-${parking.endTime}`}</dd>
</Body> </div>
</div> </Typography>
)} )}
</div> </div>
))} ))}

View File

@@ -2,6 +2,7 @@
display: grid; display: grid;
row-gap: var(--Spacing-x1); row-gap: var(--Spacing-x1);
margin: 0; margin: 0;
color: var(--Text-Default);
} }
.period { .period {
@@ -13,3 +14,7 @@
margin: 0; margin: 0;
flex: 1; flex: 1;
} }
.priceHeading {
color: var(--Text-Secondary);
}

View File

@@ -1,13 +1,13 @@
import Link from "next/link" "use client"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getIntl } from "@/i18n" import ButtonLink from "@/components/ButtonLink"
import Divider from "@/components/TempDesignSystem/Divider"
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 ParkingList from "./ParkingList"
import ParkingPrices from "./ParkingPrices" import ParkingPrices from "./ParkingPrices"
@@ -20,17 +20,19 @@ interface ParkingInformationProps {
showExternalParkingButton?: boolean showExternalParkingButton?: boolean
} }
export default async function ParkingInformation({ export default function ParkingInformation({
parking, parking,
showExternalParkingButton = true, showExternalParkingButton = true,
}: ParkingInformationProps) { }: ParkingInformationProps) {
const intl = await getIntl() const intl = useIntl()
const title = `${parking.type}${parking.name ? ` (${parking.name})` : ""}`
return ( return (
<div className={styles.parkingInformation}> <div className={styles.parkingInformation}>
<div className={styles.list}> <div className={styles.list}>
<Subtitle type="two" asChild> <Typography variant="Title/Subtitle/md">
<h4>{parking.type}</h4> <h4 className={styles.heading}>{title}</h4>
</Subtitle> </Typography>
<ParkingList <ParkingList
numberOfChargingSpaces={parking.numberOfChargingSpaces} numberOfChargingSpaces={parking.numberOfChargingSpaces}
canMakeReservation={parking.canMakeReservation} canMakeReservation={parking.canMakeReservation}
@@ -40,19 +42,17 @@ export default async function ParkingInformation({
/> />
</div> </div>
<div className={styles.prices}> <div className={styles.prices}>
<Subtitle type="two" asChild> <Typography variant="Body/Paragraph/mdBold">
<h5> <h5 className={styles.heading}>
{intl.formatMessage({ {intl.formatMessage({ defaultMessage: "Prices" })}
defaultMessage: "Prices",
})}
</h5> </h5>
</Subtitle> </Typography>
<div className={styles.priceWrapper}> <div className={styles.priceWrapper}>
<Caption color="uiTextMediumContrast" textTransform="uppercase"> <Typography variant="Title/Overline/sm">
{intl.formatMessage({ <h6 className={styles.priceHeading}>
defaultMessage: "Weekday prices", {intl.formatMessage({ defaultMessage: "Weekday prices" })}
})} </h6>
</Caption> </Typography>
<Divider color="baseSurfaceSubtleHover" /> <Divider color="baseSurfaceSubtleHover" />
{parking.pricing.localCurrency ? ( {parking.pricing.localCurrency ? (
<ParkingPrices <ParkingPrices
@@ -63,11 +63,11 @@ export default async function ParkingInformation({
) : null} ) : null}
</div> </div>
<div className={styles.priceWrapper}> <div className={styles.priceWrapper}>
<Caption color="uiTextMediumContrast" textTransform="uppercase"> <Typography variant="Title/Overline/sm">
{intl.formatMessage({ <h6 className={styles.priceHeading}>
defaultMessage: "Weekend prices", {intl.formatMessage({ defaultMessage: "Weekend prices" })}
})} </h6>
</Caption> </Typography>
<Divider color="baseSurfaceSubtleHover" /> <Divider color="baseSurfaceSubtleHover" />
{parking.pricing.localCurrency ? ( {parking.pricing.localCurrency ? (
<ParkingPrices <ParkingPrices
@@ -79,14 +79,10 @@ export default async function ParkingInformation({
</div> </div>
</div> </div>
{parking.externalParkingUrl && showExternalParkingButton && ( {parking.externalParkingUrl && showExternalParkingButton && (
<Button theme="base" intent="primary" variant="icon" asChild> <ButtonLink href={parking.externalParkingUrl} target="_blank">
<Link href={parking.externalParkingUrl} target="_blank"> {intl.formatMessage({ defaultMessage: "Book parking" })}
{intl.formatMessage({ <MaterialIcon icon="open_in_new" color="CurrentColor" />
defaultMessage: "Book parking", </ButtonLink>
})}
<MaterialIcon icon="open_in_new" color="CurrentColor" />
</Link>
</Button>
)} )}
</div> </div>
) )

View File

@@ -16,3 +16,11 @@
display: grid; display: grid;
gap: var(--Spacing-x1); gap: var(--Spacing-x1);
} }
.heading {
color: var(--Text-Default);
}
.priceHeading {
color: var(--Text-Secondary);
}

View File

@@ -0,0 +1,49 @@
"use client"
import { useIntl } from "react-intl"
import AdditionalAmenities from "@/components/SidePeeks/AmenitiesSidepeekContent/AdditionalAmenities"
import Accordion from "@/components/TempDesignSystem/Accordion"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import AccessibilityAccordionItem from "../AmenitiesSidepeekContent/Accordions/Accessibility"
import BreakfastAccordionItem from "../AmenitiesSidepeekContent/Accordions/Breakfast"
import CheckInCheckOutAccordionItem from "../AmenitiesSidepeekContent/Accordions/CheckInCheckOut"
import ParkingAccordionItem from "../AmenitiesSidepeekContent/Accordions/Parking"
import type { AmenitiesSidePeekProps } from "@/types/components/hotelReservation/amenitiesSidePeek"
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
export default function AmenitiesSidePeek({
hotel,
restaurants,
additionalHotelData,
activeSidePeek,
close,
}: AmenitiesSidePeekProps) {
const intl = useIntl()
return (
<SidePeek
title={intl.formatMessage({ defaultMessage: "Amenities" })}
isOpen={activeSidePeek === SidePeekEnum.amenities}
handleClose={close}
>
<Accordion>
<ParkingAccordionItem
parking={hotel.parking}
elevatorPitch={additionalHotelData?.hotelParking.elevatorPitch}
/>
<BreakfastAccordionItem
restaurants={restaurants}
hotelType={hotel.hotelType}
/>
<CheckInCheckOutAccordionItem checkInData={hotel.hotelFacts.checkin} />
<AccessibilityAccordionItem
elevatorPitch={additionalHotelData?.hotelSpecialNeeds.elevatorPitch}
/>
<AdditionalAmenities amenities={hotel.detailedFacilities} />
</Accordion>
</SidePeek>
)
}

View File

@@ -1,20 +1,26 @@
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" "use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import ButtonLink from "@/components/ButtonLink" import ButtonLink from "@/components/ButtonLink"
import { IconName } from "@/components/Icons/iconName" import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Body from "@/components/TempDesignSystem/Text/Body"
import { getIntl } from "@/i18n"
import styles from "./accessibilityAmenity.module.css" import styles from "./sidePeekAccordion.module.css"
import type { AccessibilityAmenityProps } from "@/types/components/hotelPage/sidepeek/accessibility" import type { AccessibilityAccordionItemProps } from "@/types/components/sidePeeks/amenities"
export default async function AccessibilityAmenity({ export default function AccessibilityAccordionItem({
elevatorPitch, elevatorPitch,
accessibilityPageUrl, accessibilityPageUrl,
}: AccessibilityAmenityProps) { }: AccessibilityAccordionItemProps) {
const intl = await getIntl() const intl = useIntl()
if (!elevatorPitch && !accessibilityPageUrl) {
return null
}
return ( return (
<AccordionItem <AccordionItem
@@ -22,13 +28,14 @@ export default async function AccessibilityAmenity({
defaultMessage: "Accessibility", defaultMessage: "Accessibility",
})} })}
iconName={IconName.Accessibility} iconName={IconName.Accessibility}
className={styles.accordionItem}
variant="sidepeek" variant="sidepeek"
trackingId="amenities:accessibility" trackingId="amenities:accessibility"
> >
<div className={styles.wrapper}> <div className={styles.accessibilityContent}>
{elevatorPitch && ( <Typography variant="Body/Paragraph/mdRegular">
<Body color="uiTextHighContrast">{elevatorPitch}</Body> <p>{elevatorPitch}</p>
)} </Typography>
{accessibilityPageUrl && ( {accessibilityPageUrl && (
<ButtonLink <ButtonLink
href={`/${accessibilityPageUrl}`} href={`/${accessibilityPageUrl}`}
@@ -38,11 +45,7 @@ export default async function AccessibilityAmenity({
typography="Body/Paragraph/mdBold" typography="Body/Paragraph/mdBold"
appendToCurrentPath appendToCurrentPath
> >
{intl.formatMessage({ {intl.formatMessage({ defaultMessage: "About accessibility" })}
defaultMessage: "About accessibility",
})}
<MaterialIcon icon="arrow_forward" color="CurrentColor" />
</ButtonLink> </ButtonLink>
)} )}
</div> </div>

View File

@@ -0,0 +1,61 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { isDefined } from "@/server/utils"
import { IconName } from "@/components/Icons/iconName"
import OpeningHours from "@/components/OpeningHours"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import styles from "./sidePeekAccordion.module.css"
import type { BreakfastAccordionItemProps } from "@/types/components/sidePeeks/amenities"
import { HotelTypeEnum } from "@/types/enums/hotelType"
export default function BreakfastAccordionItem({
restaurants,
hotelType,
}: BreakfastAccordionItemProps) {
const intl = useIntl()
const openingHours = restaurants
?.map((restaurant) => {
const breakfastDetail = restaurant.openingDetails.find(
(details) =>
details.openingHours.name === "Breakfast" ||
details.openingHours.name ===
intl.formatMessage({ defaultMessage: "Breakfast" })
)
return breakfastDetail
})
.filter(isDefined)[0]
if (!openingHours && hotelType !== HotelTypeEnum.ScandicGo) {
return null
}
return (
<AccordionItem
title={intl.formatMessage({ defaultMessage: "Breakfast" })}
iconName={IconName.CoffeeAlt}
variant="sidepeek"
className={styles.accordionItem}
trackingId="amenities:breakfast"
>
{openingHours ? (
<OpeningHours
openingHours={openingHours.openingHours}
alternateOpeningHours={openingHours.alternateOpeningHours}
heading={intl.formatMessage({ defaultMessage: "Opening hours" })}
/>
) : (
<Typography variant="Body/Paragraph/mdRegular">
<p>{intl.formatMessage({ defaultMessage: "All-day breakfast" })}</p>
</Typography>
)}
</AccordionItem>
)
}

View File

@@ -0,0 +1,60 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Divider from "@/components/TempDesignSystem/Divider"
import styles from "./sidePeekAccordion.module.css"
import type { CheckInCheckOutAccordionItemProps } from "@/types/components/sidePeeks/amenities"
export default function CheckInCheckOutAccordionItem({
checkInData,
}: CheckInCheckOutAccordionItemProps) {
const intl = useIntl()
const { checkInTime, checkOutTime } = checkInData
return (
<AccordionItem
title={intl.formatMessage({ defaultMessage: "Check-in/Check-out" })}
iconName={IconName.Business}
variant="sidepeek"
className={styles.accordionItem}
trackingId="amenities:check-in"
>
<div className={styles.checkInCheckOutContent}>
<Typography variant="Title/Overline/sm">
<h4 className={styles.subheading}>
{intl.formatMessage({ defaultMessage: "Hours" })}
</h4>
</Typography>
<Divider color="Border/Divider/Default" />
<Typography variant="Body/Paragraph/mdRegular">
<div>
<p>
{intl.formatMessage(
{ defaultMessage: "Check in from: {checkInTime}" },
{
checkInTime,
}
)}
</p>
<p>
{intl.formatMessage(
{ defaultMessage: "Check out at latest: {checkOutTime}" },
{
checkOutTime,
}
)}
</p>
</div>
</Typography>
</div>
</AccordionItem>
)
}

View File

@@ -1,19 +1,24 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import ButtonLink from "@/components/ButtonLink" import ButtonLink from "@/components/ButtonLink"
import { IconName } from "@/components/Icons/iconName" import { IconName } from "@/components/Icons/iconName"
import ParkingInformation from "@/components/ParkingInformation" import ParkingInformation from "@/components/ParkingInformation"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import { getIntl } from "@/i18n"
import styles from "./parkingAmenity.module.css" import styles from "./sidePeekAccordion.module.css"
import type { ParkingAmenityProps } from "@/types/components/hotelPage/sidepeek/parking" import type { ParkingAccordionItemProps } from "@/types/components/sidePeeks/amenities"
export default async function ParkingAmenity({ export default function ParkingAccordionItem({
parking, parking,
parkingElevatorPitch, elevatorPitch,
parkingPageUrl, parkingPageUrl,
}: ParkingAmenityProps) { }: ParkingAccordionItemProps) {
const intl = await getIntl() const intl = useIntl()
return ( return (
<AccordionItem <AccordionItem
@@ -22,10 +27,15 @@ export default async function ParkingAmenity({
})} })}
iconName={IconName.Parking} iconName={IconName.Parking}
variant="sidepeek" variant="sidepeek"
className={styles.accordionItem}
trackingId="amenities:parking" trackingId="amenities:parking"
> >
<div className={styles.wrapper}> <div className={styles.parkingContent}>
{parkingElevatorPitch} {elevatorPitch ? (
<Typography variant="Body/Paragraph/mdRegular">
<p>{elevatorPitch}</p>
</Typography>
) : null}
{parking.map((data) => ( {parking.map((data) => (
<ParkingInformation key={data.type} parking={data} /> <ParkingInformation key={data.type} parking={data} />
))} ))}

View File

@@ -0,0 +1,26 @@
.accordionItem {
color: var(--Text-Default);
}
.parkingContent,
.accessibilityContent {
display: grid;
gap: var(--Space-x3);
}
.checkInCheckOutContent {
display: grid;
gap: var(--Space-x15);
}
.checkInCheckOutContent {
display: grid;
padding: var(--Space-x2) var(--Space-x3);
gap: var(--Space-x1);
border-radius: var(--Corner-radius-Medium);
background: var(--Surface-Secondary-Default);
}
.subheading {
color: var(--Text-Secondary);
}

View File

@@ -1,6 +1,7 @@
.wrapper { .wrapper {
padding: var(--Spacing-x1) var(--Spacing-x0); padding: var(--Spacing-x1) var(--Spacing-x0);
border-bottom: 1px solid var(--Base-Border-Subtle); border-bottom: 1px solid var(--Base-Border-Subtle);
color: var(--Text-Interactive-Default);
} }
.amenity { .amenity {

View File

@@ -0,0 +1,42 @@
import { Typography } from "@scandic-hotels/design-system/Typography"
import { FacilityToIcon } from "../../../ContentType/HotelPage/data"
import styles from "./additionalAmenities.module.css"
import type { AdditionalAmenitiesProps } from "@/types/components/sidePeeks/amenities"
import { FacilityEnum } from "@/types/enums/facilities"
export default function AdditionalAmenities({
amenities,
}: AdditionalAmenitiesProps) {
const amenitiesToIgnore = [
FacilityEnum.ParkingAdditionalCost,
FacilityEnum.ParkingElectricCharging,
FacilityEnum.ParkingFreeParking,
FacilityEnum.ParkingGarage,
FacilityEnum.ParkingOutdoor,
FacilityEnum.MeetingArea,
FacilityEnum.ServesBreakfastAlwaysIncluded,
FacilityEnum.LateCheckOutUntil1400Guaranteed,
]
const filteredAmenities = amenities.filter(
(amenity) => !amenitiesToIgnore.includes(amenity.id)
)
return filteredAmenities?.map((amenity) => (
<Typography key={amenity.name} variant="Title/Subtitle/md">
<li className={styles.wrapper}>
<div className={styles.amenity}>
<FacilityToIcon
id={amenity.id}
color="Icon/Interactive/Default"
size={24}
/>
{amenity.name}
</div>
</li>
</Typography>
))
}

View File

@@ -1,24 +0,0 @@
import { useIntl } from "react-intl"
import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Body from "@/components/TempDesignSystem/Text/Body"
import type { AccessibilityProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
export default function Accessibility({
elevatorPitchText,
}: AccessibilityProps) {
const intl = useIntl()
return (
<AccordionItem
title={intl.formatMessage({
defaultMessage: "Accessibility",
})}
iconName={IconName.Accessibility}
variant="sidepeek"
>
<Body>{elevatorPitchText}</Body>
</AccordionItem>
)
}

View File

@@ -1,47 +0,0 @@
import { useIntl } from "react-intl"
import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Body from "@/components/TempDesignSystem/Text/Body"
import type { CheckInCheckOutProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
export default function CheckinCheckOut({ checkin }: CheckInCheckOutProps) {
const intl = useIntl()
return (
<AccordionItem
title={intl.formatMessage({
defaultMessage: "Check-in/Check-out",
})}
iconName={IconName.Calendar}
variant="sidepeek"
>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Hours",
})}
</Body>
<Body>
{intl.formatMessage(
{
defaultMessage: "Check in from: {checkInTime}",
},
{
checkInTime: checkin.checkInTime,
}
)}
</Body>
<Body>
{intl.formatMessage(
{
defaultMessage: "Check out at latest: {checkOutTime}",
},
{
checkOutTime: checkin.checkOutTime,
}
)}
</Body>
</AccordionItem>
)
}

View File

@@ -1,24 +0,0 @@
import { useIntl } from "react-intl"
import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Body from "@/components/TempDesignSystem/Text/Body"
import type { MeetingsAndConferencesProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
export default function MeetingsAndConferences({
meetingDescription,
}: MeetingsAndConferencesProps) {
const intl = useIntl()
return (
<AccordionItem
title={intl.formatMessage({
defaultMessage: "Meetings & Conferences",
})}
iconName={IconName.Business}
variant="sidepeek"
>
<Body>{meetingDescription}</Body>
</AccordionItem>
)
}

View File

@@ -1,74 +0,0 @@
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./sidePeekAccordion.module.css"
import type { ParkingProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
export default function Parking({ parking }: ParkingProps) {
const intl = useIntl()
return (
<AccordionItem
title={intl.formatMessage({
defaultMessage: "Parking",
})}
iconName={IconName.Parking}
className={styles.parking}
variant="sidepeek"
>
{parking.map((p) => {
const title = `${p.type}${p.name ? ` (${p.name})` : ""}`
return (
<div key={p.name}>
<Subtitle type="two">{title}</Subtitle>
<ul className={styles.list}>
{p.address !== undefined && (
<li>
<MaterialIcon icon="favorite" isFilled color="Icon/Accent" />
{intl.formatMessage(
{
defaultMessage: "Address: {address}",
},
{
address: p.address,
}
)}
</li>
)}
{p.numberOfParkingSpots !== undefined && (
<li>
<MaterialIcon icon="favorite" isFilled color="Icon/Accent" />
{intl.formatMessage(
{
defaultMessage: "Number of parking spots: {number}",
},
{ number: p.numberOfParkingSpots }
)}
</li>
)}
{p.numberOfChargingSpaces !== undefined && (
<li>
<MaterialIcon icon="favorite" isFilled color="Icon/Accent" />
{intl.formatMessage(
{
defaultMessage:
"Number of charging points for electric cars: {number}",
},
{ number: p.numberOfChargingSpaces }
)}
</li>
)}
</ul>
</div>
)
})}
</AccordionItem>
)
}

View File

@@ -1,29 +0,0 @@
import { useIntl } from "react-intl"
import { IconName } from "@/components/Icons/iconName"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Body from "@/components/TempDesignSystem/Text/Body"
import type { RestaurantProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
export default function Restaurant({
restaurantsContentDescriptionMedium,
}: RestaurantProps) {
const intl = useIntl()
return (
<AccordionItem
title={intl.formatMessage(
{
defaultMessage:
"{totalRestaurants, plural, one {Restaurant} other {Restaurants}}",
},
{ totalRestaurants: 1 }
)}
iconName={IconName.Restaurant}
variant="sidepeek"
>
<Body>{restaurantsContentDescriptionMedium}</Body>
</AccordionItem>
)
}

View File

@@ -1,24 +0,0 @@
.list {
font-family: var(--typography-Body-Regular-fontFamily);
list-style-position: inside;
list-style-type: none;
margin-top: var(--Spacing-x-one-and-half);
}
.list li {
display: flex;
align-items: center;
gap: var(--Spacing-x1);
padding-left: var(--Spacing-x1);
justify-items: flex-start;
}
.list li svg {
flex-shrink: 0;
}
.parking details > div {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
}

View File

@@ -1,35 +1,5 @@
.spacing {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
}
.content { .content {
display: grid; display: grid;
gap: var(--Spacing-x2); gap: var(--Spacing-x2);
} color: var(--Text-Default);
.content:last-child {
gap: 0;
}
.content > p {
margin-bottom: var(--Spacing-x-one-and-half);
}
.content > ul > li:first-child {
border-top: 1px solid var(--Base-Border-Subtle);
}
.amenity > p {
border-top: 1px solid var(--Base-Border-Subtle);
padding: calc(var(--Spacing-x-one-and-half) + var(--Spacing-x1))
var(--Spacing-x1);
display: flex;
align-items: center;
gap: var(--Spacing-x1);
}
.noIcon {
margin-left: var(--Spacing-x4);
} }

View File

@@ -1,16 +1,18 @@
"use client"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data" import { Typography } from "@scandic-hotels/design-system/Typography"
import Contact from "@/components/HotelReservation/Contact" import Contact from "@/components/HotelReservation/Contact"
import AdditionalAmenities from "@/components/SidePeeks/AmenitiesSidepeekContent/AdditionalAmenities"
import Accordion from "@/components/TempDesignSystem/Accordion" import Accordion from "@/components/TempDesignSystem/Accordion"
import SidePeek from "@/components/TempDesignSystem/SidePeek" import SidePeek from "@/components/TempDesignSystem/SidePeek"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Accessibility from "./Accordions/Accessibility" import AccessibilityAccordionItem from "../AmenitiesSidepeekContent/Accordions/Accessibility"
import CheckinCheckOut from "./Accordions/CheckInCheckOut" import BreakfastAccordionItem from "../AmenitiesSidepeekContent/Accordions/Breakfast"
import MeetingsAndConferences from "./Accordions/MeetingsAndConferences" import CheckInCheckOutAccordionItem from "../AmenitiesSidepeekContent/Accordions/CheckInCheckOut"
import Parking from "./Accordions/Parking" import ParkingAccordionItem from "../AmenitiesSidepeekContent/Accordions/Parking"
import Restaurant from "./Accordions/Restaurant"
import styles from "./hotelSidePeek.module.css" import styles from "./hotelSidePeek.module.css"
@@ -19,17 +21,12 @@ import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
export default function HotelSidePeek({ export default function HotelSidePeek({
hotel, hotel,
restaurants,
additionalHotelData, additionalHotelData,
activeSidePeek, activeSidePeek,
close, close,
}: HotelSidePeekProps) { }: HotelSidePeekProps) {
const intl = useIntl() const intl = useIntl()
const amenitiesList = hotel.detailedFacilities.filter(
(facility) => facility.public
)
const parking = hotel.parking.filter(
(p) => p?.numberOfParkingSpots || p?.numberOfChargingSpaces || p?.address
)
return ( return (
<SidePeek <SidePeek
@@ -38,61 +35,30 @@ export default function HotelSidePeek({
handleClose={close} handleClose={close}
> >
<div className={styles.content}> <div className={styles.content}>
<Subtitle color="baseTextHighContrast"> <Typography variant="Title/Subtitle/lg">
{intl.formatMessage({ <h3>
defaultMessage: "Practical information", {intl.formatMessage({ defaultMessage: "Practical information" })}
})} </h3>
</Subtitle> </Typography>
<Contact hotel={hotel} /> <Contact hotel={hotel} />
<Accordion>
{parking?.length > 0 && <Parking parking={parking} />}
{additionalHotelData?.restaurantsOverviewPage
?.restaurantsContentDescriptionMedium && (
<Restaurant
restaurantsContentDescriptionMedium={
additionalHotelData.restaurantsOverviewPage
.restaurantsContentDescriptionMedium
}
/>
)}
{additionalHotelData?.hotelSpecialNeeds.elevatorPitch && (
<Accessibility
elevatorPitchText={
additionalHotelData.hotelSpecialNeeds.elevatorPitch
}
/>
)}
{hotel.hotelFacts?.checkin && (
<CheckinCheckOut checkin={hotel.hotelFacts.checkin} />
)}
{hotel.hotelContent.texts?.meetingDescription?.medium && (
<MeetingsAndConferences
meetingDescription={
hotel.hotelContent.texts.meetingDescription.medium
}
/>
)}
</Accordion>
<div className={styles.amenity}>
{amenitiesList.map((amenity) => {
const Icon = (
<FacilityToIcon id={amenity.id} size={24} color="Icon/Intense" />
)
return (
<Subtitle type="two" key={amenity.id} color="uiTextHighContrast">
{Icon && Icon}
{amenity.name}
</Subtitle>
)
})}
</div>
{/* TODO: handle linking to Hotel Page */} <Accordion>
{/* {showCTA && ( <ParkingAccordionItem
<Button theme="base" intent="secondary" size="large"> parking={hotel.parking}
Read more about the hotel elevatorPitch={additionalHotelData?.hotelParking.elevatorPitch}
</Button> />
)} */} <BreakfastAccordionItem
restaurants={restaurants}
hotelType={hotel.hotelType}
/>
<CheckInCheckOutAccordionItem
checkInData={hotel.hotelFacts.checkin}
/>
<AccessibilityAccordionItem
elevatorPitch={additionalHotelData?.hotelSpecialNeeds.elevatorPitch}
/>
<AdditionalAmenities amenities={hotel.detailedFacilities} />
</Accordion>
</div> </div>
</SidePeek> </SidePeek>
) )

View File

@@ -32,7 +32,11 @@ export default function AccordionItem({
const IconComp = icon const IconComp = icon
? icon ? icon
: iconName && ( : iconName && (
<IconByIconName iconName={iconName} color="Icon/Interactive/Default" /> <IconByIconName
iconName={iconName}
color="Icon/Interactive/Default"
size={24}
/>
) )
function toggleAccordion() { function toggleAccordion() {

View File

@@ -1,4 +1,10 @@
{ {
"+2LakC": [
{
"type": 0,
"value": "Invalid membership number format"
}
],
"+CC2q2": [ "+CC2q2": [
{ {
"type": 0, "type": 0,
@@ -547,8 +553,7 @@
"value": "Please enter the code sent to " "value": "Please enter the code sent to "
}, },
{ {
"children": [ "children": [],
],
"type": 8, "type": 8,
"value": "maskedContactInfo" "value": "maskedContactInfo"
}, },
@@ -901,6 +906,12 @@
"value": "Log in/Join" "value": "Log in/Join"
} }
], ],
"5cIDjn": [
{
"type": 0,
"value": "Booking code is invalid"
}
],
"5kfntb": [ "5kfntb": [
{ {
"type": 0, "type": 0,
@@ -1009,6 +1020,12 @@
"value": "Continue with new price" "value": "Continue with new price"
} }
], ],
"6TXxwM": [
{
"type": 0,
"value": "Last name can't contain any special characters"
}
],
"6U6gjS": [ "6U6gjS": [
{ {
"type": 0, "type": 0,
@@ -1779,8 +1796,7 @@
"value": "Please enter the code sent to " "value": "Please enter the code sent to "
}, },
{ {
"children": [ "children": [],
],
"type": 8, "type": 8,
"value": "maskedContactInfo" "value": "maskedContactInfo"
}, },
@@ -2170,6 +2186,12 @@
"value": "Find" "value": "Find"
} }
], ],
"FPcbu0": [
{
"type": 0,
"value": "Please enter a valid phone number"
}
],
"FZI5xl": [ "FZI5xl": [
{ {
"type": 0, "type": 0,
@@ -2420,6 +2442,12 @@
"value": "About accessibility" "value": "About accessibility"
} }
], ],
"HRrOrP": [
{
"type": 0,
"value": "Bed choice is required"
}
],
"HW+bLl": [ "HW+bLl": [
{ {
"type": 0, "type": 0,
@@ -2849,6 +2877,12 @@
"value": "points" "value": "points"
} }
], ],
"KaH0vJ": [
{
"type": 0,
"value": "You cannot have more children in adults bed than adults in the room"
}
],
"KgkOn/": [ "KgkOn/": [
{ {
"type": 0, "type": 0,
@@ -3497,6 +3531,12 @@
"value": "See details" "value": "See details"
} }
], ],
"RS5Kmw": [
{
"type": 0,
"value": "New password is required"
}
],
"RT3W/7": [ "RT3W/7": [
{ {
"type": 0, "type": 0,
@@ -3641,6 +3681,12 @@
"value": "Go back to My Pages" "value": "Go back to My Pages"
} }
], ],
"Seanpx": [
{
"type": 0,
"value": "Required"
}
],
"SjHZ7g": [ "SjHZ7g": [
{ {
"type": 0, "type": 0,
@@ -4005,6 +4051,12 @@
"value": "You have cancelled to process to guarantee your booking." "value": "You have cancelled to process to guarantee your booking."
} }
], ],
"Vp6BHv": [
{
"type": 0,
"value": "Must be at least 18 years of age to continue"
}
],
"W1IuVy": [ "W1IuVy": [
{ {
"type": 0, "type": 0,
@@ -4335,8 +4387,7 @@
"value": "Please enter the code sent to " "value": "Please enter the code sent to "
}, },
{ {
"children": [ "children": [],
],
"type": 8, "type": 8,
"value": "maskedContactInfo" "value": "maskedContactInfo"
}, },
@@ -4387,6 +4438,12 @@
"value": "Select a rate" "value": "Select a rate"
} }
], ],
"YqklRf": [
{
"type": 0,
"value": "Destination required"
}
],
"Z+d8/1": [ "Z+d8/1": [
{ {
"type": 0, "type": 0,
@@ -4697,6 +4754,12 @@
"value": "Where to?" "value": "Where to?"
} }
], ],
"boU1pG": [
{
"type": 0,
"value": "Code and voucher is not available with reward night."
}
],
"bsUwPW": [ "bsUwPW": [
{ {
"type": 0, "type": 0,
@@ -5247,6 +5310,12 @@
"value": " (incl VAT)" "value": " (incl VAT)"
} }
], ],
"gq4AYf": [
{
"type": 0,
"value": "Only digits are allowed"
}
],
"gttxKw": [ "gttxKw": [
{ {
"type": 0, "type": 0,
@@ -5349,6 +5418,12 @@
"value": "Your transaction" "value": "Your transaction"
} }
], ],
"iZMEZ+": [
{
"type": 0,
"value": "Age is required"
}
],
"iwtnXO": [ "iwtnXO": [
{ {
"type": 0, "type": 0,
@@ -5439,6 +5514,12 @@
"value": " cm" "value": " cm"
} }
], ],
"jlqOPq": [
{
"type": 0,
"value": "Country is required"
}
],
"jvo0vs": [ "jvo0vs": [
{ {
"type": 0, "type": 0,
@@ -5557,6 +5638,12 @@
"value": "destination" "value": "destination"
} }
], ],
"ktyBmE": [
{
"type": 0,
"value": "Current password is required"
}
],
"kvOMtB": [ "kvOMtB": [
{ {
"type": 0, "type": 0,
@@ -6700,6 +6787,12 @@
"value": "Paid" "value": "Paid"
} }
], ],
"u8II6t": [
{
"type": 0,
"value": "First name can't contain any special characters"
}
],
"uDm4Mt": [ "uDm4Mt": [
{ {
"type": 1, "type": 1,
@@ -6740,6 +6833,12 @@
"value": "By adding a card you also guarantee your room booking for late arrival." "value": "By adding a card you also guarantee your room booking for late arrival."
} }
], ],
"uiFoFm": [
{
"type": 0,
"value": "Password is required"
}
],
"ujX0MU": [ "ujX0MU": [
{ {
"type": 0, "type": 0,
@@ -6920,6 +7019,12 @@
"value": "View your details" "value": "View your details"
} }
], ],
"vzqS9m": [
{
"type": 0,
"value": "Phone is required"
}
],
"w0LSDs": [ "w0LSDs": [
{ {
"type": 1, "type": 1,
@@ -6964,6 +7069,12 @@
"value": "Sign up bonus" "value": "Sign up bonus"
} }
], ],
"wRUsWd": [
{
"type": 0,
"value": "Retype new password does not match new password"
}
],
"wSh+Ly": [ "wSh+Ly": [
{ {
"type": 1, "type": 1,
@@ -7195,6 +7306,12 @@
"value": "value" "value": "value"
} }
], ],
"yNJhk3": [
{
"type": 0,
"value": "Retype new password is required"
}
],
"yP9SR1": [ "yP9SR1": [
{ {
"type": 0, "type": 0,
@@ -7243,12 +7360,24 @@
"value": "Last name is required" "value": "Last name is required"
} }
], ],
"yr+Os9": [
{
"type": 0,
"value": "Date of birth is required"
}
],
"yxguVq": [ "yxguVq": [
{ {
"type": 0, "type": 0,
"value": "Discard changes" "value": "Discard changes"
} }
], ],
"z7+EXa": [
{
"type": 0,
"value": "Zip code is required"
}
],
"zA7LuX": [ "zA7LuX": [
{ {
"type": 0, "type": 0,
@@ -7359,4 +7488,4 @@
"value": "Map" "value": "Map"
} }
] ]
} }

View File

@@ -1,4 +0,0 @@
export type AccessibilityAmenityProps = {
elevatorPitch?: string
accessibilityPageUrl?: string
}

View File

@@ -1,22 +1,19 @@
import type { Hotel, Restaurant, RestaurantOpeningHours } from "@/types/hotel" import type {
import type { AccessibilityAmenityProps } from "./accessibility" CheckInData,
DetailedFacility,
Hotel,
Restaurant,
} from "@/types/hotel"
import type { ParkingAmenityProps } from "./parking" import type { ParkingAmenityProps } from "./parking"
export type AmenitiesSidePeekProps = { export type AmenitiesSidePeekProps = {
amenitiesList: Hotel["detailedFacilities"] amenitiesList: DetailedFacility[]
parking: ParkingAmenityProps parking: ParkingAmenityProps
checkInInformation: Hotel["hotelFacts"]["checkin"] checkInInformation: CheckInData
accessibility: AccessibilityAmenityProps accessibility: {
elevatorPitch?: string
accessibilityPageUrl?: string
}
restaurants: Restaurant[] restaurants: Restaurant[]
hotelType: Hotel["hotelType"] hotelType: Hotel["hotelType"]
} }
export type FilteredAmenitiesProps = {
filteredAmenities: Hotel["detailedFacilities"]
}
export interface BreakfastAmenityProps {
openingHours?: RestaurantOpeningHours
alternateOpeningHours?: RestaurantOpeningHours
hotelType: Hotel["hotelType"]
}

View File

@@ -1,5 +0,0 @@
import type { Hotel } from "@/types/hotel"
export type CheckInAmenityProps = {
checkInInformation: Hotel["hotelFacts"]["checkin"]
}

View File

@@ -1,7 +0,0 @@
import type { RestaurantOpeningHours } from "@/types/hotel"
export interface OpeningHoursProps {
openingHours: RestaurantOpeningHours
alternateOpeningHours?: RestaurantOpeningHours
heading?: string
}

View File

@@ -0,0 +1,10 @@
import type { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
import type { AdditionalData, Hotel, Restaurant } from "@/types/hotel"
export type AmenitiesSidePeekProps = {
hotel: Hotel
restaurants: Restaurant[]
additionalHotelData: AdditionalData | undefined
activeSidePeek: SidePeekEnum
close: () => void
}

View File

@@ -1,8 +1,9 @@
import type { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek" import type { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
import type { AdditionalData, Hotel } from "@/types/hotel" import type { AdditionalData, Hotel, Restaurant } from "@/types/hotel"
export type HotelSidePeekProps = { export type HotelSidePeekProps = {
hotel: Hotel hotel: Hotel
restaurants: Restaurant[]
additionalHotelData: AdditionalData | undefined additionalHotelData: AdditionalData | undefined
activeSidePeek: SidePeekEnum activeSidePeek: SidePeekEnum
close: () => void close: () => void

View File

@@ -1,5 +1,6 @@
import type { CheckInData, Hotel, Parking } from "@/types/hotel" import type { Hotel } from "@/types/hotel"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
import type { SidePeekEnum } from "../sidePeek"
import type { import type {
AlternativeHotelsSearchParams, AlternativeHotelsSearchParams,
SelectHotelSearchParams, SelectHotelSearchParams,
@@ -15,32 +16,13 @@ export interface ReadMoreProps {
hotelId: string hotelId: string
hotel: Hotel hotel: Hotel
showCTA: boolean showCTA: boolean
sidePeekKey: SidePeekEnum
} }
export interface ContactProps { export interface ContactProps {
hotel: Hotel hotel: Hotel
} }
export interface ParkingProps {
parking: Parking[]
}
export interface AccessibilityProps {
elevatorPitchText: string
}
export interface RestaurantProps {
restaurantsContentDescriptionMedium: string
}
export interface CheckInCheckOutProps {
checkin: CheckInData
}
export interface MeetingsAndConferencesProps {
meetingDescription: string
}
export interface SelectHotelProps { export interface SelectHotelProps {
params: { params: {
lang: Lang lang: Lang

View File

@@ -1,5 +1,6 @@
export enum SidePeekEnum { export enum SidePeekEnum {
hotelDetails = "hotel-detail-side-peek", hotelDetails = "hotel-detail-side-peek",
amenities = "amenities-side-peek",
roomDetails = "room-detail-side-peek", roomDetails = "room-detail-side-peek",
bookedRoomDetails = "booked-room-detail-side-peek", bookedRoomDetails = "booked-room-detail-side-peek",
} }

View File

@@ -0,0 +1,30 @@
import type {
CheckInData,
DetailedFacility,
Parking,
Restaurant,
} from "@/types/hotel"
export interface ParkingAccordionItemProps {
parkingPageUrl?: string
parking: Parking[]
elevatorPitch?: string
}
export interface BreakfastAccordionItemProps {
restaurants?: Restaurant[]
hotelType: string
}
export interface CheckInCheckOutAccordionItemProps {
checkInData: CheckInData
}
export interface AccessibilityAccordionItemProps {
elevatorPitch?: string
accessibilityPageUrl?: string
}
export interface AdditionalAmenitiesProps {
amenities: DetailedFacility[]
}