Merged in fix/refactor-currency-display (pull request #3434)

fix(SW-3616): Handle EuroBonus point type everywhere

* Add tests to formatPrice

* formatPrice

* More work replacing config with api points type

* More work replacing config with api points type

* More fixing with currency

* maybe actually fixed it

* Fix MyStay

* Clean up

* Fix comments

* Merge branch 'master' into fix/refactor-currency-display

* Fix calculateTotalPrice for EB points + SF points + cash


Approved-by: Joakim Jäderberg
This commit is contained in:
Anton Gunnarsson
2026-01-15 09:32:17 +00:00
parent c61ddaf94d
commit 16fbdb7ae0
59 changed files with 729 additions and 282 deletions

View File

@@ -11,7 +11,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import useLang from "../../../hooks/useLang"
import { useBookingConfirmationStore } from "../../../stores/booking-confirmation"
import { ReceiptRoom as Room } from "./Room"
import { ReceiptRoom } from "./Room"
import TotalPrice from "./TotalPrice"
import styles from "./receipt.module.css"
@@ -69,7 +69,7 @@ export function Receipt() {
<Divider color="Border/Divider/Subtle" />
{filteredRooms.map((room, idx) => (
<Room
<ReceiptRoom
key={room ? room.confirmationNumber : `loader-${idx}`}
room={room}
roomNumber={idx + 1}

View File

@@ -60,7 +60,14 @@ export function LinkedReservation({
totalBookingPrice,
currencyCode
)
: formatPrice(intl, totalBookingPrice, currencyCode)
: formatPrice(
intl,
totalBookingPrice,
currencyCode,
undefined,
undefined,
roomData.pointsType
)
setFormattedTotalCost(formattedTotalCost)
}

View File

@@ -42,7 +42,8 @@ export function mapRoomState(
booking.roomPoints,
CurrencyEnum.POINTS,
booking.roomPrice,
booking.currencyCode
booking.currencyCode,
booking.roomPointType
)
} else if (booking.cheques) {
formattedRoomCost = formatPrice(
@@ -72,6 +73,7 @@ export function mapRoomState(
childBedPreferences: booking.childBedPreferences,
confirmationNumber: booking.confirmationNumber,
currencyCode: booking.currencyCode,
pointsType: booking.roomPointType,
formattedRoomCost,
fromDate: booking.checkInDate,
name: room.name,

View File

@@ -113,7 +113,8 @@ export default function Room({
room.roomPrice.perStay.local.price,
room.roomPrice.perStay.local.currency,
room.roomPrice.perStay.local.additionalPrice,
room.roomPrice.perStay.local.additionalPriceCurrency
room.roomPrice.perStay.local.additionalPriceCurrency,
room.roomPrice.perStay.local.pointsType
)
let currency: string = room.roomPrice.perStay.local.currency

View File

@@ -203,7 +203,8 @@ export default function SummaryUI({
totalPrice.local.price,
totalCurrency,
totalPrice.local.additionalPrice,
totalPrice.local.additionalPriceCurrency
totalPrice.local.additionalPriceCurrency,
totalPrice.local.pointsType
)}
</span>
</Typography>

View File

@@ -1,3 +1,4 @@
import type { PointType } from "@scandic-hotels/common/constants/pointType"
import type { imageSchema } from "@scandic-hotels/trpc/routers/hotels/schemas/image"
import type { ProductTypeCheque } from "@scandic-hotels/trpc/types/availability"
import type { Amenities } from "@scandic-hotels/trpc/types/hotel"
@@ -19,6 +20,7 @@ export type HotelPin = {
publicPrice: number | null
memberPrice: number | null
redemptionPrice: number | null
pointsType: PointType | null
voucherPrice: number | null
rateType: string | null
currency: string
@@ -59,6 +61,7 @@ export function getHotelPins(
publicPrice: productType?.public?.localPrice.pricePerNight ?? null,
memberPrice: productType?.member?.localPrice.pricePerNight ?? null,
redemptionPrice: redemptionRate?.localPrice.pointsPerStay ?? null,
pointsType: redemptionRate?.localPrice.pointsType ?? null,
voucherPrice: voucherPrice ?? null,
rateType:
productType?.public?.rateType ?? productType?.member?.rateType ?? null,

View File

@@ -13,10 +13,7 @@ import { useScrollToTop } from "@scandic-hotels/common/hooks/useScrollToTop"
import { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton"
import { HotelCard } from "@scandic-hotels/design-system/HotelCard"
import {
useBookingFlowConfig,
useGetPointsCurrency,
} from "../../bookingFlowConfig/bookingFlowConfigContext"
import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext"
import { useIsLoggedIn } from "../../hooks/useIsLoggedIn"
import useLang from "../../hooks/useLang"
import { mapApiImagesToGalleryImages } from "../../misc/imageGallery"
@@ -65,7 +62,6 @@ export default function HotelCardListing({
const { activeHotel, activate, disengage, engage } = useHotelsMapStore()
const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 })
const activeCardRef = useRef<HTMLDivElement | null>(null)
const pointsCurrency = useGetPointsCurrency()
const config = useBookingFlowConfig()
const sortBy = searchParams.get("sort") ?? DEFAULT_SORT
@@ -183,7 +179,6 @@ export default function HotelCardListing({
tripAdvisor: hotel.hotel.ratings?.tripAdvisor.rating,
},
}}
pointsCurrency={pointsCurrency}
lang={lang}
fullPrice={!hotel.availability.bookingCode}
prices={

View File

@@ -14,7 +14,6 @@ import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton
import Link from "@scandic-hotels/design-system/OldDSLink"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useGetPointsCurrency } from "../../bookingFlowConfig/bookingFlowConfigContext"
import { useIsLoggedIn } from "../../hooks/useIsLoggedIn"
import useLang from "../../hooks/useLang"
@@ -33,7 +32,6 @@ export default function ListingHotelCardDialog({
}: ListingHotelCardProps) {
const intl = useIntl()
const lang = useLang()
const pointsCurrency = useGetPointsCurrency()
const [imageError, setImageError] = useState(false)
@@ -49,6 +47,7 @@ export default function ListingHotelCardDialog({
ratings,
operaId,
redemptionPrice,
pointsType,
chequePrice,
voucherPrice,
hasEnoughPoints,
@@ -183,7 +182,7 @@ export default function ListingHotelCardDialog({
{redemptionPrice && (
<HotelPointsRow
pointsPerStay={redemptionPrice}
pointsCurrency={pointsCurrency}
pointsType={pointsType}
/>
)}
{chequePrice && (

View File

@@ -26,7 +26,8 @@ export default function LargeRow({
price.local.price,
price.local.currency,
price.local.additionalPrice,
price.local.additionalPriceCurrency
price.local.additionalPriceCurrency,
price.local.pointsType
)
const regularPrice = price.local.regularPrice
? formatPrice(
@@ -34,7 +35,8 @@ export default function LargeRow({
price.local.regularPrice,
price.local.currency,
price.local.additionalPrice,
price.local.additionalPriceCurrency
price.local.additionalPriceCurrency,
price.local.pointsType
)
: null

View File

@@ -1,16 +1,15 @@
"use client"
import { useIntl } from "react-intl"
import { type IntlShape, useIntl } from "react-intl"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { PointType } from "@scandic-hotels/common/constants/pointType"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { useGetPointsCurrency } from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
import BoldRow from "../Bold"
import RegularRow from "../Regular"
import BedTypeRow from "./BedType"
import PackagesRow from "./Packages"
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { SharedPriceRowProps } from "./price"
export interface RedemptionPriceType {
@@ -19,11 +18,12 @@ export interface RedemptionPriceType {
currency?: CurrencyEnum
pointsPerNight: number
pointsPerStay: number
pointsType: PointType
}
}
interface RedemptionPriceProps extends SharedPriceRowProps {
currency: string
currency: CurrencyEnum
nights: number
price: RedemptionPriceType["redemption"]
}
@@ -36,7 +36,6 @@ export default function RedemptionPrice({
price,
}: RedemptionPriceProps) {
const intl = useIntl()
const pointsCurrency = useGetPointsCurrency()
if (!price) {
return null
@@ -53,10 +52,17 @@ export default function RedemptionPrice({
? Math.ceil(additionalPricePerStay / nights)
: null
const additionalCurrency = price.currency ?? currency
let averagePricePerNight = `${price.pointsPerNight} ${pointsCurrency}`
const actualCurrency = price.currency || currency
const formattedCurrency = getCurrencyText(
price.pointsPerStay,
actualCurrency,
price.pointsType,
intl
)
let averagePricePerNight = `${price.pointsPerNight} ${formattedCurrency}`
if (averageAdditionalPricePerNight) {
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${additionalCurrency}`
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${formattedCurrency}`
}
return (
@@ -69,9 +75,10 @@ export default function RedemptionPrice({
value={formatPrice(
intl,
price.pointsPerStay,
pointsCurrency,
currency,
additionalPricePerStay,
additionalCurrency
formattedCurrency,
price.pointsType
)}
/>
{nights > 1 ? (
@@ -82,3 +89,47 @@ export default function RedemptionPrice({
</>
)
}
function getCurrencyText(
points: number,
currency: CurrencyEnum | undefined,
pointsType: PointType,
intl: IntlShape
) {
if (!currency) return currency
if (currency === CurrencyEnum.POINTS) {
switch (pointsType) {
case PointType.SCANDIC: {
return intl.formatMessage(
{
id: "price.numberOfScandicPoints",
defaultMessage:
"{numberOfScandicPoints, plural, one {Point} other {Points}}",
},
{
numberOfScandicPoints: points,
}
)
}
case PointType.EUROBONUS: {
return intl.formatMessage(
{
id: "price.numberOfEuroBonusPoints",
defaultMessage:
"{numberOfEuroBonusPoints, plural, one {EB Point} other {EB Points}}",
},
{
numberOfEuroBonusPoints: points,
}
)
}
default: {
const _exhaustiveCheck: never = pointsType
return currency
}
}
}
return currency
}

View File

@@ -17,7 +17,6 @@ interface VatProps {
const noVatCurrencies = [
CurrencyEnum.CC,
CurrencyEnum.POINTS,
CurrencyEnum.EUROBONUS,
CurrencyEnum.Voucher,
CurrencyEnum.Unknown,
]

View File

@@ -119,7 +119,7 @@ export default function PriceDetailsTable({
return (
<table className={styles.priceDetailsTable}>
{rooms.map((room, idx) => {
let currency = ""
let currency: CurrencyEnum = defaultCurrency
let chequePrice: CorporateChequePriceType["corporateCheque"] | undefined
if ("corporateCheque" in room.price && room.price.corporateCheque) {
chequePrice = room.price.corporateCheque
@@ -151,10 +151,6 @@ export default function PriceDetailsTable({
voucherPrice = room.price.voucher
}
if (!currency) {
currency = defaultCurrency
}
if (!price && !voucherPrice && !chequePrice && !redemptionPrice) {
return null
}

View File

@@ -19,7 +19,6 @@ import Link from "@scandic-hotels/design-system/OldDSLink"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trackEvent } from "@scandic-hotels/tracking/base"
import { useGetPointsCurrency } from "../../../../bookingFlowConfig/bookingFlowConfigContext"
import { useIsLoggedIn } from "../../../../hooks/useIsLoggedIn"
import useLang from "../../../../hooks/useLang"
import { mapApiImagesToGalleryImages } from "../../../../misc/imageGallery"
@@ -80,7 +79,6 @@ export function SelectHotelMapContent({
const setResultCount = useHotelResultCountStore(
(state) => state.setResultCount
)
const pointsCurrency = useGetPointsCurrency()
const hotelMapStore = useHotelsMapStore()
@@ -266,7 +264,6 @@ export function SelectHotelMapContent({
)}
</div>
<InteractiveMap
pointsCurrency={pointsCurrency}
closeButton={closeButton}
coordinates={coordinates}
hotelPins={filteredHotelPins.map((pin) => {

View File

@@ -167,7 +167,8 @@ export function DesktopSummary({
selectedRates.totalPrice.local.price,
selectedRates.totalPrice.local.currency,
selectedRates.totalPrice.local.additionalPrice,
selectedRates.totalPrice.local.additionalPriceCurrency
selectedRates.totalPrice.local.additionalPriceCurrency,
selectedRates.totalPrice.local.pointsType
)}
</p>
</Typography>
@@ -203,7 +204,8 @@ export function DesktopSummary({
selectedRates.totalPrice.requested.currency,
selectedRates.totalPrice.requested.additionalPrice,
selectedRates.totalPrice.requested
.additionalPriceCurrency
.additionalPriceCurrency,
selectedRates.totalPrice.local.pointsType
),
}
)}

View File

@@ -195,7 +195,8 @@ export default function SummaryContent({
selectedRates.totalPrice.local.price,
selectedRates.totalPrice.local.currency,
selectedRates.totalPrice.local.additionalPrice,
selectedRates.totalPrice.local.additionalPriceCurrency
selectedRates.totalPrice.local.additionalPriceCurrency,
selectedRates.totalPrice.local.pointsType
)}
</span>
</Typography>

View File

@@ -137,7 +137,8 @@ export default function Room({
room.roomPrice.perStay.local.price,
room.roomPrice.perStay.local.currency,
room.roomPrice.perStay.local.additionalPrice,
room.roomPrice.perStay.local.additionalPriceCurrency
room.roomPrice.perStay.local.additionalPriceCurrency,
room.roomPrice.perStay.local.pointsType
)}
</p>
{showDiscounted && room.roomPrice.perStay.local.regularPrice ? (

View File

@@ -100,7 +100,8 @@ export function MobileSummary() {
selectedRates.totalPrice.local.price,
selectedRates.totalPrice.local.currency,
selectedRates.totalPrice.local.additionalPrice,
selectedRates.totalPrice.local.additionalPriceCurrency
selectedRates.totalPrice.local.additionalPriceCurrency,
selectedRates.totalPrice.local.pointsType
)}
</span>
</Typography>

View File

@@ -1,9 +1,9 @@
"use client"
import { useIntl } from "react-intl"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import PointsRateCard from "@scandic-hotels/design-system/PointsRateCard"
import { useGetPointsCurrency } from "../../../../../../../bookingFlowConfig/bookingFlowConfigContext"
import { useSelectRateContext } from "../../../../../../../contexts/SelectRate/SelectRateContext"
import { BookingCodeFilterEnum } from "../../../../../../../stores/bookingCode-filter"
import { sumPackages } from "../../../../../../../utils/SelectRate"
@@ -35,7 +35,6 @@ export default function Redemptions({
selectedRates,
} = useSelectRateContext()
const roomNr = roomIndex + 1
const pointsCurrency = useGetPointsCurrency()
// TODO: Replace with context value when we have support for dropdown "Show all rates"
const selectedFilter = BookingCodeFilterEnum.All as BookingCodeFilterEnum
@@ -92,9 +91,10 @@ export default function Redemptions({
price: additionalPrice.toString(),
}
: undefined,
currency: pointsCurrency ?? "PTS",
currency: CurrencyEnum.POINTS,
isDisabled: !r.redemption.hasEnoughPoints,
points: r.redemption.localPrice.pointsPerStay.toString(),
points: r.redemption.localPrice.pointsPerStay,
pointsType: r.redemption.localPrice.pointsType,
rateCode: r.redemption.rateCode,
}
})