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:
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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={
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
7
packages/common/constants/pointType.ts
Normal file
7
packages/common/constants/pointType.ts
Normal 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]
|
||||||
@@ -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",
|
||||||
|
|||||||
200
packages/common/utils/numberFormatting.test.ts
Normal file
200
packages/common/utils/numberFormatting.test.ts
Normal 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")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
47
packages/design-system/lib/components/currency-utils.ts
Normal file
47
packages/design-system/lib/components/currency-utils.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user