Merged in fix/SW-2253-consolidate-autocomplete-search (pull request #1795)

Consolidate autocomplete search SW-2253 SW-2338

* use fuse.js for fuzzy search
* Handle weird behaviour when search field loses focus on destinationPage
* Add error logging for JumpTo when no URL was provided
* Switch to use <Typography /> over <Caption />
* fix: bookingWidget search label should always be red
* fix: searchHistory can no longer add invalid items
* fix: list more hits when searching
* fix: issue when searchField value was undefined
* fix: don't show searchHistory label if no searchHistory items
* simplify skeleton for listitems in search

Approved-by: Linus Flood
This commit is contained in:
Joakim Jäderberg
2025-04-17 06:39:42 +00:00
parent 8c0597727b
commit b98d6c10c0
35 changed files with 797 additions and 1602 deletions

View File

@@ -2,13 +2,16 @@ import { z } from "zod"
import { Lang } from "@/constants/languages"
import { safeProtectedServiceProcedure } from "@/server/trpc"
import { isDefined } from "@/server/utils"
import { getCacheClient } from "@/services/dataCache"
import { safeTry } from "@/utils/safeTry"
import { getCityPageUrls } from "../contentstack/destinationCityPage/utils"
import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { getCitiesByCountry, getCountries, getLocations } from "../hotels/utils"
import { filterLocationByQuery } from "./util/filterLocationByQuery"
import { filterAndCategorizeAutoComplete } from "./util/filterAndCategorizeAutoComplete"
import { mapLocationToAutoCompleteLocation } from "./util/mapLocationToAutoCompleteLocation"
import { sortAutocompleteLocations } from "./util/sortAutocompleteLocations"
import type { AutoCompleteLocation } from "./schema"
@@ -35,10 +38,13 @@ export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
.query(async ({ ctx, input }): Promise<DestinationsAutoCompleteOutput> => {
const cacheClient = await getCacheClient()
const lang = input.lang || ctx.lang
const locations: AutoCompleteLocation[] = await cacheClient.cacheOrGet(
`autocomplete:destinations:locations:${input.lang}`,
`autocomplete:destinations:locations:${lang}`,
async () => {
const lang = input.lang || ctx.lang
const hotelUrlsPromise = safeTry(getHotelPageUrls(lang))
const cityUrlsPromise = safeTry(getCityPageUrls(lang))
const countries = await getCountries({
lang: lang,
serviceToken: ctx.serviceToken,
@@ -60,16 +66,44 @@ export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
citiesByCountry: citiesByCountry,
})
const [hotelUrls, hotelUrlsError] = await hotelUrlsPromise
const [cityUrls, cityUrlsError] = await cityUrlsPromise
if (hotelUrlsError || cityUrlsError || !hotelUrls || !cityUrls) {
throw new Error("Unable to fetch location URLs")
}
return locations
.map((location) => {
let url: string | undefined
if (location.type === "cities") {
url = cityUrls.find(
(c) =>
c.city &&
location.cityIdentifier &&
c.city === location.cityIdentifier
)?.url
}
if (location.type === "hotels") {
url = hotelUrls.find(
(h) => h.hotelId && location.id && h.hotelId === location.id
)?.url
}
return { ...location, url }
})
.map(mapLocationToAutoCompleteLocation)
.filter(isDefined)
},
"1d"
)
const filteredLocations = locations.filter((location) =>
filterLocationByQuery({ location, query: input.query })
)
const hits = filterAndCategorizeAutoComplete({
locations,
query: input.query,
})
const selectedHotel = locations.find(
(location) =>
@@ -81,21 +115,8 @@ export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
location.type === "cities" && location.name === input.selectedCity
)
const sortedCities = sortAutocompleteLocations(
filteredLocations.filter(isCity),
input.query
)
const sortedHotels = sortAutocompleteLocations(
filteredLocations.filter(isHotel),
input.query
)
return {
hits: {
cities: sortedCities,
hotels: sortedHotels,
},
hits: hits,
currentSelection: {
city: isCity(selectedCity) ? selectedCity : null,
hotel: isHotel(selectedHotel) ? selectedHotel : null,
@@ -114,9 +135,3 @@ function isCity(
): location is AutoCompleteLocation & { type: "cities" } {
return !!location && location.type === "cities"
}
function isDefined(
value: AutoCompleteLocation | null | undefined
): value is AutoCompleteLocation {
return !!value
}