feat(SW-1012): Added possibility for multiple include params for hotels

This commit is contained in:
Erik Tiekstra
2024-11-26 11:07:47 +01:00
committed by Fredrik Thorsson
parent 92bbfcf533
commit 05006506f0
17 changed files with 218 additions and 57 deletions

View File

@@ -0,0 +1 @@
export default function RestaurantSidepeek() {}

View File

@@ -0,0 +1,32 @@
import { restaurantAndBar } from "@/constants/routes/hotelPageParams"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import styles from "./restaurantBar.module.css"
import type { RestaurantBarSidePeekProps } from "@/types/components/hotelPage/sidepeek/restaurantBar"
export default async function RestaurantBarSidePeek({
restaurants,
}: RestaurantBarSidePeekProps) {
const lang = getLang()
const intl = await getIntl()
return (
<SidePeek
contentKey={restaurantAndBar[lang]}
title={intl.formatMessage({ id: "Restaurant & Bar" })}
>
<div className={styles.content}>
{restaurants.map((restaurant) => (
<div key={restaurant.id}>
<h3>{restaurant.name}</h3>
</div>
))}
</div>
</SidePeek>
)
}

View File

@@ -1,7 +1,6 @@
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { Suspense } from "react" import { Suspense } from "react"
import { restaurantAndBar } from "@/constants/routes/hotelPageParams"
import { env } from "@/env/server" import { env } from "@/env/server"
import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests" import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests"
@@ -21,6 +20,7 @@ import MapCard from "./Map/MapCard"
import MapWithCardWrapper from "./Map/MapWithCard" import MapWithCardWrapper from "./Map/MapWithCard"
import MobileMapToggle from "./Map/MobileMapToggle" import MobileMapToggle from "./Map/MobileMapToggle"
import StaticMap from "./Map/StaticMap" import StaticMap from "./Map/StaticMap"
import RestaurantBarSidePeek from "./SidePeeks/RestaurantBar"
import AmenitiesList from "./AmenitiesList" import AmenitiesList from "./AmenitiesList"
import Facilities from "./Facilities" import Facilities from "./Facilities"
import IntroSection from "./IntroSection" import IntroSection from "./IntroSection"
@@ -78,8 +78,8 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
ratings, ratings,
parking, parking,
} = hotelData.data.attributes } = hotelData.data.attributes
const roomCategories = const roomCategories = hotelData.included?.rooms || []
hotelData.included?.filter((item) => item.type === "roomcategories") || [] const restaurants = hotelData.included?.restaurants || []
const images = gallery?.smallerImages const images = gallery?.smallerImages
const description = hotelContent.texts.descriptions.medium const description = hotelContent.texts.descriptions.medium
const activitiesCard = content?.[0]?.upcoming_activities_card || null const activitiesCard = content?.[0]?.upcoming_activities_card || null
@@ -197,14 +197,8 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
ecoLabels={hotelFacts.ecoLabels} ecoLabels={hotelFacts.ecoLabels}
descriptions={hotelContent.texts} descriptions={hotelContent.texts}
/> />
<SidePeek
contentKey={restaurantAndBar[lang]}
title={intl.formatMessage({ id: "Restaurant & Bar" })}
>
{/* TODO */}
Restaurant & Bar
</SidePeek>
<WellnessAndExerciseSidePeek healthFacilities={healthFacilities} /> <WellnessAndExerciseSidePeek healthFacilities={healthFacilities} />
<RestaurantBarSidePeek restaurants={restaurants} />
{activitiesCard && ( {activitiesCard && (
<ActivitiesSidePeek contentPage={activitiesCard.contentPage} /> <ActivitiesSidePeek contentPage={activitiesCard.contentPage} />
)} )}

View File

@@ -95,7 +95,7 @@ export async function RoomsContainer({
user={user} user={user}
availablePackages={packages ?? []} availablePackages={packages ?? []}
roomsAvailability={roomsAvailability} roomsAvailability={roomsAvailability}
roomCategories={hotelData?.included ?? []} roomCategories={hotelData?.included?.rooms ?? []}
hotelType={hotelData?.data.attributes?.hotelType} hotelType={hotelData?.data.attributes?.hotelType}
/> />
) )

View File

@@ -32,7 +32,7 @@ export default function HotelReservationSidePeek({
} }
) )
const selectedRoom = hotelData?.included?.find((room) => const selectedRoom = hotelData?.included?.rooms?.find((room) =>
room.roomTypes.some((type) => type.code === roomTypeCode) room.roomTypes.some((type) => type.code === roomTypeCode)
) )

View File

@@ -30,11 +30,20 @@ const wrappedFetch = fetchRetry(fetch, {
export async function get( export async function get(
endpoint: Endpoint, endpoint: Endpoint,
options: RequestOptionsWithOutBody, options: RequestOptionsWithOutBody,
params = {} params: Record<string, any> = {}
) { ) {
const url = new URL(env.API_BASEURL) const url = new URL(env.API_BASEURL)
url.pathname = endpoint url.pathname = endpoint
url.search = new URLSearchParams(params).toString() const searchParams = new URLSearchParams()
Object.entries(params).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((val) => searchParams.append(key, val))
} else {
searchParams.set(key, value)
}
})
url.search = searchParams.toString()
return wrappedFetch( return wrappedFetch(
url, url,
merge.all([defaultOptions, { method: "GET" }, options]) merge.all([defaultOptions, { method: "GET" }, options])

View File

@@ -13,6 +13,10 @@ import type {
HotelDataInput, HotelDataInput,
} from "@/server/routers/hotels/input" } from "@/server/routers/hotels/input"
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input" import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
import type {
BreackfastPackagesInput,
PackagesInput,
} from "@/types/requests/packages"
export const getLocations = cache(async function getMemoizedLocations() { export const getLocations = cache(async function getMemoizedLocations() {
return serverClient().hotel.locations.get() return serverClient().hotel.locations.get()

View File

@@ -51,6 +51,10 @@ export const getHotelDataInputSchema = z.object({
isCardOnlyPayment: z.boolean().optional(), isCardOnlyPayment: z.boolean().optional(),
}) })
export const getRestaurantsInputSchema = z.object({
hotelId: z.string(),
})
export type HotelDataInput = z.input<typeof getHotelDataInputSchema> export type HotelDataInput = z.input<typeof getHotelDataInputSchema>
export const getBreakfastPackageInputSchema = z.object({ export const getBreakfastPackageInputSchema = z.object({

View File

@@ -4,15 +4,21 @@ import { ChildBedTypeEnum, type PaymentMethodEnum } from "@/constants/booking"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import { toLang } from "@/server/utils" import { toLang } from "@/server/utils"
import { imageMetaDataSchema, imageSizesSchema } from "./schemas/image" import {
imageMetaDataSchema,
imageSchema,
imageSizesSchema,
} from "./schemas/image"
import { restaurantSchema } from "./schemas/restaurants"
import { roomSchema } from "./schemas/room" import { roomSchema } from "./schemas/room"
import { specialAlertsSchema } from "./schemas/specialAlerts"
import { getPoiGroupByCategoryName } from "./utils" import { getPoiGroupByCategoryName } from "./utils"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { AlertTypeEnum } from "@/types/enums/alert"
import { CurrencyEnum } from "@/types/enums/currency" import { CurrencyEnum } from "@/types/enums/currency"
import { FacilityEnum } from "@/types/enums/facilities" import { FacilityEnum } from "@/types/enums/facilities"
import { PackageTypeEnum } from "@/types/enums/packages" import { PackageTypeEnum } from "@/types/enums/packages"
import { RestaurantData, RoomData } from "@/types/hotel"
const ratingsSchema = z const ratingsSchema = z
.object({ .object({
@@ -159,11 +165,6 @@ export const facilitySchema = z.object({
), ),
}) })
export const imageSchema = z.object({
metaData: imageMetaDataSchema,
imageSizes: imageSizesSchema,
})
export const gallerySchema = z.object({ export const gallerySchema = z.object({
heroImages: z.array(imageSchema), heroImages: z.array(imageSchema),
smallerImages: z.array(imageSchema), smallerImages: z.array(imageSchema),
@@ -332,36 +333,6 @@ const socialMediaSchema = z.object({
facebook: z.string().optional(), facebook: z.string().optional(),
}) })
const specialAlertSchema = z.object({
type: z.string(),
title: z.string().optional(),
description: z.string().optional(),
displayInBookingFlow: z.boolean(),
startDate: z.string().optional(),
endDate: z.string().optional(),
})
const specialAlertsSchema = z
.array(specialAlertSchema)
.transform((data) => {
const now = dt().utc().format("YYYY-MM-DD")
const filteredAlerts = data.filter((alert) => {
const shouldShowNow =
alert.startDate && alert.endDate
? alert.startDate <= now && alert.endDate >= now
: true
const hasText = alert.description || alert.title
return shouldShowNow && hasText
})
return filteredAlerts.map((alert, idx) => ({
id: `alert-${alert.type}-${idx}`,
type: AlertTypeEnum.Info,
heading: alert.title || null,
text: alert.description || null,
}))
})
.default([])
const relationshipsSchema = z.object({ const relationshipsSchema = z.object({
restaurants: z.object({ restaurants: z.object({
links: z.object({ links: z.object({
@@ -487,6 +458,19 @@ export const hotelAttributesSchema = z.object({
specialNeedGroups: z.array(specialNeedGroupSchema), specialNeedGroups: z.array(specialNeedGroupSchema),
}) })
const includedSchema = z
.array(z.union([roomSchema, restaurantSchema]))
.transform((data) => {
const rooms = data.filter((d) => d.type === "roomcategories") as RoomData[]
const restaurants = data.filter(
(d) => d.type === "restaurants"
) as RestaurantData[]
return {
rooms,
restaurants,
}
})
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html // NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
export const getHotelDataSchema = z.object({ export const getHotelDataSchema = z.object({
data: z.object({ data: z.object({
@@ -504,7 +488,7 @@ export const getHotelDataSchema = z.object({
}), }),
// NOTE: We can pass an "include" param to the hotel API to retrieve // NOTE: We can pass an "include" param to the hotel API to retrieve
// additional data for an individual hotel. // additional data for an individual hotel.
included: z.array(roomSchema).optional(), included: includedSchema.optional(),
}) })
export const childrenSchema = z.object({ export const childrenSchema = z.object({

View File

@@ -51,6 +51,14 @@ const getHotelCounter = meter.createCounter("trpc.hotel.get")
const getHotelSuccessCounter = meter.createCounter("trpc.hotel.get-success") const getHotelSuccessCounter = meter.createCounter("trpc.hotel.get-success")
const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail") const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail")
const getRestaurantsCounter = meter.createCounter("trpc.hotel.restaurants.get")
const getRestaurantsSuccessCounter = meter.createCounter(
"trpc.hotel.restaurants.get-success"
)
const getRestaurantsFailCounter = meter.createCounter(
"trpc.hotel.restaurants.get-fail"
)
const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get") const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get")
const getPackagesSuccessCounter = meter.createCounter( const getPackagesSuccessCounter = meter.createCounter(
"trpc.hotel.packages.get-success" "trpc.hotel.packages.get-success"
@@ -101,12 +109,12 @@ export const getHotelData = cache(
async (input: HotelDataInput, serviceToken: string) => { async (input: HotelDataInput, serviceToken: string) => {
const { hotelId, language, isCardOnlyPayment } = input const { hotelId, language, isCardOnlyPayment } = input
const params: Record<string, string> = { const params: Record<string, string | string[]> = {
hotelId, hotelId,
language, language,
} }
params.include = "RoomCategories" // "RoomCategories","NearbyHotels","Restaurants","City", params.include = ["RoomCategories", "Restaurants"] // "RoomCategories","NearbyHotels","Restaurants","City",
getHotelCounter.add(1, { getHotelCounter.add(1, {
hotelId, hotelId,
@@ -780,7 +788,7 @@ export const hotelQueryRouter = router({
Authorization: `Bearer ${ctx.serviceToken}`, Authorization: `Bearer ${ctx.serviceToken}`,
}, },
}, },
params searchParams
) )
if (!apiResponse.ok) { if (!apiResponse.ok) {

View File

@@ -13,3 +13,8 @@ export const imageMetaDataSchema = z.object({
altText_En: z.string(), altText_En: z.string(),
copyRight: z.string(), copyRight: z.string(),
}) })
export const imageSchema = z.object({
metaData: imageMetaDataSchema,
imageSizes: imageSizesSchema,
})

View File

@@ -0,0 +1,80 @@
import { z } from "zod"
import { imageSchema } from "./image"
import { specialAlertsSchema } from "./specialAlerts"
const restaurantPriceSchema = z.object({
currency: z.string(),
amount: z.number(),
})
const restaurantDaySchema = z.object({
sortOrder: z.number(),
alwaysOpen: z.boolean(),
isClosed: z.boolean(),
openingTime: z.string(),
closingTime: z.string(),
})
const restaurantOpeningHoursSchema = z.object({
isActive: z.boolean(),
name: z.string().optional(),
openingTime: z.string().optional(),
closingTime: z.string().optional(),
monday: restaurantDaySchema.optional(),
tuesday: restaurantDaySchema.optional(),
wednesday: restaurantDaySchema.optional(),
thursday: restaurantDaySchema.optional(),
friday: restaurantDaySchema.optional(),
saturday: restaurantDaySchema.optional(),
sunday: restaurantDaySchema.optional(),
})
const restaurantOpeningDetailSchema = z.object({
openingHours: restaurantOpeningHoursSchema,
alternateOpeningHours: restaurantOpeningHoursSchema.optional(),
})
export const restaurantSchema = z
.object({
attributes: z.object({
name: z.string().optional(),
isPublished: z.boolean(),
email: z.string().optional(),
phoneNumber: z.string().optional(),
externalBreakfast: z.object({
isAvailable: z.boolean(),
localPriceForExternalGuests: restaurantPriceSchema.optional(),
requestedPriceForExternalGuests: restaurantPriceSchema.optional(),
}),
menus: z
.array(
z.object({
name: z.string(),
url: z.string(),
})
)
.optional(),
openingDetails: z.array(restaurantOpeningDetailSchema),
content: z.object({
images: z.array(imageSchema),
texts: z.object({
descriptions: z.object({
short: z.string(),
medium: z.string(),
}),
}),
bookTableUrl: z.string().optional(),
specialAlerts: specialAlertsSchema,
}),
}),
id: z.string(),
type: z.literal("restaurants"),
})
.transform(({ attributes, id, type }) => ({ ...attributes, id, type }))
export const getRestaurantsSchema = z
.object({
data: z.array(restaurantSchema),
})
.transform(({ data }) => {
return data.filter((item) => !!item.isPublished)
})

View File

@@ -78,7 +78,7 @@ export const roomSchema = z
}), }),
}), }),
id: z.string(), id: z.string(),
type: z.enum(["roomcategories"]), type: z.literal("roomcategories"),
}) })
.transform((data) => { .transform((data) => {
return { return {

View File

@@ -0,0 +1,33 @@
import { dt } from "@/lib/dt"
import { AlertTypeEnum } from "@/types/enums/alert"
import { z } from "zod"
const specialAlertSchema = z.object({
type: z.string(),
title: z.string().optional(),
description: z.string().optional(),
displayInBookingFlow: z.boolean(),
startDate: z.string().optional(),
endDate: z.string().optional(),
})
export const specialAlertsSchema = z
.array(specialAlertSchema)
.transform((data) => {
const now = dt().utc().format("YYYY-MM-DD")
const filteredAlerts = data.filter((alert) => {
const shouldShowNow =
alert.startDate && alert.endDate
? alert.startDate <= now && alert.endDate >= now
: true
const hasText = alert.description || alert.title
return shouldShowNow && hasText
})
return filteredAlerts.map((alert, idx) => ({
id: `alert-${alert.type}-${idx}`,
type: AlertTypeEnum.Info,
heading: alert.title || null,
text: alert.description || null,
}))
})
.default([])

View File

@@ -0,0 +1,5 @@
import type { RestaurantData } from "@/types/hotel"
export interface RestaurantBarSidePeekProps {
restaurants: RestaurantData[]
}

View File

@@ -4,10 +4,11 @@ import {
checkinSchema, checkinSchema,
facilitySchema, facilitySchema,
getHotelDataSchema, getHotelDataSchema,
imageSchema,
parkingSchema, parkingSchema,
pointOfInterestSchema, pointOfInterestSchema,
} from "@/server/routers/hotels/output" } from "@/server/routers/hotels/output"
import { imageSchema } from "@/server/routers/hotels/schemas/image"
import { restaurantSchema } from "@/server/routers/hotels/schemas/restaurants"
import { roomSchema } from "@/server/routers/hotels/schemas/room" import { roomSchema } from "@/server/routers/hotels/schemas/room"
export type HotelData = z.infer<typeof getHotelDataSchema> export type HotelData = z.infer<typeof getHotelDataSchema>
@@ -23,6 +24,7 @@ export type HotelTripAdvisor =
| undefined | undefined
export type RoomData = z.infer<typeof roomSchema> export type RoomData = z.infer<typeof roomSchema>
export type RestaurantData = z.infer<typeof restaurantSchema>
export type GalleryImage = z.infer<typeof imageSchema> export type GalleryImage = z.infer<typeof imageSchema>
export type CheckInData = z.infer<typeof checkinSchema> export type CheckInData = z.infer<typeof checkinSchema>