feat(SW-1717): rewrite select-rate to show all variants of rate-cards

This commit is contained in:
Simon Emanuelsson
2025-03-25 11:25:44 +01:00
committed by Michael Zetterberg
parent adde77eaa9
commit ebaea78fb3
118 changed files with 4601 additions and 4374 deletions

View File

@@ -80,156 +80,187 @@ export function subtract(...nums: (number | string | undefined)[]) {
}
export function getCurrency(roomRate: RoomRate) {
const requestedCurrency = <CurrencyEnum>(
(roomRate.publicRate?.requestedPrice?.currency ??
(roomRate.chequeRate && CurrencyEnum.CC))
)
const localCurrency = <CurrencyEnum>(
(roomRate.publicRate?.localPrice.currency ??
(roomRate.voucherRate && CurrencyEnum.Voucher) ??
(roomRate.chequeRate && CurrencyEnum.CC))
)
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 {
requestedCurrency,
localCurrency,
localCurrency: CurrencyEnum.Unknown,
requestedCurrency: CurrencyEnum.Unknown,
}
}
export function getRoomPrice(roomRate: RoomRate, isMember: boolean) {
if (isMember && roomRate.memberRate) {
if (isMember && "member" in roomRate && roomRate.member) {
return {
perNight: {
requested: roomRate.memberRate.requestedPrice && {
currency: roomRate.memberRate.requestedPrice.currency,
price: roomRate.memberRate.requestedPrice.pricePerNight,
},
requested: roomRate.member.requestedPrice
? {
currency: roomRate.member.requestedPrice.currency,
price: roomRate.member.requestedPrice.pricePerNight,
}
: undefined,
local: {
currency: roomRate.memberRate.localPrice.currency,
price: roomRate.memberRate.localPrice.pricePerNight,
currency: roomRate.member.localPrice.currency,
price: roomRate.member.localPrice.pricePerNight,
},
},
perStay: {
requested: roomRate.memberRate.requestedPrice && {
currency: roomRate.memberRate.requestedPrice.currency,
price: roomRate.memberRate.requestedPrice.pricePerStay,
},
requested: roomRate.member.requestedPrice
? {
currency: roomRate.member.requestedPrice.currency,
price: roomRate.member.requestedPrice.pricePerStay,
}
: undefined,
local: {
currency: roomRate.memberRate.localPrice.currency,
price: roomRate.memberRate.localPrice.pricePerStay,
currency: roomRate.member.localPrice.currency,
price: roomRate.member.localPrice.pricePerStay,
},
},
}
}
if (roomRate.publicRate) {
if ("public" in roomRate && roomRate.public) {
return {
perNight: {
requested: roomRate.publicRate.requestedPrice && {
currency: roomRate.publicRate.requestedPrice.currency,
price: roomRate.publicRate.requestedPrice.pricePerNight,
},
requested: roomRate.public.requestedPrice
? {
currency: roomRate.public.requestedPrice.currency,
price: roomRate.public.requestedPrice.pricePerNight,
}
: undefined,
local: {
currency: roomRate.publicRate.localPrice.currency,
price: roomRate.publicRate.localPrice.pricePerNight,
regularPrice: roomRate.publicRate.localPrice.regularPricePerNight,
currency: roomRate.public.localPrice.currency,
price: roomRate.public.localPrice.pricePerNight,
regularPrice: roomRate.public.localPrice.regularPricePerNight,
},
},
perStay: {
requested: roomRate.publicRate.requestedPrice && {
currency: roomRate.publicRate.requestedPrice.currency,
price: roomRate.publicRate.requestedPrice.pricePerStay,
},
requested: roomRate.public.requestedPrice
? {
currency: roomRate.public.requestedPrice.currency,
price: roomRate.public.requestedPrice.pricePerStay,
}
: undefined,
local: {
currency: roomRate.publicRate.localPrice.currency,
price: roomRate.publicRate.localPrice.pricePerStay,
regularPrice: roomRate.publicRate.localPrice.regularPricePerStay,
currency: roomRate.public.localPrice.currency,
price: roomRate.public.localPrice.pricePerStay,
regularPrice: roomRate.public.localPrice.regularPricePerStay,
},
},
}
}
if (roomRate.chequeRate) {
if ("corporateCheque" in roomRate) {
return {
perNight: {
requested: roomRate.chequeRate.requestedPrice && {
currency: CurrencyEnum.CC,
price: roomRate.chequeRate.requestedPrice.numberOfBonusCheques,
additionalPrice:
roomRate.chequeRate.requestedPrice.additionalPricePerStay,
additionalPriceCurrency: roomRate.chequeRate.requestedPrice.currency,
},
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.chequeRate.localPrice.numberOfBonusCheques,
price: roomRate.corporateCheque.localPrice.numberOfCheques,
additionalPrice:
roomRate.chequeRate.localPrice.additionalPricePerStay,
additionalPriceCurrency: roomRate.chequeRate.localPrice.currency,
roomRate.corporateCheque.localPrice.additionalPricePerStay,
additionalPriceCurrency:
roomRate.corporateCheque.localPrice.currency ?? undefined,
},
},
perStay: {
requested: roomRate.chequeRate.requestedPrice && {
currency: CurrencyEnum.CC,
price: roomRate.chequeRate.requestedPrice.numberOfBonusCheques,
additionalPrice:
roomRate.chequeRate.requestedPrice.additionalPricePerStay,
additionalPriceCurrency: roomRate.chequeRate.requestedPrice.currency,
},
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.chequeRate.localPrice.numberOfBonusCheques,
price: roomRate.corporateCheque.localPrice.numberOfCheques,
additionalPrice:
roomRate.chequeRate.localPrice.additionalPricePerStay,
additionalPriceCurrency: roomRate.chequeRate.localPrice.currency,
roomRate.corporateCheque.localPrice.additionalPricePerStay,
additionalPriceCurrency:
roomRate.corporateCheque.localPrice.currency ?? undefined,
},
},
}
}
if (roomRate.voucherRate) {
if ("voucher" in roomRate) {
return {
perNight: {
requested: undefined,
local: {
currency: CurrencyEnum.Voucher,
price: roomRate.voucherRate.numberOfVouchers,
price: roomRate.voucher.numberOfVouchers,
},
},
perStay: {
requested: undefined,
local: {
currency: CurrencyEnum.Voucher,
price: roomRate.voucherRate.numberOfVouchers,
price: roomRate.voucher.numberOfVouchers,
},
},
}
}
if (roomRate.redemptionRate) {
if ("redemption" in roomRate) {
return {
// ToDo Handle perNight as undefined
perNight: {
requested: undefined,
local: {
currency:
roomRate.redemptionRate.localPrice.currency ?? CurrencyEnum.POINTS,
price: roomRate.redemptionRate.localPrice.pointsPerStay,
currency: CurrencyEnum.POINTS,
price: roomRate.redemption.localPrice.pointsPerStay,
additionalPrice:
roomRate.redemptionRate.localPrice.additionalPricePerStay,
roomRate.redemption.localPrice.additionalPricePerStay,
additionalPriceCurrency:
roomRate.redemptionRate.localPrice.additionalPriceCurrency,
roomRate.redemption.localPrice.currency ?? undefined,
},
},
perStay: {
requested: undefined,
local: {
currency:
roomRate.redemptionRate.localPrice.currency ?? CurrencyEnum.POINTS,
price: roomRate.redemptionRate.localPrice.pointsPerStay,
currency: CurrencyEnum.POINTS,
price: roomRate.redemption.localPrice.pointsPerStay,
additionalPrice:
roomRate.redemptionRate.localPrice.additionalPricePerStay,
roomRate.redemption.localPrice.additionalPricePerStay,
additionalPriceCurrency:
roomRate.redemptionRate.localPrice.additionalPriceCurrency,
roomRate.redemption.localPrice.currency ?? undefined,
},
},
}
@@ -243,58 +274,64 @@ export function getRoomPrice(roomRate: RoomRate, isMember: boolean) {
export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) {
return roomRates.reduce<Price>(
(total, roomRate, idx) => {
const isFirstRoom = idx === 0
const rate =
isFirstRoom && isMember && roomRate.memberRate
? roomRate.memberRate
: roomRate.publicRate
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
}
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,
},
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
},
{
requested: undefined,
local: {
currency: (roomRates[0].publicRate?.localPrice.currency ||
roomRates[0].memberRate?.localPrice.currency)!,
currency: CurrencyEnum.Unknown,
price: 0,
},
requested: undefined,
}
)
}
export const calculateVoucherPrice = (roomRates: RoomRate[]) => {
export function calculateVoucherPrice(roomRates: RoomRate[]) {
return roomRates.reduce<Price>(
(total, room) => {
const rate = room.voucherRate
if (!rate) {
if (!("voucher" in room)) {
return total
}
return <Price>{
return {
local: {
currency: total.local.currency,
price: total.local.price + rate.numberOfVouchers,
price: total.local.price + room.voucher.numberOfVouchers,
},
requested: undefined,
}
@@ -309,50 +346,56 @@ export const calculateVoucherPrice = (roomRates: RoomRate[]) => {
)
}
export const calculateChequePrice = (roomRates: RoomRate[]) => {
export function calculateCorporateChequePrice(roomRates: RoomRate[]) {
return roomRates.reduce<Price>(
(total, room) => {
const rate = room.chequeRate
if (!rate) {
if (!("corporateCheque" in room)) {
return total
}
const price = total.local.price + rate.localPrice.numberOfBonusCheques
const rate = room.corporateCheque
const additionalPrice =
rate.localPrice.numberOfBonusCheques &&
(total.local.additionalPrice ?? 0) +
(rate.localPrice.additionalPricePerStay ?? 0)
const additionalPriceCurrency = (rate.localPrice.numberOfBonusCheques &&
rate.localPrice.currency)!
total.local.price = add(
total.local.price,
rate.localPrice.numberOfCheques
)
const requestedPrice = rate.requestedPrice?.numberOfBonusCheques
? (total.requested?.price ?? 0) +
rate.requestedPrice?.numberOfBonusCheques
: total.requested?.price
const requestedAdditionalPrice =
rate.requestedPrice?.additionalPricePerStay &&
(total.requested?.additionalPrice ??
0 + rate.requestedPrice?.additionalPricePerStay ??
0)
return <Price>{
local: {
currency: CurrencyEnum.CC,
price,
additionalPrice,
additionalPriceCurrency,
},
requested: rate.requestedPrice
? {
currency: CurrencyEnum.CC,
price: requestedPrice,
additionalPrice: requestedAdditionalPrice,
additionalPriceCurrency: rate.requestedPrice?.currency,
}
: undefined,
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: {
@@ -437,8 +480,10 @@ export function calcTotalPrice(
breakfastLocalPrice * room.adults * nights,
roomFeaturesTotal?.local ?? 0
),
additionalPriceCurrency:
roomPrice.perStay.local.additionalPriceCurrency,
additionalPriceCurrency: roomPrice.perStay.local
.additionalPriceCurrency
? roomPrice.perStay.local.additionalPriceCurrency
: undefined,
},
}

View File

@@ -11,7 +11,7 @@ import { DetailsContext } from "@/contexts/Details"
import {
add,
calcTotalPrice,
calculateChequePrice,
calculateCorporateChequePrice,
calculateVoucherPrice,
checkRoomProgress,
extractGuestFromUser,
@@ -23,9 +23,8 @@ import {
} from "./helpers"
import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast"
import {
PointsPriceSchema,
type Price} from "@/types/components/hotelReservation/price";
import type { Price } from "@/types/components/hotelReservation/price"
import { CurrencyEnum } from "@/types/enums/currency"
import { StepEnum } from "@/types/enums/step"
import type {
DetailsState,
@@ -58,20 +57,36 @@ export function createDetailsStore(
const isRedemption =
new URLSearchParams(searchParams).get("searchtype") === REDEMPTION
const isVoucher = initialState.rooms.some((room) => room.roomRate.voucherRate)
const isCorpChq = initialState.rooms.some((room) => room.roomRate.chequeRate)
const isVoucher = initialState.rooms.some(
(room) => "voucher" in room.roomRate
)
const isCorpChq = initialState.rooms.some(
(room) => "corporateCheque" in room.roomRate
)
let initialTotalPrice: Price
if (isRedemption && initialState.rooms[0].roomRate.redemptionRate) {
initialTotalPrice = PointsPriceSchema.parse(
initialState.rooms[0].roomRate.redemptionRate
)
const roomOneRoomRate = initialState.rooms[0].roomRate
if (isRedemption && "redemption" in roomOneRoomRate) {
initialTotalPrice = {
local: {
currency: CurrencyEnum.POINTS,
price: roomOneRoomRate.redemption.localPrice.pointsPerStay,
},
}
if (roomOneRoomRate.redemption.localPrice.currency) {
initialTotalPrice.local.additionalPriceCurrency =
roomOneRoomRate.redemption.localPrice.currency
}
if (roomOneRoomRate.redemption.localPrice.additionalPricePerStay) {
initialTotalPrice.local.additionalPrice =
roomOneRoomRate.redemption.localPrice.additionalPricePerStay
}
} else if (isVoucher) {
initialTotalPrice = calculateVoucherPrice(
initialState.rooms.map((r) => r.roomRate)
)
} else if (isCorpChq) {
initialTotalPrice = calculateChequePrice(
initialTotalPrice = calculateCorporateChequePrice(
initialState.rooms.map((r) => r.roomRate)
)
} else {
@@ -98,56 +113,6 @@ export function createDetailsStore(
}
})
const rooms: RoomState[] = initialState.rooms.map((room, idx) => {
const steps: RoomState["steps"] = {
[StepEnum.selectBed]: {
step: StepEnum.selectBed,
isValid: !!room.bedType,
},
[StepEnum.breakfast]: {
step: StepEnum.breakfast,
isValid: false,
},
[StepEnum.details]: {
step: StepEnum.details,
isValid: false,
},
}
if (room.breakfastIncluded || !breakfastPackages?.length) {
delete steps[StepEnum.breakfast]
}
const currentStep =
Object.values(steps).find((step) => !step.isValid)?.step ??
StepEnum.selectBed
return {
room: {
...room,
adults: initialState.booking.rooms[idx].adults,
childrenInRoom: initialState.booking.rooms[idx].childrenInRoom,
bedType: room.bedType,
breakfast:
!breakfastPackages?.length || room.breakfastIncluded
? false
: undefined,
guest:
isMember && idx === 0
? deepmerge(defaultGuestState, extractGuestFromUser(user))
: defaultGuestState,
roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
specialRequest: {
comment: "",
},
},
currentStep,
isComplete: false,
steps,
}
})
return create<DetailsState>()((set) => ({
activeRoom: 0,
booking: initialState.booking,
@@ -156,74 +121,386 @@ export function createDetailsStore(
isSubmittingDisabled: false,
isSummaryOpen: false,
lastRoom: initialState.booking.rooms.length - 1,
rooms,
rooms: initialState.rooms.map((room, idx) => {
const steps: RoomState["steps"] = {
[StepEnum.selectBed]: {
step: StepEnum.selectBed,
isValid: !!room.bedType,
},
[StepEnum.breakfast]: {
step: StepEnum.breakfast,
isValid: false,
},
[StepEnum.details]: {
step: StepEnum.details,
isValid: false,
},
}
if (room.breakfastIncluded || !breakfastPackages?.length) {
delete steps[StepEnum.breakfast]
}
const currentStep =
Object.values(steps).find((step) => !step.isValid)?.step ??
StepEnum.selectBed
return {
actions: {
setStep(step) {
return set(
produce((state: DetailsState) => {
const isSameRoom = idx === state.activeRoom
const room = state.rooms[idx]
if (isSameRoom) {
// Closed same accordion as was open
if (step === room.currentStep) {
if (room.isComplete) {
// Room is complete, move to next room or payment
const nextRoomIdx = state.rooms.findIndex(
(r) => !r.isComplete
)
state.activeRoom = nextRoomIdx
// Done, proceed to payment
if (nextRoomIdx === -1) {
room.currentStep = null
} else {
const nextRoom = state.rooms[nextRoomIdx]
const nextInvalidStep = findNextInvalidStep(nextRoom)
nextRoom.currentStep = nextInvalidStep
}
} else {
room.currentStep = findNextInvalidStep(room)
}
} else {
if (room.steps[step]?.isValid) {
room.currentStep = step
} else {
room.currentStep = findNextInvalidStep(room)
}
}
} else {
const arePreviousRoomsCompleted = state.rooms
.slice(0, idx)
.every((room) => room.isComplete)
if (arePreviousRoomsCompleted) {
state.activeRoom = idx
if (room.steps[step]?.isValid) {
room.currentStep = step
} else {
room.currentStep = findNextInvalidStep(room)
}
} else {
const firstIncompleteRoom = state.rooms.findIndex(
(r) => !r.isComplete
)
state.activeRoom = firstIncompleteRoom
if (firstIncompleteRoom === -1) {
// All rooms are done, proceed to payment
room.currentStep = null
} else {
const nextRoom = state.rooms[firstIncompleteRoom]
nextRoom.currentStep = findNextInvalidStep(nextRoom)
}
}
}
})
)
},
updateBedType(bedType) {
return set(
produce((state: DetailsState) => {
state.rooms[idx].steps[StepEnum.selectBed].isValid = true
state.rooms[idx].room.bedType = bedType
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(state.rooms[idx], state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
},
updateBreakfast(breakfast) {
return set(
produce((state: DetailsState) => {
const currentRoom = state.rooms[idx]
if (currentRoom.steps[StepEnum.breakfast]) {
currentRoom.steps[StepEnum.breakfast].isValid = true
}
const currentTotalPriceRequested = state.totalPrice.requested
let stateTotalRequestedPrice = 0
if (currentTotalPriceRequested) {
stateTotalRequestedPrice =
currentTotalPriceRequested.price ?? 0
}
const stateTotalLocalPrice = state.totalPrice.local.price
const stateTotalLocalRegularPrice =
state.totalPrice.local.regularPrice
const addToTotalPrice =
(currentRoom.room.breakfast === undefined ||
currentRoom.room.breakfast === false) &&
!!breakfast
const subtractFromTotalPrice =
currentRoom.room.breakfast && breakfast === false
const nights = dt(state.booking.toDate).diff(
state.booking.fromDate,
"days"
)
if (addToTotalPrice) {
const breakfastTotalRequestedPrice =
breakfast.requestedPrice.price *
currentRoom.room.adults *
nights
const breakfastTotalPrice =
breakfast.localPrice.price *
currentRoom.room.adults *
nights
state.totalPrice = {
requested: state.totalPrice.requested && {
currency: state.totalPrice.requested.currency,
price:
stateTotalRequestedPrice + breakfastTotalRequestedPrice,
},
local: {
currency: breakfast.localPrice.currency,
price: stateTotalLocalPrice ?? 0 + breakfastTotalPrice,
regularPrice: stateTotalLocalRegularPrice
? stateTotalLocalRegularPrice + breakfastTotalPrice
: undefined,
},
}
}
if (subtractFromTotalPrice) {
let currency = state.totalPrice.local.currency
let currentBreakfastTotalPrice = 0
let currentBreakfastTotalRequestedPrice = 0
if (currentRoom.room.breakfast) {
currentBreakfastTotalPrice =
currentRoom.room.breakfast.localPrice.price *
currentRoom.room.adults *
nights
currentBreakfastTotalRequestedPrice =
currentRoom.room.breakfast.requestedPrice.totalPrice *
currentRoom.room.adults *
nights
currency = currentRoom.room.breakfast.localPrice.currency
}
let requestedPrice =
stateTotalRequestedPrice -
currentBreakfastTotalRequestedPrice
if (requestedPrice < 0) {
requestedPrice = 0
}
let localPrice =
stateTotalLocalPrice - currentBreakfastTotalPrice
if (localPrice < 0) {
localPrice = 0
}
let regularPrice = stateTotalLocalRegularPrice
? stateTotalLocalRegularPrice - currentBreakfastTotalPrice
: undefined
state.totalPrice = {
requested: state.totalPrice.requested && {
currency: state.totalPrice.requested.currency,
price: requestedPrice,
},
local: {
currency,
price: localPrice,
regularPrice,
},
}
}
currentRoom.room.breakfast = breakfast
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(currentRoom, state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
},
updateDetails(data) {
return set(
produce((state: DetailsState) => {
state.rooms[idx].steps[StepEnum.details].isValid = true
const currentRoom = state.rooms[idx].room
currentRoom.guest.countryCode = data.countryCode
currentRoom.guest.email = data.email
currentRoom.guest.firstName = data.firstName
currentRoom.guest.join = data.join
currentRoom.guest.lastName = data.lastName
if (data.specialRequest?.comment) {
currentRoom.specialRequest.comment =
data.specialRequest.comment
}
if (data.join) {
currentRoom.guest.membershipNo = undefined
} else {
currentRoom.guest.membershipNo = data.membershipNo
}
currentRoom.guest.phoneNumber = data.phoneNumber
// Only valid for room 1
if (idx === 0 && data.join && !isMember) {
if ("dateOfBirth" in currentRoom.guest) {
currentRoom.guest.dateOfBirth = data.dateOfBirth
}
if ("zipCode" in currentRoom.guest) {
currentRoom.guest.zipCode = data.zipCode
}
}
currentRoom.roomPrice = getRoomPrice(
currentRoom.roomRate,
Boolean(data.join || data.membershipNo || isMember)
)
const nights = dt(state.booking.toDate).diff(
state.booking.fromDate,
"days"
)
state.totalPrice = calcTotalPrice(
state.rooms,
state.totalPrice.local.currency,
isMember,
nights
)
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(state.rooms[idx], state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
},
updateMultiroomDetails(data) {
return set(
produce((state: DetailsState) => {
state.rooms[idx].steps[StepEnum.details].isValid = true
state.rooms[idx].room.guest.countryCode = data.countryCode
state.rooms[idx].room.guest.email = data.email
state.rooms[idx].room.guest.firstName = data.firstName
state.rooms[idx].room.guest.join = data.join
state.rooms[idx].room.guest.lastName = data.lastName
if (data.join) {
state.rooms[idx].room.guest.membershipNo = undefined
} else {
state.rooms[idx].room.guest.membershipNo = data.membershipNo
}
state.rooms[idx].room.guest.phoneNumber = data.phoneNumber
const getMemberPrice = Boolean(data.join || data.membershipNo)
state.rooms[idx].room.roomPrice = getRoomPrice(
state.rooms[idx].room.roomRate,
getMemberPrice
)
const nights = dt(state.booking.toDate).diff(
state.booking.fromDate,
"days"
)
state.totalPrice = calcTotalPrice(
state.rooms,
state.totalPrice.local.currency,
getMemberPrice,
nights
)
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(state.rooms[idx], state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
},
},
room: {
...room,
adults: initialState.booking.rooms[idx].adults,
childrenInRoom: initialState.booking.rooms[idx].childrenInRoom,
bedType: room.bedType,
breakfast:
!breakfastPackages?.length || room.breakfastIncluded
? false
: undefined,
guest:
isMember && idx === 0
? deepmerge(defaultGuestState, extractGuestFromUser(user))
: defaultGuestState,
roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
specialRequest: {
comment: "",
},
},
currentStep,
isComplete: false,
steps,
}
}),
searchParamString: searchParams,
totalPrice: initialTotalPrice,
vat: initialState.vat,
actions: {
setStep(idx) {
return function (step) {
return set(
produce((state: DetailsState) => {
const isSameRoom = idx === state.activeRoom
const room = state.rooms[idx]
if (isSameRoom) {
// Closed same accordion as was open
if (step === room.currentStep) {
if (room.isComplete) {
// Room is complete, move to next room or payment
const nextRoomIdx = state.rooms.findIndex(
(r) => !r.isComplete
)
state.activeRoom = nextRoomIdx
// Done, proceed to payment
if (nextRoomIdx === -1) {
room.currentStep = null
} else {
const nextRoom = state.rooms[nextRoomIdx]
const nextInvalidStep = findNextInvalidStep(nextRoom)
nextRoom.currentStep = nextInvalidStep
}
} else {
room.currentStep = findNextInvalidStep(room)
}
} else {
if (room.steps[step]?.isValid) {
room.currentStep = step
} else {
room.currentStep = findNextInvalidStep(room)
}
}
} else {
const arePreviousRoomsCompleted = state.rooms
.slice(0, idx)
.every((room) => room.isComplete)
if (arePreviousRoomsCompleted) {
state.activeRoom = idx
if (room.steps[step]?.isValid) {
room.currentStep = step
} else {
room.currentStep = findNextInvalidStep(room)
}
} else {
const firstIncompleteRoom = state.rooms.findIndex(
(r) => !r.isComplete
)
state.activeRoom = firstIncompleteRoom
if (firstIncompleteRoom === -1) {
// All rooms are done, proceed to payment
room.currentStep = null
} else {
const nextRoom = state.rooms[firstIncompleteRoom]
nextRoom.currentStep = findNextInvalidStep(nextRoom)
}
}
}
})
)
}
},
setIsSubmittingDisabled(isSubmittingDisabled) {
return set(
produce((state: DetailsState) => {
@@ -246,273 +523,6 @@ export function createDetailsStore(
})
)
},
updateBedType(idx) {
return function (bedType) {
return set(
produce((state: DetailsState) => {
state.rooms[idx].steps[StepEnum.selectBed].isValid = true
state.rooms[idx].room.bedType = bedType
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(state.rooms[idx], state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
}
},
updateBreakfast(idx) {
return function (breakfast) {
return set(
produce((state: DetailsState) => {
const currentRoom = state.rooms[idx]
if (currentRoom.steps[StepEnum.breakfast]) {
currentRoom.steps[StepEnum.breakfast].isValid = true
}
const currentTotalPriceRequested = state.totalPrice.requested
let stateTotalRequestedPrice = 0
if (currentTotalPriceRequested) {
stateTotalRequestedPrice = currentTotalPriceRequested.price ?? 0
}
const stateTotalLocalPrice = state.totalPrice.local.price
const stateTotalLocalRegularPrice =
state.totalPrice.local.regularPrice
const addToTotalPrice =
(currentRoom.room.breakfast === undefined ||
currentRoom.room.breakfast === false) &&
!!breakfast
const subtractFromTotalPrice =
currentRoom.room.breakfast && breakfast === false
const nights = dt(state.booking.toDate).diff(
state.booking.fromDate,
"days"
)
if (addToTotalPrice) {
const breakfastTotalRequestedPrice =
breakfast.requestedPrice.price *
currentRoom.room.adults *
nights
const breakfastTotalPrice =
breakfast.localPrice.price * currentRoom.room.adults * nights
state.totalPrice = {
requested: state.totalPrice.requested && {
currency: state.totalPrice.requested.currency,
price:
stateTotalRequestedPrice + breakfastTotalRequestedPrice,
},
local: {
currency: breakfast.localPrice.currency,
price: stateTotalLocalPrice ?? 0 + breakfastTotalPrice,
regularPrice: stateTotalLocalRegularPrice
? stateTotalLocalRegularPrice + breakfastTotalPrice
: undefined,
},
}
}
if (subtractFromTotalPrice) {
let currency = state.totalPrice.local.currency
let currentBreakfastTotalPrice = 0
let currentBreakfastTotalRequestedPrice = 0
if (currentRoom.room.breakfast) {
currentBreakfastTotalPrice =
currentRoom.room.breakfast.localPrice.price *
currentRoom.room.adults *
nights
currentBreakfastTotalRequestedPrice =
currentRoom.room.breakfast.requestedPrice.totalPrice *
currentRoom.room.adults *
nights
currency = currentRoom.room.breakfast.localPrice.currency
}
let requestedPrice =
stateTotalRequestedPrice - currentBreakfastTotalRequestedPrice
if (requestedPrice < 0) {
requestedPrice = 0
}
let localPrice =
stateTotalLocalPrice - currentBreakfastTotalPrice
if (localPrice < 0) {
localPrice = 0
}
let regularPrice = stateTotalLocalRegularPrice
? stateTotalLocalRegularPrice - currentBreakfastTotalPrice
: undefined
state.totalPrice = {
requested: state.totalPrice.requested && {
currency: state.totalPrice.requested.currency,
price: requestedPrice,
},
local: {
currency,
price: localPrice,
regularPrice,
},
}
}
currentRoom.room.breakfast = breakfast
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(currentRoom, state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
}
},
updateDetails(idx) {
return function (data) {
return set(
produce((state: DetailsState) => {
state.rooms[idx].steps[StepEnum.details].isValid = true
const currentRoom = state.rooms[idx].room
currentRoom.guest.countryCode = data.countryCode
currentRoom.guest.email = data.email
currentRoom.guest.firstName = data.firstName
currentRoom.guest.join = data.join
currentRoom.guest.lastName = data.lastName
if (data.specialRequest?.comment) {
currentRoom.specialRequest.comment = data.specialRequest.comment
}
if (data.join) {
currentRoom.guest.membershipNo = undefined
} else {
currentRoom.guest.membershipNo = data.membershipNo
}
currentRoom.guest.phoneNumber = data.phoneNumber
// Only valid for room 1
if (idx === 0 && data.join && !isMember) {
if ("dateOfBirth" in currentRoom.guest) {
currentRoom.guest.dateOfBirth = data.dateOfBirth
}
if ("zipCode" in currentRoom.guest) {
currentRoom.guest.zipCode = data.zipCode
}
}
currentRoom.roomPrice = getRoomPrice(
currentRoom.roomRate,
Boolean(data.join || data.membershipNo || isMember)
)
const nights = dt(state.booking.toDate).diff(
state.booking.fromDate,
"days"
)
state.totalPrice = calcTotalPrice(
state.rooms,
state.totalPrice.local.currency,
isMember,
nights
)
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(state.rooms[idx], state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
}
},
updateMultiroomDetails(idx) {
return function (data) {
return set(
produce((state: DetailsState) => {
state.rooms[idx].steps[StepEnum.details].isValid = true
state.rooms[idx].room.guest.countryCode = data.countryCode
state.rooms[idx].room.guest.email = data.email
state.rooms[idx].room.guest.firstName = data.firstName
state.rooms[idx].room.guest.join = data.join
state.rooms[idx].room.guest.lastName = data.lastName
if (data.join) {
state.rooms[idx].room.guest.membershipNo = undefined
} else {
state.rooms[idx].room.guest.membershipNo = data.membershipNo
}
state.rooms[idx].room.guest.phoneNumber = data.phoneNumber
const getMemberPrice = Boolean(data.join || data.membershipNo)
state.rooms[idx].room.roomPrice = getRoomPrice(
state.rooms[idx].room.roomRate,
getMemberPrice
)
const nights = dt(state.booking.toDate).diff(
state.booking.fromDate,
"days"
)
state.totalPrice = calcTotalPrice(
state.rooms,
state.totalPrice.local.currency,
getMemberPrice,
nights
)
const isAllStepsCompleted = checkRoomProgress(
state.rooms[idx].steps
)
if (isAllStepsCompleted) {
state.rooms[idx].isComplete = true
}
handleStepProgression(state.rooms[idx], state)
writeToSessionStorage({
activeRoom: state.activeRoom,
booking: state.booking,
rooms: state.rooms,
})
})
)
}
},
updateSeachParamString(searchParamString) {
return set(
produce((state: DetailsState) => {