From 85fdefb5ace6262c10cdad6d51cd21af13fbcb72 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Tue, 22 Oct 2024 16:19:15 +0200 Subject: [PATCH 1/5] feat: save search params from select-rate to store --- .../(standard)/[step]/layout.tsx | 9 +- .../(standard)/[step]/page.tsx | 2 +- .../Overview/Stats/ExpiringPoints/index.tsx | 6 +- .../Points/EarnAndBurn/AwardPoints/index.tsx | 7 +- .../Footer/Navigation/SecondaryNav/index.tsx | 4 +- .../EnterDetails/BedType/index.tsx | 9 +- .../EnterDetails/Breakfast/index.tsx | 32 +- .../EnterDetails/Details/index.tsx | 20 +- .../EnterDetails/Provider/index.tsx | 6 +- .../EnterDetails/SidePeek/index.tsx | 2 +- .../EnterDetails/Summary/ToggleSidePeek.tsx | 2 +- .../EnterDetails/Summary/index.tsx | 284 +++++++++++------- .../EnterDetails/Summary/summary.module.css | 2 +- .../Text/Body/body.module.css | 2 +- .../TempDesignSystem/Text/Body/variants.ts | 2 +- i18n/dictionaries/da.json | 2 +- i18n/dictionaries/de.json | 2 +- i18n/dictionaries/en.json | 4 +- i18n/dictionaries/fi.json | 2 +- stores/enter-details.ts | 91 ++++-- .../enterDetails/bedType.ts | 0 .../enterDetails/bookingData.ts | 27 ++ .../enterDetails/breakfast.ts | 0 .../enterDetails/details.ts | 0 .../enterDetails/sidePeek.ts | 0 .../enterDetails/step.ts | 0 .../selectRate/sectionAccordion.ts | 2 +- utils/format.ts | 8 + 28 files changed, 332 insertions(+), 195 deletions(-) rename types/components/{ => hotelReservation}/enterDetails/bedType.ts (100%) create mode 100644 types/components/hotelReservation/enterDetails/bookingData.ts rename types/components/{ => hotelReservation}/enterDetails/breakfast.ts (100%) rename types/components/{ => hotelReservation}/enterDetails/details.ts (100%) rename types/components/{ => hotelReservation}/enterDetails/sidePeek.ts (100%) rename types/components/{ => hotelReservation}/enterDetails/step.ts (100%) create mode 100644 utils/format.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx index 271d19e6d..28a2bc570 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/layout.tsx @@ -7,7 +7,7 @@ import { preload } from "./page" import styles from "./layout.module.css" -import { StepEnum } from "@/types/components/enterDetails/step" +import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" import type { LangParams, LayoutArgs } from "@/types/params" export default async function StepLayout({ @@ -19,19 +19,18 @@ export default async function StepLayout({ LayoutArgs & { hotelHeader: React.ReactNode sidePeek: React.ReactNode - } ->) { + }>) { setLang(params.lang) preload() return ( - +
{hotelHeader}
{children}
{sidePeek} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx index 9a673c26c..a8bb4b4e9 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx @@ -17,7 +17,7 @@ import SectionAccordion from "@/components/HotelReservation/EnterDetails/Section import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { getIntl } from "@/i18n" -import { StepEnum } from "@/types/components/enterDetails/step" +import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" import type { LangParams, PageArgs } from "@/types/params" export function preload() { diff --git a/components/Blocks/DynamicContent/Overview/Stats/ExpiringPoints/index.tsx b/components/Blocks/DynamicContent/Overview/Stats/ExpiringPoints/index.tsx index e64ddb4ef..fa21a8c83 100644 --- a/components/Blocks/DynamicContent/Overview/Stats/ExpiringPoints/index.tsx +++ b/components/Blocks/DynamicContent/Overview/Stats/ExpiringPoints/index.tsx @@ -4,6 +4,7 @@ import { dt } from "@/lib/dt" import Body from "@/components/TempDesignSystem/Text/Body" import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" +import { formatNumber } from "@/utils/format" import { getMembership } from "@/utils/user" import type { UserProps } from "@/types/components/myPages/user" @@ -16,9 +17,6 @@ export default async function ExpiringPoints({ user }: UserProps) { // TODO: handle this case? return null } - - // sv hardcoded to force space on thousands - const formatter = new Intl.NumberFormat(Lang.sv) const d = dt(membership.pointsExpiryDate) const dateFormat = getLang() == Lang.fi ? "DD.MM.YYYY" : "YYYY-MM-DD" @@ -29,7 +27,7 @@ export default async function ExpiringPoints({ user }: UserProps) { {intl.formatMessage( { id: "spendable points expiring by" }, { - points: formatter.format(membership.pointsToExpire), + points: formatNumber(membership.pointsToExpire), date: d.format(dateFormat), } )} diff --git a/components/Blocks/DynamicContent/Points/EarnAndBurn/AwardPoints/index.tsx b/components/Blocks/DynamicContent/Points/EarnAndBurn/AwardPoints/index.tsx index 119324806..e32c3c5ca 100644 --- a/components/Blocks/DynamicContent/Points/EarnAndBurn/AwardPoints/index.tsx +++ b/components/Blocks/DynamicContent/Points/EarnAndBurn/AwardPoints/index.tsx @@ -1,8 +1,7 @@ import { useIntl } from "react-intl" -import { Lang } from "@/constants/languages" - import Body from "@/components/TempDesignSystem/Text/Body" +import { formatNumber } from "@/utils/format" import { awardPointsVariants } from "./awardPointsVariants" @@ -32,12 +31,10 @@ export default function AwardPoints({ variant, }) - // sv hardcoded to force space on thousands - const formatter = new Intl.NumberFormat(Lang.sv) return ( {isCalculated - ? formatter.format(awardPoints) + ? formatNumber(awardPoints) : intl.formatMessage({ id: "Points being calculated" })} ) diff --git a/components/Footer/Navigation/SecondaryNav/index.tsx b/components/Footer/Navigation/SecondaryNav/index.tsx index 42266f915..78d4f544c 100644 --- a/components/Footer/Navigation/SecondaryNav/index.tsx +++ b/components/Footer/Navigation/SecondaryNav/index.tsx @@ -18,7 +18,7 @@ export default function FooterSecondaryNav({
{appDownloads && (
diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx index a8bb4b4e9..44de099bf 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx @@ -7,6 +7,7 @@ import { getProfileSafely, getRoomAvailability, } from "@/lib/trpc/memoizedRequests" +import { HotelIncludeEnum } from "@/server/routers/hotels/input" import BedType from "@/components/HotelReservation/EnterDetails/BedType" import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast" @@ -14,11 +15,12 @@ import Details from "@/components/HotelReservation/EnterDetails/Details" import HistoryStateManager from "@/components/HotelReservation/EnterDetails/HistoryStateManager" import Payment from "@/components/HotelReservation/EnterDetails/Payment" import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion" -import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { getIntl } from "@/i18n" import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" -import type { LangParams, PageArgs } from "@/types/params" +import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" +import type { LangParams, PageArgs, } from "@/types/params" export function preload() { void getProfileSafely() @@ -32,35 +34,52 @@ function isValidStep(step: string): step is StepEnum { export default async function StepPage({ params, searchParams, -}: PageArgs) { +}: PageArgs< + LangParams & { step: StepEnum }, + SelectRateSearchParams +>) { if (!searchParams.hotel) { redirect(`/${params.lang}`) } void getBreakfastPackages(searchParams.hotel) - const stepParams = new URLSearchParams(searchParams) - const paramsObject = getHotelReservationQueryParams(stepParams) - void getRoomAvailability({ - hotelId: paramsObject.hotel, - adults: paramsObject.room[0].adults, - roomStayStartDate: paramsObject.fromDate, - roomStayEndDate: paramsObject.toDate, - }) - const intl = await getIntl() - const hotel = await getHotelData(searchParams.hotel, params.lang) + const intl = await getIntl() + const selectRoomParams = new URLSearchParams(searchParams) + const { + hotel: hotelId, + adults, + children, + roomTypeCode, + rateCode, + fromDate, + toDate, + } = getQueryParamsForEnterDetails(selectRoomParams) + + void getRoomAvailability({ + hotelId: parseInt(hotelId), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode + }) + + const hotelData = await getHotelData(hotelId, params.lang, undefined, [HotelIncludeEnum.RoomCategories]) + const user = await getProfileSafely() const savedCreditCards = await getCreditCardsSafely() const breakfastPackages = await getBreakfastPackages(searchParams.hotel) const roomAvailability = await getRoomAvailability({ - hotelId: paramsObject.hotel, - adults: paramsObject.room[0].adults, - roomStayStartDate: paramsObject.fromDate, - roomStayEndDate: paramsObject.toDate, - rateCode: paramsObject.room[0].ratecode, + hotelId: parseInt(hotelId), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode }) - if (!isValidStep(params.step) || !hotel || !roomAvailability) { + if (!isValidStep(params.step) || !hotelData || !roomAvailability) { return notFound() } @@ -79,16 +98,32 @@ export default async function StepPage({ id: "Select payment method", }) + const availableRoom = roomAvailability?.roomConfigurations + .filter((room) => room.status === "Available") + .find((room) => room.roomTypeCode === roomTypeCode)?.roomType + const roomTypes = hotelData.included + ?.find((room) => room.name === availableRoom) + ?.roomTypes.map((room) => ({ + description: room.mainBed.description, + size: room.mainBed.widthRange, + value: room.code, + })) + return (
- - - + + {/* TODO: How to handle no beds found? */} + {roomTypes ? ( + + + + ) : null} + -
+ ) } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index 36fbd59c7..5fe20e0d9 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -10,7 +10,7 @@ import { } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils" import HotelCardListing from "@/components/HotelReservation/HotelCardListing" import HotelFilter from "@/components/HotelReservation/SelectHotel/HotelFilter" -import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { ChevronRightIcon } from "@/components/Icons" import StaticMap from "@/components/Maps/StaticMap" import Link from "@/components/TempDesignSystem/Link" diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 35a940f0b..502a5833c 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -2,10 +2,11 @@ import { notFound } from "next/navigation" import { getProfileSafely } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" +import { HotelIncludeEnum } from "@/server/routers/hotels/input" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" import Rooms from "@/components/HotelReservation/SelectRate/Rooms" -import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { setLang } from "@/i18n/serverContext" import { generateChildrenString } from "../select-hotel/utils" @@ -38,7 +39,7 @@ export default async function SelectRatePage({ serverClient().hotel.hotelData.get({ hotelId: searchParams.hotel, language: params.lang, - include: ["RoomCategories"], + include: [HotelIncludeEnum.RoomCategories], }), serverClient().hotel.availability.rooms({ hotelId: parseInt(searchParams.hotel, 10), diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index 6a594199e..2acb8a397 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -11,7 +11,6 @@ import { CloseLargeIcon } from "@/components/Icons" import { debounce } from "@/utils/debounce" import { getFormattedUrlQueryParams } from "@/utils/url" -import getHotelReservationQueryParams from "../HotelReservation/SelectRate/RoomSelection/utils" import MobileToggleButton from "./MobileToggleButton" import styles from "./bookingWidget.module.css" @@ -41,10 +40,10 @@ export default function BookingWidgetClient({ const bookingWidgetSearchData: BookingWidgetSearchParams | undefined = searchParams ? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), { - adults: "number", - age: "number", - bed: "number", - }) as BookingWidgetSearchParams) + adults: "number", + age: "number", + bed: "number", + }) as BookingWidgetSearchParams) : undefined const getLocationObj = (destination: string): Location | undefined => { @@ -70,9 +69,9 @@ export default function BookingWidgetClient({ const selectedLocation = bookingWidgetSearchData ? getLocationObj( - (bookingWidgetSearchData.city ?? - bookingWidgetSearchData.hotel) as string - ) + (bookingWidgetSearchData.city ?? + bookingWidgetSearchData.hotel) as string + ) : undefined const methods = useForm({ diff --git a/components/HotelReservation/EnterDetails/BedType/index.tsx b/components/HotelReservation/EnterDetails/BedType/index.tsx index 4811a17d5..27c95d963 100644 --- a/components/HotelReservation/EnterDetails/BedType/index.tsx +++ b/components/HotelReservation/EnterDetails/BedType/index.tsx @@ -16,7 +16,18 @@ import styles from "./bedOptions.module.css" import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType" -export default function BedType() { +export default function BedType({ + roomTypes, +}: { + roomTypes: { + description: string + size: { + min: number + max: number + } + value: string + }[] +}) { const intl = useIntl() const bedType = useEnterDetailsStore((state) => state.userData.bedType) @@ -57,38 +68,25 @@ export default function BedType() { return (
- - + {roomTypes.map((roomType) => { + const width = + roomType.size.max === roomType.size.min + ? roomType.size.max + : `${roomType.size.min} cm - ${roomType.size.max} cm` + return ( + + ) + })}
) diff --git a/components/HotelReservation/EnterDetails/BedType/schema.ts b/components/HotelReservation/EnterDetails/BedType/schema.ts index 8f77ba768..5323f4e02 100644 --- a/components/HotelReservation/EnterDetails/BedType/schema.ts +++ b/components/HotelReservation/EnterDetails/BedType/schema.ts @@ -3,5 +3,5 @@ import { z } from "zod" import { BedTypeEnum } from "@/types/enums/bedType" export const bedTypeSchema = z.object({ - bedType: z.nativeEnum(BedTypeEnum), + bedType: z.string(), }) diff --git a/components/HotelReservation/EnterDetails/Payment/index.tsx b/components/HotelReservation/EnterDetails/Payment/index.tsx index e29692993..f4ea2b9f9 100644 --- a/components/HotelReservation/EnterDetails/Payment/index.tsx +++ b/components/HotelReservation/EnterDetails/Payment/index.tsx @@ -56,7 +56,7 @@ export default function Payment({ const intl = useIntl() const queryParams = useSearchParams() const { firstName, lastName, email, phoneNumber, countryCode } = - useEnterDetailsStore((state) => state.data) + useEnterDetailsStore((state) => state.userData) const [confirmationNumber, setConfirmationNumber] = useState("") const methods = useForm({ diff --git a/components/HotelReservation/EnterDetails/Summary/index.tsx b/components/HotelReservation/EnterDetails/Summary/index.tsx index 363585848..c1bad51f4 100644 --- a/components/HotelReservation/EnterDetails/Summary/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/index.tsx @@ -1,52 +1,43 @@ "use client" +import { useEffect, useState } from "react" import { useIntl } from "react-intl" import { dt } from "@/lib/dt" -import { trpc } from "@/lib/trpc/client" import { useEnterDetailsStore } from "@/stores/enter-details" import { ArrowRightIcon } from "@/components/Icons" -import LoadingSpinner from "@/components/LoadingSpinner" import Divider from "@/components/TempDesignSystem/Divider" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import useLang from "@/hooks/useLang" -import { formatNumber } from "@/utils/format" import styles from "./summary.module.css" import { RoomsData } from "@/types/components/hotelReservation/enterDetails/bookingData" -export default function Summary({ isMember }: { isMember: boolean }) { +export default function Summary({ + isMember, + room, +}: { + isMember: boolean + room: RoomsData +}) { + const [chosenBed, setChosenBed] = useState() + const [chosenBreakfast, setCosenBreakfast] = useState() const intl = useIntl() const lang = useLang() - const { fromDate, toDate, rooms, hotel, bedType, breakfast } = - useEnterDetailsStore((state) => ({ - fromDate: state.roomData.fromdate, - toDate: state.roomData.todate, + 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, - })) - - const totalAdults = rooms.reduce((total, room) => total + room.adults, 0) - - const { - data: availabilityData, - isLoading, - error, - } = trpc.hotel.availability.rooms.useQuery( - { - hotelId: parseInt(hotel), - adults: totalAdults, - roomStayStartDate: dt(fromDate).format("YYYY-MM-DD"), - roomStayEndDate: dt(toDate).format("YYYY-MM-DD"), - }, - { enabled: !!hotel && !!fromDate && !!toDate } + }) ) const diff = dt(toDate).diff(fromDate, "days") @@ -56,37 +47,15 @@ export default function Summary({ isMember }: { isMember: boolean }) { { totalNights: diff } ) - if (isLoading) { - return + let color: "uiTextHighContrast" | "red" = "uiTextHighContrast" + if (isMember) { + color = "red" } - const populatedRooms = rooms - .map((room) => { - const chosenRoom = availabilityData?.roomConfigurations.find( - (availRoom) => room.roomtypecode === availRoom.roomTypeCode - ) - const cancellationText = availabilityData?.rateDefinitions.find( - (rate) => rate.rateCode === room.ratecode - )?.cancellationText - if (chosenRoom) { - const memberPrice = chosenRoom.products.find( - (rate) => rate.productType.member?.rateCode === room.ratecode - )?.productType.member?.localPrice.pricePerStay - const publicPrice = chosenRoom.products.find( - (rate) => rate.productType.public?.rateCode === room.ratecode - )?.productType.public?.localPrice.pricePerStay - - return { - roomType: chosenRoom.roomType, - memberPrice: memberPrice && formatNumber(parseInt(memberPrice)), - publicPrice: publicPrice && formatNumber(parseInt(publicPrice)), - adults: room.adults, - children: room.child, - cancellationText, - } - } - }) - .filter((room): room is RoomsData => room !== undefined) + useEffect(() => { + setChosenBed(bedType) + setCosenBreakfast(breakfast) + }, [bedType, breakfast]) return (
@@ -100,14 +69,48 @@ export default function Summary({ isMember }: { isMember: boolean }) {
- {populatedRooms.map((room, idx) => ( - - ))} - - {bedType ? ( +
- {bedType} - + {room.roomType} + + {intl.formatMessage( + { id: "{amount} {currency}" }, + { amount: room.price, currency: "SEK" } + )} + +
+ + {intl.formatMessage( + { id: "booking.adults" }, + { totalAdults: room.adults } + )} + + {room.children?.length ? ( + + {intl.formatMessage( + { id: "booking.children" }, + { totalChildren: room.children.length } + )} + + ) : null} + + {room.cancellationText} + + + {intl.formatMessage({ id: "Rate details" })} + +
+ + {chosenBed ? ( +
+
+ {chosenBed} + + {intl.formatMessage({ id: "Based on availability" })} + +
+ + {intl.formatMessage( { id: "{amount} {currency}" }, { amount: "0", currency: "SEK" } @@ -115,10 +118,11 @@ export default function Summary({ isMember }: { isMember: boolean }) {
) : null} - {breakfast ? ( + + {chosenBreakfast ? (
- {breakfast} - + {chosenBreakfast} + {intl.formatMessage( { id: "{amount} {currency}" }, { amount: "0", currency: "SEK" } @@ -130,77 +134,35 @@ export default function Summary({ isMember }: { isMember: boolean }) {
- - {intl.formatMessage({ id: "Total price (incl VAT)" })} - - - {intl.formatMessage( - { id: "{amount} {currency}" }, - { amount: "4686", currency: "SEK" } - )} - -
-
- - {intl.formatMessage({ id: "Approx." })} - - - {intl.formatMessage( - { id: "{amount} {currency}" }, - { amount: "455", currency: "EUR" } - )} - +
+ + {intl.formatMessage( + { id: "Total price (incl VAT)" }, + { b: (str) => {str} } + )} + + + {intl.formatMessage({ id: "Price details" })} + +
+
+ + {intl.formatMessage( + { id: "{amount} {currency}" }, + { amount: room.price, currency: "SEK" } // TODO: calculate total price + )} + + + {intl.formatMessage({ id: "Approx." })}{" "} + {intl.formatMessage( + { id: "{amount} {currency}" }, + { amount: "455", currency: "EUR" } + )} + +
+
) } - -function RoomBreakdown({ - room, - isMember, -}: { - room: RoomsData - isMember: boolean -}) { - const intl = useIntl() - - let color: "uiTextHighContrast" | "red" = "uiTextHighContrast" - let price = room.publicPrice - if (isMember) { - color = "red" - price = room.memberPrice - } - - return ( -
-
- {room.roomType} - - {intl.formatMessage( - { id: "{amount} {currency}" }, - { amount: price, currency: "SEK" } - )} - -
- - {intl.formatMessage( - { id: "booking.adults" }, - { totalAdults: room.adults } - )} - - {room.children?.length ? ( - - {intl.formatMessage( - { id: "booking.children" }, - { totalChildren: room.children.length } - )} - - ) : null} - {room.cancellationText} - - {intl.formatMessage({ id: "Rate details" })} - -
- ) -} diff --git a/components/HotelReservation/EnterDetails/Summary/summary.module.css b/components/HotelReservation/EnterDetails/Summary/summary.module.css index 472508e79..b7a8d2db4 100644 --- a/components/HotelReservation/EnterDetails/Summary/summary.module.css +++ b/components/HotelReservation/EnterDetails/Summary/summary.module.css @@ -1,11 +1,10 @@ .summary { - background-color: var(--Main-Grey-White); - border: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); border-radius: var(--Corner-radius-Large); display: flex; flex-direction: column; gap: var(--Spacing-x2); - padding: var(--Spacing-x2); + padding: var(--Spacing-x3); + height: 100%; } .date { @@ -31,6 +30,9 @@ justify-content: space-between; } +.entry > :last-child { + justify-items: flex-end; +} .total { display: flex; flex-direction: column; diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index 9929a7451..4b6613e7e 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -4,7 +4,7 @@ import { useMemo, useState } from "react" import RateSummary from "./RateSummary" import RoomCard from "./RoomCard" -import getHotelReservationQueryParams from "./utils" +import { getHotelReservationQueryParams } from "./utils" import styles from "./roomSelection.module.css" diff --git a/components/HotelReservation/SelectRate/RoomSelection/utils.ts b/components/HotelReservation/SelectRate/RoomSelection/utils.ts index 1ae94cc9c..24309d355 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/utils.ts +++ b/components/HotelReservation/SelectRate/RoomSelection/utils.ts @@ -2,11 +2,22 @@ import { getFormattedUrlQueryParams } from "@/utils/url" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" -function getHotelReservationQueryParams(searchParams: URLSearchParams) { +export function getHotelReservationQueryParams(searchParams: URLSearchParams) { return getFormattedUrlQueryParams(searchParams, { adults: "number", age: "number", }) as SelectRateSearchParams } -export default getHotelReservationQueryParams +export function getQueryParamsForEnterDetails(searchParams: URLSearchParams) { + const selectRoomParamsObject = getHotelReservationQueryParams(searchParams) + + const { room } = selectRoomParamsObject + return { + ...selectRoomParamsObject, + adults: room[0].adults, // TODO: Handle multiple rooms + children: room[0].child?.length.toString(), // TODO: Handle multiple rooms + roomTypeCode: room[0].roomtypecode, + rateCode: room[0].ratecode, + } +} diff --git a/components/TempDesignSystem/Text/Body/variants.ts b/components/TempDesignSystem/Text/Body/variants.ts index 3cf9a1376..5acc36958 100644 --- a/components/TempDesignSystem/Text/Body/variants.ts +++ b/components/TempDesignSystem/Text/Body/variants.ts @@ -16,7 +16,6 @@ const config = { textHighContrast: styles.textHighContrast, white: styles.white, peach50: styles.peach50, - baseTextMediumContrast: styles.baseTextMediumContrast, uiTextHighContrast: styles.uiTextHighContrast, uiTextMediumContrast: styles.uiTextMediumContrast, uiTextPlaceholder: styles.uiTextPlaceholder, diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index fadfdedea..2cdcfa050 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -1,5 +1,7 @@ { "Included (based on availability)": "Inkluderet (baseret på tilgængelighed)", + "Total price (incl VAT)": "Samlet pris (inkl. moms)", + "{amount} {currency}/night per adult": "{amount} {currency}/nat pr. voksen", "A destination or hotel name is needed to be able to search for a hotel room.": "Et destinations- eller hotelnavn er nødvendigt for at kunne søge efter et hotelværelse.", "A photo of the room": "Et foto af værelset", "ACCE": "Tilgængelighed", @@ -35,6 +37,7 @@ "Attractions": "Attraktioner", "Back to scandichotels.com": "Tilbage til scandichotels.com", "Bar": "Bar", + "Based on availability": "Baseret på tilgængelighed", "Bed type": "Seng type", "Birth date": "Fødselsdato", "Book": "Book", @@ -242,12 +245,14 @@ "Points needed to stay on level": "Point nødvendige for at holde sig på niveau", "Previous": "Forudgående", "Previous victories": "Tidligere sejre", + "Price details": "Prisoplysninger", "Proceed to login": "Fortsæt til login", "Proceed to payment method": "Fortsæt til betalingsmetode", "Provide a payment card in the next step": "Giv os dine betalingsoplysninger i næste skridt", "Public price from": "Offentlig pris fra", "Public transport": "Offentlig transport", "Queen bed": "Queensize-seng", + "Rate details": "Oplysninger om værelsespris", "Read more": "Læs mere", "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Læs mere om hotellet", @@ -405,6 +410,5 @@ "uppercase letter": "stort bogstav", "{amount} out of {total}": "{amount} ud af {total}", "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}", - "{width} cm × {length} cm": "{width} cm × {length} cm" + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index f7223efae..df92ae71e 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -1,5 +1,7 @@ { "Included (based on availability)": "Inbegriffen (je nach Verfügbarkeit)", + "Total price (incl VAT)": "Gesamtpreis (inkl. MwSt.)", + "{amount} {currency}/night per adult": "{amount} {currency}/Nacht pro Erwachsener", "A destination or hotel name is needed to be able to search for a hotel room.": "Ein Reiseziel oder Hotelname wird benötigt, um nach einem Hotelzimmer suchen zu können.", "A photo of the room": "Ein Foto des Zimmers", "ACCE": "Zugänglichkeit", @@ -35,6 +37,7 @@ "Attraction": "Attraktion", "Back to scandichotels.com": "Zurück zu scandichotels.com", "Bar": "Bar", + "Based on availability": "Je nach Verfügbarkeit", "Bed type": "Bettentyp", "Birth date": "Geburtsdatum", "Book": "Buchen", @@ -240,12 +243,14 @@ "Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben", "Previous": "Früher", "Previous victories": "Bisherige Siege", + "Price details": "Preisdetails", "Proceed to login": "Weiter zum Login", "Proceed to payment method": "Weiter zur Zahlungsmethode", "Provide a payment card in the next step": "Geben Sie Ihre Zahlungskarteninformationen im nächsten Schritt an", "Public price from": "Öffentlicher Preis ab", "Public transport": "Öffentliche Verkehrsmittel", "Queen bed": "Queensize-Bett", + "Rate details": "Preisdetails", "Read more": "Mehr lesen", "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Lesen Sie mehr über das Hotel", @@ -404,6 +409,5 @@ "uppercase letter": "großbuchstabe", "{amount} out of {total}": "{amount} von {total}", "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}", - "{width} cm × {length} cm": "{width} cm × {length} cm" + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 6cd2128c0..eec758e7a 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -1,5 +1,7 @@ { "Included (based on availability)": "Included (based on availability)", + "Total price (incl VAT)": "Total price (incl VAT)", + "{amount} {currency}/night per adult": "{amount} {currency}/night per adult", "A destination or hotel name is needed to be able to search for a hotel room.": "A destination or hotel name is needed to be able to search for a hotel room.", "A photo of the room": "A photo of the room", "ACCE": "Accessibility", @@ -38,6 +40,7 @@ "Attractions": "Attractions", "Back to scandichotels.com": "Back to scandichotels.com", "Bar": "Bar", + "Based on availability": "Based on availability", "Bed": "Bed", "Bed type": "Bed type", "Birth date": "Birth date", @@ -252,6 +255,7 @@ "Points needed to stay on level": "Points needed to stay on level", "Previous": "Previous", "Previous victories": "Previous victories", + "Price details": "Price details", "Print confirmation": "Print confirmation", "Proceed to login": "Proceed to login", "Proceed to payment method": "Proceed to payment method", @@ -259,6 +263,7 @@ "Public price from": "Public price from", "Public transport": "Public transport", "Queen bed": "Queen bed", + "Rate details": "Rate details", "Read more": "Read more", "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Read more about the hotel", @@ -329,7 +334,6 @@ "Total cost": "Total cost", "Total price": "Total price", "Total Points": "Total Points", - "Total price (incl VAT)": "Total price (incl VAT)", "Tourist": "Tourist", "Transaction date": "Transaction date", "Transactions": "Transactions", @@ -427,6 +431,5 @@ "{amount} out of {total}": "{amount} out of {total}", "{amount} {currency}": "{amount} {currency}", "{card} ending with {cardno}": "{card} ending with {cardno}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}", - "{width} cm × {length} cm": "{width} cm × {length} cm" + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index f46700b16..25acf8a9a 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -1,5 +1,7 @@ { "Included (based on availability)": "Sisältyy (saatavuuden mukaan)", + "Total price (incl VAT)": "Kokonaishinta (sis. ALV)", + "{amount} {currency}/night per adult": "{amount} {currency}/yö per aikuinen", "A destination or hotel name is needed to be able to search for a hotel room.": "Kohteen tai hotellin nimi tarvitaan, jotta hotellihuonetta voidaan hakea.", "A photo of the room": "Kuva huoneesta", "ACCE": "Saavutettavuus", @@ -35,6 +37,7 @@ "Attractions": "Nähtävyydet", "Back to scandichotels.com": "Takaisin scandichotels.com", "Bar": "Bar", + "Based on availability": "Saatavuuden mukaan", "Bed type": "Vuodetyyppi", "Birth date": "Syntymäaika", "Book": "Varaa", @@ -242,12 +245,14 @@ "Points needed to stay on level": "Tällä tasolla pysymiseen tarvittavat pisteet", "Previous": "Aikaisempi", "Previous victories": "Edelliset voitot", + "Price details": "Hintatiedot", "Proceed to login": "Jatka kirjautumiseen", "Proceed to payment method": "Siirry maksutavalle", "Provide a payment card in the next step": "Anna maksukortin tiedot seuraavassa vaiheessa", "Public price from": "Julkinen hinta alkaen", "Public transport": "Julkinen liikenne", "Queen bed": "Queen-vuode", + "Rate details": "Hintatiedot", "Read more": "Lue lisää", "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Lue lisää hotellista", @@ -404,6 +409,5 @@ "uppercase letter": "iso kirjain", "{amount} out of {total}": "{amount}/{total}", "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}", - "{width} cm × {length} cm": "{width} cm × {length} cm" + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index f13a25268..41d740068 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -1,5 +1,7 @@ { "Included (based on availability)": "Inkludert (basert på tilgjengelighet)", + "Total price (incl VAT)": "Totalpris (inkl. mva)", + "{amount} {currency}/night per adult": "{amount} {currency}/natt per voksen", "A destination or hotel name is needed to be able to search for a hotel room.": "Et reisemål eller hotellnavn er nødvendig for å kunne søke etter et hotellrom.", "A photo of the room": "Et bilde av rommet", "ACCE": "Tilgjengelighet", @@ -35,6 +37,7 @@ "Attractions": "Attraksjoner", "Back to scandichotels.com": "Tilbake til scandichotels.com", "Bar": "Bar", + "Based on availability": "Basert på tilgjengelighet", "Bed type": "Seng type", "Birth date": "Fødselsdato", "Book": "Bestill", @@ -240,12 +243,14 @@ "Points needed to stay on level": "Poeng som trengs for å holde seg på nivå", "Previous": "Tidligere", "Previous victories": "Tidligere seire", + "Price details": "Prisdetaljer", "Proceed to login": "Fortsett til innlogging", "Proceed to payment method": "Fortsett til betalingsmetode", "Provide a payment card in the next step": "Gi oss dine betalingskortdetaljer i neste steg", "Public price from": "Offentlig pris fra", "Public transport": "Offentlig transport", "Queen bed": "Queen-size-seng", + "Rate details": "Prisdetaljer", "Read more": "Les mer", "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Les mer om hotellet", @@ -402,6 +407,5 @@ "uppercase letter": "stor bokstav", "{amount} out of {total}": "{amount} av {total}", "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}", - "{width} cm × {length} cm": "{width} cm × {length} cm" + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index b74e19c0b..01de7beaf 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -1,5 +1,7 @@ { "Included (based on availability)": "Ingår (baserat på tillgänglighet)", + "Total price (incl VAT)": "Totalpris (inkl moms)", + "{amount} {currency}/night per adult": "{amount} {currency}/natt per vuxen", "A destination or hotel name is needed to be able to search for a hotel room.": "Ett destinations- eller hotellnamn behövs för att kunna söka efter ett hotellrum.", "A photo of the room": "Ett foto av rummet", "ACCE": "Tillgänglighet", @@ -35,6 +37,7 @@ "Attractions": "Sevärdheter", "Back to scandichotels.com": "Tillbaka till scandichotels.com", "Bar": "Bar", + "Based on availability": "Baserat på tillgänglighet", "Bed type": "Sängtyp", "Birth date": "Födelsedatum", "Book": "Boka", @@ -240,12 +243,14 @@ "Points needed to stay on level": "Poäng som behövs för att hålla sig på nivå", "Previous": "Föregående", "Previous victories": "Tidigare segrar", + "Price details": "Prisdetaljer", "Proceed to login": "Fortsätt till inloggning", "Proceed to payment method": "Gå vidare till betalningsmetod", "Provide a payment card in the next step": "Ge oss dina betalkortdetaljer i nästa steg", "Public price from": "Offentligt pris från", "Public transport": "Kollektivtrafik", "Queen bed": "Queen size-säng", + "Rate details": "Detaljer om rumspriset", "Read more": "Läs mer", "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Läs mer om hotellet", @@ -405,6 +410,5 @@ "paying": "betalar", "uppercase letter": "stor bokstav", "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}", - "{width} cm × {length} cm": "{width} cm × {length} cm" + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index ec361b96c..00c404bfb 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -1,6 +1,10 @@ import { cache } from "react" import { Lang } from "@/constants/languages" +import { + GetRoomsAvailabilityInput, + HotelIncludeEnum, +} from "@/server/routers/hotels/input" import { serverClient } from "../server" @@ -53,12 +57,14 @@ export const getUserTracking = cache(async function getMemoizedUserTracking() { export const getHotelData = cache(async function getMemoizedHotelData( hotelId: string, language: string, - isCardOnlyPayment?: boolean + isCardOnlyPayment?: boolean, + include?: HotelIncludeEnum[] ) { return serverClient().hotel.hotelData.get({ hotelId, language, isCardOnlyPayment, + include, }) }) @@ -71,17 +77,9 @@ export const getRoomAvailability = cache( children, promotionCode, rateCode, - }: { - hotelId: string - adults: number - roomStayStartDate: string - roomStayEndDate: string - children?: string - promotionCode?: string - rateCode?: string - }) { + }: GetRoomsAvailabilityInput) { return serverClient().hotel.availability.rooms({ - hotelId: parseInt(hotelId), + hotelId, adults, roomStayStartDate, roomStayEndDate, diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index 2d69ab642..ff8368b2a 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -29,17 +29,26 @@ export const getRoomsAvailabilityInputSchema = z.object({ rateCode: z.string().optional(), }) +export type GetRoomsAvailabilityInput = z.input< + typeof getRoomsAvailabilityInputSchema +> + export const getRatesInputSchema = z.object({ hotelId: z.string(), }) -export const getlHotelDataInputSchema = z.object({ +export enum HotelIncludeEnum { + "RoomCategories", + "NearbyHotels", + "Restaurants", + "City", +} + +export const getHotelDataInputSchema = z.object({ hotelId: z.string(), language: z.string(), isCardOnlyPayment: z.boolean().optional(), - include: z - .array(z.enum(["RoomCategories", "NearbyHotels", "Restaurants", "City"])) - .optional(), + include: z.array(z.nativeEnum(HotelIncludeEnum)).optional(), }) export const getBreakfastPackageInput = z.object({ diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 6ad27655a..4aacd6ff2 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -32,9 +32,9 @@ import { } from "./schemas/packages" import { getBreakfastPackageInput, + getHotelDataInputSchema, getHotelInputSchema, getHotelsAvailabilityInputSchema, - getlHotelDataInputSchema, getRatesInputSchema, getRoomsAvailabilityInputSchema, } from "./input" @@ -584,7 +584,7 @@ export const hotelQueryRouter = router({ }), hotelData: router({ get: serviceProcedure - .input(getlHotelDataInputSchema) + .input(getHotelDataInputSchema) .query(async ({ ctx, input }) => { const { hotelId, language, include, isCardOnlyPayment } = input diff --git a/server/routers/hotels/schemas/room.ts b/server/routers/hotels/schemas/room.ts index 19f922db0..5a1480097 100644 --- a/server/routers/hotels/schemas/room.ts +++ b/server/routers/hotels/schemas/room.ts @@ -87,8 +87,11 @@ export const roomSchema = z name: data.attributes.name, occupancy: data.attributes.occupancy, roomSize: data.attributes.roomSize, + roomTypes: data.attributes.roomTypes, sortOrder: data.attributes.sortOrder, type: data.type, roomFacilities: data.attributes.roomFacilities, } }) + +export type RoomType = Pick, "roomTypes" | "name"> diff --git a/stores/enter-details.ts b/stores/enter-details.ts index 44289cc21..ccea01655 100644 --- a/stores/enter-details.ts +++ b/stores/enter-details.ts @@ -6,21 +6,20 @@ import { create, useStore } from "zustand" import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema" import { breakfastStoreSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema" import { detailsSchema } from "@/components/HotelReservation/EnterDetails/Details/schema" -import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast" +import type { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details" import { SidePeekEnum } from "@/types/components/hotelReservation/enterDetails/sidePeek" import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" -import { BedTypeEnum } from "@/types/enums/bedType" import { BreakfastPackageEnum } from "@/types/enums/breakfast" -import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" -import type { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details" const SESSION_STORAGE_KEY = "enterDetails" interface EnterDetailsState { userData: { - bedType: BedTypeEnum | undefined + bedType: string | undefined breakfast: BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST | undefined } & DetailsSchema roomData: BookingData diff --git a/types/components/hotelReservation/enterDetails/bookingData.ts b/types/components/hotelReservation/enterDetails/bookingData.ts index 4b4a72898..9a780fa66 100644 --- a/types/components/hotelReservation/enterDetails/bookingData.ts +++ b/types/components/hotelReservation/enterDetails/bookingData.ts @@ -5,23 +5,21 @@ interface Child { interface Room { adults: number - roomtypecode: string - ratecode: string - child: Child[] + roomtypecode?: string + ratecode?: string + child?: Child[] } - export interface BookingData { hotel: string - fromdate: string - todate: string + fromDate: string + toDate: string room: Room[] } export type RoomsData = { roomType: string - memberPrice: string | undefined - publicPrice: string | undefined + price: string adults: number - children: Child[] - cancellationText: string | undefined + children?: Child[] + cancellationText: string } diff --git a/types/components/hotelReservation/selectRate/selectRate.ts b/types/components/hotelReservation/selectRate/selectRate.ts index 553d09827..42ba91bde 100644 --- a/types/components/hotelReservation/selectRate/selectRate.ts +++ b/types/components/hotelReservation/selectRate/selectRate.ts @@ -7,7 +7,7 @@ export interface Child { interface Room { adults: number - roomcode?: string + roomtypecode?: string ratecode?: string child?: Child[] } From 7710d3f8f90c11bccd7d58e4cac0e8ef35ccad1a Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Fri, 25 Oct 2024 11:09:03 +0200 Subject: [PATCH 3/5] fix: make summary sticky --- .../(standard)/[step]/@hotelHeader/page.tsx | 5 +- .../(standard)/[step]/@sidePeek/page.tsx | 5 +- .../(standard)/[step]/@summary/page.tsx | 59 ++++++++------ .../(standard)/[step]/layout.module.css | 79 ++++++++++++++++++- .../(standard)/[step]/layout.tsx | 6 +- .../(standard)/[step]/page.tsx | 18 +++-- .../(standard)/layout.module.css | 1 - .../EnterDetails/BedType/index.tsx | 7 +- .../EnterDetails/Summary/index.tsx | 21 +++-- .../SelectRate/RoomSelection/utils.ts | 4 +- .../Form/ChoiceCard/_Card/card.ts | 2 +- lib/trpc/memoizedRequests/index.ts | 15 ++-- next.config.js | 4 +- stores/enter-details.ts | 43 +--------- .../enterDetails/bookingData.ts | 5 +- .../hotelReservation/selectRate/selectRate.ts | 3 +- 16 files changed, 170 insertions(+), 107 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx index 58a216006..75101475a 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@hotelHeader/page.tsx @@ -14,7 +14,10 @@ export default async function HotelHeader({ if (!searchParams.hotel) { redirect(home) } - const hotel = await getHotelData(searchParams.hotel, params.lang) + const hotel = await getHotelData({ + hotelId: searchParams.hotel, + language: params.lang, + }) if (!hotel?.data) { redirect(home) } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx index 13b770699..deca843c3 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@sidePeek/page.tsx @@ -13,7 +13,10 @@ export default async function HotelSidePeek({ if (!searchParams.hotel) { redirect(`/${params.lang}`) } - const hotel = await getHotelData(searchParams.hotel, params.lang) + const hotel = await getHotelData({ + hotelId: searchParams.hotel, + language: params.lang, + }) if (!hotel?.data) { redirect(`/${params.lang}`) } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx index 4a15e3994..4258d94d8 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx @@ -20,23 +20,31 @@ export default async function SummaryPage({ const { hotel, adults, children, roomTypeCode, rateCode, fromDate, toDate } = getQueryParamsForEnterDetails(selectRoomParams) - const user = await getProfileSafely() - const hotelData = await getHotelData(hotel, params.lang, undefined, [HotelIncludeEnum.RoomCategories]) - const availability = await getRoomAvailability({ - hotelId: parseInt(hotel), - adults, - children, - roomStayStartDate: fromDate, - roomStayEndDate: toDate, - }) + const [user, hotelData, availability] = await Promise.all([ + getProfileSafely(), + getHotelData({ + hotelId: hotel, + language: params.lang, + include: [HotelIncludeEnum.RoomCategories], + }), + getRoomAvailability({ + hotelId: parseInt(hotel), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + }), + ]) if (!hotelData?.data || !hotelData?.included || !availability) { console.error("No hotel or availability data", hotelData, availability) - // TODO: handle this case return null } + const cancellationText = + availability?.rateDefinitions.find((rate) => rate.rateCode === rateCode) + ?.cancellationText ?? "" const chosenRoom = availability.roomConfigurations.find( (availRoom) => availRoom.roomTypeCode === roomTypeCode ) @@ -47,28 +55,31 @@ export default async function SummaryPage({ return null } - const cancellationText = - availability?.rateDefinitions.find((rate) => rate.rateCode === rateCode) - ?.cancellationText ?? "" + const memberRate = chosenRoom.products.find( + (rate) => rate.productType.member?.rateCode === rateCode + )?.productType.member - const memberPrice = - chosenRoom.products.find( - (rate) => rate.productType.member?.rateCode === rateCode - )?.productType.member?.localPrice.pricePerStay ?? "0" + const publicRate = chosenRoom.products.find( + (rate) => rate.productType.public?.rateCode === rateCode + )?.productType.public - const publicPrice = - chosenRoom.products.find( - (rate) => rate.productType.public?.rateCode === rateCode - )?.productType.public?.localPrice.pricePerStay ?? "0" - - const price = user ? memberPrice : publicPrice + const prices = user + ? { + local: memberRate?.localPrice.pricePerStay, + euro: memberRate?.requestedPrice?.pricePerStay, + } + : { + local: publicRate?.localPrice.pricePerStay, + euro: publicRate?.requestedPrice?.pricePerStay, + } return ( {children} - + {sidePeek} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx index 44de099bf..adfcbc34a 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx @@ -20,7 +20,7 @@ import { getIntl } from "@/i18n" import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step" import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" -import type { LangParams, PageArgs, } from "@/types/params" +import type { LangParams, PageArgs } from "@/types/params" export function preload() { void getProfileSafely() @@ -34,12 +34,10 @@ function isValidStep(step: string): step is StepEnum { export default async function StepPage({ params, searchParams, -}: PageArgs< - LangParams & { step: StepEnum }, - SelectRateSearchParams ->) { +}: PageArgs) { + const { lang } = params if (!searchParams.hotel) { - redirect(`/${params.lang}`) + redirect(`/${lang}`) } void getBreakfastPackages(searchParams.hotel) @@ -64,7 +62,11 @@ export default async function StepPage({ rateCode }) - const hotelData = await getHotelData(hotelId, params.lang, undefined, [HotelIncludeEnum.RoomCategories]) + const hotelData = await getHotelData({ + hotelId, + language: lang, + include: [HotelIncludeEnum.RoomCategories], + }) const user = await getProfileSafely() const savedCreditCards = await getCreditCardsSafely() @@ -153,6 +155,6 @@ export default async function StepPage({ mustBeGuaranteed={mustBeGuaranteed} /> - + ) } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.module.css b/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.module.css index 0969a7151..1730ffa68 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.module.css @@ -1,4 +1,3 @@ .layout { - min-height: 100dvh; background-color: var(--Base-Background-Primary-Normal); } diff --git a/components/HotelReservation/EnterDetails/BedType/index.tsx b/components/HotelReservation/EnterDetails/BedType/index.tsx index 27c95d963..646ec1311 100644 --- a/components/HotelReservation/EnterDetails/BedType/index.tsx +++ b/components/HotelReservation/EnterDetails/BedType/index.tsx @@ -43,10 +43,6 @@ export default function BedType({ reValidateMode: "onChange", }) - const text = intl.formatMessage( - { id: "Included (based on availability)" }, - { b: (str) => {str} } - ) const completeStep = useEnterDetailsStore((state) => state.completeStep) const onSubmit = useCallback( @@ -71,7 +67,7 @@ export default function BedType({ {roomTypes.map((roomType) => { const width = roomType.size.max === roomType.size.min - ? roomType.size.max + ? `${roomType.size.min} cm` : `${roomType.size.min} cm - ${roomType.size.max} cm` return ( diff --git a/components/HotelReservation/EnterDetails/Summary/index.tsx b/components/HotelReservation/EnterDetails/Summary/index.tsx index c1bad51f4..094c9e860 100644 --- a/components/HotelReservation/EnterDetails/Summary/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/index.tsx @@ -17,6 +17,7 @@ import useLang from "@/hooks/useLang" import styles from "./summary.module.css" import { RoomsData } from "@/types/components/hotelReservation/enterDetails/bookingData" +import { BreakfastPackageEnum } from "@/types/enums/breakfast" export default function Summary({ isMember, @@ -25,8 +26,8 @@ export default function Summary({ isMember: boolean room: RoomsData }) { - const [chosenBed, setChosenBed] = useState() - const [chosenBreakfast, setCosenBreakfast] = useState() + const [chosenBed, setChosenBed] = useState() + const [chosenBreakfast, setCosenBreakfast] = useState() const intl = useIntl() const lang = useLang() const { fromDate, toDate, bedType, breakfast } = useEnterDetailsStore( @@ -54,7 +55,11 @@ export default function Summary({ useEffect(() => { setChosenBed(bedType) - setCosenBreakfast(breakfast) + if (breakfast === BreakfastPackageEnum.NO_BREAKFAST) { + setCosenBreakfast("No breakfast") + } else if (breakfast) { + setCosenBreakfast("Breakfast buffet") + } }, [bedType, breakfast]) return ( @@ -75,7 +80,7 @@ export default function Summary({ {intl.formatMessage( { id: "{amount} {currency}" }, - { amount: room.price, currency: "SEK" } + { amount: room.localPrice, currency: "SEK" } )} @@ -121,7 +126,9 @@ export default function Summary({ {chosenBreakfast ? (
- {chosenBreakfast} + + {intl.formatMessage({ id: chosenBreakfast })} + {intl.formatMessage( { id: "{amount} {currency}" }, @@ -149,14 +156,14 @@ export default function Summary({ {intl.formatMessage( { id: "{amount} {currency}" }, - { amount: room.price, currency: "SEK" } // TODO: calculate total price + { amount: room.localPrice, currency: "SEK" } // TODO: calculate total price )} {intl.formatMessage({ id: "Approx." })}{" "} {intl.formatMessage( { id: "{amount} {currency}" }, - { amount: "455", currency: "EUR" } + { amount: room.euroPrice, currency: "EUR" } )}
diff --git a/components/HotelReservation/SelectRate/RoomSelection/utils.ts b/components/HotelReservation/SelectRate/RoomSelection/utils.ts index 24309d355..1fe62f39c 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/utils.ts +++ b/components/HotelReservation/SelectRate/RoomSelection/utils.ts @@ -16,8 +16,8 @@ export function getQueryParamsForEnterDetails(searchParams: URLSearchParams) { return { ...selectRoomParamsObject, adults: room[0].adults, // TODO: Handle multiple rooms - children: room[0].child?.length.toString(), // TODO: Handle multiple rooms - roomTypeCode: room[0].roomtypecode, + children: room[0].child?.length.toString(), // TODO: Handle multiple rooms and children + roomTypeCode: room[0].roomtype, rateCode: room[0].ratecode, } } diff --git a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts index 145116409..8fe8476d3 100644 --- a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts +++ b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts @@ -23,7 +23,7 @@ interface ListCardProps extends BaseCardProps { interface TextCardProps extends BaseCardProps { list?: never - text: React.ReactNode + text?: React.ReactNode } export type CardProps = ListCardProps | TextCardProps diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index 00c404bfb..547ff7874 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -54,12 +54,17 @@ export const getUserTracking = cache(async function getMemoizedUserTracking() { return serverClient().user.tracking() }) -export const getHotelData = cache(async function getMemoizedHotelData( - hotelId: string, - language: string, - isCardOnlyPayment?: boolean, +export const getHotelData = cache(async function getMemoizedHotelData({ + hotelId, + language, + isCardOnlyPayment, + include, +}: { + hotelId: string + language: string + isCardOnlyPayment?: boolean include?: HotelIncludeEnum[] -) { +}) { return serverClient().hotel.hotelData.get({ hotelId, language, diff --git a/next.config.js b/next.config.js index f91af6594..fae418d6c 100644 --- a/next.config.js +++ b/next.config.js @@ -87,12 +87,12 @@ const nextConfig = { // value: undefined, // }, // { - // key: "fromdate", + // key: "fromDate", // type: "query", // value: undefined, // }, // { - // key: "todate", + // key: "toDate", // type: "query", // value: undefined, // }, diff --git a/stores/enter-details.ts b/stores/enter-details.ts index ccea01655..1c2c6b8d8 100644 --- a/stores/enter-details.ts +++ b/stores/enter-details.ts @@ -6,7 +6,7 @@ import { create, useStore } from "zustand" import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema" import { breakfastStoreSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema" import { detailsSchema } from "@/components/HotelReservation/EnterDetails/Details/schema" -import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast" @@ -37,22 +37,6 @@ interface EnterDetailsState { closeSidePeek: () => void } -function getUpdatedValue( - searchParams: URLSearchParams, - key: string, - defaultValue: T -): T { - const value = searchParams.get(key) - if (value === null) return defaultValue - if (typeof defaultValue === "number") - return parseInt(value, 10) as unknown as T - if (typeof defaultValue === "boolean") - return (value === "true") as unknown as T - if (defaultValue instanceof Date) return new Date(value) as unknown as T - - return value as unknown as T -} - export function initEditDetailsState( currentStep: StepEnum, searchParams: ReadonlyURLSearchParams @@ -62,32 +46,9 @@ export function initEditDetailsState( ? sessionStorage.getItem(SESSION_STORAGE_KEY) : null - const today = new Date() - const tomorrow = new Date() - tomorrow.setDate(today.getDate() + 1) - let roomData: BookingData if (searchParams?.size) { - roomData = getHotelReservationQueryParams(searchParams) - - roomData.room = roomData.room.map((room, index) => ({ - ...room, - adults: getUpdatedValue( - searchParams, - `room[${index}].adults`, - room.adults - ), - roomtypecode: getUpdatedValue( - searchParams, - `room[${index}].roomtypecode`, - room.roomtypecode - ), - ratecode: getUpdatedValue( - searchParams, - `room[${index}].ratecode`, - room.ratecode - ), - })) + roomData = getQueryParamsForEnterDetails(searchParams) } const defaultUserData: EnterDetailsState["userData"] = { diff --git a/types/components/hotelReservation/enterDetails/bookingData.ts b/types/components/hotelReservation/enterDetails/bookingData.ts index 9a780fa66..058d67990 100644 --- a/types/components/hotelReservation/enterDetails/bookingData.ts +++ b/types/components/hotelReservation/enterDetails/bookingData.ts @@ -5,7 +5,7 @@ interface Child { interface Room { adults: number - roomtypecode?: string + roomtype?: string ratecode?: string child?: Child[] } @@ -18,7 +18,8 @@ export interface BookingData { export type RoomsData = { roomType: string - price: string + localPrice: string + euroPrice: string adults: number children?: Child[] cancellationText: string diff --git a/types/components/hotelReservation/selectRate/selectRate.ts b/types/components/hotelReservation/selectRate/selectRate.ts index 42ba91bde..fd1771955 100644 --- a/types/components/hotelReservation/selectRate/selectRate.ts +++ b/types/components/hotelReservation/selectRate/selectRate.ts @@ -7,8 +7,9 @@ export interface Child { interface Room { adults: number - roomtypecode?: string + roomtype?: string ratecode?: string + counterratecode?: string child?: Child[] } From 46622d0515c96a982699596fdb5ed4f271a79c23 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Wed, 30 Oct 2024 15:21:51 +0100 Subject: [PATCH 4/5] fix: move crunching of data to trpc layer --- .../(standard)/[step]/@summary/page.tsx | 76 ++++---- .../(standard)/[step]/page.tsx | 60 ++++--- .../EnterDetails/BedType/index.tsx | 24 +-- .../EnterDetails/Summary/index.tsx | 71 +++++--- .../Form/ChoiceCard/_Card/card.ts | 10 +- lib/trpc/memoizedRequests/index.ts | 9 + server/routers/hotels/input.ts | 17 ++ server/routers/hotels/query.ts | 166 ++++++++++++++++++ .../hotelReservation/enterDetails/bedType.ts | 12 ++ .../enterDetails/bookingData.ts | 9 +- 10 files changed, 345 insertions(+), 109 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx index 4258d94d8..f1e1897e1 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx @@ -1,13 +1,12 @@ +import { notFound } from "next/navigation" + import { - getHotelData, getProfileSafely, - getRoomAvailability, + getSelectedRoomAvailability, } from "@/lib/trpc/memoizedRequests" -import { HotelIncludeEnum } from "@/server/routers/hotels/input" import Summary from "@/components/HotelReservation/EnterDetails/Summary" import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" -import { formatNumber } from "@/utils/format" import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import { LangParams, PageArgs, SearchParams } from "@/types/params" @@ -20,68 +19,61 @@ export default async function SummaryPage({ const { hotel, adults, children, roomTypeCode, rateCode, fromDate, toDate } = 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(), - getHotelData({ - hotelId: hotel, - language: params.lang, - include: [HotelIncludeEnum.RoomCategories], - }), - getRoomAvailability({ + getSelectedRoomAvailability({ hotelId: parseInt(hotel), adults, children, roomStayStartDate: fromDate, roomStayEndDate: toDate, + rateCode, + roomTypeCode, }), ]) - if (!hotelData?.data || !hotelData?.included || !availability) { - console.error("No hotel or availability data", hotelData, availability) + if (!availability) { + console.error("No hotel or availability data", availability) // TODO: handle this case 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 ? { - local: memberRate?.localPrice.pricePerStay, - euro: memberRate?.requestedPrice?.pricePerStay, + local: { + 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, - euro: publicRate?.requestedPrice?.pricePerStay, + local: { + price: availability.publicRate?.localPrice.pricePerStay, + currency: availability.publicRate?.localPrice.currency, + }, + euro: { + price: availability.publicRate?.requestedPrice?.pricePerStay, + currency: availability.publicRate?.requestedPrice?.currency, + }, } return ( ) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx index adfcbc34a..dc07ed3f0 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx @@ -6,6 +6,7 @@ import { getHotelData, getProfileSafely, getRoomAvailability, + getSelectedRoomAvailability, } from "@/lib/trpc/memoizedRequests" import { HotelIncludeEnum } from "@/server/routers/hotels/input" @@ -36,9 +37,7 @@ export default async function StepPage({ searchParams, }: PageArgs) { const { lang } = params - if (!searchParams.hotel) { - redirect(`/${lang}`) - } + void getBreakfastPackages(searchParams.hotel) const intl = await getIntl() @@ -62,24 +61,37 @@ export default async function StepPage({ rateCode }) - const hotelData = await getHotelData({ - hotelId, - language: lang, - include: [HotelIncludeEnum.RoomCategories], - }) - const user = await getProfileSafely() - const savedCreditCards = await getCreditCardsSafely() - const breakfastPackages = await getBreakfastPackages(searchParams.hotel) + if (!rateCode || !roomTypeCode) { + return notFound() + } - const roomAvailability = await getRoomAvailability({ - hotelId: parseInt(hotelId), - adults, - children, - roomStayStartDate: fromDate, - roomStayEndDate: toDate, - rateCode - }) + const [ + hotelData, + user, + savedCreditCards, + breakfastPackages, + roomAvailability, + ] = 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) { return notFound() @@ -100,10 +112,8 @@ export default async function StepPage({ id: "Select payment method", }) - const availableRoom = roomAvailability?.roomConfigurations - .filter((room) => room.status === "Available") - .find((room) => room.roomTypeCode === roomTypeCode)?.roomType - const roomTypes = hotelData.included + const availableRoom = roomAvailability.selectedRoom?.roomType + const bedTypes = hotelData.included ?.find((room) => room.name === availableRoom) ?.roomTypes.map((room) => ({ description: room.mainBed.description, @@ -116,13 +126,13 @@ export default async function StepPage({ {/* TODO: How to handle no beds found? */} - {roomTypes ? ( + {bedTypes ? ( - + ) : null} diff --git a/components/HotelReservation/EnterDetails/BedType/index.tsx b/components/HotelReservation/EnterDetails/BedType/index.tsx index 646ec1311..f822024c1 100644 --- a/components/HotelReservation/EnterDetails/BedType/index.tsx +++ b/components/HotelReservation/EnterDetails/BedType/index.tsx @@ -14,28 +14,20 @@ import { bedTypeSchema } from "./schema" 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({ - roomTypes, -}: { - roomTypes: { - description: string - size: { - min: number - max: number - } - value: string - }[] -}) { +export default function BedType({ bedTypes }: BedTypeProps) { const intl = useIntl() const bedType = useEnterDetailsStore((state) => state.userData.bedType) const methods = useForm({ defaultValues: bedType ? { - bedType, - } + bedType, + } : undefined, criteriaMode: "all", mode: "all", @@ -64,7 +56,7 @@ export default function BedType({ return (
- {roomTypes.map((roomType) => { + {bedTypes.map((roomType) => { const width = roomType.size.max === roomType.size.min ? `${roomType.size.min} cm` diff --git a/components/HotelReservation/EnterDetails/Summary/index.tsx b/components/HotelReservation/EnterDetails/Summary/index.tsx index 094c9e860..84c6280af 100644 --- a/components/HotelReservation/EnterDetails/Summary/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/index.tsx @@ -13,10 +13,12 @@ import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import useLang from "@/hooks/useLang" +import { formatNumber } from "@/utils/format" import styles from "./summary.module.css" import { RoomsData } from "@/types/components/hotelReservation/enterDetails/bookingData" +import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast" import { BreakfastPackageEnum } from "@/types/enums/breakfast" export default function Summary({ @@ -27,15 +29,15 @@ export default function Summary({ room: RoomsData }) { const [chosenBed, setChosenBed] = useState() - const [chosenBreakfast, setCosenBreakfast] = useState() + 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 From 317619ea78b09964099a2d23aedb49d8f888ee0e Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Wed, 30 Oct 2024 16:39:04 +0100 Subject: [PATCH 5/5] fix: preload requests to avoid needing to promise.all --- .../(standard)/[step]/@summary/page.tsx | 22 +++---- .../(standard)/[step]/page.tsx | 61 ++++++++----------- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx index f1e1897e1..38444447f 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/@summary/page.tsx @@ -24,18 +24,16 @@ export default async function SummaryPage({ return notFound() } - const [user, availability] = await Promise.all([ - getProfileSafely(), - getSelectedRoomAvailability({ - hotelId: parseInt(hotel), - adults, - children, - roomStayStartDate: fromDate, - roomStayEndDate: toDate, - rateCode, - roomTypeCode, - }), - ]) + const availability = await getSelectedRoomAvailability({ + hotelId: parseInt(hotel), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode, + roomTypeCode, + }) + const user = await getProfileSafely() if (!availability) { console.error("No hotel or availability data", availability) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx index dc07ed3f0..e5bf9f73b 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/[step]/page.tsx @@ -52,46 +52,37 @@ export default async function StepPage({ toDate, } = getQueryParamsForEnterDetails(selectRoomParams) - void getRoomAvailability({ - hotelId: parseInt(hotelId), - adults, - children, - roomStayStartDate: fromDate, - roomStayEndDate: toDate, - rateCode - }) - - if (!rateCode || !roomTypeCode) { return notFound() } - const [ - hotelData, - user, - savedCreditCards, - breakfastPackages, - roomAvailability, - ] = await Promise.all([ - getHotelData({ - hotelId, - language: lang, - include: [HotelIncludeEnum.RoomCategories], - }), + void getSelectedRoomAvailability({ + hotelId: parseInt(searchParams.hotel), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode, + roomTypeCode, + }) - getProfileSafely(), - getCreditCardsSafely(), - getBreakfastPackages(searchParams.hotel), - getSelectedRoomAvailability({ - hotelId: parseInt(searchParams.hotel), - adults, - children, - roomStayStartDate: fromDate, - roomStayEndDate: toDate, - rateCode, - roomTypeCode, - }), - ]) + const hotelData = await getHotelData({ + hotelId, + language: lang, + include: [HotelIncludeEnum.RoomCategories], + }) + const roomAvailability = await getSelectedRoomAvailability({ + hotelId: parseInt(searchParams.hotel), + adults, + children, + roomStayStartDate: fromDate, + roomStayEndDate: toDate, + rateCode, + roomTypeCode, + }) + const breakfastPackages = await getBreakfastPackages(searchParams.hotel) + const user = await getProfileSafely() + const savedCreditCards = await getCreditCardsSafely() if (!isValidStep(params.step) || !hotelData || !roomAvailability) { return notFound()