()
+ const [chosenBreakfast, setChosenBreakfast] = useState<
+ BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST
+ >()
const intl = useIntl()
const lang = useLang()
const { fromDate, toDate, bedType, breakfast } = useEnterDetailsStore(
(state) => ({
fromDate: state.roomData.fromDate,
toDate: state.roomData.toDate,
- rooms: state.roomData.room,
- hotel: state.roomData.hotel,
bedType: state.userData.bedType,
breakfast: state.userData.breakfast,
})
@@ -55,10 +57,9 @@ export default function Summary({
useEffect(() => {
setChosenBed(bedType)
- if (breakfast === BreakfastPackageEnum.NO_BREAKFAST) {
- setCosenBreakfast("No breakfast")
- } else if (breakfast) {
- setCosenBreakfast("Breakfast buffet")
+
+ if (breakfast) {
+ setChosenBreakfast(breakfast)
}
}, [bedType, breakfast])
@@ -80,7 +81,10 @@ export default function Summary({
{intl.formatMessage(
{ id: "{amount} {currency}" },
- { amount: room.localPrice, currency: "SEK" }
+ {
+ amount: formatNumber(parseInt(room.localPrice.price ?? "0")),
+ currency: room.localPrice.currency,
+ }
)}
@@ -118,24 +122,41 @@ export default function Summary({
{intl.formatMessage(
{ id: "{amount} {currency}" },
- { amount: "0", currency: "SEK" }
+ { amount: "0", currency: room.localPrice.currency }
)}
) : null}
{chosenBreakfast ? (
-
-
- {intl.formatMessage({ id: chosenBreakfast })}
-
-
- {intl.formatMessage(
- { id: "{amount} {currency}" },
- { amount: "0", currency: "SEK" }
- )}
-
-
+ chosenBreakfast === BreakfastPackageEnum.NO_BREAKFAST ? (
+
+
+ {intl.formatMessage({ id: "No breakfast" })}
+
+
+ {intl.formatMessage(
+ { id: "{amount} {currency}" },
+ { amount: "0", currency: room.localPrice.currency }
+ )}
+
+
+ ) : (
+
+
+ {intl.formatMessage({ id: "Breakfast buffet" })}
+
+
+ {intl.formatMessage(
+ { id: "{amount} {currency}" },
+ {
+ amount: chosenBreakfast.totalPrice,
+ currency: chosenBreakfast.currency,
+ }
+ )}
+
+
+ )
) : null}
@@ -156,14 +177,20 @@ export default function Summary({
{intl.formatMessage(
{ id: "{amount} {currency}" },
- { amount: room.localPrice, currency: "SEK" } // TODO: calculate total price
+ {
+ amount: formatNumber(parseInt(room.localPrice.price ?? "0")),
+ currency: room.localPrice.currency,
+ }
)}
{intl.formatMessage({ id: "Approx." })}{" "}
{intl.formatMessage(
{ id: "{amount} {currency}" },
- { amount: room.euroPrice, currency: "EUR" }
+ {
+ amount: formatNumber(parseInt(room.euroPrice.price ?? "0")),
+ currency: room.euroPrice.currency,
+ }
)}
diff --git a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts
index 8fe8476d3..7d24e46d7 100644
--- a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts
+++ b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts
@@ -23,10 +23,15 @@ interface ListCardProps extends BaseCardProps {
interface TextCardProps extends BaseCardProps {
list?: never
- text?: React.ReactNode
+ text: React.ReactNode
}
-export type CardProps = ListCardProps | TextCardProps
+interface CleanCardProps extends BaseCardProps {
+ list?: never
+ text?: never
+}
+
+export type CardProps = ListCardProps | TextCardProps | CleanCardProps
export type CheckboxProps =
| Omit
@@ -34,6 +39,7 @@ export type CheckboxProps =
export type RadioProps =
| Omit
| Omit
+ | Omit
export interface ListProps extends Pick {
list?: ListCardProps["list"]
diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts
index 547ff7874..63cde2d84 100644
--- a/lib/trpc/memoizedRequests/index.ts
+++ b/lib/trpc/memoizedRequests/index.ts
@@ -3,6 +3,7 @@ import { cache } from "react"
import { Lang } from "@/constants/languages"
import {
GetRoomsAvailabilityInput,
+ GetSelectedRoomAvailabilityInput,
HotelIncludeEnum,
} from "@/server/routers/hotels/input"
@@ -95,6 +96,14 @@ export const getRoomAvailability = cache(
}
)
+export const getSelectedRoomAvailability = cache(
+ async function getMemoizedRoomAvailability(
+ args: GetSelectedRoomAvailabilityInput
+ ) {
+ return serverClient().hotel.availability.room(args)
+ }
+)
+
export const getFooter = cache(async function getMemoizedFooter() {
return serverClient().contentstack.base.footer()
})
diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts
index ff8368b2a..bfff4cd97 100644
--- a/server/routers/hotels/input.ts
+++ b/server/routers/hotels/input.ts
@@ -29,6 +29,23 @@ export const getRoomsAvailabilityInputSchema = z.object({
rateCode: z.string().optional(),
})
+export const getSelectedRoomAvailabilityInputSchema = z.object({
+ hotelId: z.number(),
+ roomStayStartDate: z.string(),
+ roomStayEndDate: z.string(),
+ adults: z.number(),
+ children: z.string().optional(),
+ promotionCode: z.string().optional(),
+ reservationProfileType: z.string().optional().default(""),
+ attachedProfileId: z.string().optional().default(""),
+ rateCode: z.string(),
+ roomTypeCode: z.string(),
+})
+
+export type GetSelectedRoomAvailabilityInput = z.input<
+ typeof getSelectedRoomAvailabilityInputSchema
+>
+
export type GetRoomsAvailabilityInput = z.input<
typeof getRoomsAvailabilityInputSchema
>
diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts
index 4aacd6ff2..b4dc240a5 100644
--- a/server/routers/hotels/query.ts
+++ b/server/routers/hotels/query.ts
@@ -37,6 +37,7 @@ import {
getHotelsAvailabilityInputSchema,
getRatesInputSchema,
getRoomsAvailabilityInputSchema,
+ getSelectedRoomAvailabilityInputSchema,
} from "./input"
import {
breakfastPackagesSchema,
@@ -93,6 +94,16 @@ const roomsAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.rooms-fail"
)
+const selectedRoomAvailabilityCounter = meter.createCounter(
+ "trpc.hotel.availability.room"
+)
+const selectedRoomAvailabilitySuccessCounter = meter.createCounter(
+ "trpc.hotel.availability.room-success"
+)
+const selectedRoomAvailabilityFailCounter = meter.createCounter(
+ "trpc.hotel.availability.room-fail"
+)
+
const breakfastPackagesCounter = meter.createCounter("trpc.package.breakfast")
const breakfastPackagesSuccessCounter = meter.createCounter(
"trpc.package.breakfast-success"
@@ -545,6 +556,161 @@ export const hotelQueryRouter = router({
return validateAvailabilityData.data
}),
+ room: serviceProcedure
+ .input(getSelectedRoomAvailabilityInputSchema)
+ .query(async ({ input, ctx }) => {
+ const {
+ hotelId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ promotionCode,
+ reservationProfileType,
+ attachedProfileId,
+ rateCode,
+ roomTypeCode,
+ } = input
+
+ const params: Record = {
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ ...(children && { children }),
+ promotionCode,
+ reservationProfileType,
+ attachedProfileId,
+ }
+
+ selectedRoomAvailabilityCounter.add(1, {
+ hotelId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ promotionCode,
+ reservationProfileType,
+ })
+ console.info(
+ "api.hotels.selectedRoomAvailability start",
+ JSON.stringify({ query: { hotelId, params } })
+ )
+ const apiResponseAvailability = await api.get(
+ api.endpoints.v1.Availability.hotel(hotelId.toString()),
+ {
+ headers: {
+ Authorization: `Bearer ${ctx.serviceToken}`,
+ },
+ },
+ params
+ )
+
+ if (!apiResponseAvailability.ok) {
+ const text = await apiResponseAvailability.text()
+ selectedRoomAvailabilityFailCounter.add(1, {
+ hotelId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ promotionCode,
+ reservationProfileType,
+ error_type: "http_error",
+ error: JSON.stringify({
+ status: apiResponseAvailability.status,
+ statusText: apiResponseAvailability.statusText,
+ text,
+ }),
+ })
+ console.error(
+ "api.hotels.selectedRoomAvailability error",
+ JSON.stringify({
+ query: { hotelId, params },
+ error: {
+ status: apiResponseAvailability.status,
+ statusText: apiResponseAvailability.statusText,
+ text,
+ },
+ })
+ )
+ return null
+ }
+ const apiJsonAvailability = await apiResponseAvailability.json()
+ const validateAvailabilityData =
+ getRoomsAvailabilitySchema.safeParse(apiJsonAvailability)
+ if (!validateAvailabilityData.success) {
+ selectedRoomAvailabilityFailCounter.add(1, {
+ hotelId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ promotionCode,
+ reservationProfileType,
+ error_type: "validation_error",
+ error: JSON.stringify(validateAvailabilityData.error),
+ })
+ console.error(
+ "api.hotels.selectedRoomAvailability validation error",
+ JSON.stringify({
+ query: { hotelId, params },
+ error: validateAvailabilityData.error,
+ })
+ )
+ throw badRequestError()
+ }
+
+ const selectedRoom = validateAvailabilityData.data.roomConfigurations
+ .filter((room) => room.status === "Available")
+ .find((room) => room.roomTypeCode === roomTypeCode)
+
+ if (!selectedRoom) {
+ console.error("No matching room found")
+ return null
+ }
+
+ const memberRate = selectedRoom.products.find(
+ (rate) => rate.productType.member?.rateCode === rateCode
+ )?.productType.member
+
+ const publicRate = selectedRoom.products.find(
+ (rate) => rate.productType.public?.rateCode === rateCode
+ )?.productType.public
+
+ const mustBeGuaranteed =
+ validateAvailabilityData.data.rateDefinitions.filter(
+ (rate) => rate.rateCode === rateCode
+ )[0].mustBeGuaranteed
+
+ const cancellationText =
+ validateAvailabilityData.data.rateDefinitions.find(
+ (rate) => rate.rateCode === rateCode
+ )?.cancellationText ?? ""
+
+ selectedRoomAvailabilitySuccessCounter.add(1, {
+ hotelId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ promotionCode,
+ reservationProfileType,
+ })
+ console.info(
+ "api.hotels.selectedRoomAvailability success",
+ JSON.stringify({
+ query: { hotelId, params: params },
+ })
+ )
+
+ return {
+ selectedRoom,
+ mustBeGuaranteed,
+ cancellationText,
+ memberRate,
+ publicRate,
+ }
+ }),
}),
rates: router({
get: publicProcedure
diff --git a/types/components/hotelReservation/enterDetails/bedType.ts b/types/components/hotelReservation/enterDetails/bedType.ts
index c4e6e4ff0..35f41ee27 100644
--- a/types/components/hotelReservation/enterDetails/bedType.ts
+++ b/types/components/hotelReservation/enterDetails/bedType.ts
@@ -2,4 +2,16 @@ import { z } from "zod"
import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema"
+type BedType = {
+ description: string
+ size: {
+ min: number
+ max: number
+ }
+ value: string
+}
+export type BedTypeProps = {
+ bedTypes: BedType[]
+}
+
export interface BedTypeSchema extends z.output {}
diff --git a/types/components/hotelReservation/enterDetails/bookingData.ts b/types/components/hotelReservation/enterDetails/bookingData.ts
index 058d67990..249bb466d 100644
--- a/types/components/hotelReservation/enterDetails/bookingData.ts
+++ b/types/components/hotelReservation/enterDetails/bookingData.ts
@@ -16,10 +16,15 @@ export interface BookingData {
room: Room[]
}
+type Price = {
+ price?: string
+ currency?: string
+}
+
export type RoomsData = {
roomType: string
- localPrice: string
- euroPrice: string
+ localPrice: Price
+ euroPrice: Price
adults: number
children?: Child[]
cancellationText: string