fix: SW-1102 show error on invalid entry

This commit is contained in:
Hrishikesh Vaipurkar
2024-12-05 23:07:45 +01:00
parent abf6ed1fcc
commit e3f93e0de6
3 changed files with 80 additions and 35 deletions

View File

@@ -29,23 +29,17 @@ export default function SearchList({
}: SearchListProps) { }: SearchListProps) {
const intl = useIntl() const intl = useIntl()
const [hasMounted, setHasMounted] = useState(false) const [hasMounted, setHasMounted] = useState(false)
const [isFormSubmitted, setIsFormSubmitted] = useState(false)
const { const {
clearErrors, clearErrors,
formState: { errors, isSubmitted }, formState: { errors, isSubmitted },
} = useFormContext() } = useFormContext()
const searchError = errors["search"] const searchError = errors["search"]
useEffect(() => {
setIsFormSubmitted(isSubmitted)
}, [isSubmitted])
useEffect(() => { useEffect(() => {
let timeoutID: ReturnType<typeof setTimeout> | null = null let timeoutID: ReturnType<typeof setTimeout> | null = null
if (searchError && searchError.message === "Required") { if (searchError) {
timeoutID = setTimeout(() => { timeoutID = setTimeout(() => {
clearErrors("search") clearErrors("search")
setIsFormSubmitted(false)
// magic number originates from animation // magic number originates from animation
// 5000ms delay + 120ms exectuion // 5000ms delay + 120ms exectuion
}, 5120) }, 5120)
@@ -66,7 +60,7 @@ export default function SearchList({
return null return null
} }
if (searchError && isFormSubmitted) { if (searchError && isSubmitted) {
if (typeof searchError.message === "string") { if (typeof searchError.message === "string") {
if (!isOpen) { if (!isOpen) {
if (searchError.message === "Required") { if (searchError.message === "Required") {
@@ -87,6 +81,24 @@ export default function SearchList({
</Body> </Body>
</Dialog> </Dialog>
) )
} else if (searchError.type === "custom") {
return (
<Dialog
className={styles.fadeOut}
getMenuProps={getMenuProps}
variant="error"
>
<Caption className={styles.heading} color="red">
<ErrorCircleIcon color="red" />
{intl.formatMessage({ id: "No results" })}
</Caption>
<Body>
{intl.formatMessage({
id: "We couldn't find a matching location for your search.",
})}
</Body>
</Dialog>
)
} }
} }
} }

View File

@@ -27,7 +27,8 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
const name = "search" const name = "search"
export default function Search({ locations }: SearchProps) { export default function Search({ locations }: SearchProps) {
const { register, setValue, trigger } = useFormContext<BookingWidgetSchema>() const { register, setValue, trigger, unregister } =
useFormContext<BookingWidgetSchema>()
const intl = useIntl() const intl = useIntl()
const value = useWatch({ name }) const value = useWatch({ name })
const [state, dispatch] = useReducer( const [state, dispatch] = useReducer(
@@ -135,6 +136,27 @@ export default function Search({ locations }: SearchProps) {
} }
}, [dispatch]) }, [dispatch])
const stayType = state.searchData?.type === "cities" ? "city" : "hotel"
const stayValue =
(value === state.searchData?.name &&
((state.searchData?.type === "cities" && state.searchData?.name) ||
state.searchData?.id)) ||
""
useEffect(() => {
if (stayType === "city") {
unregister("hotel")
setValue(stayType, stayValue, {
shouldValidate: true,
})
} else {
unregister("city")
setValue(stayType, Number(stayValue), {
shouldValidate: true,
})
}
}, [stayType, stayValue, unregister, setValue])
return ( return (
<Downshift <Downshift
initialSelectedItem={state.searchData} initialSelectedItem={state.searchData}
@@ -155,6 +177,10 @@ export default function Search({ locations }: SearchProps) {
openMenu, openMenu,
}) => ( }) => (
<div className={styles.container}> <div className={styles.container}>
{value ? (
// Adding hidden input to define hotel or city based on destination selection for basic form submit.
<input type="hidden" {...register(stayType)} />
) : null}
<label {...getLabelProps({ htmlFor: name })} className={styles.label}> <label {...getLabelProps({ htmlFor: name })} className={styles.label}>
<Caption <Caption
type="bold" type="bold"

View File

@@ -35,30 +35,37 @@ export const guestRoomSchema = z
export const guestRoomsSchema = z.array(guestRoomSchema) export const guestRoomsSchema = z.array(guestRoomSchema)
export const bookingWidgetSchema = z.object({ export const bookingWidgetSchema = z
bookingCode: z.string(), // Update this as required when working with booking codes component .object({
date: z.object({ bookingCode: z.string(), // Update this as required when working with booking codes component
// Update this as required once started working with Date picker in Nights component date: z.object({
fromDate: z.string(), // Update this as required once started working with Date picker in Nights component
toDate: z.string(), fromDate: z.string(),
}), toDate: z.string(),
location: z.string().refine( }),
(value) => { location: z.string().refine(
if (value) { (value) => {
const parsedValue: Location = JSON.parse(decodeURIComponent(value)) if (value) {
switch (parsedValue?.type) { const parsedValue: Location = JSON.parse(decodeURIComponent(value))
case "cities": switch (parsedValue?.type) {
case "hotels": case "cities":
return true case "hotels":
default: return true
return false default:
return false
}
} }
} },
}, { message: "Required" }
{ message: "Required" } ),
), redemption: z.boolean().default(false),
redemption: z.boolean().default(false), rooms: guestRoomsSchema,
rooms: guestRoomsSchema, search: z.string({ coerce: true }).min(1, "Required"),
search: z.string({ coerce: true }).min(1, "Required"), voucher: z.boolean().default(false),
voucher: z.boolean().default(false), hotel: z.number().optional(),
}) city: z.string().optional(),
})
.refine((value) => value.hotel || value.city, {
message: "Destination required",
path: ["search"],
})