) {
setLang(params.lang)
const searchDetails = await getHotelSearchDetails({ searchParams })
- if (!searchDetails) {
- return notFound()
- }
- const { hotel, adultsInRoom, childrenInRoom, selectHotelParams } = searchDetails
-
- if (!hotel) {
+ if (!searchDetails?.hotel) {
return notFound()
}
+ const { hotel, adultsInRoom, childrenInRoom, selectHotelParams } =
+ searchDetails
const hotelData = await getHotel({
hotelId: hotel.id,
diff --git a/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/page.tsx
index c8500a2bc..5e397579d 100644
--- a/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/page.tsx
+++ b/app/[lang]/(live)/@bookingwidget/[contentType]/[uid]/page.tsx
@@ -27,8 +27,8 @@ export default async function BookingWidgetPage({
})
const hotelPageParams = {
- hotel: hotelData?.data?.id || "",
- city: hotelData?.data?.attributes?.cityName || "",
+ hotel: hotelData?.hotel?.id || "",
+ city: hotelData?.hotel?.cityName || "",
}
return
diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/index.tsx
index 27089a617..d01e6f5b1 100644
--- a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/index.tsx
+++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/index.tsx
@@ -10,7 +10,7 @@ import {
} from "@/types/components/hotelPage/sidepeek/parking"
export default async function ParkingPrices({
- currency,
+ currency = "",
freeParking,
pricing,
}: ParkingPricesProps) {
diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx
index 4049a2903..52685e9dd 100644
--- a/components/ContentType/HotelPage/index.tsx
+++ b/components/ContentType/HotelPage/index.tsx
@@ -125,10 +125,10 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
const trackingPageData = getTrackingPageData(
hotelPageData.system,
- hotelData.data.attributes,
+ hotelData.hotel,
lang
)
- const trackingHotelData = getTrackingHotelData(hotelData.data)
+ const trackingHotelData = getTrackingHotelData(hotelData.hotel)
return (
diff --git a/components/ContentType/HotelPage/utils.ts b/components/ContentType/HotelPage/utils.ts
index 4e9bb06f5..a725548f1 100644
--- a/components/ContentType/HotelPage/utils.ts
+++ b/components/ContentType/HotelPage/utils.ts
@@ -35,7 +35,7 @@ export function getTrackingPageData(
return tracking
}
-export function getTrackingHotelData(hotelData: HotelData["data"]) {
+export function getTrackingHotelData(hotelData: HotelData["hotel"]) {
const tracking: TrackingSDKHotelInfo = {
hotelID: hotelData.id,
}
diff --git a/components/Forms/Edit/Profile/schema.ts b/components/Forms/Edit/Profile/schema.ts
index e785b4716..8aad6c1fd 100644
--- a/components/Forms/Edit/Profile/schema.ts
+++ b/components/Forms/Edit/Profile/schema.ts
@@ -1,7 +1,7 @@
import { z } from "zod"
-import { passwordValidator } from "@/utils/passwordValidator"
-import { phoneValidator } from "@/utils/phoneValidator"
+import { passwordValidator } from "@/utils/zod/passwordValidator"
+import { phoneValidator } from "@/utils/zod/phoneValidator"
const countryRequiredMsg = "Country is required"
export const editProfileSchema = z
diff --git a/components/Forms/Signup/schema.ts b/components/Forms/Signup/schema.ts
index 2962d9b90..37969893e 100644
--- a/components/Forms/Signup/schema.ts
+++ b/components/Forms/Signup/schema.ts
@@ -1,7 +1,7 @@
import { z } from "zod"
-import { passwordValidator } from "@/utils/passwordValidator"
-import { phoneValidator } from "@/utils/phoneValidator"
+import { passwordValidator } from "@/utils/zod/passwordValidator"
+import { phoneValidator } from "@/utils/zod/phoneValidator"
const countryRequiredMsg = "Country is required"
export const signUpSchema = z.object({
diff --git a/components/HotelReservation/BookingConfirmation/index.tsx b/components/HotelReservation/BookingConfirmation/index.tsx
index c3573dc33..de0c1d1fe 100644
--- a/components/HotelReservation/BookingConfirmation/index.tsx
+++ b/components/HotelReservation/BookingConfirmation/index.tsx
@@ -22,9 +22,8 @@ export default async function BookingConfirmation({
confirmationNumber,
}: BookingConfirmationProps) {
const lang = getLang()
- const { booking, hotel, room } = await getBookingConfirmation(
- confirmationNumber
- )
+ const { booking, hotel, room } =
+ await getBookingConfirmation(confirmationNumber)
const arrivalDate = new Date(booking.checkInDate)
const departureDate = new Date(booking.checkOutDate)
diff --git a/components/HotelReservation/EnterDetails/Details/schema.ts b/components/HotelReservation/EnterDetails/Details/schema.ts
index 8371fe31f..8c3309044 100644
--- a/components/HotelReservation/EnterDetails/Details/schema.ts
+++ b/components/HotelReservation/EnterDetails/Details/schema.ts
@@ -2,7 +2,7 @@ import { z } from "zod"
import { dt } from "@/lib/dt"
-import { phoneValidator } from "@/utils/phoneValidator"
+import { phoneValidator } from "@/utils/zod/phoneValidator"
// stringMatcher regex is copied from current web as specified by requirements.
const stringMatcher =
diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx
index d1aed3f1d..d4379e22b 100644
--- a/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx
+++ b/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx
@@ -31,10 +31,6 @@ import {
type TrackingSDKPageData,
} from "@/types/components/tracking"
-function isValidHotelData(hotel: NullableHotelData): hotel is HotelData {
- return hotel != null
-}
-
export async function SelectHotelMapContainer({
searchParams,
isAlternativeHotels,
@@ -89,7 +85,7 @@ export async function SelectHotelMapContainer({
const [hotels] = await fetchAvailableHotelsPromise
- const validHotels = hotels?.filter(isValidHotelData) || []
+ const validHotels = (hotels?.filter(Boolean) as HotelData[]) || []
const hotelPins = getHotelPins(validHotels)
const filterList = getFiltersFromHotels(validHotels)
diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx
index 9655e1c12..7af28f4dc 100644
--- a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx
+++ b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx
@@ -66,7 +66,7 @@ export default async function HotelInfoCard({
{
address: hotel.address.streetAddress,
city: hotel.address.city,
- distanceToCityCentreInKm: getSingleDecimal(
+ distanceToCityCenterInKm: getSingleDecimal(
hotel.location.distanceToCentre / 1000
),
}
diff --git a/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx b/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx
index fac022eb9..e083c14a4 100644
--- a/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx
+++ b/components/HotelReservation/SelectRate/SelectedRoomPanel/index.tsx
@@ -12,13 +12,13 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./selectedRoomPanel.module.css"
-import type { Room } from "@/types/components/hotelReservation/selectRate/selectRate"
-import type { RoomData } from "@/types/hotel"
+import type { Room as SelectedRateRoom } from "@/types/components/hotelReservation/selectRate/selectRate"
+import type { Room } from "@/types/hotel"
interface SelectedRoomPanelProps {
roomIndex: number
- room: Room
- roomCategories: RoomData[]
+ room: SelectedRateRoom
+ roomCategories: Room[]
}
export default function SelectedRoomPanel({
diff --git a/components/TempDesignSystem/Form/NewPassword/index.tsx b/components/TempDesignSystem/Form/NewPassword/index.tsx
index 8affe22e6..69cfaecc2 100644
--- a/components/TempDesignSystem/Form/NewPassword/index.tsx
+++ b/components/TempDesignSystem/Form/NewPassword/index.tsx
@@ -14,7 +14,7 @@ import {
} from "@/components/Icons"
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
import Caption from "@/components/TempDesignSystem/Text/Caption"
-import { passwordValidators } from "@/utils/passwordValidator"
+import { passwordValidators } from "@/utils/zod/passwordValidator"
import Button from "../../Button"
import { type IconProps, type NewPasswordProps } from "./newPassword"
diff --git a/server/routers/booking/output.ts b/server/routers/booking/output.ts
index 442fb5810..07dfee4e1 100644
--- a/server/routers/booking/output.ts
+++ b/server/routers/booking/output.ts
@@ -2,7 +2,7 @@ import { z } from "zod"
import { ChildBedTypeEnum } from "@/constants/booking"
-import { phoneValidator } from "@/utils/phoneValidator"
+import { phoneValidator } from "@/utils/zod/phoneValidator"
// MUTATION
export const createBookingSchema = z
diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts
index a25b7fb12..ee7130c1f 100644
--- a/server/routers/hotels/output.ts
+++ b/server/routers/hotels/output.ts
@@ -7,7 +7,7 @@ import { productTypeSchema } from "./schemas/availability/productType"
import { citySchema } from "./schemas/city"
import {
attributesSchema,
- includesSchema,
+ includedSchema,
relationshipsSchema as hotelRelationshipsSchema,
} from "./schemas/hotel"
import { locationCitySchema } from "./schemas/location/city"
@@ -18,7 +18,13 @@ import { relationshipsSchema } from "./schemas/relationships"
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
-import type { AdditionalData, City, NearbyHotel, Restaurant, Room } from "@/types/hotel"
+import type {
+ AdditionalData,
+ City,
+ NearbyHotel,
+ Restaurant,
+ Room,
+} from "@/types/hotel"
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
export const hotelSchema = z
@@ -38,10 +44,13 @@ export const hotelSchema = z
}),
// NOTE: We can pass an "include" param to the hotel API to retrieve
// additional data for an individual hotel.
- included: includesSchema,
+ included: includedSchema,
})
.transform(({ data: { attributes, ...data }, included }) => {
- const additionalData = included.find((inc): inc is AdditionalData => inc!.type === "additionalData")
+ const additionalData =
+ included.find(
+ (inc): inc is AdditionalData => inc!.type === "additionalData"
+ ) ?? ({} as AdditionalData)
const cities = included.filter((inc): inc is City => inc!.type === "cities")
const nearbyHotels = included.filter(
(inc): inc is NearbyHotel => inc!.type === "hotels"
@@ -262,4 +271,4 @@ export const getNearbyHotelIdsSchema = z
})
),
})
- .transform((data) => data.data.map((hotel) => hotel.id))
\ No newline at end of file
+ .transform((data) => data.data.map((hotel) => hotel.id))
diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts
index 52fe76898..38531c501 100644
--- a/server/routers/hotels/query.ts
+++ b/server/routers/hotels/query.ts
@@ -1,4 +1,7 @@
+import { unstable_cache } from "next/cache"
+
import { ApiLang } from "@/constants/languages"
+import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { badRequestError } from "@/server/errors/trpc"
@@ -15,6 +18,8 @@ import { cache } from "@/utils/cache"
import { getHotelPageUrl } from "../contentstack/hotelPage/utils"
import { getVerifiedUser, parsedUser } from "../user/query"
+import { additionalDataSchema } from "./schemas/additionalData"
+import { meetingRoomsSchema } from "./schemas/meetingRoom"
import {
breakfastPackageInputSchema,
cityCoordinatesInputSchema,
@@ -54,133 +59,144 @@ import type { BedTypeSelection } from "@/types/components/hotelReservation/enter
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import { HotelTypeEnum } from "@/types/enums/hotelType"
import type { RequestOptionsWithOutBody } from "@/types/fetch"
-import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
import type { HotelData } from "@/types/hotel"
import type { HotelPageUrl } from "@/types/trpc/routers/contentstack/hotelPage"
-import { CityLocation } from "@/types/trpc/routers/hotel/locations"
-import { meetingRoomsSchema } from "./schemas/meetingRoom"
-import { env } from "@/env/server"
-import { additionalDataSchema } from "./schemas/additionalData"
+import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
+import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
export const getHotel = cache(
async (input: HotelInput, serviceToken: string) => {
- const { hotelId, isCardOnlyPayment, language } = input
- /**
- * Since API expects the params appended and not just
- * a comma separated string we need to initialize the
- * SearchParams with a sequence of pairs
- * (include=City&include=NearbyHotels&include=Restaurants etc.)
- **/
- const params = new URLSearchParams([
- ["hotelId", hotelId],
- ["include", "AdditionalData"],
- ["include", "City"],
- ["include", "NearbyHotels"],
- ["include", "Restaurants"],
- ["include", "RoomCategories"],
- ["language", toApiLang(language)],
- ])
- metrics.hotel.counter.add(1, {
- hotelId,
- language,
- })
- console.info(
- "api.hotels.hotelData start",
- JSON.stringify({ query: { hotelId, params } })
- )
+ const callable = unstable_cache(
+ async function (
+ hotelId: HotelInput["hotelId"],
+ language: HotelInput["language"],
+ isCardOnlyPayment?: HotelInput["isCardOnlyPayment"]
+ ) {
+ /**
+ * Since API expects the params appended and not just
+ * a comma separated string we need to initialize the
+ * SearchParams with a sequence of pairs
+ * (include=City&include=NearbyHotels&include=Restaurants etc.)
+ **/
+ const params = new URLSearchParams([
+ ["include", "AdditionalData"],
+ ["include", "City"],
+ ["include", "NearbyHotels"],
+ ["include", "Restaurants"],
+ ["include", "RoomCategories"],
+ ["language", toApiLang(language)],
+ ])
+ metrics.hotel.counter.add(1, {
+ hotelId,
+ language,
+ })
+ console.info(
+ "api.hotels.hotelData start",
+ JSON.stringify({ query: { hotelId, params } })
+ )
- const apiResponse = await api.get(
- api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
- {
- headers: {
- Authorization: `Bearer ${serviceToken}`,
- },
- // needs to clear default option as only
- // cache or next.revalidate is permitted
- cache: undefined,
- next: {
- revalidate: env.CACHE_TIME_HOTELDATA,
- tags: [`${language}:hotel:${hotelId}`],
- },
- },
- params
- )
-
- if (!apiResponse.ok) {
- const text = await apiResponse.text()
- console.log({ text })
- metrics.hotel.fail.add(1, {
- hotelId,
- language,
- error_type: "http_error",
- error: JSON.stringify({
- status: apiResponse.status,
- statusText: apiResponse.statusText,
- text,
- }),
- })
- console.error(
- "api.hotels.hotelData error",
- JSON.stringify({
- query: { hotelId, params },
- error: {
- status: apiResponse.status,
- statusText: apiResponse.statusText,
- text,
+ const apiResponse = await api.get(
+ api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
+ {
+ headers: {
+ Authorization: `Bearer ${serviceToken}`,
+ },
+ // needs to clear default option as only
+ // cache or next.revalidate is permitted
+ cache: undefined,
+ next: {
+ revalidate: env.CACHE_TIME_HOTELS,
+ tags: [`${language}:hotel:${hotelId}`],
+ },
},
+ params
+ )
+
+ if (!apiResponse.ok) {
+ const text = await apiResponse.text()
+ metrics.hotel.fail.add(1, {
+ hotelId,
+ language,
+ error_type: "http_error",
+ error: JSON.stringify({
+ status: apiResponse.status,
+ statusText: apiResponse.statusText,
+ text,
+ }),
+ })
+ console.error(
+ "api.hotels.hotelData error",
+ JSON.stringify({
+ query: { hotelId, params },
+ error: {
+ status: apiResponse.status,
+ statusText: apiResponse.statusText,
+ text,
+ },
+ })
+ )
+ return null
+ }
+
+ const apiJson = await apiResponse.json()
+ const validateHotelData = hotelSchema.safeParse(apiJson)
+
+ if (!validateHotelData.success) {
+ metrics.hotel.fail.add(1, {
+ hotelId,
+ language,
+ error_type: "validation_error",
+ error: JSON.stringify(validateHotelData.error),
+ })
+
+ console.error(
+ "api.hotels.hotelData validation error",
+ JSON.stringify({
+ query: { hotelId, params },
+ error: validateHotelData.error,
+ })
+ )
+ throw badRequestError()
+ }
+
+ metrics.hotel.success.add(1, {
+ hotelId,
+ language,
})
- )
- return null
- }
+ console.info(
+ "api.hotels.hotelData success",
+ JSON.stringify({
+ query: { hotelId, params: params },
+ })
+ )
+ const hotelData = validateHotelData.data
- const apiJson = await apiResponse.json()
- const validateHotelData = hotelSchema.safeParse(apiJson)
+ if (isCardOnlyPayment) {
+ hotelData.hotel.merchantInformationData.alternatePaymentOptions = []
+ }
- if (!validateHotelData.success) {
- metrics.hotel.fail.add(1, {
- hotelId,
- language,
- error_type: "validation_error",
- error: JSON.stringify(validateHotelData.error),
- })
+ const gallery = hotelData.additionalData?.gallery
+ if (gallery) {
+ const smallerImages = gallery.smallerImages
+ const hotelGalleryImages =
+ hotelData.hotel.hotelType === HotelTypeEnum.Signature
+ ? smallerImages.slice(0, 10)
+ : smallerImages.slice(0, 6)
+ hotelData.hotel.galleryImages = hotelGalleryImages
+ }
- console.error(
- "api.hotels.hotelData validation error",
- JSON.stringify({
- query: { hotelId, params },
- error: validateHotelData.error,
- })
- )
- throw badRequestError()
- }
-
- metrics.hotel.success.add(1, {
- hotelId,
- language,
- })
- console.info(
- "api.hotels.hotelData success",
- JSON.stringify({
- query: { hotelId, params: params },
- })
+ return hotelData
+ },
+ [`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`],
+ {
+ revalidate: env.CACHE_TIME_HOTELS,
+ tags: [
+ `${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`,
+ ],
+ }
)
- const hotelData = validateHotelData.data
- if (isCardOnlyPayment) {
- hotelData.hotel.merchantInformationData.alternatePaymentOptions = []
- }
-
- const gallery = hotelData.additionalData?.gallery
- if (gallery) {
- const smallerImages = gallery.smallerImages
- const hotelGalleryImages =
- hotelData.hotel.hotelType === HotelTypeEnum.Signature
- ? smallerImages.slice(0, 10)
- : smallerImages.slice(0, 6)
- hotelData.hotel.galleryImages = hotelGalleryImages
- }
-
- return hotelData
+ return callable(input.hotelId, input.language, input.isCardOnlyPayment)
}
)
@@ -731,9 +747,9 @@ export const hotelQueryRouter = router({
type: matchingRoom.mainBed.type,
extraBed: matchingRoom.fixedExtraBed
? {
- type: matchingRoom.fixedExtraBed.type,
- description: matchingRoom.fixedExtraBed.description,
- }
+ type: matchingRoom.fixedExtraBed.type,
+ description: matchingRoom.fixedExtraBed.description,
+ }
: undefined,
}
}
@@ -861,7 +877,10 @@ export const hotelQueryRouter = router({
}
const cityId = locations
- .filter((loc): loc is CityLocation => "type" in loc && loc.type === "cities")
+ .filter(
+ (loc): loc is CityLocation =>
+ "type" in loc && loc.type === "cities"
+ )
.find((loc) => loc.cityIdentifier === locationFilter.city)?.id
if (!cityId) {
@@ -973,7 +992,10 @@ export const hotelQueryRouter = router({
const hotels = await Promise.all(
hotelsToFetch.map(async (hotelId) => {
const [hotelData, url] = await Promise.all([
- getHotel({ hotelId, language }, ctx.serviceToken),
+ getHotel(
+ { hotelId, isCardOnlyPayment: false, language },
+ ctx.serviceToken
+ ),
getHotelPageUrl(language, hotelId),
])
@@ -1000,7 +1022,8 @@ export const hotelQueryRouter = router({
)
return hotels.filter(
- (hotel): hotel is { data: HotelData; url: HotelPageUrl } => !!hotel.data
+ (hotel): hotel is { data: HotelData; url: HotelPageUrl } =>
+ !!hotel.data
)
}),
}),
@@ -1096,7 +1119,7 @@ export const hotelQueryRouter = router({
Authorization: `Bearer ${ctx.serviceToken}`,
},
next: {
- revalidate: TWENTYFOUR_HOURS,
+ revalidate: env.CACHE_TIME_HOTELS,
},
}
diff --git a/server/routers/hotels/schemas/availability/occupancy.ts b/server/routers/hotels/schemas/availability/occupancy.ts
index c066008c3..c9b26924f 100644
--- a/server/routers/hotels/schemas/availability/occupancy.ts
+++ b/server/routers/hotels/schemas/availability/occupancy.ts
@@ -9,5 +9,5 @@ export const childrenSchema = z.object({
export const occupancySchema = z.object({
adults: z.number(),
- children: z.array(childrenSchema),
+ children: z.array(childrenSchema).default([]),
})
diff --git a/server/routers/hotels/schemas/hotel.ts b/server/routers/hotels/schemas/hotel.ts
index 6e5257097..103579e5e 100644
--- a/server/routers/hotels/schemas/hotel.ts
+++ b/server/routers/hotels/schemas/hotel.ts
@@ -1,5 +1,7 @@
import { z } from "zod"
+import { nullableNumberValidator } from "@/utils/zod/numberValidator"
+
import { addressSchema } from "./hotel/address"
import { contactInformationSchema } from "./hotel/contactInformation"
import { hotelContentSchema } from "./hotel/content"
@@ -17,8 +19,8 @@ import { rewardNightSchema } from "./hotel/rewardNight"
import { socialMediaSchema } from "./hotel/socialMedia"
import { specialAlertsSchema } from "./hotel/specialAlerts"
import { specialNeedGroupSchema } from "./hotel/specialNeedGroups"
-import { imageSchema } from "./image"
import { facilitySchema } from "./additionalData"
+import { imageSchema } from "./image"
export const attributesSchema = z.object({
accessibilityElevatorPitchText: z.string().optional(),
@@ -51,13 +53,23 @@ export const attributesSchema = z.object({
socialMedia: socialMediaSchema,
specialAlerts: specialAlertsSchema,
specialNeedGroups: z.array(specialNeedGroupSchema),
- vat: z.number(),
+ vat: nullableNumberValidator,
})
-export const includesSchema = z
+export const includedSchema = z
.array(includeSchema)
.default([])
- .transform((data) => data.filter((item) => !!item))
+ .transform((data) =>
+ data.filter((item) => {
+ if (item) {
+ if ("isPublished" in item && item.isPublished === false) {
+ return false
+ }
+ return true
+ }
+ return false
+ })
+ )
const relationshipSchema = z.object({
links: z.object({
diff --git a/server/routers/hotels/schemas/hotel/include/restaurants.ts b/server/routers/hotels/schemas/hotel/include/restaurants.ts
index 849e9fd4c..b55365bd1 100644
--- a/server/routers/hotels/schemas/hotel/include/restaurants.ts
+++ b/server/routers/hotels/schemas/hotel/include/restaurants.ts
@@ -2,70 +2,92 @@ import { z } from "zod"
import { imageSchema } from "@/server/routers/hotels/schemas/image"
+import {
+ nullableIntValidator,
+ nullableNumberValidator,
+} from "@/utils/zod/numberValidator"
+import {
+ nullableStringUrlValidator,
+ nullableStringValidator,
+} from "@/utils/zod/stringValidator"
+
+import { specialAlertsSchema } from "../specialAlerts"
+
import { CurrencyEnum } from "@/types/enums/currency"
+const descriptionSchema = z.object({
+ medium: nullableStringValidator,
+ short: nullableStringValidator,
+})
+
+const textSchema = z.object({
+ descriptions: descriptionSchema,
+ facilityInformation: nullableStringValidator,
+ meetingDescription: descriptionSchema.optional(),
+ surroundingInformation: nullableStringValidator,
+})
+
const contentSchema = z.object({
images: z.array(imageSchema).default([]),
- texts: z.object({
- descriptions: z.object({
- medium: z.string().default(""),
- short: z.string().default(""),
- }),
- }),
+ texts: textSchema,
+})
+
+const restaurantPriceSchema = z.object({
+ amount: nullableNumberValidator,
+ currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.SEK),
})
const externalBreakfastSchema = z.object({
isAvailable: z.boolean().default(false),
- localPriceForExternalGuests: z.object({
- amount: z.number().default(0),
- currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.SEK),
- }),
+ localPriceForExternalGuests: restaurantPriceSchema.optional(),
+ requestedPriceForExternalGuests: restaurantPriceSchema.optional(),
})
const menuItemSchema = z.object({
- name: z.string(),
- url: z.string().url(),
+ name: nullableStringValidator,
+ url: nullableStringUrlValidator,
})
-const daySchema = z.object({
+export const openingHoursDetailsSchema = z.object({
alwaysOpen: z.boolean().default(false),
- closingTime: z.string().default(""),
+ closingTime: nullableStringValidator,
isClosed: z.boolean().default(false),
- openingTime: z.string().default(""),
- sortOrder: z.number().int().default(0),
+ openingTime: nullableStringValidator,
+ sortOrder: nullableIntValidator,
+})
+
+export const openingHoursSchema = z.object({
+ friday: openingHoursDetailsSchema.optional(),
+ isActive: z.boolean().default(false),
+ monday: openingHoursDetailsSchema.optional(),
+ name: nullableStringValidator,
+ saturday: openingHoursDetailsSchema.optional(),
+ sunday: openingHoursDetailsSchema.optional(),
+ thursday: openingHoursDetailsSchema.optional(),
+ tuesday: openingHoursDetailsSchema.optional(),
+ wednesday: openingHoursDetailsSchema.optional(),
})
const openingDetailsSchema = z.object({
- alternateOpeningHours: z.object({
- isActive: z.boolean().default(false),
- }),
- openingHours: z.object({
- friday: daySchema,
- isActive: z.boolean().default(false),
- monday: daySchema,
- name: z.string().default(""),
- saturday: daySchema,
- sunday: daySchema,
- thursday: daySchema,
- tuesday: daySchema,
- wednesday: daySchema,
- }),
+ alternateOpeningHours: openingHoursSchema.optional(),
+ openingHours: openingHoursSchema,
+ ordinary: openingHoursSchema.optional(),
+ weekends: openingHoursSchema.optional(),
})
export const restaurantsSchema = z.object({
attributes: z.object({
- bookTableUrl: z.string().default(""),
+ bookTableUrl: nullableStringValidator,
content: contentSchema,
- // When using .email().default("") is not sufficent
- // so .optional also needs to be chained
email: z.string().email().optional(),
externalBreakfast: externalBreakfastSchema,
isPublished: z.boolean().default(false),
menus: z.array(menuItemSchema).default([]),
name: z.string().default(""),
openingDetails: z.array(openingDetailsSchema).default([]),
+ phoneNumber: z.string().optional(),
restaurantPage: z.boolean().default(false),
- specialAlerts: z.array(z.object({})).default([]),
+ specialAlerts: specialAlertsSchema,
}),
id: z.string(),
type: z.literal("restaurants"),
diff --git a/server/routers/hotels/schemas/hotel/specialAlerts.ts b/server/routers/hotels/schemas/hotel/specialAlerts.ts
index 8687a81a6..5d89fe936 100644
--- a/server/routers/hotels/schemas/hotel/specialAlerts.ts
+++ b/server/routers/hotels/schemas/hotel/specialAlerts.ts
@@ -2,15 +2,17 @@ import { z } from "zod"
import { dt } from "@/lib/dt"
+import { nullableStringValidator } from "@/utils/zod/stringValidator"
+
import { AlertTypeEnum } from "@/types/enums/alert"
const specialAlertSchema = z.object({
- description: z.string().optional(),
- displayInBookingFlow: z.boolean(),
- endDate: z.string().optional(),
- startDate: z.string().optional(),
- title: z.string().optional(),
- type: z.string(),
+ description: nullableStringValidator,
+ displayInBookingFlow: z.boolean().default(false),
+ endDate: nullableStringValidator,
+ startDate: nullableStringValidator,
+ title: nullableStringValidator,
+ type: nullableStringValidator,
})
export const specialAlertsSchema = z
diff --git a/server/routers/hotels/schemas/roomAvailability/configuration.ts b/server/routers/hotels/schemas/roomAvailability/configuration.ts
index 4d433917b..128512d7f 100644
--- a/server/routers/hotels/schemas/roomAvailability/configuration.ts
+++ b/server/routers/hotels/schemas/roomAvailability/configuration.ts
@@ -5,17 +5,19 @@ import { productSchema } from "./product"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
export const roomConfigurationSchema = z.object({
- features: z.array(
- z.object({
- inventory: z.number(),
- code: z.enum([
- RoomPackageCodeEnum.PET_ROOM,
- RoomPackageCodeEnum.ALLERGY_ROOM,
- RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
- ]),
- })
- ),
- products: z.array(productSchema),
+ features: z
+ .array(
+ z.object({
+ inventory: z.number(),
+ code: z.enum([
+ RoomPackageCodeEnum.PET_ROOM,
+ RoomPackageCodeEnum.ALLERGY_ROOM,
+ RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
+ ]),
+ })
+ )
+ .default([]),
+ products: z.array(productSchema).default([]),
roomsLeft: z.number(),
roomType: z.string(),
roomTypeCode: z.string(),
diff --git a/stores/select-rate/rate-selection.ts b/stores/select-rate/rate-selection.ts
index cd156a392..55b2bbcfd 100644
--- a/stores/select-rate/rate-selection.ts
+++ b/stores/select-rate/rate-selection.ts
@@ -4,17 +4,17 @@ import { calculateRoomSummary } from "./helper"
import type {
RoomPackageCodeEnum,
- RoomPackageData,
+ RoomPackages,
} from "@/types/components/hotelReservation/selectRate/roomFilter"
import type {
Rate,
RateCode,
} from "@/types/components/hotelReservation/selectRate/selectRate"
-import type { RoomConfiguration } from "@/server/routers/hotels/output"
+import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
export interface RateSummaryParams {
getFilteredRooms: (roomIndex: number) => RoomConfiguration[]
- availablePackages: RoomPackageData
+ availablePackages: RoomPackages
roomCategories: Array<{ name: string; roomTypes: Array<{ code: string }> }>
selectedPackagesByRoom: Record
}
diff --git a/stores/select-rate/room-filtering.ts b/stores/select-rate/room-filtering.ts
index 9454ce692..6d6ef3556 100644
--- a/stores/select-rate/room-filtering.ts
+++ b/stores/select-rate/room-filtering.ts
@@ -10,7 +10,7 @@ import type {
import type {
RoomConfiguration,
RoomsAvailability,
-} from "@/server/routers/hotels/output"
+} from "@/types/trpc/routers/hotel/roomAvailability"
interface RoomFilteringState {
selectedPackagesByRoom: Record
diff --git a/types/components/form/newPassword.ts b/types/components/form/newPassword.ts
index cd6aa0c50..a3cbe91da 100644
--- a/types/components/form/newPassword.ts
+++ b/types/components/form/newPassword.ts
@@ -1,3 +1,3 @@
-import { passwordValidators } from "@/utils/passwordValidator"
+import type { passwordValidators } from "@/utils/zod/passwordValidator"
export type PasswordValidatorKey = keyof typeof passwordValidators
diff --git a/types/components/hotelPage/sidepeek/amenities.ts b/types/components/hotelPage/sidepeek/amenities.ts
index e85b9ad19..1f591741e 100644
--- a/types/components/hotelPage/sidepeek/amenities.ts
+++ b/types/components/hotelPage/sidepeek/amenities.ts
@@ -1,9 +1,5 @@
-import type {
- Hotel,
- Restaurant,
- RestaurantOpeningHours,
-} from "@/types/hotel"
import type { ParkingAmenityProps } from "./parking"
+import type { Hotel, Restaurant, RestaurantOpeningHours } from "@/types/hotel"
export type AmenitiesSidePeekProps = {
amenitiesList: Hotel["detailedFacilities"]
diff --git a/types/components/hotelReservation/selectRate/roomCard.ts b/types/components/hotelReservation/selectRate/roomCard.ts
index 1b3366d52..c38c0b62c 100644
--- a/types/components/hotelReservation/selectRate/roomCard.ts
+++ b/types/components/hotelReservation/selectRate/roomCard.ts
@@ -8,7 +8,6 @@ import type {
import type { packagePriceSchema } from "@/server/routers/hotels/schemas/packages"
import type { RoomPriceSchema } from "./flexibilityOption"
import type { RoomPackageCodes, RoomPackages } from "./roomFilter"
-import type { RateCode } from "./selectRate"
export type RoomCardProps = {
hotelId: string
@@ -19,7 +18,6 @@ export type RoomCardProps = {
selectedPackages: RoomPackageCodes[]
roomListIndex: number
packages: RoomPackages | undefined
- handleSelectRate: React.Dispatch>
}
type RoomPackagePriceSchema = z.output
diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts
index cdae49d50..3f3512ea7 100644
--- a/types/components/hotelReservation/selectRate/roomSelection.ts
+++ b/types/components/hotelReservation/selectRate/roomSelection.ts
@@ -1,17 +1,15 @@
+import type { Room } from "@/types/hotel"
+import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
import type {
DefaultFilterOptions,
- RoomPackage,
RoomPackageCodes,
RoomPackages,
} from "./roomFilter"
-import type { Room } from "@/types/hotel"
-import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
-
export interface RoomTypeListProps {
roomsAvailability: RoomsAvailability
roomCategories: Room[]
- availablePackages: RoomPackage | undefined
+ availablePackages: RoomPackages | undefined
selectedPackages: RoomPackageCodes[]
hotelType: string | undefined
roomListIndex: number
@@ -27,7 +25,7 @@ export interface SelectRateProps {
export interface RoomSelectionPanelProps {
roomCategories: Room[]
- availablePackages: RoomPackage[]
+ availablePackages: RoomPackages
selectedPackages: RoomPackageCodes[]
hotelType: string | undefined
defaultPackages: DefaultFilterOptions[]
diff --git a/types/enums/currency.ts b/types/enums/currency.ts
new file mode 100644
index 000000000..580041361
--- /dev/null
+++ b/types/enums/currency.ts
@@ -0,0 +1,8 @@
+export enum CurrencyEnum {
+ DKK = "DKK",
+ EUR = "EUR",
+ NOK = "NOK",
+ PLN = "PLN",
+ SEK = "SEK",
+ Unknown = "Unknown",
+}
diff --git a/types/hotel.ts b/types/hotel.ts
index 3e46ec5b4..6a755e99c 100644
--- a/types/hotel.ts
+++ b/types/hotel.ts
@@ -7,18 +7,20 @@ import type { addressSchema } from "@/server/routers/hotels/schemas/hotel/addres
import type { hotelContentSchema } from "@/server/routers/hotels/schemas/hotel/content"
import type { detailedFacilitiesSchema } from "@/server/routers/hotels/schemas/hotel/detailedFacility"
import type { checkinSchema } from "@/server/routers/hotels/schemas/hotel/facts"
+import type { healthFacilitySchema } from "@/server/routers/hotels/schemas/hotel/healthFacilities"
import type { nearbyHotelsSchema } from "@/server/routers/hotels/schemas/hotel/include/nearbyHotels"
-import type { restaurantsSchema } from "@/server/routers/hotels/schemas/hotel/include/restaurants"
+import type {
+ openingHoursDetailsSchema,
+ openingHoursSchema,
+ restaurantsSchema,
+} from "@/server/routers/hotels/schemas/hotel/include/restaurants"
import type { transformRoomCategories } from "@/server/routers/hotels/schemas/hotel/include/roomCategories"
import type { locationSchema } from "@/server/routers/hotels/schemas/hotel/location"
import type { parkingSchema } from "@/server/routers/hotels/schemas/hotel/parking"
import type { pointOfInterestSchema } from "@/server/routers/hotels/schemas/hotel/poi"
import type { ratingsSchema } from "@/server/routers/hotels/schemas/hotel/rating"
import type { imageSchema } from "@/server/routers/hotels/schemas/image"
-import { restaurantOpeningHoursSchema } from "@/server/routers/hotels/schemas/restaurants"
-import { healthFacilitySchema } from "@/server/routers/hotels/schemas/hotel/healthFacilities"
import type {
- additionalDataSchema,
extraPageSchema,
facilitySchema,
transformAdditionalData,
@@ -45,9 +47,12 @@ export type NearbyHotel = Pick &
export type Parking = z.output
export type PointOfInterest = z.output
type RestaurantSchema = z.output
-export type RestaurantOpeningHours = z.output
export type Restaurant = Pick &
RestaurantSchema["attributes"]
+export type RestaurantOpeningHours = z.output
+export type RestaurantOpeningHoursDay = z.output<
+ typeof openingHoursDetailsSchema
+>
export type Room = ReturnType
export type HotelMapContentProps = {
diff --git a/types/trpc/routers/hotel/availability.ts b/types/trpc/routers/hotel/availability.ts
index 90f1ccb07..2aaba9363 100644
--- a/types/trpc/routers/hotel/availability.ts
+++ b/types/trpc/routers/hotel/availability.ts
@@ -7,3 +7,6 @@ import type { productTypePriceSchema } from "@/server/routers/hotels/schemas/pro
export type HotelsAvailability = z.output
export type ProductType = z.output
export type ProductTypePrices = z.output
+
+export type HotelsAvailabilityItem =
+ HotelsAvailability["data"][number]["attributes"]
diff --git a/utils/zod/numberValidator.ts b/utils/zod/numberValidator.ts
new file mode 100644
index 000000000..7b6927bc9
--- /dev/null
+++ b/utils/zod/numberValidator.ts
@@ -0,0 +1,12 @@
+import { z } from "zod"
+
+export const nullableNumberValidator = z
+ .number()
+ .nullish()
+ .transform((num) => (typeof num === "number" ? num : 0))
+
+export const nullableIntValidator = z
+ .number()
+ .int()
+ .nullish()
+ .transform((num) => (typeof num === "number" ? num : 0))
diff --git a/utils/passwordValidator.ts b/utils/zod/passwordValidator.ts
similarity index 100%
rename from utils/passwordValidator.ts
rename to utils/zod/passwordValidator.ts
diff --git a/utils/phoneValidator.ts b/utils/zod/phoneValidator.ts
similarity index 100%
rename from utils/phoneValidator.ts
rename to utils/zod/phoneValidator.ts
diff --git a/utils/zod/stringValidator.ts b/utils/zod/stringValidator.ts
new file mode 100644
index 000000000..f7adfca42
--- /dev/null
+++ b/utils/zod/stringValidator.ts
@@ -0,0 +1,12 @@
+import { z } from "zod"
+
+export const nullableStringValidator = z
+ .string()
+ .nullish()
+ .transform((str) => (str ? str : ""))
+
+export const nullableStringUrlValidator = z
+ .string()
+ .url()
+ .nullish()
+ .transform((str) => (str ? str : ""))