Merged in feat/SW-1599 (pull request #1385)
Feat/SW-1599 Approved-by: Tobias Johansson
This commit is contained in:
@@ -8,7 +8,7 @@ export function getTypeSpecificInformation(
|
||||
const { images } = hotel.hotelContent
|
||||
const { descriptions, meetingDescription } = hotel.hotelContent.texts
|
||||
const hotelData = {
|
||||
description: descriptions.short,
|
||||
description: descriptions?.short,
|
||||
imageSrc: images.imageSizes.small,
|
||||
altText: images.metaData.altText,
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function HotelListingItem({
|
||||
</Caption>
|
||||
</div>
|
||||
</div>
|
||||
<Body>{hotel.hotelContent.texts.descriptions.short}</Body>
|
||||
<Body>{hotel.hotelContent.texts.descriptions?.short}</Body>
|
||||
<ul className={styles.amenityList}>
|
||||
{amenities.map((amenity) => {
|
||||
const IconComponent = mapFacilityToIcon(amenity.id)
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import type {
|
||||
Hotel,
|
||||
HotelAddress,
|
||||
HotelContent,
|
||||
HotelLocation,
|
||||
HotelTripAdvisor,
|
||||
} from "@/types/hotel"
|
||||
|
||||
export type IntroSectionProps = {
|
||||
address: HotelAddress
|
||||
hotelDescription: HotelContent["texts"]["descriptions"]["short"]
|
||||
hotelDescription: string | undefined
|
||||
hotelName: Hotel["name"]
|
||||
location: HotelLocation
|
||||
tripAdvisor: HotelTripAdvisor
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function AboutTheHotelSidePeek({
|
||||
ecoLabels={ecoLabels}
|
||||
/>
|
||||
<Divider color="baseSurfaceSubtleHover" />
|
||||
<Preamble>{descriptions.descriptions.medium}</Preamble>
|
||||
<Preamble>{descriptions.descriptions?.medium}</Preamble>
|
||||
<Body>{descriptions.facilityInformation}</Body>
|
||||
<Body>{descriptions.surroundingInformation}</Body>
|
||||
</section>
|
||||
|
||||
@@ -98,7 +98,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
||||
} = hotelData.additionalData
|
||||
|
||||
const images = gallery?.smallerImages
|
||||
const description = hotelContent.texts.descriptions.medium
|
||||
const description = hotelContent.texts.descriptions?.medium
|
||||
|
||||
const { spaPage, activitiesCards } = content
|
||||
|
||||
@@ -107,9 +107,9 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
||||
const hasWellness = healthFacilities.length > 0
|
||||
|
||||
const facilities = setFacilityCards(
|
||||
restaurantImages,
|
||||
conferencesAndMeetings,
|
||||
healthAndWellness,
|
||||
restaurantImages ?? undefined,
|
||||
conferencesAndMeetings ?? undefined,
|
||||
healthAndWellness ?? undefined,
|
||||
hasRestaurants,
|
||||
hasMeetingRooms,
|
||||
hasWellness
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function Header({
|
||||
busyStatus: "FREE",
|
||||
categories: ["booking", "hotel", "stay"],
|
||||
created: generateDateTime(booking.createDateTime),
|
||||
description: hotel.hotelContent.texts.descriptions.medium,
|
||||
description: hotel.hotelContent.texts.descriptions?.medium,
|
||||
end: generateDateTime(booking.checkOutDate),
|
||||
endInputType: "utc",
|
||||
geo: {
|
||||
|
||||
@@ -14,6 +14,7 @@ export default function ToggleSidePeek({
|
||||
hotelId,
|
||||
roomTypeCode,
|
||||
intent = "textInverted",
|
||||
title,
|
||||
}: ToggleSidePeekProps) {
|
||||
const intl = useIntl()
|
||||
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
|
||||
@@ -29,7 +30,7 @@ export default function ToggleSidePeek({
|
||||
intent={intent}
|
||||
wrapping
|
||||
>
|
||||
{intl.formatMessage({ id: "See room details" })}
|
||||
{title ? title : intl.formatMessage({ id: "See room details" })}
|
||||
<ChevronRight height="14" />
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -137,7 +137,7 @@ function HotelCard({
|
||||
</div>
|
||||
</div>
|
||||
<Body className={styles.hotelDescription}>
|
||||
{hotelData.hotelContent.texts.descriptions.short}
|
||||
{hotelData.hotelContent.texts.descriptions?.short}
|
||||
</Body>
|
||||
<div className={styles.facilities}>
|
||||
{amenities.map((facility) => {
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function ActionPanel({
|
||||
busyStatus: "FREE",
|
||||
categories: ["booking", "hotel", "stay"],
|
||||
created: generateDateTime(booking.createDateTime),
|
||||
description: hotel.hotelContent.texts.descriptions.medium,
|
||||
description: hotel.hotelContent.texts.descriptions?.medium,
|
||||
end: generateDateTime(booking.checkOutDate),
|
||||
endInputType: "utc",
|
||||
geo: {
|
||||
|
||||
@@ -58,7 +58,7 @@ export default async function HotelInfoCard({ hotelData }: HotelInfoCardProps) {
|
||||
)}
|
||||
</Caption>
|
||||
<Body color="uiTextHighContrast">
|
||||
{hotel.hotelContent.texts.descriptions.medium}
|
||||
{hotel.hotelContent.texts.descriptions?.medium}
|
||||
</Body>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getRates } from "@/components/HotelReservation/SelectRate/utils"
|
||||
import { EditIcon } from "@/components/Icons"
|
||||
import Image from "@/components/Image"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Chip from "@/components/TempDesignSystem/Chip"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
@@ -72,7 +73,7 @@ export default function SelectedRoomPanel() {
|
||||
|
||||
return (
|
||||
<div className={styles.selectedRoomPanel}>
|
||||
<div>
|
||||
<div className={styles.content}>
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "Room {roomIndex}" },
|
||||
@@ -90,20 +91,22 @@ export default function SelectedRoomPanel() {
|
||||
{intl.formatMessage({ id: "night" })}
|
||||
</Body>
|
||||
</div>
|
||||
<div className={styles.imageAndModifyButtonContainer}>
|
||||
{images?.[0]?.imageSizes?.tiny && (
|
||||
<div className={styles.imageContainer}>
|
||||
<Image
|
||||
alt={selectedRate?.roomType ?? images[0].metaData?.altText ?? ""}
|
||||
fill
|
||||
src={images[0].imageSizes.tiny}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.imageContainer}>
|
||||
{images?.[0]?.imageSizes?.tiny ? (
|
||||
<Image
|
||||
alt={selectedRate?.roomType ?? images[0].metaData?.altText ?? ""}
|
||||
className={styles.img}
|
||||
height={300}
|
||||
src={images[0].imageSizes.tiny}
|
||||
width={600}
|
||||
/>
|
||||
) : null}
|
||||
<div className={styles.modifyButtonContainer}>
|
||||
<Button variant="icon" size="small" onClick={modifyRate}>
|
||||
<EditIcon />
|
||||
{intl.formatMessage({ id: "Modify" })}
|
||||
<Button clean onClick={modifyRate}>
|
||||
<Chip size="small" variant="uiTextHighContrast">
|
||||
<EditIcon />
|
||||
{intl.formatMessage({ id: "Modify" })}
|
||||
</Chip>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,50 +1,53 @@
|
||||
.selectedRoomPanel {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
display: grid;
|
||||
grid-template-areas: "content image";
|
||||
grid-template-columns: 1fr 190px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modifyButtonContainer {
|
||||
position: absolute;
|
||||
right: var(--Spacing-x2);
|
||||
bottom: var(--Spacing-x2);
|
||||
.content {
|
||||
grid-area: content;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
width: 187px;
|
||||
height: 105px;
|
||||
position: relative;
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
grid-area: image;
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
.img {
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
height: auto;
|
||||
max-height: 105px;
|
||||
object-fit: fill;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modifyButtonContainer {
|
||||
bottom: var(--Spacing-x1);
|
||||
position: absolute;
|
||||
right: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
div.selectedRoomPanel p.subtitle {
|
||||
padding-bottom: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.imageContainer {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.imageAndModifyButtonContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
@media screen and (max-width: 768px) {
|
||||
.selectedRoomPanel {
|
||||
gap: var(--Spacing-x1);
|
||||
grid-template-areas: "image" "content";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
.modifyButtonContainer {
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
.img {
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.img {
|
||||
max-height: 190px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useIntl } from "react-intl"
|
||||
|
||||
import { useRatesStore } from "@/stores/select-rate"
|
||||
|
||||
import { ChevronUpIcon } from "@/components/Icons"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { useRoomContext } from "@/contexts/Room"
|
||||
|
||||
@@ -17,7 +19,13 @@ export default function MultiRoomWrapper({
|
||||
}: React.PropsWithChildren<{ isMultiRoom: boolean }>) {
|
||||
const intl = useIntl()
|
||||
const activeRoom = useRatesStore((state) => state.activeRoom)
|
||||
const { bookingRoom, isActiveRoom, roomNr, selectedRate } = useRoomContext()
|
||||
const {
|
||||
actions: { closeSection },
|
||||
bookingRoom,
|
||||
isActiveRoom,
|
||||
roomNr,
|
||||
selectedRate,
|
||||
} = useRoomContext()
|
||||
|
||||
const onlyAdultsMsg = intl.formatMessage(
|
||||
{ id: "{adults} adults" },
|
||||
@@ -56,19 +64,33 @@ export default function MultiRoomWrapper({
|
||||
selected: !!selectedRate && !isActiveRoom,
|
||||
})
|
||||
return (
|
||||
<div className={styles.roomContainer}>
|
||||
{selectedRate && !isActiveRoom ? null : (
|
||||
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "Room {roomIndex}" },
|
||||
{ roomIndex: roomNr }
|
||||
)}
|
||||
,{" "}
|
||||
{bookingRoom.childrenInRoom?.length
|
||||
? adultsAndChildrenMsg
|
||||
: onlyAdultsMsg}
|
||||
</Subtitle>
|
||||
)}
|
||||
<div className={styles.roomContainer} data-multiroom="true">
|
||||
<div className={styles.header}>
|
||||
{selectedRate && !isActiveRoom ? null : (
|
||||
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "Room {roomIndex}" },
|
||||
{ roomIndex: roomNr }
|
||||
)}
|
||||
,{" "}
|
||||
{bookingRoom.childrenInRoom?.length
|
||||
? adultsAndChildrenMsg
|
||||
: onlyAdultsMsg}
|
||||
</Subtitle>
|
||||
)}
|
||||
{selectedRate && isActiveRoom ? (
|
||||
<Button
|
||||
intent="text"
|
||||
onClick={closeSection}
|
||||
size="medium"
|
||||
theme="base"
|
||||
variant="icon"
|
||||
>
|
||||
{intl.formatMessage({ id: "Close" })}
|
||||
<ChevronUpIcon height={20} width={20} />
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={classNames}>
|
||||
<div className={styles.roomPanel}>
|
||||
<SelectedRoomPanel />
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.roomPanel,
|
||||
@@ -27,27 +33,21 @@
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.roomSelectionPanelContainer.active .roomSelectionPanel,
|
||||
.roomSelectionPanelContainer.selected .roomPanel {
|
||||
grid-template-rows: 1fr;
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.roomSelectionPanelContainer.active .roomPanel {
|
||||
padding-top: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.roomSelectionPanelContainer.selected .roomSelectionPanel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.roomSelectionPanelContainer.active .roomSelectionPanel {
|
||||
grid-template-rows: 1fr;
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
padding-top: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
div.roomContainer p.subtitle {
|
||||
padding-bottom: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.roomContainer {
|
||||
padding: var(--Spacing-x2);
|
||||
|
||||
@@ -138,20 +138,20 @@ export default function PriceList({
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{isUserLoggedIn
|
||||
? intl.formatMessage(
|
||||
{ id: "{memberPrice} {currency}" },
|
||||
{
|
||||
memberPrice: totalMemberRequestedPricePerNight,
|
||||
currency: publicRequestedPrice.currency,
|
||||
}
|
||||
)
|
||||
{ id: "{memberPrice} {currency}" },
|
||||
{
|
||||
memberPrice: totalMemberRequestedPricePerNight,
|
||||
currency: publicRequestedPrice.currency,
|
||||
}
|
||||
)
|
||||
: intl.formatMessage(
|
||||
{ id: "{publicPrice}/{memberPrice} {currency}" },
|
||||
{
|
||||
publicPrice: totalPublicRequestedPricePerNight,
|
||||
memberPrice: totalMemberRequestedPricePerNight,
|
||||
currency: publicRequestedPrice.currency,
|
||||
}
|
||||
)}
|
||||
{ id: "{publicPrice}/{memberPrice} {currency}" },
|
||||
{
|
||||
publicPrice: totalPublicRequestedPricePerNight,
|
||||
memberPrice: totalMemberRequestedPricePerNight,
|
||||
currency: publicRequestedPrice.currency,
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
cursor: pointer;
|
||||
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
||||
}
|
||||
|
||||
.checkIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
@@ -33,10 +34,18 @@
|
||||
align-items: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="radio"].radio {
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + .card {
|
||||
border: 1px solid var(--Primary-Dark-On-Surface-Divider);
|
||||
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + .card .checkIcon {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@@ -81,11 +90,13 @@ input[type="radio"]:checked + .card .checkIcon {
|
||||
.terms {
|
||||
padding-top: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.termsText:nth-child(n) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.termsIcon {
|
||||
padding-right: var(--Spacing-x1);
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function FlexibilityOption({
|
||||
const {
|
||||
actions: { selectRate },
|
||||
isMainRoom,
|
||||
roomNr,
|
||||
} = useRoomContext()
|
||||
|
||||
function handleSelect() {
|
||||
@@ -74,7 +75,8 @@ export default function FlexibilityOption({
|
||||
<label>
|
||||
<input
|
||||
checked={isSelected}
|
||||
name={`rateCode-${rate.rateCode}`}
|
||||
className={styles.radio}
|
||||
name={`rateCode-${roomNr}-${rate.rateCode}`}
|
||||
onChange={handleSelect}
|
||||
type="radio"
|
||||
value={rate.rateCode}
|
||||
|
||||
@@ -14,23 +14,29 @@ export default function RoomSize({ roomSize }: RoomSizeProps) {
|
||||
|
||||
if (roomSize.min === roomSize.max) {
|
||||
return (
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "{roomSize} m²" },
|
||||
{ roomSize: roomSize.min }
|
||||
)}
|
||||
</Caption>
|
||||
<>
|
||||
<Caption color="uiTextMediumContrast">∙</Caption>
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "{roomSize} m²" },
|
||||
{ roomSize: roomSize.min }
|
||||
)}
|
||||
</Caption>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "{roomSizeMin} - {roomSizeMax} m²" },
|
||||
{
|
||||
roomSizeMin: roomSize.min,
|
||||
roomSizeMax: roomSize.max,
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
<>
|
||||
<Caption color="uiTextMediumContrast">∙</Caption>
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{intl.formatMessage(
|
||||
{ id: "{roomSizeMin} - {roomSizeMax} m²" },
|
||||
{
|
||||
roomSizeMin: roomSize.min,
|
||||
roomSizeMax: roomSize.max,
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,13 +35,17 @@ function getBreakfastMessage(
|
||||
memberBreakfastIncluded: boolean,
|
||||
hotelType: string | undefined,
|
||||
userIsLoggedIn: boolean,
|
||||
msgs: Record<"included" | "noSelection" | "scandicgo" | "notIncluded", string>
|
||||
msgs: Record<
|
||||
"included" | "noSelection" | "scandicgo" | "notIncluded",
|
||||
string
|
||||
>,
|
||||
roomNr: number
|
||||
) {
|
||||
if (hotelType === HotelTypeEnum.ScandicGo) {
|
||||
return msgs.scandicgo
|
||||
}
|
||||
|
||||
if (userIsLoggedIn && memberBreakfastIncluded) {
|
||||
if (userIsLoggedIn && memberBreakfastIncluded && roomNr === 1) {
|
||||
return msgs.included
|
||||
}
|
||||
|
||||
@@ -81,7 +85,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
rateDefinitions: state.roomsAvailability?.rateDefinitions,
|
||||
roomCategories: state.roomCategories,
|
||||
}))
|
||||
const { isMainRoom, selectedPackage, selectedRate } = useRoomContext()
|
||||
const { isMainRoom, roomNr, selectedPackage, selectedRate } = useRoomContext()
|
||||
|
||||
const classNames = cardVariants({
|
||||
availability:
|
||||
@@ -105,7 +109,8 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
roomConfiguration.breakfastIncludedInAllRatesMember,
|
||||
hotelType,
|
||||
isUserLoggedIn,
|
||||
breakfastMessages
|
||||
breakfastMessages,
|
||||
roomNr
|
||||
)
|
||||
|
||||
if (!rateDefinitions) {
|
||||
@@ -124,7 +129,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
)
|
||||
)
|
||||
|
||||
const { name, roomSize, totalOccupancy, images } = selectedRoom || {}
|
||||
const { images, name, occupancy, roomSize } = selectedRoom || {}
|
||||
const galleryImages = mapApiImagesToGalleryImages(images || [])
|
||||
|
||||
const freeCancelation = intl.formatMessage({ id: "Free cancellation" })
|
||||
@@ -203,86 +208,87 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
||||
|
||||
return (
|
||||
<li className={classNames}>
|
||||
<div>
|
||||
<div className={styles.imageContainer}>
|
||||
<div className={styles.chipContainer}>
|
||||
{lessThanFiveRoomsLeft ? (
|
||||
<span className={styles.chip}>
|
||||
<Footnote color="burgundy" textTransform="uppercase">
|
||||
{intl.formatMessage(
|
||||
{ id: "{amount, number} left" },
|
||||
{ amount: roomConfiguration.roomsLeft }
|
||||
)}
|
||||
</Footnote>
|
||||
<div className={styles.imageContainer}>
|
||||
<div className={styles.chipContainer}>
|
||||
{lessThanFiveRoomsLeft ? (
|
||||
<span className={styles.chip}>
|
||||
<Footnote color="burgundy" textTransform="uppercase">
|
||||
{intl.formatMessage(
|
||||
{ id: "{amount, number} left" },
|
||||
{ amount: roomConfiguration.roomsLeft }
|
||||
)}
|
||||
</Footnote>
|
||||
</span>
|
||||
) : null}
|
||||
{roomConfiguration.features
|
||||
.filter((feature) => selectedPackage === feature.code)
|
||||
.map((feature) => (
|
||||
<span className={styles.chip} key={feature.code}>
|
||||
{createElement(getIconForFeatureCode(feature.code), {
|
||||
color: "burgundy",
|
||||
height: 16,
|
||||
width: 16,
|
||||
})}
|
||||
</span>
|
||||
) : null}
|
||||
{roomConfiguration.features
|
||||
.filter((feature) => selectedPackage === feature.code)
|
||||
.map((feature) => (
|
||||
<span className={styles.chip} key={feature.code}>
|
||||
{createElement(getIconForFeatureCode(feature.code), {
|
||||
color: "burgundy",
|
||||
height: 16,
|
||||
width: 16,
|
||||
})}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<ImageGallery
|
||||
images={galleryImages}
|
||||
title={roomConfiguration.roomType}
|
||||
fill
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ImageGallery
|
||||
images={galleryImages}
|
||||
title={roomConfiguration.roomType}
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.specification}>
|
||||
{totalOccupancy && (
|
||||
<Caption color="uiTextMediumContrast" className={styles.guests}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "Max {max, plural, one {{range} guest} other {{range} guests}}",
|
||||
},
|
||||
{ max: totalOccupancy.max, range: totalOccupancy.range }
|
||||
)}
|
||||
</Caption>
|
||||
<div className={styles.specification}>
|
||||
{occupancy && (
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{occupancy.max === occupancy.min
|
||||
? intl.formatMessage(
|
||||
{ id: "guests.plural" },
|
||||
{ guests: occupancy.max }
|
||||
)
|
||||
: intl.formatMessage({ id: "guests.span" }, occupancy)}
|
||||
</Caption>
|
||||
)}
|
||||
<RoomSize roomSize={roomSize} />
|
||||
<div className={styles.toggleSidePeek}>
|
||||
{roomConfiguration.roomTypeCode && (
|
||||
<ToggleSidePeek
|
||||
hotelId={hotelId.toString()}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
title={intl.formatMessage({ id: "Room details" })}
|
||||
/>
|
||||
)}
|
||||
<RoomSize roomSize={roomSize} />
|
||||
<div className={styles.toggleSidePeek}>
|
||||
{roomConfiguration.roomTypeCode && (
|
||||
<ToggleSidePeek
|
||||
hotelId={hotelId.toString()}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.roomDetails}>
|
||||
<Subtitle className={styles.name} type="two">
|
||||
{name}
|
||||
</Subtitle>
|
||||
{/* Out of scope for now
|
||||
</div>
|
||||
<div className={styles.roomDetails}>
|
||||
<Subtitle className={styles.name} type="two">
|
||||
{name}
|
||||
</Subtitle>
|
||||
{/* Out of scope for now
|
||||
<Body>{descriptions?.short}</Body>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
{roomConfiguration.status === AvailabilityEnum.NotAvailable ? (
|
||||
<div className={styles.noRoomsContainer}>
|
||||
<div className={styles.noRooms}>
|
||||
<ErrorCircleIcon color="red" width={16} />
|
||||
<Caption color="uiTextHighContrast" type="bold">
|
||||
{intl.formatMessage({
|
||||
id: "This room is not available",
|
||||
})}
|
||||
</Caption>
|
||||
<>
|
||||
{/** The empty div is used to allow for subgrid to align rows */}
|
||||
<div></div>
|
||||
<div className={styles.noRoomsContainer}>
|
||||
<div className={styles.noRooms}>
|
||||
<ErrorCircleIcon color="red" width={16} />
|
||||
<Caption color="uiTextHighContrast" type="bold">
|
||||
{intl.formatMessage({
|
||||
id: "This room is not available",
|
||||
})}
|
||||
</Caption>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Caption color="uiTextHighContrast" type="bold">
|
||||
{breakfastMessage}
|
||||
</Caption>
|
||||
<Caption color="uiTextHighContrast">{breakfastMessage}</Caption>
|
||||
{roomConfiguration.products.map((product) => {
|
||||
const rate = getRateInfo(product)
|
||||
const isSelectedRateCode =
|
||||
|
||||
@@ -1,112 +1,100 @@
|
||||
.card {
|
||||
font-size: 14px;
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
display: grid;
|
||||
font-size: 14px;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
grid-row: span 7;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: subgrid;
|
||||
background-color: #fff;
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
grid-row: span 5;
|
||||
padding: 0 var(--Spacing-x2) var(--Spacing-x2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div[data-multiroom="true"] .card {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card.noAvailability {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.specification {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--Spacing-x1);
|
||||
padding: 0 var(--Spacing-x1) 0 var(--Spacing-x-one-and-half);
|
||||
.imageContainer {
|
||||
margin: 0 calc(-1 * var(--Spacing-x2));
|
||||
min-height: 190px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.specification .guests {
|
||||
border-right: 1px solid var(--Base-Border-Subtle);
|
||||
padding-right: var(--Spacing-x1);
|
||||
div[data-multiroom="true"] .imageContainer {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chipContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x1);
|
||||
left: 12px;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.chip {
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.card .imageContainer img {
|
||||
aspect-ratio: 16/9;
|
||||
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
|
||||
max-width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.specification {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x1);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.toggleSidePeek {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.toggleSidePeek button {
|
||||
.specification .toggleSidePeek button {
|
||||
padding: 0;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2) var(--Spacing-x2);
|
||||
display: grid;
|
||||
grid-template-rows: subgrid;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
grid-row: span 4;
|
||||
}
|
||||
|
||||
/* Make sure rows with only unavailable rooms still has a min-height */
|
||||
.container:has(.noRoomsContainer) {
|
||||
min-height: 400px;
|
||||
grid-template-rows: auto repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.roomDetails {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2) 0;
|
||||
padding-bottom: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.name {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.card img {
|
||||
max-width: 100%;
|
||||
aspect-ratio: 16/9;
|
||||
object-fit: cover;
|
||||
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
|
||||
}
|
||||
|
||||
.flexibilityOptions {
|
||||
.container {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.chipContainer {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.chip {
|
||||
background-color: var(--Main-Grey-White);
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
min-height: 190px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.noRoomsContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
grid-row: span 4;
|
||||
grid-template-rows: subgrid;
|
||||
}
|
||||
|
||||
.noRooms {
|
||||
padding: var(--Spacing-x2);
|
||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
margin: 0;
|
||||
|
||||
display: flex;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
margin: 0;
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
@@ -41,16 +41,14 @@ export default function RoomSelectionPanel() {
|
||||
</div>
|
||||
) : null}
|
||||
<RoomTypeFilter />
|
||||
<div className={styles.wrapper}>
|
||||
<ul className={styles.roomList}>
|
||||
{rooms.map((roomConfiguration) => (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<ul className={styles.roomList}>
|
||||
{rooms.map((roomConfiguration) => (
|
||||
<RoomCard
|
||||
key={roomConfiguration.roomTypeCode}
|
||||
roomConfiguration={roomConfiguration}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.roomList input[type="radio"] {
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.hotelAlert {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -47,22 +47,26 @@ export default async function ParkingInformation({
|
||||
{intl.formatMessage({ id: "Weekday prices" })}
|
||||
</Caption>
|
||||
<Divider color="baseSurfaceSubtleHover" />
|
||||
<ParkingPrices
|
||||
pricing={parking.pricing.localCurrency?.ordinary}
|
||||
currency={parking.pricing.localCurrency?.currency}
|
||||
freeParking={parking.pricing.freeParking}
|
||||
/>
|
||||
{parking.pricing.localCurrency ? (
|
||||
<ParkingPrices
|
||||
currency={parking.pricing.localCurrency.currency}
|
||||
freeParking={parking.pricing.freeParking}
|
||||
pricing={parking.pricing.localCurrency.ordinary}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.priceWrapper}>
|
||||
<Caption color="uiTextMediumContrast" textTransform="uppercase">
|
||||
{intl.formatMessage({ id: "Weekend prices" })}
|
||||
</Caption>
|
||||
<Divider color="baseSurfaceSubtleHover" />
|
||||
<ParkingPrices
|
||||
pricing={parking.pricing.localCurrency?.weekend}
|
||||
currency={parking.pricing.localCurrency?.currency}
|
||||
freeParking={parking.pricing.freeParking}
|
||||
/>
|
||||
{parking.pricing.localCurrency ? (
|
||||
<ParkingPrices
|
||||
currency={parking.pricing.localCurrency.currency}
|
||||
freeParking={parking.pricing.freeParking}
|
||||
pricing={parking.pricing.localCurrency.weekend}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{parking.externalParkingUrl && showExternalParkingButton && (
|
||||
|
||||
@@ -55,10 +55,12 @@ a.text {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* TODO: The variants for combinations of size/text/wrapping should be looked at and iterated on */
|
||||
.text:not(.wrapping) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* VARIANTS */
|
||||
.default,
|
||||
a.default {
|
||||
@@ -827,3 +829,15 @@ a.default {
|
||||
.icon.tertiaryLightSecondary:disabled svg * {
|
||||
fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled);
|
||||
}
|
||||
|
||||
button.btn.clean {
|
||||
background: none;
|
||||
background-color: unset;
|
||||
border: none;
|
||||
border-color: unset;
|
||||
border-radius: unset;
|
||||
color: unset;
|
||||
gap: unset;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { ButtonProps } from "./button"
|
||||
export default function Button(props: ButtonProps) {
|
||||
const {
|
||||
className,
|
||||
clean,
|
||||
intent,
|
||||
size,
|
||||
theme,
|
||||
@@ -21,6 +22,7 @@ export default function Button(props: ButtonProps) {
|
||||
|
||||
const classNames = buttonVariants({
|
||||
className,
|
||||
clean,
|
||||
intent,
|
||||
size,
|
||||
theme,
|
||||
|
||||
@@ -28,12 +28,16 @@ export const buttonVariants = cva(styles.btn, {
|
||||
tertiaryDark: "",
|
||||
},
|
||||
variant: {
|
||||
clean: styles.clean,
|
||||
default: styles.default,
|
||||
icon: styles.icon,
|
||||
},
|
||||
wrapping: {
|
||||
true: styles.wrapping,
|
||||
},
|
||||
clean: {
|
||||
true: styles.clean,
|
||||
},
|
||||
fullWidth: {
|
||||
true: styles.fullWidth,
|
||||
},
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
div.chip {
|
||||
--chip-text-color: var(--Base-Text-High-contrast);
|
||||
--chip-background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
gap: var(--Spacing-x-half);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
color: var(--chip-text-color);
|
||||
background-color: var(--chip-background-color);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-half);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chip.small {
|
||||
padding: var(--Spacing-x-quarter) var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.chip.medium {
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.chip *,
|
||||
@@ -29,3 +36,8 @@ div.chip {
|
||||
.chip.tag {
|
||||
--chip-background-color: var(--Base-Surface-Subtle-Hover);
|
||||
}
|
||||
|
||||
.chip.uiTextHighContrast {
|
||||
--chip-background-color: var(--UI-Text-High-contrast);
|
||||
--chip-text-color: var(--UI-Input-Controls-On-Fill-Normal);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,15 @@ import { chipVariants } from "./variants"
|
||||
|
||||
import type { ChipProps } from "./chip"
|
||||
|
||||
export default function Chip({ children, className, variant }: ChipProps) {
|
||||
export default function Chip({
|
||||
children,
|
||||
className,
|
||||
size,
|
||||
variant,
|
||||
}: ChipProps) {
|
||||
const classNames = chipVariants({
|
||||
className,
|
||||
size,
|
||||
variant,
|
||||
})
|
||||
return (
|
||||
|
||||
@@ -4,14 +4,20 @@ import styles from "./chip.module.css"
|
||||
|
||||
export const chipVariants = cva(styles.chip, {
|
||||
variants: {
|
||||
size: {
|
||||
small: styles.small,
|
||||
medium: styles.medium,
|
||||
},
|
||||
variant: {
|
||||
default: styles.default,
|
||||
burgundy: styles.burgundy,
|
||||
transparent: styles.transparent,
|
||||
tag: styles.tag,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "medium",
|
||||
variant: "default",
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user