feat: refactor of my stay
This commit is contained in:
committed by
Simon.Emanuelsson
parent
b5deb84b33
commit
ec087a3d15
@@ -1,397 +1,48 @@
|
||||
"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 Actions from "./Actions"
|
||||
import BookingCode from "./BookingCode"
|
||||
import Cancellations from "./Cancellations"
|
||||
import Dates from "./Dates"
|
||||
import GuaranteeInfo from "./GuaranteeInfo"
|
||||
import Guests from "./Guests"
|
||||
import Reference from "./Reference"
|
||||
import Room from "./Room"
|
||||
|
||||
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) {
|
||||
export function ReferenceCard() {
|
||||
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>
|
||||
<Reference />
|
||||
<Dates />
|
||||
<Guests />
|
||||
<Room />
|
||||
<Cancellations />
|
||||
<GuaranteeInfo />
|
||||
<Divider color="subtle" />
|
||||
|
||||
<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">
|
||||
<div className={styles.row}>
|
||||
<Typography variant="Body/Lead text">
|
||||
<p>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Total",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
<TotalPrice variant="Title/Subtitle/md" type={priceType} />
|
||||
<TotalPrice />
|
||||
</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>
|
||||
<BookingCode />
|
||||
<Actions />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user