Merged in feat/SW-454-select-room-api (pull request #648)
Feat/SW-454 Create select rate page foundation * Extract select-rate page to its own, fixed route * Rename availability to hotelsAvailability * Update availability hotels response * Number to string Approved-by: Pontus Dreij
This commit is contained in:
@@ -8,7 +8,6 @@ import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
|
||||
import Details from "@/components/HotelReservation/EnterDetails/Details"
|
||||
import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader"
|
||||
import Payment from "@/components/HotelReservation/SelectRate/Payment"
|
||||
import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection"
|
||||
import SectionAccordion from "@/components/HotelReservation/SelectRate/SectionAccordion"
|
||||
import Summary from "@/components/HotelReservation/SelectRate/Summary"
|
||||
import { getIntl } from "@/i18n"
|
||||
@@ -138,18 +137,7 @@ export default async function SectionsPage({
|
||||
: undefined
|
||||
}
|
||||
path={`select-rate?${currentSearchParams}`}
|
||||
>
|
||||
{params.section === "select-rate" && (
|
||||
<RoomSelection
|
||||
alternatives={rooms}
|
||||
nextPath="select-bed"
|
||||
// TODO: Get real value
|
||||
nrOfNights={1}
|
||||
// TODO: Get real value
|
||||
nrOfAdults={1}
|
||||
/>
|
||||
)}
|
||||
</SectionAccordion>
|
||||
></SectionAccordion>
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Bed type" })}
|
||||
selection={selectedBed}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Filter } from "@/types/components/hotelReservation/selectHotel/hotelFil
|
||||
export async function fetchAvailableHotels(
|
||||
input: AvailabilityInput
|
||||
): Promise<HotelData[]> {
|
||||
const availableHotels = await serverClient().hotel.availability.get(input)
|
||||
const availableHotels = await serverClient().hotel.availability.hotels(input)
|
||||
|
||||
if (!availableHotels) throw new Error()
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.page {
|
||||
min-height: 100dvh;
|
||||
padding-top: var(--Spacing-x6);
|
||||
padding-left: var(--Spacing-x2);
|
||||
padding-right: var(--Spacing-x2);
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 1134px;
|
||||
margin-top: var(--Spacing-x5);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--Spacing-x7);
|
||||
}
|
||||
|
||||
.main {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.summary {
|
||||
max-width: 340px;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
import tempHotelData from "@/server/routers/hotels/tempHotelData.json"
|
||||
|
||||
import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function SelectRatePage({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
|
||||
setLang(params.lang)
|
||||
|
||||
// TODO: Use real endpoint.
|
||||
const hotel = tempHotelData.data.attributes
|
||||
|
||||
const rates = await serverClient().hotel.rates.get({
|
||||
// TODO: pass the correct hotel ID and all other parameters that should be included in the search
|
||||
hotelId: searchParams.hotel,
|
||||
})
|
||||
|
||||
// const rates = await serverClient().hotel.availability.getForHotel({
|
||||
// hotelId: 811,
|
||||
// roomStayStartDate: "2024-11-02",
|
||||
// roomStayEndDate: "2024-11-03",
|
||||
// adults: 1,
|
||||
// })
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* TODO: Add Hotel Listing Card */}
|
||||
<div>Hotel Listing Card TBI</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<div className={styles.main}>
|
||||
<RoomSelection
|
||||
rates={rates}
|
||||
// TODO: Get real value
|
||||
nrOfNights={1}
|
||||
// TODO: Get real value
|
||||
nrOfAdults={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -5,11 +5,10 @@ import RoomCard from "./RoomCard"
|
||||
|
||||
import styles from "./roomSelection.module.css"
|
||||
|
||||
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/section"
|
||||
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
|
||||
|
||||
export default function RoomSelection({
|
||||
alternatives,
|
||||
nextPath,
|
||||
rates,
|
||||
nrOfNights,
|
||||
nrOfAdults,
|
||||
}: RoomSelectionProps) {
|
||||
@@ -21,17 +20,17 @@ export default function RoomSelection({
|
||||
const queryParams = new URLSearchParams(searchParams)
|
||||
queryParams.set("roomClass", e.currentTarget.roomClass?.value)
|
||||
queryParams.set("flexibility", e.currentTarget.flexibility?.value)
|
||||
router.push(`${nextPath}?${queryParams}`)
|
||||
router.push(`select-bed?${queryParams}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<ul className={styles.roomList}>
|
||||
{alternatives.map((room) => (
|
||||
{rates.map((room) => (
|
||||
<li key={room.id}>
|
||||
<form
|
||||
method="GET"
|
||||
action={`${nextPath}?${searchParams}`}
|
||||
action={`select-bed?${searchParams}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<input
|
||||
@@ -50,6 +49,7 @@ export default function RoomSelection({
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className={styles.summary}>This is summary</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,3 +21,10 @@
|
||||
position: fixed;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.summary {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export namespace endpoints {
|
||||
profile = "profile/v0/Profile",
|
||||
}
|
||||
export const enum v1 {
|
||||
availability = "availability/v1/availabilities/city",
|
||||
hotelsAvailability = "availability/v1/availabilities/city",
|
||||
profile = "profile/v1/Profile",
|
||||
booking = "booking/v1/Bookings",
|
||||
creditCards = `${profile}/creditCards`,
|
||||
|
||||
@@ -6,7 +6,7 @@ export const getHotelInputSchema = z.object({
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export const getAvailabilityInputSchema = z.object({
|
||||
export const getHotelsAvailabilityInputSchema = z.object({
|
||||
cityId: z.string(),
|
||||
roomStayStartDate: z.string(),
|
||||
roomStayEndDate: z.string(),
|
||||
|
||||
@@ -525,26 +525,18 @@ const occupancySchema = z.object({
|
||||
|
||||
const bestPricePerStaySchema = z.object({
|
||||
currency: z.string(),
|
||||
amount: z.number(),
|
||||
regularAmount: z.number(),
|
||||
memberAmount: z.number(),
|
||||
discountRate: z.number(),
|
||||
discountAmount: z.number(),
|
||||
points: z.number(),
|
||||
numberOfVouchers: z.number(),
|
||||
numberOfBonusCheques: z.number(),
|
||||
// TODO: remove optional when API is ready
|
||||
regularAmount: z.string().optional(),
|
||||
// TODO: remove optional when API is ready
|
||||
memberAmount: z.string().optional(),
|
||||
})
|
||||
|
||||
const bestPricePerNightSchema = z.object({
|
||||
currency: z.string(),
|
||||
amount: z.number(),
|
||||
regularAmount: z.number(),
|
||||
memberAmount: z.number(),
|
||||
discountRate: z.number(),
|
||||
discountAmount: z.number(),
|
||||
points: z.number(),
|
||||
numberOfVouchers: z.number(),
|
||||
numberOfBonusCheques: z.number(),
|
||||
// TODO: remove optional when API is ready
|
||||
regularAmount: z.string().optional(),
|
||||
// TODO: remove optional when API is ready
|
||||
memberAmount: z.string().optional(),
|
||||
})
|
||||
|
||||
const linksSchema = z.object({
|
||||
@@ -556,7 +548,7 @@ const linksSchema = z.object({
|
||||
),
|
||||
})
|
||||
|
||||
const availabilitySchema = z.object({
|
||||
const hotelsAvailabilitySchema = z.object({
|
||||
data: z.array(
|
||||
z.object({
|
||||
attributes: z.object({
|
||||
@@ -575,10 +567,10 @@ const availabilitySchema = z.object({
|
||||
),
|
||||
})
|
||||
|
||||
export const getAvailabilitySchema = availabilitySchema
|
||||
export type Availability = z.infer<typeof availabilitySchema>
|
||||
export type AvailabilityPrices =
|
||||
Availability["data"][number]["attributes"]["bestPricePerNight"]
|
||||
export const getHotelsAvailabilitySchema = hotelsAvailabilitySchema
|
||||
export type HotelsAvailability = z.infer<typeof hotelsAvailabilitySchema>
|
||||
export type HotelsAvailabilityPrices =
|
||||
HotelsAvailability["data"][number]["attributes"]["bestPricePerNight"]
|
||||
|
||||
const flexibilityPrice = z.object({
|
||||
standard: z.number(),
|
||||
|
||||
@@ -20,14 +20,14 @@ import { toApiLang } from "@/server/utils"
|
||||
|
||||
import { hotelPageSchema } from "../contentstack/hotelPage/output"
|
||||
import {
|
||||
getAvailabilityInputSchema,
|
||||
getHotelInputSchema,
|
||||
getHotelsAvailabilityInputSchema,
|
||||
getlHotelDataInputSchema,
|
||||
getRatesInputSchema,
|
||||
} from "./input"
|
||||
import {
|
||||
getAvailabilitySchema,
|
||||
getHotelDataSchema,
|
||||
getHotelsAvailabilitySchema,
|
||||
getRatesSchema,
|
||||
roomSchema,
|
||||
} from "./output"
|
||||
@@ -51,12 +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 availabilityCounter = meter.createCounter("trpc.hotel.availability")
|
||||
const availabilitySuccessCounter = meter.createCounter(
|
||||
"trpc.hotel.availability-success"
|
||||
const hotelsAvailabilityCounter = meter.createCounter(
|
||||
"trpc.hotel.availability.hotels"
|
||||
)
|
||||
const availabilityFailCounter = meter.createCounter(
|
||||
"trpc.hotel.availability-fail"
|
||||
const hotelsAvailabilitySuccessCounter = meter.createCounter(
|
||||
"trpc.hotel.availability.hotels-success"
|
||||
)
|
||||
const hotelsAvailabilityFailCounter = meter.createCounter(
|
||||
"trpc.hotel.availability.hotels-fail"
|
||||
)
|
||||
|
||||
async function getContentstackData(
|
||||
@@ -250,8 +252,8 @@ export const hotelQueryRouter = router({
|
||||
}
|
||||
}),
|
||||
availability: router({
|
||||
get: hotelServiceProcedure
|
||||
.input(getAvailabilityInputSchema)
|
||||
hotels: hotelServiceProcedure
|
||||
.input(getHotelsAvailabilityInputSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const {
|
||||
cityId,
|
||||
@@ -274,7 +276,7 @@ export const hotelQueryRouter = router({
|
||||
attachedProfileId,
|
||||
}
|
||||
|
||||
availabilityCounter.add(1, {
|
||||
hotelsAvailabilityCounter.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
@@ -284,11 +286,11 @@ export const hotelQueryRouter = router({
|
||||
reservationProfileType,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.availability start",
|
||||
"api.hotels.hotelsAvailability start",
|
||||
JSON.stringify({ query: { cityId, params } })
|
||||
)
|
||||
const apiResponse = await api.get(
|
||||
`${api.endpoints.v1.availability}/${cityId}`,
|
||||
`${api.endpoints.v1.hotelsAvailability}/${cityId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
@@ -298,7 +300,7 @@ export const hotelQueryRouter = router({
|
||||
)
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
availabilityFailCounter.add(1, {
|
||||
hotelsAvailabilityFailCounter.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
@@ -314,7 +316,7 @@ export const hotelQueryRouter = router({
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.availability error",
|
||||
"api.hotels.hotelsAvailability error",
|
||||
JSON.stringify({
|
||||
query: { cityId, params },
|
||||
error: {
|
||||
@@ -328,9 +330,9 @@ export const hotelQueryRouter = router({
|
||||
}
|
||||
const apiJson = await apiResponse.json()
|
||||
const validateAvailabilityData =
|
||||
getAvailabilitySchema.safeParse(apiJson)
|
||||
getHotelsAvailabilitySchema.safeParse(apiJson)
|
||||
if (!validateAvailabilityData.success) {
|
||||
availabilityFailCounter.add(1, {
|
||||
hotelsAvailabilityFailCounter.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
@@ -342,7 +344,7 @@ export const hotelQueryRouter = router({
|
||||
error: JSON.stringify(validateAvailabilityData.error),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.availability validation error",
|
||||
"api.hotels.hotelsAvailability validation error",
|
||||
JSON.stringify({
|
||||
query: { cityId, params },
|
||||
error: validateAvailabilityData.error,
|
||||
@@ -350,7 +352,7 @@ export const hotelQueryRouter = router({
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
availabilitySuccessCounter.add(1, {
|
||||
hotelsAvailabilitySuccessCounter.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
@@ -360,7 +362,7 @@ export const hotelQueryRouter = router({
|
||||
reservationProfileType,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.availability success",
|
||||
"api.hotels.hotelsAvailability success",
|
||||
JSON.stringify({
|
||||
query: { cityId, params: params },
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AvailabilityPrices } from "@/server/routers/hotels/output"
|
||||
import { HotelsAvailabilityPrices } from "@/server/routers/hotels/output"
|
||||
|
||||
import { Hotel } from "@/types/hotel"
|
||||
|
||||
@@ -8,5 +8,5 @@ export type HotelCardListingProps = {
|
||||
|
||||
export type HotelData = {
|
||||
hotelData: Hotel
|
||||
price: AvailabilityPrices
|
||||
price: HotelsAvailabilityPrices
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Rate } from "@/server/routers/hotels/output"
|
||||
|
||||
export interface RoomSelectionProps {
|
||||
rates: Rate[]
|
||||
nrOfAdults: number
|
||||
nrOfNights: number
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Rate } from "@/server/routers/hotels/output"
|
||||
|
||||
import { Hotel } from "@/types/hotel"
|
||||
|
||||
export interface SectionProps {
|
||||
@@ -27,12 +25,6 @@ export interface BreakfastSelectionProps extends SectionProps {
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface RoomSelectionProps extends SectionProps {
|
||||
alternatives: Rate[]
|
||||
nrOfAdults: number
|
||||
nrOfNights: number
|
||||
}
|
||||
|
||||
export interface DetailsProps extends SectionProps {}
|
||||
|
||||
export interface PaymentProps {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface SelectRateSearchParams {
|
||||
fromDate: string
|
||||
toDate: string
|
||||
hotel: string
|
||||
}
|
||||
Reference in New Issue
Block a user