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:
committed by
Arvid Norlin
parent
f43ee4a0e6
commit
b394d54c3f
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user