Merged in feat/enter-details-multiroom (pull request #1280)

feat(SW-1259): Enter details multiroom

* refactor: remove per-step URLs

* WIP: map multiroom data

* fix: lint errors in details page

* fix: made useEnterDetailsStore tests pass

* fix: WIP refactor enter details store

* fix: WIP enter details store update

* fix: added room index to select correct room

* fix: added logic for navigating between steps and rooms

* fix: update summary to work with store changes

* fix: added room and total price calculation

* fix: removed unused code and added test for breakfast included

* refactor: move store selectors into helpers

* refactor: session storage state for multiroom booking

* feat: update enter details accordion navigation

* fix: added room index to each form component so they select correct room

* fix: added unique id to input to handle case when multiple inputs have same name

* fix: update payment step with store changes

* fix: rebase issues

* fix: now you should only be able to go to a step if previous room is completed

* refactor: cleanup

* fix: if no availability just skip that room for now

* fix: add select-rate Summary and adjust typings


Approved-by: Arvid Norlin
This commit is contained in:
Tobias Johansson
2025-02-11 14:24:24 +00:00
committed by Arvid Norlin
parent f43ee4a0e6
commit b394d54c3f
48 changed files with 1870 additions and 1150 deletions

View File

@@ -9,7 +9,7 @@ import LoadingSpinner from "@/components/LoadingSpinner"
import { trackPaymentEvent } from "@/utils/tracking"
import { convertObjToSearchParams } from "@/utils/url"
import type { PersistedState } from "@/types/stores/enter-details"
// import type { PersistedState } from "@/types/stores/enter-details"
export default function PaymentCallback({
returnUrl,
@@ -28,7 +28,7 @@ export default function PaymentCallback({
const bookingData = window.sessionStorage.getItem(detailsStorageName)
if (bookingData) {
const detailsStorage: PersistedState = JSON.parse(bookingData)
const detailsStorage: any = JSON.parse(bookingData) // TODO: fix type here
const searchParams = convertObjToSearchParams(
detailsStorage.booking,
searchObject

View File

@@ -26,6 +26,7 @@ import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import { toast } from "@/components/TempDesignSystem/Toasts"
import { useAvailablePaymentOptions } from "@/hooks/booking/useAvailablePaymentOptions"
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
@@ -55,7 +56,6 @@ function isPaymentMethodEnum(value: string): value is PaymentMethodEnum {
export default function PaymentClient({
user,
roomPrice,
otherPaymentOptions,
savedCreditCards,
mustBeGuaranteed,
@@ -65,13 +65,18 @@ export default function PaymentClient({
const intl = useIntl()
const searchParams = useSearchParams()
const totalPrice = useEnterDetailsStore((state) => state.totalPrice)
const { bedType, booking, breakfast } = useEnterDetailsStore((state) => ({
bedType: state.bedType,
booking: state.booking,
breakfast: state.breakfast,
}))
const userData = useEnterDetailsStore((state) => state.guest)
const { totalPrice, booking, rooms, bookingProgress } = useEnterDetailsStore(
(state) => {
return {
totalPrice: state.totalPrice,
booking: state.booking,
rooms: state.rooms,
bookingProgress: state.bookingProgress,
}
}
)
const canProceedToPayment = bookingProgress.canProceedToPayment
const setIsSubmittingDisabled = useEnterDetailsStore(
(state) => state.actions.setIsSubmittingDisabled
)
@@ -87,7 +92,7 @@ export default function PaymentClient({
newPrice: number
} | null>()
const { toDate, fromDate, rooms, hotelId } = booking
const { toDate, fromDate, hotelId } = booking
usePaymentFailedToast()
@@ -115,7 +120,7 @@ export default function PaymentClient({
if (priceChange) {
setPriceChangeData({
oldPrice: roomPrice.publicPrice,
oldPrice: rooms[0].roomPrice.perStay.local.price,
newPrice: priceChange.totalPrice,
})
} else {
@@ -202,18 +207,6 @@ export default function PaymentClient({
const handleSubmit = useCallback(
(data: PaymentFormData) => {
const {
firstName,
lastName,
email,
phoneNumber,
countryCode,
membershipNo,
join,
dateOfBirth,
zipCode,
} = userData
// set payment method to card if saved card is submitted
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
? data.paymentMethod
@@ -239,41 +232,50 @@ export default function PaymentClient({
hotelId,
checkInDate: fromDate,
checkOutDate: toDate,
rooms: rooms.map((room) => ({
rooms: rooms.map((room, idx) => ({
adults: room.adults,
childrenAges: room.childrenInRoom?.map((child) => ({
age: child.age,
bedType: bedTypeMap[parseInt(child.bed.toString())],
})),
rateCode:
(user || join || membershipNo) && room.counterRateCode
? room.counterRateCode
: room.rateCode,
roomTypeCode: bedType!.roomTypeCode, // A selection has been made in order to get to this step.
(user || room.guest.join || room.guest.membershipNo) &&
booking.rooms[idx].counterRateCode
? booking.rooms[idx].counterRateCode
: booking.rooms[idx].rateCode,
roomTypeCode: room.bedType!.roomTypeCode, // A selection has been made in order to get to this step.
guest: {
firstName,
lastName,
email,
phoneNumber,
countryCode,
membershipNumber: membershipNo,
becomeMember: join,
dateOfBirth,
postalCode: zipCode,
firstName: room.guest.firstName,
lastName: room.guest.lastName,
email: room.guest.email,
phoneNumber: room.guest.phoneNumber,
countryCode: room.guest.countryCode,
membershipNumber: room.guest.membershipNo,
becomeMember: room.guest.join,
dateOfBirth: room.guest.dateOfBirth,
postalCode: room.guest.zipCode,
},
packages: {
breakfast: !!(breakfast && breakfast.code),
breakfast: !!(room.breakfast && room.breakfast.code),
allergyFriendly:
room.packages?.includes(RoomPackageCodeEnum.ALLERGY_ROOM) ??
false,
room.roomFeatures?.some(
(feature) => feature.code === RoomPackageCodeEnum.ALLERGY_ROOM
) ?? false,
petFriendly:
room.packages?.includes(RoomPackageCodeEnum.PET_ROOM) ?? false,
room.roomFeatures?.some(
(feature) => feature.code === RoomPackageCodeEnum.PET_ROOM
) ?? false,
accessibility:
room.packages?.includes(RoomPackageCodeEnum.ACCESSIBILITY_ROOM) ??
false,
room.roomFeatures?.some(
(feature) =>
feature.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
) ?? false,
},
smsConfirmationRequested: data.smsConfirmation,
roomPrice,
roomPrice: {
memberPrice: room.roomRate.memberRate?.localPrice.pricePerStay,
publicPrice: room.roomRate.publicRate.localPrice.pricePerStay,
},
})),
payment: {
paymentMethod,
@@ -292,7 +294,6 @@ export default function PaymentClient({
})
},
[
userData,
savedCreditCards,
lang,
initiateBooking,
@@ -301,9 +302,7 @@ export default function PaymentClient({
toDate,
rooms,
user,
bedType,
breakfast,
roomPrice,
booking,
]
)
@@ -316,8 +315,22 @@ export default function PaymentClient({
return <LoadingSpinner />
}
const paymentGuarantee = intl.formatMessage({
id: "Payment Guarantee",
})
const payment = intl.formatMessage({
id: "Payment",
})
return (
<>
<section
className={`${styles.paymentSection} ${canProceedToPayment ? "" : styles.disabled}`}
>
<header>
<Title level="h2" as="h4">
{mustBeGuaranteed ? paymentGuarantee : payment}
</Title>
</header>
<FormProvider {...methods}>
<form
className={styles.paymentContainer}
@@ -460,6 +473,6 @@ export default function PaymentClient({
}
/>
) : null}
</>
</section>
)
}

View File

@@ -6,7 +6,6 @@ import type { PaymentProps } from "@/types/components/hotelReservation/selectRat
export default async function Payment({
user,
roomPrice,
otherPaymentOptions,
mustBeGuaranteed,
supportedCards,
@@ -18,7 +17,6 @@ export default async function Payment({
return (
<PaymentClient
user={user}
roomPrice={roomPrice}
otherPaymentOptions={otherPaymentOptions}
savedCreditCards={savedCreditCards}
mustBeGuaranteed={mustBeGuaranteed}

View File

@@ -1,3 +1,14 @@
.paymentSection {
display: flex;
flex-direction: column;
gap: var(--Spacing-x4);
}
.disabled {
opacity: 0.5;
pointer-events: none;
}
.paymentContainer {
display: flex;
flex-direction: column;