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

@@ -1,7 +1,7 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useCallback, useEffect, useMemo } from "react"
import { useCallback, useEffect, useMemo, useRef } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
@@ -32,6 +32,7 @@ export default function Details() {
const intl = useIntl()
const lang = useLang()
const config = useBookingFlowConfig()
const refs = useRef<Record<string, HTMLElement | null>>({})
const { addPreSubmitCallback, rooms } = useEnterDetailsStore((state) => ({
addPreSubmitCallback: state.actions.addPreSubmitCallback,
@@ -106,12 +107,32 @@ export default function Details() {
)
useEffect(() => {
function callback() {
trigger()
async function callback() {
await trigger()
trackFormSubmit()
const fieldOrder = [
"firstName",
"lastName",
"countryCode",
"email",
"phoneNumber",
"membershipNo",
]
for (const name of fieldOrder) {
const fieldError =
methods.formState.errors[
name as keyof typeof methods.formState.errors
]
if (fieldError && refs.current[name]) {
return refs.current[name] ?? undefined
}
}
return
}
addPreSubmitCallback(`${idx}-details`, callback)
}, [addPreSubmitCallback, idx, trigger, trackFormSubmit])
}, [addPreSubmitCallback, idx, trigger, trackFormSubmit, methods])
const updateDetailsStore = useCallback(() => {
if (isValid) {
@@ -188,88 +209,124 @@ export default function Details() {
defaultMessage: "Guest information",
})}
</Footnote>
<BookingFlowInput
label={intl.formatMessage({
id: "common.firstName",
defaultMessage: "First name",
})}
maxLength={30}
name="firstName"
registerOptions={{
required: true,
deps: "lastName",
onBlur: updateDetailsStore,
<div
ref={(el) => {
refs.current.firstName = el
}}
/>
<BookingFlowInput
label={intl.formatMessage({
id: "common.lastName",
defaultMessage: "Last name",
})}
maxLength={30}
name="lastName"
registerOptions={{
required: true,
deps: "firstName",
onBlur: updateDetailsStore,
}}
/>
<CountrySelect
className={styles.fullWidth}
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
config.variant,
errors.countryCode?.message
)}
label={intl.formatMessage({
id: "common.country",
defaultMessage: "Country",
})}
lang={lang}
name="countryCode"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
<BookingFlowInput
className={styles.fullWidth}
label={intl.formatMessage({
id: "common.emailAddress",
defaultMessage: "Email address",
})}
name="email"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
<Phone
countryLabel={intl.formatMessage({
id: "common.countryCode",
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(
intl,
config.variant,
errors.phoneNumber?.message
)}
className={styles.fullWidth}
label={intl.formatMessage({
id: "common.phoneNumber",
defaultMessage: "Phone number",
})}
name="phoneNumber"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
{showMembershipIdInput ? (
>
<BookingFlowInput
className={styles.fullWidth}
label={intl.formatMessage({
id: "common.membershipId",
defaultMessage: "Membership ID",
id: "common.firstName",
defaultMessage: "First name",
})}
name="membershipNo"
type="tel"
registerOptions={{ onBlur: updateDetailsStore }}
maxLength={30}
name="firstName"
registerOptions={{
required: true,
deps: "lastName",
onBlur: updateDetailsStore,
}}
/>
</div>
<div
ref={(el) => {
refs.current.lastName = el
}}
>
<BookingFlowInput
label={intl.formatMessage({
id: "common.lastName",
defaultMessage: "Last name",
})}
maxLength={30}
name="lastName"
registerOptions={{
required: true,
deps: "firstName",
onBlur: updateDetailsStore,
}}
/>
</div>
<div
ref={(el) => {
refs.current.countryCode = el
}}
className={styles.fullWidth}
>
<CountrySelect
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
config.variant,
errors.countryCode?.message
)}
label={intl.formatMessage({
id: "common.country",
defaultMessage: "Country",
})}
lang={lang}
name="countryCode"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
</div>
<div
ref={(el) => {
refs.current.email = el
}}
className={styles.fullWidth}
>
<BookingFlowInput
label={intl.formatMessage({
id: "common.emailAddress",
defaultMessage: "Email address",
})}
name="email"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
</div>
<div
ref={(el) => {
refs.current.phoneNumber = el
}}
className={styles.fullWidth}
>
<Phone
countryLabel={intl.formatMessage({
id: "common.countryCode",
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(
intl,
config.variant,
errors.phoneNumber?.message
)}
label={intl.formatMessage({
id: "common.phoneNumber",
defaultMessage: "Phone number",
})}
name="phoneNumber"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
</div>
{showMembershipIdInput ? (
<div
ref={(el) => {
refs.current.membershipNo = el
}}
className={styles.fullWidth}
>
<BookingFlowInput
label={intl.formatMessage({
id: "common.membershipId",
defaultMessage: "Membership ID",
})}
name="membershipNo"
type="tel"
registerOptions={{ onBlur: updateDetailsStore }}
/>
</div>
) : null}
<SpecialRequests registerOptions={{ onBlur: updateDetailsStore }} />
</div>