398 lines
12 KiB
TypeScript
398 lines
12 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
|
|
import { BookingStatusEnum } from "@/constants/booking"
|
|
import { dt } from "@/lib/dt"
|
|
import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
|
|
import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
|
|
|
|
import Button from "@/components/TempDesignSystem/Button"
|
|
import Divider from "@/components/TempDesignSystem/Divider"
|
|
import IconChip from "@/components/TempDesignSystem/IconChip"
|
|
import Link from "@/components/TempDesignSystem/Link"
|
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
import { useGuaranteePaymentFailedToast } from "@/hooks/booking/useGuaranteePaymentFailedToast"
|
|
import useLang from "@/hooks/useLang"
|
|
|
|
import ManageStay from "../ManageStay"
|
|
import TotalPrice from "../Rooms/TotalPrice"
|
|
import { mapRoomDetails } from "../utils/mapRoomDetails"
|
|
import ReferenceCardSkeleton from "./ReferenceCardSkeleton"
|
|
|
|
import styles from "./referenceCard.module.css"
|
|
|
|
import type { Hotel, Room } from "@/types/hotel"
|
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
|
import type { CreditCard } from "@/types/user"
|
|
|
|
interface ReferenceCardProps {
|
|
booking: BookingConfirmation["booking"]
|
|
hotel: Hotel
|
|
room:
|
|
| (Room & {
|
|
bedType: Room["roomTypes"][number]
|
|
})
|
|
| null
|
|
savedCreditCards: CreditCard[] | null
|
|
refId: string
|
|
isLoggedIn: boolean
|
|
}
|
|
|
|
export function ReferenceCard({
|
|
booking,
|
|
hotel,
|
|
room,
|
|
savedCreditCards,
|
|
refId,
|
|
isLoggedIn,
|
|
}: ReferenceCardProps) {
|
|
const intl = useIntl()
|
|
const lang = useLang()
|
|
const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
|
|
const linkedReservationRooms = useMyStayRoomDetailsStore(
|
|
(state) => state.linkedReservationRooms
|
|
)
|
|
const addBookedRoom = useMyStayRoomDetailsStore(
|
|
(state) => state.actions.addBookedRoom
|
|
)
|
|
const addRoomPrice = useMyStayTotalPriceStore(
|
|
(state) => state.actions.addRoomPrice
|
|
)
|
|
|
|
// Initialize store with server data
|
|
useEffect(() => {
|
|
// Add price and details for booked room (main room or single room)
|
|
addRoomPrice({
|
|
id: booking.confirmationNumber,
|
|
totalPrice:
|
|
booking.reservationStatus === BookingStatusEnum.Cancelled
|
|
? 0
|
|
: booking.totalPrice,
|
|
currencyCode: booking.currencyCode,
|
|
isMainBooking: true,
|
|
roomPoints: booking.roomPoints,
|
|
})
|
|
addBookedRoom(
|
|
mapRoomDetails({
|
|
booking,
|
|
room,
|
|
roomNumber: 1,
|
|
})
|
|
)
|
|
}, [booking, room, addBookedRoom, addRoomPrice])
|
|
|
|
useGuaranteePaymentFailedToast()
|
|
|
|
if (!bookedRoom.roomNumber) return <ReferenceCardSkeleton />
|
|
|
|
const {
|
|
confirmationNumber,
|
|
cancellationNumber,
|
|
checkInDate,
|
|
checkOutDate,
|
|
isCancelled,
|
|
bookingCode,
|
|
rateDefinition,
|
|
priceType,
|
|
} = bookedRoom
|
|
|
|
const isMultiRoom = bookedRoom.linkedReservations.length > 0
|
|
|
|
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
|
|
|
const allRooms = [bookedRoom, ...linkedReservationRooms]
|
|
|
|
const adults = allRooms
|
|
.filter((room) => !room.isCancelled)
|
|
.reduce((acc, room) => acc + room.adults, 0)
|
|
|
|
const children = allRooms
|
|
.filter((room) => !room.isCancelled)
|
|
.reduce((acc, room) => acc + (room.childrenAges?.length ?? 0), 0)
|
|
|
|
const cancelledRooms = allRooms.filter((room) => room.isCancelled).length
|
|
const allRoomsCancelled = allRooms.every((room) => room.isCancelled)
|
|
|
|
const adultsMsg = intl.formatMessage(
|
|
{
|
|
defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
|
|
},
|
|
{
|
|
adults: adults,
|
|
}
|
|
)
|
|
|
|
const childrenMsg = intl.formatMessage(
|
|
{
|
|
defaultMessage: "{children, plural, one {# child} other {# children}}",
|
|
},
|
|
{
|
|
children: children,
|
|
}
|
|
)
|
|
|
|
const cancelledRoomsMsg = intl.formatMessage(
|
|
{
|
|
defaultMessage: "{rooms, plural, one {# room} other {# rooms}}",
|
|
},
|
|
{
|
|
rooms: cancelledRooms,
|
|
}
|
|
)
|
|
|
|
const roomCancelledRoomsMsg = intl.formatMessage({
|
|
defaultMessage: "Room cancelled",
|
|
})
|
|
|
|
const roomsMsg = intl.formatMessage(
|
|
{
|
|
defaultMessage: "{rooms, plural, one {# room} other {# rooms}}",
|
|
},
|
|
{
|
|
rooms: allRooms.filter((room) => !room.isCancelled).length,
|
|
}
|
|
)
|
|
const adultsOnlyMsg = adultsMsg
|
|
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
|
const adultsAndRoomsMsg = [adultsMsg, roomsMsg].join(", ")
|
|
const adultsAndChildrenAndRoomsMsg = [adultsMsg, childrenMsg, roomsMsg].join(
|
|
", "
|
|
)
|
|
|
|
return (
|
|
<div className={styles.referenceCard}>
|
|
{!isMultiRoom && (
|
|
<>
|
|
<div className={styles.referenceRow}>
|
|
<Subtitle color="uiTextHighContrast" className={styles.titleMobile}>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Reference",
|
|
})}
|
|
</Subtitle>
|
|
<Subtitle
|
|
color="uiTextHighContrast"
|
|
className={styles.titleDesktop}
|
|
>
|
|
{isCancelled && !isMultiRoom
|
|
? intl.formatMessage({
|
|
defaultMessage: "Cancellation number",
|
|
})
|
|
: intl.formatMessage({
|
|
defaultMessage: "Reference number",
|
|
})}
|
|
</Subtitle>
|
|
<Subtitle color="uiTextHighContrast">
|
|
{isCancelled && !isMultiRoom
|
|
? cancellationNumber
|
|
: confirmationNumber}
|
|
</Subtitle>
|
|
</div>
|
|
|
|
<Divider color="subtle" className={styles.divider} />
|
|
</>
|
|
)}
|
|
|
|
{!allRoomsCancelled && (
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Guests",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{allRooms.length > 1
|
|
? children > 0
|
|
? adultsAndChildrenAndRoomsMsg
|
|
: adultsAndRoomsMsg
|
|
: children > 0
|
|
? adultsAndChildrenMsg
|
|
: adultsOnlyMsg}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
{allRooms.some((room) => room.isCancelled) && (
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Cancellation",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p className={styles.cancelledRooms}>
|
|
{isMultiRoom
|
|
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
|
`${cancelledRoomsMsg} ${intl.formatMessage({
|
|
defaultMessage: "cancelled",
|
|
})}`
|
|
: roomCancelledRoomsMsg}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
{!allRoomsCancelled && (
|
|
<>
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Check-in",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{`${dt(checkInDate).locale(lang).format("dddd, D MMMM")} ${intl.formatMessage(
|
|
{
|
|
defaultMessage: "from",
|
|
}
|
|
)} ${hotel.hotelFacts.checkin.checkInTime}`}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Check-out",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{`${dt(checkOutDate).locale(lang).format("dddd, D MMMM")} ${intl.formatMessage(
|
|
{
|
|
defaultMessage: "until",
|
|
}
|
|
)} ${hotel.hotelFacts.checkin.checkOutTime}`}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
</>
|
|
)}
|
|
<Divider color="subtle" className={styles.divider} />
|
|
{booking.guaranteeInfo && !allRoomsCancelled && (
|
|
<>
|
|
<div className={styles.guaranteed}>
|
|
<MaterialIcon
|
|
icon="check_circle"
|
|
color="Icon/Feedback/Success"
|
|
size={20}
|
|
/>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<p className={styles.guaranteedText}>
|
|
<strong>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Booking guaranteed.",
|
|
})}
|
|
</strong>
|
|
{/* eslint-disable formatjs/no-literal-string-in-jsx */}{" "}
|
|
{/* eslint-enable formatjs/no-literal-string-in-jsx */}
|
|
{intl.formatMessage({
|
|
defaultMessage:
|
|
"Your stay remains available for check-in after 18:00.",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
<Divider color="subtle" className={styles.divider} />
|
|
</>
|
|
)}
|
|
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Total",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<TotalPrice variant="Title/Subtitle/md" type={priceType} />
|
|
</div>
|
|
{bookingCode && (
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Booking code",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
<IconChip
|
|
color="blue"
|
|
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
|
>
|
|
{intl.formatMessage(
|
|
{
|
|
defaultMessage: "<strong>Booking code</strong>: {value}",
|
|
},
|
|
{
|
|
value: bookingCode,
|
|
strong: (text) => (
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
<strong>{text}</strong>
|
|
</Typography>
|
|
),
|
|
}
|
|
)}
|
|
</IconChip>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
<div className={styles.actionArea}>
|
|
<ManageStay
|
|
hotel={hotel}
|
|
savedCreditCards={savedCreditCards}
|
|
refId={refId}
|
|
isLoggedIn={isLoggedIn}
|
|
/>
|
|
<Button fullWidth intent="secondary" asChild size="small">
|
|
<Link href={directionsUrl} target="_blank">
|
|
{intl.formatMessage({
|
|
defaultMessage: "Get directions",
|
|
})}
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
{isMultiRoom && (
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
<p className={styles.note}>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Multi-room stay",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
)}
|
|
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<p
|
|
className={`${styles.note} ${allRoomsCancelled ? styles.cancelledNote : ""}`}
|
|
>
|
|
{rateDefinition.generalTerms.map((term) => (
|
|
<span key={term}>
|
|
{term}
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{term.endsWith(".") ? " " : ". "}
|
|
</span>
|
|
))}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
)
|
|
}
|