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 { CurrencyEnum } from "@/types/enums/currency" 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 getCurrency(roomRate: RoomRate) { if ("corporateCheque" in roomRate) { return { localCurrency: CurrencyEnum.CC, requestedCurrency: CurrencyEnum.CC, } } else if ("redemption" in roomRate) { return { localCurrency: CurrencyEnum.POINTS, requestedCurrency: CurrencyEnum.POINTS, } } else if ("voucher" in roomRate) { return { localCurrency: CurrencyEnum.Voucher, requestedCurrency: CurrencyEnum.Voucher, } } else if ("public" in roomRate && roomRate.public) { return { localCurrency: roomRate.public.localPrice.currency, requestedCurrency: roomRate.public.requestedPrice?.currency, } } else if ("member" in roomRate && roomRate.member) { return { localCurrency: roomRate.member.localPrice.currency, requestedCurrency: roomRate.member.requestedPrice?.currency, } } return { localCurrency: CurrencyEnum.Unknown, requestedCurrency: CurrencyEnum.Unknown, } } export function getRoomPrice(roomRate: RoomRate, isMember: boolean) { if (isMember && "member" in roomRate && roomRate.member) { return { perNight: { requested: roomRate.member.requestedPrice ? { currency: roomRate.member.requestedPrice.currency, price: roomRate.member.requestedPrice.pricePerNight, } : undefined, local: { currency: roomRate.member.localPrice.currency, price: roomRate.member.localPrice.pricePerNight, }, }, perStay: { requested: roomRate.member.requestedPrice ? { currency: roomRate.member.requestedPrice.currency, price: roomRate.member.requestedPrice.pricePerStay, } : undefined, local: { currency: roomRate.member.localPrice.currency, price: roomRate.member.localPrice.pricePerStay, }, }, } } if ("public" in roomRate && roomRate.public) { return { perNight: { requested: roomRate.public.requestedPrice ? { currency: roomRate.public.requestedPrice.currency, price: roomRate.public.requestedPrice.pricePerNight, } : undefined, local: { currency: roomRate.public.localPrice.currency, price: roomRate.public.localPrice.pricePerNight, regularPrice: roomRate.public.localPrice.regularPricePerNight, }, }, perStay: { requested: roomRate.public.requestedPrice ? { currency: roomRate.public.requestedPrice.currency, price: roomRate.public.requestedPrice.pricePerStay, } : undefined, local: { currency: roomRate.public.localPrice.currency, price: roomRate.public.localPrice.pricePerStay, regularPrice: roomRate.public.localPrice.regularPricePerStay, }, }, } } if ("corporateCheque" in roomRate) { return { perNight: { requested: roomRate.corporateCheque.requestedPrice ? { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.requestedPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.requestedPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.requestedPrice.currency ?? undefined, } : undefined, local: { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.localPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.localPrice.currency ?? undefined, }, }, perStay: { requested: roomRate.corporateCheque.requestedPrice ? { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.requestedPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.requestedPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.requestedPrice.currency ?? undefined, } : undefined, local: { currency: CurrencyEnum.CC, price: roomRate.corporateCheque.localPrice.numberOfCheques, additionalPrice: roomRate.corporateCheque.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.corporateCheque.localPrice.currency ?? undefined, }, }, } } if ("voucher" in roomRate) { return { perNight: { requested: undefined, local: { currency: CurrencyEnum.Voucher, price: roomRate.voucher.numberOfVouchers, }, }, perStay: { requested: undefined, local: { currency: CurrencyEnum.Voucher, price: roomRate.voucher.numberOfVouchers, }, }, } } if ("redemption" in roomRate) { return { // ToDo Handle perNight as undefined perNight: { requested: undefined, local: { currency: CurrencyEnum.POINTS, price: roomRate.redemption.localPrice.pointsPerStay, additionalPrice: roomRate.redemption.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.redemption.localPrice.currency ?? undefined, }, }, perStay: { requested: undefined, local: { currency: CurrencyEnum.POINTS, price: roomRate.redemption.localPrice.pointsPerStay, additionalPrice: roomRate.redemption.localPrice.additionalPricePerStay, additionalPriceCurrency: roomRate.redemption.localPrice.currency ?? undefined, }, }, } } throw new Error( `Unable to calculate RoomPrice since user is neither a member or memberRate is missing, or publicRate is missing` ) } export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) { return roomRates.reduce( (total, roomRate, idx) => { const isMainRoom = idx === 0 let rate if (isMainRoom && isMember && "member" in roomRate && roomRate.member) { rate = roomRate.member } else if ("public" in roomRate && roomRate.public) { rate = roomRate.public } // TODO: Handle other products? if (!rate) { return total } total.local.currency = rate.localPrice.currency total.local.price = add(total.local.price, rate.localPrice.pricePerStay) if (rate.localPrice.regularPricePerStay) { total.local.regularPrice = add( total.local.regularPrice, rate.localPrice.regularPricePerStay ) } if (rate.requestedPrice) { if (total.requested) { total.requested.price = add( total.requested.price, rate.requestedPrice.pricePerStay ) } else { total.requested = { currency: rate.requestedPrice.currency, price: rate.requestedPrice.pricePerStay, } } } return total }, { local: { currency: CurrencyEnum.Unknown, price: 0, }, requested: undefined, } ) } export function calculateVoucherPrice(roomRates: RoomRate[]) { return roomRates.reduce( (total, room) => { if (!("voucher" in room)) { return total } return { local: { currency: total.local.currency, price: total.local.price + room.voucher.numberOfVouchers, }, requested: undefined, } }, { local: { currency: CurrencyEnum.Voucher, price: 0, }, requested: undefined, } ) } export function calculateCorporateChequePrice(roomRates: RoomRate[]) { return roomRates.reduce( (total, room) => { if (!("corporateCheque" in room)) { return total } const rate = room.corporateCheque total.local.price = add( total.local.price, rate.localPrice.numberOfCheques ) if (rate.localPrice.additionalPricePerStay) { total.local.additionalPrice = add( total.local.additionalPrice, rate.localPrice.additionalPricePerStay ) } if (rate.localPrice.currency) { total.local.additionalPriceCurrency = rate.localPrice.currency } if (rate.requestedPrice) { if (total.requested) { total.requested.price = add( total.requested.price, rate.requestedPrice.numberOfCheques ) } else { total.requested = { currency: CurrencyEnum.CC, price: rate.requestedPrice.numberOfCheques, } } if (rate.requestedPrice.additionalPricePerStay) { total.requested.additionalPrice = add( total.requested.additionalPrice, rate.requestedPrice.additionalPricePerStay ) } if (rate.requestedPrice.currency) { total.requested.additionalPriceCurrency = rate.requestedPrice.currency } } return total }, { local: { currency: CurrencyEnum.CC, price: 0, }, requested: undefined, } ) } 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 ? (room.breakfast.requestedPrice?.price ?? 0) : 0 const breakfastLocalPrice = room.breakfast ? (room.breakfast.localPrice?.price ?? 0) : 0 const roomFeaturesTotal = room.roomFeatures?.reduce( (total, pkg) => { if (pkg.requestedPrice.totalPrice) { total.requestedPrice = add( total.requestedPrice, pkg.requestedPrice.totalPrice ) } total.local = add(total.local, pkg.localPrice.totalPrice) return total }, { local: 0, requestedPrice: 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?.local ?? 0 ), regularPrice: add( acc.local.regularPrice, roomPrice.perStay.local.regularPrice, breakfastLocalPrice * room.adults * nights, roomFeaturesTotal?.requestedPrice ?? 0 ), additionalPrice: add( acc.local.additionalPrice, roomPrice.perStay.local.additionalPrice, breakfastLocalPrice * room.adults * nights, roomFeaturesTotal?.local ?? 0 ), additionalPriceCurrency: roomPrice.perStay.local .additionalPriceCurrency ? roomPrice.perStay.local.additionalPriceCurrency : undefined, }, } 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) }