Merged in feat/SW-1076-no-room-availability (pull request #1467)
Feat/SW-1076 no room availability * fix: update booking error codes * feat(SW-1076): handle no room availabilty on enter-details * fix: parse to json in api mutation instead of expecting json * fix: remove 'isComplete' state from sectionAccordion because it was not needed Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -2,8 +2,8 @@ import { redirect } from "next/navigation"
|
||||
|
||||
import {
|
||||
BOOKING_CONFIRMATION_NUMBER,
|
||||
BookingErrorCodeEnum,
|
||||
MEMBERSHIP_FAILED_ERROR,
|
||||
PaymentErrorCodeEnum,
|
||||
} from "@/constants/booking"
|
||||
import {
|
||||
bookingConfirmation,
|
||||
@@ -70,17 +70,17 @@ export default async function PaymentCallbackPage({
|
||||
"errorCode",
|
||||
error
|
||||
? error.errorCode.toString()
|
||||
: PaymentErrorCodeEnum.Failed.toString()
|
||||
: BookingErrorCodeEnum.TransactionFailed
|
||||
)
|
||||
} catch {
|
||||
console.error(
|
||||
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
|
||||
)
|
||||
if (status === "cancel") {
|
||||
searchObject.set("errorCode", PaymentErrorCodeEnum.Cancelled.toString())
|
||||
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionCancelled)
|
||||
}
|
||||
if (status === "error") {
|
||||
searchObject.set("errorCode", PaymentErrorCodeEnum.Failed.toString())
|
||||
searchObject.set("errorCode", BookingErrorCodeEnum.TransactionFailed)
|
||||
errorMessage = `Failed to get booking status for ${confirmationNumber}, status: ${status}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { notFound, redirect } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { selectRate } from "@/constants/routes/hotelReservation"
|
||||
import {
|
||||
getBreakfastPackages,
|
||||
getHotel,
|
||||
@@ -16,13 +17,17 @@ import RoomOne from "@/components/HotelReservation/EnterDetails/Room/One"
|
||||
import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop"
|
||||
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
|
||||
import { generateChildrenString } from "@/components/HotelReservation/utils"
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import { getIntl } from "@/i18n"
|
||||
import RoomProvider from "@/providers/Details/RoomProvider"
|
||||
import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
|
||||
import { convertSearchParamsToObj } from "@/utils/url"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
import type { Room } from "@/types/providers/details/room"
|
||||
|
||||
@@ -81,7 +86,8 @@ export default async function DetailsPage({
|
||||
)
|
||||
|
||||
if (!roomAvailability) {
|
||||
continue // TODO: handle no room availability
|
||||
// redirect back to select-rate if availability call fails
|
||||
redirect(`${selectRate(lang)}?${selectRoomParams.toString()}`)
|
||||
}
|
||||
|
||||
rooms.push({
|
||||
@@ -98,6 +104,8 @@ export default async function DetailsPage({
|
||||
memberRate: roomAvailability?.memberRate,
|
||||
publicRate: roomAvailability.publicRate,
|
||||
},
|
||||
isAvailable:
|
||||
roomAvailability.selectedRoom.status === AvailabilityEnum.Available,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -139,8 +147,11 @@ export default async function DetailsPage({
|
||||
// region: hotel?.address.city,
|
||||
// }
|
||||
|
||||
const intl = await getIntl()
|
||||
|
||||
const firstRoom = rooms[0]
|
||||
const multirooms = rooms.slice(1)
|
||||
const isRoomNotAvailable = rooms.some((room) => !room.isAvailable)
|
||||
return (
|
||||
<EnterDetailsProvider
|
||||
booking={booking}
|
||||
@@ -153,6 +164,21 @@ export default async function DetailsPage({
|
||||
<main>
|
||||
<HotelHeader hotelData={hotelData} />
|
||||
<div className={styles.container}>
|
||||
{isRoomNotAvailable && (
|
||||
<Alert
|
||||
type={AlertTypeEnum.Alarm}
|
||||
variant="inline"
|
||||
heading={intl.formatMessage({ id: "Room sold out" })}
|
||||
text={intl.formatMessage({
|
||||
id: "Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.",
|
||||
})}
|
||||
link={{
|
||||
title: intl.formatMessage({ id: "Change room" }),
|
||||
url: `${selectRate(lang)}?${selectRoomParams.toString()}`,
|
||||
keepSearchParams: true,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.content}>
|
||||
<RoomProvider idx={0} room={firstRoom}>
|
||||
<RoomOne user={user} />
|
||||
|
||||
@@ -7,6 +7,7 @@ import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import {
|
||||
BookingErrorCodeEnum,
|
||||
BookingStatusEnum,
|
||||
PAYMENT_METHOD_TITLES,
|
||||
PaymentMethodEnum,
|
||||
@@ -107,6 +108,15 @@ export default function PaymentClient({
|
||||
const initiateBooking = trpc.booking.create.useMutation({
|
||||
onSuccess: (result) => {
|
||||
if (result) {
|
||||
if ("error" in result) {
|
||||
if (result.cause === BookingErrorCodeEnum.AvailabilityError) {
|
||||
window.location.reload() // reload to refetch room data because we dont know which room is unavailable
|
||||
} else {
|
||||
handlePaymentError(result.cause)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
setBookingNumber(result.id)
|
||||
|
||||
const priceChange = result.rooms.find(
|
||||
|
||||
@@ -25,14 +25,12 @@ export default function SectionAccordion({
|
||||
actions: { setStep },
|
||||
currentStep,
|
||||
isActiveRoom,
|
||||
room: { bedType, breakfast },
|
||||
room: { bedType, breakfast, isAvailable },
|
||||
steps,
|
||||
} = useRoomContext()
|
||||
|
||||
const [isComplete, setIsComplete] = useState(false)
|
||||
const isStepComplete = !!(steps[step]?.isValid && isAvailable)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const isValid = steps[step]?.isValid ?? false
|
||||
|
||||
const [title, setTitle] = useState(label)
|
||||
|
||||
const noBreakfastTitle = intl.formatMessage({ id: "No breakfast" })
|
||||
@@ -54,14 +52,10 @@ export default function SectionAccordion({
|
||||
}
|
||||
}, [bedType, breakfast, setTitle, step, breakfastTitle, noBreakfastTitle])
|
||||
|
||||
useEffect(() => {
|
||||
setIsComplete(isValid)
|
||||
}, [isValid, setIsComplete])
|
||||
|
||||
const accordionRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const shouldBeOpen = currentStep === step && isActiveRoom
|
||||
const shouldBeOpen = currentStep === step && isActiveRoom && isAvailable
|
||||
setIsOpen(shouldBeOpen)
|
||||
|
||||
// Scroll to this section when it is opened,
|
||||
@@ -91,7 +85,7 @@ export default function SectionAccordion({
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentStep, isActiveRoom, setIsOpen, step])
|
||||
}, [currentStep, isActiveRoom, isAvailable, setIsOpen, step])
|
||||
|
||||
function goToStep() {
|
||||
setStep(step)
|
||||
@@ -103,7 +97,7 @@ export default function SectionAccordion({
|
||||
}
|
||||
|
||||
const textColor =
|
||||
isComplete || isOpen ? "uiTextHighContrast" : "baseTextDisabled"
|
||||
isStepComplete || isOpen ? "uiTextHighContrast" : "baseTextDisabled"
|
||||
return (
|
||||
<div
|
||||
className={styles.accordion}
|
||||
@@ -112,8 +106,8 @@ export default function SectionAccordion({
|
||||
ref={accordionRef}
|
||||
>
|
||||
<div className={styles.iconWrapper}>
|
||||
<div className={styles.circle} data-checked={isComplete}>
|
||||
{isComplete ? (
|
||||
<div className={styles.circle} data-checked={isStepComplete}>
|
||||
{isStepComplete ? (
|
||||
<CheckIcon color="white" height="16" width="16" />
|
||||
) : null}
|
||||
</div>
|
||||
@@ -121,7 +115,7 @@ export default function SectionAccordion({
|
||||
<header className={styles.header}>
|
||||
<button
|
||||
onClick={isOpen ? close : goToStep}
|
||||
disabled={!isComplete}
|
||||
disabled={!isStepComplete}
|
||||
className={styles.modifyButton}
|
||||
>
|
||||
<Footnote
|
||||
@@ -136,7 +130,7 @@ export default function SectionAccordion({
|
||||
<Subtitle className={styles.selection} type="two" color={textColor}>
|
||||
{title}
|
||||
</Subtitle>
|
||||
{isComplete && (
|
||||
{isStepComplete && (
|
||||
<ChevronDownIcon
|
||||
className={`${styles.button} ${isOpen ? styles.buttonOpen : ""}`}
|
||||
color="burgundy"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
import { CheckIcon, EditIcon } from "@/components/Icons"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { useRoomContext } from "@/contexts/Details/Room"
|
||||
@@ -39,7 +40,7 @@ export default function SelectedRoom() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.wrapper} data-available={room.isAvailable}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<div className={styles.circle}>
|
||||
<CheckIcon color="white" height="16" width="16" />
|
||||
@@ -74,13 +75,15 @@ export default function SelectedRoom() {
|
||||
</Subtitle>
|
||||
<Button
|
||||
variant="icon"
|
||||
intent="text"
|
||||
size="small"
|
||||
color="burgundy"
|
||||
onClick={changeRoom}
|
||||
disabled={isPending}
|
||||
>
|
||||
<EditIcon color="burgundy" />
|
||||
{intl.formatMessage({ id: "Change room" })}
|
||||
<Caption color="burgundy" type="bold">
|
||||
{intl.formatMessage({ id: "Change room" })}
|
||||
</Caption>
|
||||
</Button>
|
||||
</div>
|
||||
{room.roomTypeCode && (
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.wrapper[data-available="false"] .title,
|
||||
.wrapper[data-available="false"] .description,
|
||||
.wrapper[data-available="false"] .details {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
@@ -52,6 +59,10 @@
|
||||
background-color: var(--UI-Input-Controls-Fill-Selected);
|
||||
}
|
||||
|
||||
.wrapper[data-available="false"] .circle {
|
||||
background-color: var(--Base-Surface-Subtle-Hover);
|
||||
}
|
||||
|
||||
.rate {
|
||||
color: var(--UI-Text-Placeholder);
|
||||
display: block;
|
||||
|
||||
@@ -381,7 +381,7 @@ export default function SummaryUI({
|
||||
totalPrice.local.currency
|
||||
)}
|
||||
</Body>
|
||||
{totalPrice.local.regularPrice && (
|
||||
{totalPrice.local.regularPrice ? (
|
||||
<Caption color="uiTextMediumContrast" striked={true}>
|
||||
{formatPrice(
|
||||
intl,
|
||||
@@ -389,7 +389,7 @@ export default function SummaryUI({
|
||||
totalPrice.local.currency
|
||||
)}
|
||||
</Caption>
|
||||
)}
|
||||
) : null}
|
||||
{totalPrice.requested && (
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{intl.formatMessage(
|
||||
|
||||
@@ -63,6 +63,7 @@ const rooms: RoomState[] = [
|
||||
roomRate: roomRate,
|
||||
roomType: "Standard",
|
||||
roomTypeCode: "QS",
|
||||
isAvailable: true,
|
||||
},
|
||||
steps: {
|
||||
[StepEnum.selectBed]: {
|
||||
@@ -100,6 +101,7 @@ const rooms: RoomState[] = [
|
||||
roomRate: roomRate,
|
||||
roomType: "Standard",
|
||||
roomTypeCode: "QS",
|
||||
isAvailable: true,
|
||||
},
|
||||
steps: {
|
||||
[StepEnum.selectBed]: {
|
||||
|
||||
@@ -64,10 +64,16 @@ export enum PaymentMethodEnum {
|
||||
discover = "discover",
|
||||
}
|
||||
|
||||
export enum PaymentErrorCodeEnum {
|
||||
Abandoned = 5,
|
||||
Cancelled = 6,
|
||||
Failed = 7,
|
||||
export enum BookingErrorCodeEnum {
|
||||
InternalError = "InternalError",
|
||||
ReservationError = "ReservationError",
|
||||
AvailabilityError = "AvailabilityError",
|
||||
BookingStatusNotFound = "BookingStatusNotFound",
|
||||
TransactionAbandoned = "TransactionAbandoned",
|
||||
TransactionCancelled = "TransactionCancelled",
|
||||
TransactionFailed = "TransactionFailed",
|
||||
BookingStateError = "BookingStateError",
|
||||
MembershipFailedError = "MembershipFailedError",
|
||||
}
|
||||
|
||||
export const PAYMENT_METHOD_TITLES: Record<
|
||||
|
||||
@@ -4,7 +4,7 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||
import { useCallback, useEffect } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { PaymentErrorCodeEnum } from "@/constants/booking"
|
||||
import { BookingErrorCodeEnum } from "@/constants/booking"
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
@@ -19,9 +19,9 @@ export function usePaymentFailedToast() {
|
||||
const router = useRouter()
|
||||
|
||||
const getErrorMessage = useCallback(
|
||||
(errorCode: PaymentErrorCodeEnum) => {
|
||||
(errorCode: string | null) => {
|
||||
switch (errorCode) {
|
||||
case PaymentErrorCodeEnum.Cancelled:
|
||||
case BookingErrorCodeEnum.TransactionCancelled:
|
||||
return intl.formatMessage({
|
||||
id: "You have now cancelled your payment.",
|
||||
})
|
||||
@@ -34,8 +34,7 @@ export function usePaymentFailedToast() {
|
||||
[intl]
|
||||
)
|
||||
|
||||
const errorCodeString = searchParams.get("errorCode")
|
||||
const errorCode = Number(errorCodeString) as PaymentErrorCodeEnum
|
||||
const errorCode = searchParams.get("errorCode")
|
||||
const errorMessage = getErrorMessage(errorCode)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -44,7 +43,9 @@ export function usePaymentFailedToast() {
|
||||
// setTimeout is needed to show toasts on page load: https://sonner.emilkowal.ski/toast#render-toast-on-page-load
|
||||
setTimeout(() => {
|
||||
const toastType =
|
||||
errorCode === PaymentErrorCodeEnum.Cancelled ? "warning" : "error"
|
||||
errorCode === BookingErrorCodeEnum.TransactionCancelled
|
||||
? "warning"
|
||||
: "error"
|
||||
|
||||
toast[toastType](errorMessage)
|
||||
})
|
||||
|
||||
@@ -575,6 +575,7 @@
|
||||
"Room charge": "Værelsesafgift",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Værelsesfaciliteter",
|
||||
"Room sold out": "Værelse solgt ud",
|
||||
"Room total": "Værelse total",
|
||||
"Room {roomIndex}": "Værelse {roomIndex}",
|
||||
"Rooms": "Værelser",
|
||||
@@ -686,6 +687,7 @@
|
||||
"Type of bed": "Sengtype",
|
||||
"Type of room": "Værelsestype",
|
||||
"U-shape": "U-form",
|
||||
"Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.": "Desværre er et af de værelser, du har valgt, solgt ud. Vælg et andet værelse for at fortsætte.",
|
||||
"Unlink accounts": "Unlink accounts",
|
||||
"Unpaid": "Ikke betalt",
|
||||
"Until {time}, {date}": "Indtil {time} den {date}",
|
||||
|
||||
@@ -574,6 +574,7 @@
|
||||
"Room charge": "Zimmerpreis",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Zimmerausstattung",
|
||||
"Room sold out": "Zimmer verkauft",
|
||||
"Room total": "Zimmer total",
|
||||
"Room {roomIndex}": "Zimmer {roomIndex}",
|
||||
"Rooms": "Räume",
|
||||
@@ -684,6 +685,7 @@
|
||||
"Type of bed": "Bettentyp",
|
||||
"Type of room": "Zimmerart",
|
||||
"U-shape": "U-shape",
|
||||
"Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.": "Leider ist eines der von Ihnen ausgewählten Zimmer verkauft. Bitte wählen Sie ein anderes Zimmer, um fortzufahren.",
|
||||
"Unlink accounts": "Unlink accounts",
|
||||
"Unpaid": "Nicht bezahlt",
|
||||
"Until {time}, {date}": "Bis {time} am {date}",
|
||||
|
||||
@@ -581,6 +581,7 @@
|
||||
"Room charge": "Room charge",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Room facilities",
|
||||
"Room sold out": "Room sold out",
|
||||
"Room total": "Room total",
|
||||
"Room {roomIndex}": "Room {roomIndex}",
|
||||
"Rooms": "Rooms",
|
||||
@@ -692,6 +693,7 @@
|
||||
"Type of bed": "Type of bed",
|
||||
"Type of room": "Type of room",
|
||||
"U-shape": "U-shape",
|
||||
"Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.": "Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.",
|
||||
"Unlink accounts": "Unlink accounts",
|
||||
"Unpaid": "Unpaid",
|
||||
"Until {time}, {date}": "Until {time}, {date}",
|
||||
|
||||
@@ -573,6 +573,7 @@
|
||||
"Room charge": "Huonemaksu",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Huoneen varustelu",
|
||||
"Room sold out": "Huone slutsattu",
|
||||
"Room total": "Huoneen kokonaishinta",
|
||||
"Room {roomIndex}": "Huone {roomIndex}",
|
||||
"Rooms": "Huoneet",
|
||||
@@ -684,6 +685,7 @@
|
||||
"Type of bed": "Vuodetyyppi",
|
||||
"Type of room": "Huonetyyppi",
|
||||
"U-shape": "U-muoto",
|
||||
"Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.": "Valitettavasti valitsemasi huone on loppuunmyyty. Valitse toinen huone jatkaaksesi.",
|
||||
"Unlink accounts": "Unlink accounts",
|
||||
"Unpaid": "Maksettaa",
|
||||
"Until {time}, {date}": "Asti {time}, {date}",
|
||||
|
||||
@@ -572,6 +572,7 @@
|
||||
"Room charge": "Rumspris",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Rumfaciliteter",
|
||||
"Room sold out": "Rum slutsålt",
|
||||
"Room total": "Rum total",
|
||||
"Room {roomIndex}": "Rum {roomIndex}",
|
||||
"Rooms": "Rum",
|
||||
@@ -682,6 +683,7 @@
|
||||
"Type of bed": "Sängtyp",
|
||||
"Type of room": "Rumstyp",
|
||||
"U-shape": "U-form",
|
||||
"Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.": "Tyvärr, ett av de rum du valde är slutsålt. Vänligen välj ett annat rum för att fortsätta.",
|
||||
"Unlink accounts": "Unlink accounts",
|
||||
"Unpaid": "Ej betalt",
|
||||
"Until {time}, {date}": "Tills {time} den {date}",
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"react-international-phone": "^4.2.6",
|
||||
"react-intl": "^6.6.8",
|
||||
"react-to-print": "^3.0.2",
|
||||
"secure-json-parse": "^4.0.0",
|
||||
"server-only": "^0.0.1",
|
||||
"slugify": "^1.6.6",
|
||||
"sonner": "^1.7.0",
|
||||
|
||||
@@ -37,6 +37,7 @@ export default function EnterDetailsProvider({
|
||||
rooms: rooms
|
||||
.filter((r) => r.bedTypes?.length) // TODO: how to handle room without bedtypes?
|
||||
.map((room) => ({
|
||||
isAvailable: room.isAvailable,
|
||||
breakfastIncluded: !!room.breakfastIncluded,
|
||||
cancellationText: room.cancellationText,
|
||||
rateDetails: room.rateDetails,
|
||||
@@ -86,6 +87,10 @@ export default function EnterDetailsProvider({
|
||||
// since store is readonly
|
||||
const currentRoom = deepmerge({}, store.rooms[idx])
|
||||
|
||||
if (!currentRoom.room.isAvailable) {
|
||||
return currentRoom
|
||||
}
|
||||
|
||||
if (!currentRoom.room.bedType && storedRoom.room.bedType) {
|
||||
const sameBed = currentRoom.room.bedTypes.find(
|
||||
(bedType) => bedType.value === storedRoom.room.bedType?.roomTypeCode
|
||||
@@ -134,7 +139,9 @@ export default function EnterDetailsProvider({
|
||||
return currentRoom
|
||||
})
|
||||
|
||||
const canProceedToPayment = updatedRooms.every((room) => room.isComplete)
|
||||
const canProceedToPayment = updatedRooms.every(
|
||||
(room) => room.isComplete && room.room.isAvailable
|
||||
)
|
||||
|
||||
const nights = dt(booking.toDate).diff(booking.fromDate, "days")
|
||||
const currency = (updatedRooms[0].room.roomRate.publicRate?.localPrice
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
import sjson from "secure-json-parse"
|
||||
|
||||
import * as api from "@/lib/api"
|
||||
import { getVerifiedUser } from "@/server/routers/user/query"
|
||||
@@ -11,8 +12,8 @@ import {
|
||||
cancelBookingInput,
|
||||
createBookingInput,
|
||||
priceChangeInput,
|
||||
updateBookingInput,
|
||||
removePackageInput,
|
||||
updateBookingInput,
|
||||
} from "./input"
|
||||
import { createBookingSchema } from "./output"
|
||||
|
||||
@@ -135,10 +136,17 @@ export const bookingMutationRouter = router({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
error: text,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const apiJson = sjson.safeParse(text)
|
||||
if ("errors" in apiJson && apiJson.errors.length) {
|
||||
const error = apiJson.errors[0]
|
||||
return { error: true, cause: error.code } as const
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ import {
|
||||
} from "./utils"
|
||||
|
||||
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||
import { HotelTypeEnum } from "@/types/enums/hotelType"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
@@ -595,7 +594,6 @@ export const hotelQueryRouter = router({
|
||||
bookingCode,
|
||||
rateCode,
|
||||
roomTypeCode,
|
||||
packageCodes,
|
||||
} = input
|
||||
|
||||
const params: Record<string, string | number | undefined> = {
|
||||
@@ -691,27 +689,11 @@ export const hotelQueryRouter = router({
|
||||
ctx.serviceToken
|
||||
)
|
||||
|
||||
const availableRooms =
|
||||
validateAvailabilityData.data.roomConfigurations.filter((room) => {
|
||||
if (packageCodes) {
|
||||
return (
|
||||
room.status === AvailabilityEnum.Available &&
|
||||
room.features.some(
|
||||
(feature) =>
|
||||
packageCodes.includes(feature.code) && feature.inventory > 0
|
||||
)
|
||||
)
|
||||
}
|
||||
return room.status === AvailabilityEnum.Available
|
||||
})
|
||||
|
||||
const selectedRoom = availableRooms.find(
|
||||
const rooms = validateAvailabilityData.data.roomConfigurations
|
||||
const selectedRoom = rooms.find(
|
||||
(room) => room.roomTypeCode === roomTypeCode
|
||||
)
|
||||
|
||||
const availableRoomsInCategory = availableRooms.filter(
|
||||
(room) => room.roomType === selectedRoom?.roomType
|
||||
)
|
||||
if (!selectedRoom) {
|
||||
metrics.selectedRoomAvailability.fail.add(1, {
|
||||
hotelId,
|
||||
@@ -720,6 +702,7 @@ export const hotelQueryRouter = router({
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
roomTypeCode,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find selected room with input: ${roomTypeCode}`,
|
||||
})
|
||||
@@ -727,6 +710,10 @@ export const hotelQueryRouter = router({
|
||||
return null
|
||||
}
|
||||
|
||||
const availableRoomsInCategory = rooms.filter(
|
||||
(room) => room.roomType === selectedRoom?.roomType
|
||||
)
|
||||
|
||||
const rateTypes = selectedRoom.products.find(
|
||||
(rate) =>
|
||||
rate.public?.rateCode === rateCode ||
|
||||
|
||||
@@ -26,15 +26,15 @@ export function extractGuestFromUser(user: NonNullable<SafeUser>) {
|
||||
}
|
||||
|
||||
export function checkIsSameBooking(
|
||||
prev: SelectRateSearchParams,
|
||||
next: SelectRateSearchParams
|
||||
prev: SelectRateSearchParams & { errorCode?: string },
|
||||
next: SelectRateSearchParams & { errorCode?: string }
|
||||
) {
|
||||
const { rooms: prevRooms, ...prevBooking } = prev
|
||||
const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev
|
||||
|
||||
const prevRoomsWithoutRateCodes = prevRooms.map(
|
||||
({ rateCode, counterRateCode, roomTypeCode, ...room }) => room
|
||||
)
|
||||
const { rooms: nextRooms, ...nextBooking } = next
|
||||
const { rooms: nextRooms, errorCode: nextErrorCode, ...nextBooking } = next
|
||||
|
||||
const nextRoomsWithoutRateCodes = nextRooms.map(
|
||||
({ rateCode, counterRateCode, roomTypeCode, ...room }) => room
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface Room {
|
||||
roomRate: RoomRate
|
||||
roomType: string
|
||||
roomTypeCode: string
|
||||
isAvailable: boolean
|
||||
}
|
||||
|
||||
export interface RoomProviderProps extends React.PropsWithChildren {
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
import type { Packages } from "../requests/packages"
|
||||
|
||||
export interface InitialRoomData {
|
||||
isAvailable: boolean
|
||||
bedType?: BedTypeSchema // used when there is only one bedtype to preselect it
|
||||
bedTypes: BedTypeSelection[]
|
||||
breakfastIncluded: boolean
|
||||
|
||||
@@ -6147,6 +6147,7 @@ __metadata:
|
||||
react-intl: "npm:^6.6.8"
|
||||
react-to-print: "npm:^3.0.2"
|
||||
schema-dts: "npm:^1.1.2"
|
||||
secure-json-parse: "npm:^4.0.0"
|
||||
server-only: "npm:^0.0.1"
|
||||
slugify: "npm:^1.6.6"
|
||||
sonner: "npm:^1.7.0"
|
||||
@@ -18487,6 +18488,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"secure-json-parse@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "secure-json-parse@npm:4.0.0"
|
||||
checksum: 10c0/1a298cf00e1de91e833cee5eb406d6e77fb2f7eca9bef3902047d49e7f5d3e6c21b5de61ff73466c831e716430bfe87d732a6e645a7dabb5f1e8a8e4d3e15eb4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^5.6.0":
|
||||
version: 5.7.2
|
||||
resolution: "semver@npm:5.7.2"
|
||||
|
||||
Reference in New Issue
Block a user