Merged in feat(SW-1722)-mystay-multiroom-view (pull request #1396)
Feat(SW-1722) mystay multiroom view * feat(SW-1722) View all rooms on my stay * feat(sW-1722) Show linked reservation * feat(SW-1722) merged monorepo * feat(SW-1722) yarn install * feat(SW-1722) removed unused data * feat(SW-1722) Streaming data from the server to the client Approved-by: Niclas Edenvin
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -12,10 +17,10 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
|||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { Toast } from "@/components/TempDesignSystem/Toasts"
|
import { Toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
import { getIntl } from "@/i18n"
|
import useLang from "@/hooks/useLang"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice"
|
||||||
import SummaryCard from "./SummaryCard"
|
import SummaryCard from "./SummaryCard"
|
||||||
|
|
||||||
import styles from "./bookingSummary.module.css"
|
import styles from "./bookingSummary.module.css"
|
||||||
@@ -28,12 +33,22 @@ interface BookingSummaryProps {
|
|||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function BookingSummary({
|
export default function BookingSummary({
|
||||||
booking,
|
booking,
|
||||||
hotel,
|
hotel,
|
||||||
}: BookingSummaryProps) {
|
}: BookingSummaryProps) {
|
||||||
const intl = await getIntl()
|
const intl = useIntl()
|
||||||
const lang = getLang()
|
const lang = useLang()
|
||||||
|
const { totalPrice, currencyCode, addRoomPrice } = useMyStayTotalPriceStore()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addRoomPrice({
|
||||||
|
id: booking.confirmationNumber ?? "",
|
||||||
|
totalPrice: booking.totalPrice,
|
||||||
|
currencyCode: booking.currencyCode,
|
||||||
|
isMainBooking: true,
|
||||||
|
})
|
||||||
|
}, [booking, addRoomPrice])
|
||||||
|
|
||||||
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
||||||
const isPaid =
|
const isPaid =
|
||||||
@@ -49,7 +64,7 @@ export default async function BookingSummary({
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
<div className={styles.bookingSummaryContent}>
|
<div className={styles.bookingSummaryContent}>
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
title={formatPrice(intl, booking.totalPrice, booking.currencyCode)}
|
title={formatPrice(intl, totalPrice, currencyCode)}
|
||||||
image={{
|
image={{
|
||||||
src: "/_static/img/scandic-coin.svg",
|
src: "/_static/img/scandic-coin.svg",
|
||||||
alt: "Scandic coin",
|
alt: "Scandic coin",
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||||
|
|
||||||
|
import styles from "./linkedReservation.module.css"
|
||||||
|
|
||||||
|
export default function LinkedReservationSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className={styles.linkedReservation}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width: "100px", height: "24px" }}
|
||||||
|
>
|
||||||
|
<SkeletonShimmer width="100px" height="24px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.details}>
|
||||||
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width: "200px", height: "18px" }}
|
||||||
|
>
|
||||||
|
<SkeletonShimmer width="200px" height="18px" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.guests}>
|
||||||
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width: "150px", height: "18px" }}
|
||||||
|
>
|
||||||
|
<SkeletonShimmer width="150px" height="18px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.dates}>
|
||||||
|
<div className={styles.date}>
|
||||||
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width: "80px", height: "18px" }}
|
||||||
|
>
|
||||||
|
<SkeletonShimmer width="80px" height="18px" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width: "160px", height: "18px" }}
|
||||||
|
>
|
||||||
|
<SkeletonShimmer width="160px" height="18px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.date}>
|
||||||
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width: "80px", height: "18px" }}
|
||||||
|
>
|
||||||
|
<SkeletonShimmer width="80px" height="18px" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.skeleton}
|
||||||
|
style={{ width: "160px", height: "18px" }}
|
||||||
|
>
|
||||||
|
<SkeletonShimmer width="160px" height="18px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
"use client"
|
||||||
|
import { use, useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice"
|
||||||
|
|
||||||
|
import styles from "./linkedReservation.module.css"
|
||||||
|
|
||||||
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
|
interface LinkedReservationProps {
|
||||||
|
bookingPromise: Promise<BookingConfirmation | null>
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LinkedReservation({
|
||||||
|
bookingPromise,
|
||||||
|
index,
|
||||||
|
}: LinkedReservationProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const lang = useLang()
|
||||||
|
|
||||||
|
const { addRoomPrice } = useMyStayTotalPriceStore()
|
||||||
|
|
||||||
|
const bookingConfirmation = use(bookingPromise)
|
||||||
|
const { booking } = bookingConfirmation ?? {}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (booking) {
|
||||||
|
addRoomPrice({
|
||||||
|
id: booking.confirmationNumber ?? "",
|
||||||
|
totalPrice: booking.totalPrice,
|
||||||
|
currencyCode: booking.currencyCode,
|
||||||
|
isMainBooking: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [booking, addRoomPrice])
|
||||||
|
|
||||||
|
if (!booking) return null
|
||||||
|
|
||||||
|
const fromDate = dt(booking.checkInDate).locale(lang)
|
||||||
|
const toDate = dt(booking.checkOutDate).locale(lang)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className={styles.linkedReservation}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
<Subtitle color="burgundy">
|
||||||
|
{intl.formatMessage({ id: "Room" }) + " " + (index + 2)}
|
||||||
|
</Subtitle>
|
||||||
|
</div>
|
||||||
|
<div className={styles.details}>
|
||||||
|
<Caption textTransform="uppercase" type="bold">
|
||||||
|
{intl.formatMessage({ id: "Reference" })} {booking.confirmationNumber}
|
||||||
|
</Caption>
|
||||||
|
<div className={styles.guests}>
|
||||||
|
<Caption color="uiTextHighContrast">
|
||||||
|
{booking.childrenAges.length > 0
|
||||||
|
? intl.formatMessage(
|
||||||
|
{ id: "{adults} adults, {children} children" },
|
||||||
|
{
|
||||||
|
adults: booking.adults,
|
||||||
|
children: booking.childrenAges.length,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: intl.formatMessage(
|
||||||
|
{ id: "{adults} adults" },
|
||||||
|
{
|
||||||
|
adults: booking.adults,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.dates}>
|
||||||
|
<div className={styles.date}>
|
||||||
|
<Caption
|
||||||
|
type="bold"
|
||||||
|
textTransform="uppercase"
|
||||||
|
color="uiTextHighContrast"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "Check-in" })}
|
||||||
|
</Caption>
|
||||||
|
<Caption color="uiTextHighContrast">
|
||||||
|
{`${fromDate.format("dddd, D MMMM")} `}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
<div className={styles.date}>
|
||||||
|
<Caption
|
||||||
|
type="bold"
|
||||||
|
textTransform="uppercase"
|
||||||
|
color="uiTextHighContrast"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "Check-out" })}
|
||||||
|
</Caption>
|
||||||
|
<Caption color="uiTextHighContrast">
|
||||||
|
{`${toDate.format("dddd, D MMMM")} `}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
.linkedReservation {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
|
padding: var(--Spacing-x3);
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
border-right: 1px solid var(--Base-Border-Normal);
|
||||||
|
width: 40%;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 var(--Spacing-x1);
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.dates {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
@@ -27,17 +27,19 @@ import type { EventAttributes } from "ics"
|
|||||||
import type { Hotel } from "@/types/hotel"
|
import type { Hotel } from "@/types/hotel"
|
||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
|
interface ActionPanelProps {
|
||||||
|
booking: BookingConfirmation["booking"]
|
||||||
|
hotel: Hotel
|
||||||
|
showCancelButton: boolean
|
||||||
|
onCancelClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
export default function ActionPanel({
|
export default function ActionPanel({
|
||||||
booking,
|
booking,
|
||||||
hotel,
|
hotel,
|
||||||
showCancelButton,
|
showCancelButton,
|
||||||
onCancelClick,
|
onCancelClick,
|
||||||
}: {
|
}: ActionPanelProps) {
|
||||||
booking: BookingConfirmation["booking"]
|
|
||||||
hotel: Hotel
|
|
||||||
showCancelButton: boolean
|
|
||||||
onCancelClick: () => void
|
|
||||||
}) {
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
|
|||||||
@@ -24,17 +24,19 @@ import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmat
|
|||||||
|
|
||||||
type ActiveView = "actionPanel" | "cancelStay"
|
type ActiveView = "actionPanel" | "cancelStay"
|
||||||
|
|
||||||
|
interface ManageStayProps {
|
||||||
|
booking: BookingConfirmation["booking"]
|
||||||
|
hotel: Hotel
|
||||||
|
setBookingStatus: (status: BookingStatusEnum) => void
|
||||||
|
bookingStatus: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export default function ManageStay({
|
export default function ManageStay({
|
||||||
booking,
|
booking,
|
||||||
hotel,
|
hotel,
|
||||||
setBookingStatus,
|
setBookingStatus,
|
||||||
bookingStatus,
|
bookingStatus,
|
||||||
}: {
|
}: ManageStayProps) {
|
||||||
booking: BookingConfirmation["booking"]
|
|
||||||
hotel: Hotel
|
|
||||||
setBookingStatus: (status: BookingStatusEnum) => void
|
|
||||||
bookingStatus: string | null
|
|
||||||
}) {
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [animation, setAnimation] = useState<AnimationState>(
|
const [animation, setAnimation] = useState<AnimationState>(
|
||||||
AnimationStateEnum.visible
|
AnimationStateEnum.visible
|
||||||
|
|||||||
@@ -17,22 +17,23 @@ import useLang from "@/hooks/useLang"
|
|||||||
import { formatPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import ManageStay from "../ManageStay"
|
import ManageStay from "../ManageStay"
|
||||||
|
import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice"
|
||||||
|
|
||||||
import styles from "./referenceCard.module.css"
|
import styles from "./referenceCard.module.css"
|
||||||
|
|
||||||
import type { Hotel } from "@/types/hotel"
|
import type { Hotel } from "@/types/hotel"
|
||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
export function ReferenceCard({
|
interface ReferenceCardProps {
|
||||||
booking,
|
|
||||||
hotel,
|
|
||||||
}: {
|
|
||||||
booking: BookingConfirmation["booking"]
|
booking: BookingConfirmation["booking"]
|
||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
export function ReferenceCard({ booking, hotel }: ReferenceCardProps) {
|
||||||
const [bookingStatus, setBookingStatus] = useState(booking.reservationStatus)
|
const [bookingStatus, setBookingStatus] = useState(booking.reservationStatus)
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
const { totalPrice, currencyCode } = useMyStayTotalPriceStore()
|
||||||
|
|
||||||
const fromDate = dt(booking.checkInDate).locale(lang)
|
const fromDate = dt(booking.checkInDate).locale(lang)
|
||||||
const toDate = dt(booking.checkOutDate).locale(lang)
|
const toDate = dt(booking.checkOutDate).locale(lang)
|
||||||
@@ -43,6 +44,19 @@ export function ReferenceCard({
|
|||||||
|
|
||||||
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
||||||
|
|
||||||
|
const adults =
|
||||||
|
booking.adults +
|
||||||
|
(booking.linkedReservations?.reduce(
|
||||||
|
(acc, linkedReservation) => acc + linkedReservation.adults,
|
||||||
|
0
|
||||||
|
) ?? 0)
|
||||||
|
const children =
|
||||||
|
booking.childrenAges.length +
|
||||||
|
(booking.linkedReservations?.reduce(
|
||||||
|
(acc, linkedReservation) => acc + linkedReservation.children,
|
||||||
|
0
|
||||||
|
) ?? 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.referenceCard}>
|
<div className={styles.referenceCard}>
|
||||||
<div className={styles.referenceRow}>
|
<div className={styles.referenceRow}>
|
||||||
@@ -56,9 +70,7 @@ export function ReferenceCard({
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
<Subtitle color="uiTextHighContrast">
|
<Subtitle color="uiTextHighContrast">
|
||||||
{/* TODO: Implement this: https://scandichotels.atlassian.net/browse/API2-2883 to get correct cancellation number */}
|
{/* TODO: Implement this: https://scandichotels.atlassian.net/browse/API2-2883 to get correct cancellation number */}
|
||||||
{isCancelled
|
{isCancelled ? "" : booking.confirmationNumber}
|
||||||
? booking.linkedReservations[0]?.cancellationNumber
|
|
||||||
: booking.confirmationNumber}
|
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
</div>
|
</div>
|
||||||
<Divider color="primaryLightSubtle" className={styles.divider} />
|
<Divider color="primaryLightSubtle" className={styles.divider} />
|
||||||
@@ -75,14 +87,14 @@ export function ReferenceCard({
|
|||||||
? intl.formatMessage(
|
? intl.formatMessage(
|
||||||
{ id: "{adults} adults, {children} children" },
|
{ id: "{adults} adults, {children} children" },
|
||||||
{
|
{
|
||||||
adults: booking.adults,
|
adults: adults,
|
||||||
children: booking.childrenAges.length,
|
children: children,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: intl.formatMessage(
|
: intl.formatMessage(
|
||||||
{ id: "{adults} adults" },
|
{ id: "{adults} adults" },
|
||||||
{
|
{
|
||||||
adults: booking.adults,
|
adults: adults,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
@@ -121,7 +133,7 @@ export function ReferenceCard({
|
|||||||
{intl.formatMessage({ id: "Total paid" })}
|
{intl.formatMessage({ id: "Total paid" })}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Caption type="bold" color="uiTextHighContrast">
|
<Caption type="bold" color="uiTextHighContrast">
|
||||||
{formatPrice(intl, booking.totalPrice, booking.currencyCode)}
|
{formatPrice(intl, totalPrice, currencyCode)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
{!showCancelButton && (
|
{!showCancelButton && (
|
||||||
@@ -150,7 +162,7 @@ export function ReferenceCard({
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{booking.rateDefinition.cancellationRule !== "NotCancellable" && (
|
{booking.isModifiable && (
|
||||||
<Caption className={styles.note} color="uiTextHighContrast">
|
<Caption className={styles.note} color="uiTextHighContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,15 +14,17 @@ import styles from "./room.module.css"
|
|||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
import type { User } from "@/types/user"
|
import type { User } from "@/types/user"
|
||||||
|
|
||||||
|
interface GuestDetailsProps {
|
||||||
|
user: User | null
|
||||||
|
booking: BookingConfirmation["booking"]
|
||||||
|
isMobile?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export default function GuestDetails({
|
export default function GuestDetails({
|
||||||
user,
|
user,
|
||||||
booking,
|
booking,
|
||||||
isMobile = false,
|
isMobile = false,
|
||||||
}: {
|
}: GuestDetailsProps) {
|
||||||
user: User | null
|
|
||||||
booking: BookingConfirmation["booking"]
|
|
||||||
isMobile?: boolean
|
|
||||||
}) {
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import { homeHrefs } from "@/constants/homeHrefs"
|
import { homeHrefs } from "@/constants/homeHrefs"
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
@@ -13,9 +14,11 @@ import Image from "@/components/Image"
|
|||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
import LinkedReservationSkeleton from "./LinkedReservation/LinkedReservationSkeleton"
|
||||||
import { Ancillaries } from "./Ancillaries"
|
import { Ancillaries } from "./Ancillaries"
|
||||||
import BookingSummary from "./BookingSummary"
|
import BookingSummary from "./BookingSummary"
|
||||||
import { Header } from "./Header"
|
import { Header } from "./Header"
|
||||||
|
import LinkedReservation from "./LinkedReservation"
|
||||||
import Promo from "./Promo"
|
import Promo from "./Promo"
|
||||||
import { ReferenceCard } from "./ReferenceCard"
|
import { ReferenceCard } from "./ReferenceCard"
|
||||||
import { Room } from "./Room"
|
import { Room } from "./Room"
|
||||||
@@ -30,6 +33,12 @@ export async function MyStay({ reservationId }: { reservationId: string }) {
|
|||||||
|
|
||||||
const { booking, hotel, room } = bookingConfirmation
|
const { booking, hotel, room } = bookingConfirmation
|
||||||
|
|
||||||
|
const linkedBookingPromises = booking.linkedReservations
|
||||||
|
? booking.linkedReservations.map((linkedBooking) => {
|
||||||
|
return getBookingConfirmation(linkedBooking.confirmationNumber)
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
|
||||||
const userResponse = await getProfileSafely()
|
const userResponse = await getProfileSafely()
|
||||||
const user = userResponse && !("error" in userResponse) ? userResponse : null
|
const user = userResponse && !("error" in userResponse) ? userResponse : null
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
@@ -39,7 +48,6 @@ export async function MyStay({ reservationId }: { reservationId: string }) {
|
|||||||
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
|
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
|
||||||
const hotelId = hotel.operaId
|
const hotelId = hotel.operaId
|
||||||
const ancillaryInput = { fromDate, hotelId, toDate }
|
const ancillaryInput = { fromDate, hotelId, toDate }
|
||||||
void getAncillaryPackages(ancillaryInput)
|
|
||||||
const ancillaryPackages = await getAncillaryPackages(ancillaryInput)
|
const ancillaryPackages = await getAncillaryPackages(ancillaryInput)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,7 +78,20 @@ export async function MyStay({ reservationId }: { reservationId: string }) {
|
|||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Room booking={booking} room={room} hotel={hotel} user={user} />
|
<div>
|
||||||
|
<Room booking={booking} room={room} hotel={hotel} user={user} />
|
||||||
|
{booking.linkedReservations.map((linkedRes, index) => (
|
||||||
|
<Suspense
|
||||||
|
key={linkedRes.confirmationNumber}
|
||||||
|
fallback={<LinkedReservationSkeleton />}
|
||||||
|
>
|
||||||
|
<LinkedReservation
|
||||||
|
bookingPromise={linkedBookingPromises[index]}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<BookingSummary booking={booking} hotel={hotel} />
|
<BookingSummary booking={booking} hotel={hotel} />
|
||||||
<Promo
|
<Promo
|
||||||
buttonText={intl.formatMessage({ id: "Book another stay" })}
|
buttonText={intl.formatMessage({ id: "Book another stay" })}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { create } from "zustand"
|
||||||
|
|
||||||
|
interface RoomPrice {
|
||||||
|
id: string
|
||||||
|
totalPrice: number
|
||||||
|
currencyCode: string
|
||||||
|
isMainBooking?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MyStayTotalPriceState {
|
||||||
|
rooms: RoomPrice[]
|
||||||
|
totalPrice: number
|
||||||
|
currencyCode: string
|
||||||
|
|
||||||
|
// Add a single room price
|
||||||
|
addRoomPrice: (room: RoomPrice) => void
|
||||||
|
|
||||||
|
// Get the calculated total
|
||||||
|
getTotalPrice: () => number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMyStayTotalPriceStore = create<MyStayTotalPriceState>(
|
||||||
|
(set, get) => ({
|
||||||
|
rooms: [],
|
||||||
|
totalPrice: 0,
|
||||||
|
currencyCode: "",
|
||||||
|
|
||||||
|
addRoomPrice: (room) => {
|
||||||
|
set((state) => {
|
||||||
|
// Check if room with this ID already exists
|
||||||
|
const existingIndex = state.rooms.findIndex((r) => r.id === room.id)
|
||||||
|
let newRooms = [...state.rooms]
|
||||||
|
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
// Update existing room
|
||||||
|
newRooms[existingIndex] = room
|
||||||
|
} else {
|
||||||
|
// Add new room
|
||||||
|
newRooms.push(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get currency from main booking or first room
|
||||||
|
const mainRoom = newRooms.find((r) => r.isMainBooking) || newRooms[0]
|
||||||
|
const currencyCode = mainRoom?.currencyCode || ""
|
||||||
|
|
||||||
|
// Calculate total (only same currency for now)
|
||||||
|
const total = newRooms.reduce((sum, r) => {
|
||||||
|
if (r.currencyCode === currencyCode) {
|
||||||
|
return sum + r.totalPrice
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return {
|
||||||
|
rooms: newRooms,
|
||||||
|
totalPrice: total,
|
||||||
|
currencyCode,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getTotalPrice: () => {
|
||||||
|
return get().totalPrice
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
@@ -12,5 +12,5 @@ export interface BookingConfirmationRoomsProps
|
|||||||
mainRoom: Room & {
|
mainRoom: Room & {
|
||||||
bedType: Room["roomTypes"][number]
|
bedType: Room["roomTypes"][number]
|
||||||
}
|
}
|
||||||
linkedReservations: LinkedReservationSchema[]
|
linkedReservations?: LinkedReservationSchema[] | null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user