Merged in feat/SW-1451-country-page-sorting (pull request #1426)

Feat/SW-1451 country page filtering and sorting

* feat(SW-1451): implemented sorting and filtering on country pages

* feat(SW-1451): Renamed hotel-data to destination-data because of its multi-purpose use

* feat(SW-1451): Now filtering after change of url instead of inside the store after submit


Approved-by: Fredrik Thorsson
This commit is contained in:
Erik Tiekstra
2025-02-28 06:30:16 +00:00
parent 747201b0f7
commit bee6c6d83a
69 changed files with 1124 additions and 531 deletions

View File

@@ -2,4 +2,5 @@ import { z } from "zod"
export const getMetadataInput = z.object({
subpage: z.string().optional(),
filterFromUrl: z.string().optional(),
})

View File

@@ -7,6 +7,7 @@ import { getDescription, getImage, getTitle } from "./utils"
import type { Metadata } from "next"
import { Country } from "@/types/enums/country"
import { RTETypeEnum } from "@/types/rte/enums"
const metaDataJsonSchema = z.object({
@@ -70,6 +71,7 @@ export const rawMetadataSchema = z.object({
city_poland: z.string().optional().nullable(),
city_norway: z.string().optional().nullable(),
city_sweden: z.string().optional().nullable(),
country: z.nativeEnum(Country).optional().nullable(),
})
.optional()
.nullable(),
@@ -89,9 +91,9 @@ export const rawMetadataSchema = z.object({
.pick({ name: true, address: true, hotelContent: true, gallery: true })
.optional()
.nullable(),
cityName: z.string().optional().nullable(),
cityFilter: z.string().optional().nullable(),
cityFilterType: z.enum(["facility", "surroundings"]).optional().nullable(),
location: z.string().optional().nullable(),
filter: z.string().optional().nullable(),
filterType: z.enum(["facility", "surroundings"]).optional().nullable(),
system: systemSchema,
})

View File

@@ -18,7 +18,7 @@ import { generateTag } from "@/utils/generateTag"
import { getHotel } from "../../hotels/query"
import { getMetadataInput } from "./input"
import { metadataSchema } from "./output"
import { affix, getCityData } from "./utils"
import { affix, getCityData, getCountryData } from "./utils"
import { PageContentTypeEnum } from "@/types/requests/contentType"
import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata"
@@ -153,9 +153,16 @@ export const metadataQueryRouter = router({
const destinationCountryPageResponse = await fetchMetadata<{
destination_country_page: RawMetadataSchema
}>(GetDestinationCountryPageMetadata, variables)
return getTransformedMetadata(
destinationCountryPageResponse.destination_country_page
const countryData = await getCountryData(
destinationCountryPageResponse.destination_country_page,
input,
ctx.serviceToken,
ctx.lang
)
return getTransformedMetadata({
...destinationCountryPageResponse.destination_country_page,
...countryData,
})
case PageContentTypeEnum.destinationCityPage:
const destinationCityPageResponse = await fetchMetadata<{
destination_city_page: RawMetadataSchema

View File

@@ -1,19 +1,23 @@
import { getFiltersFromHotels } from "@/stores/hotel-data/helper"
import { ApiLang, type Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { getFiltersFromHotels } from "@/stores/destination-data/helper"
import { getIntl } from "@/i18n"
import {
getCityByCityIdentifier,
getHotelIdsByCityIdentifier,
getHotelIdsByCountry,
getHotelsByHotelIds,
} from "../../hotels/utils"
import { ApiCountry } from "@/types/enums/country"
import type { RequestOptionsWithOutBody } from "@/types/fetch"
import { RTETypeEnum } from "@/types/rte/enums"
import type {
MetadataInputSchema,
RawMetadataSchema,
} from "@/types/trpc/routers/contentstack/metadata"
import type { Lang } from "@/constants/languages"
export const affix = "metadata"
@@ -87,25 +91,26 @@ export async function getTitle(data: RawMetadataSchema) {
}
)
}
if (data.system.content_type_uid === "destination_city_page") {
if (data.cityName) {
if (data.cityFilter) {
if (data.cityFilterType === "facility") {
if (
data.system.content_type_uid === "destination_city_page" ||
data.system.content_type_uid === "destination_country_page"
) {
const { location, filter, filterType } = data
if (location) {
if (filter) {
if (filterType === "facility") {
return intl.formatMessage(
{ id: "Hotels with {filter} in {cityName}" },
{ cityName: data.cityName, filter: data.cityFilter }
{ id: "Hotels with {filter} in {location}" },
{ location, filter }
)
} else if (data.cityFilterType === "surroundings") {
} else if (filterType === "surroundings") {
return intl.formatMessage(
{ id: "Hotels near {filter} in {cityName}" },
{ cityName: data.cityName, filter: data.cityFilter }
{ id: "Hotels near {filter} in {location}" },
{ location, filter }
)
}
}
return intl.formatMessage(
{ id: "Hotels in {city}" },
{ city: data.cityName }
)
return intl.formatMessage({ id: "Hotels in {location}" }, { location })
}
}
if (data.web?.breadcrumbs?.title) {
@@ -190,10 +195,7 @@ export async function getCityData(
lang: Lang
) {
const destinationSettings = data.destination_settings
const cityFilter = input.subpage
let cityIdentifier
let cityData
let filterType
const filter = input.filterFromUrl
if (destinationSettings) {
const {
@@ -213,23 +215,27 @@ export async function getCityData(
city_sweden,
].filter((city): city is string => Boolean(city))
cityIdentifier = cities[0]
const cityIdentifier = cities[0]
if (cityIdentifier) {
cityData = await getCityByCityIdentifier(cityIdentifier, serviceToken)
const cityData = await getCityByCityIdentifier(
cityIdentifier,
serviceToken
)
const hotelIds = await getHotelIdsByCityIdentifier(
cityIdentifier,
serviceToken
)
const hotels = await getHotelsByHotelIds(hotelIds, lang, serviceToken)
let filterType
if (cityFilter) {
if (filter) {
const allFilters = getFiltersFromHotels(hotels)
const facilityFilter = allFilters.facilityFilters.find(
(f) => f.slug === cityFilter
(f) => f.slug === filter
)
const surroudingsFilter = allFilters.surroundingsFilters.find(
(f) => f.slug === cityFilter
(f) => f.slug === filter
)
if (facilityFilter) {
@@ -238,8 +244,65 @@ export async function getCityData(
filterType = "surroundings"
}
}
return { location: cityData?.name, filter, filterType }
}
return { cityName: cityData?.name, cityFilter, cityFilterType: filterType }
}
return null
}
export async function getCountryData(
data: RawMetadataSchema,
input: MetadataInputSchema,
serviceToken: string,
lang: Lang
) {
const country = data.destination_settings?.country
const filter = input.filterFromUrl
if (country) {
const translatedCountry = ApiCountry[lang][country]
let filterType
const options: RequestOptionsWithOutBody = {
// needs to clear default option as only
// cache or next.revalidate is permitted
cache: undefined,
headers: {
Authorization: `Bearer ${serviceToken}`,
},
next: {
revalidate: env.CACHE_TIME_HOTELS,
},
}
const hotelIdsParams = new URLSearchParams({
language: ApiLang.En,
country,
})
const hotelIds = await getHotelIdsByCountry(
country,
options,
hotelIdsParams
)
const hotels = await getHotelsByHotelIds(hotelIds, lang, serviceToken)
if (filter) {
const allFilters = getFiltersFromHotels(hotels)
const facilityFilter = allFilters.facilityFilters.find(
(f) => f.slug === filter
)
const surroudingsFilter = allFilters.surroundingsFilters.find(
(f) => f.slug === filter
)
if (facilityFilter) {
filterType = "facility"
} else if (surroudingsFilter) {
filterType = "surroundings"
}
}
return { location: translatedCountry, filter, filterType }
}
return null
}