feat(SW-1012): Added possibility for multiple include params for hotels
This commit is contained in:
committed by
Fredrik Thorsson
parent
92bbfcf533
commit
05006506f0
@@ -0,0 +1 @@
|
||||
export default function RestaurantSidepeek() {}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { restaurantAndBar } from "@/constants/routes/hotelPageParams"
|
||||
import { env } from "@/env/server"
|
||||
import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
@@ -21,6 +20,7 @@ import MapCard from "./Map/MapCard"
|
||||
import MapWithCardWrapper from "./Map/MapWithCard"
|
||||
import MobileMapToggle from "./Map/MobileMapToggle"
|
||||
import StaticMap from "./Map/StaticMap"
|
||||
import RestaurantBarSidePeek from "./SidePeeks/RestaurantBar"
|
||||
import AmenitiesList from "./AmenitiesList"
|
||||
import Facilities from "./Facilities"
|
||||
import IntroSection from "./IntroSection"
|
||||
@@ -78,8 +78,8 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
||||
ratings,
|
||||
parking,
|
||||
} = hotelData.data.attributes
|
||||
const roomCategories =
|
||||
hotelData.included?.filter((item) => item.type === "roomcategories") || []
|
||||
const roomCategories = hotelData.included?.rooms || []
|
||||
const restaurants = hotelData.included?.restaurants || []
|
||||
const images = gallery?.smallerImages
|
||||
const description = hotelContent.texts.descriptions.medium
|
||||
const activitiesCard = content?.[0]?.upcoming_activities_card || null
|
||||
@@ -197,14 +197,8 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
||||
ecoLabels={hotelFacts.ecoLabels}
|
||||
descriptions={hotelContent.texts}
|
||||
/>
|
||||
<SidePeek
|
||||
contentKey={restaurantAndBar[lang]}
|
||||
title={intl.formatMessage({ id: "Restaurant & Bar" })}
|
||||
>
|
||||
{/* TODO */}
|
||||
Restaurant & Bar
|
||||
</SidePeek>
|
||||
<WellnessAndExerciseSidePeek healthFacilities={healthFacilities} />
|
||||
<RestaurantBarSidePeek restaurants={restaurants} />
|
||||
{activitiesCard && (
|
||||
<ActivitiesSidePeek contentPage={activitiesCard.contentPage} />
|
||||
)}
|
||||
|
||||
@@ -95,7 +95,7 @@ export async function RoomsContainer({
|
||||
user={user}
|
||||
availablePackages={packages ?? []}
|
||||
roomsAvailability={roomsAvailability}
|
||||
roomCategories={hotelData?.included ?? []}
|
||||
roomCategories={hotelData?.included?.rooms ?? []}
|
||||
hotelType={hotelData?.data.attributes?.hotelType}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
@@ -30,11 +30,20 @@ const wrappedFetch = fetchRetry(fetch, {
|
||||
export async function get(
|
||||
endpoint: Endpoint,
|
||||
options: RequestOptionsWithOutBody,
|
||||
params = {}
|
||||
params: Record<string, any> = {}
|
||||
) {
|
||||
const url = new URL(env.API_BASEURL)
|
||||
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(
|
||||
url,
|
||||
merge.all([defaultOptions, { method: "GET" }, options])
|
||||
|
||||
@@ -13,6 +13,10 @@ import type {
|
||||
HotelDataInput,
|
||||
} from "@/server/routers/hotels/input"
|
||||
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
|
||||
import type {
|
||||
BreackfastPackagesInput,
|
||||
PackagesInput,
|
||||
} from "@/types/requests/packages"
|
||||
|
||||
export const getLocations = cache(async function getMemoizedLocations() {
|
||||
return serverClient().hotel.locations.get()
|
||||
|
||||
@@ -51,6 +51,10 @@ export const getHotelDataInputSchema = z.object({
|
||||
isCardOnlyPayment: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const getRestaurantsInputSchema = z.object({
|
||||
hotelId: z.string(),
|
||||
})
|
||||
|
||||
export type HotelDataInput = z.input<typeof getHotelDataInputSchema>
|
||||
|
||||
export const getBreakfastPackageInputSchema = z.object({
|
||||
|
||||
@@ -4,15 +4,21 @@ import { ChildBedTypeEnum, type PaymentMethodEnum } from "@/constants/booking"
|
||||
import { dt } from "@/lib/dt"
|
||||
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 { specialAlertsSchema } from "./schemas/specialAlerts"
|
||||
import { getPoiGroupByCategoryName } from "./utils"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
import { FacilityEnum } from "@/types/enums/facilities"
|
||||
import { PackageTypeEnum } from "@/types/enums/packages"
|
||||
import { RestaurantData, RoomData } from "@/types/hotel"
|
||||
|
||||
const ratingsSchema = z
|
||||
.object({
|
||||
@@ -159,11 +165,6 @@ export const facilitySchema = z.object({
|
||||
),
|
||||
})
|
||||
|
||||
export const imageSchema = z.object({
|
||||
metaData: imageMetaDataSchema,
|
||||
imageSizes: imageSizesSchema,
|
||||
})
|
||||
|
||||
export const gallerySchema = z.object({
|
||||
heroImages: z.array(imageSchema),
|
||||
smallerImages: z.array(imageSchema),
|
||||
@@ -332,36 +333,6 @@ const socialMediaSchema = z.object({
|
||||
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({
|
||||
restaurants: z.object({
|
||||
links: z.object({
|
||||
@@ -487,6 +458,19 @@ export const hotelAttributesSchema = z.object({
|
||||
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
|
||||
export const getHotelDataSchema = 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
|
||||
// additional data for an individual hotel.
|
||||
included: z.array(roomSchema).optional(),
|
||||
included: includedSchema.optional(),
|
||||
})
|
||||
|
||||
export const childrenSchema = z.object({
|
||||
|
||||
@@ -51,6 +51,14 @@ const getHotelCounter = meter.createCounter("trpc.hotel.get")
|
||||
const getHotelSuccessCounter = meter.createCounter("trpc.hotel.get-success")
|
||||
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 getPackagesSuccessCounter = meter.createCounter(
|
||||
"trpc.hotel.packages.get-success"
|
||||
@@ -101,12 +109,12 @@ export const getHotelData = cache(
|
||||
async (input: HotelDataInput, serviceToken: string) => {
|
||||
const { hotelId, language, isCardOnlyPayment } = input
|
||||
|
||||
const params: Record<string, string> = {
|
||||
const params: Record<string, string | string[]> = {
|
||||
hotelId,
|
||||
language,
|
||||
}
|
||||
|
||||
params.include = "RoomCategories" // "RoomCategories","NearbyHotels","Restaurants","City",
|
||||
params.include = ["RoomCategories", "Restaurants"] // "RoomCategories","NearbyHotels","Restaurants","City",
|
||||
|
||||
getHotelCounter.add(1, {
|
||||
hotelId,
|
||||
@@ -780,7 +788,7 @@ export const hotelQueryRouter = router({
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
searchParams
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
|
||||
@@ -13,3 +13,8 @@ export const imageMetaDataSchema = z.object({
|
||||
altText_En: z.string(),
|
||||
copyRight: z.string(),
|
||||
})
|
||||
|
||||
export const imageSchema = z.object({
|
||||
metaData: imageMetaDataSchema,
|
||||
imageSizes: imageSizesSchema,
|
||||
})
|
||||
|
||||
80
server/routers/hotels/schemas/restaurants.ts
Normal file
80
server/routers/hotels/schemas/restaurants.ts
Normal 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)
|
||||
})
|
||||
@@ -78,7 +78,7 @@ export const roomSchema = z
|
||||
}),
|
||||
}),
|
||||
id: z.string(),
|
||||
type: z.enum(["roomcategories"]),
|
||||
type: z.literal("roomcategories"),
|
||||
})
|
||||
.transform((data) => {
|
||||
return {
|
||||
|
||||
33
server/routers/hotels/schemas/specialAlerts.ts
Normal file
33
server/routers/hotels/schemas/specialAlerts.ts
Normal 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([])
|
||||
5
types/components/hotelPage/sidepeek/restaurantBar.ts
Normal file
5
types/components/hotelPage/sidepeek/restaurantBar.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { RestaurantData } from "@/types/hotel"
|
||||
|
||||
export interface RestaurantBarSidePeekProps {
|
||||
restaurants: RestaurantData[]
|
||||
}
|
||||
@@ -4,10 +4,11 @@ import {
|
||||
checkinSchema,
|
||||
facilitySchema,
|
||||
getHotelDataSchema,
|
||||
imageSchema,
|
||||
parkingSchema,
|
||||
pointOfInterestSchema,
|
||||
} 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"
|
||||
|
||||
export type HotelData = z.infer<typeof getHotelDataSchema>
|
||||
@@ -23,6 +24,7 @@ export type HotelTripAdvisor =
|
||||
| undefined
|
||||
|
||||
export type RoomData = z.infer<typeof roomSchema>
|
||||
export type RestaurantData = z.infer<typeof restaurantSchema>
|
||||
export type GalleryImage = z.infer<typeof imageSchema>
|
||||
export type CheckInData = z.infer<typeof checkinSchema>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user