Files
web/components/Forms/BookingWidget/FormContent/Search/index.tsx
2024-10-08 10:49:26 +02:00

182 lines
5.2 KiB
TypeScript

"use client"
import Downshift from "downshift"
import {
ChangeEvent,
FocusEvent,
FormEvent,
useCallback,
useReducer,
} from "react"
import { useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Input from "../Input"
import { init, localStorageKey, reducer, sessionStorageKey } from "./reducer"
import SearchList from "./SearchList"
import styles from "./search.module.css"
import type { BookingWidgetSchema } from "@/types/components/bookingWidget"
import { ActionType } from "@/types/components/form/bookingwidget"
import type { SearchProps } from "@/types/components/search"
import type { Location } from "@/types/trpc/routers/hotel/locations"
const name = "search"
export default function Search({ locations }: SearchProps) {
const [state, dispatch] = useReducer(
reducer,
{ defaultLocations: locations },
init
)
const { register, setValue, trigger } = useFormContext<BookingWidgetSchema>()
const intl = useIntl()
const value = useWatch({ name })
const handleMatchLocations = useCallback(
function (searchValue: string) {
return locations.filter((location) => {
return location.name.toLowerCase().includes(searchValue.toLowerCase())
})
},
[locations]
)
function handleClearSearchHistory() {
localStorage.removeItem(localStorageKey)
dispatch({ type: ActionType.CLEAR_HISTORY_LOCATIONS })
}
function handleOnBlur() {
if (!value && state.searchData?.name) {
setValue(name, state.searchData.name)
// Always need to manually trigger
// revalidation when setting value r-h-f
trigger()
}
}
function handleOnChange(
evt: FormEvent<HTMLInputElement> | ChangeEvent<HTMLInputElement>
) {
const value = evt.currentTarget.value
if (value) {
dispatch({
payload: { search: value },
type: ActionType.SEARCH_LOCATIONS,
})
} else {
dispatch({ type: ActionType.CLEAR_SEARCH_LOCATIONS })
}
}
function handleOnFocus(evt: FocusEvent<HTMLInputElement>) {
const searchValue = evt.currentTarget.value
if (searchValue) {
const matchingLocations = handleMatchLocations(searchValue)
if (matchingLocations.length) {
dispatch({
payload: { search: searchValue },
type: ActionType.SEARCH_LOCATIONS,
})
}
}
}
function handleOnSelect(selectedItem: Location | null) {
if (selectedItem) {
const stringified = JSON.stringify(selectedItem)
setValue("location", encodeURIComponent(stringified))
sessionStorage.setItem(sessionStorageKey, stringified)
setValue(name, selectedItem.name)
trigger()
const searchHistoryMap = new Map()
searchHistoryMap.set(selectedItem.name, selectedItem)
if (state.searchHistory) {
state.searchHistory.forEach((location) => {
searchHistoryMap.set(location.name, location)
})
}
const searchHistory: Location[] = []
searchHistoryMap.forEach((location) => {
searchHistory.push(location)
})
localStorage.setItem(localStorageKey, JSON.stringify(searchHistory))
dispatch({
payload: {
location: selectedItem,
searchHistory,
},
type: ActionType.SELECT_ITEM,
})
} else {
sessionStorage.removeItem(sessionStorageKey)
}
}
return (
<Downshift
initialSelectedItem={state.searchData}
inputValue={value}
itemToString={(value) => (value ? value.name : "")}
onSelect={handleOnSelect}
>
{({
closeMenu,
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getRootProps,
highlightedIndex,
isOpen,
openMenu,
}) => (
<div className={styles.container}>
<label {...getLabelProps({ htmlFor: name })} className={styles.label}>
<Caption color={isOpen ? "uiTextActive" : "red"}>
{intl.formatMessage({ id: "Where to" })}
</Caption>
</label>
<div {...getRootProps({}, { suppressRefError: true })}>
<Input
{...getInputProps({
id: name,
onFocus(evt) {
handleOnFocus(evt)
openMenu()
},
placeholder: intl.formatMessage({
id: "Destinations & hotels",
}),
...register(name, {
onBlur: function () {
handleOnBlur()
closeMenu()
},
onChange: handleOnChange,
}),
type: "search",
})}
/>
</div>
<SearchList
getItemProps={getItemProps}
getMenuProps={getMenuProps}
handleClearSearchHistory={handleClearSearchHistory}
highlightedIndex={highlightedIndex}
isOpen={isOpen}
locations={state.locations}
search={state.search}
searchHistory={state.searchHistory}
/>
</div>
)}
</Downshift>
)
}