feat(SW-66, SW-348): search functionality and ui

This commit is contained in:
Simon Emanuelsson
2024-08-28 10:47:57 +02:00
parent b9dbcf7d90
commit af850c90e7
437 changed files with 7663 additions and 9881 deletions

View File

@@ -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
}),
}),
})