From 5204146ba57fab6729145ef4183466c09beed1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Mon, 18 Nov 2024 14:52:22 +0100 Subject: [PATCH 001/103] fix: load room availabiltity separately --- .../HotelInfoCard/NoRoomsAlert.module.css | 5 + .../SelectRate/HotelInfoCard/NoRoomsAlert.tsx | 73 ++++++++++++++ .../SelectRate/Rooms/RoomsContainer.tsx | 96 +++++++++++++++++++ utils/safeTry.ts | 11 +++ 4 files changed, 185 insertions(+) create mode 100644 components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css create mode 100644 components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx create mode 100644 components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx create mode 100644 utils/safeTry.ts diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css new file mode 100644 index 000000000..3bb1f51a7 --- /dev/null +++ b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css @@ -0,0 +1,5 @@ +.hotelAlert { + max-width: var(--max-width-navigation); + margin: 0 auto; + padding-top: var(--Spacing-x-one-and-half); +} diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx new file mode 100644 index 000000000..bfca1364f --- /dev/null +++ b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx @@ -0,0 +1,73 @@ +import { Lang } from "@/constants/languages" +import { dt } from "@/lib/dt" +import { getRoomAvailability } from "@/lib/trpc/memoizedRequests" + +import Alert from "@/components/TempDesignSystem/Alert" +import { getIntl } from "@/i18n" +import { safeTry } from "@/utils/safeTry" + +import { generateChildrenString } from "../RoomSelection/utils" + +import styles from "./NoRoomsAlert.module.css" + +import { Child } from "@/types/components/hotelReservation/selectRate/selectRate" +import { AlertTypeEnum } from "@/types/enums/alert" + +type Props = { + hotelId: number + lang: Lang + adultCount: number + childArray: Child[] + fromDate: Date + toDate: Date +} + +export async function NoRoomsAlert({ + hotelId, + fromDate, + toDate, + childArray, + adultCount, + lang, +}: Props) { + const [availability, availabilityError] = await safeTry( + getRoomAvailability({ + hotelId: hotelId, + roomStayStartDate: dt(fromDate).format("YYYY-MM-DD"), + roomStayEndDate: dt(toDate).format("YYYY-MM-DD"), + adults: adultCount, + children: generateChildrenString(childArray), // TODO: Handle multiple rooms, + }) + ) + + if (!availability) { + return null + } + + if (availabilityError) { + // TODO: Handle error + } + + const noRoomsAvailable = availability.roomConfigurations.reduce( + (acc, room) => { + return acc && room.status === "NotAvailable" + }, + true + ) + + if (!noRoomsAvailable) { + return null + } + + const intl = await getIntl(lang) + return ( +
+ +
+ ) +} diff --git a/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx new file mode 100644 index 000000000..d0c662431 --- /dev/null +++ b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx @@ -0,0 +1,96 @@ +import { Lang } from "@/constants/languages" +import { dt } from "@/lib/dt" +import { + getHotelData, + getPackages, + getProfileSafely, + getRoomAvailability, +} from "@/lib/trpc/memoizedRequests" +import { serverClient } from "@/lib/trpc/server" + +import { safeTry } from "@/utils/safeTry" + +import { generateChildrenString } from "../RoomSelection/utils" +import Rooms from "." + +import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" +import { Child } from "@/types/components/hotelReservation/selectRate/selectRate" + +export type Props = { + hotelId: number + fromDate: Date + toDate: Date + adultCount: number + childArray: Child[] + lang: Lang +} + +export async function RoomsContainer({ + hotelId, + fromDate, + toDate, + adultCount, + childArray, + lang, +}: Props) { + const user = await getProfileSafely() + + const fromDateString = dt(fromDate).format("YYYY-MM-DD") + const toDateString = dt(toDate).format("YYYY-MM-DD") + + const hotelDataPromise = safeTry( + getHotelData({ hotelId: hotelId.toString(), language: lang }) + ) + + const packagesPromise = safeTry( + getPackages({ + hotelId: hotelId.toString(), + startDate: fromDateString, + endDate: toDateString, + adults: adultCount, + children: childArray.length > 0 ? childArray.length : undefined, + packageCodes: [ + RoomPackageCodeEnum.ACCESSIBILITY_ROOM, + RoomPackageCodeEnum.PET_ROOM, + RoomPackageCodeEnum.ALLERGY_ROOM, + ], + }) + ) + + const roomsAvailabilityPromise = safeTry( + getRoomAvailability({ + hotelId: hotelId, + roomStayStartDate: fromDateString, + roomStayEndDate: toDateString, + adults: adultCount, + children: generateChildrenString(childArray), + }) + ) + + const [hotelData, hotelDataError] = await hotelDataPromise + const [packages, packagesError] = await packagesPromise + const [roomsAvailability, roomsAvailabilityError] = + await roomsAvailabilityPromise + + if (packagesError) { + console.error("packagesError", { ...packagesError }) + + return ( +
Unable to get packages. {JSON.stringify({ ...packagesError })}
+ ) + } + + if (roomsAvailabilityError || !roomsAvailability) { + console.error("roomsAvailabilityError", roomsAvailabilityError) + return
Unable to get room availability
+ } + + return ( + + ) +} diff --git a/utils/safeTry.ts b/utils/safeTry.ts new file mode 100644 index 000000000..d3bb81596 --- /dev/null +++ b/utils/safeTry.ts @@ -0,0 +1,11 @@ +export type SafeTryResult = Promise< + [T, undefined] | [undefined, Error | unknown] +> + +export async function safeTry(func: Promise): SafeTryResult { + try { + return [await func, undefined] + } catch (err) { + return [undefined, err] + } +} From 8c3715a5b33c474bb306ad957407dafd0af08e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Mon, 18 Nov 2024 15:25:46 +0100 Subject: [PATCH 002/103] fix: load room availability separately --- .../(standard)/select-rate/page.tsx | 107 +++++++----------- .../SelectRate/HotelInfoCard/index.tsx | 71 +++++++----- 2 files changed, 80 insertions(+), 98 deletions(-) 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 fd9db4d6c..5682773c3 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -1,22 +1,16 @@ import { notFound } from "next/navigation" +import { Suspense } from "react" import { dt } from "@/lib/dt" -import { - getHotelData, - getLocations, - getProfileSafely, -} from "@/lib/trpc/memoizedRequests" -import { serverClient } from "@/lib/trpc/server" +import { getHotelData, getLocations } from "@/lib/trpc/memoizedRequests" +import LoadingSpinner from "@/components/Current/LoadingSpinner" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" -import Rooms from "@/components/HotelReservation/SelectRate/Rooms" -import { - generateChildrenString, - getHotelReservationQueryParams, -} from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { RoomsContainer } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainer" +import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { setLang } from "@/i18n/serverContext" +import { safeTry } from "@/utils/safeTry" -import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import type { LangParams, PageArgs } from "@/types/params" @@ -45,71 +39,48 @@ export default async function SelectRatePage({ return notFound() } - const validFromDate = + const fromDate = searchParams.fromDate && dt(searchParams.fromDate).isAfter(dt().subtract(1, "day")) - ? searchParams.fromDate - : dt().utc().format("YYYY-MM-DD") - const validToDate = - searchParams.toDate && dt(searchParams.toDate).isAfter(validFromDate) - ? searchParams.toDate - : dt().utc().add(1, "day").format("YYYY-MM-DD") + ? dt(searchParams.fromDate) + : dt().utc() + const toDate = + searchParams.toDate && dt(searchParams.toDate).isAfter(fromDate) + ? dt(searchParams.toDate) + : dt().utc().add(1, "day") + const adults = selectRoomParamsObject.room[0].adults || 1 // TODO: Handle multiple rooms - const childrenCount = selectRoomParamsObject.room[0].child?.length - const children = selectRoomParamsObject.room[0].child - ? generateChildrenString(selectRoomParamsObject.room[0].child) - : undefined // TODO: Handle multiple rooms + const children = selectRoomParamsObject.room[0].child // TODO: Handle multiple rooms - const [hotelData, roomsAvailability, packages, user] = await Promise.all([ - getHotelData({ hotelId: searchParams.hotel, language: params.lang }), - serverClient().hotel.availability.rooms({ - hotelId: parseInt(searchParams.hotel, 10), - roomStayStartDate: validFromDate, - roomStayEndDate: validToDate, - adults, - children, - }), - serverClient().hotel.packages.get({ - hotelId: searchParams.hotel, - startDate: searchParams.fromDate, - endDate: searchParams.toDate, - adults, - children: childrenCount, - packageCodes: [ - RoomPackageCodeEnum.ACCESSIBILITY_ROOM, - RoomPackageCodeEnum.PET_ROOM, - RoomPackageCodeEnum.ALLERGY_ROOM, - ], - }), - getProfileSafely(), - ]) - - if (!roomsAvailability) { - return "No rooms found" // TODO: Add a proper error message - } - - if (!hotelData) { - return "No hotel data found" // TODO: Add a proper error message - } - - const roomCategories = hotelData?.included - - const noRoomsAvailable = roomsAvailability.roomConfigurations.reduce( - (acc, room) => { - return acc && room.status === "NotAvailable" - }, - true + const [hotelData, hotelDataError] = await safeTry( + getHotelData({ hotelId: searchParams.hotel, language: params.lang }) ) + if (!hotelData && !hotelDataError) { + return notFound() + } + + const hotelId = +searchParams.hotel return ( <> - - + }> + + ) } diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx index 946b82ad2..92eebc27d 100644 --- a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx +++ b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx @@ -1,7 +1,8 @@ -"use client" -import { useEffect } from "react" +import { Suspense, useEffect } from "react" import { useIntl } from "react-intl" +import { Lang } from "@/constants/languages" +import { getHotelData } from "@/lib/trpc/memoizedRequests" import useRoomAvailableStore from "@/stores/roomAvailability" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" @@ -11,38 +12,54 @@ import Divider from "@/components/TempDesignSystem/Divider" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" +import { getIntl } from "@/i18n" import ReadMore from "../../ReadMore" import TripAdvisorChip from "../../TripAdvisorChip" +import { NoRoomsAlert } from "./NoRoomsAlert" import styles from "./hotelInfoCard.module.css" -import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCardProps" -import { AlertTypeEnum } from "@/types/enums/alert" +import { Child } from "@/types/components/hotelReservation/selectRate/selectRate" + +type Props = { + hotelId: number + lang: Lang + fromDate: Date + toDate: Date + adultCount: number + childArray: Child[] +} + +export default async function HotelInfoCard({ + hotelId, + lang, + ...props +}: Props) { + const hotelData = await getHotelData({ + hotelId: hotelId.toString(), + language: lang, + }) -export default function HotelInfoCard({ - hotelData, - noAvailability = false, -}: HotelInfoCardProps) { const hotelAttributes = hotelData?.data.attributes - const intl = useIntl() + const intl = await getIntl() - const noRoomsAvailable = useRoomAvailableStore( - (state) => state.noRoomsAvailable - ) - const setNoRoomsAvailable = useRoomAvailableStore( - (state) => state.setNoRoomsAvailable - ) + // const noRoomsAvailable = useRoomAvailableStore( + // (state) => state.noRoomsAvailable + // ) + // const setNoRoomsAvailable = useRoomAvailableStore( + // (state) => state.setNoRoomsAvailable + // ) const sortedFacilities = hotelAttributes?.detailedFacilities .sort((a, b) => b.sortOrder - a.sortOrder) .slice(0, 5) - useEffect(() => { - if (noAvailability) { - setNoRoomsAvailable() - } - }, [noAvailability, setNoRoomsAvailable]) + // useEffect(() => { + // if (noAvailability) { + // setNoRoomsAvailable() + // } + // }, [noAvailability, setNoRoomsAvailable]) return (
@@ -117,16 +134,10 @@ export default function HotelInfoCard({ ) })} - {noRoomsAvailable ? ( -
- -
- ) : null} + + + +
) } From 260be9b6412dd1eea947b46f66008fb296c75639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Mon, 18 Nov 2024 14:52:22 +0100 Subject: [PATCH 003/103] fix: load room availabiltity separately --- .../HotelInfoCard/NoRoomsAlert.module.css | 5 + .../SelectRate/HotelInfoCard/NoRoomsAlert.tsx | 73 ++++++++++++++ .../SelectRate/Rooms/RoomsContainer.tsx | 96 +++++++++++++++++++ utils/safeTry.ts | 11 +++ 4 files changed, 185 insertions(+) create mode 100644 components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css create mode 100644 components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx create mode 100644 components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx create mode 100644 utils/safeTry.ts diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css new file mode 100644 index 000000000..3bb1f51a7 --- /dev/null +++ b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.module.css @@ -0,0 +1,5 @@ +.hotelAlert { + max-width: var(--max-width-navigation); + margin: 0 auto; + padding-top: var(--Spacing-x-one-and-half); +} diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx new file mode 100644 index 000000000..bfca1364f --- /dev/null +++ b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx @@ -0,0 +1,73 @@ +import { Lang } from "@/constants/languages" +import { dt } from "@/lib/dt" +import { getRoomAvailability } from "@/lib/trpc/memoizedRequests" + +import Alert from "@/components/TempDesignSystem/Alert" +import { getIntl } from "@/i18n" +import { safeTry } from "@/utils/safeTry" + +import { generateChildrenString } from "../RoomSelection/utils" + +import styles from "./NoRoomsAlert.module.css" + +import { Child } from "@/types/components/hotelReservation/selectRate/selectRate" +import { AlertTypeEnum } from "@/types/enums/alert" + +type Props = { + hotelId: number + lang: Lang + adultCount: number + childArray: Child[] + fromDate: Date + toDate: Date +} + +export async function NoRoomsAlert({ + hotelId, + fromDate, + toDate, + childArray, + adultCount, + lang, +}: Props) { + const [availability, availabilityError] = await safeTry( + getRoomAvailability({ + hotelId: hotelId, + roomStayStartDate: dt(fromDate).format("YYYY-MM-DD"), + roomStayEndDate: dt(toDate).format("YYYY-MM-DD"), + adults: adultCount, + children: generateChildrenString(childArray), // TODO: Handle multiple rooms, + }) + ) + + if (!availability) { + return null + } + + if (availabilityError) { + // TODO: Handle error + } + + const noRoomsAvailable = availability.roomConfigurations.reduce( + (acc, room) => { + return acc && room.status === "NotAvailable" + }, + true + ) + + if (!noRoomsAvailable) { + return null + } + + const intl = await getIntl(lang) + return ( +
+ +
+ ) +} diff --git a/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx new file mode 100644 index 000000000..d0c662431 --- /dev/null +++ b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx @@ -0,0 +1,96 @@ +import { Lang } from "@/constants/languages" +import { dt } from "@/lib/dt" +import { + getHotelData, + getPackages, + getProfileSafely, + getRoomAvailability, +} from "@/lib/trpc/memoizedRequests" +import { serverClient } from "@/lib/trpc/server" + +import { safeTry } from "@/utils/safeTry" + +import { generateChildrenString } from "../RoomSelection/utils" +import Rooms from "." + +import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" +import { Child } from "@/types/components/hotelReservation/selectRate/selectRate" + +export type Props = { + hotelId: number + fromDate: Date + toDate: Date + adultCount: number + childArray: Child[] + lang: Lang +} + +export async function RoomsContainer({ + hotelId, + fromDate, + toDate, + adultCount, + childArray, + lang, +}: Props) { + const user = await getProfileSafely() + + const fromDateString = dt(fromDate).format("YYYY-MM-DD") + const toDateString = dt(toDate).format("YYYY-MM-DD") + + const hotelDataPromise = safeTry( + getHotelData({ hotelId: hotelId.toString(), language: lang }) + ) + + const packagesPromise = safeTry( + getPackages({ + hotelId: hotelId.toString(), + startDate: fromDateString, + endDate: toDateString, + adults: adultCount, + children: childArray.length > 0 ? childArray.length : undefined, + packageCodes: [ + RoomPackageCodeEnum.ACCESSIBILITY_ROOM, + RoomPackageCodeEnum.PET_ROOM, + RoomPackageCodeEnum.ALLERGY_ROOM, + ], + }) + ) + + const roomsAvailabilityPromise = safeTry( + getRoomAvailability({ + hotelId: hotelId, + roomStayStartDate: fromDateString, + roomStayEndDate: toDateString, + adults: adultCount, + children: generateChildrenString(childArray), + }) + ) + + const [hotelData, hotelDataError] = await hotelDataPromise + const [packages, packagesError] = await packagesPromise + const [roomsAvailability, roomsAvailabilityError] = + await roomsAvailabilityPromise + + if (packagesError) { + console.error("packagesError", { ...packagesError }) + + return ( +
Unable to get packages. {JSON.stringify({ ...packagesError })}
+ ) + } + + if (roomsAvailabilityError || !roomsAvailability) { + console.error("roomsAvailabilityError", roomsAvailabilityError) + return
Unable to get room availability
+ } + + return ( + + ) +} diff --git a/utils/safeTry.ts b/utils/safeTry.ts new file mode 100644 index 000000000..d3bb81596 --- /dev/null +++ b/utils/safeTry.ts @@ -0,0 +1,11 @@ +export type SafeTryResult = Promise< + [T, undefined] | [undefined, Error | unknown] +> + +export async function safeTry(func: Promise): SafeTryResult { + try { + return [await func, undefined] + } catch (err) { + return [undefined, err] + } +} From 4b6abb0a31358d6e4090531b8239f996f844181b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Mon, 18 Nov 2024 15:25:46 +0100 Subject: [PATCH 004/103] fix: load room availability separately --- .../(standard)/select-rate/page.tsx | 107 +++++++----------- .../SelectRate/HotelInfoCard/index.tsx | 71 +++++++----- 2 files changed, 80 insertions(+), 98 deletions(-) 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 fd9db4d6c..5682773c3 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -1,22 +1,16 @@ import { notFound } from "next/navigation" +import { Suspense } from "react" import { dt } from "@/lib/dt" -import { - getHotelData, - getLocations, - getProfileSafely, -} from "@/lib/trpc/memoizedRequests" -import { serverClient } from "@/lib/trpc/server" +import { getHotelData, getLocations } from "@/lib/trpc/memoizedRequests" +import LoadingSpinner from "@/components/Current/LoadingSpinner" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" -import Rooms from "@/components/HotelReservation/SelectRate/Rooms" -import { - generateChildrenString, - getHotelReservationQueryParams, -} from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import { RoomsContainer } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainer" +import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { setLang } from "@/i18n/serverContext" +import { safeTry } from "@/utils/safeTry" -import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import type { LangParams, PageArgs } from "@/types/params" @@ -45,71 +39,48 @@ export default async function SelectRatePage({ return notFound() } - const validFromDate = + const fromDate = searchParams.fromDate && dt(searchParams.fromDate).isAfter(dt().subtract(1, "day")) - ? searchParams.fromDate - : dt().utc().format("YYYY-MM-DD") - const validToDate = - searchParams.toDate && dt(searchParams.toDate).isAfter(validFromDate) - ? searchParams.toDate - : dt().utc().add(1, "day").format("YYYY-MM-DD") + ? dt(searchParams.fromDate) + : dt().utc() + const toDate = + searchParams.toDate && dt(searchParams.toDate).isAfter(fromDate) + ? dt(searchParams.toDate) + : dt().utc().add(1, "day") + const adults = selectRoomParamsObject.room[0].adults || 1 // TODO: Handle multiple rooms - const childrenCount = selectRoomParamsObject.room[0].child?.length - const children = selectRoomParamsObject.room[0].child - ? generateChildrenString(selectRoomParamsObject.room[0].child) - : undefined // TODO: Handle multiple rooms + const children = selectRoomParamsObject.room[0].child // TODO: Handle multiple rooms - const [hotelData, roomsAvailability, packages, user] = await Promise.all([ - getHotelData({ hotelId: searchParams.hotel, language: params.lang }), - serverClient().hotel.availability.rooms({ - hotelId: parseInt(searchParams.hotel, 10), - roomStayStartDate: validFromDate, - roomStayEndDate: validToDate, - adults, - children, - }), - serverClient().hotel.packages.get({ - hotelId: searchParams.hotel, - startDate: searchParams.fromDate, - endDate: searchParams.toDate, - adults, - children: childrenCount, - packageCodes: [ - RoomPackageCodeEnum.ACCESSIBILITY_ROOM, - RoomPackageCodeEnum.PET_ROOM, - RoomPackageCodeEnum.ALLERGY_ROOM, - ], - }), - getProfileSafely(), - ]) - - if (!roomsAvailability) { - return "No rooms found" // TODO: Add a proper error message - } - - if (!hotelData) { - return "No hotel data found" // TODO: Add a proper error message - } - - const roomCategories = hotelData?.included - - const noRoomsAvailable = roomsAvailability.roomConfigurations.reduce( - (acc, room) => { - return acc && room.status === "NotAvailable" - }, - true + const [hotelData, hotelDataError] = await safeTry( + getHotelData({ hotelId: searchParams.hotel, language: params.lang }) ) + if (!hotelData && !hotelDataError) { + return notFound() + } + + const hotelId = +searchParams.hotel return ( <> - - + }> + + ) } diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx index 946b82ad2..92eebc27d 100644 --- a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx +++ b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx @@ -1,7 +1,8 @@ -"use client" -import { useEffect } from "react" +import { Suspense, useEffect } from "react" import { useIntl } from "react-intl" +import { Lang } from "@/constants/languages" +import { getHotelData } from "@/lib/trpc/memoizedRequests" import useRoomAvailableStore from "@/stores/roomAvailability" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" @@ -11,38 +12,54 @@ import Divider from "@/components/TempDesignSystem/Divider" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" +import { getIntl } from "@/i18n" import ReadMore from "../../ReadMore" import TripAdvisorChip from "../../TripAdvisorChip" +import { NoRoomsAlert } from "./NoRoomsAlert" import styles from "./hotelInfoCard.module.css" -import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCardProps" -import { AlertTypeEnum } from "@/types/enums/alert" +import { Child } from "@/types/components/hotelReservation/selectRate/selectRate" + +type Props = { + hotelId: number + lang: Lang + fromDate: Date + toDate: Date + adultCount: number + childArray: Child[] +} + +export default async function HotelInfoCard({ + hotelId, + lang, + ...props +}: Props) { + const hotelData = await getHotelData({ + hotelId: hotelId.toString(), + language: lang, + }) -export default function HotelInfoCard({ - hotelData, - noAvailability = false, -}: HotelInfoCardProps) { const hotelAttributes = hotelData?.data.attributes - const intl = useIntl() + const intl = await getIntl() - const noRoomsAvailable = useRoomAvailableStore( - (state) => state.noRoomsAvailable - ) - const setNoRoomsAvailable = useRoomAvailableStore( - (state) => state.setNoRoomsAvailable - ) + // const noRoomsAvailable = useRoomAvailableStore( + // (state) => state.noRoomsAvailable + // ) + // const setNoRoomsAvailable = useRoomAvailableStore( + // (state) => state.setNoRoomsAvailable + // ) const sortedFacilities = hotelAttributes?.detailedFacilities .sort((a, b) => b.sortOrder - a.sortOrder) .slice(0, 5) - useEffect(() => { - if (noAvailability) { - setNoRoomsAvailable() - } - }, [noAvailability, setNoRoomsAvailable]) + // useEffect(() => { + // if (noAvailability) { + // setNoRoomsAvailable() + // } + // }, [noAvailability, setNoRoomsAvailable]) return (
@@ -117,16 +134,10 @@ export default function HotelInfoCard({ ) })} - {noRoomsAvailable ? ( -
- -
- ) : null} + + + +
) } From 0c6371d805ceafed36aff0d3de8d6fb023ed0c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Tue, 19 Nov 2024 10:52:35 +0100 Subject: [PATCH 005/103] wip --- .../HotelReservation/SelectRate/HotelInfoCard/index.tsx | 4 +--- .../HotelReservation/SelectRate/Rooms/RoomsContainer.tsx | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx index 92eebc27d..0ac25e94c 100644 --- a/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx +++ b/components/HotelReservation/SelectRate/HotelInfoCard/index.tsx @@ -1,9 +1,7 @@ -import { Suspense, useEffect } from "react" -import { useIntl } from "react-intl" +import { Suspense } from "react" import { Lang } from "@/constants/languages" import { getHotelData } from "@/lib/trpc/memoizedRequests" -import useRoomAvailableStore from "@/stores/roomAvailability" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" import ImageGallery from "@/components/ImageGallery" diff --git a/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx index d0c662431..6d963e5b0 100644 --- a/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx +++ b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx @@ -6,7 +6,6 @@ import { getProfileSafely, getRoomAvailability, } from "@/lib/trpc/memoizedRequests" -import { serverClient } from "@/lib/trpc/server" import { safeTry } from "@/utils/safeTry" @@ -63,7 +62,8 @@ export async function RoomsContainer({ roomStayStartDate: fromDateString, roomStayEndDate: toDateString, adults: adultCount, - children: generateChildrenString(childArray), + children: + childArray.length > 0 ? generateChildrenString(childArray) : undefined, }) ) @@ -73,7 +73,7 @@ export async function RoomsContainer({ await roomsAvailabilityPromise if (packagesError) { - console.error("packagesError", { ...packagesError }) + console.error("packagesError", packagesError) return (
Unable to get packages. {JSON.stringify({ ...packagesError })}
From c4caccae5ac839adc75525f40123b8756813613d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Tue, 19 Nov 2024 13:40:51 +0100 Subject: [PATCH 006/103] WIP --- .../SelectRate/Rooms/index.tsx | 197 +++++++++++------- 1 file changed, 118 insertions(+), 79 deletions(-) diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 626664988..78528db3e 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -1,8 +1,6 @@ "use client" -import { useCallback, useState } from "react" - -import useRoomAvailableStore from "@/stores/roomAvailability" +import { useCallback, useMemo, useState } from "react" import RoomFilter from "../RoomFilter" import RoomSelection from "../RoomSelection" @@ -30,22 +28,9 @@ export default function Rooms({ const visibleRooms: RoomConfiguration[] = filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations) const [rateSummary, setRateSummary] = useState(null) - const [rooms, setRooms] = useState({ - ...roomsAvailability, - roomConfigurations: visibleRooms, - }) const [selectedPackages, setSelectedPackages] = useState( [] ) - const noRoomsAvailable = useRoomAvailableStore( - (state) => state.noRoomsAvailable - ) - const setNoRoomsAvailable = useRoomAvailableStore( - (state) => state.setNoRoomsAvailable - ) - const setRoomsAvailable = useRoomAvailableStore( - (state) => state.setRoomsAvailable - ) const handleFilter = useCallback( (filter: Record) => { @@ -55,81 +40,135 @@ export default function Rooms({ setSelectedPackages(filteredPackages) - if (filteredPackages.length === 0) { - setRooms({ - ...roomsAvailability, - roomConfigurations: visibleRooms, - }) + // if (filteredPackages.length === 0) { + // setRooms({ + // ...roomsAvailability, + // roomConfigurations: visibleRooms, + // }) - if (!!rateSummary) { - setRateSummary({ - ...rateSummary, - features: [], - }) - } + // if (!!rateSummary) { + // setRateSummary({ + // ...rateSummary, + // features: [], + // }) + // } - if (noRoomsAvailable) { - setRoomsAvailable() - } + // if (noRoomsAvailable) { + // setRoomsAvailable() + // } - return - } + // return + // } - const filteredRooms = visibleRooms.filter((room) => - filteredPackages.every((filteredPackage) => - room.features.some((feature) => feature.code === filteredPackage) - ) + // const filteredRooms = visibleRooms.filter((room) => + // filteredPackages.every((filteredPackage) => + // room.features.some((feature) => feature.code === filteredPackage) + // ) + // ) + // let notAvailableRooms = visibleRooms.filter((room) => + // filteredPackages.every( + // (filteredPackage) => + // !room.features.some((feature) => feature.code === filteredPackage) + // ) + // ) + // // Clone nested object to keep original object intact and not messup the room data + // notAvailableRooms = JSON.parse(JSON.stringify(notAvailableRooms)) + // notAvailableRooms.forEach((room) => { + // room.status = "NotAvailable" + // }) + // setRooms({ + // ...roomsAvailability, + // roomConfigurations: [...filteredRooms, ...notAvailableRooms], + // }) + + // if (filteredRooms.length == 0) { + // setNoRoomsAvailable() + // } else if (noRoomsAvailable) { + // setRoomsAvailable() + // } + + // const petRoomPackage = + // (filteredPackages.includes(RoomPackageCodeEnum.PET_ROOM) && + // packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) || + // undefined + + // const features = filteredRooms.find((room) => + // room.features.some( + // (feature) => feature.code === RoomPackageCodeEnum.PET_ROOM + // ) + // )?.features + + // if (!!rateSummary) { + // setRateSummary({ + // ...rateSummary, + // features: petRoomPackage && features ? features : [], + // }) + // } + }, + [ + // roomsAvailability, + // visibleRooms, + // rateSummary, + // packages, + // noRoomsAvailable, + // setNoRoomsAvailable, + // setRoomsAvailable, + ] + ) + + const filteredRooms = useMemo(() => { + return visibleRooms.filter((room) => + selectedPackages.every((filteredPackage) => + room.features.some((feature) => feature.code === filteredPackage) ) - let notAvailableRooms = visibleRooms.filter((room) => - filteredPackages.every( + ) + }, [visibleRooms, selectedPackages]) + + const rooms = useMemo(() => { + if (selectedPackages.length === 0) { + return { + ...roomsAvailability, + roomConfigurations: visibleRooms, + } + } + + let notAvailableRooms = visibleRooms + .filter((room) => + selectedPackages.every( (filteredPackage) => !room.features.some((feature) => feature.code === filteredPackage) ) ) - // Clone nested object to keep original object intact and not messup the room data - notAvailableRooms = JSON.parse(JSON.stringify(notAvailableRooms)) - notAvailableRooms.forEach((room) => { - room.status = "NotAvailable" - }) - setRooms({ - ...roomsAvailability, - roomConfigurations: [...filteredRooms, ...notAvailableRooms], - }) + .map((room) => ({ + ...room, + status: "NotAvailable", + })) - if (filteredRooms.length == 0) { - setNoRoomsAvailable() - } else if (noRoomsAvailable) { - setRoomsAvailable() - } + return { + ...roomsAvailability, + roomConfigurations: [...filteredRooms, ...notAvailableRooms], + } + }, [roomsAvailability, visibleRooms, selectedPackages, filteredRooms]) - const petRoomPackage = - (filteredPackages.includes(RoomPackageCodeEnum.PET_ROOM) && - packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) || - undefined + const rSummary: Rate | null = useMemo(() => { + const petRoomPackage = + (selectedPackages.includes(RoomPackageCodeEnum.PET_ROOM) && + packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) || + undefined - const features = filteredRooms.find((room) => - room.features.some( - (feature) => feature.code === RoomPackageCodeEnum.PET_ROOM - ) - )?.features + const features = filteredRooms.find((room) => + room.features.some( + (feature) => feature.code === RoomPackageCodeEnum.PET_ROOM + ) + )?.features - if (!!rateSummary) { - setRateSummary({ - ...rateSummary, - features: petRoomPackage && features ? features : [], - }) - } - }, - [ - roomsAvailability, - visibleRooms, - rateSummary, - packages, - noRoomsAvailable, - setNoRoomsAvailable, - setRoomsAvailable, - ] - ) + if (!rateSummary) return null + + return { + ...rateSummary, + features: petRoomPackage && features ? features : [], + } + }, [filteredRooms, rateSummary, packages, selectedPackages]) return (
@@ -145,7 +184,7 @@ export default function Rooms({ packages={packages} selectedPackages={selectedPackages} setRateSummary={setRateSummary} - rateSummary={rateSummary} + rateSummary={rSummary} />
) From 84f2bc4c0721ce4a773b94af93a94df515e24b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Wed, 20 Nov 2024 08:43:55 +0100 Subject: [PATCH 007/103] fix: enable force-cache for CurrentFooter and booking widget toggle query --- server/routers/contentstack/base/query.ts | 1 + server/routers/contentstack/bookingwidget/query.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/server/routers/contentstack/base/query.ts b/server/routers/contentstack/base/query.ts index b45029eb8..8cdb9b0b9 100644 --- a/server/routers/contentstack/base/query.ts +++ b/server/routers/contentstack/base/query.ts @@ -422,6 +422,7 @@ export const baseQueryRouter = router({ locale: input.lang, }, { + cache: "force-cache", next: { tags: [generateTag(input.lang, currentFooterUID)], }, diff --git a/server/routers/contentstack/bookingwidget/query.ts b/server/routers/contentstack/bookingwidget/query.ts index 50af970e1..32292e7e0 100644 --- a/server/routers/contentstack/bookingwidget/query.ts +++ b/server/routers/contentstack/bookingwidget/query.ts @@ -70,6 +70,7 @@ export const bookingwidgetQueryRouter = router({ locale: lang, }, { + cache: "force-cache", next: { tags: [generateTag(lang, uid, bookingwidgetAffix)], }, From b5331670443b09c6ce2da68a9b2f86104eb286e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Wed, 20 Nov 2024 08:44:23 +0100 Subject: [PATCH 008/103] fix: optimize reward query by fetching loyalty levels concurrently --- server/routers/contentstack/reward/query.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts index b1f5ccb8c..7746822ff 100644 --- a/server/routers/contentstack/reward/query.ts +++ b/server/routers/contentstack/reward/query.ts @@ -130,13 +130,15 @@ export const rewardQueryRouter = router({ .map((reward) => reward?.rewardId) .filter((id): id is string => Boolean(id)) - const contentStackRewards = await getCmsRewards(ctx.lang, rewardIds) + const [contentStackRewards, loyaltyLevelsConfig] = await Promise.all([ + getCmsRewards(ctx.lang, rewardIds), + getLoyaltyLevel(ctx, input.level_id), + ]) + if (!contentStackRewards) { return null } - const loyaltyLevelsConfig = await getLoyaltyLevel(ctx, input.level_id) - const levelsWithRewards = apiRewards .map((reward) => { const contentStackReward = contentStackRewards.find((r) => { From dfdbdb7621232bcf61c7bb8f56918e2a92ea70ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Wed, 20 Nov 2024 10:17:55 +0100 Subject: [PATCH 009/103] feat: break apart loading of room availability and hotel card feat: add skeletons --- .../(standard)/select-rate/page.tsx | 5 ++-- .../SelectRate/HotelInfoCard/index.tsx | 13 ---------- .../RoomCard/RoomCardSkeleton.module.css | 26 +++++++++++++++++++ .../RoomCard/RoomCardSkeleton.tsx | 21 +++++++++++++++ .../Rooms/RoomsContainerSkeleton.module.css | 21 +++++++++++++++ .../Rooms/RoomsContainerSkeleton.tsx | 24 +++++++++++++++++ server/routers/contentstack/base/utils.ts | 2 ++ stores/roomAvailability.ts | 17 ------------ 8 files changed, 97 insertions(+), 32 deletions(-) create mode 100644 components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.module.css create mode 100644 components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.tsx create mode 100644 components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.module.css create mode 100644 components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.tsx delete mode 100644 stores/roomAvailability.ts 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 5682773c3..7557a4b59 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -4,9 +4,9 @@ import { Suspense } from "react" import { dt } from "@/lib/dt" import { getHotelData, getLocations } from "@/lib/trpc/memoizedRequests" -import LoadingSpinner from "@/components/Current/LoadingSpinner" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" import { RoomsContainer } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainer" +import { RoomsContainerSkeleton } from "@/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton" import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { setLang } from "@/i18n/serverContext" import { safeTry } from "@/utils/safeTry" @@ -71,7 +71,8 @@ export default async function SelectRatePage({ adultCount={adults} childArray={children ?? []} /> - }> + + }> state.noRoomsAvailable - // ) - // const setNoRoomsAvailable = useRoomAvailableStore( - // (state) => state.setNoRoomsAvailable - // ) - const sortedFacilities = hotelAttributes?.detailedFacilities .sort((a, b) => b.sortOrder - a.sortOrder) .slice(0, 5) - // useEffect(() => { - // if (noAvailability) { - // setNoRoomsAvailable() - // } - // }, [noAvailability, setNoRoomsAvailable]) - return (
{hotelAttributes && ( diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.module.css b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.module.css new file mode 100644 index 000000000..d5c618625 --- /dev/null +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.module.css @@ -0,0 +1,26 @@ +.card { + font-size: 14px; + display: flex; + flex-direction: column; + background-color: #fff; + border-radius: var(--Corner-radius-Large); + border: 1px solid var(--Base-Border-Subtle); + position: relative; + height: 100%; + justify-content: space-between; + min-height: 200px; + flex: 1; + overflow: hidden; +} + +.imageContainer { + aspect-ratio: 16/9; + width: 100%; +} + +.priceVariants { + display: flex; + flex-direction: column; + gap: var(--Spacing-x1); + padding: var(--Spacing-x2); +} diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.tsx new file mode 100644 index 000000000..0e032ed1c --- /dev/null +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/RoomCardSkeleton.tsx @@ -0,0 +1,21 @@ +import SkeletonShimmer from "@/components/SkeletonShimmer" + +import styles from "./RoomCardSkeleton.module.css" + +export function RoomCardSkeleton() { + return ( +
+ {/* image container */} +
+ +
+ +
+ {/* price variants */} + {Array.from({ length: 3 }).map((_, index) => ( + + ))} +
+
+ ) +} diff --git a/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.module.css b/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.module.css new file mode 100644 index 000000000..7f7d28860 --- /dev/null +++ b/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.module.css @@ -0,0 +1,21 @@ +.container { + padding: var(--Spacing-x2); +} + +.filterContainer { + height: 38px; +} + +.skeletonContainer { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + /* used to hide overflowing rows */ + grid-template-rows: auto; + grid-auto-rows: 0; + overflow: hidden; + + flex-wrap: wrap; + justify-content: space-between; + margin-top: 20px; + gap: var(--Spacing-x2); +} diff --git a/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.tsx b/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.tsx new file mode 100644 index 000000000..7731ff375 --- /dev/null +++ b/components/HotelReservation/SelectRate/Rooms/RoomsContainerSkeleton.tsx @@ -0,0 +1,24 @@ +import Body from "@/components/TempDesignSystem/Text/Body" +import { getIntl } from "@/i18n" + +import { RoomCardSkeleton } from "../RoomSelection/RoomCard/RoomCardSkeleton" + +import styles from "./RoomsContainerSkeleton.module.css" + +type Props = { + count?: number +} + +export async function RoomsContainerSkeleton({ count = 4 }: Props) { + const intl = await getIntl() + return ( +
+
+
+ {Array.from({ length: count }).map((_, index) => ( + + ))} +
+
+ ) +} diff --git a/server/routers/contentstack/base/utils.ts b/server/routers/contentstack/base/utils.ts index 27ec2304f..daf4575a0 100644 --- a/server/routers/contentstack/base/utils.ts +++ b/server/routers/contentstack/base/utils.ts @@ -82,6 +82,8 @@ export function getSiteConfigConnections(refs: GetSiteConfigRefData) { const siteConfigData = refs.all_site_config.items[0] const connections: System["system"][] = [] + if (!siteConfigData) return connections + const alertConnection = siteConfigData.sitewide_alert.alertConnection alertConnection.edges.forEach(({ node }) => { diff --git a/stores/roomAvailability.ts b/stores/roomAvailability.ts deleted file mode 100644 index ad01453e4..000000000 --- a/stores/roomAvailability.ts +++ /dev/null @@ -1,17 +0,0 @@ -"use client" - -import { create } from "zustand" - -interface RoomAvailabilityState { - noRoomsAvailable: boolean - setNoRoomsAvailable: () => void - setRoomsAvailable: () => void -} - -const useRoomAvailableStore = create((set) => ({ - noRoomsAvailable: false, - setNoRoomsAvailable: () => set(() => ({ noRoomsAvailable: true })), - setRoomsAvailable: () => set(() => ({ noRoomsAvailable: false })), -})) - -export default useRoomAvailableStore From d32a595e2eccf8fd33cd799c77ecff683a0d041c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Wed, 20 Nov 2024 11:25:11 +0100 Subject: [PATCH 010/103] feat: contain logic for selecting price at the top level instead of on individual room card --- .../SelectRate/HotelInfoCard/NoRoomsAlert.tsx | 6 +- .../RoomSelection/FlexibilityOption/index.tsx | 8 +- .../RoomSelection/RoomCard/index.tsx | 2 +- .../SelectRate/RoomSelection/index.tsx | 4 +- .../SelectRate/Rooms/index.tsx | 128 ++++++------------ .../selectRate/flexibilityOption.ts | 5 +- .../selectRate/hotelInfoCardProps.ts | 6 - .../hotelReservation/selectRate/roomCard.ts | 5 +- .../selectRate/roomSelection.ts | 5 +- 9 files changed, 63 insertions(+), 106 deletions(-) delete mode 100644 types/components/hotelReservation/selectRate/hotelInfoCardProps.ts diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx index bfca1364f..51578df8b 100644 --- a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx +++ b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx @@ -40,14 +40,10 @@ export async function NoRoomsAlert({ }) ) - if (!availability) { + if (!availability || availabilityError) { return null } - if (availabilityError) { - // TODO: Handle error - } - const noRoomsAvailable = availability.roomConfigurations.reduce( (acc, room) => { return acc && room.status === "NotAvailable" diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx index 941d6257d..2194f8f35 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx @@ -54,7 +54,13 @@ export default function FlexibilityOption({ member: memberPrice, features: petRoomPackage ? features : [], } - handleSelectRate(rate) + + console.log("Rate", rate) + + handleSelectRate({ + publicRateCode: publicPrice.rateCode, + roomTypeCode: roomTypeCode, + }) } return ( diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index fb713fddb..0f20cd65b 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -161,7 +161,7 @@ export default function RoomCard({ {roomConfiguration.roomType} - {/* Out of scope for now + {/* Out of scope for now {descriptions?.short} */} diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index b3624552f..f7c530521 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -16,7 +16,7 @@ export default function RoomSelection({ user, packages, selectedPackages, - setRateSummary, + setRateCode, rateSummary, }: RoomSelectionProps) { const router = useRouter() @@ -70,7 +70,7 @@ export default function RoomSelection({ rateDefinitions={rateDefinitions} roomConfiguration={roomConfiguration} roomCategories={roomCategories} - handleSelectRate={setRateSummary} + handleSelectRate={setRateCode} selectedPackages={selectedPackages} packages={packages} /> diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 78528db3e..7a661e385 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -1,6 +1,6 @@ "use client" -import { useCallback, useMemo, useState } from "react" +import { useCallback, useEffect, useMemo, useState } from "react" import RoomFilter from "../RoomFilter" import RoomSelection from "../RoomSelection" @@ -14,10 +14,7 @@ import { } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { SelectRateProps } from "@/types/components/hotelReservation/selectRate/roomSelection" import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" -import type { - RoomConfiguration, - RoomsAvailability, -} from "@/server/routers/hotels/output" +import type { RoomConfiguration } from "@/server/routers/hotels/output" export default function Rooms({ roomsAvailability, @@ -27,7 +24,10 @@ export default function Rooms({ }: SelectRateProps) { const visibleRooms: RoomConfiguration[] = filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations) - const [rateSummary, setRateSummary] = useState(null) + // const [internalRateSummary, setRateSummary] = useState(null) + const [selectedRate, setSelectedRate] = useState< + { publicRateCode: string; roomTypeCode: string } | undefined + >(undefined) const [selectedPackages, setSelectedPackages] = useState( [] ) @@ -39,81 +39,9 @@ export default function Rooms({ ) as RoomPackageCodeEnum[] setSelectedPackages(filteredPackages) - - // if (filteredPackages.length === 0) { - // setRooms({ - // ...roomsAvailability, - // roomConfigurations: visibleRooms, - // }) - - // if (!!rateSummary) { - // setRateSummary({ - // ...rateSummary, - // features: [], - // }) - // } - - // if (noRoomsAvailable) { - // setRoomsAvailable() - // } - - // return - // } - - // const filteredRooms = visibleRooms.filter((room) => - // filteredPackages.every((filteredPackage) => - // room.features.some((feature) => feature.code === filteredPackage) - // ) - // ) - // let notAvailableRooms = visibleRooms.filter((room) => - // filteredPackages.every( - // (filteredPackage) => - // !room.features.some((feature) => feature.code === filteredPackage) - // ) - // ) - // // Clone nested object to keep original object intact and not messup the room data - // notAvailableRooms = JSON.parse(JSON.stringify(notAvailableRooms)) - // notAvailableRooms.forEach((room) => { - // room.status = "NotAvailable" - // }) - // setRooms({ - // ...roomsAvailability, - // roomConfigurations: [...filteredRooms, ...notAvailableRooms], - // }) - - // if (filteredRooms.length == 0) { - // setNoRoomsAvailable() - // } else if (noRoomsAvailable) { - // setRoomsAvailable() - // } - - // const petRoomPackage = - // (filteredPackages.includes(RoomPackageCodeEnum.PET_ROOM) && - // packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) || - // undefined - - // const features = filteredRooms.find((room) => - // room.features.some( - // (feature) => feature.code === RoomPackageCodeEnum.PET_ROOM - // ) - // )?.features - - // if (!!rateSummary) { - // setRateSummary({ - // ...rateSummary, - // features: petRoomPackage && features ? features : [], - // }) - // } + // setRateSummary(null) }, - [ - // roomsAvailability, - // visibleRooms, - // rateSummary, - // packages, - // noRoomsAvailable, - // setNoRoomsAvailable, - // setRoomsAvailable, - ] + [] ) const filteredRooms = useMemo(() => { @@ -150,7 +78,20 @@ export default function Rooms({ } }, [roomsAvailability, visibleRooms, selectedPackages, filteredRooms]) - const rSummary: Rate | null = useMemo(() => { + const rateSummary: Rate | null = useMemo(() => { + const room = filteredRooms.find( + (room) => room.roomTypeCode === selectedRate?.roomTypeCode + ) + + if (!room) return null + + const product = room.products.find( + (product) => + product.productType.public.rateCode === selectedRate?.publicRateCode + ) + + if (!product) return null + const petRoomPackage = (selectedPackages.includes(RoomPackageCodeEnum.PET_ROOM) && packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) || @@ -162,13 +103,24 @@ export default function Rooms({ ) )?.features - if (!rateSummary) return null - - return { - ...rateSummary, + const rateSummary: Rate = { features: petRoomPackage && features ? features : [], + priceName: room.roomType, + public: product.productType.public, + member: product.productType.member, + roomType: room.roomType, + roomTypeCode: room.roomTypeCode, } - }, [filteredRooms, rateSummary, packages, selectedPackages]) + + return rateSummary + }, [filteredRooms, packages, selectedPackages, selectedRate]) + + useEffect(() => { + if (rateSummary) return + if (!selectedRate) return + + setSelectedRate(undefined) + }, [rateSummary, selectedRate]) return (
@@ -183,8 +135,8 @@ export default function Rooms({ user={user} packages={packages} selectedPackages={selectedPackages} - setRateSummary={setRateSummary} - rateSummary={rSummary} + setRateCode={setSelectedRate} + rateSummary={rateSummary} />
) diff --git a/types/components/hotelReservation/selectRate/flexibilityOption.ts b/types/components/hotelReservation/selectRate/flexibilityOption.ts index 811afc139..5baef06a4 100644 --- a/types/components/hotelReservation/selectRate/flexibilityOption.ts +++ b/types/components/hotelReservation/selectRate/flexibilityOption.ts @@ -23,7 +23,10 @@ export type FlexibilityOptionProps = { roomTypeCode: RoomConfiguration["roomTypeCode"] features: RoomConfiguration["features"] petRoomPackage: RoomPackage | undefined - handleSelectRate: (rate: Rate) => void + handleSelectRate: (rateCode: { + publicRateCode: string + roomTypeCode: string + }) => void } export interface PriceListProps { diff --git a/types/components/hotelReservation/selectRate/hotelInfoCardProps.ts b/types/components/hotelReservation/selectRate/hotelInfoCardProps.ts deleted file mode 100644 index 088c54e51..000000000 --- a/types/components/hotelReservation/selectRate/hotelInfoCardProps.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { HotelData } from "@/types/hotel" - -export type HotelInfoCardProps = { - hotelData: HotelData | null - noAvailability: boolean -} diff --git a/types/components/hotelReservation/selectRate/roomCard.ts b/types/components/hotelReservation/selectRate/roomCard.ts index aa0d647be..cc7836a62 100644 --- a/types/components/hotelReservation/selectRate/roomCard.ts +++ b/types/components/hotelReservation/selectRate/roomCard.ts @@ -19,7 +19,10 @@ export type RoomCardProps = { roomCategories: RoomData[] selectedPackages: RoomPackageCodes[] packages: RoomPackageData | undefined - handleSelectRate: (rate: Rate) => void + handleSelectRate: (rateCode: { + publicRateCode: string + roomTypeCode: string + }) => void } type RoomPackagePriceSchema = z.output diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts index 163bfd6fa..bfb750490 100644 --- a/types/components/hotelReservation/selectRate/roomSelection.ts +++ b/types/components/hotelReservation/selectRate/roomSelection.ts @@ -10,7 +10,10 @@ export interface RoomSelectionProps { user: SafeUser packages: RoomPackageData | undefined selectedPackages: RoomPackageCodes[] - setRateSummary: (rateSummary: Rate) => void + setRateCode: (rateCode: { + publicRateCode: string + roomTypeCode: string + }) => void rateSummary: Rate | null } From 39b3cc39d71c0cec4a6b9cac7d9b2a1043072a0b Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Wed, 20 Nov 2024 12:54:54 +0000 Subject: [PATCH 011/103] Merged in fix/SW-940-sort-by-price (pull request #946) fix(SW-940): change logic for price sorting * fix(SW-940): change logic for price sorting * fix(SW-940): change logic for price sorting Approved-by: Pontus Dreij --- components/HotelReservation/HotelCardListing/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx index 6ebf3006e..6ceb9aa85 100644 --- a/components/HotelReservation/HotelCardListing/index.tsx +++ b/components/HotelReservation/HotelCardListing/index.tsx @@ -51,7 +51,7 @@ export default function HotelCardListing({ return ( hotel.price?.member?.localPrice?.pricePerNight ?? hotel.price?.public?.localPrice?.pricePerNight ?? - 0 + Infinity ) } return [...hotelData].sort( From 0d739ff81d8827f97104519d35d331e61e9e8446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Wed, 20 Nov 2024 14:31:53 +0100 Subject: [PATCH 012/103] fix: remove console.log and unused variable --- .../RoomSelection/FlexibilityOption/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx index 2194f8f35..7df61cbe0 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx @@ -46,17 +46,6 @@ export default function FlexibilityOption({ const { public: publicPrice, member: memberPrice } = product.productType function onChange() { - const rate = { - roomTypeCode, - roomType, - priceName: name, - public: publicPrice, - member: memberPrice, - features: petRoomPackage ? features : [], - } - - console.log("Rate", rate) - handleSelectRate({ publicRateCode: publicPrice.rateCode, roomTypeCode: roomTypeCode, From eb5dd49981f3a571046bbc7167169b86ec5ddaf2 Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Wed, 20 Nov 2024 13:39:52 +0000 Subject: [PATCH 013/103] Merged in fix/SW-955-restaurantsOverviewPage-attributes-optional (pull request #943) fix(SW-955): restaurantsOverviewPage atttributes optional * fix(SW-955): restaurantsOverviewPage atttributes optional Approved-by: Pontus Dreij --- server/routers/hotels/output.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 41f9a6b52..d383d5628 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -112,10 +112,10 @@ const hotelContentSchema = z.object({ }), }), restaurantsOverviewPage: z.object({ - restaurantsOverviewPageLinkText: z.string(), - restaurantsOverviewPageLink: z.string(), - restaurantsContentDescriptionShort: z.string(), - restaurantsContentDescriptionMedium: z.string(), + restaurantsOverviewPageLinkText: z.string().optional(), + restaurantsOverviewPageLink: z.string().optional(), + restaurantsContentDescriptionShort: z.string().optional(), + restaurantsContentDescriptionMedium: z.string().optional(), }), }) From a1f1531fc2496bdd6dd106239a80fed469e27dea Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 14:25:48 +0100 Subject: [PATCH 014/103] fix(SW-925): Don't throw error if api for packages throws error --- server/routers/hotels/output.ts | 38 +++++++++++++++++---------------- server/routers/hotels/query.ts | 3 +-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index d383d5628..f9a5710de 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -864,22 +864,24 @@ export const packagesSchema = z.object({ export const getRoomPackagesSchema = z .object({ - data: z.object({ - attributes: z.object({ - hotelId: z.number(), - packages: z.array(packagesSchema).optional().default([]), - }), - relationships: z - .object({ - links: z.array( - z.object({ - url: z.string(), - type: z.string(), - }) - ), - }) - .optional(), - type: z.string(), - }), + data: z + .object({ + attributes: z.object({ + hotelId: z.number(), + packages: z.array(packagesSchema).optional().default([]), + }), + relationships: z + .object({ + links: z.array( + z.object({ + url: z.string(), + type: z.string(), + }) + ), + }) + .optional(), + type: z.string(), + }) + .optional(), }) - .transform((data) => data.data.attributes.packages) + .transform((data) => data.data?.attributes?.packages ?? []) diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 34a1b3722..ddb2b3a31 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -939,12 +939,11 @@ export const hotelQueryRouter = router({ "api.hotels.packages error", JSON.stringify({ query: { hotelId, params } }) ) - throw serverErrorByStatus(apiResponse.status, apiResponse) } const apiJson = await apiResponse.json() const validatedPackagesData = getRoomPackagesSchema.safeParse(apiJson) - + console.log("validatedPackagesData", validatedPackagesData) if (!validatedPackagesData.success) { getHotelFailCounter.add(1, { hotelId, From a0627b9ac521037a504eb28acc3cd5f22a445a5c Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 14:28:00 +0100 Subject: [PATCH 015/103] fix(SW-925) remove log --- server/routers/hotels/query.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index ddb2b3a31..eb31aacb6 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -943,7 +943,6 @@ export const hotelQueryRouter = router({ const apiJson = await apiResponse.json() const validatedPackagesData = getRoomPackagesSchema.safeParse(apiJson) - console.log("validatedPackagesData", validatedPackagesData) if (!validatedPackagesData.success) { getHotelFailCounter.add(1, { hotelId, From 10d2e094e4784e2f45f000eb3e22788d45506a37 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Wed, 20 Nov 2024 09:46:44 +0100 Subject: [PATCH 016/103] fix(SW-925) Fix default packages --- .../(standard)/select-rate/page.tsx | 2 +- .../SelectRate/RoomFilter/index.tsx | 1 + .../SelectRate/RoomSelection/index.tsx | 6 ++--- .../SelectRate/Rooms/index.tsx | 23 +++++++++++++++---- .../hotelReservation/selectRate/roomFilter.ts | 12 ++++++---- .../selectRate/roomSelection.ts | 4 ++-- 6 files changed, 32 insertions(+), 16 deletions(-) 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 fd9db4d6c..f8632f938 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -108,7 +108,7 @@ export default async function SelectRatePage({ roomsAvailability={roomsAvailability} roomCategories={roomCategories ?? []} user={user} - packages={packages ?? []} + availablePackages={packages ?? []} /> ) diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index a07e8353f..2620d3efe 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -26,6 +26,7 @@ export default function RoomFilter({ onFilter, filterOptions, }: RoomFilterProps) { + console.log(filterOptions) const initialFilterValues = useMemo( () => filterOptions.reduce( diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index b3624552f..75759459f 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -14,7 +14,7 @@ export default function RoomSelection({ roomsAvailability, roomCategories, user, - packages, + availablePackages, selectedPackages, setRateSummary, rateSummary, @@ -72,7 +72,7 @@ export default function RoomSelection({ roomCategories={roomCategories} handleSelectRate={setRateSummary} selectedPackages={selectedPackages} - packages={packages} + packages={availablePackages} /> ))} @@ -81,7 +81,7 @@ export default function RoomSelection({ )} diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 626664988..06e0d954d 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -11,6 +11,7 @@ import { filterDuplicateRoomTypesByLowestPrice } from "./utils" import styles from "./rooms.module.css" import { + DefaultFilterOptions, RoomPackageCodeEnum, type RoomPackageCodes, } from "@/types/components/hotelReservation/selectRate/roomFilter" @@ -25,8 +26,9 @@ export default function Rooms({ roomsAvailability, roomCategories = [], user, - packages, + availablePackages, }: SelectRateProps) { + console.log(availablePackages) const visibleRooms: RoomConfiguration[] = filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations) const [rateSummary, setRateSummary] = useState(null) @@ -47,6 +49,15 @@ export default function Rooms({ (state) => state.setRoomsAvailable ) + const defaultPackages: DefaultFilterOptions[] = [ + { code: RoomPackageCodeEnum.PET_ROOM, description: "Pet friendly" }, + { code: RoomPackageCodeEnum.ALLERGY_ROOM, description: "Allergy friendly" }, + { + code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM, + description: "Accessibility", + }, + ] + const handleFilter = useCallback( (filter: Record) => { const filteredPackages = Object.keys(filter).filter( @@ -104,7 +115,9 @@ export default function Rooms({ const petRoomPackage = (filteredPackages.includes(RoomPackageCodeEnum.PET_ROOM) && - packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) || + availablePackages.find( + (pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM + )) || undefined const features = filteredRooms.find((room) => @@ -124,7 +137,7 @@ export default function Rooms({ roomsAvailability, visibleRooms, rateSummary, - packages, + availablePackages, noRoomsAvailable, setNoRoomsAvailable, setRoomsAvailable, @@ -136,13 +149,13 @@ export default function Rooms({ ) => void - filterOptions: RoomPackageData + filterOptions: DefaultFilterOptions[] } export type RoomPackage = z.output diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts index 163bfd6fa..cd1e48775 100644 --- a/types/components/hotelReservation/selectRate/roomSelection.ts +++ b/types/components/hotelReservation/selectRate/roomSelection.ts @@ -8,7 +8,7 @@ export interface RoomSelectionProps { roomsAvailability: RoomsAvailability roomCategories: RoomData[] user: SafeUser - packages: RoomPackageData | undefined + availablePackages: RoomPackageData | undefined selectedPackages: RoomPackageCodes[] setRateSummary: (rateSummary: Rate) => void rateSummary: Rate | null @@ -18,5 +18,5 @@ export interface SelectRateProps { roomsAvailability: RoomsAvailability roomCategories: RoomData[] user: SafeUser - packages: RoomPackageData + availablePackages: RoomPackageData } From 37098d23cf187de63e4ed73362fd6dc9eed02336 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Wed, 20 Nov 2024 10:14:06 +0100 Subject: [PATCH 017/103] fix(SW-925) disable filter if there are no availablePackages --- .../SelectRate/RoomFilter/index.tsx | 7 +++--- .../SelectRate/Rooms/index.tsx | 23 +++++++++++++++---- .../Form/FilterChip/_Chip/chip.module.css | 9 +++++--- .../hotelReservation/selectRate/roomFilter.ts | 1 + 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index 2620d3efe..48c73cac2 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -26,7 +26,6 @@ export default function RoomFilter({ onFilter, filterOptions, }: RoomFilterProps) { - console.log(filterOptions) const initialFilterValues = useMemo( () => filterOptions.reduce( @@ -100,11 +99,13 @@ export default function RoomFilter({
{filterOptions.map((option) => { - const { code, description } = option + const { code, description, itemCode } = option const isPetRoom = code === RoomPackageCodeEnum.PET_ROOM const isAllergyRoom = code === RoomPackageCodeEnum.ALLERGY_ROOM const isDisabled = - (isAllergyRoom && petFriendly) || (isPetRoom && allergyFriendly) + (isAllergyRoom && petFriendly) || + (isPetRoom && allergyFriendly) || + !itemCode const checkboxChip = ( (null) @@ -50,11 +49,26 @@ export default function Rooms({ ) const defaultPackages: DefaultFilterOptions[] = [ - { code: RoomPackageCodeEnum.PET_ROOM, description: "Pet friendly" }, - { code: RoomPackageCodeEnum.ALLERGY_ROOM, description: "Allergy friendly" }, { code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM, - description: "Accessibility", + description: "Accessible Room", + itemCode: availablePackages.find( + (pkg) => pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM + )?.itemCode, + }, + { + code: RoomPackageCodeEnum.ALLERGY_ROOM, + description: "Allergy Room", + itemCode: availablePackages.find( + (pkg) => pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM + )?.itemCode, + }, + { + code: RoomPackageCodeEnum.PET_ROOM, + description: "Pet Room", + itemCode: availablePackages.find( + (pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM + )?.itemCode, }, ] @@ -102,6 +116,7 @@ export default function Rooms({ notAvailableRooms.forEach((room) => { room.status = "NotAvailable" }) + setRooms({ ...roomsAvailability, roomConfigurations: [...filteredRooms, ...notAvailableRooms], diff --git a/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css b/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css index 44fa78a14..fa029bb47 100644 --- a/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css +++ b/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css @@ -2,11 +2,13 @@ display: flex; align-items: center; gap: var(--Spacing-x-half); - padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); + padding: calc(var(--Spacing-x1) - 2px) var(--Spacing-x-one-and-half); border: 1px solid var(--Base-Border-Subtle); border-radius: var(--Corner-radius-Small); background-color: var(--Base-Surface-Secondary-light-Normal); cursor: pointer; + height: 32px; + background-color: var(--Base-Surface-Secondary-light-Normal); } .label[data-selected="true"], @@ -21,8 +23,9 @@ } .label[data-disabled="true"] { - background-color: var(--Base-Button-Primary-Fill-Disabled); - border-color: var(--Base-Button-Primary-Fill-Disabled); + background-color: var(--UI-Input-Controls-Surface-Disabled); + border-color: var(--UI-Input-Controls-Border-Disabled); + color: var(--Base-Text-Disabled); cursor: not-allowed; } diff --git a/types/components/hotelReservation/selectRate/roomFilter.ts b/types/components/hotelReservation/selectRate/roomFilter.ts index 239c6fa70..2bdd501b1 100644 --- a/types/components/hotelReservation/selectRate/roomFilter.ts +++ b/types/components/hotelReservation/selectRate/roomFilter.ts @@ -11,6 +11,7 @@ export enum RoomPackageCodeEnum { export interface DefaultFilterOptions { code: RoomPackageCodeEnum description: string + itemCode: string | undefined } export interface RoomFilterProps { numberOfRooms: number From f5a215b27f089fb16cf51d0079e799e4ea970244 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Wed, 20 Nov 2024 10:52:41 +0100 Subject: [PATCH 018/103] fix(SW-925) Removed adding of rooms not in filter --- .../HotelReservation/SelectRate/Rooms/index.tsx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 58f5727f3..854b5b940 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -28,6 +28,7 @@ export default function Rooms({ user, availablePackages, }: SelectRateProps) { + console.log(roomsAvailability) const visibleRooms: RoomConfiguration[] = filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations) const [rateSummary, setRateSummary] = useState(null) @@ -105,21 +106,12 @@ export default function Rooms({ room.features.some((feature) => feature.code === filteredPackage) ) ) - let notAvailableRooms = visibleRooms.filter((room) => - filteredPackages.every( - (filteredPackage) => - !room.features.some((feature) => feature.code === filteredPackage) - ) - ) - // Clone nested object to keep original object intact and not messup the room data - notAvailableRooms = JSON.parse(JSON.stringify(notAvailableRooms)) - notAvailableRooms.forEach((room) => { - room.status = "NotAvailable" - }) + console.log("filteredRooms", filteredRooms) + console.log("visibleRooms", visibleRooms) setRooms({ ...roomsAvailability, - roomConfigurations: [...filteredRooms, ...notAvailableRooms], + roomConfigurations: [...filteredRooms], }) if (filteredRooms.length == 0) { From f2de6f103e7e706046267d80e07628eadee90b0e Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Wed, 20 Nov 2024 10:57:20 +0100 Subject: [PATCH 019/103] fix(SW-925) remove log --- components/HotelReservation/SelectRate/Rooms/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 854b5b940..bdf69ec7a 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -28,7 +28,6 @@ export default function Rooms({ user, availablePackages, }: SelectRateProps) { - console.log(roomsAvailability) const visibleRooms: RoomConfiguration[] = filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations) const [rateSummary, setRateSummary] = useState(null) From a12fad1a4518ab82fe1ce87809bf821e670a06ce Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Wed, 20 Nov 2024 10:58:00 +0100 Subject: [PATCH 020/103] fix(SW-925) remove log --- components/HotelReservation/SelectRate/Rooms/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index bdf69ec7a..2c3570a95 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -105,8 +105,6 @@ export default function Rooms({ room.features.some((feature) => feature.code === filteredPackage) ) ) - console.log("filteredRooms", filteredRooms) - console.log("visibleRooms", visibleRooms) setRooms({ ...roomsAvailability, From 9ce56203ccd2032e4eb8e8e6eece002168dafdc8 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Wed, 20 Nov 2024 14:24:59 +0100 Subject: [PATCH 021/103] Fix: SW-943 LightBox responsive view update --- components/Lightbox/Lightbox.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Lightbox/Lightbox.module.css b/components/Lightbox/Lightbox.module.css index 4c62a90dc..80901eabd 100644 --- a/components/Lightbox/Lightbox.module.css +++ b/components/Lightbox/Lightbox.module.css @@ -227,7 +227,7 @@ .galleryContent { width: 1090px; - height: 725px; + height: min(725px, 85dvh); } .fullViewContent { From 55bf718045357fb5b7900e817def58071b812b0d Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Wed, 20 Nov 2024 15:09:24 +0000 Subject: [PATCH 022/103] Merged in fix/add-translation-no-prices-available (pull request #949) fix: add translation for no prices available * fix: add translation for no prices available Approved-by: Pontus Dreij Approved-by: Christian Andolf --- .../SelectRate/RoomSelection/FlexibilityOption/index.tsx | 2 +- i18n/dictionaries/da.json | 1 + i18n/dictionaries/de.json | 1 + i18n/dictionaries/en.json | 1 + i18n/dictionaries/fi.json | 1 + i18n/dictionaries/no.json | 1 + i18n/dictionaries/sv.json | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx index 941d6257d..b17a2bedc 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx @@ -36,7 +36,7 @@ export default function FlexibilityOption({
diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index a9fdcfae4..a0bc269bd 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -227,6 +227,7 @@ "No breakfast": "Ingen morgenmad", "No content published": "Intet indhold offentliggjort", "No matching location found": "Der blev ikke fundet nogen matchende placering", + "No prices available": "Ingen tilgængelige priser", "No results": "Ingen resultater", "No transactions available": "Ingen tilgængelige transaktioner", "No, keep card": "Nej, behold kortet", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 96bcef2de..228f08007 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -225,6 +225,7 @@ "No breakfast": "Kein Frühstück", "No content published": "Kein Inhalt veröffentlicht", "No matching location found": "Kein passender Standort gefunden", + "No prices available": "Keine Preise verfügbar", "No results": "Keine Ergebnisse", "No transactions available": "Keine Transaktionen verfügbar", "No, keep card": "Nein, Karte behalten", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 385861316..c4870959c 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -244,6 +244,7 @@ "No breakfast": "No breakfast", "No content published": "No content published", "No matching location found": "No matching location found", + "No prices available": "No prices available", "No results": "No results", "No transactions available": "No transactions available", "No, keep card": "No, keep card", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 09fb8a403..e6f40b19f 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -227,6 +227,7 @@ "No breakfast": "Ei aamiaista", "No content published": "Ei julkaistua sisältöä", "No matching location found": "Vastaavaa sijaintia ei löytynyt", + "No prices available": "Hintoja ei ole saatavilla", "No results": "Ei tuloksia", "No transactions available": "Ei tapahtumia saatavilla", "No, keep card": "Ei, pidä kortti", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index dcbcdddf4..c7d7dc1ed 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -225,6 +225,7 @@ "No breakfast": "Ingen frokost", "No content published": "Ingen innhold publisert", "No matching location found": "Fant ingen samsvarende plassering", + "No prices available": "Ingen priser tilgjengelig", "No results": "Ingen resultater", "No transactions available": "Ingen transaksjoner tilgjengelig", "No, keep card": "Nei, behold kortet", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 404896e15..7b89c06c1 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -225,6 +225,7 @@ "No breakfast": "Ingen frukost", "No content published": "Inget innehåll publicerat", "No matching location found": "Ingen matchande plats hittades", + "No prices available": "Inga priser tillgängliga", "No results": "Inga resultat", "No transactions available": "Inga transaktioner tillgängliga", "No, keep card": "Nej, behåll kortet", From 8b66c16e17db2beea1388bff0fa7ee841b1f3ac3 Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Thu, 21 Nov 2024 07:28:39 +0000 Subject: [PATCH 023/103] Merged in feat/SW-903-breadcrumbs-select-hotel (pull request #924) Feat/SW-903 breadcrumbs select hotel * feat(SW-903): break out breadcrumbs component and add on select-hotel page * feat(903): updated paths * feat(903): fix padding and remove translations * feat(903): fix type * feat(903): refactor padding * feat(903): refactor padding again * feat(903): refactor * feat(903): fix comments * feat(903): rename content breadcrumbs back Approved-by: Pontus Dreij Approved-by: Erik Tiekstra --- .../my-pages/@breadcrumbs/[...path]/page.tsx | 2 +- .../[contentType]/[uid]/@breadcrumbs/page.tsx | 2 +- .../(standard)/select-hotel/page.module.css | 12 +++- .../(standard)/select-hotel/page.tsx | 32 +++++++++- components/Breadcrumbs/index.tsx | 53 +--------------- .../BreadcrumbsSkeleton/index.tsx} | 3 +- .../Breadcrumbs/breadcrumbs.module.css | 4 +- .../Breadcrumbs/breadcrumbs.ts | 9 +++ .../TempDesignSystem/Breadcrumbs/index.tsx | 61 +++++++++++++++++++ i18n/dictionaries/da.json | 3 + i18n/dictionaries/de.json | 3 + i18n/dictionaries/en.json | 3 + i18n/dictionaries/fi.json | 3 + i18n/dictionaries/no.json | 3 + i18n/dictionaries/sv.json | 3 + 15 files changed, 137 insertions(+), 59 deletions(-) rename components/{Breadcrumbs/BreadcrumbsSkeleton.tsx => TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx} (89%) rename components/{ => TempDesignSystem}/Breadcrumbs/breadcrumbs.module.css (82%) create mode 100644 components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts create mode 100644 components/TempDesignSystem/Breadcrumbs/index.tsx diff --git a/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/[...path]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/[...path]/page.tsx index 6775fd188..048cf9e5f 100644 --- a/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/[...path]/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/@breadcrumbs/[...path]/page.tsx @@ -1,7 +1,7 @@ import { Suspense } from "react" import Breadcrumbs from "@/components/Breadcrumbs" -import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton" +import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" import { setLang } from "@/i18n/serverContext" import { LangParams, PageArgs } from "@/types/params" diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx index ec8f14553..f8024a816 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx @@ -1,7 +1,7 @@ import { Suspense } from "react" import Breadcrumbs from "@/components/Breadcrumbs" -import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton" +import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" import { setLang } from "@/i18n/serverContext" import { LangParams, PageArgs } from "@/types/params" diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.module.css index e42544196..cb7245753 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.module.css @@ -14,6 +14,10 @@ padding: var(--Spacing-x3) var(--Spacing-x2) 0 var(--Spacing-x2); } +.header nav { + display: none; +} + .cityInformation { display: flex; flex-wrap: wrap; @@ -65,13 +69,19 @@ var(--Spacing-x5); } + .header nav { + display: block; + max-width: var(--max-width-navigation); + padding-left: 0; + } + .sorter { display: block; width: 339px; } .title { - margin: 0 auto; + margin: var(--Spacing-x3) auto 0; display: flex; max-width: var(--max-width-navigation); align-items: center; 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 62cab6b85..49893c54d 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -1,6 +1,10 @@ import { notFound } from "next/navigation" +import { Suspense } from "react" -import { selectHotelMap } from "@/constants/routes/hotelReservation" +import { + selectHotel, + selectHotelMap, +} from "@/constants/routes/hotelReservation" import { getLocations } from "@/lib/trpc/memoizedRequests" import { @@ -19,6 +23,8 @@ import { import { ChevronRightIcon } from "@/components/Icons" import StaticMap from "@/components/Maps/StaticMap" import Alert from "@/components/TempDesignSystem/Alert" +import Breadcrumbs from "@/components/TempDesignSystem/Breadcrumbs" +import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" @@ -65,12 +71,36 @@ export default async function SelectHotelPage({ }) const filterList = getFiltersFromHotels(hotels) + const breadcrumbs = [ + { + title: intl.formatMessage({ id: "Home" }), + href: `/${params.lang}`, + uid: "home-page", + }, + { + title: intl.formatMessage({ id: "Hotel reservation" }), + href: `/${params.lang}/hotelreservation`, + uid: "hotel-reservation", + }, + { + title: intl.formatMessage({ id: "Select hotel" }), + href: `${selectHotel(params.lang)}/?${selectHotelParams}`, + uid: "select-hotel", + }, + { + title: city.name, + uid: city.id, + }, + ] const isAllUnavailable = hotels.every((hotel) => hotel.price === undefined) return ( <>
+ }> + +
{city.name} diff --git a/components/Breadcrumbs/index.tsx b/components/Breadcrumbs/index.tsx index a3244fab8..31a4d445a 100644 --- a/components/Breadcrumbs/index.tsx +++ b/components/Breadcrumbs/index.tsx @@ -1,60 +1,13 @@ import { serverClient } from "@/lib/trpc/server" -import { ChevronRightSmallIcon,HouseIcon } from "@/components/Icons" -import Link from "@/components/TempDesignSystem/Link" -import Footnote from "@/components/TempDesignSystem/Text/Footnote" - -import styles from "./breadcrumbs.module.css" +import BreadcrumbsComp from "@/components/TempDesignSystem/Breadcrumbs" export default async function Breadcrumbs() { const breadcrumbs = await serverClient().contentstack.breadcrumbs.get() + if (!breadcrumbs?.length) { return null } - const homeBreadcrumb = breadcrumbs.shift() - return ( - - ) + return } diff --git a/components/Breadcrumbs/BreadcrumbsSkeleton.tsx b/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx similarity index 89% rename from components/Breadcrumbs/BreadcrumbsSkeleton.tsx rename to components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx index 68004a81e..a72f1d1a2 100644 --- a/components/Breadcrumbs/BreadcrumbsSkeleton.tsx +++ b/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx @@ -1,8 +1,7 @@ import { ChevronRightIcon, HouseIcon } from "@/components/Icons" +import styles from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css" import Footnote from "@/components/TempDesignSystem/Text/Footnote" -import styles from "./breadcrumbs.module.css" - export default function BreadcrumbsSkeleton() { return (
- - {intl.formatMessage( - { id: "{amount} {currency}" }, - { - amount: intl.formatNumber(totalPrice.local.price), - currency: totalPrice.local.currency, - } - )} - - {totalPrice.euro && ( + {totalPrice.local.amount > 0 && ( + + {intl.formatMessage( + { id: "{amount} {currency}" }, + { + amount: intl.formatNumber(totalPrice.local.amount), + currency: totalPrice.local.currency, + } + )} + + )} + {totalPrice.euro && totalPrice.euro.amount > 0 && ( {intl.formatMessage({ id: "Approx." })}{" "} {intl.formatMessage( { id: "{amount} {currency}" }, { - amount: intl.formatNumber(totalPrice.euro.price), + amount: intl.formatNumber(totalPrice.euro.amount), currency: totalPrice.euro.currency, } )} diff --git a/components/HotelReservation/SelectRate/RoomSelection/utils.ts b/components/HotelReservation/SelectRate/RoomSelection/utils.ts index 43af470e3..d6c54450a 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/utils.ts +++ b/components/HotelReservation/SelectRate/RoomSelection/utils.ts @@ -50,6 +50,7 @@ export function getQueryParamsForEnterDetails( roomTypeCode: room.roomtype, rateCode: room.ratecode, packages: room.packages?.split(",") as RoomPackageCodeEnum[], + counterRateCode: room.counterratecode, })), } } diff --git a/stores/details.ts b/stores/details.ts index a382ad7d7..52e6d41a8 100644 --- a/stores/details.ts +++ b/stores/details.ts @@ -135,8 +135,8 @@ export function createDetailsStore( }, totalPrice: { - euro: { currency: "", price: 0 }, - local: { currency: "", price: 0 }, + euro: { currency: "", amount: 0 }, + local: { currency: "", amount: 0 }, }, }), { diff --git a/types/components/hotelReservation/enterDetails/bookingData.ts b/types/components/hotelReservation/enterDetails/bookingData.ts index 0afabf91a..0683c4739 100644 --- a/types/components/hotelReservation/enterDetails/bookingData.ts +++ b/types/components/hotelReservation/enterDetails/bookingData.ts @@ -7,6 +7,7 @@ interface Room { adults: number roomTypeCode: string rateCode: string + counterRateCode: string children?: Child[] packages?: RoomPackageCodeEnum[] } @@ -18,14 +19,24 @@ export interface BookingData { } type Price = { - price: number + amount: number currency: string } export type RoomsData = { roomType: string - localPrice: Price - euroPrice: Price | undefined + prices: { + public: { + local: Price + euro: Price | undefined + } + member: + | { + local: Price + euro: Price | undefined + } + | undefined + } adults: number children?: Child[] rateDetails?: string[] diff --git a/types/components/hotelReservation/selectRate/section.ts b/types/components/hotelReservation/selectRate/section.ts index 05d86ff6c..eaea36f2e 100644 --- a/types/components/hotelReservation/selectRate/section.ts +++ b/types/components/hotelReservation/selectRate/section.ts @@ -1,4 +1,4 @@ -import { CreditCard } from "@/types/user" +import { CreditCard, SafeUser } from "@/types/user" export interface SectionProps { nextPath: string @@ -28,6 +28,7 @@ export interface BreakfastSelectionProps extends SectionProps { export interface DetailsProps extends SectionProps {} export interface PaymentProps { + user: SafeUser roomPrice: { publicPrice: number; memberPrice: number | undefined } otherPaymentOptions: string[] savedCreditCards: CreditCard[] | null diff --git a/types/components/hotelReservation/selectRate/selectRate.ts b/types/components/hotelReservation/selectRate/selectRate.ts index a1da31b84..12eb83eb1 100644 --- a/types/components/hotelReservation/selectRate/selectRate.ts +++ b/types/components/hotelReservation/selectRate/selectRate.ts @@ -11,7 +11,7 @@ interface Room { adults: number roomtype: string ratecode: string - counterratecode?: string + counterratecode: string child?: Child[] packages?: string } diff --git a/types/stores/details.ts b/types/stores/details.ts index ef6d101dc..d10bb0d61 100644 --- a/types/stores/details.ts +++ b/types/stores/details.ts @@ -8,7 +8,7 @@ export interface DetailsState { actions: { setIsSubmittingDisabled: (isSubmittingDisabled: boolean) => void setTotalPrice: (totalPrice: TotalPrice) => void - toggleSummaryOpen: () => void, + toggleSummaryOpen: () => void updateBedType: (data: BedTypeSchema) => void updateBreakfast: (data: BreakfastPackage | false) => void updateDetails: (data: DetailsSchema) => void @@ -31,10 +31,10 @@ export interface InitialState extends Partial { interface Price { currency: string - price: number + amount: number } export interface TotalPrice { euro: Price | undefined local: Price -} \ No newline at end of file +} From 3a6cfcfe5c485f4c6f37867edee1e17612f8e185 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Tue, 19 Nov 2024 09:57:54 +0100 Subject: [PATCH 025/103] fix: avoid sending query params to planet --- .../payment-callback/layout.module.css | 3 + .../payment-callback/layout.tsx | 16 ++++ .../payment-callback/page.tsx | 70 +++++++++++++++++ .../payment-callback/[lang]/[status]/route.ts | 75 ------------------- .../EnterDetails/Details/schema.ts | 4 + .../Payment/PaymentCallback/index.tsx | 42 +++++++++++ .../EnterDetails/Payment/index.tsx | 12 ++- .../SelectRate/RoomSelection/utils.ts | 47 ++++++++++++ env/client.ts | 4 - lib/graphql/batchRequest.ts | 20 +---- next-env.d.ts | 2 +- next.config.js | 5 ++ server/routers/booking/output.ts | 9 ++- stores/details.ts | 11 ++- stores/steps.ts | 2 +- utils/merge.ts | 19 +++++ 16 files changed, 227 insertions(+), 114 deletions(-) create mode 100644 app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.module.css create mode 100644 app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx create mode 100644 app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx delete mode 100644 app/api/web/payment-callback/[lang]/[status]/route.ts create mode 100644 components/HotelReservation/EnterDetails/Payment/PaymentCallback/index.tsx create mode 100644 utils/merge.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.module.css b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.module.css new file mode 100644 index 000000000..1730ffa68 --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.module.css @@ -0,0 +1,3 @@ +.layout { + background-color: var(--Base-Background-Primary-Normal); +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx new file mode 100644 index 000000000..b9ad3b13c --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx @@ -0,0 +1,16 @@ +import { notFound } from "next/navigation" + +import { env } from "@/env/server" + +import styles from "./layout.module.css" + +import { LangParams, LayoutArgs } from "@/types/params" + +export default function PaymentCallbackLayout({ + children, +}: React.PropsWithChildren>) { + if (env.HIDE_FOR_NEXT_RELEASE) { + return notFound() + } + return
{children}
+} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx new file mode 100644 index 000000000..0e4e716f2 --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx @@ -0,0 +1,70 @@ +import { redirect } from "next/navigation" + +import { + BOOKING_CONFIRMATION_NUMBER, + PaymentErrorCodeEnum, +} from "@/constants/booking" +import { Lang } from "@/constants/languages" +import { + bookingConfirmation, + payment, +} from "@/constants/routes/hotelReservation" +import { serverClient } from "@/lib/trpc/server" + +import PaymentCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback" + +import { LangParams, PageArgs } from "@/types/params" + +export default async function PaymentCallbackPage({ + params, + searchParams, +}: PageArgs< + LangParams, + { status: "error" | "success" | "cancel"; confirmationNumber?: string } +>) { + console.log(`[payment-callback] callback started`) + const lang = params.lang + const status = searchParams.status + const confirmationNumber = searchParams.confirmationNumber + + if (status === "success" && confirmationNumber) { + const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${confirmationNumber}` + + console.log(`[payment-callback] redirecting to: ${confirmationUrl}`) + redirect(confirmationUrl) + } + + const returnUrl = payment(lang) + const searchObject = new URLSearchParams() + + if (confirmationNumber) { + try { + const bookingStatus = await serverClient().booking.status({ + confirmationNumber, + }) + if (bookingStatus.metadata) { + searchObject.set( + "errorCode", + bookingStatus.metadata.errorCode?.toString() ?? "" + ) + } + } catch (error) { + console.error( + `[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}` + ) + if (status === "cancel") { + searchObject.set("errorCode", PaymentErrorCodeEnum.Cancelled.toString()) + } + if (status === "error") { + searchObject.set("errorCode", PaymentErrorCodeEnum.Failed.toString()) + } + } + } + + return ( + + ) +} diff --git a/app/api/web/payment-callback/[lang]/[status]/route.ts b/app/api/web/payment-callback/[lang]/[status]/route.ts deleted file mode 100644 index 5884d63f9..000000000 --- a/app/api/web/payment-callback/[lang]/[status]/route.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" - -import { - BOOKING_CONFIRMATION_NUMBER, - PaymentErrorCodeEnum, -} from "@/constants/booking" -import { Lang } from "@/constants/languages" -import { - bookingConfirmation, - payment, -} from "@/constants/routes/hotelReservation" -import { serverClient } from "@/lib/trpc/server" -import { getPublicURL } from "@/server/utils" - -export async function GET( - request: NextRequest, - { params }: { params: { lang: string; status: string } } -): Promise { - const publicURL = getPublicURL(request) - - console.log(`[payment-callback] callback started`) - const lang = params.lang as Lang - const status = params.status - - const queryParams = request.nextUrl.searchParams - const confirmationNumber = queryParams.get(BOOKING_CONFIRMATION_NUMBER) - - if (status === "success" && confirmationNumber) { - const confirmationUrl = new URL(`${publicURL}/${bookingConfirmation(lang)}`) - confirmationUrl.searchParams.set( - BOOKING_CONFIRMATION_NUMBER, - confirmationNumber - ) - - console.log(`[payment-callback] redirecting to: ${confirmationUrl}`) - return NextResponse.redirect(confirmationUrl) - } - - const returnUrl = new URL(`${publicURL}/${payment(lang)}`) - returnUrl.search = queryParams.toString() - - if (confirmationNumber) { - try { - const bookingStatus = await serverClient().booking.status({ - confirmationNumber, - }) - if (bookingStatus.metadata) { - returnUrl.searchParams.set( - "errorCode", - bookingStatus.metadata.errorCode?.toString() ?? "" - ) - } - } catch (error) { - console.error( - `[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}` - ) - - if (status === "cancel") { - returnUrl.searchParams.set( - "errorCode", - PaymentErrorCodeEnum.Cancelled.toString() - ) - } - if (status === "error") { - returnUrl.searchParams.set( - "errorCode", - PaymentErrorCodeEnum.Failed.toString() - ) - } - } - } - - console.log(`[payment-callback] redirecting to: ${returnUrl}`) - return NextResponse.redirect(returnUrl) -} diff --git a/components/HotelReservation/EnterDetails/Details/schema.ts b/components/HotelReservation/EnterDetails/Details/schema.ts index abb29ac2b..b3e161dc9 100644 --- a/components/HotelReservation/EnterDetails/Details/schema.ts +++ b/components/HotelReservation/EnterDetails/Details/schema.ts @@ -55,4 +55,8 @@ export const signedInDetailsSchema = z.object({ firstName: z.string().optional(), lastName: z.string().optional(), phoneNumber: z.string().optional(), + join: z + .boolean() + .optional() + .transform((_) => false), }) diff --git a/components/HotelReservation/EnterDetails/Payment/PaymentCallback/index.tsx b/components/HotelReservation/EnterDetails/Payment/PaymentCallback/index.tsx new file mode 100644 index 000000000..1c303dfbe --- /dev/null +++ b/components/HotelReservation/EnterDetails/Payment/PaymentCallback/index.tsx @@ -0,0 +1,42 @@ +"use client" + +import { useRouter } from "next/navigation" +import { useEffect } from "react" + +import { detailsStorageName } from "@/stores/details" + +import { createQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" +import LoadingSpinner from "@/components/LoadingSpinner" + +import { DetailsState } from "@/types/stores/details" + +export default function PaymentCallback({ + returnUrl, + searchObject, +}: { + returnUrl: string + searchObject: URLSearchParams +}) { + const router = useRouter() + + useEffect(() => { + const bookingData = window.sessionStorage.getItem(detailsStorageName) + + if (bookingData) { + const detailsStorage: Record< + "state", + Pick + > = JSON.parse(bookingData) + const searchParams = createQueryParamsForEnterDetails( + detailsStorage.state.data.booking, + searchObject + ) + + if (searchParams.size > 0) { + router.replace(`${returnUrl}?${searchParams.toString()}`) + } + } + }, [returnUrl, router, searchObject]) + + return +} diff --git a/components/HotelReservation/EnterDetails/Payment/index.tsx b/components/HotelReservation/EnterDetails/Payment/index.tsx index 6a2f8877b..ab1f78807 100644 --- a/components/HotelReservation/EnterDetails/Payment/index.tsx +++ b/components/HotelReservation/EnterDetails/Payment/index.tsx @@ -60,7 +60,6 @@ export default function Payment({ const router = useRouter() const lang = useLang() const intl = useIntl() - const queryParams = useSearchParams() const { booking, ...userData } = useDetailsStore((state) => state.data) const setIsSubmittingDisabled = useDetailsStore( (state) => state.actions.setIsSubmittingDisabled @@ -164,9 +163,6 @@ export default function Payment({ ]) function handleSubmit(data: PaymentFormData) { - const allQueryParams = - queryParams.size > 0 ? `?${queryParams.toString()}` : "" - // set payment method to card if saved card is submitted const paymentMethod = isPaymentMethodEnum(data.paymentMethod) ? data.paymentMethod @@ -176,6 +172,8 @@ export default function Payment({ (card) => card.id === data.paymentMethod ) + const paymentRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}/${lang}/hotelreservation/payment-callback` + initiateBooking.mutate({ hotelId: hotel, checkInDate: fromDate, @@ -224,9 +222,9 @@ export default function Payment({ } : undefined, - success: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/success`, - error: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/error${allQueryParams}`, - cancel: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/cancel${allQueryParams}`, + success: `${paymentRedirectUrl}/success`, + error: `${paymentRedirectUrl}/error`, + cancel: `${paymentRedirectUrl}/cancel`, }, }) } diff --git a/components/HotelReservation/SelectRate/RoomSelection/utils.ts b/components/HotelReservation/SelectRate/RoomSelection/utils.ts index d6c54450a..c47841708 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/utils.ts +++ b/components/HotelReservation/SelectRate/RoomSelection/utils.ts @@ -54,3 +54,50 @@ export function getQueryParamsForEnterDetails( })), } } + +export function createQueryParamsForEnterDetails( + bookingData: BookingData, + intitalSearchParams: URLSearchParams +) { + const { hotel, fromDate, toDate, rooms } = bookingData + + const bookingSearchParams = new URLSearchParams({ hotel, fromDate, toDate }) + const searchParams = new URLSearchParams([ + ...intitalSearchParams, + ...bookingSearchParams, + ]) + + rooms.forEach((item, index) => { + if (item?.adults) { + searchParams.set(`room[${index}].adults`, item.adults.toString()) + } + if (item?.children) { + item.children.forEach((child, childIndex) => { + searchParams.set( + `room[${index}].child[${childIndex}].age`, + child.age.toString() + ) + searchParams.set( + `room[${index}].child[${childIndex}].bed`, + child.bed.toString() + ) + }) + } + if (item?.roomTypeCode) { + searchParams.set(`room[${index}].roomtype`, item.roomTypeCode) + } + if (item?.rateCode) { + searchParams.set(`room[${index}].ratecode`, item.rateCode) + } + + if (item?.counterRateCode) { + searchParams.set(`room[${index}].counterratecode`, item.counterRateCode) + } + + if (item.packages && item.packages.length > 0) { + searchParams.set(`room[${index}].packages`, item.packages.join(",")) + } + }) + + return searchParams +} diff --git a/env/client.ts b/env/client.ts index 4eafd5592..467100c01 100644 --- a/env/client.ts +++ b/env/client.ts @@ -5,14 +5,10 @@ export const env = createEnv({ client: { NEXT_PUBLIC_NODE_ENV: z.enum(["development", "test", "production"]), NEXT_PUBLIC_PORT: z.string().default("3000"), - NEXT_PUBLIC_PAYMENT_CALLBACK_URL: z - .string() - .default("/api/web/payment-callback"), }, emptyStringAsUndefined: true, runtimeEnv: { NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV, NEXT_PUBLIC_PORT: process.env.NEXT_PUBLIC_PORT, - NEXT_PUBLIC_PAYMENT_CALLBACK_URL: `${process.env.NODE_ENV === "development" ? `http://localhost:${process.env.NEXT_PUBLIC_PORT}` : ""}/api/web/payment-callback`, }, }) diff --git a/lib/graphql/batchRequest.ts b/lib/graphql/batchRequest.ts index b6b5dbe3f..86361d527 100644 --- a/lib/graphql/batchRequest.ts +++ b/lib/graphql/batchRequest.ts @@ -2,30 +2,14 @@ import "server-only" import deepmerge from "deepmerge" +import { arrayMerge } from "@/utils/merge" + import { request } from "./request" import type { BatchRequestDocument } from "graphql-request" import type { Data } from "@/types/request" -function arrayMerge( - target: any[], - source: any[], - options: deepmerge.ArrayMergeOptions | undefined -) { - const destination = target.slice() - source.forEach((item, index) => { - if (typeof destination[index] === "undefined") { - destination[index] = options?.cloneUnlessOtherwiseSpecified(item, options) - } else if (options?.isMergeableObject(item)) { - destination[index] = deepmerge(target[index], item, options) - } else if (target.indexOf(item) === -1) { - destination.push(item) - } - }) - return destination -} - export async function batchRequest( queries: (BatchRequestDocument & { options?: RequestInit })[] ): Promise> { diff --git a/next-env.d.ts b/next-env.d.ts index 40c3d6809..4f11a03dc 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js index 222f085ac..34616f754 100644 --- a/next.config.js +++ b/next.config.js @@ -282,6 +282,11 @@ const nextConfig = { "/:lang/hotelreservation/:step(breakfast|details|payment|select-bed)", destination: "/:lang/hotelreservation/step?step=:step", }, + { + source: "/:lang/hotelreservation/payment-callback/:status", + destination: + "/:lang/hotelreservation/payment-callback?status=:status", + }, ], } }, diff --git a/server/routers/booking/output.ts b/server/routers/booking/output.ts index ae2e14cf4..5c8879c00 100644 --- a/server/routers/booking/output.ts +++ b/server/routers/booking/output.ts @@ -17,13 +17,14 @@ export const createBookingSchema = z paymentUrl: z.string().nullable(), metadata: z .object({ - errorCode: z.number().optional(), - errorMessage: z.string().optional(), + errorCode: z.number().nullable().optional(), + errorMessage: z.string().nullable().optional(), priceChangedMetadata: z .object({ - roomPrice: z.number().optional(), - totalPrice: z.number().optional(), + roomPrice: z.number().nullable().optional(), + totalPrice: z.number().nullable().optional(), }) + .nullable() .optional(), }) .nullable(), diff --git a/stores/details.ts b/stores/details.ts index 52e6d41a8..f8698e54c 100644 --- a/stores/details.ts +++ b/stores/details.ts @@ -11,11 +11,12 @@ import { signedInDetailsSchema, } from "@/components/HotelReservation/EnterDetails/Details/schema" import { DetailsContext } from "@/contexts/Details" +import { arrayMerge } from "@/utils/merge" import { StepEnum } from "@/types/enums/step" import type { DetailsState, InitialState } from "@/types/stores/details" -export const storageName = "details-storage" +export const detailsStorageName = "details-storage" export function createDetailsStore( initialState: InitialState, isMember: boolean @@ -27,13 +28,15 @@ export function createDetailsStore( * we cannot use the data as `defaultValues` for our forms. * RHF caches defaultValues on mount. */ - const detailsStorageUnparsed = sessionStorage.getItem(storageName) + const detailsStorageUnparsed = sessionStorage.getItem(detailsStorageName) if (detailsStorageUnparsed) { const detailsStorage: Record< "state", Pick > = JSON.parse(detailsStorageUnparsed) - initialState = merge(initialState, detailsStorage.state.data) + initialState = merge(detailsStorage.state.data, initialState, { + arrayMerge, + }) } } return create()( @@ -140,7 +143,7 @@ export function createDetailsStore( }, }), { - name: storageName, + name: detailsStorageName, onRehydrateStorage() { return function (state) { if (state) { diff --git a/stores/steps.ts b/stores/steps.ts index cf14f6768..efa356c8b 100644 --- a/stores/steps.ts +++ b/stores/steps.ts @@ -13,7 +13,7 @@ import { } from "@/components/HotelReservation/EnterDetails/Details/schema" import { StepsContext } from "@/contexts/Steps" -import { storageName as detailsStorageName } from "./details" +import { detailsStorageName as detailsStorageName } from "./details" import { StepEnum } from "@/types/enums/step" import type { DetailsState } from "@/types/stores/details" diff --git a/utils/merge.ts b/utils/merge.ts new file mode 100644 index 000000000..84aaae145 --- /dev/null +++ b/utils/merge.ts @@ -0,0 +1,19 @@ +import merge from "deepmerge" + +export function arrayMerge( + target: any[], + source: any[], + options: merge.ArrayMergeOptions +) { + const destination = target.slice() + source.forEach((item, index) => { + if (typeof destination[index] === "undefined") { + destination[index] = options.cloneUnlessOtherwiseSpecified(item, options) + } else if (options?.isMergeableObject(item)) { + destination[index] = merge(target[index], item, options) + } else if (target.indexOf(item) === -1) { + destination.push(item) + } + }) + return destination +} From 92b0ec9c84052787e1025884f12557c26c8a238c Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Thu, 21 Nov 2024 11:14:28 +0100 Subject: [PATCH 026/103] fix: hydrate store with updated values --- stores/details.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/stores/details.ts b/stores/details.ts index f8698e54c..30bf1ed81 100644 --- a/stores/details.ts +++ b/stores/details.ts @@ -144,34 +144,33 @@ export function createDetailsStore( }), { name: detailsStorageName, - onRehydrateStorage() { + onRehydrateStorage(prevState) { return function (state) { if (state) { const validatedBedType = bedTypeSchema.safeParse(state.data) - if (validatedBedType.success) { - state.actions.updateValidity(StepEnum.selectBed, true) - } else { - state.actions.updateValidity(StepEnum.selectBed, false) + if (validatedBedType.success !== state.isValid["select-bed"]) { + state.isValid["select-bed"] = validatedBedType.success } const validatedBreakfast = breakfastStoreSchema.safeParse( state.data ) - if (validatedBreakfast.success) { - state.actions.updateValidity(StepEnum.breakfast, true) - } else { - state.actions.updateValidity(StepEnum.breakfast, false) + if (validatedBreakfast.success !== state.isValid.breakfast) { + state.isValid.breakfast = validatedBreakfast.success } const detailsSchema = isMember ? signedInDetailsSchema : guestDetailsSchema const validatedDetails = detailsSchema.safeParse(state.data) - if (validatedDetails.success) { - state.actions.updateValidity(StepEnum.details, true) - } else { - state.actions.updateValidity(StepEnum.details, false) + if (validatedDetails.success !== state.isValid.details) { + state.isValid.details = validatedDetails.success } + + const mergedState = merge(state.data, prevState.data, { + arrayMerge, + }) + state.data = mergedState } } }, From de2057b41c93d622fa5abdc5d9b3a67c88b67b5b Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Thu, 21 Nov 2024 11:11:31 +0100 Subject: [PATCH 027/103] fix: SW-967 Alert showing no hotels with filters --- .../HotelCardListing/index.tsx | 32 ++++++++++++------- i18n/dictionaries/en.json | 2 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx index 6ceb9aa85..3e96a6cac 100644 --- a/components/HotelReservation/HotelCardListing/index.tsx +++ b/components/HotelReservation/HotelCardListing/index.tsx @@ -1,9 +1,11 @@ "use client" import { useSearchParams } from "next/navigation" import { useEffect, useMemo, useState } from "react" +import { useIntl } from "react-intl" import { useHotelFilterStore } from "@/stores/hotel-filters" +import Alert from "@/components/TempDesignSystem/Alert" import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton" import HotelCard from "../HotelCard" @@ -17,6 +19,7 @@ import { type HotelData, } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter" +import { AlertTypeEnum } from "@/types/enums/alert" export default function HotelCardListing({ hotelData, @@ -28,6 +31,7 @@ export default function HotelCardListing({ const activeFilters = useHotelFilterStore((state) => state.activeFilters) const setResultCount = useHotelFilterStore((state) => state.setResultCount) const [showBackToTop, setShowBackToTop] = useState(false) + const intl = useIntl() const sortBy = useMemo( () => searchParams.get("sort") ?? DEFAULT_SORT, @@ -101,17 +105,23 @@ export default function HotelCardListing({ return (
- {hotels?.length - ? hotels.map((hotel) => ( - - )) - : null} + {hotels?.length ? ( + hotels.map((hotel) => ( + + )) + ) : activeFilters ? ( + + ) : null} {showBackToTop && }
) diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 950bbb36e..4abc07ea7 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -473,6 +473,8 @@ "breakfast.price.free": "{amount} {currency} 0 {currency}/night", "by": "by", "characters": "characters", + "filters.nohotel.heading": "No hotels match your filters", + "filters.nohotel.text": "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.", "from": "from", "guaranteeing": "guaranteeing", "guest": "guest", From 01638f4dd73df61799bb562b4c102ae8748b2930 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Thu, 21 Nov 2024 11:18:02 +0100 Subject: [PATCH 028/103] fix: SW-967 Fixed bad setState call error --- components/HotelReservation/HotelCardListing/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx index 3e96a6cac..f666c5ba2 100644 --- a/components/HotelReservation/HotelCardListing/index.tsx +++ b/components/HotelReservation/HotelCardListing/index.tsx @@ -73,7 +73,6 @@ export default function HotelCardListing({ const hotels = useMemo(() => { if (activeFilters.length === 0) { - setResultCount(sortedHotels.length) return sortedHotels } @@ -85,9 +84,8 @@ export default function HotelCardListing({ ) ) - setResultCount(filteredHotels.length) return filteredHotels - }, [activeFilters, sortedHotels, setResultCount]) + }, [activeFilters, sortedHotels]) useEffect(() => { const handleScroll = () => { @@ -99,6 +97,10 @@ export default function HotelCardListing({ return () => window.removeEventListener("scroll", handleScroll) }, []) + useEffect(() => { + setResultCount(hotels ? hotels.length : 0) + }, [hotels, setResultCount]) + function scrollToTop() { window.scrollTo({ top: 0, behavior: "smooth" }) } From 0824f7ce26e1a914bc19ffc36b5e431fbe386825 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Thu, 7 Nov 2024 11:51:21 +0100 Subject: [PATCH 029/103] fix(SW-696): add unwrap to surprises add animations to sliding cards various minor fixes --- .../(live)/(protected)/my-pages/layout.tsx | 6 +- .../Surprises/SurprisesNotification.tsx | 227 ++++++++++++------ components/MyPages/Surprises/index.tsx | 5 + .../MyPages/Surprises/surprises.module.css | 21 +- i18n/dictionaries/da.json | 2 +- i18n/dictionaries/de.json | 2 +- i18n/dictionaries/en.json | 2 +- i18n/dictionaries/fi.json | 2 +- i18n/dictionaries/no.json | 2 +- i18n/dictionaries/sv.json | 2 +- public/_static/img/confetti.svg | 1 + server/routers/contentstack/reward/input.ts | 9 +- server/routers/contentstack/reward/query.ts | 102 +++++--- types/components/blocks/surprises.ts | 9 +- 14 files changed, 264 insertions(+), 128 deletions(-) create mode 100644 public/_static/img/confetti.svg diff --git a/app/[lang]/(live)/(protected)/my-pages/layout.tsx b/app/[lang]/(live)/(protected)/my-pages/layout.tsx index 43268adca..80a038b9a 100644 --- a/app/[lang]/(live)/(protected)/my-pages/layout.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/layout.tsx @@ -2,8 +2,8 @@ import { Suspense } from "react" import LoadingSpinner from "@/components/LoadingSpinner" import Sidebar from "@/components/MyPages/Sidebar" +import Surprises from "@/components/MyPages/Surprises" -// import Surprises from "@/components/MyPages/Surprises" import styles from "./layout.module.css" export default async function MyPagesLayout({ @@ -24,9 +24,7 @@ export default async function MyPagesLayout({ - {/* TODO: Waiting on new API stuff - - */} +
) } diff --git a/components/MyPages/Surprises/SurprisesNotification.tsx b/components/MyPages/Surprises/SurprisesNotification.tsx index 6b71c6703..f69269c8c 100644 --- a/components/MyPages/Surprises/SurprisesNotification.tsx +++ b/components/MyPages/Surprises/SurprisesNotification.tsx @@ -1,5 +1,6 @@ "use client" +import { AnimatePresence, motion } from "framer-motion" import { usePathname } from "next/navigation" import React, { useState } from "react" import { Dialog, Modal, ModalOverlay } from "react-aria-components" @@ -21,7 +22,29 @@ import useLang from "@/hooks/useLang" import styles from "./surprises.module.css" -import type { SurprisesProps } from "@/types/components/blocks/surprises" +import type { + Surprise, + SurprisesProps, +} from "@/types/components/blocks/surprises" + +const variants = { + enter: (direction: number) => { + return { + x: direction > 0 ? 1000 : -1000, + opacity: 0, + } + }, + center: { + x: 0, + opacity: 1, + }, + exit: (direction: number) => { + return { + x: direction < 0 ? 1000 : -1000, + opacity: 0, + } + }, +} export default function SurprisesNotification({ surprises, @@ -30,9 +53,29 @@ export default function SurprisesNotification({ const lang = useLang() const pathname = usePathname() const [open, setOpen] = useState(true) - const [selectedSurprise, setSelectedSurprise] = useState(0) + const [[selectedSurprise, direction], setSelectedSurprise] = useState([0, 0]) const [showSurprises, setShowSurprises] = useState(false) - const update = trpc.contentstack.rewards.update.useMutation() + const unwrap = trpc.contentstack.rewards.unwrap.useMutation({ + onSuccess: () => { + if (pathname.indexOf(benefits[lang]) !== 0) { + toast.success( + <> + {intl.formatMessage( + { id: "Gift(s) added to your benefits" }, + { amount: surprises.length } + )} +
+ + {intl.formatMessage({ id: "Go to My Benefits" })} + + + ) + } + }, + onError: (error) => { + console.error("Error", error) + }, + }) const intl = useIntl() if (!surprises.length) { @@ -41,36 +84,48 @@ export default function SurprisesNotification({ const surprise = surprises[selectedSurprise] - function showSurprise(n: number) { - setSelectedSurprise((surprise) => surprise + n) + function showSurprise(newDirection: number) { + setSelectedSurprise(([currentIndex]) => [ + currentIndex + newDirection, + newDirection, + ]) } - function viewRewards() { - if (surprise.reward_id) { - update.mutate({ id: surprise.reward_id }) - } - } + async function viewRewards() { + const updates = surprises + .map((surprise) => { + const coupons = surprise.coupons + ?.map((coupon) => { + if (coupon?.couponCode) { + return { + rewardId: surprise.id, + couponCode: coupon.couponCode, + } + } + }) + .filter( + (coupon): coupon is { rewardId: string; couponCode: string } => + !!coupon + ) - function closeModal(close: VoidFunction) { - viewRewards() - close() - - if (pathname.indexOf(benefits[lang]) !== 0) { - toast.success( - <> - {intl.formatMessage( - { id: "Gift(s) added to your benefits" }, - { amount: surprises.length } - )} -
- - {intl.formatMessage({ id: "Go to My Benefits" })} - - + return coupons + }) + .flat() + .filter( + (coupon): coupon is { rewardId: string; couponCode: string } => !!coupon ) - } + + unwrap.mutate(updates) } + const earliestExpirationDate = surprise.coupons?.reduce( + (earliestDate, coupon) => { + const expiresAt = dt(coupon.expiresAt) + return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt + }, + dt() + ) + return ( )}
{showSurprises ? ( <> -
- - {surprise.description} -
- - {intl.formatMessage({ id: "Valid through" })}{" "} - {dt(surprise.endsAt) - .locale(lang) - .format("DD MMM YYYY")} - - - {intl.formatMessage({ id: "Membership ID" })}{" "} - {membershipNumber} - -
-
-
+ + + + {surprise.description} +
+ + {intl.formatMessage( + { id: "Expires at the earliest" }, + { + date: dt(earliestExpirationDate) + .locale(lang) + .format("D MMM YYYY"), + } + )} + + + {intl.formatMessage({ + id: "Membership ID", + })}{" "} + {membershipNumber} + +
+
+
+
{surprises.length > 1 && ( <>