feat(SW-66, SW-348): search functionality and ui
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
import { unstable_cache } from "next/cache"
|
||||
|
||||
import * as api from "@/lib/api"
|
||||
import { GetHotelPage } from "@/lib/graphql/Query/HotelPage.graphql"
|
||||
import { GetHotelPage } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import {
|
||||
badRequestError,
|
||||
@@ -17,14 +18,7 @@ import {
|
||||
} from "@/server/trpc"
|
||||
import { toApiLang } from "@/server/utils"
|
||||
|
||||
import { makeImageVaultImage } from "@/utils/imageVault"
|
||||
import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import {
|
||||
type ContentBlockItem,
|
||||
type HotelPageDataRaw,
|
||||
validateHotelPageSchema,
|
||||
} from "../contentstack/hotelPage/output"
|
||||
import { hotelPageSchema } from "../contentstack/hotelPage/output"
|
||||
import {
|
||||
getAvailabilityInputSchema,
|
||||
getHotelInputSchema,
|
||||
@@ -38,9 +32,17 @@ import {
|
||||
roomSchema,
|
||||
} from "./output"
|
||||
import tempRatesData from "./tempRatesData.json"
|
||||
import {
|
||||
getCitiesByCountry,
|
||||
getCountries,
|
||||
getLocations,
|
||||
locationsAffix,
|
||||
TWENTYFOUR_HOURS,
|
||||
} from "./utils"
|
||||
|
||||
import { HotelBlocksTypenameEnum } from "@/types/components/hotelPage/enums"
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import type { GetHotelPageData } from "@/types/trpc/routers/contentstack/hotelPage"
|
||||
|
||||
const meter = metrics.getMeter("trpc.hotels")
|
||||
const getHotelCounter = meter.createCounter("trpc.hotel.get")
|
||||
@@ -59,19 +61,16 @@ async function getContentstackData(
|
||||
locale: string,
|
||||
uid: string | null | undefined
|
||||
) {
|
||||
const rawContentStackData = await request<HotelPageDataRaw>(GetHotelPage, {
|
||||
const response = await request<GetHotelPageData>(GetHotelPage, {
|
||||
locale,
|
||||
uid,
|
||||
})
|
||||
|
||||
if (!rawContentStackData.data) {
|
||||
throw notFound(rawContentStackData)
|
||||
if (!response.data) {
|
||||
throw notFound(response)
|
||||
}
|
||||
|
||||
const hotelPageData = validateHotelPageSchema.safeParse(
|
||||
rawContentStackData.data
|
||||
)
|
||||
|
||||
const hotelPageData = hotelPageSchema.safeParse(response.data)
|
||||
if (!hotelPageData.success) {
|
||||
console.error(
|
||||
`Failed to validate Hotel Page - (uid: ${uid}, lang: ${locale})`
|
||||
@@ -116,7 +115,6 @@ export const hotelQueryRouter = router({
|
||||
const apiResponse = await api.get(
|
||||
`${api.endpoints.v1.hotels}/${hotelId}`,
|
||||
{
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
@@ -180,60 +178,38 @@ export const hotelQueryRouter = router({
|
||||
|
||||
const roomCategories = included
|
||||
? included
|
||||
.filter((item) => item.type === "roomcategories")
|
||||
.map((roomCategory) => {
|
||||
const validatedRoom = roomSchema.safeParse(roomCategory)
|
||||
if (!validatedRoom.success) {
|
||||
getHotelFailCounter.add(1, {
|
||||
hotelId,
|
||||
lang,
|
||||
include,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(
|
||||
validatedRoom.error.issues.map(({ code, message }) => ({
|
||||
code,
|
||||
message,
|
||||
}))
|
||||
),
|
||||
.filter((item) => item.type === "roomcategories")
|
||||
.map((roomCategory) => {
|
||||
const validatedRoom = roomSchema.safeParse(roomCategory)
|
||||
if (!validatedRoom.success) {
|
||||
getHotelFailCounter.add(1, {
|
||||
hotelId,
|
||||
lang,
|
||||
include,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(
|
||||
validatedRoom.error.issues.map(({ code, message }) => ({
|
||||
code,
|
||||
message,
|
||||
}))
|
||||
),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotel validation error",
|
||||
JSON.stringify({
|
||||
query: { hotelId, params },
|
||||
error: validatedRoom.error,
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotel validation error",
|
||||
JSON.stringify({
|
||||
query: { hotelId, params },
|
||||
error: validatedRoom.error,
|
||||
})
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
|
||||
return validatedRoom.data
|
||||
})
|
||||
return validatedRoom.data
|
||||
})
|
||||
: []
|
||||
|
||||
const activities = contentstackData?.content
|
||||
? contentstackData.content.map((block: ContentBlockItem) => {
|
||||
switch (block.__typename) {
|
||||
case HotelBlocksTypenameEnum.HotelPageContentUpcomingActivitiesCard:
|
||||
return {
|
||||
...block.upcoming_activities_card,
|
||||
background_image: makeImageVaultImage(
|
||||
block.upcoming_activities_card?.background_image
|
||||
),
|
||||
contentPage:
|
||||
block.upcoming_activities_card?.hotel_page_activities_content_pageConnection?.edges.map(
|
||||
({ node: contentPage }: { node: any }) => {
|
||||
return {
|
||||
href:
|
||||
contentPage.web?.original_url ||
|
||||
removeMultipleSlashes(
|
||||
`/${contentPage.system.locale}/${contentPage.url}`
|
||||
),
|
||||
}
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
})[0]
|
||||
? contentstackData?.content[0]
|
||||
: null
|
||||
|
||||
getHotelSuccessCounter.add(1, { hotelId, lang, include })
|
||||
@@ -253,7 +229,7 @@ export const hotelQueryRouter = router({
|
||||
hotelImages: images,
|
||||
pointsOfInterest: hotelAttributes.pointsOfInterest,
|
||||
roomCategories,
|
||||
activitiesCard: activities,
|
||||
activitiesCard: activities?.upcoming_activities_card,
|
||||
}
|
||||
}),
|
||||
availability: router({
|
||||
@@ -516,4 +492,66 @@ export const hotelQueryRouter = router({
|
||||
return validateHotelData.data
|
||||
}),
|
||||
}),
|
||||
locations: router({
|
||||
get: serviceProcedure.query(async function ({ ctx }) {
|
||||
const searchParams = new URLSearchParams()
|
||||
searchParams.set("language", toApiLang(ctx.lang))
|
||||
|
||||
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: TWENTYFOUR_HOURS,
|
||||
},
|
||||
}
|
||||
|
||||
const getCachedCountries = unstable_cache(
|
||||
getCountries,
|
||||
[`${ctx.lang}:${locationsAffix}:countries`],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)
|
||||
|
||||
const countries = await getCachedCountries(options, searchParams)
|
||||
|
||||
const getCachedCitiesByCountry = unstable_cache(
|
||||
getCitiesByCountry,
|
||||
[`${ctx.lang}:${locationsAffix}:cities-by-country`],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)
|
||||
|
||||
let citiesByCountry = null
|
||||
if (countries) {
|
||||
citiesByCountry = await getCachedCitiesByCountry(
|
||||
countries,
|
||||
options,
|
||||
searchParams
|
||||
)
|
||||
}
|
||||
|
||||
const getCachedLocations = unstable_cache(
|
||||
getLocations,
|
||||
[`${ctx.lang}:${locationsAffix}`],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)
|
||||
|
||||
const locations = await getCachedLocations(
|
||||
ctx.lang,
|
||||
options,
|
||||
searchParams,
|
||||
citiesByCountry
|
||||
)
|
||||
|
||||
if (Array.isArray(locations)) {
|
||||
return {
|
||||
data: locations,
|
||||
}
|
||||
}
|
||||
|
||||
return locations
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user