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) { 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( (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( (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) }