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 } from "react"
import { useCallback, useEffect, useRef } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
@@ -40,6 +40,8 @@ type DetailsProps = {
const formID = "enter-details"
export default function Details({ user }: DetailsProps) {
const refs = useRef<Record<string, HTMLElement | null>>({})
const intl = useIntl()
const lang = useLang()
const config = useBookingFlowConfig()
@@ -107,12 +109,36 @@ export default function Details({ user }: DetailsProps) {
)
useEffect(() => {
function callback() {
trigger()
async function callback() {
await trigger()
trackFormSubmit()
const baseFieldOrder = [
"firstName",
"lastName",
"countryCode",
"email",
"phoneNumber",
"membershipNo",
]
const joinChecked = methods.watch("join")
const fieldOrder = joinChecked
? [...baseFieldOrder, "zipCode", "dateOfBirth"]
: baseFieldOrder
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 onSubmit = useCallback(
(values: GuestDetailsSchema) => {
@@ -133,12 +159,12 @@ export default function Details({ user }: DetailsProps) {
setIncomplete()
}
}, [
handleSubmit,
formState.isValid,
handleSubmit,
onSubmit,
setIncomplete,
updatePartialGuestData,
getValues,
setIncomplete,
])
useEffect(updateDetailsStore, [updateDetailsStore])
@@ -174,83 +200,114 @@ export default function Details({ user }: DetailsProps) {
defaultMessage: "Guest information",
})}
</Footnote>
<BookingFlowInput
autoComplete="given-name"
label={intl.formatMessage({
id: "common.firstName",
defaultMessage: "First name",
})}
maxLength={30}
name="firstName"
readOnly={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
<BookingFlowInput
autoComplete="family-name"
label={intl.formatMessage({
id: "common.lastName",
defaultMessage: "Last name",
})}
maxLength={30}
name="lastName"
readOnly={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
<CountrySelect
<div
ref={(el) => {
refs.current.firstName = el
}}
>
<BookingFlowInput
autoComplete="given-name"
label={intl.formatMessage({
id: "common.firstName",
defaultMessage: "First name",
})}
maxLength={30}
name="firstName"
readOnly={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
</div>
<div
ref={(el) => {
refs.current.lastName = el
}}
>
<BookingFlowInput
autoComplete="family-name"
label={intl.formatMessage({
id: "common.lastName",
defaultMessage: "Last name",
})}
maxLength={30}
name="lastName"
readOnly={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
</div>
<div
ref={(el) => {
refs.current.countryCode = el
}}
className={styles.fullWidth}
label={intl.formatMessage({
id: "common.country",
defaultMessage: "Country",
})}
lang={lang}
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
config.variant,
formState.errors.countryCode?.message
)}
name="countryCode"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
disabled={!!user}
/>
<BookingFlowInput
autoComplete="email"
>
<CountrySelect
label={intl.formatMessage({
id: "common.country",
defaultMessage: "Country",
})}
lang={lang}
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
config.variant,
formState.errors.countryCode?.message
)}
name="countryCode"
registerOptions={{ required: true, onBlur: updateDetailsStore }}
disabled={!!user}
/>
</div>
<div
ref={(el) => {
refs.current.email = el
}}
className={styles.fullWidth}
label={intl.formatMessage({
id: "common.emailAddress",
defaultMessage: "Email address",
})}
name="email"
readOnly={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
<Phone
>
<BookingFlowInput
autoComplete="email"
label={intl.formatMessage({
id: "common.emailAddress",
defaultMessage: "Email address",
})}
name="email"
readOnly={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
</div>
<div
ref={(el) => {
refs.current.phoneNumber = el
}}
className={styles.fullWidth}
countryLabel={intl.formatMessage({
id: "common.countryCode",
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(
intl,
config.variant,
formState.errors.phoneNumber?.message
)}
label={intl.formatMessage({
id: "common.phoneNumber",
defaultMessage: "Phone number",
})}
name="phoneNumber"
disabled={!!user}
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,
formState.errors.phoneNumber?.message
)}
label={intl.formatMessage({
id: "common.phoneNumber",
defaultMessage: "Phone number",
})}
name="phoneNumber"
disabled={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
</div>
{user ? null : (
<div className={styles.fullWidth}>
<Signup
errors={formState.errors}
name="join"
registerOptions={{ onBlur: updateDetailsStore }}
refs={refs}
/>
</div>
)}