diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts b/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts index 9478a3cce..c5ea2ab5d 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/utils.ts @@ -56,7 +56,7 @@ export function mapRoomState( formattedRoomCost = formatPrice( intl, booking.vouchers, - intl.formatMessage({ defaultMessage: "Voucher" }) + CurrencyEnum.Voucher ) } diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Room/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Room/index.tsx index a0de474f0..0863cfed7 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Room/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/Room/index.tsx @@ -1,6 +1,7 @@ import { cx } from "class-variance-authority" import { useIntl } from "react-intl" +import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" import { Button } from "@scandic-hotels/design-system/Button" import { Divider } from "@scandic-hotels/design-system/Divider" @@ -16,8 +17,6 @@ import Breakfast from "./Breakfast" import styles from "./room.module.css" -import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency" - import type { Room as RoomType } from "@/types/stores/enter-details" interface RoomProps { @@ -114,14 +113,13 @@ export default function Room({ ) let currency: string = room.roomPrice.perStay.local.currency - const voucherCurrency = intl.formatMessage({ defaultMessage: "Voucher" }) const isVoucher = "voucher" in room.roomRate if (isVoucher) { - currency = voucherCurrency + currency = CurrencyEnum.Voucher price = formatPrice( intl, room.roomPrice.perStay.local.price, - voucherCurrency, + currency, room.roomPrice.perStay.local.additionalPrice, room.roomPrice.perStay.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 c27133c07..94eb0920d 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -86,15 +86,9 @@ export default function SummaryUI({ const showDiscounted = containsBookingCodeRate || isMember const totalCurrency = isVoucherRate - ? intl.formatMessage({ defaultMessage: "Voucher" }) + ? CurrencyEnum.Voucher : totalPrice.local.currency - if (isVoucherRate && defaultCurrency === CurrencyEnum.Voucher) { - defaultCurrency = intl.formatMessage({ - defaultMessage: "Voucher", - }) as CurrencyEnum - } - return (
({ + ...redemption, + localPrice: { + ...redemption.localPrice, + currency: redemption.localPrice.currency, + }, + }) + ), + } + } onHover={() => engage(hotel.hotel.name)} onHoverEnd={() => disengage()} onAddressClick={() => { diff --git a/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx index bcb6e856b..4ddaccc72 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx @@ -1,12 +1,11 @@ "use client" import { useIntl } from "react-intl" +import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" import { Typography } from "@scandic-hotels/design-system/Typography" -import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency" - export default function Vouchers({ currencyCode, isCancelled, @@ -27,7 +26,7 @@ export default function Vouchers({ const totalPrice = formatPrice( intl, vouchers, - intl.formatMessage({ defaultMessage: "Voucher" }), + CurrencyEnum.Voucher, price, currencyCode ) diff --git a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx index a99a2be1b..5ca994a92 100644 --- a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx +++ b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Large.tsx @@ -1,7 +1,6 @@ import { cx } from "class-variance-authority" import { useIntl } from "react-intl" -import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" import { Typography } from "@scandic-hotels/design-system/Typography" @@ -21,14 +20,11 @@ export default function LargeRow({ price, }: RowProps) { const intl = useIntl() - const isVoucherRate = price.local.currency === CurrencyEnum.Voucher - const currency = isVoucherRate - ? intl.formatMessage({ defaultMessage: "Voucher" }) - : price.local.currency + const totalPrice = formatPrice( intl, price.local.price, - currency, + price.local.currency, price.local.additionalPrice, price.local.additionalPriceCurrency ) diff --git a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Price/Voucher.tsx b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Price/Voucher.tsx index 4e49fd596..ce5c47b6c 100644 --- a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Price/Voucher.tsx +++ b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Row/Price/Voucher.tsx @@ -1,6 +1,7 @@ "use client" import { useIntl } from "react-intl" +import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" import BoldRow from "../Bold" @@ -35,19 +36,21 @@ export default function VoucherPrice({ return null } - const voucherCurrency = intl.formatMessage({ defaultMessage: "Voucher" }) - const averagePriceTitle = intl.formatMessage({ defaultMessage: "Average price per night", }) - const averagePricePerNight = `${price.numberOfVouchers / nights} ${voucherCurrency}` + const averagePricePerNight = formatPrice( + intl, + price.numberOfVouchers / nights, + CurrencyEnum.Voucher + ) return ( <> {nights > 1 ? ( 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 c46c0db33..d98fd76c0 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts @@ -8,7 +8,6 @@ import type { Product, RedemptionProduct, } from "@scandic-hotels/trpc/types/roomAvailability" -import type { IntlShape } from "react-intl" import type { Price } from "@/types/components/hotelReservation/price" import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" @@ -214,8 +213,7 @@ export function calculateCorporateChequePrice(selectedRateSummary: Rate[]) { export function getTotalPrice( mainRoomProduct: Rate | null, rateSummary: Array, - isUserLoggedIn: boolean, - intl: IntlShape + isUserLoggedIn: boolean ): Price | null { const summaryArray = rateSummary.filter((rate): rate is Rate => rate !== null) @@ -235,9 +233,6 @@ export function getTotalPrice( } if ("voucher" in product) { const voucherPrice = calculateVoucherPrice(summaryArray) - voucherPrice.local.currency = intl.formatMessage({ - defaultMessage: "Voucher", - }) as CurrencyEnum return voucherPrice } diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx index 72f8ee575..5919467db 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomListItem/Rates/Code.tsx @@ -338,15 +338,25 @@ function VoucherCode({ const rateTermDetails = getRateTermDetails(codeProduct) - const voucherMsg = intl - .formatMessage({ - defaultMessage: "Voucher", - }) - .toUpperCase() - let price = `${numberOfVouchers} ${voucherMsg}` - if (packagesSum.price) { - price = `${price} + ${packagesSum.price}` - } + const voucherMsg = intl.formatMessage( + { + defaultMessage: + "{numberOfVouchers, plural, one {Voucher} other {Vouchers}}", + }, + { + numberOfVouchers: numberOfVouchers, + } + ) + + const { price, currency } = packagesSum.price + ? { + price: `${numberOfVouchers} ${voucherMsg} + ${packagesSum.price}`, + currency: packagesSum.currency ?? "", + } + : { + price: `${numberOfVouchers} ${voucherMsg}`, + currency: "", + } return ( { - return defaultMessage - }, -} as IntlShape - describe("getTotalPrice", () => { it("should return null when no rates are selected", () => { const result = getTotalPrice({ selectedRates: [], useMemberPrices: false, - intl: mockIntl, }) expect(result).toEqual({ diff --git a/apps/scandic-web/contexts/SelectRate/getTotalPrice.ts b/apps/scandic-web/contexts/SelectRate/getTotalPrice.ts index 541a1e2f3..b3318730a 100644 --- a/apps/scandic-web/contexts/SelectRate/getTotalPrice.ts +++ b/apps/scandic-web/contexts/SelectRate/getTotalPrice.ts @@ -3,7 +3,6 @@ import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { sumPackages } from "@/components/HotelReservation/utils" import type { RedemptionProduct } from "@scandic-hotels/trpc/types/roomAvailability" -import type { IntlShape } from "react-intl" import type { AvailabilityWithRoomInfo, @@ -32,11 +31,9 @@ type SelectedRate = { export function getTotalPrice({ selectedRates, useMemberPrices, - intl, }: { selectedRates: Array useMemberPrices: boolean - intl: IntlShape }): Price | null { const mainRoom = selectedRates[0] const mainRoomRate = mainRoom?.rate @@ -63,10 +60,6 @@ export function getTotalPrice({ } if ("voucher" in mainRoomRate) { const voucherPrice = calculateVoucherPrice(summaryArray) - // TODO: This is a workaround, should be handled where we print the price. - voucherPrice.local.currency = intl.formatMessage({ - defaultMessage: "Voucher", - }) as CurrencyEnum return voucherPrice } diff --git a/apps/scandic-web/providers/BookingConfirmationProvider.tsx b/apps/scandic-web/providers/BookingConfirmationProvider.tsx index c781d7223..b55284458 100644 --- a/apps/scandic-web/providers/BookingConfirmationProvider.tsx +++ b/apps/scandic-web/providers/BookingConfirmationProvider.tsx @@ -63,7 +63,6 @@ export default function BookingConfirmationProvider({ ) } else if (totalBookingVouchers) { const room = rooms?.[0] - const voucherCurrency = intl.formatMessage({ defaultMessage: "Voucher" }) if (room?.packages) { const pkgsSum = room.packages.reduce( (total, pkg) => total + pkg.totalPrice, @@ -74,7 +73,7 @@ export default function BookingConfirmationProvider({ formattedTotalCost = formatPrice( intl, totalBookingVouchers, - voucherCurrency, + CurrencyEnum.Voucher, pkgsSum, currency ) @@ -83,7 +82,7 @@ export default function BookingConfirmationProvider({ formattedTotalCost = formatPrice( intl, totalBookingVouchers, - voucherCurrency + CurrencyEnum.Voucher ) } } diff --git a/apps/scandic-web/stores/my-stay/helpers.ts b/apps/scandic-web/stores/my-stay/helpers.ts index eecfd9777..1c4aa0651 100644 --- a/apps/scandic-web/stores/my-stay/helpers.ts +++ b/apps/scandic-web/stores/my-stay/helpers.ts @@ -51,7 +51,15 @@ export function calculateTotalPrice( } if (totals.vouchers) { const appendTotalPrice = totalPrice ? `${totalPrice} + ` : "" - totalPrice = `${appendTotalPrice}${totals.vouchers} ${intl.formatMessage({ defaultMessage: "Voucher" })}` + totalPrice = `${appendTotalPrice}${totals.vouchers} ${intl.formatMessage( + { + defaultMessage: + "{numberOfVouchers, plural, one {Voucher} other {Vouchers}}", + }, + { + numberOfVouchers: totals.vouchers, + } + )}` } if (totals.cash) { const appendTotalPrice = totalPrice ? `${totalPrice} + ` : "" diff --git a/packages/common/utils/numberFormatting.ts b/packages/common/utils/numberFormatting.ts index 86cf117cf..ce1b348d8 100644 --- a/packages/common/utils/numberFormatting.ts +++ b/packages/common/utils/numberFormatting.ts @@ -1,3 +1,5 @@ +import { CurrencyEnum } from "../constants/currency" + import type { IntlShape } from "react-intl" /** @@ -21,7 +23,7 @@ export function getSingleDecimal(n: Number | string) { export function formatPrice( intl: IntlShape, price: number, - currency: string, + currency: string | CurrencyEnum, additionalPrice?: number, additionalPriceCurrency?: string ) { @@ -37,5 +39,18 @@ export function formatPrice( formattedAdditionalPrice = ` + ${localizedAdditionalPrice} ${additionalPriceCurrency}` } - return `${localizedPrice} ${currency}${formattedAdditionalPrice}` + const currencyText = + currency === CurrencyEnum.Voucher + ? intl.formatMessage( + { + defaultMessage: + "{numberOfVouchers, plural, one {Voucher} other {Vouchers}}", + }, + { + numberOfVouchers: price, + } + ) + : currency + + return `${localizedPrice} ${currencyText}${formattedAdditionalPrice}` } diff --git a/packages/design-system/lib/components/HotelCard/HotelVoucherCard/index.tsx b/packages/design-system/lib/components/HotelCard/HotelVoucherCard/index.tsx index e96967359..3724bc7ea 100644 --- a/packages/design-system/lib/components/HotelCard/HotelVoucherCard/index.tsx +++ b/packages/design-system/lib/components/HotelCard/HotelVoucherCard/index.tsx @@ -1,6 +1,5 @@ import { useIntl } from 'react-intl' -import { CurrencyEnum } from '@scandic-hotels/common/constants/currency' import Caption from '../../Caption' import Subtitle from '../../Subtitle' @@ -29,7 +28,15 @@ export default function HotelVoucherCard({ {productTypeVoucher.numberOfVouchers} - {CurrencyEnum.Voucher} + {intl.formatMessage( + { + defaultMessage: + '{numberOfVouchers, plural, one {Voucher} other {Vouchers}}', + }, + { + numberOfVouchers: productTypeVoucher.numberOfVouchers, + } + )} diff --git a/packages/design-system/lib/components/HotelCard/index.tsx b/packages/design-system/lib/components/HotelCard/index.tsx index a2061cc37..5e9430a7a 100644 --- a/packages/design-system/lib/components/HotelCard/index.tsx +++ b/packages/design-system/lib/components/HotelCard/index.tsx @@ -31,10 +31,10 @@ import styles from './hotelCard.module.css' import type { Lang } from '@scandic-hotels/common/constants/language' import { FacilityEnum } from '@scandic-hotels/common/constants/facilities' import { RateTypeEnum } from '@scandic-hotels/common/constants/rateType' -import { CurrencyEnum } from '@scandic-hotels/common/constants/currency' import { BookingCodeChip } from '../BookingCodeChip' import { HotelType } from '@scandic-hotels/common/constants/hotelType' import { TripAdvisorChip } from '../TripAdvisorChip' +import { CurrencyEnum } from '@scandic-hotels/common/constants/currency' type Price = { pricePerStay: number @@ -57,47 +57,48 @@ export type HotelCardProps = { tripAdvisor?: number } } - prices: { - public?: { - rateType: RateTypeEnum - localPrice: Price - requestedPrice?: Price - } - member?: { - rateType: RateTypeEnum - localPrice: Price - requestedPrice?: Price - } - voucher?: { - numberOfVouchers: number - rateCode: string - rateType: RateTypeEnum - } - bonusCheque?: { - rateCode: string - rateType: RateTypeEnum - localPrice: { - additionalPricePerStay: number - currency: CurrencyEnum | null | undefined - numberOfCheques: number + prices: + | { + public?: { + rateType: RateTypeEnum + localPrice: Price + requestedPrice?: Price + } + member?: { + rateType: RateTypeEnum + localPrice: Price + requestedPrice?: Price + } + voucher?: { + numberOfVouchers: number + rateCode: string + rateType: RateTypeEnum + } + bonusCheque?: { + rateCode: string + rateType: RateTypeEnum + localPrice: { + additionalPricePerStay: number + currency: CurrencyEnum | null | undefined + numberOfCheques: number + } + requestedPrice?: { + additionalPricePerStay: number + currency: CurrencyEnum | null | undefined + numberOfCheques: number + } + } + redemptions?: { + rateCode: string + hasEnoughPoints: boolean + localPrice: { + additionalPricePerStay: number + pointsPerStay: number + currency: CurrencyEnum | null | undefined + } + }[] } - requestedPrice?: { - additionalPricePerStay: number - currency: CurrencyEnum | null | undefined - numberOfCheques: number - } - } - redemptions?: { - rateCode: string - hasEnoughPoints: boolean - localPrice: { - additionalPricePerStay: number - pointsPerStay: number - currency: string - } - }[] - } - + | undefined images: GalleryImage[] distanceToCityCenter: number isUserLoggedIn: boolean @@ -105,6 +106,7 @@ export type HotelCardProps = { state?: 'default' | 'active' bookingCode?: string | null isAlternative?: boolean + fullPrice: boolean lang: Lang @@ -128,6 +130,7 @@ export const HotelCard = memo( images, lang, belowInfoSlot, + fullPrice, onAddressClick, onHover, onHoverEnd, @@ -151,16 +154,15 @@ export const HotelCard = memo( } const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}` - const fullPrice = !bookingCode - const hasInsufficientPoints = !prices.redemptions?.some( + const hasInsufficientPoints = !prices?.redemptions?.some( (r) => r.hasEnoughPoints ) const notEnoughPointsLabel = intl.formatMessage({ defaultMessage: 'Not enough points', }) - const isDisabled = prices.redemptions?.length && hasInsufficientPoints + const isDisabled = prices?.redemptions?.length && hasInsufficientPoints return (