fix: move crunching of data to trpc layer
This commit is contained in:
@@ -1,13 +1,12 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getHotelData,
|
|
||||||
getProfileSafely,
|
getProfileSafely,
|
||||||
getRoomAvailability,
|
getSelectedRoomAvailability,
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
} from "@/lib/trpc/memoizedRequests"
|
||||||
import { HotelIncludeEnum } from "@/server/routers/hotels/input"
|
|
||||||
|
|
||||||
import Summary from "@/components/HotelReservation/EnterDetails/Summary"
|
import Summary from "@/components/HotelReservation/EnterDetails/Summary"
|
||||||
import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||||
import { formatNumber } from "@/utils/format"
|
|
||||||
|
|
||||||
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import { LangParams, PageArgs, SearchParams } from "@/types/params"
|
import { LangParams, PageArgs, SearchParams } from "@/types/params"
|
||||||
@@ -20,68 +19,61 @@ export default async function SummaryPage({
|
|||||||
const { hotel, adults, children, roomTypeCode, rateCode, fromDate, toDate } =
|
const { hotel, adults, children, roomTypeCode, rateCode, fromDate, toDate } =
|
||||||
getQueryParamsForEnterDetails(selectRoomParams)
|
getQueryParamsForEnterDetails(selectRoomParams)
|
||||||
|
|
||||||
const [user, hotelData, availability] = await Promise.all([
|
if (!roomTypeCode || !rateCode) {
|
||||||
|
console.log("No roomTypeCode or rateCode")
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
const [user, availability] = await Promise.all([
|
||||||
getProfileSafely(),
|
getProfileSafely(),
|
||||||
getHotelData({
|
getSelectedRoomAvailability({
|
||||||
hotelId: hotel,
|
|
||||||
language: params.lang,
|
|
||||||
include: [HotelIncludeEnum.RoomCategories],
|
|
||||||
}),
|
|
||||||
getRoomAvailability({
|
|
||||||
hotelId: parseInt(hotel),
|
hotelId: parseInt(hotel),
|
||||||
adults,
|
adults,
|
||||||
children,
|
children,
|
||||||
roomStayStartDate: fromDate,
|
roomStayStartDate: fromDate,
|
||||||
roomStayEndDate: toDate,
|
roomStayEndDate: toDate,
|
||||||
|
rateCode,
|
||||||
|
roomTypeCode,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (!hotelData?.data || !hotelData?.included || !availability) {
|
if (!availability) {
|
||||||
console.error("No hotel or availability data", hotelData, availability)
|
console.error("No hotel or availability data", availability)
|
||||||
// TODO: handle this case
|
// TODO: handle this case
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancellationText =
|
|
||||||
availability?.rateDefinitions.find((rate) => rate.rateCode === rateCode)
|
|
||||||
?.cancellationText ?? ""
|
|
||||||
const chosenRoom = availability.roomConfigurations.find(
|
|
||||||
(availRoom) => availRoom.roomTypeCode === roomTypeCode
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!chosenRoom) {
|
|
||||||
// TODO: handle this case
|
|
||||||
console.error("No chosen room", chosenRoom)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const memberRate = chosenRoom.products.find(
|
|
||||||
(rate) => rate.productType.member?.rateCode === rateCode
|
|
||||||
)?.productType.member
|
|
||||||
|
|
||||||
const publicRate = chosenRoom.products.find(
|
|
||||||
(rate) => rate.productType.public?.rateCode === rateCode
|
|
||||||
)?.productType.public
|
|
||||||
|
|
||||||
const prices = user
|
const prices = user
|
||||||
? {
|
? {
|
||||||
local: memberRate?.localPrice.pricePerStay,
|
local: {
|
||||||
euro: memberRate?.requestedPrice?.pricePerStay,
|
price: availability.memberRate?.localPrice.pricePerStay,
|
||||||
|
currency: availability.memberRate?.localPrice.currency,
|
||||||
|
},
|
||||||
|
euro: {
|
||||||
|
price: availability.memberRate?.requestedPrice?.pricePerStay,
|
||||||
|
currency: availability.memberRate?.requestedPrice?.currency,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
local: publicRate?.localPrice.pricePerStay,
|
local: {
|
||||||
euro: publicRate?.requestedPrice?.pricePerStay,
|
price: availability.publicRate?.localPrice.pricePerStay,
|
||||||
|
currency: availability.publicRate?.localPrice.currency,
|
||||||
|
},
|
||||||
|
euro: {
|
||||||
|
price: availability.publicRate?.requestedPrice?.pricePerStay,
|
||||||
|
currency: availability.publicRate?.requestedPrice?.currency,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Summary
|
<Summary
|
||||||
isMember={!!user}
|
isMember={!!user}
|
||||||
room={{
|
room={{
|
||||||
roomType: chosenRoom.roomType,
|
roomType: availability.selectedRoom.roomType,
|
||||||
localPrice: formatNumber(parseInt(prices.local ?? "0")),
|
localPrice: prices.local,
|
||||||
euroPrice: formatNumber(parseInt(prices.euro ?? "0")),
|
euroPrice: prices.euro,
|
||||||
adults,
|
adults,
|
||||||
cancellationText,
|
cancellationText: availability.cancellationText,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getHotelData,
|
getHotelData,
|
||||||
getProfileSafely,
|
getProfileSafely,
|
||||||
getRoomAvailability,
|
getRoomAvailability,
|
||||||
|
getSelectedRoomAvailability,
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
} from "@/lib/trpc/memoizedRequests"
|
||||||
import { HotelIncludeEnum } from "@/server/routers/hotels/input"
|
import { HotelIncludeEnum } from "@/server/routers/hotels/input"
|
||||||
|
|
||||||
@@ -36,9 +37,7 @@ export default async function StepPage({
|
|||||||
searchParams,
|
searchParams,
|
||||||
}: PageArgs<LangParams & { step: StepEnum }, SelectRateSearchParams>) {
|
}: PageArgs<LangParams & { step: StepEnum }, SelectRateSearchParams>) {
|
||||||
const { lang } = params
|
const { lang } = params
|
||||||
if (!searchParams.hotel) {
|
|
||||||
redirect(`/${lang}`)
|
|
||||||
}
|
|
||||||
void getBreakfastPackages(searchParams.hotel)
|
void getBreakfastPackages(searchParams.hotel)
|
||||||
|
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
@@ -62,24 +61,37 @@ export default async function StepPage({
|
|||||||
rateCode
|
rateCode
|
||||||
})
|
})
|
||||||
|
|
||||||
const hotelData = await getHotelData({
|
|
||||||
hotelId,
|
|
||||||
language: lang,
|
|
||||||
include: [HotelIncludeEnum.RoomCategories],
|
|
||||||
})
|
|
||||||
|
|
||||||
const user = await getProfileSafely()
|
if (!rateCode || !roomTypeCode) {
|
||||||
const savedCreditCards = await getCreditCardsSafely()
|
return notFound()
|
||||||
const breakfastPackages = await getBreakfastPackages(searchParams.hotel)
|
}
|
||||||
|
|
||||||
const roomAvailability = await getRoomAvailability({
|
const [
|
||||||
hotelId: parseInt(hotelId),
|
hotelData,
|
||||||
adults,
|
user,
|
||||||
children,
|
savedCreditCards,
|
||||||
roomStayStartDate: fromDate,
|
breakfastPackages,
|
||||||
roomStayEndDate: toDate,
|
roomAvailability,
|
||||||
rateCode
|
] = await Promise.all([
|
||||||
})
|
getHotelData({
|
||||||
|
hotelId,
|
||||||
|
language: lang,
|
||||||
|
include: [HotelIncludeEnum.RoomCategories],
|
||||||
|
}),
|
||||||
|
|
||||||
|
getProfileSafely(),
|
||||||
|
getCreditCardsSafely(),
|
||||||
|
getBreakfastPackages(searchParams.hotel),
|
||||||
|
getSelectedRoomAvailability({
|
||||||
|
hotelId: parseInt(searchParams.hotel),
|
||||||
|
adults,
|
||||||
|
children,
|
||||||
|
roomStayStartDate: fromDate,
|
||||||
|
roomStayEndDate: toDate,
|
||||||
|
rateCode,
|
||||||
|
roomTypeCode,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
if (!isValidStep(params.step) || !hotelData || !roomAvailability) {
|
if (!isValidStep(params.step) || !hotelData || !roomAvailability) {
|
||||||
return notFound()
|
return notFound()
|
||||||
@@ -100,10 +112,8 @@ export default async function StepPage({
|
|||||||
id: "Select payment method",
|
id: "Select payment method",
|
||||||
})
|
})
|
||||||
|
|
||||||
const availableRoom = roomAvailability?.roomConfigurations
|
const availableRoom = roomAvailability.selectedRoom?.roomType
|
||||||
.filter((room) => room.status === "Available")
|
const bedTypes = hotelData.included
|
||||||
.find((room) => room.roomTypeCode === roomTypeCode)?.roomType
|
|
||||||
const roomTypes = hotelData.included
|
|
||||||
?.find((room) => room.name === availableRoom)
|
?.find((room) => room.name === availableRoom)
|
||||||
?.roomTypes.map((room) => ({
|
?.roomTypes.map((room) => ({
|
||||||
description: room.mainBed.description,
|
description: room.mainBed.description,
|
||||||
@@ -116,13 +126,13 @@ export default async function StepPage({
|
|||||||
<HistoryStateManager />
|
<HistoryStateManager />
|
||||||
|
|
||||||
{/* TODO: How to handle no beds found? */}
|
{/* TODO: How to handle no beds found? */}
|
||||||
{roomTypes ? (
|
{bedTypes ? (
|
||||||
<SectionAccordion
|
<SectionAccordion
|
||||||
header="Select bed"
|
header="Select bed"
|
||||||
step={StepEnum.selectBed}
|
step={StepEnum.selectBed}
|
||||||
label={intl.formatMessage({ id: "Request bedtype" })}
|
label={intl.formatMessage({ id: "Request bedtype" })}
|
||||||
>
|
>
|
||||||
<BedType roomTypes={roomTypes} />
|
<BedType bedTypes={bedTypes} />
|
||||||
</SectionAccordion>
|
</SectionAccordion>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -14,28 +14,20 @@ import { bedTypeSchema } from "./schema"
|
|||||||
|
|
||||||
import styles from "./bedOptions.module.css"
|
import styles from "./bedOptions.module.css"
|
||||||
|
|
||||||
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
|
import type {
|
||||||
|
BedTypeProps,
|
||||||
|
BedTypeSchema,
|
||||||
|
} from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||||
|
|
||||||
export default function BedType({
|
export default function BedType({ bedTypes }: BedTypeProps) {
|
||||||
roomTypes,
|
|
||||||
}: {
|
|
||||||
roomTypes: {
|
|
||||||
description: string
|
|
||||||
size: {
|
|
||||||
min: number
|
|
||||||
max: number
|
|
||||||
}
|
|
||||||
value: string
|
|
||||||
}[]
|
|
||||||
}) {
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const bedType = useEnterDetailsStore((state) => state.userData.bedType)
|
const bedType = useEnterDetailsStore((state) => state.userData.bedType)
|
||||||
|
|
||||||
const methods = useForm<BedTypeSchema>({
|
const methods = useForm<BedTypeSchema>({
|
||||||
defaultValues: bedType
|
defaultValues: bedType
|
||||||
? {
|
? {
|
||||||
bedType,
|
bedType,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
criteriaMode: "all",
|
criteriaMode: "all",
|
||||||
mode: "all",
|
mode: "all",
|
||||||
@@ -64,7 +56,7 @@ export default function BedType({
|
|||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form className={styles.form} onSubmit={methods.handleSubmit(onSubmit)}>
|
<form className={styles.form} onSubmit={methods.handleSubmit(onSubmit)}>
|
||||||
{roomTypes.map((roomType) => {
|
{bedTypes.map((roomType) => {
|
||||||
const width =
|
const width =
|
||||||
roomType.size.max === roomType.size.min
|
roomType.size.max === roomType.size.min
|
||||||
? `${roomType.size.min} cm`
|
? `${roomType.size.min} cm`
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
|||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
import { formatNumber } from "@/utils/format"
|
||||||
|
|
||||||
import styles from "./summary.module.css"
|
import styles from "./summary.module.css"
|
||||||
|
|
||||||
import { RoomsData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
import { RoomsData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||||
|
import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||||
|
|
||||||
export default function Summary({
|
export default function Summary({
|
||||||
@@ -27,15 +29,15 @@ export default function Summary({
|
|||||||
room: RoomsData
|
room: RoomsData
|
||||||
}) {
|
}) {
|
||||||
const [chosenBed, setChosenBed] = useState<string>()
|
const [chosenBed, setChosenBed] = useState<string>()
|
||||||
const [chosenBreakfast, setCosenBreakfast] = useState<string>()
|
const [chosenBreakfast, setChosenBreakfast] = useState<
|
||||||
|
BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST
|
||||||
|
>()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const { fromDate, toDate, bedType, breakfast } = useEnterDetailsStore(
|
const { fromDate, toDate, bedType, breakfast } = useEnterDetailsStore(
|
||||||
(state) => ({
|
(state) => ({
|
||||||
fromDate: state.roomData.fromDate,
|
fromDate: state.roomData.fromDate,
|
||||||
toDate: state.roomData.toDate,
|
toDate: state.roomData.toDate,
|
||||||
rooms: state.roomData.room,
|
|
||||||
hotel: state.roomData.hotel,
|
|
||||||
bedType: state.userData.bedType,
|
bedType: state.userData.bedType,
|
||||||
breakfast: state.userData.breakfast,
|
breakfast: state.userData.breakfast,
|
||||||
})
|
})
|
||||||
@@ -55,10 +57,9 @@ export default function Summary({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChosenBed(bedType)
|
setChosenBed(bedType)
|
||||||
if (breakfast === BreakfastPackageEnum.NO_BREAKFAST) {
|
|
||||||
setCosenBreakfast("No breakfast")
|
if (breakfast) {
|
||||||
} else if (breakfast) {
|
setChosenBreakfast(breakfast)
|
||||||
setCosenBreakfast("Breakfast buffet")
|
|
||||||
}
|
}
|
||||||
}, [bedType, breakfast])
|
}, [bedType, breakfast])
|
||||||
|
|
||||||
@@ -80,7 +81,10 @@ export default function Summary({
|
|||||||
<Caption color={color}>
|
<Caption color={color}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: room.localPrice, currency: "SEK" }
|
{
|
||||||
|
amount: formatNumber(parseInt(room.localPrice.price ?? "0")),
|
||||||
|
currency: room.localPrice.currency,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,24 +122,41 @@ export default function Summary({
|
|||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: "0", currency: "SEK" }
|
{ amount: "0", currency: room.localPrice.currency }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{chosenBreakfast ? (
|
{chosenBreakfast ? (
|
||||||
<div className={styles.entry}>
|
chosenBreakfast === BreakfastPackageEnum.NO_BREAKFAST ? (
|
||||||
<Body color="textHighContrast">
|
<div className={styles.entry}>
|
||||||
{intl.formatMessage({ id: chosenBreakfast })}
|
<Body color="textHighContrast">
|
||||||
</Body>
|
{intl.formatMessage({ id: "No breakfast" })}
|
||||||
<Caption color="uiTextMediumContrast">
|
</Body>
|
||||||
{intl.formatMessage(
|
<Caption color="uiTextMediumContrast">
|
||||||
{ id: "{amount} {currency}" },
|
{intl.formatMessage(
|
||||||
{ amount: "0", currency: "SEK" }
|
{ id: "{amount} {currency}" },
|
||||||
)}
|
{ amount: "0", currency: room.localPrice.currency }
|
||||||
</Caption>
|
)}
|
||||||
</div>
|
</Caption>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Body color="textHighContrast">
|
||||||
|
{intl.formatMessage({ id: "Breakfast buffet" })}
|
||||||
|
</Body>
|
||||||
|
<Caption color="uiTextMediumContrast">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{amount} {currency}" },
|
||||||
|
{
|
||||||
|
amount: chosenBreakfast.totalPrice,
|
||||||
|
currency: chosenBreakfast.currency,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
@@ -156,14 +177,20 @@ export default function Summary({
|
|||||||
<Body textTransform="bold">
|
<Body textTransform="bold">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: room.localPrice, currency: "SEK" } // TODO: calculate total price
|
{
|
||||||
|
amount: formatNumber(parseInt(room.localPrice.price ?? "0")),
|
||||||
|
currency: room.localPrice.currency,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage({ id: "Approx." })}{" "}
|
{intl.formatMessage({ id: "Approx." })}{" "}
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: room.euroPrice, currency: "EUR" }
|
{
|
||||||
|
amount: formatNumber(parseInt(room.euroPrice.price ?? "0")),
|
||||||
|
currency: room.euroPrice.currency,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ interface ListCardProps extends BaseCardProps {
|
|||||||
|
|
||||||
interface TextCardProps extends BaseCardProps {
|
interface TextCardProps extends BaseCardProps {
|
||||||
list?: never
|
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 =
|
export type CheckboxProps =
|
||||||
| Omit<ListCardProps, "type">
|
| Omit<ListCardProps, "type">
|
||||||
@@ -34,6 +39,7 @@ export type CheckboxProps =
|
|||||||
export type RadioProps =
|
export type RadioProps =
|
||||||
| Omit<ListCardProps, "type">
|
| Omit<ListCardProps, "type">
|
||||||
| Omit<TextCardProps, "type">
|
| Omit<TextCardProps, "type">
|
||||||
|
| Omit<CleanCardProps, "type">
|
||||||
|
|
||||||
export interface ListProps extends Pick<ListCardProps, "declined"> {
|
export interface ListProps extends Pick<ListCardProps, "declined"> {
|
||||||
list?: ListCardProps["list"]
|
list?: ListCardProps["list"]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { cache } from "react"
|
|||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
import {
|
import {
|
||||||
GetRoomsAvailabilityInput,
|
GetRoomsAvailabilityInput,
|
||||||
|
GetSelectedRoomAvailabilityInput,
|
||||||
HotelIncludeEnum,
|
HotelIncludeEnum,
|
||||||
} from "@/server/routers/hotels/input"
|
} 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() {
|
export const getFooter = cache(async function getMemoizedFooter() {
|
||||||
return serverClient().contentstack.base.footer()
|
return serverClient().contentstack.base.footer()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,6 +29,23 @@ export const getRoomsAvailabilityInputSchema = z.object({
|
|||||||
rateCode: z.string().optional(),
|
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<
|
export type GetRoomsAvailabilityInput = z.input<
|
||||||
typeof getRoomsAvailabilityInputSchema
|
typeof getRoomsAvailabilityInputSchema
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
getHotelsAvailabilityInputSchema,
|
getHotelsAvailabilityInputSchema,
|
||||||
getRatesInputSchema,
|
getRatesInputSchema,
|
||||||
getRoomsAvailabilityInputSchema,
|
getRoomsAvailabilityInputSchema,
|
||||||
|
getSelectedRoomAvailabilityInputSchema,
|
||||||
} from "./input"
|
} from "./input"
|
||||||
import {
|
import {
|
||||||
breakfastPackagesSchema,
|
breakfastPackagesSchema,
|
||||||
@@ -93,6 +94,16 @@ const roomsAvailabilityFailCounter = meter.createCounter(
|
|||||||
"trpc.hotel.availability.rooms-fail"
|
"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 breakfastPackagesCounter = meter.createCounter("trpc.package.breakfast")
|
||||||
const breakfastPackagesSuccessCounter = meter.createCounter(
|
const breakfastPackagesSuccessCounter = meter.createCounter(
|
||||||
"trpc.package.breakfast-success"
|
"trpc.package.breakfast-success"
|
||||||
@@ -545,6 +556,161 @@ export const hotelQueryRouter = router({
|
|||||||
|
|
||||||
return validateAvailabilityData.data
|
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<string, string | number | undefined> = {
|
||||||
|
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({
|
rates: router({
|
||||||
get: publicProcedure
|
get: publicProcedure
|
||||||
|
|||||||
@@ -2,4 +2,16 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema"
|
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<typeof bedTypeSchema> {}
|
export interface BedTypeSchema extends z.output<typeof bedTypeSchema> {}
|
||||||
|
|||||||
@@ -16,10 +16,15 @@ export interface BookingData {
|
|||||||
room: Room[]
|
room: Room[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Price = {
|
||||||
|
price?: string
|
||||||
|
currency?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type RoomsData = {
|
export type RoomsData = {
|
||||||
roomType: string
|
roomType: string
|
||||||
localPrice: string
|
localPrice: Price
|
||||||
euroPrice: string
|
euroPrice: Price
|
||||||
adults: number
|
adults: number
|
||||||
children?: Child[]
|
children?: Child[]
|
||||||
cancellationText: string
|
cancellationText: string
|
||||||
|
|||||||
Reference in New Issue
Block a user