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
365 lines
10 KiB
TypeScript
365 lines
10 KiB
TypeScript
import isEqual from "fast-deep-equal"
|
|
|
|
import { detailsStorageName } from "."
|
|
|
|
import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
|
import type { Price } from "@/types/components/hotelReservation/price"
|
|
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
|
import { StepEnum } from "@/types/enums/step"
|
|
import type {
|
|
DetailsState,
|
|
PersistedState,
|
|
RoomState,
|
|
} from "@/types/stores/enter-details"
|
|
import type { SafeUser } from "@/types/user"
|
|
|
|
export function extractGuestFromUser(user: NonNullable<SafeUser>) {
|
|
return {
|
|
countryCode: user.address.countryCode?.toString(),
|
|
email: user.email,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
join: false,
|
|
membershipNo: user.membership?.membershipNumber,
|
|
phoneNumber: user.phoneNumber ?? "",
|
|
}
|
|
}
|
|
|
|
export function checkIsSameBooking(
|
|
prev: SelectRateSearchParams & { errorCode?: string },
|
|
next: SelectRateSearchParams & { errorCode?: string }
|
|
) {
|
|
const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev
|
|
|
|
const prevRoomsWithoutRateCodes = prevRooms.map(
|
|
({ rateCode, counterRateCode, roomTypeCode, ...room }) => room
|
|
)
|
|
const { rooms: nextRooms, errorCode: nextErrorCode, ...nextBooking } = next
|
|
|
|
const nextRoomsWithoutRateCodes = nextRooms.map(
|
|
({ rateCode, counterRateCode, roomTypeCode, ...room }) => room
|
|
)
|
|
|
|
return isEqual(
|
|
{
|
|
...prevBooking,
|
|
rooms: prevRoomsWithoutRateCodes,
|
|
},
|
|
{
|
|
...nextBooking,
|
|
rooms: nextRoomsWithoutRateCodes,
|
|
}
|
|
)
|
|
}
|
|
|
|
export function add(...nums: (number | string | undefined)[]) {
|
|
return nums.reduce((total: number, num) => {
|
|
if (typeof num === "undefined") {
|
|
num = 0
|
|
}
|
|
total = total + parseInt(`${num}`)
|
|
return total
|
|
}, 0)
|
|
}
|
|
|
|
export function subtract(...nums: (number | string | undefined)[]) {
|
|
return nums.reduce((total: number, num, idx) => {
|
|
if (typeof num === "undefined") {
|
|
num = 0
|
|
}
|
|
if (idx === 0) {
|
|
return parseInt(`${num}`)
|
|
}
|
|
total = total - parseInt(`${num}`)
|
|
if (total < 0) {
|
|
return 0
|
|
}
|
|
return total
|
|
}, 0)
|
|
}
|
|
|
|
export function getRoomPrice(roomRate: RoomRate, isMember: boolean) {
|
|
if (isMember && roomRate.memberRate) {
|
|
return {
|
|
perNight: {
|
|
requested: roomRate.memberRate.requestedPrice && {
|
|
currency: roomRate.memberRate.requestedPrice.currency,
|
|
price: roomRate.memberRate.requestedPrice.pricePerNight,
|
|
},
|
|
local: {
|
|
currency: roomRate.memberRate.localPrice.currency,
|
|
price: roomRate.memberRate.localPrice.pricePerNight,
|
|
},
|
|
},
|
|
perStay: {
|
|
requested: roomRate.memberRate.requestedPrice && {
|
|
currency: roomRate.memberRate.requestedPrice.currency,
|
|
price: roomRate.memberRate.requestedPrice.pricePerStay,
|
|
},
|
|
local: {
|
|
currency: roomRate.memberRate.localPrice.currency,
|
|
price: roomRate.memberRate.localPrice.pricePerStay,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
if (roomRate.publicRate) {
|
|
return {
|
|
perNight: {
|
|
requested: roomRate.publicRate.requestedPrice && {
|
|
currency: roomRate.publicRate.requestedPrice.currency,
|
|
price: roomRate.publicRate.requestedPrice.pricePerNight,
|
|
},
|
|
local: {
|
|
currency: roomRate.publicRate.localPrice.currency,
|
|
price: roomRate.publicRate.localPrice.pricePerNight,
|
|
regularPrice: roomRate.publicRate.localPrice.regularPricePerNight,
|
|
},
|
|
},
|
|
perStay: {
|
|
requested: roomRate.publicRate.requestedPrice && {
|
|
currency: roomRate.publicRate.requestedPrice.currency,
|
|
price: roomRate.publicRate.requestedPrice.pricePerStay,
|
|
},
|
|
local: {
|
|
currency: roomRate.publicRate.localPrice.currency,
|
|
price: roomRate.publicRate.localPrice.pricePerStay,
|
|
regularPrice: roomRate.publicRate.localPrice.regularPricePerStay,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
`Unable to calculate RoomPrice since user is neither a member or memberRate is missing, or publicRate is missing`
|
|
)
|
|
}
|
|
|
|
type TotalPrice = {
|
|
requested: { currency: string; price: number } | undefined
|
|
local: { currency: string; price: number; regularPrice?: number }
|
|
}
|
|
|
|
export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) {
|
|
return roomRates.reduce<TotalPrice>(
|
|
(total, roomRate, idx) => {
|
|
const isFirstRoom = idx === 0
|
|
const rate =
|
|
isFirstRoom && isMember && roomRate.memberRate
|
|
? roomRate.memberRate
|
|
: roomRate.publicRate
|
|
|
|
if (!rate) {
|
|
return total
|
|
}
|
|
|
|
return {
|
|
requested: rate.requestedPrice
|
|
? {
|
|
currency: rate.requestedPrice.currency,
|
|
price: add(
|
|
total.requested?.price ?? 0,
|
|
rate.requestedPrice.pricePerStay
|
|
),
|
|
}
|
|
: total.requested,
|
|
local: {
|
|
currency: rate.localPrice.currency,
|
|
price: add(total.local.price ?? 0, rate.localPrice.pricePerStay),
|
|
regularPrice: rate.localPrice.regularPricePerStay
|
|
? add(total.local.regularPrice, rate.localPrice.regularPricePerStay)
|
|
: total.local.regularPrice,
|
|
},
|
|
}
|
|
},
|
|
{
|
|
requested: undefined,
|
|
local: {
|
|
currency: (roomRates[0].publicRate?.localPrice.currency ||
|
|
roomRates[0].memberRate?.localPrice.currency)!,
|
|
price: 0,
|
|
},
|
|
}
|
|
)
|
|
}
|
|
|
|
export function calcTotalPrice(
|
|
rooms: RoomState[],
|
|
currency: Price["local"]["currency"],
|
|
isMember: boolean,
|
|
nights: number
|
|
) {
|
|
return rooms.reduce<Price>(
|
|
(acc, { room }, index) => {
|
|
const isFirstRoomAndMember = index === 0 && isMember
|
|
const join = Boolean(room.guest.join || room.guest.membershipNo)
|
|
|
|
const roomPrice = getRoomPrice(
|
|
room.roomRate,
|
|
isFirstRoomAndMember || join
|
|
)
|
|
|
|
if (!roomPrice) {
|
|
return acc
|
|
}
|
|
|
|
const breakfastRequestedPrice = room.breakfast
|
|
? parseInt(room.breakfast.requestedPrice?.price ?? 0)
|
|
: 0
|
|
const breakfastLocalPrice = room.breakfast
|
|
? parseInt(room.breakfast.localPrice?.price ?? 0)
|
|
: 0
|
|
|
|
const roomFeaturesTotal = room.roomFeatures?.reduce((total, pkg) => {
|
|
if (pkg.requestedPrice.totalPrice) {
|
|
total = add(total, pkg.requestedPrice.totalPrice)
|
|
}
|
|
return total
|
|
}, 0)
|
|
|
|
const result: Price = {
|
|
requested: roomPrice.perStay.requested
|
|
? {
|
|
currency: roomPrice.perStay.requested.currency,
|
|
price: add(
|
|
acc.requested?.price ?? 0,
|
|
roomPrice.perStay.requested.price,
|
|
breakfastRequestedPrice * room.adults * nights
|
|
),
|
|
}
|
|
: undefined,
|
|
local: {
|
|
currency: roomPrice.perStay.local.currency,
|
|
price: add(
|
|
acc.local.price,
|
|
roomPrice.perStay.local.price,
|
|
breakfastLocalPrice * room.adults * nights,
|
|
roomFeaturesTotal
|
|
),
|
|
regularPrice: add(
|
|
acc.local.regularPrice,
|
|
roomPrice.perStay.local.regularPrice,
|
|
breakfastLocalPrice * room.adults * nights,
|
|
roomFeaturesTotal
|
|
),
|
|
},
|
|
}
|
|
|
|
return result
|
|
},
|
|
{
|
|
requested: undefined,
|
|
local: { currency, price: 0 },
|
|
}
|
|
)
|
|
}
|
|
|
|
export function getFirstInteractiveStepOfRoom(room: RoomState["room"]) {
|
|
if (!room.bedType) {
|
|
return StepEnum.selectBed
|
|
}
|
|
if (room.breakfast !== false) {
|
|
return StepEnum.breakfast
|
|
}
|
|
return StepEnum.details
|
|
}
|
|
|
|
export function findNextInvalidStep(roomState: RoomState) {
|
|
return (
|
|
Object.values(roomState.steps).find((stp) => !stp.isValid)?.step ??
|
|
getFirstInteractiveStepOfRoom(roomState.room)
|
|
)
|
|
}
|
|
|
|
export const selectNextStep = (room: RoomState) => {
|
|
if (room.currentStep === null) {
|
|
throw new Error("getNextStep: currentStep is null")
|
|
}
|
|
|
|
if (!room.steps[room.currentStep]?.isValid) {
|
|
return room.currentStep
|
|
}
|
|
|
|
const stepsArray = Object.values(room.steps)
|
|
const currentIndex = stepsArray.findIndex(
|
|
(step) => step?.step === room.currentStep
|
|
)
|
|
if (currentIndex === stepsArray.length - 1) {
|
|
return null
|
|
}
|
|
|
|
const nextInvalidStep = stepsArray
|
|
.slice(currentIndex + 1)
|
|
.find((step) => !step.isValid)
|
|
|
|
return nextInvalidStep?.step ?? null
|
|
}
|
|
|
|
export const checkRoomProgress = (steps: RoomState["steps"]) => {
|
|
return Object.values(steps)
|
|
.filter(Boolean)
|
|
.every((step) => step.isValid)
|
|
}
|
|
|
|
export function handleStepProgression(room: RoomState, state: DetailsState) {
|
|
const isAllRoomsCompleted = state.rooms.every((r) => r.isComplete)
|
|
if (isAllRoomsCompleted) {
|
|
room.currentStep = null
|
|
state.canProceedToPayment = true
|
|
} else if (room.isComplete) {
|
|
room.currentStep = null
|
|
const nextRoomIndex = state.rooms.findIndex((r) => !r.isComplete)
|
|
state.activeRoom = nextRoomIndex
|
|
|
|
const nextRoom = state.rooms[nextRoomIndex]
|
|
const nextStep = selectNextStep(nextRoom)
|
|
nextRoom.currentStep = nextStep
|
|
} else if (selectNextStep(room)) {
|
|
room.currentStep = selectNextStep(room)
|
|
}
|
|
}
|
|
|
|
export function readFromSessionStorage(): PersistedState | undefined {
|
|
if (typeof window === "undefined") {
|
|
return undefined
|
|
}
|
|
|
|
try {
|
|
const storedData = sessionStorage.getItem(detailsStorageName)
|
|
if (!storedData) {
|
|
return undefined
|
|
}
|
|
|
|
const parsedData = JSON.parse(storedData) as PersistedState
|
|
|
|
if (!parsedData.booking || !parsedData.rooms) {
|
|
return undefined
|
|
}
|
|
|
|
return parsedData
|
|
} catch (error) {
|
|
console.error("Error reading from session storage:", error)
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
export function writeToSessionStorage(state: PersistedState) {
|
|
if (typeof window === "undefined") {
|
|
return
|
|
}
|
|
|
|
try {
|
|
sessionStorage.setItem(detailsStorageName, JSON.stringify(state))
|
|
} catch (error) {
|
|
console.error("Error writing to session storage:", error)
|
|
}
|
|
}
|
|
|
|
export function clearSessionStorage() {
|
|
if (typeof window === "undefined") {
|
|
return
|
|
}
|
|
sessionStorage.removeItem(detailsStorageName)
|
|
}
|