fix(SW-2501): validation trigger * fix(SW-2501): validation trigger On enter details, when submitting we want to trigger the validation for the details forms for each room. This will display error messages for the form fields with errors if they are not already displayed, so the user knows which fields has errors. Approved-by: Tobias Johansson
198 lines
5.9 KiB
TypeScript
198 lines
5.9 KiB
TypeScript
"use client"
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { useCallback, useEffect, useMemo } from "react"
|
|
import { FormProvider, useForm } from "react-hook-form"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { useEnterDetailsStore } from "@/stores/enter-details"
|
|
|
|
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
|
|
import CountrySelect from "@/components/TempDesignSystem/Form/Country"
|
|
import Input from "@/components/TempDesignSystem/Form/Input"
|
|
import Phone from "@/components/TempDesignSystem/Form/Phone"
|
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
|
import { useRoomContext } from "@/contexts/Details/Room"
|
|
|
|
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
|
|
import { getMultiroomDetailsSchema } from "./schema"
|
|
|
|
import styles from "./details.module.css"
|
|
|
|
import type { MultiroomDetailsSchema } from "@/types/components/hotelReservation/enterDetails/details"
|
|
|
|
const formID = "enter-details"
|
|
export default function Details() {
|
|
const intl = useIntl()
|
|
|
|
const { addPreSubmitCallback, rooms } = useEnterDetailsStore((state) => ({
|
|
addPreSubmitCallback: state.actions.addPreSubmitCallback,
|
|
rooms: state.rooms,
|
|
}))
|
|
|
|
const {
|
|
actions: { updateDetails, setIncomplete },
|
|
idx,
|
|
room,
|
|
roomNr,
|
|
} = useRoomContext()
|
|
const initialData = room.guest
|
|
|
|
/**
|
|
* The data that each room needs from each other to do validations
|
|
* across the rooms
|
|
*/
|
|
const crossValidationData = useMemo(
|
|
() =>
|
|
rooms
|
|
.filter((_, i) => i !== idx)
|
|
.map((room) => ({
|
|
firstName: room.room.guest.firstName,
|
|
lastName: room.room.guest.lastName,
|
|
membershipNo: room.room.guest.membershipNo,
|
|
})),
|
|
[idx, rooms]
|
|
)
|
|
|
|
const methods = useForm<MultiroomDetailsSchema>({
|
|
criteriaMode: "all",
|
|
mode: "all",
|
|
resolver: zodResolver(getMultiroomDetailsSchema(crossValidationData)),
|
|
reValidateMode: "onChange",
|
|
values: {
|
|
countryCode: initialData.countryCode,
|
|
email: initialData.email,
|
|
firstName: initialData.firstName,
|
|
join: initialData.join,
|
|
lastName: initialData.lastName,
|
|
membershipNo: initialData.membershipNo,
|
|
phoneNumber: initialData.phoneNumber,
|
|
specialRequest: {
|
|
comment: room.specialRequest.comment,
|
|
},
|
|
},
|
|
})
|
|
|
|
const {
|
|
formState: { isValid },
|
|
handleSubmit,
|
|
trigger,
|
|
} = methods
|
|
|
|
useEffect(() => {
|
|
addPreSubmitCallback(`${idx}-details`, trigger)
|
|
}, [addPreSubmitCallback, idx, trigger])
|
|
|
|
const updateDetailsStore = useCallback(() => {
|
|
if (isValid) {
|
|
handleSubmit(updateDetails)()
|
|
} else {
|
|
setIncomplete()
|
|
}
|
|
}, [handleSubmit, isValid, setIncomplete, updateDetails])
|
|
|
|
useEffect(updateDetailsStore, [methods.formState.isValid, updateDetailsStore])
|
|
|
|
// Trigger validation of the room manually when another room changes its data.
|
|
// Only do it if the field has a value, to avoid error states before the user
|
|
// has filled anything in.
|
|
useEffect(() => {
|
|
const { firstName, lastName, membershipNo } = methods.getValues()
|
|
if (firstName) {
|
|
methods.trigger("firstName")
|
|
}
|
|
if (lastName) {
|
|
methods.trigger("lastName")
|
|
}
|
|
if (membershipNo) {
|
|
methods.trigger("membershipNo")
|
|
}
|
|
}, [crossValidationData, methods])
|
|
|
|
const guestIsGoingToJoin = methods.watch("join")
|
|
const guestIsMember = methods.watch("membershipNo")
|
|
|
|
return (
|
|
<FormProvider {...methods}>
|
|
<form
|
|
className={styles.form}
|
|
id={`${formID}-room-${roomNr}`}
|
|
onSubmit={methods.handleSubmit(updateDetails)}
|
|
>
|
|
{guestIsMember ? null : <JoinScandicFriendsCard />}
|
|
<div className={styles.container}>
|
|
<Footnote
|
|
color="uiTextHighContrast"
|
|
textTransform="uppercase"
|
|
type="label"
|
|
className={styles.fullWidth}
|
|
>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Guest information",
|
|
})}
|
|
</Footnote>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
defaultMessage: "First name",
|
|
})}
|
|
maxLength={30}
|
|
name="firstName"
|
|
registerOptions={{
|
|
required: true,
|
|
deps: "lastName",
|
|
onBlur: updateDetailsStore,
|
|
}}
|
|
/>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Last name",
|
|
})}
|
|
maxLength={30}
|
|
name="lastName"
|
|
registerOptions={{
|
|
required: true,
|
|
deps: "firstName",
|
|
onBlur: updateDetailsStore,
|
|
}}
|
|
/>
|
|
<CountrySelect
|
|
className={styles.fullWidth}
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Country",
|
|
})}
|
|
name="countryCode"
|
|
registerOptions={{ required: true, onBlur: updateDetailsStore }}
|
|
/>
|
|
<Input
|
|
className={styles.fullWidth}
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Email address",
|
|
})}
|
|
name="email"
|
|
registerOptions={{ required: true, onBlur: updateDetailsStore }}
|
|
/>
|
|
<Phone
|
|
className={styles.fullWidth}
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Phone number",
|
|
})}
|
|
name="phoneNumber"
|
|
registerOptions={{ required: true, onBlur: updateDetailsStore }}
|
|
/>
|
|
{guestIsGoingToJoin ? null : (
|
|
<Input
|
|
className={styles.fullWidth}
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Membership no",
|
|
})}
|
|
name="membershipNo"
|
|
type="tel"
|
|
registerOptions={{ onBlur: updateDetailsStore }}
|
|
/>
|
|
)}
|
|
<SpecialRequests registerOptions={{ onBlur: updateDetailsStore }} />
|
|
</div>
|
|
</form>
|
|
</FormProvider>
|
|
)
|
|
}
|