Merged in fix/BOOK-323-enter-details-scroll-error (pull request #2986)

Fix/BOOK-323 enter details scroll error

* fix(BOOK-323): scroll to invalid element on submit on enter details

* fix(BOOK-323): update error message design

* fix(BOOK-323): clean up

* fix(BOOK-323): scroll to fields in room in right order

* fix(BOOK-323): add id to translations

* fix(BOOK-323): remove undefined

* fix(BOOK-323): fix submitting state

* fix(BOOK-323): use ref in multiroom for scrolling to right element, add membershipNo

* fix(BOOK-323): fix invalid border country

* fix(BOOK-323): use error message component

* fix(BOOK-323): fix invalid focused styling on mobile

* fix(BOOK-323): remove redundant dependency in callback


Approved-by: Erik Tiekstra
This commit is contained in:
Bianca Widstam
2025-10-24 11:30:56 +00:00
parent 6543ca5dc3
commit c473bbc8b0
27 changed files with 692 additions and 288 deletions

View File

@@ -95,15 +95,15 @@ export default function PaymentClient({
rooms,
totalPrice,
isSubmitting,
preSubmitCallbacks,
setIsSubmitting,
runPreSubmitCallbacks,
} = useEnterDetailsStore((state) => ({
booking: state.booking,
rooms: state.rooms,
totalPrice: state.totalPrice,
preSubmitCallbacks: state.preSubmitCallbacks,
isSubmitting: state.isSubmitting,
setIsSubmitting: state.actions.setIsSubmitting,
runPreSubmitCallbacks: state.actions.runPreSubmitCallbacks,
}))
const bookingMustBeGuaranteed = rooms.some(({ room }, idx) => {
@@ -312,35 +312,39 @@ export default function PaymentClient({
[hasFlexRates]
)
const scrollToInvalidField = useCallback(async (): Promise<boolean> => {
// If any room is not complete/valid, scroll to the first invalid field, this is needed as rooms and other fields are in separate forms
const invalidField = await runPreSubmitCallbacks()
const errorNames = Object.keys(methods.formState.errors)
const firstIncompleteRoomIndex = rooms.findIndex((room) => !room.isComplete)
const scrollToElement = (el: HTMLElement) => {
const offset = getTopOffset()
const top = el.getBoundingClientRect().top + window.scrollY - offset - 20
window.scrollTo({ top, behavior: "smooth" })
const input = el.querySelector<HTMLElement>("input")
input?.focus({ preventScroll: true })
}
if (invalidField) {
scrollToElement(invalidField)
} else if (errorNames.length > 0) {
const firstErrorEl = document.querySelector(`[name="${errorNames[0]}"]`)
if (firstErrorEl) {
scrollToElement(firstErrorEl as HTMLElement)
}
}
return firstIncompleteRoomIndex !== -1
}, [runPreSubmitCallbacks, rooms, methods.formState.errors, getTopOffset])
const handleSubmit = useCallback(
(data: PaymentFormData) => {
async (data: PaymentFormData) => {
setIsSubmitting(true)
Object.values(preSubmitCallbacks).forEach((callback) => {
callback()
})
const firstIncompleteRoomIndex = rooms.findIndex(
(room) => !room.isComplete
)
// If any room is not complete/valid, scroll to it
if (firstIncompleteRoomIndex !== -1) {
const roomElement = document.getElementById(
`room-${firstIncompleteRoomIndex + 1}`
)
if (!roomElement) {
setIsSubmitting(false)
return
}
const roomElementTop =
roomElement.getBoundingClientRect().top + window.scrollY
window.scrollTo({
top: roomElementTop - getTopOffset() - 20,
behavior: "smooth",
})
const isRoomInvalid = await scrollToInvalidField()
if (isRoomInvalid) {
setIsSubmitting(false)
return
}
@@ -502,13 +506,11 @@ export default function PaymentClient({
}
),
}
initiateBooking.mutate(payload)
},
[
setIsSubmitting,
preSubmitCallbacks,
rooms,
scrollToInvalidField,
getPaymentMethod,
savedCreditCards,
lang,
@@ -517,8 +519,8 @@ export default function PaymentClient({
fromDate,
toDate,
hotelId,
rooms,
initiateBooking,
getTopOffset,
isUserLoggedIn,
booking.rooms,
user?.data?.partnerLoyaltyNumber,
@@ -534,6 +536,13 @@ export default function PaymentClient({
defaultMessage: "Select payment method",
})
const handleInvalidSubmit = async () => {
const valid = await methods.trigger()
if (!valid) {
await scrollToInvalidField()
}
}
return (
<section
className={cx(styles.paymentSection, {
@@ -549,7 +558,7 @@ export default function PaymentClient({
<FormProvider {...methods}>
<form
className={styles.paymentContainer}
onSubmit={methods.handleSubmit(handleSubmit)}
onSubmit={methods.handleSubmit(handleSubmit, handleInvalidSubmit)}
id={formId}
>
{booking.searchType === SEARCH_TYPE_REDEMPTION ? (