fix: adjust allowed step navigation condition * fix: adjust allowed step navigation condition Approved-by: Tobias Johansson
377 lines
11 KiB
TypeScript
377 lines
11 KiB
TypeScript
import deepmerge from "deepmerge"
|
|
import { produce } from "immer"
|
|
import { useContext } from "react"
|
|
import { create, useStore } from "zustand"
|
|
|
|
import { DetailsContext } from "@/contexts/Details"
|
|
|
|
import {
|
|
add,
|
|
calcTotalPrice,
|
|
checkRoomProgress,
|
|
extractGuestFromUser,
|
|
getRoomPrice,
|
|
getTotalPrice,
|
|
handleStepProgression,
|
|
selectPreviousSteps,
|
|
selectRoom,
|
|
selectRoomStatus,
|
|
writeToSessionStorage,
|
|
} from "./helpers"
|
|
|
|
import { StepEnum } from "@/types/enums/step"
|
|
import type {
|
|
DetailsState,
|
|
InitialState,
|
|
RoomState,
|
|
RoomStatus,
|
|
RoomStep,
|
|
} from "@/types/stores/enter-details"
|
|
import type { SafeUser } from "@/types/user"
|
|
|
|
const defaultGuestState = {
|
|
countryCode: "",
|
|
dateOfBirth: "",
|
|
email: "",
|
|
firstName: "",
|
|
join: false,
|
|
lastName: "",
|
|
membershipNo: "",
|
|
phoneNumber: "",
|
|
zipCode: "",
|
|
}
|
|
|
|
export const detailsStorageName = "rooms-details-storage"
|
|
|
|
export function createDetailsStore(
|
|
initialState: InitialState,
|
|
searchParams: string,
|
|
user: SafeUser
|
|
) {
|
|
const isMember = !!user
|
|
|
|
const initialTotalPrice = getTotalPrice(
|
|
initialState.rooms.map((r) => r.roomRate),
|
|
isMember
|
|
)
|
|
|
|
initialState.rooms.forEach((room) => {
|
|
if (room.roomFeatures) {
|
|
room.roomFeatures.forEach((pkg) => {
|
|
if (initialTotalPrice.requested) {
|
|
initialTotalPrice.requested.price = add(
|
|
initialTotalPrice.requested.price,
|
|
pkg.requestedPrice.totalPrice
|
|
)
|
|
}
|
|
initialTotalPrice.local.price = add(
|
|
initialTotalPrice.local.price,
|
|
pkg.localPrice.totalPrice
|
|
)
|
|
})
|
|
}
|
|
})
|
|
|
|
const rooms: RoomState[] = initialState.rooms.map((room, idx) => {
|
|
return {
|
|
...room,
|
|
adults: initialState.booking.rooms[idx].adults,
|
|
childrenInRoom: initialState.booking.rooms[idx].childrenInRoom,
|
|
bedType: room.bedType,
|
|
breakfast:
|
|
initialState.breakfast === false ? initialState.breakfast : undefined,
|
|
guest: isMember
|
|
? deepmerge(defaultGuestState, extractGuestFromUser(user))
|
|
: defaultGuestState,
|
|
roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
|
|
}
|
|
})
|
|
|
|
const roomStatuses: RoomStatus[] = initialState.rooms.map((room, idx) => {
|
|
const steps: RoomStatus["steps"] = {
|
|
[StepEnum.selectBed]: {
|
|
step: StepEnum.selectBed,
|
|
isValid: !!room.bedType,
|
|
},
|
|
[StepEnum.breakfast]: {
|
|
step: StepEnum.breakfast,
|
|
isValid: false,
|
|
},
|
|
[StepEnum.details]: {
|
|
step: StepEnum.details,
|
|
isValid: false,
|
|
},
|
|
}
|
|
|
|
if (initialState.breakfast === false) {
|
|
delete steps[StepEnum.breakfast]
|
|
}
|
|
|
|
const currentStep =
|
|
idx === 0
|
|
? Object.values(steps).find((step) => !step.isValid)?.step ??
|
|
StepEnum.selectBed
|
|
: null
|
|
|
|
return {
|
|
isComplete: false,
|
|
currentStep: currentStep,
|
|
lastCompletedStep: undefined,
|
|
steps,
|
|
}
|
|
})
|
|
|
|
return create<DetailsState>()((set, get) => ({
|
|
searchParamString: searchParams,
|
|
booking: initialState.booking,
|
|
breakfast:
|
|
initialState.breakfast === false ? initialState.breakfast : undefined,
|
|
isSubmittingDisabled: false,
|
|
isSummaryOpen: false,
|
|
isPriceDetailsModalOpen: false,
|
|
totalPrice: initialTotalPrice,
|
|
vat: initialState.vat,
|
|
rooms,
|
|
bookingProgress: {
|
|
currentRoomIndex: 0,
|
|
roomStatuses,
|
|
canProceedToPayment: false,
|
|
},
|
|
|
|
actions: {
|
|
setStep(step: StepEnum | null, roomIndex?: number) {
|
|
if (step === null) {
|
|
return
|
|
}
|
|
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
const currentRoomIndex =
|
|
roomIndex ?? state.bookingProgress.currentRoomIndex
|
|
const previousSteps = selectPreviousSteps(state, roomIndex)
|
|
const arePreviousStepsCompleted = Object.values(
|
|
previousSteps
|
|
).every((step: RoomStep) => step.isValid)
|
|
const arePreviousRoomsCompleted = state.bookingProgress.roomStatuses
|
|
.slice(0, currentRoomIndex)
|
|
.every((room) => room.isComplete)
|
|
const roomStatus = selectRoomStatus(state, roomIndex)
|
|
|
|
if (arePreviousRoomsCompleted && arePreviousStepsCompleted) {
|
|
roomStatus.currentStep = step
|
|
|
|
if (roomIndex !== undefined) {
|
|
state.bookingProgress.currentRoomIndex = roomIndex
|
|
}
|
|
}
|
|
})
|
|
)
|
|
},
|
|
setIsSubmittingDisabled(isSubmittingDisabled) {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
state.isSubmittingDisabled = isSubmittingDisabled
|
|
})
|
|
)
|
|
},
|
|
setTotalPrice(totalPrice) {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
state.totalPrice.requested = totalPrice.requested
|
|
state.totalPrice.local = totalPrice.local
|
|
})
|
|
)
|
|
},
|
|
toggleSummaryOpen() {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
state.isSummaryOpen = !state.isSummaryOpen
|
|
})
|
|
)
|
|
},
|
|
togglePriceDetailsModalOpen() {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
state.isPriceDetailsModalOpen = !state.isPriceDetailsModalOpen
|
|
})
|
|
)
|
|
},
|
|
updateBedType(bedType) {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
const roomStatus = selectRoomStatus(state)
|
|
roomStatus.steps[StepEnum.selectBed].isValid = true
|
|
|
|
const room = selectRoom(state)
|
|
room.bedType = bedType
|
|
|
|
handleStepProgression(state)
|
|
|
|
writeToSessionStorage({
|
|
booking: state.booking,
|
|
rooms: state.rooms,
|
|
bookingProgress: state.bookingProgress,
|
|
})
|
|
})
|
|
)
|
|
},
|
|
updateBreakfast(breakfast) {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
const roomStatus = selectRoomStatus(state)
|
|
if (roomStatus.steps[StepEnum.breakfast]) {
|
|
roomStatus.steps[StepEnum.breakfast].isValid = true
|
|
}
|
|
|
|
const stateTotalRequestedPrice =
|
|
state.totalPrice.requested?.price || 0
|
|
|
|
const stateTotalLocalPrice = state.totalPrice.local.price
|
|
|
|
const addToTotalPrice =
|
|
(state.breakfast === undefined || state.breakfast === false) &&
|
|
!!breakfast
|
|
|
|
const subtractFromTotalPrice =
|
|
(state.breakfast === undefined || state.breakfast) &&
|
|
breakfast === false
|
|
|
|
if (addToTotalPrice) {
|
|
const breakfastTotalRequestedPrice = parseInt(
|
|
breakfast.requestedPrice.totalPrice
|
|
)
|
|
const breakfastTotalPrice = parseInt(
|
|
breakfast.localPrice.totalPrice
|
|
)
|
|
|
|
state.totalPrice = {
|
|
requested: state.totalPrice.requested && {
|
|
currency: state.totalPrice.requested.currency,
|
|
price:
|
|
stateTotalRequestedPrice + breakfastTotalRequestedPrice,
|
|
},
|
|
local: {
|
|
currency: breakfast.localPrice.currency,
|
|
price: stateTotalLocalPrice + breakfastTotalPrice,
|
|
},
|
|
}
|
|
}
|
|
|
|
if (subtractFromTotalPrice) {
|
|
let currency = state.totalPrice.local.currency
|
|
let currentBreakfastTotalPrice = 0
|
|
let currentBreakfastTotalRequestedPrice = 0
|
|
if (state.breakfast) {
|
|
currentBreakfastTotalPrice = parseInt(
|
|
state.breakfast.localPrice.totalPrice
|
|
)
|
|
currentBreakfastTotalRequestedPrice = parseInt(
|
|
state.breakfast.requestedPrice.totalPrice
|
|
)
|
|
currency = state.breakfast.localPrice.currency
|
|
}
|
|
|
|
let requestedPrice =
|
|
stateTotalRequestedPrice - currentBreakfastTotalRequestedPrice
|
|
if (requestedPrice < 0) {
|
|
requestedPrice = 0
|
|
}
|
|
let localPrice = stateTotalLocalPrice - currentBreakfastTotalPrice
|
|
if (localPrice < 0) {
|
|
localPrice = 0
|
|
}
|
|
|
|
state.totalPrice = {
|
|
requested: state.totalPrice.requested && {
|
|
currency: state.totalPrice.requested.currency,
|
|
price: requestedPrice,
|
|
},
|
|
local: {
|
|
currency,
|
|
price: localPrice,
|
|
},
|
|
}
|
|
}
|
|
|
|
const room = selectRoom(state)
|
|
room.breakfast = breakfast
|
|
|
|
handleStepProgression(state)
|
|
|
|
writeToSessionStorage({
|
|
booking: state.booking,
|
|
rooms: state.rooms,
|
|
bookingProgress: state.bookingProgress,
|
|
})
|
|
})
|
|
)
|
|
},
|
|
updateDetails(data) {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
const roomStatus = selectRoomStatus(state)
|
|
roomStatus.steps[StepEnum.details].isValid = true
|
|
|
|
const room = selectRoom(state)
|
|
room.guest.countryCode = data.countryCode
|
|
room.guest.dateOfBirth = data.dateOfBirth
|
|
room.guest.email = data.email
|
|
room.guest.firstName = data.firstName
|
|
room.guest.join = data.join
|
|
room.guest.lastName = data.lastName
|
|
|
|
if (data.join) {
|
|
room.guest.membershipNo = undefined
|
|
} else {
|
|
room.guest.membershipNo = data.membershipNo
|
|
}
|
|
room.guest.phoneNumber = data.phoneNumber
|
|
room.guest.zipCode = data.zipCode
|
|
|
|
room.roomPrice = getRoomPrice(
|
|
room.roomRate,
|
|
Boolean(data.join || data.membershipNo || isMember)
|
|
)
|
|
|
|
state.totalPrice = calcTotalPrice(
|
|
state.rooms,
|
|
state.totalPrice,
|
|
isMember
|
|
)
|
|
|
|
const isAllStepsCompleted = checkRoomProgress(state)
|
|
if (isAllStepsCompleted) {
|
|
roomStatus.isComplete = true
|
|
}
|
|
|
|
handleStepProgression(state)
|
|
|
|
writeToSessionStorage({
|
|
booking: state.booking,
|
|
rooms: state.rooms,
|
|
bookingProgress: state.bookingProgress,
|
|
})
|
|
})
|
|
)
|
|
},
|
|
updateSeachParamString(searchParamString) {
|
|
return set(
|
|
produce((state: DetailsState) => {
|
|
state.searchParamString = searchParamString
|
|
})
|
|
)
|
|
},
|
|
},
|
|
}))
|
|
}
|
|
|
|
export function useEnterDetailsStore<T>(selector: (store: DetailsState) => T) {
|
|
const store = useContext(DetailsContext)
|
|
|
|
if (!store) {
|
|
throw new Error("useEnterDetailsStore must be used within DetailsProvider")
|
|
}
|
|
|
|
return useStore(store, selector)
|
|
}
|