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:
@@ -51,6 +51,14 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"import/no-relative-packages": "error",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Typography } from "../../../Typography"
|
||||
import { HotelCardDialogImage } from "../../HotelCardDialogImage"
|
||||
import { NoPriceAvailableCard } from "../../NoPriceAvailableCard"
|
||||
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { selectRate } from "@scandic-hotels/common/constants/routes/hotelReservation"
|
||||
import { useUrlWithSearchParam } from "@scandic-hotels/common/hooks/useUrlWithSearchParam"
|
||||
@@ -26,7 +25,6 @@ interface StandaloneHotelCardProps {
|
||||
isUserLoggedIn: boolean
|
||||
handleClose: () => void
|
||||
onClick?: () => void
|
||||
pointsCurrency?: CurrencyEnum
|
||||
}
|
||||
|
||||
export function StandaloneHotelCardDialog({
|
||||
@@ -35,7 +33,6 @@ export function StandaloneHotelCardDialog({
|
||||
handleClose,
|
||||
isUserLoggedIn,
|
||||
onClick,
|
||||
pointsCurrency,
|
||||
}: StandaloneHotelCardProps) {
|
||||
const intl = useIntl()
|
||||
const [imageError, setImageError] = useState(false)
|
||||
@@ -45,6 +42,7 @@ export function StandaloneHotelCardDialog({
|
||||
publicPrice,
|
||||
memberPrice,
|
||||
redemptionPrice,
|
||||
pointsType,
|
||||
voucherPrice,
|
||||
currency,
|
||||
amenities,
|
||||
@@ -183,7 +181,7 @@ export function StandaloneHotelCardDialog({
|
||||
{redemptionPrice ? (
|
||||
<HotelPointsRow
|
||||
pointsPerStay={redemptionPrice}
|
||||
pointsCurrency={pointsCurrency}
|
||||
pointsType={pointsType}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -3,34 +3,37 @@ import { useIntl } from "react-intl"
|
||||
import { RoomPrice } from "../../HotelCard/RoomPrice"
|
||||
import { Typography } from "../../Typography"
|
||||
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
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 = {
|
||||
pointsPerStay: number
|
||||
additionalPricePerStay?: number
|
||||
additionalPriceCurrency?: string
|
||||
pointsCurrency?: CurrencyEnum
|
||||
pointsType: PointType | null
|
||||
}
|
||||
export function HotelPointsRow({
|
||||
pointsPerStay,
|
||||
additionalPricePerStay,
|
||||
additionalPriceCurrency,
|
||||
pointsCurrency,
|
||||
pointsType,
|
||||
}: PointsRowProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
const currency = getCurrencyText(
|
||||
intl,
|
||||
CurrencyEnum.POINTS,
|
||||
pointsPerStay,
|
||||
pointsType
|
||||
)
|
||||
|
||||
return (
|
||||
<RoomPrice
|
||||
className={styles.roomPrice}
|
||||
price={pointsPerStay}
|
||||
currency={
|
||||
pointsCurrency ??
|
||||
intl.formatMessage({
|
||||
id: "common.points",
|
||||
defaultMessage: "Points",
|
||||
})
|
||||
}
|
||||
currency={currency}
|
||||
includePerNight={false}
|
||||
>
|
||||
{additionalPricePerStay ? (
|
||||
|
||||
@@ -36,6 +36,7 @@ import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
|
||||
import { BookingCodeChip } from "../BookingCodeChip"
|
||||
import { FakeButton } from "../FakeButton"
|
||||
import { TripAdvisorChip } from "../TripAdvisorChip"
|
||||
import { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
|
||||
type Price = {
|
||||
pricePerStay: number
|
||||
@@ -96,6 +97,7 @@ export type HotelCardProps = {
|
||||
additionalPricePerStay: number
|
||||
pointsPerStay: number
|
||||
currency: CurrencyEnum | null | undefined
|
||||
pointsType?: PointType | null
|
||||
}
|
||||
}[]
|
||||
}
|
||||
@@ -108,7 +110,6 @@ export type HotelCardProps = {
|
||||
bookingCode?: string | null
|
||||
isAlternative?: boolean
|
||||
isPartnerBrand: boolean
|
||||
pointsCurrency?: CurrencyEnum
|
||||
fullPrice: boolean
|
||||
isCampaignWithBookingCode: boolean
|
||||
lang: Lang
|
||||
@@ -133,7 +134,6 @@ export const HotelCardComponent = memo(
|
||||
bookingCode = "",
|
||||
isAlternative,
|
||||
isPartnerBrand,
|
||||
pointsCurrency,
|
||||
images,
|
||||
lang,
|
||||
belowInfoSlot,
|
||||
@@ -358,7 +358,7 @@ export const HotelCardComponent = memo(
|
||||
additionalPriceCurrency={
|
||||
redemption.localPrice.currency ?? undefined
|
||||
}
|
||||
pointsCurrency={pointsCurrency}
|
||||
pointsType={redemption.localPrice.pointsType ?? null}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Typography } from "../../../../Typography"
|
||||
import HotelMarker from "../../../Markers/HotelMarker"
|
||||
|
||||
import styles from "./hotelPin.module.css"
|
||||
import { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
|
||||
interface HotelPinProps {
|
||||
isActive: boolean
|
||||
@@ -14,6 +15,7 @@ interface HotelPinProps {
|
||||
currency: string
|
||||
hotelAdditionalPrice?: number
|
||||
hotelAdditionalCurrency?: string
|
||||
pointsType?: PointType | null
|
||||
}
|
||||
const NOT_AVAILABLE = "-"
|
||||
export function HotelPin({
|
||||
@@ -22,6 +24,7 @@ export function HotelPin({
|
||||
currency,
|
||||
hotelAdditionalPrice,
|
||||
hotelAdditionalCurrency,
|
||||
pointsType,
|
||||
}: HotelPinProps) {
|
||||
const intl = useIntl()
|
||||
const isNotAvailable = !hotelPrice
|
||||
@@ -51,7 +54,8 @@ export function HotelPin({
|
||||
hotelPrice,
|
||||
currency,
|
||||
hotelAdditionalPrice,
|
||||
hotelAdditionalCurrency
|
||||
hotelAdditionalCurrency,
|
||||
pointsType
|
||||
)}
|
||||
</p>
|
||||
</Typography>
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
} from "@vis.gl/react-google-maps"
|
||||
import { useMediaQuery } from "usehooks-ts"
|
||||
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { useIntl } from "react-intl"
|
||||
import { StandaloneHotelCardDialog } from "../../../HotelCard/HotelDialogCard/StandaloneHotelCardDialog"
|
||||
import type { HotelPin as HotelPinType } from "../../types"
|
||||
import styles from "./hotelListingMapContent.module.css"
|
||||
import { HotelPin } from "./HotelPin"
|
||||
import { getCurrencyText } from "../../../currency-utils"
|
||||
|
||||
export type HotelListingMapContentProps = {
|
||||
hotelPins: HotelPinType[]
|
||||
@@ -19,7 +19,6 @@ export type HotelListingMapContentProps = {
|
||||
hoveredHotel?: string | null
|
||||
lang: Lang
|
||||
isUserLoggedIn: boolean
|
||||
pointsCurrency?: CurrencyEnum
|
||||
onClickHotel?: (hotelId: string) => void
|
||||
setActiveHotel?: (args: { hotelName: string; hotelId: string } | null) => void
|
||||
setHoveredHotel?: (
|
||||
@@ -35,7 +34,6 @@ export function HotelListingMapContent({
|
||||
setHoveredHotel,
|
||||
lang,
|
||||
onClickHotel,
|
||||
pointsCurrency,
|
||||
}: HotelListingMapContentProps) {
|
||||
const intl = useIntl()
|
||||
const isDesktop = useMediaQuery("(min-width: 900px)")
|
||||
@@ -65,10 +63,12 @@ export function HotelListingMapContent({
|
||||
null
|
||||
|
||||
const pinCurrency = pin.redemptionPrice
|
||||
? intl.formatMessage({
|
||||
id: "common.points",
|
||||
defaultMessage: "Points",
|
||||
})
|
||||
? getCurrencyText(
|
||||
intl,
|
||||
pin.currency,
|
||||
pin.redemptionPrice,
|
||||
pin.pointsType
|
||||
)
|
||||
: pin.currency
|
||||
|
||||
const hotelAdditionalPrice = pin.chequePrice
|
||||
@@ -116,7 +116,6 @@ export function HotelListingMapContent({
|
||||
onClick={() => {
|
||||
onClickHotel?.(pin.operaId)
|
||||
}}
|
||||
pointsCurrency={pointsCurrency}
|
||||
/>
|
||||
</InfoWindow>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,6 @@ import PoiMapMarkers from "./PoiMapMarkers"
|
||||
|
||||
import styles from "./interactiveMap.module.css"
|
||||
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import { HotelPin, MarkerInfo, PointOfInterest } from "../types"
|
||||
|
||||
@@ -27,7 +26,6 @@ export type InteractiveMapProps = {
|
||||
}
|
||||
activePoi?: string | null
|
||||
hotelPins?: HotelPin[]
|
||||
pointsCurrency?: CurrencyEnum
|
||||
pointsOfInterest?: PointOfInterest[]
|
||||
markerInfo?: MarkerInfo
|
||||
mapId: string
|
||||
@@ -74,7 +72,6 @@ export function InteractiveMap({
|
||||
hoveredHotelPin,
|
||||
activeHotelPin,
|
||||
isUserLoggedIn,
|
||||
pointsCurrency,
|
||||
onClickHotel,
|
||||
onHoverHotelPin,
|
||||
onSetActiveHotelPin,
|
||||
@@ -124,7 +121,6 @@ export function InteractiveMap({
|
||||
activeHotel={activeHotelPin}
|
||||
hoveredHotel={hoveredHotelPin}
|
||||
onClickHotel={onClickHotel}
|
||||
pointsCurrency={pointsCurrency}
|
||||
/>
|
||||
)}
|
||||
{pointsOfInterest && markerInfo && (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import { HotelPin } from "../types"
|
||||
import { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
|
||||
export const hotelPins: HotelPin[] = [
|
||||
{
|
||||
@@ -15,6 +16,7 @@ export const hotelPins: HotelPin[] = [
|
||||
voucherPrice: null,
|
||||
rateType: "Regular",
|
||||
currency: "SEK",
|
||||
pointsType: PointType.SCANDIC,
|
||||
amenities: [
|
||||
{
|
||||
filter: "Hotel facilities",
|
||||
@@ -90,6 +92,7 @@ export const hotelPins: HotelPin[] = [
|
||||
voucherPrice: null,
|
||||
rateType: "Regular",
|
||||
currency: "SEK",
|
||||
pointsType: PointType.SCANDIC,
|
||||
amenities: [
|
||||
{
|
||||
filter: "Hotel facilities",
|
||||
@@ -168,6 +171,7 @@ export const hotelPins: HotelPin[] = [
|
||||
voucherPrice: null,
|
||||
rateType: "Regular",
|
||||
currency: "CC",
|
||||
pointsType: PointType.SCANDIC,
|
||||
amenities: [
|
||||
{
|
||||
filter: "Hotel facilities",
|
||||
@@ -242,6 +246,7 @@ export const hotelPins: HotelPin[] = [
|
||||
voucherPrice: null,
|
||||
rateType: "Regular",
|
||||
currency: "Points",
|
||||
pointsType: PointType.SCANDIC,
|
||||
amenities: [
|
||||
{
|
||||
filter: "None",
|
||||
@@ -316,6 +321,7 @@ export const hotelPins: HotelPin[] = [
|
||||
voucherPrice: 1,
|
||||
rateType: "Regular",
|
||||
currency: "Voucher",
|
||||
pointsType: PointType.SCANDIC,
|
||||
amenities: [
|
||||
{
|
||||
filter: "Hotel facilities",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import { FacilityEnum } from "@scandic-hotels/common/constants/facilities"
|
||||
import { HotelType } from "@scandic-hotels/common/constants/hotelType"
|
||||
import { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
|
||||
export type HotelPin = {
|
||||
bookingCode?: string | null
|
||||
@@ -17,6 +18,7 @@ export type HotelPin = {
|
||||
publicPrice: number | null
|
||||
memberPrice: number | null
|
||||
redemptionPrice: number | null
|
||||
pointsType: PointType | null
|
||||
voucherPrice: number | null
|
||||
rateType: string | null
|
||||
currency: string
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs-vite"
|
||||
|
||||
import PointsRateCard from "."
|
||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
|
||||
const meta: Meta<typeof PointsRateCard> = {
|
||||
title: "Product Components/RateCard/Points",
|
||||
@@ -36,25 +38,25 @@ export const Default: Story = {
|
||||
bannerText: "Reward night ∙ Breakfast included",
|
||||
rates: [
|
||||
{
|
||||
points: "20000",
|
||||
currency: "PTS",
|
||||
points: 20000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
rateCode: "REDNIGHT7",
|
||||
},
|
||||
{
|
||||
points: "15000",
|
||||
currency: "PTS",
|
||||
points: 15000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
additionalPrice: {
|
||||
price: "250",
|
||||
currency: "EUR",
|
||||
currency: CurrencyEnum.EUR,
|
||||
},
|
||||
rateCode: "REDNIGHT7A",
|
||||
},
|
||||
{
|
||||
points: "10000",
|
||||
currency: "PTS",
|
||||
points: 10000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
additionalPrice: {
|
||||
price: "500",
|
||||
currency: "EUR",
|
||||
currency: CurrencyEnum.EUR,
|
||||
},
|
||||
rateCode: "REDNIGHT7B",
|
||||
},
|
||||
@@ -77,27 +79,27 @@ export const WithDisabledRates: Story = {
|
||||
bannerText: "Reward night ∙ Breakfast included",
|
||||
rates: [
|
||||
{
|
||||
points: "20000",
|
||||
currency: "PTS",
|
||||
points: 20000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
isDisabled: true,
|
||||
rateCode: "REDNIGHT7",
|
||||
},
|
||||
{
|
||||
points: "15000",
|
||||
currency: "PTS",
|
||||
points: 15000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
isDisabled: true,
|
||||
additionalPrice: {
|
||||
price: "250",
|
||||
currency: "EUR",
|
||||
currency: CurrencyEnum.EUR,
|
||||
},
|
||||
rateCode: "REDNIGHT7A",
|
||||
},
|
||||
{
|
||||
points: "10000",
|
||||
currency: "PTS",
|
||||
points: 10000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
additionalPrice: {
|
||||
price: "500",
|
||||
currency: "EUR",
|
||||
currency: CurrencyEnum.EUR,
|
||||
},
|
||||
rateCode: "REDNIGHT7B",
|
||||
},
|
||||
@@ -120,25 +122,25 @@ export const NotEnoughPoints: Story = {
|
||||
bannerText: "Reward night ∙ Breakfast included",
|
||||
rates: [
|
||||
{
|
||||
points: "20000",
|
||||
currency: "PTS",
|
||||
points: 20000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
rateCode: "REDNIGHT7",
|
||||
},
|
||||
{
|
||||
points: "15000",
|
||||
currency: "PTS",
|
||||
points: 15000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
additionalPrice: {
|
||||
price: "250",
|
||||
currency: "EUR",
|
||||
currency: CurrencyEnum.EUR,
|
||||
},
|
||||
rateCode: "REDNIGHT7A",
|
||||
},
|
||||
{
|
||||
points: "10000",
|
||||
currency: "PTS",
|
||||
points: 10000,
|
||||
currency: CurrencyEnum.POINTS,
|
||||
additionalPrice: {
|
||||
price: "500",
|
||||
currency: "EUR",
|
||||
currency: CurrencyEnum.EUR,
|
||||
},
|
||||
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 { variants } from "../variants"
|
||||
import { MaterialIcon } from "../../Icons/MaterialIcon"
|
||||
import { getCurrencyText } from "../../currency-utils"
|
||||
|
||||
interface PointsRateCardProps {
|
||||
rateTitle: string
|
||||
@@ -120,7 +121,7 @@ export default function PointsRateCard({
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>
|
||||
{/* 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>
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { PointType } from "@scandic-hotels/common/constants/pointType"
|
||||
|
||||
export type Rate = {
|
||||
label?: string
|
||||
price: string
|
||||
@@ -6,7 +8,8 @@ export type Rate = {
|
||||
|
||||
export type RatePointsOption = {
|
||||
rateCode: string
|
||||
points: string
|
||||
points: number
|
||||
pointsType?: PointType | null
|
||||
currency: string
|
||||
isDisabled?: boolean
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user