327 lines
10 KiB
TypeScript
327 lines
10 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import {
|
|
Discount22Icon,
|
|
MaterialIcon,
|
|
} from "@scandic-hotels/design-system/Icons"
|
|
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,
|
|
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(
|
|
{ id: "{adults, plural, one {# adult} other {# adults}}" },
|
|
{
|
|
adults: adults,
|
|
}
|
|
)
|
|
|
|
const childrenMsg = intl.formatMessage(
|
|
{
|
|
id: "{children, plural, one {# child} other {# children}}",
|
|
},
|
|
{
|
|
children: children,
|
|
}
|
|
)
|
|
|
|
const cancelledRoomsMsg = intl.formatMessage(
|
|
{ id: "{rooms, plural, one {# room} other {# rooms}}" },
|
|
{
|
|
rooms: cancelledRooms,
|
|
}
|
|
)
|
|
|
|
const roomCancelledRoomsMsg = intl.formatMessage({ id: "Room cancelled" })
|
|
|
|
const roomsMsg = intl.formatMessage(
|
|
{ id: "{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({ id: "Reference" })}
|
|
</Subtitle>
|
|
<Subtitle
|
|
color="uiTextHighContrast"
|
|
className={styles.titleDesktop}
|
|
>
|
|
{isCancelled && !isMultiRoom
|
|
? intl.formatMessage({ id: "Cancellation number" })
|
|
: intl.formatMessage({ id: "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({ id: "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({ id: "Cancellation" })}</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p className={styles.cancelledRooms}>
|
|
{isMultiRoom
|
|
? `${cancelledRoomsMsg} ${intl.formatMessage({ id: "cancelled" })}`
|
|
: roomCancelledRoomsMsg}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
{!allRoomsCancelled && (
|
|
<>
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>{intl.formatMessage({ id: "Check-in" })}</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{`${dt(booking.checkInDate).locale(lang).format("dddd, D MMMM")} ${intl.formatMessage({ id: "from" })} ${hotel.hotelFacts.checkin.checkInTime}`}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>{intl.formatMessage({ id: "Check-out" })}</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{`${dt(booking.checkOutDate).locale(lang).format("dddd, D MMMM")} ${intl.formatMessage({ id: "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({ id: "Booking guaranteed." })}
|
|
</strong>{" "}
|
|
{intl.formatMessage({
|
|
id: "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({ id: "Total" })}</p>
|
|
</Typography>
|
|
<TotalPrice variant="Title/Subtitle/md" type={priceType} />
|
|
</div>
|
|
{bookingCode && (
|
|
<div className={styles.referenceRow}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<p>{intl.formatMessage({ id: "Booking code" })}</p>
|
|
</Typography>
|
|
<IconChip
|
|
color="blue"
|
|
icon={<Discount22Icon color="Icon/Feedback/Information" />}
|
|
>
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
<p className={styles.bookingCode}>
|
|
<strong>{intl.formatMessage({ id: "Booking code" })}</strong>:{" "}
|
|
{bookingCode}
|
|
</p>
|
|
</Typography>
|
|
</IconChip>
|
|
</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({ id: "Get directions" })}
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
{isMultiRoom && (
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
<p className={styles.note}>
|
|
{intl.formatMessage({ id: "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}
|
|
{term.endsWith(".") ? " " : ". "}
|
|
</span>
|
|
))}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
)
|
|
}
|