Files
web/stores/enter-details/helpers.ts
Tobias Johansson 59eefb877e Merged in feat/SW-1077-enter-details-edit-room (pull request #1360)
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
2025-02-19 10:03:11 +00:00

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