Files
web/packages/design-system/lib/components/HotelCard/HotelDialogCard/StandaloneHotelCardDialog/index.tsx
Anton Gunnarsson 16fbdb7ae0 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
2026-01-15 09:32:17 +00:00

216 lines
6.8 KiB
TypeScript

"use client"
import { useState } from "react"
import { useIntl } from "react-intl"
import { IconButton } from "../../../IconButton"
import { Typography } from "../../../Typography"
import { HotelCardDialogImage } from "../../HotelCardDialogImage"
import { NoPriceAvailableCard } from "../../NoPriceAvailableCard"
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"
import ButtonLink from "../../../ButtonLink"
import { FacilityToIcon } from "../../../FacilityToIcon"
import { HotelPin } from "../../../Map/types"
import { HotelPointsRow } from "../../HotelPointsRow"
import { RoomPrice } from "../../RoomPrice"
import styles from "./standaloneHotelCardDialog.module.css"
interface StandaloneHotelCardProps {
lang: Lang
data: HotelPin
isUserLoggedIn: boolean
handleClose: () => void
onClick?: () => void
}
export function StandaloneHotelCardDialog({
data,
lang,
handleClose,
isUserLoggedIn,
onClick,
}: StandaloneHotelCardProps) {
const intl = useIntl()
const [imageError, setImageError] = useState(false)
const {
name,
chequePrice,
publicPrice,
memberPrice,
redemptionPrice,
pointsType,
voucherPrice,
currency,
amenities,
image,
ratings,
operaId,
hasEnoughPoints,
} = data
const notEnoughPointsLabel = intl.formatMessage({
id: "booking.notEnoughPoints",
defaultMessage: "Not enough points",
})
const shouldShowNotEnoughPoints = redemptionPrice && !hasEnoughPoints
const selectRateUrl = useUrlWithSearchParam(
{ key: "hotel", value: operaId },
selectRate(lang)
)
const showPriceCard = !!(
publicPrice ||
memberPrice ||
redemptionPrice ||
voucherPrice ||
chequePrice
)
return (
<div className={styles.container}>
<IconButton
variant="Muted"
emphasis
className={styles.closeButton}
onPress={handleClose}
aria-label={intl.formatMessage({
id: "common.close",
defaultMessage: "Close",
})}
iconName="close"
/>
<HotelCardDialogImage
imageSrc={image?.url}
altText={image?.alt}
rating={{ tripAdvisor: ratings?.tripAdvisor ?? null }}
imageError={imageError}
setImageError={setImageError}
position="left"
/>
<div className={styles.content}>
<Typography variant="Body/Paragraph/mdBold">
<h4 className={styles.name}>{name}</h4>
</Typography>
<div className={styles.facilities}>
{amenities.slice(0, 3).map((facility) => {
const Icon = (
<FacilityToIcon id={facility.id} size={16} color="Icon/Default" />
)
return (
<div className={styles.facilitiesItem} key={facility.id}>
{Icon && Icon}
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>{facility.name}</span>
</Typography>
</div>
)
})}
</div>
{showPriceCard ? (
<>
<div className={styles.priceCard}>
<Typography variant="Body/Supporting text (caption)/smBold">
<p>
{redemptionPrice
? intl.formatMessage({
id: "hotelCard.availableRates",
defaultMessage: "Available rates",
})
: intl.formatMessage({
id: "common.from",
defaultMessage: "From",
})}
</p>
</Typography>
{chequePrice ? (
<RoomPrice
price={chequePrice.numberOfCheques}
currency="CC"
includePerNight={false}
>
{chequePrice.additionalPricePerStay > 0 ? (
<>
<Typography variant="Body/Paragraph/mdBold">
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span> + </span>
</Typography>
<Typography variant="Title/Subtitle/md">
<span>{chequePrice.additionalPricePerStay}</span>
</Typography>
<Typography variant="Body/Paragraph/mdBold">
<span> {chequePrice.currency}</span>
</Typography>
</>
) : null}
</RoomPrice>
) : null}
{voucherPrice ? (
<RoomPrice
price={voucherPrice}
currency={intl.formatMessage(
{
id: "price.numberOfVouchers",
defaultMessage:
"{numberOfVouchers, plural, one {Voucher} other {Vouchers}}",
},
{
numberOfVouchers: voucherPrice,
}
)}
includePerNight={false}
/>
) : null}
{/* Show public price if:
1) user is not logged in, or
2) user is logged in but no member price is available (use of booking codes)
*/}
{publicPrice && (!isUserLoggedIn || !memberPrice) ? (
<RoomPrice price={publicPrice} currency={currency} />
) : null}
{memberPrice ? (
<RoomPrice
className={styles.memberPrice}
price={memberPrice}
currency={currency}
/>
) : null}
{redemptionPrice ? (
<HotelPointsRow
pointsPerStay={redemptionPrice}
pointsType={pointsType}
/>
) : null}
</div>
{shouldShowNotEnoughPoints ? (
<Typography variant="Body/Paragraph/mdBold">
<div className={styles.notEnoughPointsButton}>
{notEnoughPointsLabel}
</div>
</Typography>
) : (
<ButtonLink
href={selectRateUrl}
variant="Primary"
color="Primary"
size="sm"
onClick={onClick}
>
{intl.formatMessage({
id: "common.seeRooms",
defaultMessage: "See rooms",
})}
</ButtonLink>
)}
</>
) : (
<NoPriceAvailableCard />
)}
</div>
</div>
)
}