Feat/SW-1077 enter details edit room * feat(SW-1077): persist state when changing rooms * fix: issue with step state when closing accordion and transition to correct room when modifying step Approved-by: Pontus Dreij
393 lines
11 KiB
TypeScript
393 lines
11 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,
|
|
RoomStatus,
|
|
RoomStep,
|
|
} 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 checkIsSameBedTypes(
|
|
storedBedTypes: string,
|
|
bedTypesData: string
|
|
) {
|
|
return storedBedTypes === bedTypesData
|
|
}
|
|
|
|
export function checkIsSameBooking(
|
|
prev: SelectRateSearchParams,
|
|
next: SelectRateSearchParams
|
|
) {
|
|
const { rooms: prevRooms, ...prevBooking } = prev
|
|
|
|
const prevRoomsWithoutRateCodes = prevRooms.map(
|
|
({ rateCode, counterRateCode, roomTypeCode, ...room }) => room
|
|
)
|
|
const { rooms: nextRooms, ...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,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
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,
|
|
},
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
type TotalPrice = {
|
|
requested: { currency: string; price: number } | undefined
|
|
local: { currency: string; price: 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
|
|
|
|
return {
|
|
requested: rate.requestedPrice
|
|
? {
|
|
currency: rate.requestedPrice.currency,
|
|
price: add(
|
|
total.requested?.price ?? 0,
|
|
rate.requestedPrice.pricePerStay
|
|
),
|
|
}
|
|
: undefined,
|
|
local: {
|
|
currency: rate.localPrice.currency,
|
|
price: add(total.local.price ?? 0, rate.localPrice.pricePerStay),
|
|
},
|
|
}
|
|
},
|
|
{
|
|
requested: undefined,
|
|
local: {
|
|
currency: roomRates[0].publicRate.localPrice.currency,
|
|
price: 0,
|
|
},
|
|
}
|
|
)
|
|
}
|
|
|
|
export function calcTotalPrice(
|
|
rooms: RoomState[],
|
|
totalPrice: Price,
|
|
isMember: boolean
|
|
) {
|
|
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
|
|
)
|
|
|
|
const breakfastRequestedPrice = room.breakfast
|
|
? room.breakfast.requestedPrice?.totalPrice ?? 0
|
|
: 0
|
|
const breakfastLocalPrice = room.breakfast
|
|
? room.breakfast.localPrice?.totalPrice ?? 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
|
|
),
|
|
}
|
|
: undefined,
|
|
local: {
|
|
currency: roomPrice.perStay.local.currency,
|
|
price: add(
|
|
acc.local.price,
|
|
roomPrice.perStay.local.price,
|
|
breakfastLocalPrice,
|
|
roomFeaturesTotal
|
|
),
|
|
},
|
|
}
|
|
|
|
return result
|
|
},
|
|
{
|
|
requested: undefined,
|
|
local: { currency: totalPrice.local.currency, price: 0 },
|
|
}
|
|
)
|
|
}
|
|
|
|
export const selectRoomStatus = (state: DetailsState, index?: number) =>
|
|
state.bookingProgress.roomStatuses[
|
|
index ?? state.bookingProgress.currentRoomIndex
|
|
]
|
|
|
|
export const selectRoom = (state: DetailsState, index?: number) =>
|
|
state.rooms[index ?? state.bookingProgress.currentRoomIndex]
|
|
|
|
export const selectRoomSteps = (state: DetailsState, index?: number) =>
|
|
state.bookingProgress.roomStatuses[
|
|
index ?? state.bookingProgress.currentRoomIndex
|
|
].steps
|
|
|
|
export const selectPreviousSteps = (
|
|
state: DetailsState,
|
|
index?: number
|
|
): {
|
|
[StepEnum.selectBed]?: RoomStep
|
|
[StepEnum.breakfast]?: RoomStep
|
|
[StepEnum.details]?: RoomStep
|
|
} => {
|
|
const roomStatus =
|
|
state.bookingProgress.roomStatuses[
|
|
index ?? state.bookingProgress.currentRoomIndex
|
|
]
|
|
const stepKeys = Object.keys(roomStatus.steps)
|
|
const currentStepIndex = stepKeys.indexOf(`${roomStatus.currentStep}`)
|
|
return Object.entries(roomStatus.steps)
|
|
.slice(0, currentStepIndex)
|
|
.reduce((acc, [key, value]) => {
|
|
return { ...acc, [key]: value }
|
|
}, {})
|
|
}
|
|
|
|
export const selectNextStep = (roomStatus: RoomStatus) => {
|
|
if (roomStatus.currentStep === null) {
|
|
throw new Error("getNextStep: currentStep is null")
|
|
}
|
|
|
|
if (!roomStatus.steps[roomStatus.currentStep]?.isValid) {
|
|
return roomStatus.currentStep
|
|
}
|
|
|
|
const stepsArray = Object.values(roomStatus.steps)
|
|
const currentIndex = stepsArray.findIndex(
|
|
(step) => step?.step === roomStatus.currentStep
|
|
)
|
|
if (currentIndex === stepsArray.length - 1) {
|
|
return null
|
|
}
|
|
|
|
const nextInvalidStep = stepsArray
|
|
.slice(currentIndex + 1)
|
|
.find((step) => !step.isValid)
|
|
|
|
return nextInvalidStep?.step ?? null
|
|
}
|
|
|
|
export const selectBookingProgress = (state: DetailsState) =>
|
|
state.bookingProgress
|
|
|
|
export const checkBookingProgress = (state: DetailsState) => {
|
|
return state.bookingProgress.roomStatuses.every((r) => r.isComplete)
|
|
}
|
|
|
|
export const checkRoomProgress = (state: DetailsState) => {
|
|
const steps = selectRoomSteps(state)
|
|
return Object.values(steps)
|
|
.filter(Boolean)
|
|
.every((step) => step.isValid)
|
|
}
|
|
|
|
export function handleStepProgression(state: DetailsState) {
|
|
const isAllRoomsCompleted = checkBookingProgress(state)
|
|
if (isAllRoomsCompleted) {
|
|
const roomStatus = selectRoomStatus(state)
|
|
roomStatus.currentStep = null
|
|
state.bookingProgress.canProceedToPayment = true
|
|
return
|
|
}
|
|
|
|
const roomStatus = selectRoomStatus(state)
|
|
if (roomStatus.isComplete) {
|
|
const nextRoomIndex = state.bookingProgress.roomStatuses.findIndex(
|
|
(room) => !room.isComplete
|
|
)
|
|
roomStatus.lastCompletedStep = roomStatus.currentStep ?? undefined
|
|
roomStatus.currentStep = null
|
|
const nextRoomStatus = selectRoomStatus(state, nextRoomIndex)
|
|
nextRoomStatus.currentStep =
|
|
Object.values(nextRoomStatus.steps).find((step) => !step.isValid)?.step ??
|
|
StepEnum.selectBed
|
|
|
|
const nextStep = selectNextStep(nextRoomStatus)
|
|
nextRoomStatus.currentStep = nextStep
|
|
state.bookingProgress.currentRoomIndex = nextRoomIndex
|
|
return
|
|
}
|
|
|
|
const nextStep = selectNextStep(roomStatus)
|
|
if (nextStep !== null && roomStatus.currentStep !== null) {
|
|
roomStatus.lastCompletedStep = roomStatus.currentStep
|
|
roomStatus.currentStep = nextStep
|
|
return
|
|
}
|
|
}
|
|
|
|
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 ||
|
|
!parsedData.bookingProgress
|
|
) {
|
|
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)
|
|
}
|