Merged in feat/SW-1444-destination-page-add-destination-list-component (pull request #1240)
feat/SW-1444 destination page add destination list component * feat(SW-1444): add list component * feat(SW-1444): add subtitle to accordion * feat(SW-1444): refactor component structure * feat(SW-1444): add desktop breakpoint * feat(SW-1444): fix typo * feat(SW-1444): add props * feat(SW-1444): add query * feat(SW-1444): updated query * feat(SW-1444): display data * feat(SW-1444): fix merge hickup * feat(SW-1444): change var name * feat(SW-1444): remove unsued translations * feat(SW-1444): use country as title * feat(SW-1444): sort hotels in query * feat(SW-1444): make responsive * feat(SW-1444): fetch country url * feat(SW-1444): update logging * feat(SW-1444): remove spread Approved-by: Erik Tiekstra
This commit is contained in:
@@ -172,14 +172,17 @@ export const destinationCountryPageQueryRouter = router({
|
||||
const selectedCountry =
|
||||
validatedResponse.data.destinationCountryPage.destination_settings.country
|
||||
const apiCountry = ApiCountry[lang][selectedCountry]
|
||||
const cities = await getCitiesByCountry([apiCountry], options, params, lang)
|
||||
|
||||
const publishedCities = cities[apiCountry].filter(
|
||||
(city) => city.isPublished
|
||||
const cities = await getCitiesByCountry(
|
||||
[apiCountry],
|
||||
options,
|
||||
params,
|
||||
lang,
|
||||
true,
|
||||
"destinationCountryPage"
|
||||
)
|
||||
|
||||
const cityPages = await Promise.all(
|
||||
publishedCities.map(async (city) => {
|
||||
cities[apiCountry].map(async (city) => {
|
||||
if (!city.cityIdentifier) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import { systemSchema } from "../schemas/system"
|
||||
|
||||
export const destinationOverviewPageSchema = z.object({
|
||||
@@ -17,6 +19,27 @@ export const destinationOverviewPageSchema = z.object({
|
||||
}),
|
||||
})
|
||||
|
||||
export const countryPageUrlSchema = z
|
||||
.object({
|
||||
all_destination_country_page: z.object({
|
||||
items: z.array(
|
||||
z
|
||||
.object({
|
||||
url: z.string(),
|
||||
system: systemSchema,
|
||||
})
|
||||
.transform((data) => {
|
||||
return {
|
||||
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
|
||||
}
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
.transform(
|
||||
({ all_destination_country_page }) => all_destination_country_page.items[0]
|
||||
)
|
||||
|
||||
/** REFS */
|
||||
export const destinationOverviewPageRefsSchema = z.object({
|
||||
destination_overview_page: z.object({
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import { env } from "@/env/server"
|
||||
import {
|
||||
GetDestinationOverviewPage,
|
||||
GetDestinationOverviewPageRefs,
|
||||
} from "@/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
|
||||
import {
|
||||
contentstackExtendedProcedureUID,
|
||||
router,
|
||||
serviceProcedure,
|
||||
} from "@/server/trpc"
|
||||
import { toApiLang } from "@/server/utils"
|
||||
|
||||
import { generateTag } from "@/utils/generateTag"
|
||||
|
||||
import {
|
||||
getCitiesByCountry,
|
||||
getCountries,
|
||||
getHotelIdsByCityId,
|
||||
} from "../../hotels/utils"
|
||||
import { getCityListDataByCityIdentifier } from "../destinationCountryPage/utils"
|
||||
import {
|
||||
destinationOverviewPageRefsSchema,
|
||||
destinationOverviewPageSchema,
|
||||
@@ -20,11 +32,14 @@ import {
|
||||
getDestinationOverviewPageRefsSuccessCounter,
|
||||
getDestinationOverviewPageSuccessCounter,
|
||||
} from "./telemetry"
|
||||
import { getCountryPageUrl } from "./utils"
|
||||
|
||||
import type { DestinationsData } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
|
||||
import {
|
||||
TrackingChannelEnum,
|
||||
type TrackingSDKPageData,
|
||||
} from "@/types/components/tracking"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import type {
|
||||
GetDestinationOverviewPageData,
|
||||
GetDestinationOverviewPageRefsSchema,
|
||||
@@ -187,4 +202,90 @@ export const destinationOverviewPageQueryRouter = router({
|
||||
tracking,
|
||||
}
|
||||
}),
|
||||
destinations: router({
|
||||
get: serviceProcedure.query(async function ({ ctx }) {
|
||||
const apiLang = toApiLang(ctx.lang)
|
||||
const params = new URLSearchParams({
|
||||
language: apiLang,
|
||||
})
|
||||
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
// needs to clear default option as only
|
||||
// cache or next.revalidate is permitted
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: env.CACHE_TIME_HOTELS,
|
||||
},
|
||||
}
|
||||
|
||||
const countries = await getCountries(options, params, ctx.lang)
|
||||
|
||||
if (!countries) {
|
||||
return null
|
||||
}
|
||||
|
||||
const countryNames = countries.data.map((country) => country.name)
|
||||
|
||||
const citiesByCountry = await getCitiesByCountry(
|
||||
countryNames,
|
||||
options,
|
||||
params,
|
||||
ctx.lang,
|
||||
true
|
||||
)
|
||||
|
||||
const destinations: DestinationsData = await Promise.all(
|
||||
Object.entries(citiesByCountry).map(async ([country, cities]) => {
|
||||
const citiesWithHotelCount = await Promise.all(
|
||||
cities.map(async (city) => {
|
||||
const hotelIdsParams = new URLSearchParams({
|
||||
language: apiLang,
|
||||
city: city.id,
|
||||
onlyBasicInfo: "true",
|
||||
})
|
||||
|
||||
const hotels = await getHotelIdsByCityId(
|
||||
city.id,
|
||||
options,
|
||||
hotelIdsParams
|
||||
)
|
||||
|
||||
let cityUrl
|
||||
if (city.cityIdentifier) {
|
||||
cityUrl = await getCityListDataByCityIdentifier(
|
||||
ctx.lang,
|
||||
city.cityIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
id: city.id,
|
||||
name: city.name,
|
||||
hotelIds: hotels,
|
||||
hotelCount: hotels?.length ?? 0,
|
||||
url: cityUrl?.url,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const countryUrl = await getCountryPageUrl(ctx.lang, country)
|
||||
|
||||
return {
|
||||
country,
|
||||
countryUrl: countryUrl?.url,
|
||||
numberOfHotels: citiesWithHotelCount.reduce(
|
||||
(acc, city) => acc + city.hotelCount,
|
||||
0
|
||||
),
|
||||
cities: citiesWithHotelCount,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return destinations.sort((a, b) => a.country.localeCompare(b.country))
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -21,3 +21,15 @@ export const getDestinationOverviewPageSuccessCounter = meter.createCounter(
|
||||
export const getDestinationOverviewPageFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.destinationOverviewPage.get-fail"
|
||||
)
|
||||
|
||||
export const getCountryPageUrlCounter = meter.createCounter(
|
||||
"trpc.contentstack.getCountryPageUrl"
|
||||
)
|
||||
|
||||
export const getCountryPageUrlSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.getCountryPageUrl-success"
|
||||
)
|
||||
|
||||
export const getCountryPageUrlFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.getCountryPageUrl-fail"
|
||||
)
|
||||
|
||||
76
server/routers/contentstack/destinationOverviewPage/utils.ts
Normal file
76
server/routers/contentstack/destinationOverviewPage/utils.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { GetCountryPageUrl } from "@/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
|
||||
import { countryPageUrlSchema } from "./output"
|
||||
import {
|
||||
getCountryPageUrlCounter,
|
||||
getCountryPageUrlFailCounter,
|
||||
getCountryPageUrlSuccessCounter,
|
||||
} from "./telemetry"
|
||||
|
||||
import type { GetCountryPageUrlData } from "@/types/trpc/routers/contentstack/destinationOverviewPage"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export async function getCountryPageUrl(lang: Lang, country: string) {
|
||||
getCountryPageUrlCounter.add(1, { lang, country })
|
||||
console.info(
|
||||
"contentstack.countryPageUrl start",
|
||||
JSON.stringify({ query: { lang, country } })
|
||||
)
|
||||
|
||||
const tag = `${lang}:country_page_url:${country}`
|
||||
const response = await request<GetCountryPageUrlData>(
|
||||
GetCountryPageUrl,
|
||||
{
|
||||
locale: lang,
|
||||
country,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [tag],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
getCountryPageUrlFailCounter.add(1, {
|
||||
lang,
|
||||
country,
|
||||
error_type: "not_found",
|
||||
error: `Country page not found for country: ${country}`,
|
||||
})
|
||||
console.error(
|
||||
"contentstack.countryPageUrl not found error",
|
||||
JSON.stringify({ query: { lang, country } })
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const validatedCountryPageUrl = countryPageUrlSchema.safeParse(response.data)
|
||||
|
||||
if (!validatedCountryPageUrl.success) {
|
||||
getCountryPageUrlFailCounter.add(1, {
|
||||
lang,
|
||||
country,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedCountryPageUrl.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.countryPageUrl validation error",
|
||||
JSON.stringify({
|
||||
query: { lang, country },
|
||||
error: validatedCountryPageUrl.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getCountryPageUrlSuccessCounter.add(1, { lang, country })
|
||||
console.info(
|
||||
"contentstack.countryPageUrl success",
|
||||
JSON.stringify({ query: { lang, country } })
|
||||
)
|
||||
|
||||
return validatedCountryPageUrl.data
|
||||
}
|
||||
Reference in New Issue
Block a user