Files
web/apps/scandic-web/stores/enter-details/helpers.ts
Tobias Johansson 7c233ab846 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
2025-03-10 12:13:15 +00:00

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