feat: SW-602 Implemented no availability states

This commit is contained in:
Hrishikesh Vaipurkar
2024-11-14 23:20:04 +01:00
parent dc3516f4e1
commit a17010a431
14 changed files with 144 additions and 38 deletions

View File

@@ -84,25 +84,33 @@ export default async function SelectRatePage({
getProfileSafely(), getProfileSafely(),
]) ])
if (!roomsAvailability) {
return "No rooms found" // TODO: Add a proper error message
}
if (!hotelData) { if (!hotelData) {
return "No hotel data found" // TODO: Add a proper error message return "No hotel data found" // TODO: Add a proper error message
} }
const roomCategories = hotelData?.included const roomCategories = hotelData?.included
const noRoomsAvailable = roomsAvailability?.roomConfigurations.reduce(
(acc, room) => {
return acc && room.status === "NotAvailable"
},
true
)
return ( return (
<> <>
<HotelInfoCard hotelData={hotelData} /> <HotelInfoCard
<Rooms hotelData={hotelData}
roomsAvailability={roomsAvailability} noAvailability={!roomsAvailability || !!noRoomsAvailable}
roomCategories={roomCategories ?? []}
user={user}
packages={packages ?? []}
/> />
{roomsAvailability ? (
<Rooms
roomsAvailability={roomsAvailability}
roomCategories={roomCategories ?? []}
user={user}
packages={packages ?? []}
/>
) : null}
</> </>
) )
} }

View File

@@ -15,8 +15,12 @@ import TripAdvisorChip from "../../TripAdvisorChip"
import styles from "./hotelInfoCard.module.css" import styles from "./hotelInfoCard.module.css"
import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCardProps" import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCardProps"
import { AlertTypeEnum } from "@/types/enums/alert"
export default function HotelInfoCard({ hotelData }: HotelInfoCardProps) { export default function HotelInfoCard({
hotelData,
noAvailability = false,
}: HotelInfoCardProps) {
const hotelAttributes = hotelData?.data.attributes const hotelAttributes = hotelData?.data.attributes
const intl = useIntl() const intl = useIntl()
@@ -97,6 +101,16 @@ export default function HotelInfoCard({ hotelData }: HotelInfoCardProps) {
</div> </div>
) )
})} })}
{noAvailability ? (
<div className={styles.hotelAlert}>
<Alert
type={AlertTypeEnum.Info}
text={intl.formatMessage({
id: "There are no rooms available that match your request",
})}
/>
</div>
) : null}
</article> </article>
) )
} }

View File

@@ -1,5 +1,5 @@
.card, .card,
.disabledCard { .noPricesCard {
border-radius: var(--Corner-radius-Large); border-radius: var(--Corner-radius-Large);
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
background-color: var(--Base-Surface-Secondary-light-Normal); background-color: var(--Base-Surface-Secondary-light-Normal);
@@ -10,11 +10,11 @@
gap: var(--Spacing-x-half); gap: var(--Spacing-x-half);
} }
.disabledCard { .noPricesCard {
opacity: 0.6; gap: var(--Spacing-x2);
} }
.disabledCard:hover { .noPricesCard:hover {
cursor: not-allowed; cursor: not-allowed;
} }
@@ -62,9 +62,16 @@ input[type="radio"]:checked + .card .checkIcon {
outline: none; outline: none;
} }
.noPricesLabel {
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
text-align: center;
background-color: var(--Base-Surface-Subtle-Normal);
border-radius: var(--Corner-radius-Rounded);
margin: 0 auto var(--Spacing-x2);
}
.popover { .popover {
background-color: var(--Main-Grey-White); background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Medium); border-radius: var(--Corner-radius-Medium);
left: 0px; left: 0px;
max-height: 400px; max-height: 400px;

View File

@@ -1,8 +1,10 @@
"use client" "use client"
import { useRef, useState } from "react" import { useRef, useState } from "react"
import { Button } from "react-aria-components" import { Button } from "react-aria-components"
import { useIntl } from "react-intl"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons" import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import Label from "@/components/TempDesignSystem/Form/Label"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import PricePopover from "./Popover" import PricePopover from "./Popover"
@@ -27,6 +29,7 @@ export default function FlexibilityOption({
const [isPopoverOpen, setIsPopoverOpen] = useState(false) const [isPopoverOpen, setIsPopoverOpen] = useState(false)
let triggerRef = useRef<HTMLButtonElement>(null) let triggerRef = useRef<HTMLButtonElement>(null)
const buttonClickedRef = useRef(false) const buttonClickedRef = useRef(false)
const intl = useIntl()
function setRef(node: Element | null) { function setRef(node: Element | null) {
if (node) { if (node) {
@@ -36,13 +39,17 @@ export default function FlexibilityOption({
if (!product) { if (!product) {
return ( return (
<div className={styles.disabledCard}> <div className={styles.noPricesCard}>
<div className={styles.header}> <div className={styles.header}>
<InfoCircleIcon width={16} height={16} color="uiTextMediumContrast" /> <InfoCircleIcon width={16} height={16} color="uiTextMediumContrast" />
<Caption color="disabled">{name}</Caption> <Caption>{name}</Caption>
<Caption color="disabled">({paymentTerm})</Caption> <Caption color="uiTextPlaceholder">({paymentTerm})</Caption>
</div> </div>
<PriceTable /> <Label size="regular" className={styles.noPricesLabel}>
<Caption color="uiTextHighContrast" type="bold">
{intl.formatMessage({ id: "No Prices available" })}
</Caption>
</Label>
</div> </div>
) )
} }

View File

@@ -0,0 +1,12 @@
import { cva } from "class-variance-authority"
import styles from "./roomCard.module.css"
export const cardVariants = cva(styles.card, {
variants: {
availability: {
noAvailability: styles.noAvailability,
default: "",
},
},
})

View File

@@ -7,12 +7,14 @@ import { RateDefinition } from "@/server/routers/hotels/output"
import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek" import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek"
import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption" import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption"
import { ErrorCircleIcon } from "@/components/Icons"
import ImageGallery from "@/components/ImageGallery" import ImageGallery from "@/components/ImageGallery"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIconForFeatureCode } from "../../utils" import { getIconForFeatureCode } from "../../utils"
import { cardVariants } from "./cardVariants"
import styles from "./roomCard.module.css" import styles from "./roomCard.module.css"
@@ -89,8 +91,15 @@ export default function RoomCard({
[freeCancelation, freeBooking, nonRefundable] [freeCancelation, freeBooking, nonRefundable]
) )
const classNames = cardVariants({
availability:
roomConfiguration.status === "NotAvailable"
? "noAvailability"
: "default",
})
return ( return (
<div className={styles.card}> <div className={classNames}>
<div> <div>
{mainImage && ( {mainImage && (
<div className={styles.imageContainer}> <div className={styles.imageContainer}>
@@ -163,23 +172,36 @@ export default function RoomCard({
id: "Breakfast selection in next step.", id: "Breakfast selection in next step.",
})} })}
</Caption> </Caption>
<div className={styles.flexibilityOptions}> {roomConfiguration.status === "NotAvailable" ? (
{Object.entries(rates).map(([key, rate]) => ( <div className={styles.noRoomsContainer}>
<FlexibilityOption <div className={styles.noRooms}>
key={key} <ErrorCircleIcon color="red" width={16} />
name={rateKey(key)} <Caption color="uiTextHighContrast" type="bold">
value={key.toLowerCase()} {intl.formatMessage({
paymentTerm={key === "flexRate" ? payLater : payNow} id: "This room is not available",
product={findProductForRate(rate)} })}
priceInformation={getPriceInformationForRate(rate)} </Caption>
handleSelectRate={handleSelectRate} </div>
roomType={roomConfiguration.roomType} </div>
roomTypeCode={roomConfiguration.roomTypeCode} ) : (
features={roomConfiguration.features} <div className={styles.flexibilityOptions}>
petRoomPackage={petRoomPackage} {Object.entries(rates).map(([key, rate]) => (
/> <FlexibilityOption
))} key={key}
</div> name={rateKey(key)}
value={key.toLowerCase()}
paymentTerm={key === "flexRate" ? payLater : payNow}
product={findProductForRate(rate)}
priceInformation={getPriceInformationForRate(rate)}
handleSelectRate={handleSelectRate}
roomType={roomConfiguration.roomType}
roomTypeCode={roomConfiguration.roomTypeCode}
features={roomConfiguration.features}
petRoomPackage={petRoomPackage}
/>
))}
</div>
)}
</div> </div>
</div> </div>
) )

View File

@@ -10,6 +10,22 @@
justify-content: space-between; justify-content: space-between;
} }
.card.noAvailability {
justify-content: flex-start;
}
.card.noAvailability:before {
background-color: rgba(0, 0, 0, 40%);
content: "";
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
}
.specification { .specification {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -79,3 +95,16 @@
min-height: 190px; min-height: 190px;
position: relative; position: relative;
} }
.noRoomsContainer {
padding: var(--Spacing-x2);
background-color: var(--Base-Surface-Secondary-light-Normal);
border-radius: var(--Corner-radius-Medium);
margin: 0;
width: 100%;
}
.noRooms {
display: flex;
gap: var(--Spacing-x1);
}

View File

@@ -339,6 +339,7 @@
"There are no rooms available that match your request.": "Der er ingen værelser tilgængelige, der matcher din forespørgsel.", "There are no rooms available that match your request.": "Der er ingen værelser tilgængelige, der matcher din forespørgsel.",
"There are no transactions to display": "Der er ingen transaktioner at vise", "There are no transactions to display": "Der er ingen transaktioner at vise",
"Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}", "Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}",
"This room is not available": "Dette værelse er ikke tilgængeligt",
"To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "For at få medlemsprisen <span>{amount} {currency}</span>, log ind eller tilmeld dig, når du udfylder bookingen.", "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "For at få medlemsprisen <span>{amount} {currency}</span>, log ind eller tilmeld dig, når du udfylder bookingen.",
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For at sikre din reservation, beder vi om at du giver os dine betalingsoplysninger. Du kan så være sikker på, at ingen gebyrer vil blive opkrævet på dette tidspunkt.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For at sikre din reservation, beder vi om at du giver os dine betalingsoplysninger. Du kan så være sikker på, at ingen gebyrer vil blive opkrævet på dette tidspunkt.",
"Total Points": "Samlet antal point", "Total Points": "Samlet antal point",

View File

@@ -338,6 +338,7 @@
"There are no rooms available that match your request.": "Es sind keine Zimmer verfügbar, die Ihrer Anfrage entsprechen.", "There are no rooms available that match your request.": "Es sind keine Zimmer verfügbar, die Ihrer Anfrage entsprechen.",
"There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden", "There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden",
"Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}", "Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}",
"This room is not available": "Dieses Zimmer ist nicht verfügbar",
"To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "Um den Mitgliederpreis von <span>{amount} {currency}</span> zu erhalten, loggen Sie sich ein oder treten Sie Scandic Friends bei, wenn Sie die Buchung abschließen.", "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "Um den Mitgliederpreis von <span>{amount} {currency}</span> zu erhalten, loggen Sie sich ein oder treten Sie Scandic Friends bei, wenn Sie die Buchung abschließen.",
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Um Ihre Reservierung zu sichern, bitten wir Sie, Ihre Zahlungskarteninformationen zu geben. Sie können sicher sein, dass keine Gebühren zu diesem Zeitpunkt erhoben werden.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Um Ihre Reservierung zu sichern, bitten wir Sie, Ihre Zahlungskarteninformationen zu geben. Sie können sicher sein, dass keine Gebühren zu diesem Zeitpunkt erhoben werden.",
"Total Points": "Gesamtpunktzahl", "Total Points": "Gesamtpunktzahl",

View File

@@ -368,6 +368,7 @@
"There are no rooms available that match your request.": "There are no rooms available that match your request.", "There are no rooms available that match your request.": "There are no rooms available that match your request.",
"There are no transactions to display": "There are no transactions to display", "There are no transactions to display": "There are no transactions to display",
"Things nearby HOTEL_NAME": "Things nearby {hotelName}", "Things nearby HOTEL_NAME": "Things nearby {hotelName}",
"This room is not available": "This room is not available",
"To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.", "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.",
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.",
"Total Points": "Total Points", "Total Points": "Total Points",

View File

@@ -340,6 +340,7 @@
"There are no rooms available that match your request.": "Ei huoneita saatavilla, jotka vastaavat pyyntöäsi.", "There are no rooms available that match your request.": "Ei huoneita saatavilla, jotka vastaavat pyyntöäsi.",
"There are no transactions to display": "Näytettäviä tapahtumia ei ole", "There are no transactions to display": "Näytettäviä tapahtumia ei ole",
"Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}", "Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}",
"This room is not available": "Tämä huone ei ole käytettävissä",
"To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "Jäsenhintaan saavat sisäänkirjautuneet tai liittyneet jäsenet.", "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "Jäsenhintaan saavat sisäänkirjautuneet tai liittyneet jäsenet.",
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Varmistaaksesi varauksen, pyydämme sinua antamaan meille maksukortin tiedot. Varmista, että ei veloiteta maksusi tällä hetkellä.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Varmistaaksesi varauksen, pyydämme sinua antamaan meille maksukortin tiedot. Varmista, että ei veloiteta maksusi tällä hetkellä.",
"Total Points": "Kokonaispisteet", "Total Points": "Kokonaispisteet",

View File

@@ -337,6 +337,7 @@
"There are no rooms available that match your request.": "Det er ingen rom tilgjengelige som matcher din forespørsel.", "There are no rooms available that match your request.": "Det er ingen rom tilgjengelige som matcher din forespørsel.",
"There are no transactions to display": "Det er ingen transaksjoner å vise", "There are no transactions to display": "Det er ingen transaksjoner å vise",
"Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}", "Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}",
"This room is not available": "Dette rommet er ikke tilgjengelig",
"To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "For å få medlemsprisen <span>{amount} {currency}</span>, logg inn eller bli med når du fullfører bestillingen.", "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "For å få medlemsprisen <span>{amount} {currency}</span>, logg inn eller bli med når du fullfører bestillingen.",
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For å sikre din reservasjon, ber vi om at du gir oss dine betalingskortdetaljer. Vær sikker på at ingen gebyrer vil bli belastet på dette tidspunktet.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For å sikre din reservasjon, ber vi om at du gir oss dine betalingskortdetaljer. Vær sikker på at ingen gebyrer vil bli belastet på dette tidspunktet.",
"Total Points": "Totale poeng", "Total Points": "Totale poeng",

View File

@@ -337,6 +337,7 @@
"There are no rooms available that match your request.": "Det finns inga rum tillgängliga som matchar din begäran.", "There are no rooms available that match your request.": "Det finns inga rum tillgängliga som matchar din begäran.",
"There are no transactions to display": "Det finns inga transaktioner att visa", "There are no transactions to display": "Det finns inga transaktioner att visa",
"Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}", "Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}",
"This room is not available": "Detta rum är inte tillgängligt",
"To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "För att få medlemsprisen <span>{amount} {currency}</span>, logga in eller bli medlem när du slutför bokningen.", "To get the member price <span>{amount} {currency}</span>, log in or join when completing the booking.": "För att få medlemsprisen <span>{amount} {currency}</span>, logga in eller bli medlem när du slutför bokningen.",
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "För att säkra din bokning ber vi om att du ger oss dina betalkortdetaljer. Välj säker på att ingen avgifter kommer att debiteras just nu.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "För att säkra din bokning ber vi om att du ger oss dina betalkortdetaljer. Välj säker på att ingen avgifter kommer att debiteras just nu.",
"Total Points": "Poäng totalt", "Total Points": "Poäng totalt",

View File

@@ -2,4 +2,5 @@ import type { HotelData } from "@/types/hotel"
export type HotelInfoCardProps = { export type HotelInfoCardProps = {
hotelData: HotelData | null hotelData: HotelData | null
noAvailability: boolean
} }