From b0674d07f5f7cdf3556908a8f6fd124a1627611f Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Mon, 24 Mar 2025 11:23:11 +0000 Subject: [PATCH] Merged in feat/SW-1308-booking-codes-track-b (pull request #1607) Feat/SW-1308 booking codes track b * feat: SW-1308 Booking codes track b * feat: SW-1308 Booking codes Track B implementation * feat: SW-1308 Optimized after rebase Approved-by: Arvid Norlin --- .../(standard)/details/page.tsx | 2 + .../BookingCode/booking-code.module.css | 2 +- .../FormContent/BookingCode/index.tsx | 19 ++- .../FormContent/RewardNight/index.tsx | 18 +- .../RewardNight/reward-night.module.css | 1 - .../components/Forms/BookingWidget/schema.ts | 17 +- .../JoinScandicFriendsCard/index.tsx | 5 +- .../RoomOne/JoinScandicFriendsCard/index.tsx | 5 +- .../RoomOne/MemberPriceModal/index.tsx | 6 +- .../Summary/UI/PriceDetailsTable/index.tsx | 122 ++++++++----- .../EnterDetails/Summary/UI/index.tsx | 13 +- .../hotelChequeCard.module.css | 21 +++ .../HotelCard/HotelChequeCard/index.tsx | 59 +++++++ .../HotelPriceCard/hotelPriceCard.module.css | 5 + .../HotelCard/HotelPriceCard/index.tsx | 75 ++++---- .../hotelVoucherCard.module.css | 19 +++ .../HotelCard/HotelVoucherCard/index.tsx | 32 ++++ .../HotelReservation/HotelCard/index.tsx | 21 ++- .../HotelCardListing/index.tsx | 49 ++++-- .../HotelCardListing/utils.ts | 19 +-- .../HotelReservation/SelectHotel/index.tsx | 64 +++++-- .../RateSummary/MobileSummary/Summary.tsx | 10 +- .../RoomsContainer/RateSummary/index.tsx | 35 +++- .../RoomsContainer/RateSummary/utils.ts | 81 +++++++++ .../FlexibilityOption/PriceList/index.tsx | 47 +++-- .../PriceList/priceList.module.css | 1 + .../RoomCard/FlexibilityOption/index.tsx | 5 - .../ChequePrice/chequePrice.module.css | 15 ++ .../ChequePrice/index.tsx | 79 +++++++++ .../flexibilityOptionCheque.module.css | 73 ++++++++ .../FlexibilityOptionCheque/index.tsx | 118 +++++++++++++ .../VoucherPrice/index.tsx | 36 ++++ .../VoucherPrice/voucherPrice.module.css | 15 ++ .../flexibilityOptionVoucher.module.css | 73 ++++++++ .../FlexibilityOptionVoucher/index.tsx | 117 +++++++++++++ .../RoomSelectionPanel/RoomCard/index.tsx | 41 ++++- .../Rooms/RoomSelectionPanel/index.tsx | 104 ++++++------ .../TempDesignSystem/Form/Input/index.tsx | 3 +- .../TempDesignSystem/Form/Input/input.ts | 1 + apps/scandic-web/i18n/dictionaries/da.json | 1 + apps/scandic-web/i18n/dictionaries/de.json | 1 + apps/scandic-web/i18n/dictionaries/en.json | 1 + apps/scandic-web/i18n/dictionaries/fi.json | 1 + apps/scandic-web/i18n/dictionaries/no.json | 1 + apps/scandic-web/i18n/dictionaries/sv.json | 1 + .../providers/EnterDetailsProvider.tsx | 1 + .../providers/SelectRate/RoomProvider.tsx | 8 + .../server/routers/hotels/output.ts | 32 ++++ .../server/routers/hotels/query.ts | 30 +++- .../schemas/availability/productType.ts | 4 + .../hotels/schemas/productTypePrice.ts | 21 +++ .../schemas/roomAvailability/configuration.ts | 9 +- .../schemas/roomAvailability/product.ts | 4 + .../stores/enter-details/helpers.ts | 160 ++++++++++++++++++ .../scandic-web/stores/enter-details/index.ts | 17 +- apps/scandic-web/stores/select-rate/index.ts | 110 +++++++++++- .../hotelReservation/enterDetails/details.ts | 6 +- .../selectHotel/priceCardProps.ts | 14 +- .../selectRate/flexibilityOption.ts | 6 + .../hotelReservation/selectRate/selectRate.ts | 20 ++- .../types/contexts/select-rate/room.ts | 2 + apps/scandic-web/types/enums/currency.ts | 2 + apps/scandic-web/types/enums/rateType.ts | 1 + apps/scandic-web/types/stores/rates.ts | 2 + .../types/trpc/routers/hotel/availability.ts | 4 + apps/scandic-web/utils/numberFormatting.ts | 10 +- 66 files changed, 1612 insertions(+), 285 deletions(-) create mode 100644 apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/hotelChequeCard.module.css create mode 100644 apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/hotelVoucherCard.module.css create mode 100644 apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/ChequePrice/chequePrice.module.css create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/ChequePrice/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/flexibilityOptionCheque.module.css create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/voucherPrice.module.css create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/flexibilityOptionVoucher.module.css create mode 100644 apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/index.tsx diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx index 2dd221915..cd46c4065 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx @@ -110,6 +110,8 @@ export default async function DetailsPage({ memberRate: roomAvailability?.memberRate, publicRate: roomAvailability.publicRate, redemptionRate: roomAvailability.redemptionRate, + voucherRate: roomAvailability.voucherRate, + chequeRate: roomAvailability.chequeRate, }, isAvailable: roomAvailability.selectedRoom.status === AvailabilityEnum.Available, diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css index 470729f51..99034d6b7 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css @@ -21,7 +21,7 @@ .errorContainer { display: flex; flex-direction: column; - gap: var(--Spacing-x-half); + gap: var(--Spacing-x1); } .error { display: flex; diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx index 9ec9cedad..32456bf77 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx @@ -25,6 +25,8 @@ import type { } from "@/types/components/bookingWidget" import type { ButtonProps } from "@/components/TempDesignSystem/Button/button" +const invalidBookingCodeMsg = "Invalid booking code" + export default function BookingCode() { const intl = useIntl() const checkIsTablet = useMediaQuery( @@ -53,11 +55,14 @@ export default function BookingCode() { function updateBookingCodeFormValue(value: string) { // Set value and show error if validation fails - setValue("bookingCode.value", value, { shouldValidate: true }) + setValue("bookingCode.value", value.toUpperCase(), { shouldValidate: true }) if (getValues(REDEMPTION)) { // Remove the redemption as user types booking code and show notification for the same - setValue(REDEMPTION, false) + // Add delay to handle table mode rendering + setTimeout(function () { + setValue(REDEMPTION, false) + }) // Hide the above notification popup after 5 seconds by re-triggering validation // This is kept consistent with location search field error notification timeout setTimeout(function () { @@ -247,12 +252,13 @@ function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) { function BookingCodeError({ codeError }: { codeError: FieldError }) { const intl = useIntl() const isMultiroomErr = codeError.message?.indexOf("Multi-room") !== -1 + const isInvalidErr = codeError.message === invalidBookingCodeMsg return (
- + {intl.formatMessage({ id: codeError.message })} @@ -282,7 +288,7 @@ export function RemoveExtraRooms({ ...props }: ButtonProps) { type="button" onClick={removeExtraRooms} size="small" - intent="secondary" + intent="primary" {...props} > {intl.formatMessage({ id: "Remove extra rooms" })} @@ -354,10 +360,11 @@ function TabletBookingCode({ onChange: (e) => updateValue(e.target.value), })} autoComplete="off" + hideError />
{codeError?.message ? ( - + ) : ( (null) const reward = intl.formatMessage({ id: "Book Reward Night" }) const redemptionErr = errors[REDEMPTION] - const bookingCode = getValues("bookingCode.value") const isMultiRoomError = redemptionErr?.message?.indexOf("Multi-room") === 0 - const errorInfoColor = isMultiRoomError ? "red" : "blue" function validateRedemption(value: boolean) { // Validate redemption as per the rules defined in the schema trigger(REDEMPTION) - if (value && bookingCode) { + if (value && getValues("bookingCode.value")) { + setValue("bookingCode.flag", false) setValue("bookingCode.value", "", { shouldValidate: true }) // Hide the notification popup after 5 seconds by re-triggering validation // This is kept consistent with location search field error notification timeout @@ -87,15 +86,8 @@ export default function RewardNight() { {redemptionErr && (
- - + + {intl.formatMessage({ id: redemptionErr.message })} {isMultiRoomError ? : null} diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/RewardNight/reward-night.module.css b/apps/scandic-web/components/Forms/BookingWidget/FormContent/RewardNight/reward-night.module.css index 150e7aa86..e9b8772fc 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/RewardNight/reward-night.module.css +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/RewardNight/reward-night.module.css @@ -12,7 +12,6 @@ .error { display: flex; gap: var(--Spacing-x-half); - align-items: center; } .errorIcon { diff --git a/apps/scandic-web/components/Forms/BookingWidget/schema.ts b/apps/scandic-web/components/Forms/BookingWidget/schema.ts index 40b760b48..6b0f71786 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/schema.ts +++ b/apps/scandic-web/components/Forms/BookingWidget/schema.ts @@ -104,10 +104,7 @@ export const bookingWidgetSchema = z path: ["search"], }) } - if ( - value.rooms.length > 1 && - value.bookingCode?.value.toLowerCase().startsWith("vo") - ) { + if (value.rooms.length > 1 && value.bookingCode?.value.startsWith("VO")) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Multi-room booking is not available with this booking code.", @@ -119,18 +116,6 @@ export const bookingWidgetSchema = z path: ["rooms"], }) } - if (value.rooms.length > 1 && value.redemption) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Multi-room booking is not available with reward night.", - path: ["bookingCode.value"], - }) - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Multi-room booking is not available with reward night.", - path: ["rooms"], - }) - } if (value.rooms.length > 1 && value.redemption) { ctx.addIssue({ code: z.ZodIssueCode.custom, diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx index 130538de9..a43d704ba 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx @@ -11,6 +11,7 @@ import { formatPrice } from "@/utils/numberFormatting" import styles from "./joinScandicFriendsCard.module.css" import type { JoinScandicFriendsCardProps } from "@/types/components/hotelReservation/enterDetails/details" +import { CurrencyEnum } from "@/types/enums/currency" export default function JoinScandicFriendsCard({ name = "join", @@ -35,8 +36,8 @@ export default function JoinScandicFriendsCard({ { amount: formatPrice( intl, - room.roomRate.memberRate.localPrice.pricePerStay, - room.roomRate.memberRate.localPrice.currency + room.roomRate.memberRate.localPrice.pricePerStay ?? 0, + room.roomRate.memberRate.localPrice.currency ?? CurrencyEnum.Unknown ), roomNr, } diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx index 36e6530ed..5bc3e5d6d 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx @@ -17,6 +17,7 @@ import { formatPrice } from "@/utils/numberFormatting" import styles from "./joinScandicFriendsCard.module.css" import type { JoinScandicFriendsCardProps } from "@/types/components/hotelReservation/enterDetails/details" +import { CurrencyEnum } from "@/types/enums/currency" export default function JoinScandicFriendsCard({ name = "join", @@ -42,8 +43,8 @@ export default function JoinScandicFriendsCard({ { amount: formatPrice( intl, - room.roomRate.memberRate.localPrice.pricePerStay, - room.roomRate.memberRate.localPrice.currency + room.roomRate.memberRate.localPrice.pricePerStay ?? 0, + room.roomRate.memberRate.localPrice.currency ?? CurrencyEnum.Unknown ), } ) diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/MemberPriceModal/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/MemberPriceModal/index.tsx index 83bff3d40..0f638d797 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/MemberPriceModal/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/MemberPriceModal/index.tsx @@ -15,6 +15,8 @@ import styles from "./modal.module.css" import type { Dispatch, SetStateAction } from "react" +import { CurrencyEnum } from "@/types/enums/currency" + export default function MemberPriceModal({ isOpen, setIsOpen, @@ -49,8 +51,8 @@ export default function MemberPriceModal({ {formatPrice( intl, - memberPrice.pricePerStay, - memberPrice.currency + memberPrice.pricePerStay ?? 0, + memberPrice.currency ?? CurrencyEnum.Unknown )} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/PriceDetailsTable/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/PriceDetailsTable/index.tsx index 535f3ee5f..d299b20dc 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/PriceDetailsTable/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/PriceDetailsTable/index.tsx @@ -14,6 +14,7 @@ import { formatPrice } from "@/utils/numberFormatting" import styles from "./priceDetailsTable.module.css" import type { Price } from "@/types/components/hotelReservation/price" +import { CurrencyEnum } from "@/types/enums/currency" import type { RoomState } from "@/types/stores/enter-details" function Row({ @@ -117,6 +118,8 @@ export default function PriceDetailsTable({ getMemberRate && room.roomRate.memberRate ? room.roomRate.memberRate : room.roomRate.publicRate + const voucherPrice = room.roomRate.voucherRate + const chequePrice = room.roomRate.chequeRate if (!price) { return null } @@ -129,42 +132,72 @@ export default function PriceDetailsTable({ )} - - {room.roomFeatures - ? room.roomFeatures.map((feature) => ( + {price && ( + <> + + {room.roomFeatures + ? room.roomFeatures.map((feature) => ( + + )) + : null} + {room.bedType ? ( - )) - : null} - {room.bedType ? ( + ) : null} + + + )} + {voucherPrice && ( - ) : null} - + )} + {chequePrice && ( + + )} {room.breakfast ? ( @@ -218,14 +251,19 @@ export default function PriceDetailsTable({ })} - - + {totalPrice.local.currency !== CurrencyEnum.Voucher && + totalPrice.local.currency !== CurrencyEnum.CC ? ( + <> + + + + ) : null} @@ -237,7 +275,9 @@ export default function PriceDetailsTable({ {formatPrice( intl, totalPrice.local.price, - totalPrice.local.currency + totalPrice.local.currency, + totalPrice.local.additionalPrice, + totalPrice.local.additionalPriceCurrency )} diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx index dc78bc4e8..603add904 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -32,6 +32,7 @@ import styles from "./ui.module.css" import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums" import type { RoomRate } from "@/types/components/hotelReservation/enterDetails/details" import type { EnterDetailsSummaryProps } from "@/types/components/hotelReservation/summary" +import { CurrencyEnum } from "@/types/enums/currency" export default function SummaryUI({ booking, @@ -60,9 +61,10 @@ export default function SummaryUI({ function getMemberPrice(roomRate: RoomRate) { return roomRate?.memberRate ? { - currency: roomRate.memberRate.localPrice.currency, + currency: + roomRate.memberRate.localPrice.currency ?? CurrencyEnum.Unknown, pricePerNight: roomRate.memberRate.localPrice.pricePerNight, - amount: roomRate.memberRate.localPrice.pricePerStay, + amount: roomRate.memberRate.localPrice.pricePerStay ?? 0, } : null } @@ -249,7 +251,8 @@ export default function SummaryUI({ {formatPrice( intl, 0, - room.roomPrice.perStay.local.currency + room.roomPrice.perStay.local.additionalPriceCurrency ?? + room.roomPrice.perStay.local.currency )}
@@ -413,7 +416,9 @@ export default function SummaryUI({ value: formatPrice( intl, totalPrice.requested.price, - totalPrice.requested.currency + totalPrice.requested.currency, + totalPrice.requested.additionalPrice, + totalPrice.requested.additionalPriceCurrency ), } )} diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/hotelChequeCard.module.css b/apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/hotelChequeCard.module.css new file mode 100644 index 000000000..582d9787f --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/hotelChequeCard.module.css @@ -0,0 +1,21 @@ +.chequeCard { + padding: var(--Spacing-x-one-and-half); + background-color: var(--Base-Surface-Secondary-light-Normal); + border-radius: var(--Corner-radius-Medium); + margin: 0; + width: 100%; + display: grid; + gap: var(--Spacing-x1); +} + +.chequeRow, +.cheque { + display: flex; + gap: var(--Spacing-x-half); + justify-content: space-between; + align-items: baseline; +} + +.cheque { + justify-content: end; +} diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/index.tsx new file mode 100644 index 000000000..7395f0452 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/HotelCard/HotelChequeCard/index.tsx @@ -0,0 +1,59 @@ +import { useIntl } from "react-intl" + +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" + +import styles from "./hotelChequeCard.module.css" + +import { CurrencyEnum } from "@/types/enums/currency" +import type { ProductTypeCheque } from "@/types/trpc/routers/hotel/availability" + +export default function HotelChequeCard({ + productTypeCheque, +}: { + productTypeCheque: ProductTypeCheque +}) { + const intl = useIntl() + return ( +
+
+ {intl.formatMessage({ id: "From" })} +
+ + {productTypeCheque.localPrice.numberOfBonusCheques} + + + {CurrencyEnum.CC} + + {productTypeCheque.localPrice.additionalPricePerStay && ( + <> + {"+"} + + {productTypeCheque.localPrice.additionalPricePerStay} + + + {productTypeCheque.localPrice.currency} + + + )} +
+
+ {productTypeCheque.requestedPrice ? ( +
+ + {intl.formatMessage({ id: "Approx." })} + + + {productTypeCheque.requestedPrice.numberOfBonusCheques}{" "} + {CurrencyEnum.CC} + {productTypeCheque.requestedPrice.additionalPricePerStay + ? " + " + : ""} + {productTypeCheque.requestedPrice.additionalPricePerStay}{" "} + {productTypeCheque.requestedPrice.currency} + +
+ ) : null} +
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/hotelPriceCard.module.css b/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/hotelPriceCard.module.css index bbcfbc83c..2b129d6a0 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/hotelPriceCard.module.css +++ b/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/hotelPriceCard.module.css @@ -21,6 +21,11 @@ gap: var(--Spacing-x-half); } +.voucherChqRate { + justify-content: start; + align-items: baseline; +} + .perNight { font-weight: 400; font-size: var(--typography-Caption-Regular-fontSize); diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/index.tsx index 03d354733..f96b48874 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCard/HotelPriceCard/index.tsx @@ -8,32 +8,37 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./hotelPriceCard.module.css" import type { PriceCardProps } from "@/types/components/hotelReservation/selectHotel/priceCardProps" +import { RateTypeEnum } from "@/types/enums/rateType" export default function HotelPriceCard({ productTypePrices, isMemberPrice = false, }: PriceCardProps) { const intl = useIntl() + const isRegularOrPublicPromotionRate = + productTypePrices.rateType === RateTypeEnum.Regular || + productTypePrices.rateType === RateTypeEnum.PublicPromotion return (
- {isMemberPrice ? ( -
-
- - {intl.formatMessage({ id: "Member price" })} - -
-
- ) : ( -
-
- - {intl.formatMessage({ id: "Standard price" })} - -
-
- )} + {isRegularOrPublicPromotionRate && + (isMemberPrice ? ( +
+
+ + {intl.formatMessage({ id: "Member price" })} + +
+
+ ) : ( +
+
+ + {intl.formatMessage({ id: "Standard price" })} + +
+
+ ))}
)} {productTypePrices.localPrice.pricePerStay !== + productTypePrices.localPrice.pricePerNight && + // Handle undefined scenarios productTypePrices.localPrice.pricePerNight && ( - <> - -
-
- - {intl.formatMessage({ id: "Total" })} - -
-
- - {productTypePrices.localPrice.pricePerStay}{" "} - {productTypePrices.localPrice.currency} - -
-
- - )} + <> + +
+
+ + {intl.formatMessage({ id: "Total" })} + +
+
+ + {productTypePrices.localPrice.pricePerStay}{" "} + {productTypePrices.localPrice.currency} + +
+
+ + )}
) } diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/hotelVoucherCard.module.css b/apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/hotelVoucherCard.module.css new file mode 100644 index 000000000..09d8f1b1e --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/hotelVoucherCard.module.css @@ -0,0 +1,19 @@ +.voucherCard { + padding: var(--Spacing-x-one-and-half); + background-color: var(--Base-Surface-Secondary-light-Normal); + border-radius: var(--Corner-radius-Medium); + margin: 0; + width: 100%; +} + +.voucherRow, +.voucher { + display: flex; + gap: var(--Spacing-x-half); + justify-content: space-between; + align-items: baseline; +} + +.voucher { + justify-content: end; +} diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/index.tsx new file mode 100644 index 000000000..f8908a96c --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/HotelCard/HotelVoucherCard/index.tsx @@ -0,0 +1,32 @@ +import { useIntl } from "react-intl" + +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" + +import styles from "./hotelVoucherCard.module.css" + +import { CurrencyEnum } from "@/types/enums/currency" +import type { ProductTypeVoucher } from "@/types/trpc/routers/hotel/availability" + +export default function HotelVoucherCard({ + productTypeVoucher, +}: { + productTypeVoucher: ProductTypeVoucher +}) { + const intl = useIntl() + return ( +
+
+ {intl.formatMessage({ id: "From" })} +
+ + {productTypeVoucher.numberOfVouchers} + + + {CurrencyEnum.Voucher} + +
+
+
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx index 3c9271992..7be2a24df 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx @@ -13,6 +13,7 @@ import HotelLogo from "@/components/Icons/Logos" import ImageGallery from "@/components/ImageGallery" import Button from "@/components/TempDesignSystem/Button" import Divider from "@/components/TempDesignSystem/Divider" +import IconChip from "@/components/TempDesignSystem/IconChip" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" @@ -22,8 +23,10 @@ import { getSingleDecimal } from "@/utils/numberFormatting" import ReadMore from "../ReadMore" import TripAdvisorChip from "../TripAdvisorChip" +import HotelChequeCard from "./HotelChequeCard" import HotelPointsRow from "./HotelPointsRow" import HotelPriceCard from "./HotelPriceCard" +import HotelVoucherCard from "./HotelVoucherCard" import NoPriceAvailableCard from "./NoPriceAvailableCard" import { hotelCardVariants } from "./variants" @@ -31,6 +34,7 @@ import styles from "./hotelCard.module.css" import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps" +import { RateTypeEnum } from "@/types/enums/rateType" import type { Lang } from "@/constants/languages" function HotelCard({ @@ -64,7 +68,8 @@ function HotelCard({ const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}` const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || []) const fullPrice = - availability.productType?.public?.rateType?.toLowerCase() === "regular" + availability.productType?.public?.rateType === RateTypeEnum.Regular || + availability.productType?.member?.rateType === RateTypeEnum.Regular const price = availability.productType return ( @@ -155,8 +160,12 @@ function HotelCard({ <> {bookingCode && ( - - {bookingCode} + } + > + {bookingCode} + )} {(!isUserLoggedIn || @@ -171,6 +180,12 @@ function HotelCard({ isMemberPrice /> )} + {price?.voucher && ( + + )} + {price?.bonusCheque && ( + + )} {!!price?.redemptions?.length && (
diff --git a/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx index 339e1ae65..93af1cbed 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx @@ -43,6 +43,13 @@ export default function HotelCardListing({ const sortBy = searchParams.get("sort") ?? DEFAULT_SORT const bookingCode = searchParams.get("bookingCode") + // Special rates (corporate cheque, voucher and reward nights) will not have regular rate hotels availability + const isSpecialRate = hotelData.find( + (hotel) => + hotel.availability.productType?.bonusCheque || + hotel.availability.productType?.voucher || + hotel.availability.productType?.redemptions + ) const activeCodeFilter = useBookingCodeFilterStore( (state) => state.activeCodeFilter ) @@ -51,21 +58,26 @@ export default function HotelCardListing({ const sortedHotels = getSortedHotels({ hotels: hotelData, sortBy, - bookingCode, + bookingCode: isSpecialRate ? null : bookingCode, }) - const updatedHotelsList = bookingCode - ? sortedHotels.filter( - (hotel) => - !hotel.availability.productType || - activeCodeFilter === BookingCodeFilterEnum.All || - (activeCodeFilter === BookingCodeFilterEnum.Discounted && - hotel.availability.productType.public?.rateType !== - RateTypeEnum.Regular) || - (activeCodeFilter === BookingCodeFilterEnum.Regular && - hotel.availability.productType.public?.rateType === - RateTypeEnum.Regular) - ) - : sortedHotels + const updatedHotelsList = + bookingCode && !isSpecialRate + ? sortedHotels.filter( + (hotel) => + !hotel.availability.productType || + activeCodeFilter === BookingCodeFilterEnum.All || + (activeCodeFilter === BookingCodeFilterEnum.Discounted && + hotel.availability.productType.public?.rateType !== + RateTypeEnum.Regular && + hotel.availability.productType.member?.rateType !== + RateTypeEnum.Regular) || + (activeCodeFilter === BookingCodeFilterEnum.Regular && + (hotel.availability.productType.public?.rateType === + RateTypeEnum.Regular || + hotel.availability.productType.member?.rateType === + RateTypeEnum.Regular)) + ) + : sortedHotels if (!activeFilters.length) { return updatedHotelsList @@ -78,7 +90,14 @@ export default function HotelCardListing({ ) ) ) - }, [activeCodeFilter, activeFilters, bookingCode, hotelData, sortBy]) + }, [ + activeCodeFilter, + activeFilters, + bookingCode, + hotelData, + sortBy, + isSpecialRate, + ]) useEffect(() => { setResultCount(hotels.length) diff --git a/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts b/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts index 2ac0cdf1d..9167b6288 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts +++ b/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts @@ -1,4 +1,5 @@ import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter" +import { RateTypeEnum } from "@/types/enums/rateType" import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" function getPricePerNight(hotel: HotelResponse): number { @@ -47,23 +48,21 @@ export function getSortedHotels({ sortingStrategies[sortBy] ?? sortingStrategies[SortOrder.Distance] if (bookingCode) { - const bookingCodeHotels = hotels.filter( + const bookingCodeRateHotels = availableHotels.filter( (hotel) => - (hotel.availability.productType?.public?.rateType?.toLowerCase() !== - "regular" || - hotel.availability.productType?.member?.rateType?.toLowerCase() !== - "regular") && + (hotel.availability.productType?.public?.rateType !== RateTypeEnum.Regular && + hotel.availability.productType?.member?.rateType !== RateTypeEnum.Regular) && !!hotel.availability.productType ) - const regularHotels = hotels.filter( + const regularRateHotels = availableHotels.filter( (hotel) => - hotel.availability.productType?.public?.rateType?.toLowerCase() === - "regular" + hotel.availability.productType?.public?.rateType === RateTypeEnum.Regular || + hotel?.availability.productType?.member?.rateType === RateTypeEnum.Regular ) - return bookingCodeHotels + return bookingCodeRateHotels .sort(sortStrategy) - .concat(regularHotels.sort(sortStrategy)) + .concat(regularRateHotels.sort(sortStrategy)) .concat(unavailableHotels.sort(sortStrategy)) } diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/index.tsx b/apps/scandic-web/components/HotelReservation/SelectHotel/index.tsx index d9b974039..17ec6b1e0 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectHotel/index.tsx @@ -34,6 +34,7 @@ import styles from "./selectHotel.module.css" import type { SelectHotelProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" +import { RateTypeEnum } from "@/types/enums/rateType" export default async function SelectHotel({ params, @@ -71,7 +72,7 @@ export default async function SelectHotel({ isAlternativeFor, bookingCode, city, - !!redemption, + !!redemption ) const arrivalDate = new Date(selectHotelParams.fromDate) @@ -95,24 +96,24 @@ export default async function SelectHotel({ }, isAlternativeFor ? { - title: intl.formatMessage({ id: "Alternative hotels" }), - href: `${alternativeHotels(params.lang)}/?${convertedSearchParams}`, - uid: "alternative-hotels", - } + title: intl.formatMessage({ id: "Alternative hotels" }), + href: `${alternativeHotels(params.lang)}/?${convertedSearchParams}`, + uid: "alternative-hotels", + } : { - title: intl.formatMessage({ id: "Select hotel" }), - href: `${selectHotel(params.lang)}/?${convertedSearchParams}`, - uid: "select-hotel", - }, + title: intl.formatMessage({ id: "Select hotel" }), + href: `${selectHotel(params.lang)}/?${convertedSearchParams}`, + uid: "select-hotel", + }, isAlternativeFor ? { - title: isAlternativeFor.name, - uid: isAlternativeFor.id, - } + title: isAlternativeFor.name, + uid: isAlternativeFor.id, + } : { - title: city.name, - uid: city.id, - }, + title: city.name, + uid: city.id, + }, ] const isAllUnavailable = !hotels.length @@ -133,6 +134,33 @@ export default async function SelectHotel({ ) const suspenseKey = stringify(searchParams) + + let isFullPriceHotelAvailable + let isBookingCodeRateAvaliable + if (bookingCode) { + isFullPriceHotelAvailable = hotels?.find( + (hotel) => + hotel.availability.productType?.public?.rateType === + RateTypeEnum.Regular || + hotel.availability.productType?.member?.rateType === + RateTypeEnum.Regular + ) + isBookingCodeRateAvaliable = hotels?.find( + (hotel) => + hotel.availability.productType?.public?.rateType !== + RateTypeEnum.Regular || + hotel.availability.productType?.member?.rateType !== + RateTypeEnum.Regular + ) + } + + // Special rates (corporate cheque, voucher and reward nights) will not have regular rate hotels availability + const isSpecialRate = hotels?.some( + (hotel) => + hotel.availability.productType?.bonusCheque || + hotel.availability.productType?.voucher || + hotel.availability.productType?.redemptions + ) return ( <>
@@ -160,7 +188,11 @@ export default async function SelectHotel({
- {bookingCode ? : null} + {isBookingCodeRateAvaliable && + isFullPriceHotelAvailable && + !isSpecialRate ? ( + + ) : null}
{hotels.length ? ( rate.public?.rateType !== RateTypeEnum.Regular ) - const showDiscounted = isUserLoggedIn || isBookingCodeRate + const isVoucherRate = rateSummary.some((rate) => rate.voucher) + const isChequeRate = rateSummary.some((rate) => rate.bonusCheque) + const showDiscounted = + isUserLoggedIn || isBookingCodeRate || isVoucherRate || isChequeRate - // In case of reward night (redemption) only single room booking is supported by business rules - const totalPriceToShow: Price = - isRedemption && rateSummary[0].redemption - ? PointsPriceSchema.parse(rateSummary[0].redemption) - : calculateTotalPrice(rateSummary, isUserLoggedIn, petRoomPackage) + let totalPriceToShow: Price + if (isVoucherRate) { + totalPriceToShow = calculateVoucherPrice(rateSummary) + } else if (isChequeRate) { + totalPriceToShow = calculateChequePrice(rateSummary) + } else if (rateSummary[0].redemption) { + // In case of reward night (redemption) only single room booking is supported by business rules + totalPriceToShow = PointsPriceSchema.parse(rateSummary[0].redemption) + } else { + totalPriceToShow = calculateTotalPrice( + rateSummary, + isUserLoggedIn, + petRoomPackage + ) + } return (
@@ -271,7 +288,9 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) { value: formatPrice( intl, totalPriceToShow.requested.price, - totalPriceToShow.requested.currency + totalPriceToShow.requested.currency, + totalPriceToShow.requested.additionalPrice, + totalPriceToShow.requested.additionalPriceCurrency ), } )} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts index 90240671c..19fbd05e6 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts @@ -4,6 +4,7 @@ import { RoomPackageCodeEnum, } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" +import { CurrencyEnum } from "@/types/enums/currency" export const calculateTotalPrice = ( selectedRateSummary: Rate[], @@ -68,3 +69,83 @@ export const calculateTotalPrice = ( } ) } + +export const calculateVoucherPrice = (selectedRateSummary: Rate[]) => { + return selectedRateSummary.reduce( + (total, room) => { + const rate = room.voucher + if (!rate) { + return total + } + + return { + local: { + currency: total.local.currency, + price: total.local.price + rate.numberOfVouchers, + }, + requested: undefined, + } + }, + { + local: { + currency: CurrencyEnum.Voucher, + price: 0, + }, + requested: undefined, + } + ) +} + +export const calculateChequePrice = (selectedRateSummary: Rate[]) => { + return selectedRateSummary.reduce( + (total, room) => { + const rate = room.bonusCheque + if (!rate) { + return total + } + + const price = total.local.price + rate.localPrice.numberOfBonusCheques + + const additionalPrice = + rate.localPrice.numberOfBonusCheques && + (total.local.additionalPrice ?? 0) + + (rate.localPrice.additionalPricePerStay ?? 0) + const additionalPriceCurrency = (rate.localPrice.numberOfBonusCheques && + rate.localPrice.currency)! + + const requestedPrice = rate.requestedPrice?.numberOfBonusCheques + ? (total.requested?.price ?? 0) + + rate.requestedPrice?.numberOfBonusCheques + : total.requested?.price + + const requestedAdditionalPrice = + rate.requestedPrice?.additionalPricePerStay && + (total.requested?.additionalPrice ?? 0) + + (rate.requestedPrice?.additionalPricePerStay ?? 0) + + return { + local: { + currency: CurrencyEnum.CC, + price, + additionalPrice, + additionalPriceCurrency, + }, + requested: rate.requestedPrice + ? { + currency: CurrencyEnum.CC, + price: requestedPrice, + additionalPrice: requestedAdditionalPrice, + additionalPriceCurrency: rate.requestedPrice?.currency, + } + : undefined, + } + }, + { + local: { + currency: CurrencyEnum.CC, + price: 0, + }, + requested: undefined, + } + ) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/index.tsx index a8dcfb87b..8353035ac 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/index.tsx @@ -14,6 +14,7 @@ import { calculatePricesPerNight } from "./utils" import styles from "./priceList.module.css" import type { PriceListProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption" +import { RateTypeEnum } from "@/types/enums/rateType" export default function PriceList({ publicPrice = {}, @@ -34,9 +35,11 @@ export default function PriceList({ const petRoomRequestedPrice = petRoomPackage?.requestedPrice const showRequestedPrice = - publicRequestedPrice && - memberRequestedPrice && - publicRequestedPrice.currency !== publicLocalPrice.currency + (publicRequestedPrice && + memberRequestedPrice && + publicRequestedPrice.currency !== publicLocalPrice.currency) || + (publicPrice.rateType !== RateTypeEnum.Regular && publicRequestedPrice) + const searchParams = useSearchParams() const fromDate = searchParams.get("fromDate") const toDate = searchParams.get("toDate") @@ -76,16 +79,18 @@ export default function PriceList({ {isUserLoggedIn && isMainRoom && memberLocalPrice ? null : (
- {rateName ? null : ( + { - {intl.formatMessage({ id: "Standard price" })} + {rateName + ? rateName + : intl.formatMessage({ id: "Standard price" })} - )} + }
{publicLocalPrice ? ( @@ -163,19 +168,27 @@ export default function PriceList({
- {isUserLoggedIn - ? intl.formatMessage( - { id: "{memberPrice} {currency}" }, - { - memberPrice: totalMemberRequestedPricePerNight, - currency: publicRequestedPrice.currency, - } - ) + {totalMemberRequestedPricePerNight + ? isUserLoggedIn + ? intl.formatMessage( + { id: "{memberPrice} {currency}" }, + { + memberPrice: totalMemberRequestedPricePerNight, + currency: publicRequestedPrice.currency, + } + ) + : intl.formatMessage( + { id: "{publicPrice}/{memberPrice} {currency}" }, + { + publicPrice: totalPublicRequestedPricePerNight, + memberPrice: totalMemberRequestedPricePerNight, + currency: publicRequestedPrice.currency, + } + ) : intl.formatMessage( - { id: "{publicPrice}/{memberPrice} {currency}" }, + { id: "{price} {currency}" }, { - publicPrice: totalPublicRequestedPricePerNight, - memberPrice: totalMemberRequestedPricePerNight, + price: publicRequestedPrice.pricePerNight, currency: publicRequestedPrice.currency, } )} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/priceList.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/priceList.module.css index cd039ea67..4b4f991b3 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/priceList.module.css +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/PriceList/priceList.module.css @@ -20,6 +20,7 @@ .price { display: flex; gap: var(--Spacing-x-half); + align-items: baseline; } .priceStriked { diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/index.tsx index dc55f5b6c..0c8f121a4 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOption/index.tsx @@ -104,11 +104,6 @@ export default function FlexibilityOption({ value={rate.rateCode} />
- {rateName ? ( -
- {rateName} -
- ) : null}
+ rateTitle: string +}) { + const intl = useIntl() + + return ( +
+
+
+ + {rateTitle} + +
+
+
+ + {chequePrice.localPrice.numberOfBonusCheques} + + {CurrencyEnum.CC} + {chequePrice.localPrice.additionalPricePerStay ? ( + <> + {" + "} + + {chequePrice.localPrice.additionalPricePerStay} + + {chequePrice.localPrice.currency} + + ) : null} +
+
+
+ {chequePrice.requestedPrice?.additionalPricePerStay ? ( +
+
+ + {intl.formatMessage({ id: "Approx." })} + +
+
+
+ + {intl.formatMessage( + { id: "{price} {currency}" }, + { + price: chequePrice.requestedPrice.numberOfBonusCheques, + currency: CurrencyEnum.CC, + } + )} + {" + "} + {intl.formatMessage( + { id: "{price} {currency}" }, + { + price: chequePrice.requestedPrice.additionalPricePerStay, + currency: chequePrice.requestedPrice.currency, + } + )} + +
+
+
+ ) : null} +
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/flexibilityOptionCheque.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/flexibilityOptionCheque.module.css new file mode 100644 index 000000000..bef4fa4b0 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/flexibilityOptionCheque.module.css @@ -0,0 +1,73 @@ +.card { + border-radius: var(--Corner-radius-Large); + padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); + background-color: var(--Base-Surface-Secondary-light-Normal); + border: 1px solid var(--Base-Surface-Secondary-light-Normal); + position: relative; + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); + height: 100%; +} + +.card:hover { + cursor: pointer; + background-color: var(--Base-Surface-Primary-light-Hover-alt); +} + +.checkIcon { + width: 24px; + height: 24px; + border-radius: 100px; + background-color: var(--UI-Input-Controls-Fill-Selected); + border: 2px solid var(--Base-Border-Inverted); + justify-content: center; + align-items: center; + display: none; +} + +input[type="radio"].radio { + opacity: 0; + position: fixed; + width: 0; +} + +input[type="radio"]:checked + .card { + border: 1px solid var(--Primary-Dark-On-Surface-Divider); + background-color: var(--Base-Surface-Primary-light-Hover-alt); +} + +input[type="radio"]:checked + .card .checkIcon { + display: flex; + position: absolute; + top: -10px; + right: -10px; +} + +.header { + display: flex; + gap: var(--Spacing-x-half); + align-items: flex-start; +} + +.priceType { + display: flex; + gap: var(--Spacing-x-half); + flex-wrap: wrap; +} + +.terms { + padding-top: var(--Spacing-x3); +} + +.termsText:nth-child(n) { + display: flex; + align-items: center; + padding-bottom: var(--Spacing-x1); +} + +.termsIcon { + padding-right: var(--Spacing-x1); + flex-shrink: 0; + flex-basis: 32px; +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/index.tsx new file mode 100644 index 000000000..a16e0010c --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionCheque/index.tsx @@ -0,0 +1,118 @@ +"use client" + +import { useIntl } from "react-intl" + +import { CheckIcon, InfoCircleIcon } from "@/components/Icons" +import Modal from "@/components/Modal" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import { useRoomContext } from "@/contexts/SelectRate/Room" + +import ChequePrice from "./ChequePrice" + +import styles from "./flexibilityOptionCheque.module.css" + +import type { FlexibilityOptionChequeProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption" + +export default function FlexibilityOptionCheque({ + features, + paymentTerm, + priceInformation, + product, + roomType, + roomTypeCode, + title, + rateName, +}: FlexibilityOptionChequeProps) { + const intl = useIntl() + const { + actions: { selectRateCheque }, + roomNr, + selectedRate, + } = useRoomContext() + + if (!product.bonusCheque) { + return null + } + + function handleSelect() { + selectRateCheque({ + features, + product, + roomType, + roomTypeCode, + }) + } + + const voucherRate = product.bonusCheque + const isSelected = !!( + selectedRate?.product.bonusCheque && + selectedRate?.product.bonusCheque.rateCode === voucherRate?.rateCode && + selectedRate?.roomTypeCode === roomTypeCode + ) + + const rate = product.bonusCheque + const chequeRateName = + rateName ?? intl.formatMessage({ id: "Corporate Cheque" }) + + return ( + + ) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/index.tsx new file mode 100644 index 000000000..1ddeecd38 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/index.tsx @@ -0,0 +1,36 @@ +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" + +import styles from "./voucherPrice.module.css" + +import { CurrencyEnum } from "@/types/enums/currency" +import type { Product } from "@/types/trpc/routers/hotel/roomAvailability" + +export default function VoucherPrice({ + voucherPrice, + rateTitle, +}: { + voucherPrice: NonNullable + rateTitle: string +}) { + return ( +
+
+
+ + {rateTitle} + +
+
+
+ + {voucherPrice.numberOfVouchers} + + {CurrencyEnum.Voucher} +
+
+
+
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/voucherPrice.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/voucherPrice.module.css new file mode 100644 index 000000000..76baff9a3 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/VoucherPrice/voucherPrice.module.css @@ -0,0 +1,15 @@ +.priceList { + margin: 0; +} + +.priceRow { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + display: flex; + gap: var(--Spacing-x-half); + align-items: baseline; +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/flexibilityOptionVoucher.module.css b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/flexibilityOptionVoucher.module.css new file mode 100644 index 000000000..bef4fa4b0 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/flexibilityOptionVoucher.module.css @@ -0,0 +1,73 @@ +.card { + border-radius: var(--Corner-radius-Large); + padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); + background-color: var(--Base-Surface-Secondary-light-Normal); + border: 1px solid var(--Base-Surface-Secondary-light-Normal); + position: relative; + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); + height: 100%; +} + +.card:hover { + cursor: pointer; + background-color: var(--Base-Surface-Primary-light-Hover-alt); +} + +.checkIcon { + width: 24px; + height: 24px; + border-radius: 100px; + background-color: var(--UI-Input-Controls-Fill-Selected); + border: 2px solid var(--Base-Border-Inverted); + justify-content: center; + align-items: center; + display: none; +} + +input[type="radio"].radio { + opacity: 0; + position: fixed; + width: 0; +} + +input[type="radio"]:checked + .card { + border: 1px solid var(--Primary-Dark-On-Surface-Divider); + background-color: var(--Base-Surface-Primary-light-Hover-alt); +} + +input[type="radio"]:checked + .card .checkIcon { + display: flex; + position: absolute; + top: -10px; + right: -10px; +} + +.header { + display: flex; + gap: var(--Spacing-x-half); + align-items: flex-start; +} + +.priceType { + display: flex; + gap: var(--Spacing-x-half); + flex-wrap: wrap; +} + +.terms { + padding-top: var(--Spacing-x3); +} + +.termsText:nth-child(n) { + display: flex; + align-items: center; + padding-bottom: var(--Spacing-x1); +} + +.termsIcon { + padding-right: var(--Spacing-x1); + flex-shrink: 0; + flex-basis: 32px; +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/index.tsx new file mode 100644 index 000000000..a07fe717b --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/FlexibilityOptionVoucher/index.tsx @@ -0,0 +1,117 @@ +"use client" + +import { useIntl } from "react-intl" + +import { CheckIcon, InfoCircleIcon } from "@/components/Icons" +import Modal from "@/components/Modal" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import { useRoomContext } from "@/contexts/SelectRate/Room" + +import VoucherPrice from "./VoucherPrice" + +import styles from "./flexibilityOptionVoucher.module.css" + +import type { FlexibilityOptionVoucherProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption" + +export default function FlexibilityOptionVoucher({ + features, + paymentTerm, + priceInformation, + product, + roomType, + roomTypeCode, + title, + rateName, +}: FlexibilityOptionVoucherProps) { + const intl = useIntl() + const { + actions: { selectRateVoucher }, + roomNr, + selectedRate, + } = useRoomContext() + + if (!product.voucher) { + return null + } + + function handleSelect() { + selectRateVoucher({ + features, + product, + roomType, + roomTypeCode, + }) + } + + const voucherRate = product.voucher + const isSelected = !!( + selectedRate?.product.voucher && + selectedRate?.product.voucher.rateCode === voucherRate?.rateCode && + selectedRate?.roomTypeCode === roomTypeCode + ) + + const rate = product.voucher + const voucherRateName = rateName ?? intl.formatMessage({ id: "Voucher" }) + + return ( + + ) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/index.tsx index f495927db..97aed4e26 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/RoomCard/index.tsx @@ -20,6 +20,8 @@ import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" import { cardVariants } from "./cardVariants" import FlexibilityOption from "./FlexibilityOption" import FlexibilityOptionPoints from "./FlexibilityOptionPoints" +import FlexibilityOptionCheque from "./FlexibilityOptionCheque" +import FlexibilityOptionVoucher from "./FlexibilityOptionVoucher" import RoomSize from "./RoomSize" import styles from "./roomCard.module.css" @@ -171,6 +173,10 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) { rateCode = product.member.rateCode } else if (product.public?.rateCode) { rateCode = product.public.rateCode + } else if (product.voucher) { + rateCode = product.voucher.rateCode + } else if (product.bonusCheque) { + rateCode = product.bonusCheque.rateCode } else if (product.redemptions?.length) { // In case of redemption there will be same rate terms and title // irrespective of ratecodes @@ -292,7 +298,9 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) { const isAvailable = product.public || (product.member && isUserLoggedIn && isMainRoom) || - product.redemptions?.length + product.redemptions?.length || + product.bonusCheque || + product.voucher const rateDefinition = getRateDefinition( product, roomAvailability.rateDefinitions @@ -307,14 +315,35 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) { roomTypeCode: roomConfiguration.roomTypeCode, title: rateTitle, rateName: - isBookingCodeRate || isRedemption + isBookingCodeRate || isRedemption || + product.voucher || + product.bonusCheque ? rateDefinition?.title : undefined, } - return isRedemption ? ( - - ) : ( - + return (<> + {isRedemption && + } + {product.voucher ? ( + + ) : null} + {product.bonusCheque ? ( + + ) : null} + {product.public || product.member ? ( + + ) : null} + ) })} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/index.tsx index f194a5bdc..1f4f08700 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomSelectionPanel/index.tsx @@ -38,52 +38,60 @@ export default function RoomSelectionPanel() { (state) => state.activeCodeFilter ) - // Regular Rates (Save, Change and Flex) always should send both public and member rates - // so we can check public rates for availability - const isRegularRatesAvailableWithCode = - bookingCode && - rooms.some( + const isVoucherOrCorpChequeRate = rooms.find((room) => + room.products.some((product) => product.voucher || product.bonusCheque) + ) + + let isRegularRatesAvailableWithCode = false, + isBookingCodeRatesAvailable = false + let visibleRooms = rooms + if (bookingCode && !isVoucherOrCorpChequeRate) { + // Regular Rates (Save, Change and Flex) always should send both public and member rates + // so we can check public rates for availability + isRegularRatesAvailableWithCode = rooms.some( (room) => room.status === AvailabilityEnum.Available && room.products.some( - (product) => product.public?.rateType === RateTypeEnum.Regular + (product) => + product.public?.rateType === RateTypeEnum.Regular || + product.member?.rateType === RateTypeEnum.Regular ) ) - // Booking codes rate comes with various rate types but Regular is reserved - // for non-booking code rates (Save, Change & Flex) - // With Booking code rates we will always obtain public rate and maybe a member rate - // so we check for public rate and ignore member rate - const isBookingCodeRatesAvailable = - bookingCode && - rooms.some( + // Booking codes rate comes with various rate types but Regular is reserved + // for non-booking code rates (Save, Change & Flex) + // With Booking code rates we will always obtain public rate and maybe a member rate + // so we check for public rate and ignore member rate + isBookingCodeRatesAvailable = rooms.some( (room) => room.status === AvailabilityEnum.Available && room.products.some( - (product) => product.public?.rateType !== RateTypeEnum.Regular + (product) => + product.public?.rateType !== RateTypeEnum.Regular || + product.member?.rateType !== RateTypeEnum.Regular ) ) - // Show all rooms if either booking code rates or regular rates are not available - // or filter selection is All rooms - const showAllRooms = - !isBookingCodeRatesAvailable || - !isRegularRatesAvailableWithCode || - activeCodeFilter === BookingCodeFilterEnum.All - const bookingCodeDiscountedRooms = rooms.filter( - (room) => - room.status === AvailabilityEnum.Available && - room.products.every( - (product) => product.public?.rateType !== RateTypeEnum.Regular + if (activeCodeFilter === BookingCodeFilterEnum.Discounted) { + visibleRooms = rooms.filter( + (room) => + room.status === AvailabilityEnum.Available && + room.products.every( + (product) => product.public?.rateType !== RateTypeEnum.Regular + ) ) - ) - const regularRateRooms = rooms.filter( - (room) => - room.status === AvailabilityEnum.Available && - room.products.every( - (product) => product.public?.rateType === RateTypeEnum.Regular + } else if (activeCodeFilter === BookingCodeFilterEnum.Regular) { + visibleRooms = rooms.filter( + (room) => + room.status === AvailabilityEnum.Available && + room.products.every( + (product) => + product.public?.rateType === RateTypeEnum.Regular || + product.member?.rateType === RateTypeEnum.Regular + ) ) - ) + } + } // Show booking code filter when both of the booking code rates or regular rates are available const showBookingCodeFilter = isRegularRatesAvailableWithCode && isBookingCodeRatesAvailable @@ -114,7 +122,10 @@ export default function RoomSelectionPanel() { return ( <> - {noAvailableRooms || (bookingCode && !isBookingCodeRatesAvailable) ? ( + {noAvailableRooms || + (bookingCode && + !isBookingCodeRatesAvailable && + !isVoucherOrCorpChequeRate) ? (
{showBookingCodeFilter ? : null}
    - {/* Show either Booking code filtered rooms or all the rooms */} - {showAllRooms - ? rooms.map((roomConfiguration) => ( - - )) - : activeCodeFilter === BookingCodeFilterEnum.Discounted - ? bookingCodeDiscountedRooms.map((roomConfiguration) => ( - - )) - : regularRateRooms.map((roomConfiguration) => ( - - ))} + {visibleRooms.map((roomConfiguration) => ( + + ))}
) diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx index 6fc606a4d..b692547db 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx @@ -25,6 +25,7 @@ export default function Input({ readOnly = false, registerOptions = {}, type = "text", + hideError, }: InputProps) { const intl = useIntl() const { control } = useFormContext() @@ -73,7 +74,7 @@ export default function Input({ ) : null} - {fieldState.error ? ( + {fieldState.error && !hideError ? ( {intl.formatMessage({ id: fieldState.error.message })} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Input/input.ts b/apps/scandic-web/components/TempDesignSystem/Form/Input/input.ts index e480363b9..a8e8cb60f 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Input/input.ts +++ b/apps/scandic-web/components/TempDesignSystem/Form/Input/input.ts @@ -6,4 +6,5 @@ export interface InputProps label: string name: string registerOptions?: RegisterOptions + hideError?: boolean } diff --git a/apps/scandic-web/i18n/dictionaries/da.json b/apps/scandic-web/i18n/dictionaries/da.json index 7d892821b..f070b1e1c 100644 --- a/apps/scandic-web/i18n/dictionaries/da.json +++ b/apps/scandic-web/i18n/dictionaries/da.json @@ -205,6 +205,7 @@ "Continue with new price": "Fortsæt med ny pris", "Copied to clipboard": "Copied to clipboard", "Copy promotion code": "Copy promotion code", + "Corporate Cheque": "Corporate Cheque", "Could not find requested resource": "Kunne ikke finde den anmodede ressource", "Country": "Land", "Country code": "Landekode", diff --git a/apps/scandic-web/i18n/dictionaries/de.json b/apps/scandic-web/i18n/dictionaries/de.json index 2007a6035..ab2f928ca 100644 --- a/apps/scandic-web/i18n/dictionaries/de.json +++ b/apps/scandic-web/i18n/dictionaries/de.json @@ -206,6 +206,7 @@ "Continue with new price": "Mit neuer Preis fortsetzen", "Copied to clipboard": "Copied to clipboard", "Copy promotion code": "Copy promotion code", + "Corporate Cheque": "Corporate Cheque", "Could not find requested resource": "Die angeforderte Ressource konnte nicht gefunden werden.", "Country": "Land", "Country code": "Landesvorwahl", diff --git a/apps/scandic-web/i18n/dictionaries/en.json b/apps/scandic-web/i18n/dictionaries/en.json index 6807229b5..42a9d82ae 100644 --- a/apps/scandic-web/i18n/dictionaries/en.json +++ b/apps/scandic-web/i18n/dictionaries/en.json @@ -204,6 +204,7 @@ "Continue with new price": "Continue with new price", "Copied to clipboard": "Copied to clipboard", "Copy promotion code": "Copy promotion code", + "Corporate Cheque": "Corporate Cheque", "Could not find requested resource": "Could not find requested resource", "Country": "Country", "Country code": "Country code", diff --git a/apps/scandic-web/i18n/dictionaries/fi.json b/apps/scandic-web/i18n/dictionaries/fi.json index 62800df75..857306714 100644 --- a/apps/scandic-web/i18n/dictionaries/fi.json +++ b/apps/scandic-web/i18n/dictionaries/fi.json @@ -205,6 +205,7 @@ "Continue with new price": "Jatka uudella hinnalla", "Copied to clipboard": "Copied to clipboard", "Copy promotion code": "Copy promotion code", + "Corporate Cheque": "Corporate Cheque", "Could not find requested resource": "Pyydettyä resurssia ei löytynyt", "Country": "Maa", "Country code": "Maatunnus", diff --git a/apps/scandic-web/i18n/dictionaries/no.json b/apps/scandic-web/i18n/dictionaries/no.json index fc4faa209..74326dc5f 100644 --- a/apps/scandic-web/i18n/dictionaries/no.json +++ b/apps/scandic-web/i18n/dictionaries/no.json @@ -204,6 +204,7 @@ "Continue with new price": "Fortsett med ny pris", "Copied to clipboard": "Copied to clipboard", "Copy promotion code": "Copy promotion code", + "Corporate Cheque": "Corporate Cheque", "Could not find requested resource": "Kunne ikke finne den forespurte ressursen", "Country": "Land", "Country code": "Landskode", diff --git a/apps/scandic-web/i18n/dictionaries/sv.json b/apps/scandic-web/i18n/dictionaries/sv.json index 8444c5fd5..0684ea1fb 100644 --- a/apps/scandic-web/i18n/dictionaries/sv.json +++ b/apps/scandic-web/i18n/dictionaries/sv.json @@ -204,6 +204,7 @@ "Continue with new price": "Fortsätt med nytt pris", "Copied to clipboard": "Copied to clipboard", "Copy promotion code": "Copy promotion code", + "Corporate Cheque": "Corporate Cheque", "Could not find requested resource": "Det gick inte att hitta den begärda resursen", "Country": "Land", "Country code": "Landskod", diff --git a/apps/scandic-web/providers/EnterDetailsProvider.tsx b/apps/scandic-web/providers/EnterDetailsProvider.tsx index 419e2024c..4b444ed14 100644 --- a/apps/scandic-web/providers/EnterDetailsProvider.tsx +++ b/apps/scandic-web/providers/EnterDetailsProvider.tsx @@ -17,6 +17,7 @@ import { guestDetailsSchema } from "@/components/HotelReservation/EnterDetails/D import { DetailsContext } from "@/contexts/Details" import type { DetailsStore } from "@/types/contexts/enter-details" +import { CurrencyEnum } from "@/types/enums/currency" import { StepEnum } from "@/types/enums/step" import type { DetailsProviderProps } from "@/types/providers/enter-details" import type { InitialState, RoomState } from "@/types/stores/enter-details" diff --git a/apps/scandic-web/providers/SelectRate/RoomProvider.tsx b/apps/scandic-web/providers/SelectRate/RoomProvider.tsx index 6aee36644..9e58059ac 100644 --- a/apps/scandic-web/providers/SelectRate/RoomProvider.tsx +++ b/apps/scandic-web/providers/SelectRate/RoomProvider.tsx @@ -22,6 +22,12 @@ export default function RoomProvider({ const selectRateRedemption = useRatesStore((state) => state.actions.selectRateRedemption(idx) ) + const selectRateCheque = useRatesStore((state) => + state.actions.selectRateCheque(idx) + ) + const selectRateVoucher = useRatesStore((state) => + state.actions.selectRateVoucher(idx) + ) const roomNr = idx + 1 return ( rate.rateCode === voucherRate.rateCode + ) + if (voucherRateDefinition) { + const rate = getRate(voucherRateDefinition) + if (rate) { + product.rate = rate + if (rate === "flex") { + product.isFlex = true + } + } + } + } + + const chequeRate = product.bonusCheque + if (chequeRate?.rateCode) { + const chequeRateDefinition = rateDefinitions.find( + (rate) => rate.rateCode === chequeRate.rateCode + ) + if (chequeRateDefinition) { + const rate = getRate(chequeRateDefinition) + if (rate) { + product.rate = rate + if (rate === "flex") { + product.isFlex = true + } + } + } + } + return product }) diff --git a/apps/scandic-web/server/routers/hotels/query.ts b/apps/scandic-web/server/routers/hotels/query.ts index 9f04e0df6..1ef8800f9 100644 --- a/apps/scandic-web/server/routers/hotels/query.ts +++ b/apps/scandic-web/server/routers/hotels/query.ts @@ -717,7 +717,9 @@ export const getRoomAvailability = async ( (rate) => rate.public?.rateCode === rateCode || rate.member?.rateCode === rateCode || - rate.redemptions?.find((r) => r?.rateCode === rateCode) + rate.redemptions?.find((r) => r?.rateCode === rateCode) || + rate.bonusCheque?.rateCode === rateCode || + rate.voucher?.rateCode === rateCode ) if (!rateTypes) { @@ -791,6 +793,7 @@ export const getRoomAvailability = async ( breakfastIncluded: !!rateDefinition?.breakfastIncluded, cancellationRule: rateDefinition?.cancellationRule, cancellationText: rateDefinition?.cancellationText ?? "", + chequeRate: rates?.bonusCheque, isFlexRate: rateDefinition?.cancellationRule === CancellationRuleEnum.CancellableBefore6PM, @@ -809,6 +812,7 @@ export const getRoomAvailability = async ( : undefined, rateType: rateDefinition?.rateType ?? "", selectedRoom, + voucherRate: rates?.voucher, } } @@ -889,15 +893,23 @@ export const hotelQueryRouter = router({ return null } + // Do not search for regular rates if voucher or corporate cheque codes + const isVoucherOrChqRate = + bookingCodeAvailabilityResponse?.availability.some( + (hotel) => + hotel.productType?.bonusCheque || hotel.productType?.voucher + ) + // Get regular availability of hotels which don't have availability with booking code. - const unavailableHotelIds = - bookingCodeAvailabilityResponse?.availability - .filter((hotel) => { - return hotel.status === "NotAvailable" - }) - .flatMap((hotel) => { - return hotel.hotelId - }) + const unavailableHotelIds = !isVoucherOrChqRate + ? bookingCodeAvailabilityResponse?.availability + .filter((hotel) => { + return hotel.status === "NotAvailable" + }) + .flatMap((hotel) => { + return hotel.hotelId + }) + : null // All hotels have availability with booking code no need to fetch regular prices. // return response as is without any filtering as below. diff --git a/apps/scandic-web/server/routers/hotels/schemas/availability/productType.ts b/apps/scandic-web/server/routers/hotels/schemas/availability/productType.ts index 65c7f4a53..727a58137 100644 --- a/apps/scandic-web/server/routers/hotels/schemas/availability/productType.ts +++ b/apps/scandic-web/server/routers/hotels/schemas/availability/productType.ts @@ -1,14 +1,18 @@ import { z } from "zod" import { + productTypeChequeSchema, productTypePointsSchema, productTypePriceSchema, + productTypeVoucherSchema, } from "../productTypePrice" export const productTypeSchema = z .object({ + bonusCheque: productTypeChequeSchema.optional(), public: productTypePriceSchema.optional(), member: productTypePriceSchema.optional(), redemptions: z.array(productTypePointsSchema).optional(), + voucher: productTypeVoucherSchema.optional(), }) .optional() diff --git a/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts b/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts index af68e610b..554c5e71d 100644 --- a/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts +++ b/apps/scandic-web/server/routers/hotels/schemas/productTypePrice.ts @@ -27,6 +27,17 @@ export const pointsSchema = z additionalPrice: data.additionalPricePerStay, })) +export const voucherSchema = z.object({ + currency: z.nativeEnum(CurrencyEnum), + pricePerStay: z.number(), +}) + +export const chequeSchema = z.object({ + additionalPricePerStay: z.number().optional(), + currency: z.nativeEnum(CurrencyEnum).optional(), + numberOfBonusCheques: z.coerce.number(), +}) + const partialPriceSchema = z.object({ rateCode: z.string(), rateType: z.string().optional(), @@ -39,4 +50,14 @@ export const productTypePriceSchema = partialPriceSchema.extend({ export const productTypePointsSchema = partialPriceSchema.extend({ localPrice: pointsSchema, + requestedPrice: pointsSchema.optional(), +}) + +export const productTypeVoucherSchema = partialPriceSchema.extend({ + numberOfVouchers: z.coerce.number(), +}) + +export const productTypeChequeSchema = partialPriceSchema.extend({ + localPrice: chequeSchema, + requestedPrice: chequeSchema.optional(), }) diff --git a/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/configuration.ts b/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/configuration.ts index 888dd467a..d11064ae8 100644 --- a/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/configuration.ts +++ b/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/configuration.ts @@ -33,7 +33,14 @@ export const roomConfigurationSchema = z // No need of rate check in reward night scenario return { ...data } } else { - /** + const isVoucher = data.products.some((product) => product.voucher) + const isCorpChq = data.products.some((product) => product.bonusCheque) + if (isVoucher || isCorpChq) { + return { + ...data, + } + } + /** * Just guaranteeing that if all products all miss * both public and member rateCode that status is * set to `NotAvailable` diff --git a/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/product.ts b/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/product.ts index 02449a601..7de55f9ae 100644 --- a/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/product.ts +++ b/apps/scandic-web/server/routers/hotels/schemas/roomAvailability/product.ts @@ -2,7 +2,9 @@ import { z } from "zod" import { productTypePointsSchema, + productTypeChequeSchema, productTypePriceSchema, + productTypeVoucherSchema, } from "../productTypePrice" export const productSchema = z @@ -10,9 +12,11 @@ export const productSchema = z // Is product flex rate isFlex: z.boolean().default(false), productType: z.object({ + bonusCheque: productTypeChequeSchema.optional(), member: productTypePriceSchema.optional(), public: productTypePriceSchema.optional(), redemptions: z.array(productTypePointsSchema).optional(), + voucher: productTypeVoucherSchema.optional(), }), // Used to set the rate that we use to chose titles etc. rate: z.enum(["change", "flex", "save"]).default("save"), diff --git a/apps/scandic-web/stores/enter-details/helpers.ts b/apps/scandic-web/stores/enter-details/helpers.ts index 565c13502..9b2133286 100644 --- a/apps/scandic-web/stores/enter-details/helpers.ts +++ b/apps/scandic-web/stores/enter-details/helpers.ts @@ -79,6 +79,23 @@ export function subtract(...nums: (number | string | undefined)[]) { }, 0) } +export function getCurrency(roomRate: RoomRate) { + const requestedCurrency = ( + (roomRate.publicRate?.requestedPrice?.currency ?? + (roomRate.chequeRate && CurrencyEnum.CC)) + ) + const localCurrency = ( + (roomRate.publicRate?.localPrice.currency ?? + (roomRate.voucherRate && CurrencyEnum.Voucher) ?? + (roomRate.chequeRate && CurrencyEnum.CC)) + ) + + return { + requestedCurrency, + localCurrency, + } +} + export function getRoomPrice(roomRate: RoomRate, isMember: boolean) { if (isMember && roomRate.memberRate) { return { @@ -132,6 +149,62 @@ export function getRoomPrice(roomRate: RoomRate, isMember: boolean) { } } + if (roomRate.chequeRate) { + return { + perNight: { + requested: roomRate.chequeRate.requestedPrice && { + currency: CurrencyEnum.CC, + price: roomRate.chequeRate.requestedPrice.numberOfBonusCheques, + additionalPrice: + roomRate.chequeRate.requestedPrice.additionalPricePerStay, + additionalPriceCurrency: roomRate.chequeRate.requestedPrice.currency, + }, + local: { + currency: CurrencyEnum.CC, + price: roomRate.chequeRate.localPrice.numberOfBonusCheques, + additionalPrice: + roomRate.chequeRate.localPrice.additionalPricePerStay, + additionalPriceCurrency: roomRate.chequeRate.localPrice.currency, + }, + }, + perStay: { + requested: roomRate.chequeRate.requestedPrice && { + currency: CurrencyEnum.CC, + price: roomRate.chequeRate.requestedPrice.numberOfBonusCheques, + additionalPrice: + roomRate.chequeRate.requestedPrice.additionalPricePerStay, + additionalPriceCurrency: roomRate.chequeRate.requestedPrice.currency, + }, + local: { + currency: CurrencyEnum.CC, + price: roomRate.chequeRate.localPrice.numberOfBonusCheques, + additionalPrice: + roomRate.chequeRate.localPrice.additionalPricePerStay, + additionalPriceCurrency: roomRate.chequeRate.localPrice.currency, + }, + }, + } + } + + if (roomRate.voucherRate) { + return { + perNight: { + requested: undefined, + local: { + currency: CurrencyEnum.Voucher, + price: roomRate.voucherRate.numberOfVouchers, + }, + }, + perStay: { + requested: undefined, + local: { + currency: CurrencyEnum.Voucher, + price: roomRate.voucherRate.numberOfVouchers, + }, + }, + } + } + if (roomRate.redemptionRate) { return { // ToDo Handle perNight as undefined @@ -210,6 +283,87 @@ export function getTotalPrice(roomRates: RoomRate[], isMember: boolean) { ) } +export const calculateVoucherPrice = (roomRates: RoomRate[]) => { + return roomRates.reduce( + (total, room) => { + const rate = room.voucherRate + if (!rate) { + return total + } + + return { + local: { + currency: total.local.currency, + price: total.local.price + rate.numberOfVouchers, + }, + requested: undefined, + } + }, + { + local: { + currency: CurrencyEnum.Voucher, + price: 0, + }, + requested: undefined, + } + ) +} + +export const calculateChequePrice = (roomRates: RoomRate[]) => { + return roomRates.reduce( + (total, room) => { + const rate = room.chequeRate + if (!rate) { + return total + } + + const price = total.local.price + rate.localPrice.numberOfBonusCheques + + const additionalPrice = + rate.localPrice.numberOfBonusCheques && + (total.local.additionalPrice ?? 0) + + (rate.localPrice.additionalPricePerStay ?? 0) + const additionalPriceCurrency = (rate.localPrice.numberOfBonusCheques && + rate.localPrice.currency)! + + const requestedPrice = rate.requestedPrice?.numberOfBonusCheques + ? (total.requested?.price ?? 0) + + rate.requestedPrice?.numberOfBonusCheques + : total.requested?.price + + const requestedAdditionalPrice = + rate.requestedPrice?.additionalPricePerStay && + (total.requested?.additionalPrice ?? + 0 + rate.requestedPrice?.additionalPricePerStay ?? + 0) + + return { + local: { + currency: CurrencyEnum.CC, + price, + additionalPrice, + additionalPriceCurrency, + }, + requested: rate.requestedPrice + ? { + currency: CurrencyEnum.CC, + price: requestedPrice, + additionalPrice: requestedAdditionalPrice, + additionalPriceCurrency: rate.requestedPrice?.currency, + } + : undefined, + } + }, + { + local: { + currency: CurrencyEnum.CC, + price: 0, + }, + requested: undefined, + } + ) +} + export function calcTotalPrice( rooms: RoomState[], currency: Price["local"]["currency"], @@ -277,6 +431,12 @@ export function calcTotalPrice( breakfastLocalPrice * room.adults * nights, roomFeaturesTotal?.requestedPrice ?? 0 ), + additionalPrice: add( + acc.local.additionalPrice, + roomPrice.perStay.local.additionalPrice, + breakfastLocalPrice * room.adults * nights, + roomFeaturesTotal?.local ?? 0 + ), }, } diff --git a/apps/scandic-web/stores/enter-details/index.ts b/apps/scandic-web/stores/enter-details/index.ts index abc11e77a..6c70118a0 100644 --- a/apps/scandic-web/stores/enter-details/index.ts +++ b/apps/scandic-web/stores/enter-details/index.ts @@ -11,6 +11,8 @@ import { DetailsContext } from "@/contexts/Details" import { add, calcTotalPrice, + calculateChequePrice, + calculateVoucherPrice, checkRoomProgress, extractGuestFromUser, findNextInvalidStep, @@ -56,11 +58,22 @@ export function createDetailsStore( const isRedemption = new URLSearchParams(searchParams).get("searchtype") === REDEMPTION + const isVoucher = initialState.rooms.some((room) => room.roomRate.voucherRate) + const isCorpChq = initialState.rooms.some((room) => room.roomRate.chequeRate) + let initialTotalPrice: Price if (isRedemption && initialState.rooms[0].roomRate.redemptionRate) { initialTotalPrice = PointsPriceSchema.parse( initialState.rooms[0].roomRate.redemptionRate ) + } else if (isVoucher) { + initialTotalPrice = calculateVoucherPrice( + initialState.rooms.map((r) => r.roomRate) + ) + } else if (isCorpChq) { + initialTotalPrice = calculateChequePrice( + initialState.rooms.map((r) => r.roomRate) + ) } else { initialTotalPrice = getTotalPrice( initialState.rooms.map((r) => r.roomRate), @@ -270,7 +283,7 @@ export function createDetailsStore( const currentTotalPriceRequested = state.totalPrice.requested let stateTotalRequestedPrice = 0 if (currentTotalPriceRequested) { - stateTotalRequestedPrice = currentTotalPriceRequested.price + stateTotalRequestedPrice = currentTotalPriceRequested.price ?? 0 } const stateTotalLocalPrice = state.totalPrice.local.price @@ -305,7 +318,7 @@ export function createDetailsStore( }, local: { currency: breakfast.localPrice.currency, - price: stateTotalLocalPrice + breakfastTotalPrice, + price: stateTotalLocalPrice ?? 0 + breakfastTotalPrice, regularPrice: stateTotalLocalRegularPrice ? stateTotalLocalRegularPrice + breakfastTotalPrice : undefined, diff --git a/apps/scandic-web/stores/select-rate/index.ts b/apps/scandic-web/stores/select-rate/index.ts index a32fa3398..a9234bfe9 100644 --- a/apps/scandic-web/stores/select-rate/index.ts +++ b/apps/scandic-web/stores/select-rate/index.ts @@ -28,7 +28,9 @@ function findSelectedRate( room.products.find( (product) => product.public?.rateCode === rateCode || - product.member?.rateCode === rateCode + product.member?.rateCode === rateCode || + product.bonusCheque?.rateCode === rateCode || + product.voucher?.rateCode === rateCode ) ) } @@ -94,7 +96,9 @@ export function createRatesStore({ product.member?.rateCode === room.rateCode || product.redemptions?.find( (redemption) => redemption?.rateCode === room.rateCode - ) + ) || + product.bonusCheque?.rateCode === room.rateCode || + product.voucher?.rateCode === room.rateCode ) ) const redemptionProduct = selectedRoom?.products[0].redemptions?.find( @@ -103,18 +107,26 @@ export function createRatesStore({ const product = selectedRoom?.products.find( (p) => p.public?.rateCode === room.rateCode || - p.member?.rateCode === room.rateCode + p.member?.rateCode === room.rateCode || + p.bonusCheque?.rateCode === room.rateCode || + p.voucher?.rateCode === room.rateCode ) if (selectedRoom && product) { rateSummary[idx] = { features: selectedRoom.features, - member: product.member, - public: product.public, redemption: undefined, rate: product.rate, roomType: selectedRoom.roomType, roomTypeCode: selectedRoom.roomTypeCode, } + if (product.member || product.public) { + rateSummary[idx].member = product.member + rateSummary[idx].public = product.public + } else if (product.bonusCheque) { + rateSummary[idx].bonusCheque = product.bonusCheque + } else if (product.voucher) { + rateSummary[idx].voucher = product.voucher + } } else if (selectedRoom && redemptionProduct) { rateSummary[idx] = { features: selectedRoom.features, @@ -300,6 +312,90 @@ export function createRatesStore({ selectedRate.roomTypeCode ) + state.searchParams = new ReadonlyURLSearchParams(searchParams) + window.history.pushState( + {}, + "", + `${state.pathname}?${searchParams}` + ) + }) + ) + } + }, + selectRateVoucher() { + return function (selectedRate) { + return set( + produce((state: RatesState) => { + const voucherRate = selectedRate.product.voucher + if (!voucherRate) { + return + } + + state.rooms[0].selectedRate = selectedRate + state.rateSummary[0] = { + features: selectedRate.features, + voucher: selectedRate.product.voucher, + bonusCheque: undefined, + package: state.rooms[0].selectedPackage, + rate: selectedRate.product.rate, + roomType: selectedRate.roomType, + roomTypeCode: selectedRate.roomTypeCode, + } + + const searchParams = new URLSearchParams(state.searchParams) + searchParams.set(`room[0].ratecode`, voucherRate.rateCode) + searchParams.set(`room[0].roomtype`, selectedRate.roomTypeCode) + + if (state.rateSummary.length === state.booking.rooms.length) { + state.activeRoom = -1 + } else { + state.activeRoom = 1 + } + + state.searchParams = new ReadonlyURLSearchParams(searchParams) + window.history.pushState( + {}, + "", + `${state.pathname}?${searchParams}` + ) + }) + ) + } + }, + selectRateCheque(idx) { + return function (selectedRate) { + return set( + produce((state: RatesState) => { + const chequeRate = selectedRate.product.bonusCheque + if (!chequeRate) { + return + } + + state.rooms[idx].selectedRate = selectedRate + state.rateSummary[idx] = { + features: selectedRate.features, + package: state.rooms[idx].selectedPackage, + rate: selectedRate.product.rate, + voucher: undefined, + bonusCheque: chequeRate, + roomType: selectedRate.roomType, + roomTypeCode: selectedRate.roomTypeCode, + } + + const searchParams = new URLSearchParams(state.searchParams) + searchParams.set(`room[${idx}].ratecode`, chequeRate.rateCode) + + searchParams.set( + `room[${idx}].roomtype`, + selectedRate.roomTypeCode + ) + + if (state.rateSummary.length === state.booking.rooms.length) { + state.activeRoom = -1 + } else { + state.activeRoom = idx + 1 + } + state.searchParams = new ReadonlyURLSearchParams(searchParams) window.history.pushState( {}, @@ -332,7 +428,9 @@ export function createRatesStore({ const product = selectedRate?.products.find( (prd) => prd.public?.rateCode === room.rateCode || - prd.member?.rateCode === room.rateCode + prd.member?.rateCode === room.rateCode || + prd.bonusCheque?.rateCode === room.rateCode || + prd.voucher?.rateCode === room.rateCode ) const selectedPackage = room.packages?.[0] diff --git a/apps/scandic-web/types/components/hotelReservation/enterDetails/details.ts b/apps/scandic-web/types/components/hotelReservation/enterDetails/details.ts index e5d54529f..223b1bcf2 100644 --- a/apps/scandic-web/types/components/hotelReservation/enterDetails/details.ts +++ b/apps/scandic-web/types/components/hotelReservation/enterDetails/details.ts @@ -29,7 +29,9 @@ export type JoinScandicFriendsCardProps = { } export type RoomRate = { - memberRate?: Product["member"] - publicRate?: Product["public"] + memberRate?: NonNullable + publicRate?: NonNullable + voucherRate?: NonNullable + chequeRate?: NonNullable redemptionRate?: ProductTypePointsSchema } diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/priceCardProps.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/priceCardProps.ts index c5a0824b0..d98c27470 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/priceCardProps.ts +++ b/apps/scandic-web/types/components/hotelReservation/selectHotel/priceCardProps.ts @@ -1,4 +1,8 @@ -import type { ProductTypePrices } from "@/types/trpc/routers/hotel/availability" +import type { + ProductTypeCheque, + ProductTypePrices, + ProductTypeVoucher, +} from "@/types/trpc/routers/hotel/availability" export type PriceCardProps = { productTypePrices: ProductTypePrices @@ -10,3 +14,11 @@ export type PointsRowProps = { additionalPricePerStay?: number additionalPriceCurrency?: string } + +export type VoucherCardProps = { + productTypeVoucher: ProductTypeVoucher +} + +export type BonusChequeCardProps = { + productTypeVoucher: ProductTypeCheque +} diff --git a/apps/scandic-web/types/components/hotelReservation/selectRate/flexibilityOption.ts b/apps/scandic-web/types/components/hotelReservation/selectRate/flexibilityOption.ts index f39ce8c03..515f1c2f9 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectRate/flexibilityOption.ts +++ b/apps/scandic-web/types/components/hotelReservation/selectRate/flexibilityOption.ts @@ -25,6 +25,12 @@ export type FlexibilityOptionProps = { rateName?: string // Obtained in case of booking code and redemption rates } +export interface FlexibilityOptionVoucherProps + extends Omit { + product: Product +} +export type FlexibilityOptionChequeProps = FlexibilityOptionVoucherProps + export interface PriceListProps { publicPrice?: ProductPrice | Record memberPrice?: ProductPrice | Record diff --git a/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts b/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts index db99748df..d4051b1c7 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts +++ b/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts @@ -44,14 +44,32 @@ export type Rate = { roomTypeCode: RoomConfiguration["roomTypeCode"] } & ( | { + bonusCheque?: never member?: NonNullable public?: NonNullable redemption?: never + voucher?: never } | { + bonusCheque?: never member?: never public?: never - redemption: NonNullable + redemption?: never + voucher?: NonNullable + } + | { + bonusCheque?: NonNullable + member?: never + public?: never + redemption?: never + voucher?: never + } + | { + bonusCheque?: never + member?: never + public?: never + redemption?: NonNullable + voucher?: never } ) diff --git a/apps/scandic-web/types/contexts/select-rate/room.ts b/apps/scandic-web/types/contexts/select-rate/room.ts index 0b0b7b755..2080d589b 100644 --- a/apps/scandic-web/types/contexts/select-rate/room.ts +++ b/apps/scandic-web/types/contexts/select-rate/room.ts @@ -15,6 +15,8 @@ export interface RoomContextValue extends SelectedRoom { rate: SelectedRate, selectedRateCode?: string ) => void + selectRateCheque: (rate: SelectedRate) => void + selectRateVoucher: (rate: SelectedRate) => void } isActiveRoom: boolean isMainRoom: boolean diff --git a/apps/scandic-web/types/enums/currency.ts b/apps/scandic-web/types/enums/currency.ts index 6f7bd78e6..b70d109d5 100644 --- a/apps/scandic-web/types/enums/currency.ts +++ b/apps/scandic-web/types/enums/currency.ts @@ -5,5 +5,7 @@ export enum CurrencyEnum { PLN = "PLN", SEK = "SEK", POINTS = "POINTS", + Voucher = "Voucher", + CC = "CC", Unknown = "Unknown", } diff --git a/apps/scandic-web/types/enums/rateType.ts b/apps/scandic-web/types/enums/rateType.ts index a84b1c1b3..c85044281 100644 --- a/apps/scandic-web/types/enums/rateType.ts +++ b/apps/scandic-web/types/enums/rateType.ts @@ -3,6 +3,7 @@ export enum RateTypeEnum { BonusCheque = "BonusCheque", Company = "Company", Promotion = "Promotion", + PublicPromotion = "PublicPromotion", Redemption = "Redemption", Regular = "Regular", TravelAgent = "TravelAgent", diff --git a/apps/scandic-web/types/stores/rates.ts b/apps/scandic-web/types/stores/rates.ts index a4f374eca..3da0e865c 100644 --- a/apps/scandic-web/types/stores/rates.ts +++ b/apps/scandic-web/types/stores/rates.ts @@ -30,6 +30,8 @@ interface Actions { selectRateRedemption: ( idx: number ) => (rate: SelectedRate, selectedRateCode?: string) => void + selectRateVoucher: (idx: number) => (rate: SelectedRate) => void + selectRateCheque: (idx: number) => (rate: SelectedRate) => void } export interface SelectedRate { diff --git a/apps/scandic-web/types/trpc/routers/hotel/availability.ts b/apps/scandic-web/types/trpc/routers/hotel/availability.ts index 68b95cbb0..4f19fe05c 100644 --- a/apps/scandic-web/types/trpc/routers/hotel/availability.ts +++ b/apps/scandic-web/types/trpc/routers/hotel/availability.ts @@ -10,8 +10,10 @@ import type { z } from "zod" import type { hotelsAvailabilitySchema } from "@/server/routers/hotels/output" import type { productTypeSchema } from "@/server/routers/hotels/schemas/availability/productType" import type { + productTypeChequeSchema, productTypePointsSchema, productTypePriceSchema, + productTypeVoucherSchema, } from "@/server/routers/hotels/schemas/productTypePrice" export type HotelsAvailability = z.output @@ -30,6 +32,8 @@ export type SelectedRoomAvailabilitySchema = z.output< export type ProductType = z.output export type ProductTypePrices = z.output export type ProductTypePoints = z.output +export type ProductTypeVoucher = z.output +export type ProductTypeCheque = z.output export type HotelsAvailabilityItem = HotelsAvailability["data"][number]["attributes"] diff --git a/apps/scandic-web/utils/numberFormatting.ts b/apps/scandic-web/utils/numberFormatting.ts index 98e97c972..cdbe68a9e 100644 --- a/apps/scandic-web/utils/numberFormatting.ts +++ b/apps/scandic-web/utils/numberFormatting.ts @@ -16,11 +16,17 @@ export function getSingleDecimal(n: Number | string) { * @param currency - currency code * @returns localized and formatted number in string type with currency */ -export function formatPrice(intl: IntlShape, price: number, currency: string) { +export function formatPrice( + intl: IntlShape, + price: number, + currency: string, + additionalPrice?: number, + additionalPriceCurrency?: string +) { const localizedPrice = intl.formatNumber(price, { minimumFractionDigits: 0, }) - return `${localizedPrice} ${currency}` + return `${localizedPrice} ${currency} ${additionalPrice ? "+ " + additionalPrice + " " + additionalPriceCurrency : ""}` } // This will handle redemption and bonus cheque (corporate cheque) scneario with partial payments