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

@@ -9,10 +9,10 @@ import DateSelect from "@scandic-hotels/design-system/Form/Date"
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput" import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
import Phone from "@scandic-hotels/design-system/Form/Phone" import Phone from "@scandic-hotels/design-system/Form/Phone"
import { FormSelect } from "@scandic-hotels/design-system/Form/Select" import { FormSelect } from "@scandic-hotels/design-system/Form/Select"
import { PasswordInput } from "@scandic-hotels/design-system/PasswordInput"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { getLocalizedLanguageOptions } from "@/constants/languages" import { getLocalizedLanguageOptions } from "@/constants/languages"
import { PasswordInput } from "@scandic-hotels/design-system/PasswordInput"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { getFormattedCountryList } from "@/utils/countries" import { getFormattedCountryList } from "@/utils/countries"

View File

@@ -34,9 +34,10 @@ export function mapToPrice(room: Room) {
return { return {
redemption: { redemption: {
additionalPricePerStay: room.roomPrice.perStay.local.price, additionalPricePerStay: room.roomPrice.perStay.local.price,
currency: room.currencyCode, currency: CurrencyEnum.POINTS,
pointsPerNight: room.roomPoints / nights, pointsPerNight: room.roomPoints / nights,
pointsPerStay: room.roomPoints, pointsPerStay: room.roomPoints,
pointsType: room.roomPointType,
}, },
} }
case PriceTypeEnum.voucher: case PriceTypeEnum.voucher:
@@ -71,16 +72,6 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
break break
case PriceTypeEnum.points: case PriceTypeEnum.points:
{ {
if (
room.roomPoints &&
room.roomPointType &&
room.roomPointType !== "Scandic"
) {
total.local.currency =
roomPointTypeToCurrencyMap[room.roomPointType]
total.local.price = total.local.price + room.roomPoints
break
}
total.local.currency = CurrencyEnum.POINTS total.local.currency = CurrencyEnum.POINTS
total.local.price = total.local.price + room.totalPoints total.local.price = total.local.price + room.totalPoints
} }
@@ -95,6 +86,17 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
case PriceTypeEnum.cheque: case PriceTypeEnum.cheque:
case PriceTypeEnum.points: case PriceTypeEnum.points:
{ {
if (
room.roomPoints &&
room.roomPointType &&
room.roomPointType !== "Scandic"
) {
total.local.currency = CurrencyEnum.POINTS
total.local.price = total.local.price + room.roomPoints
total.local.pointsType = room.roomPointType
break
}
if (room.totalPrice) { if (room.totalPrice) {
total.local.additionalPrice = total.local.additionalPrice =
(total.local.additionalPrice || 0) + room.totalPrice (total.local.additionalPrice || 0) + room.totalPrice
@@ -140,16 +142,9 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
local: { local: {
currency, currency,
price: 0, price: 0,
pointsType: undefined,
}, },
requested: undefined, requested: undefined,
} }
) )
} }
const roomPointTypeToCurrencyMap: Record<
NonNullable<Room["roomPointType"]>,
CurrencyEnum
> = {
Scandic: CurrencyEnum.POINTS,
EuroBonus: CurrencyEnum.EUROBONUS,
}

View File

@@ -9,16 +9,15 @@ import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConf
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay" import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
interface PriceTypeProps interface PriceTypeProps extends Pick<
extends Pick< BookingConfirmation["booking"],
BookingConfirmation["booking"], | "cheques"
| "cheques" | "currencyCode"
| "currencyCode" | "rateDefinition"
| "rateDefinition" | "totalPoints"
| "totalPoints" | "totalPrice"
| "totalPrice" | "vouchers"
| "vouchers" > {
> {
formattedTotalPrice: string formattedTotalPrice: string
isCancelled: boolean isCancelled: boolean
priceType: PriceTypeEnum priceType: PriceTypeEnum

View File

@@ -10,7 +10,6 @@ export default function TotalPrice() {
const { bookedRoom, totalPrice } = useMyStayStore((state) => ({ const { bookedRoom, totalPrice } = useMyStayStore((state) => ({
bookedRoom: state.bookedRoom, bookedRoom: state.bookedRoom,
totalPrice: state.totalPrice, totalPrice: state.totalPrice,
rooms: state.rooms,
})) }))
return ( return (
<Price <Price

View File

@@ -1,27 +1,25 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { createIntl, createIntlCache } from "react-intl"
import { beforeAll, describe, expect, it, vi } from "vitest" import { beforeAll, describe, expect, it, vi } from "vitest"
import { calculateTotalPrice } from "./helpers" import { calculateTotalPrice } from "./helpers"
const cache = createIntlCache()
const createTestIntl = (locale: string = "en-US") =>
createIntl({ locale, messages: {}, onError: () => {} }, cache)
describe("calculateTotalPrice", () => { describe("calculateTotalPrice", () => {
const baseRoom: Parameters<typeof calculateTotalPrice>[0][0] = { const baseRoom: Parameters<typeof calculateTotalPrice>[0][0] = {
totalPrice: 0, totalPrice: 0,
isCancelled: false, isCancelled: false,
cheques: 0, cheques: 0,
roomPoints: 0,
roomPointType: null, roomPointType: null,
totalPoints: 0, totalPoints: 0,
roomPoints: 0,
vouchers: 0, vouchers: 0,
} }
const mockIntlSimple = {
formatMessage: vi.fn(({}, values) => {
if (values?.numberOfVouchers === 1) return "Voucher"
return "Vouchers"
}),
formatNumber: vi.fn((num) => String(num)),
} as any
vi.mock("@scandic-hotels/common/utils/numberFormatting", () => ({ vi.mock("@scandic-hotels/common/utils/numberFormatting", () => ({
formatPrice: (_intl: any, price: number, currency: string) => formatPrice: (_intl: any, price: number, currency: string) =>
`${price} ${currency}`, `${price} ${currency}`,
@@ -40,7 +38,7 @@ describe("calculateTotalPrice", () => {
const result = calculateTotalPrice( const result = calculateTotalPrice(
rooms, rooms,
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
expect(result).toBe("1500 SEK") expect(result).toBe("1500 SEK")
@@ -60,7 +58,7 @@ describe("calculateTotalPrice", () => {
const result = calculateTotalPrice( const result = calculateTotalPrice(
rooms, rooms,
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
expect(result).toBe("1000 SEK") expect(result).toBe("1000 SEK")
@@ -70,7 +68,7 @@ describe("calculateTotalPrice", () => {
const result = calculateTotalPrice( const result = calculateTotalPrice(
[{ ...baseRoom, vouchers: 2, totalPrice: -1, isCancelled: false }], [{ ...baseRoom, vouchers: 2, totalPrice: -1, isCancelled: false }],
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
expect(result).toContain("2 Vouchers") expect(result).toContain("2 Vouchers")
@@ -84,19 +82,17 @@ describe("calculateTotalPrice", () => {
totalPrice: 100, totalPrice: 100,
isCancelled: false, isCancelled: false,
totalPoints: 0, totalPoints: 0,
roomPoints: 0,
}, },
{ {
...baseRoom, ...baseRoom,
totalPrice: 0, totalPrice: 0,
totalPoints: 20000, totalPoints: 20000,
roomPoints: 20000,
roomPointType: "Scandic", roomPointType: "Scandic",
isCancelled: false, isCancelled: false,
}, },
], ],
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
@@ -109,7 +105,7 @@ describe("calculateTotalPrice", () => {
const result = calculateTotalPrice( const result = calculateTotalPrice(
rooms, rooms,
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
expect(result).toContain("2 CC") expect(result).toContain("2 CC")
@@ -131,7 +127,7 @@ describe("calculateTotalPrice", () => {
}, },
], ],
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
expect(result).toMatch(/1 Voucher \+ 500 SEK/) expect(result).toMatch(/1 Voucher \+ 500 SEK/)
@@ -151,10 +147,10 @@ describe("calculateTotalPrice", () => {
}, },
], ],
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
expect(result).toMatch(/1 EuroBonus \+ 500 SEK/) expect(result).toMatch(/1 EB Point \+ 500 SEK/)
}) })
it("should combine Eurobonus points, Scandic Friends points and Cash", () => { it("should combine Eurobonus points, Scandic Friends points and Cash", () => {
@@ -172,9 +168,9 @@ describe("calculateTotalPrice", () => {
}, },
], ],
"SEK" as any, "SEK" as any,
mockIntlSimple, createTestIntl(),
false false
) )
expect(result).toMatch(/500 Points \+ 1 EuroBonus \+ 500 SEK/) expect(result).toMatch(/1 EB Point \+ 500 Points \+ 500 SEK/)
}) })
}) })

View File

@@ -1,4 +1,5 @@
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" 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 { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import type { IntlShape } from "react-intl" import type { IntlShape } from "react-intl"
@@ -10,8 +11,8 @@ export function calculateTotalPrice(
Room, Room,
| "cheques" | "cheques"
| "vouchers" | "vouchers"
| "roomPoints"
| "roomPointType" | "roomPointType"
| "roomPoints"
| "totalPoints" | "totalPoints"
| "totalPrice" | "totalPrice"
| "isCancelled" | "isCancelled"
@@ -34,18 +35,19 @@ export function calculateTotalPrice(
total.vouchers = total.vouchers + room.vouchers total.vouchers = total.vouchers + room.vouchers
} }
// If roomPointType isn't Scandic, these should be counted as partnerPoints.
// If they're not Scandic points, we can ignore them on roomPoints as they
// are included in totalPoints, which in turn never contain partner points.
if ( if (
room.roomPoints && room.roomPoints &&
room.roomPointType && room.roomPointType &&
room.roomPointType !== "Scandic" room.roomPointType !== "Scandic"
) { ) {
total.partnerPoints = total.partnerPoints + room.roomPoints total.partnerPoints = total.partnerPoints + room.roomPoints
total.partnerPointsCurrency = room.roomPointType ?? null
} }
if (room.totalPoints) { if (room.totalPoints) {
total.scandicFriendsPoints = total.scandicPoints = total.scandicPoints + room.totalPoints
total.scandicFriendsPoints + room.totalPoints
} }
// room.totalPrice is a negative value when // room.totalPrice is a negative value when
@@ -59,9 +61,8 @@ export function calculateTotalPrice(
{ {
cash: 0, cash: 0,
cheques: 0, cheques: 0,
scandicFriendsPoints: 0, scandicPoints: 0,
partnerPoints: 0, partnerPoints: 0,
partnerPointsCurrency: null as Room["roomPointType"],
vouchers: 0, vouchers: 0,
} }
) )
@@ -86,12 +87,24 @@ export function calculateTotalPrice(
priceParts.push(`${totals.cheques} ${CurrencyEnum.CC}`) priceParts.push(`${totals.cheques} ${CurrencyEnum.CC}`)
} }
if (totals.scandicFriendsPoints) { if (totals.partnerPoints) {
priceParts.push(`${totals.scandicFriendsPoints} ${CurrencyEnum.POINTS}`) // We can assume that all rooms has the same point type
const roomPointType = rooms[0]?.roomPointType || PointType.SCANDIC
const currencyText = getPointsCurrencyText(
totals.partnerPoints,
roomPointType,
intl
)
priceParts.push(`${totals.partnerPoints} ${currencyText}`)
} }
if (totals.partnerPoints) { if (totals.scandicPoints) {
priceParts.push(`${totals.partnerPoints} ${totals.partnerPointsCurrency}`) const currencyText = getPointsCurrencyText(
totals.scandicPoints,
PointType.SCANDIC,
intl
)
priceParts.push(`${totals.scandicPoints} ${currencyText}`)
} }
if (totals.cash) { if (totals.cash) {
@@ -102,18 +115,43 @@ export function calculateTotalPrice(
return priceParts.join(" + ") return priceParts.join(" + ")
} }
export function calculateTotalPoints(
rooms: Room[],
allRoomsAreCancelled: boolean
) {
return rooms.reduce((total, room) => {
if (!allRoomsAreCancelled && room.isCancelled) {
return total
}
return total + room.totalPoints
}, 0)
}
export function isAllRoomsCancelled(rooms: Room[]) { export function isAllRoomsCancelled(rooms: Room[]) {
return !rooms.some((room) => room.isCancelled === false) return !rooms.some((room) => room.isCancelled === false)
} }
function getPointsCurrencyText(
points: number,
pointsType: PointType,
intl: IntlShape
) {
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 "Points"
}
}
}

View File

@@ -8,11 +8,7 @@ import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { mapRoomDetails } from "@/components/HotelReservation/MyStay/utils/mapRoomDetails" import { mapRoomDetails } from "@/components/HotelReservation/MyStay/utils/mapRoomDetails"
import { MyStayContext } from "@/contexts/MyStay" import { MyStayContext } from "@/contexts/MyStay"
import { import { calculateTotalPrice, isAllRoomsCancelled } from "./helpers"
calculateTotalPoints,
calculateTotalPrice,
isAllRoomsCancelled,
} from "./helpers"
import type { InitialState, MyStayState } from "@/types/stores/my-stay" import type { InitialState, MyStayState } from "@/types/stores/my-stay"
@@ -56,8 +52,6 @@ export function createMyStayStore({
const allRoomsAreCancelled = isAllRoomsCancelled(mappedRooms) const allRoomsAreCancelled = isAllRoomsCancelled(mappedRooms)
const totalPoints = calculateTotalPoints(mappedRooms, allRoomsAreCancelled)
const totalPrice = calculateTotalPrice( const totalPrice = calculateTotalPrice(
mappedRooms, mappedRooms,
bookedRoom.currencyCode, bookedRoom.currencyCode,
@@ -80,7 +74,6 @@ export function createMyStayStore({
refId, refId,
rooms: mappedRooms, rooms: mappedRooms,
savedCreditCards, savedCreditCards,
totalPoints,
totalPrice, totalPrice,
isPastBooking, isPastBooking,

View File

@@ -1,4 +1,5 @@
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { PointType } from "@scandic-hotels/common/constants/pointType"
interface TPrice { interface TPrice {
additionalPrice?: number additionalPrice?: number
@@ -6,6 +7,7 @@ interface TPrice {
currency: CurrencyEnum currency: CurrencyEnum
price: number price: number
regularPrice?: number regularPrice?: number
pointsType?: PointType | null
} }
export interface Price { export interface Price {

View File

@@ -57,7 +57,6 @@ export interface MyStayState {
refId: string refId: string
rooms: Room[] rooms: Room[]
savedCreditCards: CreditCard[] | null savedCreditCards: CreditCard[] | null
totalPoints: number
totalPrice: string totalPrice: string
isPastBooking: boolean isPastBooking: boolean
} }

View File

@@ -2,8 +2,6 @@
import { createContext, useContext } from "react" import { createContext, useContext } from "react"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { BookingFlowConfig } from "./bookingFlowConfig" import type { BookingFlowConfig } from "./bookingFlowConfig"
type BookingFlowConfigContextData = BookingFlowConfig type BookingFlowConfigContextData = BookingFlowConfig
@@ -24,20 +22,6 @@ export const useBookingFlowConfig = (): BookingFlowConfigContextData => {
return context return context
} }
export const useGetPointsCurrency = () => {
const config = useBookingFlowConfig()
switch (config.variant) {
case "scandic":
return CurrencyEnum.POINTS
case "partner-sas":
return CurrencyEnum.EUROBONUS
default:
const _exhaustiveCheck: never = config.variant
throw new Error(`Unknown variant: ${config.variant}`)
}
}
export function BookingFlowConfigContextProvider({ export function BookingFlowConfigContextProvider({
children, children,
config, config,

View File

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

View File

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

View File

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

View File

@@ -113,7 +113,8 @@ export default function Room({
room.roomPrice.perStay.local.price, room.roomPrice.perStay.local.price,
room.roomPrice.perStay.local.currency, room.roomPrice.perStay.local.currency,
room.roomPrice.perStay.local.additionalPrice, 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 let currency: string = room.roomPrice.perStay.local.currency

View File

@@ -203,7 +203,8 @@ export default function SummaryUI({
totalPrice.local.price, totalPrice.local.price,
totalCurrency, totalCurrency,
totalPrice.local.additionalPrice, totalPrice.local.additionalPrice,
totalPrice.local.additionalPriceCurrency totalPrice.local.additionalPriceCurrency,
totalPrice.local.pointsType
)} )}
</span> </span>
</Typography> </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 { imageSchema } from "@scandic-hotels/trpc/routers/hotels/schemas/image"
import type { ProductTypeCheque } from "@scandic-hotels/trpc/types/availability" import type { ProductTypeCheque } from "@scandic-hotels/trpc/types/availability"
import type { Amenities } from "@scandic-hotels/trpc/types/hotel" import type { Amenities } from "@scandic-hotels/trpc/types/hotel"
@@ -19,6 +20,7 @@ export type HotelPin = {
publicPrice: number | null publicPrice: number | null
memberPrice: number | null memberPrice: number | null
redemptionPrice: number | null redemptionPrice: number | null
pointsType: PointType | null
voucherPrice: number | null voucherPrice: number | null
rateType: string | null rateType: string | null
currency: string currency: string
@@ -59,6 +61,7 @@ export function getHotelPins(
publicPrice: productType?.public?.localPrice.pricePerNight ?? null, publicPrice: productType?.public?.localPrice.pricePerNight ?? null,
memberPrice: productType?.member?.localPrice.pricePerNight ?? null, memberPrice: productType?.member?.localPrice.pricePerNight ?? null,
redemptionPrice: redemptionRate?.localPrice.pointsPerStay ?? null, redemptionPrice: redemptionRate?.localPrice.pointsPerStay ?? null,
pointsType: redemptionRate?.localPrice.pointsType ?? null,
voucherPrice: voucherPrice ?? null, voucherPrice: voucherPrice ?? null,
rateType: rateType:
productType?.public?.rateType ?? productType?.member?.rateType ?? null, 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 { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton"
import { HotelCard } from "@scandic-hotels/design-system/HotelCard" import { HotelCard } from "@scandic-hotels/design-system/HotelCard"
import { import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext"
useBookingFlowConfig,
useGetPointsCurrency,
} from "../../bookingFlowConfig/bookingFlowConfigContext"
import { useIsLoggedIn } from "../../hooks/useIsLoggedIn" import { useIsLoggedIn } from "../../hooks/useIsLoggedIn"
import useLang from "../../hooks/useLang" import useLang from "../../hooks/useLang"
import { mapApiImagesToGalleryImages } from "../../misc/imageGallery" import { mapApiImagesToGalleryImages } from "../../misc/imageGallery"
@@ -65,7 +62,6 @@ export default function HotelCardListing({
const { activeHotel, activate, disengage, engage } = useHotelsMapStore() const { activeHotel, activate, disengage, engage } = useHotelsMapStore()
const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 }) const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 })
const activeCardRef = useRef<HTMLDivElement | null>(null) const activeCardRef = useRef<HTMLDivElement | null>(null)
const pointsCurrency = useGetPointsCurrency()
const config = useBookingFlowConfig() const config = useBookingFlowConfig()
const sortBy = searchParams.get("sort") ?? DEFAULT_SORT const sortBy = searchParams.get("sort") ?? DEFAULT_SORT
@@ -183,7 +179,6 @@ export default function HotelCardListing({
tripAdvisor: hotel.hotel.ratings?.tripAdvisor.rating, tripAdvisor: hotel.hotel.ratings?.tripAdvisor.rating,
}, },
}} }}
pointsCurrency={pointsCurrency}
lang={lang} lang={lang}
fullPrice={!hotel.availability.bookingCode} fullPrice={!hotel.availability.bookingCode}
prices={ 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 Link from "@scandic-hotels/design-system/OldDSLink"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { useGetPointsCurrency } from "../../bookingFlowConfig/bookingFlowConfigContext"
import { useIsLoggedIn } from "../../hooks/useIsLoggedIn" import { useIsLoggedIn } from "../../hooks/useIsLoggedIn"
import useLang from "../../hooks/useLang" import useLang from "../../hooks/useLang"
@@ -33,7 +32,6 @@ export default function ListingHotelCardDialog({
}: ListingHotelCardProps) { }: ListingHotelCardProps) {
const intl = useIntl() const intl = useIntl()
const lang = useLang() const lang = useLang()
const pointsCurrency = useGetPointsCurrency()
const [imageError, setImageError] = useState(false) const [imageError, setImageError] = useState(false)
@@ -49,6 +47,7 @@ export default function ListingHotelCardDialog({
ratings, ratings,
operaId, operaId,
redemptionPrice, redemptionPrice,
pointsType,
chequePrice, chequePrice,
voucherPrice, voucherPrice,
hasEnoughPoints, hasEnoughPoints,
@@ -183,7 +182,7 @@ export default function ListingHotelCardDialog({
{redemptionPrice && ( {redemptionPrice && (
<HotelPointsRow <HotelPointsRow
pointsPerStay={redemptionPrice} pointsPerStay={redemptionPrice}
pointsCurrency={pointsCurrency} pointsType={pointsType}
/> />
)} )}
{chequePrice && ( {chequePrice && (

View File

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

View File

@@ -1,16 +1,15 @@
"use client" "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 { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { useGetPointsCurrency } from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
import BoldRow from "../Bold" import BoldRow from "../Bold"
import RegularRow from "../Regular" import RegularRow from "../Regular"
import BedTypeRow from "./BedType" import BedTypeRow from "./BedType"
import PackagesRow from "./Packages" import PackagesRow from "./Packages"
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { SharedPriceRowProps } from "./price" import type { SharedPriceRowProps } from "./price"
export interface RedemptionPriceType { export interface RedemptionPriceType {
@@ -19,11 +18,12 @@ export interface RedemptionPriceType {
currency?: CurrencyEnum currency?: CurrencyEnum
pointsPerNight: number pointsPerNight: number
pointsPerStay: number pointsPerStay: number
pointsType: PointType
} }
} }
interface RedemptionPriceProps extends SharedPriceRowProps { interface RedemptionPriceProps extends SharedPriceRowProps {
currency: string currency: CurrencyEnum
nights: number nights: number
price: RedemptionPriceType["redemption"] price: RedemptionPriceType["redemption"]
} }
@@ -36,7 +36,6 @@ export default function RedemptionPrice({
price, price,
}: RedemptionPriceProps) { }: RedemptionPriceProps) {
const intl = useIntl() const intl = useIntl()
const pointsCurrency = useGetPointsCurrency()
if (!price) { if (!price) {
return null return null
@@ -53,10 +52,17 @@ export default function RedemptionPrice({
? Math.ceil(additionalPricePerStay / nights) ? Math.ceil(additionalPricePerStay / nights)
: null : null
const additionalCurrency = price.currency ?? currency const actualCurrency = price.currency || currency
let averagePricePerNight = `${price.pointsPerNight} ${pointsCurrency}` const formattedCurrency = getCurrencyText(
price.pointsPerStay,
actualCurrency,
price.pointsType,
intl
)
let averagePricePerNight = `${price.pointsPerNight} ${formattedCurrency}`
if (averageAdditionalPricePerNight) { if (averageAdditionalPricePerNight) {
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${additionalCurrency}` averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${formattedCurrency}`
} }
return ( return (
@@ -69,9 +75,10 @@ export default function RedemptionPrice({
value={formatPrice( value={formatPrice(
intl, intl,
price.pointsPerStay, price.pointsPerStay,
pointsCurrency, currency,
additionalPricePerStay, additionalPricePerStay,
additionalCurrency formattedCurrency,
price.pointsType
)} )}
/> />
{nights > 1 ? ( {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 = [ const noVatCurrencies = [
CurrencyEnum.CC, CurrencyEnum.CC,
CurrencyEnum.POINTS, CurrencyEnum.POINTS,
CurrencyEnum.EUROBONUS,
CurrencyEnum.Voucher, CurrencyEnum.Voucher,
CurrencyEnum.Unknown, CurrencyEnum.Unknown,
] ]

View File

@@ -119,7 +119,7 @@ export default function PriceDetailsTable({
return ( return (
<table className={styles.priceDetailsTable}> <table className={styles.priceDetailsTable}>
{rooms.map((room, idx) => { {rooms.map((room, idx) => {
let currency = "" let currency: CurrencyEnum = defaultCurrency
let chequePrice: CorporateChequePriceType["corporateCheque"] | undefined let chequePrice: CorporateChequePriceType["corporateCheque"] | undefined
if ("corporateCheque" in room.price && room.price.corporateCheque) { if ("corporateCheque" in room.price && room.price.corporateCheque) {
chequePrice = room.price.corporateCheque chequePrice = room.price.corporateCheque
@@ -151,10 +151,6 @@ export default function PriceDetailsTable({
voucherPrice = room.price.voucher voucherPrice = room.price.voucher
} }
if (!currency) {
currency = defaultCurrency
}
if (!price && !voucherPrice && !chequePrice && !redemptionPrice) { if (!price && !voucherPrice && !chequePrice && !redemptionPrice) {
return null 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 { Typography } from "@scandic-hotels/design-system/Typography"
import { trackEvent } from "@scandic-hotels/tracking/base" import { trackEvent } from "@scandic-hotels/tracking/base"
import { useGetPointsCurrency } from "../../../../bookingFlowConfig/bookingFlowConfigContext"
import { useIsLoggedIn } from "../../../../hooks/useIsLoggedIn" import { useIsLoggedIn } from "../../../../hooks/useIsLoggedIn"
import useLang from "../../../../hooks/useLang" import useLang from "../../../../hooks/useLang"
import { mapApiImagesToGalleryImages } from "../../../../misc/imageGallery" import { mapApiImagesToGalleryImages } from "../../../../misc/imageGallery"
@@ -80,7 +79,6 @@ export function SelectHotelMapContent({
const setResultCount = useHotelResultCountStore( const setResultCount = useHotelResultCountStore(
(state) => state.setResultCount (state) => state.setResultCount
) )
const pointsCurrency = useGetPointsCurrency()
const hotelMapStore = useHotelsMapStore() const hotelMapStore = useHotelsMapStore()
@@ -266,7 +264,6 @@ export function SelectHotelMapContent({
)} )}
</div> </div>
<InteractiveMap <InteractiveMap
pointsCurrency={pointsCurrency}
closeButton={closeButton} closeButton={closeButton}
coordinates={coordinates} coordinates={coordinates}
hotelPins={filteredHotelPins.map((pin) => { hotelPins={filteredHotelPins.map((pin) => {

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ import { createContext, useEffect, useRef, useState } from "react"
import { dt } from "@scandic-hotels/common/dt" import { dt } from "@scandic-hotels/common/dt"
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner" import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
import { useGetPointsCurrency } from "../../bookingFlowConfig/bookingFlowConfigContext"
import { getMultiroomDetailsSchema } from "../../components/EnterDetails/Details/Multiroom/schema" import { getMultiroomDetailsSchema } from "../../components/EnterDetails/Details/Multiroom/schema"
import { guestDetailsSchema } from "../../components/EnterDetails/Details/RoomOne/schema" import { guestDetailsSchema } from "../../components/EnterDetails/Details/RoomOne/schema"
import { import {
@@ -65,7 +64,6 @@ export default function EnterDetailsProvider({
// rendering the form until that has been done. // rendering the form until that has been done.
const [hasInitializedStore, setHasInitializedStore] = useState(false) const [hasInitializedStore, setHasInitializedStore] = useState(false)
const storeRef = useRef<EnterDetailsStore>(undefined) const storeRef = useRef<EnterDetailsStore>(undefined)
const pointsCurrency = useGetPointsCurrency()
// eslint-disable-next-line react-hooks/refs // eslint-disable-next-line react-hooks/refs
if (!storeRef.current) { if (!storeRef.current) {
const initialData: InitialState = { const initialData: InitialState = {
@@ -108,8 +106,7 @@ export default function EnterDetailsProvider({
searchParamsStr, searchParamsStr,
user, user,
breakfastPackages, breakfastPackages,
lang, lang
pointsCurrency
) )
} }

View File

@@ -12,7 +12,6 @@ import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel"
import { selectRateRoomsAvailabilityInputSchema } from "@scandic-hotels/trpc/routers/hotels/availability/selectRate/rooms/schema" import { selectRateRoomsAvailabilityInputSchema } from "@scandic-hotels/trpc/routers/hotels/availability/selectRate/rooms/schema"
import { useGetPointsCurrency } from "../../../bookingFlowConfig/bookingFlowConfigContext"
import { useIsLoggedIn } from "../../../hooks/useIsLoggedIn" import { useIsLoggedIn } from "../../../hooks/useIsLoggedIn"
import useLang from "../../../hooks/useLang" import useLang from "../../../hooks/useLang"
import { BookingCodeFilterEnum } from "../../../stores/bookingCode-filter" import { BookingCodeFilterEnum } from "../../../stores/bookingCode-filter"
@@ -63,7 +62,6 @@ export function SelectRateProvider({
const updateBooking = useUpdateBooking() const updateBooking = useUpdateBooking()
const isUserLoggedIn = useIsLoggedIn() const isUserLoggedIn = useIsLoggedIn()
const intl = useIntl() const intl = useIntl()
const pointsCurrency = useGetPointsCurrency()
const [activeRoomIndex, setInternalActiveRoomIndex] = useQueryState<number>( const [activeRoomIndex, setInternalActiveRoomIndex] = useQueryState<number>(
"activeRoomIndex", "activeRoomIndex",
@@ -228,7 +226,6 @@ export function SelectRateProvider({
roomConfiguration: roomAvailability[ix]?.[0], roomConfiguration: roomAvailability[ix]?.[0],
})), })),
isMember: isUserLoggedIn, isMember: isUserLoggedIn,
pointsCurrency,
}) })
const getPriceForRoom = useCallback( const getPriceForRoom = useCallback(
@@ -249,10 +246,9 @@ export function SelectRateProvider({
], ],
isMember: isUserLoggedIn && roomIndex === 0, isMember: isUserLoggedIn && roomIndex === 0,
addAdditionalCost: false, addAdditionalCost: false,
pointsCurrency,
}) })
}, },
[selectedRates, roomAvailability, isUserLoggedIn, pointsCurrency] [selectedRates, roomAvailability, isUserLoggedIn]
) )
const setActiveRoomIndex = useCallback( const setActiveRoomIndex = useCallback(

View File

@@ -17,12 +17,10 @@ export function getTotalPrice({
selectedRates, selectedRates,
isMember, isMember,
addAdditionalCost = true, addAdditionalCost = true,
pointsCurrency,
}: { }: {
selectedRates: Array<SelectedRate | null> selectedRates: Array<SelectedRate | null>
isMember: boolean isMember: boolean
addAdditionalCost?: boolean addAdditionalCost?: boolean
pointsCurrency?: CurrencyEnum
}): Price | null { }): Price | null {
const mainRoom = selectedRates[0] const mainRoom = selectedRates[0]
const mainRoomRate = mainRoom?.rate const mainRoomRate = mainRoom?.rate
@@ -47,8 +45,7 @@ export function getTotalPrice({
mainRoom.roomConfiguration?.selectedPackages.filter( mainRoom.roomConfiguration?.selectedPackages.filter(
(pkg) => "localPrice" in pkg (pkg) => "localPrice" in pkg
) ?? null, ) ?? null,
addAdditionalCost, addAdditionalCost
pointsCurrency
) )
} }
if ("voucher" in mainRoomRate) { if ("voucher" in mainRoomRate) {
@@ -159,8 +156,7 @@ function calculateTotalPrice(
function calculateRedemptionTotalPrice( function calculateRedemptionTotalPrice(
redemption: RedemptionProduct["redemption"], redemption: RedemptionProduct["redemption"],
packages: RoomPackage[] | null, packages: RoomPackage[] | null,
addAdditonalCost: boolean, addAdditonalCost: boolean
pointsCurrency?: CurrencyEnum
) { ) {
const pkgsSum = addAdditonalCost const pkgsSum = addAdditonalCost
? sumPackages(packages) ? sumPackages(packages)
@@ -183,8 +179,9 @@ function calculateRedemptionTotalPrice(
local: { local: {
additionalPrice, additionalPrice,
additionalPriceCurrency, additionalPriceCurrency,
currency: pointsCurrency ?? CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
price: redemption.localPrice.pointsPerStay, price: redemption.localPrice.pointsPerStay,
pointsType: redemption.localPrice.pointsType,
}, },
} }
} }

View File

@@ -45,13 +45,16 @@ export function BookingConfirmationProvider({
let isVatCurrency = true let isVatCurrency = true
if (totalBookingPoints) { if (totalBookingPoints) {
// We can assume all rooms have the same point type
const pointsType = rooms?.[0]?.pointsType
isVatCurrency = false isVatCurrency = false
formattedTotalCost = formatPrice( formattedTotalCost = formatPrice(
intl, intl,
totalBookingPoints, totalBookingPoints,
CurrencyEnum.POINTS, CurrencyEnum.POINTS,
totalBookingPrice, totalBookingPrice,
currencyCode currencyCode,
pointsType
) )
} else if (totalBookingCheques) { } else if (totalBookingCheques) {
isVatCurrency = false isVatCurrency = false

View File

@@ -15,7 +15,6 @@ import {
} from "./helpers" } from "./helpers"
import { getRoomPrice, getTotalPrice } from "./priceCalculations" import { getRoomPrice, getTotalPrice } from "./priceCalculations"
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { Lang } from "@scandic-hotels/common/constants/language" import type { Lang } from "@scandic-hotels/common/constants/language"
import type { BreakfastPackages } from "@scandic-hotels/trpc/routers/hotels/output" import type { BreakfastPackages } from "@scandic-hotels/trpc/routers/hotels/output"
import type { User } from "@scandic-hotels/trpc/types/user" import type { User } from "@scandic-hotels/trpc/types/user"
@@ -41,8 +40,7 @@ export function createDetailsStore(
searchParams: string, searchParams: string,
user: User | null, user: User | null,
breakfastPackages: BreakfastPackages, breakfastPackages: BreakfastPackages,
lang: Lang, lang: Lang
pointsCurrency?: CurrencyEnum
) { ) {
const isMember = !!user const isMember = !!user
const nights = dt(initialState.booking.toDate).diff( const nights = dt(initialState.booking.toDate).diff(
@@ -67,23 +65,14 @@ export function createDetailsStore(
...defaultGuestState, ...defaultGuestState,
phoneNumberCC: getDefaultCountryFromLang(lang), phoneNumberCC: getDefaultCountryFromLang(lang),
}, },
roomPrice: getRoomPrice( roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
room.roomRate,
isMember && idx === 0,
pointsCurrency
),
specialRequest: { specialRequest: {
comment: "", comment: "",
}, },
} }
}) })
const initialTotalPrice = getTotalPrice( const initialTotalPrice = getTotalPrice(initialRooms, isMember, nights)
initialRooms,
isMember,
nights,
pointsCurrency
)
const availableBeds = initialState.rooms.reduce< const availableBeds = initialState.rooms.reduce<
DetailsState["availableBeds"] DetailsState["availableBeds"]
@@ -184,8 +173,7 @@ export function createDetailsStore(
state.totalPrice = getTotalPrice( state.totalPrice = getTotalPrice(
state.rooms.map((r) => r.room), state.rooms.map((r) => r.room),
isMember, isMember,
nights, nights
pointsCurrency
) )
const isAllStepsCompleted = checkRoomProgress( const isAllStepsCompleted = checkRoomProgress(
@@ -216,8 +204,7 @@ export function createDetailsStore(
} }
currentRoom.roomPrice = getRoomPrice( currentRoom.roomPrice = getRoomPrice(
currentRoom.roomRate, currentRoom.roomRate,
isValidMembershipNo || currentRoom.guest.join, isValidMembershipNo || currentRoom.guest.join
pointsCurrency
) )
const nights = dt(state.booking.toDate).diff( const nights = dt(state.booking.toDate).diff(
@@ -228,8 +215,7 @@ export function createDetailsStore(
state.totalPrice = getTotalPrice( state.totalPrice = getTotalPrice(
state.rooms.map((r) => r.room), state.rooms.map((r) => r.room),
isMember, isMember,
nights, nights
pointsCurrency
) )
writeToSessionStorage({ writeToSessionStorage({
@@ -252,8 +238,7 @@ export function createDetailsStore(
currentRoom.roomPrice = getRoomPrice( currentRoom.roomPrice = getRoomPrice(
currentRoom.roomRate, currentRoom.roomRate,
join || !!currentRoom.guest.membershipNo, join || !!currentRoom.guest.membershipNo
pointsCurrency
) )
const nights = dt(state.booking.toDate).diff( const nights = dt(state.booking.toDate).diff(
@@ -264,8 +249,7 @@ export function createDetailsStore(
state.totalPrice = getTotalPrice( state.totalPrice = getTotalPrice(
state.rooms.map((r) => r.room), state.rooms.map((r) => r.room),
isMember, isMember,
nights, nights
pointsCurrency
) )
writeToSessionStorage({ writeToSessionStorage({
@@ -330,8 +314,7 @@ export function createDetailsStore(
currentRoom.roomPrice = getRoomPrice( currentRoom.roomPrice = getRoomPrice(
currentRoom.roomRate, currentRoom.roomRate,
Boolean(data.join || data.membershipNo || isMemberAndRoomOne), Boolean(data.join || data.membershipNo || isMemberAndRoomOne)
pointsCurrency
) )
const nights = dt(state.booking.toDate).diff( const nights = dt(state.booking.toDate).diff(
@@ -342,8 +325,7 @@ export function createDetailsStore(
state.totalPrice = getTotalPrice( state.totalPrice = getTotalPrice(
state.rooms.map((r) => r.room), state.rooms.map((r) => r.room),
isMember, isMember,
nights, nights
pointsCurrency
) )
const isAllStepsCompleted = checkRoomProgress( const isAllStepsCompleted = checkRoomProgress(

View File

@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest" import { describe, expect, it } from "vitest"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { PointType } from "@scandic-hotels/common/constants/pointType"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { import {
@@ -998,16 +999,24 @@ describe("getRedemptionPrice", () => {
const result = getRedemptionPrice([], 1) const result = getRedemptionPrice([], 1)
expect(result).toEqual({ expect(result).toEqual({
local: { price: 0, currency: CurrencyEnum.POINTS }, local: {
price: 0,
currency: CurrencyEnum.POINTS,
pointsType: PointType.SCANDIC,
},
requested: undefined, requested: undefined,
}) })
}) })
it("returns price 0 and set currency when rooms are empty", () => { it("returns price 0 and set currency when rooms are empty", () => {
const result = getRedemptionPrice([], 1, CurrencyEnum.EUROBONUS) const result = getRedemptionPrice([], 1)
expect(result).toEqual({ expect(result).toEqual({
local: { price: 0, currency: CurrencyEnum.EUROBONUS }, local: {
price: 0,
currency: CurrencyEnum.POINTS,
pointsType: PointType.SCANDIC,
},
requested: undefined, requested: undefined,
}) })
}) })
@@ -1026,6 +1035,7 @@ describe("getRedemptionPrice", () => {
pointsPerStay: 100, pointsPerStay: 100,
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0, additionalPricePerStay: 0,
pointsType: PointType.SCANDIC,
}, },
}, },
}, },
@@ -1040,6 +1050,7 @@ describe("getRedemptionPrice", () => {
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPrice: 0, additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS, additionalPriceCurrency: CurrencyEnum.POINTS,
pointsType: PointType.SCANDIC,
}, },
requested: undefined, requested: undefined,
}) })
@@ -1059,6 +1070,7 @@ describe("getRedemptionPrice", () => {
pointsPerStay: 100, pointsPerStay: 100,
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0, additionalPricePerStay: 0,
pointsType: PointType.SCANDIC,
}, },
}, },
}, },
@@ -1073,6 +1085,7 @@ describe("getRedemptionPrice", () => {
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPrice: 0, additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS, additionalPriceCurrency: CurrencyEnum.POINTS,
pointsType: PointType.SCANDIC,
}, },
requested: undefined, requested: undefined,
}) })
@@ -1092,6 +1105,7 @@ describe("getRedemptionPrice", () => {
pointsPerStay: 100, pointsPerStay: 100,
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0, additionalPricePerStay: 0,
pointsType: PointType.SCANDIC,
}, },
}, },
}, },
@@ -1106,6 +1120,7 @@ describe("getRedemptionPrice", () => {
pointsPerStay: 150, pointsPerStay: 150,
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0, additionalPricePerStay: 0,
pointsType: PointType.SCANDIC,
}, },
}, },
}, },
@@ -1120,6 +1135,7 @@ describe("getRedemptionPrice", () => {
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPrice: 0, additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS, additionalPriceCurrency: CurrencyEnum.POINTS,
pointsType: PointType.SCANDIC,
}, },
requested: undefined, requested: undefined,
}) })
@@ -1144,6 +1160,7 @@ describe("getRedemptionPrice", () => {
pointsPerStay: 100, pointsPerStay: 100,
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0, additionalPricePerStay: 0,
pointsType: PointType.SCANDIC,
}, },
}, },
}, },
@@ -1158,6 +1175,7 @@ describe("getRedemptionPrice", () => {
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPrice: 33, additionalPrice: 33,
additionalPriceCurrency: CurrencyEnum.POINTS, additionalPriceCurrency: CurrencyEnum.POINTS,
pointsType: PointType.SCANDIC,
}, },
requested: undefined, requested: undefined,
}) })
@@ -1818,6 +1836,7 @@ describe("getTotalPrice", () => {
pointsPerStay: 100, pointsPerStay: 100,
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPricePerStay: 0, additionalPricePerStay: 0,
pointsType: PointType.SCANDIC,
}, },
}, },
}, },
@@ -1856,6 +1875,7 @@ describe("getTotalPrice", () => {
currency: CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
additionalPrice: 0, additionalPrice: 0,
additionalPriceCurrency: CurrencyEnum.POINTS, additionalPriceCurrency: CurrencyEnum.POINTS,
pointsType: PointType.SCANDIC,
}, },
requested: undefined, requested: undefined,
}) })

View File

@@ -1,4 +1,5 @@
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { PointType } from "@scandic-hotels/common/constants/pointType"
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType" import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
import { logger } from "@scandic-hotels/common/logger" import { logger } from "@scandic-hotels/common/logger"
@@ -19,11 +20,7 @@ function add(...nums: (number | string | undefined)[]) {
}, 0) }, 0)
} }
export function getRoomPrice( export function getRoomPrice(roomRate: Product, isMember: boolean) {
roomRate: Product,
isMember: boolean,
pointsCurrency?: CurrencyEnum
) {
if (isMember && "member" in roomRate && roomRate.member) { if (isMember && "member" in roomRate && roomRate.member) {
let publicRate let publicRate
if ( if (
@@ -167,23 +164,25 @@ export function getRoomPrice(
perNight: { perNight: {
requested: undefined, requested: undefined,
local: { local: {
currency: pointsCurrency ?? CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
price: roomRate.redemption.localPrice.pointsPerStay, price: roomRate.redemption.localPrice.pointsPerStay,
additionalPrice: additionalPrice:
roomRate.redemption.localPrice.additionalPricePerStay, roomRate.redemption.localPrice.additionalPricePerStay,
additionalPriceCurrency: additionalPriceCurrency:
roomRate.redemption.localPrice.currency ?? undefined, roomRate.redemption.localPrice.currency ?? undefined,
pointsType: roomRate.redemption.localPrice.pointsType,
}, },
}, },
perStay: { perStay: {
requested: undefined, requested: undefined,
local: { local: {
currency: pointsCurrency ?? CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
price: roomRate.redemption.localPrice.pointsPerStay, price: roomRate.redemption.localPrice.pointsPerStay,
additionalPrice: additionalPrice:
roomRate.redemption.localPrice.additionalPricePerStay, roomRate.redemption.localPrice.additionalPricePerStay,
additionalPriceCurrency: additionalPriceCurrency:
roomRate.redemption.localPrice.currency ?? undefined, roomRate.redemption.localPrice.currency ?? undefined,
pointsType: roomRate.redemption.localPrice.pointsType,
}, },
}, },
} }
@@ -410,17 +409,18 @@ type RedemptionRoom = BasePriceCalculationRoom & {
localPrice: { localPrice: {
pointsPerStay: number pointsPerStay: number
additionalPricePerStay: number additionalPricePerStay: number
pointsType: PointType
currency?: CurrencyEnum currency?: CurrencyEnum
} }
} }
} }
} }
export function getRedemptionPrice( export function getRedemptionPrice(rooms: RedemptionRoom[], nights: number) {
rooms: RedemptionRoom[], // We can assume that all rooms have the same pointsType
nights: number, const pointsType =
pointsCurrency?: CurrencyEnum rooms[0]?.roomRate.redemption.localPrice.pointsType || PointType.SCANDIC
) {
return rooms.reduce<Price>( return rooms.reduce<Price>(
(total, room) => { (total, room) => {
const redemption = room.roomRate.redemption const redemption = room.roomRate.redemption
@@ -444,8 +444,9 @@ export function getRedemptionPrice(
}, },
{ {
local: { local: {
currency: pointsCurrency ?? CurrencyEnum.POINTS, currency: CurrencyEnum.POINTS,
price: 0, price: 0,
pointsType,
}, },
requested: undefined, requested: undefined,
} }
@@ -580,8 +581,7 @@ export function getTotalPrice(
| VoucherRoom | VoucherRoom
)[], )[],
isMember: boolean, isMember: boolean,
nights: number, nights: number
pointsCurrency?: CurrencyEnum
) { ) {
const corporateChequeRooms = rooms.filter( const corporateChequeRooms = rooms.filter(
(x): x is CorporateCheckRoom => "corporateCheque" in x.roomRate (x): x is CorporateCheckRoom => "corporateCheque" in x.roomRate
@@ -594,7 +594,7 @@ export function getTotalPrice(
(x): x is RedemptionRoom => "redemption" in x.roomRate (x): x is RedemptionRoom => "redemption" in x.roomRate
) )
if (redemptionRooms.length > 0) { if (redemptionRooms.length > 0) {
return getRedemptionPrice(redemptionRooms, nights, pointsCurrency) return getRedemptionPrice(redemptionRooms, nights)
} }
const voucherRooms = rooms.filter( const voucherRooms = rooms.filter(

View File

@@ -1,4 +1,5 @@
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { PointType } from "@scandic-hotels/common/constants/pointType"
interface TPrice { interface TPrice {
additionalPrice?: number additionalPrice?: number
@@ -6,6 +7,7 @@ interface TPrice {
currency: CurrencyEnum currency: CurrencyEnum
price: number price: number
regularPrice?: number regularPrice?: number
pointsType?: PointType | null
} }
// TODO after migration, check this type for duplicates and maybe move to better location // TODO after migration, check this type for duplicates and maybe move to better location

View File

@@ -1,4 +1,5 @@
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { PointType } from "@scandic-hotels/common/constants/pointType"
import type { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum" import type { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum"
import type { import type {
BookingConfirmation, BookingConfirmation,
@@ -32,6 +33,7 @@ export interface Room {
refId: string refId: string
roomFeatures?: PackageSchema[] | null roomFeatures?: PackageSchema[] | null
roomPoints: number roomPoints: number
pointsType: PointType | null
roomPrice: number roomPrice: number
roomTypeCode: string | null roomTypeCode: string | null
toDate: string toDate: string

View File

@@ -6,9 +6,8 @@ export enum CurrencyEnum {
NOK = "NOK", NOK = "NOK",
PLN = "PLN", PLN = "PLN",
SEK = "SEK", SEK = "SEK",
POINTS = "Points",
Voucher = "Voucher", Voucher = "Voucher",
CC = "CC", CC = "CC",
Unknown = "Unknown", Unknown = "Unknown",
EUROBONUS = "EB Points", POINTS = "Points",
} }

View File

@@ -0,0 +1,7 @@
export const PointType = {
SCANDIC: "Scandic",
EUROBONUS: "EuroBonus",
} as const
export const pointTypes = Object.values(PointType)
export type PointType = (typeof PointType)[keyof typeof PointType]

View File

@@ -25,6 +25,7 @@
"./constants/membershipLevels": "./constants/membershipLevels.ts", "./constants/membershipLevels": "./constants/membershipLevels.ts",
"./constants/paymentCallbackStatusEnum": "./constants/paymentCallbackStatusEnum.ts", "./constants/paymentCallbackStatusEnum": "./constants/paymentCallbackStatusEnum.ts",
"./constants/paymentMethod": "./constants/paymentMethod.ts", "./constants/paymentMethod": "./constants/paymentMethod.ts",
"./constants/pointType": "./constants/pointType.ts",
"./constants/rate": "./constants/rate.ts", "./constants/rate": "./constants/rate.ts",
"./constants/rateType": "./constants/rateType.ts", "./constants/rateType": "./constants/rateType.ts",
"./constants/routes/*": "./constants/routes/*.ts", "./constants/routes/*": "./constants/routes/*.ts",

View File

@@ -0,0 +1,200 @@
import { createIntl, createIntlCache } from "react-intl"
import { describe, expect, it } from "vitest"
import { CurrencyEnum } from "../constants/currency"
import { PointType } from "../constants/pointType"
import { formatPrice } from "./numberFormatting"
const cache = createIntlCache()
const createTestIntl = (locale: string = "en-US") =>
createIntl({ locale, messages: {}, onError: () => {} }, cache)
describe("formatPrice", () => {
it("should format price with currency", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 1000, CurrencyEnum.SEK)
expect(result).toBe("1,000 SEK")
})
it("should format price with decimal values", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 99.5, CurrencyEnum.EUR)
expect(result).toBe("99.5 EUR")
})
it("should format price with additional price and currency", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 500, CurrencyEnum.NOK, 100, "SEK")
expect(result).toBe("500 NOK + 100 SEK")
})
it("should not include additional price when only additionalPrice is provided without currency", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 500, CurrencyEnum.DKK, 100, undefined)
expect(result).toBe("500 DKK")
})
it("should not include additional price when only additionalPriceCurrency is provided without amount", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 500, CurrencyEnum.PLN, undefined, "SEK")
expect(result).toBe("500 PLN")
})
it("should format Voucher currency with plural form", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 2, CurrencyEnum.Voucher)
expect(result).toBe("2 Vouchers")
})
it("should format single Voucher correctly", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 1, CurrencyEnum.Voucher)
expect(result).toBe("1 Voucher")
})
it("should handle string currency codes", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 250, "USD")
expect(result).toBe("250 USD")
})
it("should handle zero price", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 0, CurrencyEnum.SEK)
expect(result).toBe("0 SEK")
})
it("should handle large numbers", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 1000000, CurrencyEnum.EUR)
expect(result).toBe("1,000,000 EUR")
})
it("should format numbers according to locale", () => {
const intl = createTestIntl("sv-SE")
const result = formatPrice(intl, 1000, CurrencyEnum.SEK)
// Swedish locale uses non-breaking space as thousands separator
expect(result).toBe("1\u00a0000 SEK")
})
it("should format POINTS currency without pointType", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(intl, 5000, CurrencyEnum.POINTS)
expect(result).toBe("5,000 Points")
})
it("should format POINTS currency with Scandic pointType and plural", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(
intl,
5000,
CurrencyEnum.POINTS,
undefined,
undefined,
PointType.SCANDIC
)
expect(result).toBe("5,000 Points")
})
it("should format POINTS currency with Scandic pointType and singular", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(
intl,
1,
CurrencyEnum.POINTS,
undefined,
undefined,
PointType.SCANDIC
)
expect(result).toBe("1 Point")
})
it("should format POINTS currency with EuroBonus pointType and plural", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(
intl,
10000,
CurrencyEnum.POINTS,
undefined,
undefined,
PointType.EUROBONUS
)
expect(result).toBe("10,000 EB Points")
})
it("should format POINTS currency with EuroBonus pointType and singular", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(
intl,
1,
CurrencyEnum.POINTS,
undefined,
undefined,
PointType.EUROBONUS
)
expect(result).toBe("1 EB Point")
})
it("should format POINTS with additional price and Scandic pointType", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(
intl,
5000,
CurrencyEnum.POINTS,
100,
"SEK",
PointType.SCANDIC
)
expect(result).toBe("5,000 Points + 100 SEK")
})
it("should format POINTS with additional price and EuroBonus pointType", () => {
const intl = createTestIntl("en-US")
const result = formatPrice(
intl,
5000,
CurrencyEnum.POINTS,
100,
"SEK",
PointType.EUROBONUS
)
expect(result).toBe("5,000 EB Points + 100 SEK")
})
})

View File

@@ -1,4 +1,6 @@
import { CurrencyEnum } from "../constants/currency" import { CurrencyEnum } from "../constants/currency"
import { PointType } from "../constants/pointType"
import { logger } from "../logger"
import type { IntlShape } from "react-intl" import type { IntlShape } from "react-intl"
@@ -18,6 +20,7 @@ export function getSingleDecimal(n: Number | string) {
* @param currency - currency code * @param currency - currency code
* @param additionalPrice - number (obtained in reward nights and Corporate cheque scenarios) * @param additionalPrice - number (obtained in reward nights and Corporate cheque scenarios)
* @param additionalPriceCurrency - currency code (obtained in reward nights and Corporate cheque scenarios) * @param additionalPriceCurrency - currency code (obtained in reward nights and Corporate cheque scenarios)
* @param pointsType - type of points when currency is points
* @returns localized and formatted number in string type with currency * @returns localized and formatted number in string type with currency
*/ */
export function formatPrice( export function formatPrice(
@@ -25,7 +28,8 @@ export function formatPrice(
price: number, price: number,
currency: string | CurrencyEnum, currency: string | CurrencyEnum,
additionalPrice?: number, additionalPrice?: number,
additionalPriceCurrency?: string additionalPriceCurrency?: string,
pointsType?: PointType | null
) { ) {
const localizedPrice = intl.formatNumber(price, { const localizedPrice = intl.formatNumber(price, {
minimumFractionDigits: 0, minimumFractionDigits: 0,
@@ -41,19 +45,66 @@ export function formatPrice(
formattedAdditionalPrice = ` + ${localizedAdditionalPrice} ${additionalPriceCurrency}` formattedAdditionalPrice = ` + ${localizedAdditionalPrice} ${additionalPriceCurrency}`
} }
const currencyText = const currencyText = getCurrencyText(intl, price, currency, pointsType)
currency === CurrencyEnum.Voucher
? intl.formatMessage(
{
id: "price.numberOfVouchers",
defaultMessage:
"{numberOfVouchers, plural, one {Voucher} other {Vouchers}}",
},
{
numberOfVouchers: price,
}
)
: currency
return `${localizedPrice} ${currencyText}${formattedAdditionalPrice}` return `${localizedPrice} ${currencyText}${formattedAdditionalPrice}`
} }
function getCurrencyText(
intl: IntlShape,
price: number,
currency: string | CurrencyEnum,
pointsType?: PointType | null
) {
if (currency === CurrencyEnum.Voucher) {
return intl.formatMessage(
{
id: "price.numberOfVouchers",
defaultMessage:
"{numberOfVouchers, plural, one {Voucher} other {Vouchers}}",
},
{
numberOfVouchers: price,
}
)
}
if (currency === CurrencyEnum.POINTS) {
if (!pointsType) return currency
switch (pointsType) {
case PointType.SCANDIC: {
return intl.formatMessage(
{
id: "price.numberOfScandicPoints",
defaultMessage:
"{numberOfScandicPoints, plural, one {Point} other {Points}}",
},
{
numberOfScandicPoints: price,
}
)
}
case PointType.EUROBONUS: {
return intl.formatMessage(
{
id: "price.numberOfEuroBonusPoints",
defaultMessage:
"{numberOfEuroBonusPoints, plural, one {EB Point} other {EB Points}}",
},
{
numberOfEuroBonusPoints: price,
}
)
}
default: {
const _exhaustiveCheck: never = pointsType
void _exhaustiveCheck
logger.warn(`Unknown point type provided: ${pointsType}`)
return currency
}
}
}
return currency
}

View File

@@ -51,6 +51,14 @@ export default defineConfig([
}, },
}, },
rules: { rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
"import/no-relative-packages": "error", "import/no-relative-packages": "error",
"import/no-extraneous-dependencies": [ "import/no-extraneous-dependencies": [
"error", "error",

View File

@@ -9,7 +9,6 @@ import { Typography } from "../../../Typography"
import { HotelCardDialogImage } from "../../HotelCardDialogImage" import { HotelCardDialogImage } from "../../HotelCardDialogImage"
import { NoPriceAvailableCard } from "../../NoPriceAvailableCard" import { NoPriceAvailableCard } from "../../NoPriceAvailableCard"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { Lang } from "@scandic-hotels/common/constants/language" import { Lang } from "@scandic-hotels/common/constants/language"
import { selectRate } from "@scandic-hotels/common/constants/routes/hotelReservation" import { selectRate } from "@scandic-hotels/common/constants/routes/hotelReservation"
import { useUrlWithSearchParam } from "@scandic-hotels/common/hooks/useUrlWithSearchParam" import { useUrlWithSearchParam } from "@scandic-hotels/common/hooks/useUrlWithSearchParam"
@@ -26,7 +25,6 @@ interface StandaloneHotelCardProps {
isUserLoggedIn: boolean isUserLoggedIn: boolean
handleClose: () => void handleClose: () => void
onClick?: () => void onClick?: () => void
pointsCurrency?: CurrencyEnum
} }
export function StandaloneHotelCardDialog({ export function StandaloneHotelCardDialog({
@@ -35,7 +33,6 @@ export function StandaloneHotelCardDialog({
handleClose, handleClose,
isUserLoggedIn, isUserLoggedIn,
onClick, onClick,
pointsCurrency,
}: StandaloneHotelCardProps) { }: StandaloneHotelCardProps) {
const intl = useIntl() const intl = useIntl()
const [imageError, setImageError] = useState(false) const [imageError, setImageError] = useState(false)
@@ -45,6 +42,7 @@ export function StandaloneHotelCardDialog({
publicPrice, publicPrice,
memberPrice, memberPrice,
redemptionPrice, redemptionPrice,
pointsType,
voucherPrice, voucherPrice,
currency, currency,
amenities, amenities,
@@ -183,7 +181,7 @@ export function StandaloneHotelCardDialog({
{redemptionPrice ? ( {redemptionPrice ? (
<HotelPointsRow <HotelPointsRow
pointsPerStay={redemptionPrice} pointsPerStay={redemptionPrice}
pointsCurrency={pointsCurrency} pointsType={pointsType}
/> />
) : null} ) : null}
</div> </div>

View File

@@ -3,34 +3,37 @@ import { useIntl } from "react-intl"
import { RoomPrice } from "../../HotelCard/RoomPrice" import { RoomPrice } from "../../HotelCard/RoomPrice"
import { Typography } from "../../Typography" import { Typography } from "../../Typography"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import styles from "./hotelPointsRow.module.css" import styles from "./hotelPointsRow.module.css"
import { PointType } from "@scandic-hotels/common/constants/pointType"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { getCurrencyText } from "../../currency-utils"
export type PointsRowProps = { export type PointsRowProps = {
pointsPerStay: number pointsPerStay: number
additionalPricePerStay?: number additionalPricePerStay?: number
additionalPriceCurrency?: string additionalPriceCurrency?: string
pointsCurrency?: CurrencyEnum pointsType: PointType | null
} }
export function HotelPointsRow({ export function HotelPointsRow({
pointsPerStay, pointsPerStay,
additionalPricePerStay, additionalPricePerStay,
additionalPriceCurrency, additionalPriceCurrency,
pointsCurrency, pointsType,
}: PointsRowProps) { }: PointsRowProps) {
const intl = useIntl() const intl = useIntl()
const currency = getCurrencyText(
intl,
CurrencyEnum.POINTS,
pointsPerStay,
pointsType
)
return ( return (
<RoomPrice <RoomPrice
className={styles.roomPrice} className={styles.roomPrice}
price={pointsPerStay} price={pointsPerStay}
currency={ currency={currency}
pointsCurrency ??
intl.formatMessage({
id: "common.points",
defaultMessage: "Points",
})
}
includePerNight={false} includePerNight={false}
> >
{additionalPricePerStay ? ( {additionalPricePerStay ? (

View File

@@ -36,6 +36,7 @@ import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
import { BookingCodeChip } from "../BookingCodeChip" import { BookingCodeChip } from "../BookingCodeChip"
import { FakeButton } from "../FakeButton" import { FakeButton } from "../FakeButton"
import { TripAdvisorChip } from "../TripAdvisorChip" import { TripAdvisorChip } from "../TripAdvisorChip"
import { PointType } from "@scandic-hotels/common/constants/pointType"
type Price = { type Price = {
pricePerStay: number pricePerStay: number
@@ -96,6 +97,7 @@ export type HotelCardProps = {
additionalPricePerStay: number additionalPricePerStay: number
pointsPerStay: number pointsPerStay: number
currency: CurrencyEnum | null | undefined currency: CurrencyEnum | null | undefined
pointsType?: PointType | null
} }
}[] }[]
} }
@@ -108,7 +110,6 @@ export type HotelCardProps = {
bookingCode?: string | null bookingCode?: string | null
isAlternative?: boolean isAlternative?: boolean
isPartnerBrand: boolean isPartnerBrand: boolean
pointsCurrency?: CurrencyEnum
fullPrice: boolean fullPrice: boolean
isCampaignWithBookingCode: boolean isCampaignWithBookingCode: boolean
lang: Lang lang: Lang
@@ -133,7 +134,6 @@ export const HotelCardComponent = memo(
bookingCode = "", bookingCode = "",
isAlternative, isAlternative,
isPartnerBrand, isPartnerBrand,
pointsCurrency,
images, images,
lang, lang,
belowInfoSlot, belowInfoSlot,
@@ -358,7 +358,7 @@ export const HotelCardComponent = memo(
additionalPriceCurrency={ additionalPriceCurrency={
redemption.localPrice.currency ?? undefined redemption.localPrice.currency ?? undefined
} }
pointsCurrency={pointsCurrency} pointsType={redemption.localPrice.pointsType ?? null}
/> />
))} ))}
</div> </div>

View File

@@ -7,6 +7,7 @@ import { Typography } from "../../../../Typography"
import HotelMarker from "../../../Markers/HotelMarker" import HotelMarker from "../../../Markers/HotelMarker"
import styles from "./hotelPin.module.css" import styles from "./hotelPin.module.css"
import { PointType } from "@scandic-hotels/common/constants/pointType"
interface HotelPinProps { interface HotelPinProps {
isActive: boolean isActive: boolean
@@ -14,6 +15,7 @@ interface HotelPinProps {
currency: string currency: string
hotelAdditionalPrice?: number hotelAdditionalPrice?: number
hotelAdditionalCurrency?: string hotelAdditionalCurrency?: string
pointsType?: PointType | null
} }
const NOT_AVAILABLE = "-" const NOT_AVAILABLE = "-"
export function HotelPin({ export function HotelPin({
@@ -22,6 +24,7 @@ export function HotelPin({
currency, currency,
hotelAdditionalPrice, hotelAdditionalPrice,
hotelAdditionalCurrency, hotelAdditionalCurrency,
pointsType,
}: HotelPinProps) { }: HotelPinProps) {
const intl = useIntl() const intl = useIntl()
const isNotAvailable = !hotelPrice const isNotAvailable = !hotelPrice
@@ -51,7 +54,8 @@ export function HotelPin({
hotelPrice, hotelPrice,
currency, currency,
hotelAdditionalPrice, hotelAdditionalPrice,
hotelAdditionalCurrency hotelAdditionalCurrency,
pointsType
)} )}
</p> </p>
</Typography> </Typography>

View File

@@ -5,13 +5,13 @@ import {
} from "@vis.gl/react-google-maps" } from "@vis.gl/react-google-maps"
import { useMediaQuery } from "usehooks-ts" import { useMediaQuery } from "usehooks-ts"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { Lang } from "@scandic-hotels/common/constants/language" import { Lang } from "@scandic-hotels/common/constants/language"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { StandaloneHotelCardDialog } from "../../../HotelCard/HotelDialogCard/StandaloneHotelCardDialog" import { StandaloneHotelCardDialog } from "../../../HotelCard/HotelDialogCard/StandaloneHotelCardDialog"
import type { HotelPin as HotelPinType } from "../../types" import type { HotelPin as HotelPinType } from "../../types"
import styles from "./hotelListingMapContent.module.css" import styles from "./hotelListingMapContent.module.css"
import { HotelPin } from "./HotelPin" import { HotelPin } from "./HotelPin"
import { getCurrencyText } from "../../../currency-utils"
export type HotelListingMapContentProps = { export type HotelListingMapContentProps = {
hotelPins: HotelPinType[] hotelPins: HotelPinType[]
@@ -19,7 +19,6 @@ export type HotelListingMapContentProps = {
hoveredHotel?: string | null hoveredHotel?: string | null
lang: Lang lang: Lang
isUserLoggedIn: boolean isUserLoggedIn: boolean
pointsCurrency?: CurrencyEnum
onClickHotel?: (hotelId: string) => void onClickHotel?: (hotelId: string) => void
setActiveHotel?: (args: { hotelName: string; hotelId: string } | null) => void setActiveHotel?: (args: { hotelName: string; hotelId: string } | null) => void
setHoveredHotel?: ( setHoveredHotel?: (
@@ -35,7 +34,6 @@ export function HotelListingMapContent({
setHoveredHotel, setHoveredHotel,
lang, lang,
onClickHotel, onClickHotel,
pointsCurrency,
}: HotelListingMapContentProps) { }: HotelListingMapContentProps) {
const intl = useIntl() const intl = useIntl()
const isDesktop = useMediaQuery("(min-width: 900px)") const isDesktop = useMediaQuery("(min-width: 900px)")
@@ -65,10 +63,12 @@ export function HotelListingMapContent({
null null
const pinCurrency = pin.redemptionPrice const pinCurrency = pin.redemptionPrice
? intl.formatMessage({ ? getCurrencyText(
id: "common.points", intl,
defaultMessage: "Points", pin.currency,
}) pin.redemptionPrice,
pin.pointsType
)
: pin.currency : pin.currency
const hotelAdditionalPrice = pin.chequePrice const hotelAdditionalPrice = pin.chequePrice
@@ -116,7 +116,6 @@ export function HotelListingMapContent({
onClick={() => { onClick={() => {
onClickHotel?.(pin.operaId) onClickHotel?.(pin.operaId)
}} }}
pointsCurrency={pointsCurrency}
/> />
</InfoWindow> </InfoWindow>
)} )}

View File

@@ -15,7 +15,6 @@ import PoiMapMarkers from "./PoiMapMarkers"
import styles from "./interactiveMap.module.css" import styles from "./interactiveMap.module.css"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { Lang } from "@scandic-hotels/common/constants/language" import { Lang } from "@scandic-hotels/common/constants/language"
import { HotelPin, MarkerInfo, PointOfInterest } from "../types" import { HotelPin, MarkerInfo, PointOfInterest } from "../types"
@@ -27,7 +26,6 @@ export type InteractiveMapProps = {
} }
activePoi?: string | null activePoi?: string | null
hotelPins?: HotelPin[] hotelPins?: HotelPin[]
pointsCurrency?: CurrencyEnum
pointsOfInterest?: PointOfInterest[] pointsOfInterest?: PointOfInterest[]
markerInfo?: MarkerInfo markerInfo?: MarkerInfo
mapId: string mapId: string
@@ -74,7 +72,6 @@ export function InteractiveMap({
hoveredHotelPin, hoveredHotelPin,
activeHotelPin, activeHotelPin,
isUserLoggedIn, isUserLoggedIn,
pointsCurrency,
onClickHotel, onClickHotel,
onHoverHotelPin, onHoverHotelPin,
onSetActiveHotelPin, onSetActiveHotelPin,
@@ -124,7 +121,6 @@ export function InteractiveMap({
activeHotel={activeHotelPin} activeHotel={activeHotelPin}
hoveredHotel={hoveredHotelPin} hoveredHotel={hoveredHotelPin}
onClickHotel={onClickHotel} onClickHotel={onClickHotel}
pointsCurrency={pointsCurrency}
/> />
)} )}
{pointsOfInterest && markerInfo && ( {pointsOfInterest && markerInfo && (

View File

@@ -1,5 +1,6 @@
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { HotelPin } from "../types" import { HotelPin } from "../types"
import { PointType } from "@scandic-hotels/common/constants/pointType"
export const hotelPins: HotelPin[] = [ export const hotelPins: HotelPin[] = [
{ {
@@ -15,6 +16,7 @@ export const hotelPins: HotelPin[] = [
voucherPrice: null, voucherPrice: null,
rateType: "Regular", rateType: "Regular",
currency: "SEK", currency: "SEK",
pointsType: PointType.SCANDIC,
amenities: [ amenities: [
{ {
filter: "Hotel facilities", filter: "Hotel facilities",
@@ -90,6 +92,7 @@ export const hotelPins: HotelPin[] = [
voucherPrice: null, voucherPrice: null,
rateType: "Regular", rateType: "Regular",
currency: "SEK", currency: "SEK",
pointsType: PointType.SCANDIC,
amenities: [ amenities: [
{ {
filter: "Hotel facilities", filter: "Hotel facilities",
@@ -168,6 +171,7 @@ export const hotelPins: HotelPin[] = [
voucherPrice: null, voucherPrice: null,
rateType: "Regular", rateType: "Regular",
currency: "CC", currency: "CC",
pointsType: PointType.SCANDIC,
amenities: [ amenities: [
{ {
filter: "Hotel facilities", filter: "Hotel facilities",
@@ -242,6 +246,7 @@ export const hotelPins: HotelPin[] = [
voucherPrice: null, voucherPrice: null,
rateType: "Regular", rateType: "Regular",
currency: "Points", currency: "Points",
pointsType: PointType.SCANDIC,
amenities: [ amenities: [
{ {
filter: "None", filter: "None",
@@ -316,6 +321,7 @@ export const hotelPins: HotelPin[] = [
voucherPrice: 1, voucherPrice: 1,
rateType: "Regular", rateType: "Regular",
currency: "Voucher", currency: "Voucher",
pointsType: PointType.SCANDIC,
amenities: [ amenities: [
{ {
filter: "Hotel facilities", filter: "Hotel facilities",

View File

@@ -1,6 +1,7 @@
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { FacilityEnum } from "@scandic-hotels/common/constants/facilities" import { FacilityEnum } from "@scandic-hotels/common/constants/facilities"
import { HotelType } from "@scandic-hotels/common/constants/hotelType" import { HotelType } from "@scandic-hotels/common/constants/hotelType"
import { PointType } from "@scandic-hotels/common/constants/pointType"
export type HotelPin = { export type HotelPin = {
bookingCode?: string | null bookingCode?: string | null
@@ -17,6 +18,7 @@ export type HotelPin = {
publicPrice: number | null publicPrice: number | null
memberPrice: number | null memberPrice: number | null
redemptionPrice: number | null redemptionPrice: number | null
pointsType: PointType | null
voucherPrice: number | null voucherPrice: number | null
rateType: string | null rateType: string | null
currency: string currency: string

View File

@@ -1,6 +1,8 @@
import type { Meta, StoryObj } from "@storybook/nextjs-vite" import type { Meta, StoryObj } from "@storybook/nextjs-vite"
import PointsRateCard from "." import PointsRateCard from "."
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { PointType } from "@scandic-hotels/common/constants/pointType"
const meta: Meta<typeof PointsRateCard> = { const meta: Meta<typeof PointsRateCard> = {
title: "Product Components/RateCard/Points", title: "Product Components/RateCard/Points",
@@ -36,25 +38,25 @@ export const Default: Story = {
bannerText: "Reward night ∙ Breakfast included", bannerText: "Reward night ∙ Breakfast included",
rates: [ rates: [
{ {
points: "20000", points: 20000,
currency: "PTS", currency: CurrencyEnum.POINTS,
rateCode: "REDNIGHT7", rateCode: "REDNIGHT7",
}, },
{ {
points: "15000", points: 15000,
currency: "PTS", currency: CurrencyEnum.POINTS,
additionalPrice: { additionalPrice: {
price: "250", price: "250",
currency: "EUR", currency: CurrencyEnum.EUR,
}, },
rateCode: "REDNIGHT7A", rateCode: "REDNIGHT7A",
}, },
{ {
points: "10000", points: 10000,
currency: "PTS", currency: CurrencyEnum.POINTS,
additionalPrice: { additionalPrice: {
price: "500", price: "500",
currency: "EUR", currency: CurrencyEnum.EUR,
}, },
rateCode: "REDNIGHT7B", rateCode: "REDNIGHT7B",
}, },
@@ -77,27 +79,27 @@ export const WithDisabledRates: Story = {
bannerText: "Reward night ∙ Breakfast included", bannerText: "Reward night ∙ Breakfast included",
rates: [ rates: [
{ {
points: "20000", points: 20000,
currency: "PTS", currency: CurrencyEnum.POINTS,
isDisabled: true, isDisabled: true,
rateCode: "REDNIGHT7", rateCode: "REDNIGHT7",
}, },
{ {
points: "15000", points: 15000,
currency: "PTS", currency: CurrencyEnum.POINTS,
isDisabled: true, isDisabled: true,
additionalPrice: { additionalPrice: {
price: "250", price: "250",
currency: "EUR", currency: CurrencyEnum.EUR,
}, },
rateCode: "REDNIGHT7A", rateCode: "REDNIGHT7A",
}, },
{ {
points: "10000", points: 10000,
currency: "PTS", currency: CurrencyEnum.POINTS,
additionalPrice: { additionalPrice: {
price: "500", price: "500",
currency: "EUR", currency: CurrencyEnum.EUR,
}, },
rateCode: "REDNIGHT7B", rateCode: "REDNIGHT7B",
}, },
@@ -120,25 +122,25 @@ export const NotEnoughPoints: Story = {
bannerText: "Reward night ∙ Breakfast included", bannerText: "Reward night ∙ Breakfast included",
rates: [ rates: [
{ {
points: "20000", points: 20000,
currency: "PTS", currency: CurrencyEnum.POINTS,
rateCode: "REDNIGHT7", rateCode: "REDNIGHT7",
}, },
{ {
points: "15000", points: 15000,
currency: "PTS", currency: CurrencyEnum.POINTS,
additionalPrice: { additionalPrice: {
price: "250", price: "250",
currency: "EUR", currency: CurrencyEnum.EUR,
}, },
rateCode: "REDNIGHT7A", rateCode: "REDNIGHT7A",
}, },
{ {
points: "10000", points: 10000,
currency: "PTS", currency: CurrencyEnum.POINTS,
additionalPrice: { additionalPrice: {
price: "500", price: "500",
currency: "EUR", currency: CurrencyEnum.EUR,
}, },
rateCode: "REDNIGHT7B", rateCode: "REDNIGHT7B",
}, },
@@ -155,3 +157,47 @@ export const NotEnoughPoints: Story = {
], ],
}, },
} }
export const WithEuroBonusPoints: Story = {
args: {
rateTitle: "FREE CANCELLATION",
paymentTerm: "PAY LATER",
bannerText: "Reward night ∙ Breakfast included",
rates: [
{
points: 20000,
currency: CurrencyEnum.POINTS,
rateCode: "REDNIGHT7",
pointsType: PointType.EUROBONUS,
},
{
points: 15000,
currency: CurrencyEnum.POINTS,
additionalPrice: {
price: "250",
currency: CurrencyEnum.EUR,
},
rateCode: "REDNIGHT7A",
pointsType: PointType.EUROBONUS,
},
{
points: 10000,
currency: CurrencyEnum.POINTS,
additionalPrice: {
price: "500",
currency: CurrencyEnum.EUR,
},
rateCode: "REDNIGHT7B",
pointsType: PointType.EUROBONUS,
},
],
selectedRate: undefined,
onRateSelect: (value) => console.log(value),
rateTermDetails: [
{
title: "Rate definition 1",
terms: ["term 1", "term 2", "term 3"],
},
],
},
}

View File

@@ -10,6 +10,7 @@ import Modal from "../Modal"
import styles from "../rate-card.module.css" import styles from "../rate-card.module.css"
import { variants } from "../variants" import { variants } from "../variants"
import { MaterialIcon } from "../../Icons/MaterialIcon" import { MaterialIcon } from "../../Icons/MaterialIcon"
import { getCurrencyText } from "../../currency-utils"
interface PointsRateCardProps { interface PointsRateCardProps {
rateTitle: string rateTitle: string
@@ -120,7 +121,7 @@ export default function PointsRateCard({
<Typography variant="Body/Supporting text (caption)/smBold"> <Typography variant="Body/Supporting text (caption)/smBold">
<span> <span>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{`${rate.currency} ${rate.additionalPrice ? " + " : ""}`} {`${getCurrencyText(intl, rate.currency, rate.points, rate.pointsType)} ${rate.additionalPrice ? " + " : ""}`}
</span> </span>
</Typography> </Typography>
</p> </p>

View File

@@ -1,3 +1,5 @@
import { PointType } from "@scandic-hotels/common/constants/pointType"
export type Rate = { export type Rate = {
label?: string label?: string
price: string price: string
@@ -6,7 +8,8 @@ export type Rate = {
export type RatePointsOption = { export type RatePointsOption = {
rateCode: string rateCode: string
points: string points: number
pointsType?: PointType | null
currency: string currency: string
isDisabled?: boolean isDisabled?: boolean
additionalPrice?: AdditionalPrice additionalPrice?: AdditionalPrice

View File

@@ -0,0 +1,47 @@
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { PointType } from "@scandic-hotels/common/constants/pointType"
import { logger } from "@scandic-hotels/common/logger"
import { IntlShape } from "react-intl"
export function getCurrencyText(
intl: IntlShape,
currency: string,
price: number,
pointsType?: PointType | null
) {
if (currency !== CurrencyEnum.POINTS) return currency
if (!pointsType) return currency
switch (pointsType) {
case PointType.SCANDIC: {
return intl.formatMessage(
{
id: "price.numberOfScandicPoints",
defaultMessage:
"{numberOfScandicPoints, plural, one {Point} other {Points}}",
},
{
numberOfScandicPoints: price,
}
)
}
case PointType.EUROBONUS: {
return intl.formatMessage(
{
id: "price.numberOfEuroBonusPoints",
defaultMessage:
"{numberOfEuroBonusPoints, plural, one {EB Point} other {EB Points}}",
},
{
numberOfEuroBonusPoints: price,
}
)
}
default: {
const _exhaustiveCheck: never = pointsType
void _exhaustiveCheck
logger.warn(`Unknown point type provided: ${pointsType}`)
return currency
}
}
}

View File

@@ -176,7 +176,11 @@ export const bookingConfirmationSchema = z
rateDefinition: rateDefinitionSchema, rateDefinition: rateDefinitionSchema,
reservationStatus: z.string().nullable().default(""), reservationStatus: z.string().nullable().default(""),
roomPoints: z.number(), roomPoints: z.number(),
roomPointType: z.nullable(z.enum(["Scandic", "EuroBonus"])).catch(null), roomPointType: z
.enum(["Scandic", "EuroBonus"])
.nullable()
.default(null)
.catch(null),
roomPrice: z.number(), roomPrice: z.number(),
roomTypeCode: z.string().default(""), roomTypeCode: z.string().default(""),
totalPoints: z.number(), totalPoints: z.number(),

View File

@@ -1,6 +1,10 @@
import { z } from "zod" import { z } from "zod"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import {
PointType,
pointTypes,
} from "@scandic-hotels/common/constants/pointType"
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType" import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
import { nullableNumberValidator } from "@scandic-hotels/common/utils/zod/numberValidator" import { nullableNumberValidator } from "@scandic-hotels/common/utils/zod/numberValidator"
import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/stringValidator" import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/stringValidator"
@@ -22,6 +26,10 @@ export const redemptionSchema = z.object({
currency: z.nativeEnum(CurrencyEnum).nullish(), currency: z.nativeEnum(CurrencyEnum).nullish(),
pointsPerNight: nullableNumberValidator, pointsPerNight: nullableNumberValidator,
pointsPerStay: nullableNumberValidator, pointsPerStay: nullableNumberValidator,
pointsType: z
.enum(pointTypes as [PointType, ...PointType[]])
.nullish()
.catch(PointType.SCANDIC),
}) })
export const priceSchema = z.object({ export const priceSchema = z.object({

View File

@@ -79,6 +79,7 @@
"@types/react": "19.2.7", "@types/react": "19.2.7",
"@typescript-eslint/eslint-plugin": "^8.32.0", "@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0", "@typescript-eslint/parser": "^8.32.0",
"@typescript/native-preview": "^7.0.0-dev.20251104.1",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"eslint": "^9", "eslint": "^9",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",

View File

@@ -5363,6 +5363,7 @@ __metadata:
"@types/react": "npm:19.2.7" "@types/react": "npm:19.2.7"
"@typescript-eslint/eslint-plugin": "npm:^8.32.0" "@typescript-eslint/eslint-plugin": "npm:^8.32.0"
"@typescript-eslint/parser": "npm:^8.32.0" "@typescript-eslint/parser": "npm:^8.32.0"
"@typescript/native-preview": "npm:^7.0.0-dev.20251104.1"
dayjs: "npm:^1.11.13" dayjs: "npm:^1.11.13"
deepmerge: "npm:^4.3.1" deepmerge: "npm:^4.3.1"
dotenv: "npm:^16.5.0" dotenv: "npm:^16.5.0"