feat(SW-66, SW-348): search functionality and ui
This commit is contained in:
@@ -1,5 +1,23 @@
|
||||
import { IconName } from "@/types/components/icon"
|
||||
|
||||
import deepmerge from "deepmerge"
|
||||
import { unstable_cache } from "next/cache"
|
||||
|
||||
import * as api from "@/lib/api"
|
||||
|
||||
import {
|
||||
apiCitiesByCountrySchema,
|
||||
apiCitySchema,
|
||||
apiCountriesSchema,
|
||||
apiLocationsSchema,
|
||||
type CitiesGroupedByCountry,
|
||||
type Countries,
|
||||
} from "./output"
|
||||
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import type { Endpoint } from "@/lib/api/endpoints"
|
||||
|
||||
export function getIconByPoiCategory(category: string) {
|
||||
switch (category) {
|
||||
case "Transportations":
|
||||
@@ -16,3 +34,169 @@ export function getIconByPoiCategory(category: string) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const locationsAffix = "locations"
|
||||
|
||||
export const TWENTYFOUR_HOURS = 60 * 60 * 24
|
||||
export async function getCity(
|
||||
cityUrl: string,
|
||||
options: RequestOptionsWithOutBody
|
||||
) {
|
||||
const url = new URL(cityUrl)
|
||||
const cityResponse = await api.get(
|
||||
url.pathname as Endpoint,
|
||||
options,
|
||||
url.searchParams
|
||||
)
|
||||
|
||||
if (!cityResponse.ok) {
|
||||
return null
|
||||
}
|
||||
|
||||
const cityJson = await cityResponse.json()
|
||||
const city = apiCitySchema.safeParse(cityJson)
|
||||
if (!city.success) {
|
||||
console.info(`Validation of city failed`)
|
||||
console.info(`cityUrl: ${cityUrl}`)
|
||||
console.error(city.error)
|
||||
return null
|
||||
}
|
||||
|
||||
return city.data
|
||||
}
|
||||
|
||||
export async function getCountries(
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams
|
||||
) {
|
||||
const countryResponse = await api.get(
|
||||
api.endpoints.v1.countries,
|
||||
options,
|
||||
params
|
||||
)
|
||||
|
||||
if (!countryResponse.ok) {
|
||||
return null
|
||||
}
|
||||
|
||||
const countriesJson = await countryResponse.json()
|
||||
const countries = apiCountriesSchema.safeParse(countriesJson)
|
||||
if (!countries.success) {
|
||||
console.info(`Validation for countries failed`)
|
||||
console.error(countries.error)
|
||||
return null
|
||||
}
|
||||
|
||||
return countries.data
|
||||
}
|
||||
|
||||
export async function getCitiesByCountry(
|
||||
countries: Countries,
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams
|
||||
) {
|
||||
const citiesGroupedByCountry: CitiesGroupedByCountry = {}
|
||||
|
||||
await Promise.all(
|
||||
countries.data.map(async (country) => {
|
||||
const countryResponse = await api.get(
|
||||
`${api.endpoints.v1.citiesCountry}/${country.name}`,
|
||||
options,
|
||||
params
|
||||
)
|
||||
|
||||
if (!countryResponse.ok) {
|
||||
return null
|
||||
}
|
||||
|
||||
const countryJson = await countryResponse.json()
|
||||
const citiesByCountry = apiCitiesByCountrySchema.safeParse(countryJson)
|
||||
if (!citiesByCountry.success) {
|
||||
console.info(`Failed to validate Cities by Country payload`)
|
||||
console.error(citiesByCountry.error)
|
||||
return null
|
||||
}
|
||||
|
||||
citiesGroupedByCountry[country.name] = citiesByCountry.data.data
|
||||
return true
|
||||
})
|
||||
)
|
||||
|
||||
return citiesGroupedByCountry
|
||||
}
|
||||
|
||||
export async function getLocations(
|
||||
lang: Lang,
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams,
|
||||
citiesByCountry: CitiesGroupedByCountry | null
|
||||
) {
|
||||
const apiResponse = await api.get(api.endpoints.v1.locations, options, params)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
if (apiResponse.status === 401) {
|
||||
return { error: true, cause: "unauthorized" } as const
|
||||
} else if (apiResponse.status === 403) {
|
||||
return { error: true, cause: "forbidden" } as const
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const verifiedLocations = apiLocationsSchema.safeParse(apiJson)
|
||||
if (!verifiedLocations.success) {
|
||||
console.info(`Locations Verification Failed`)
|
||||
console.error(verifiedLocations.error)
|
||||
return null
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
verifiedLocations.data.data.map(async (location) => {
|
||||
if (location.type === "cities") {
|
||||
if (citiesByCountry) {
|
||||
const country = Object.keys(citiesByCountry).find((country) => {
|
||||
if (
|
||||
citiesByCountry[country].find((loc) => loc.name === location.name)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (country) {
|
||||
return {
|
||||
...location,
|
||||
country,
|
||||
}
|
||||
} else {
|
||||
console.info(
|
||||
`Location cannot be found in any of the countries cities`
|
||||
)
|
||||
console.info(location)
|
||||
}
|
||||
}
|
||||
} else if (location.type === "hotels") {
|
||||
if (location.relationships.city?.url) {
|
||||
const getCachedCity = unstable_cache(
|
||||
getCity,
|
||||
[`${lang}:${location.relationships.city}`],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)
|
||||
|
||||
const city = await getCachedCity(
|
||||
location.relationships.city.url,
|
||||
options
|
||||
)
|
||||
if (city) {
|
||||
return deepmerge(location, {
|
||||
relationships: {
|
||||
city,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return location
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user