fix(BOOK-418): Refactored StandaloneHotelCardDialog and fixed pricing issue when using redemption or booking codes

Approved-by: Bianca Widstam
This commit is contained in:
Erik Tiekstra
2025-10-20 10:40:38 +00:00
parent 710309b7eb
commit 3e3a7fc423
12 changed files with 605 additions and 412 deletions

View File

@@ -3,30 +3,27 @@
import { useState } from 'react'
import { useIntl } from 'react-intl'
import { selectRate } from '@scandic-hotels/common/constants/routes/hotelReservation'
import Body from '../../../Body'
import Caption from '../../../Caption'
import Footnote from '../../../Footnote'
import { IconButton } from '../../../IconButton'
import { MaterialIcon } from '../../../Icons/MaterialIcon'
import Link from '../../../Link'
import { OldDSButton as Button } from '../../../OldDSButton'
import Subtitle from '../../../Subtitle'
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'
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'
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
interface StandaloneHotelCardProps {
data: HotelPin
lang: Lang
data: HotelPin
isUserLoggedIn: boolean
handleClose: () => void
onClick?: () => void
@@ -63,6 +60,17 @@ export function StandaloneHotelCardDialog({
})
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}>
@@ -86,9 +94,9 @@ export function StandaloneHotelCardDialog({
position="left"
/>
<div className={styles.content}>
<div className={styles.name}>
<Body textTransform="bold">{name}</Body>
</div>
<Typography variant="Body/Paragraph/mdBold">
<h4 className={styles.name}>{name}</h4>
</Typography>
<div className={styles.facilities}>
{amenities.slice(0, 3).map((facility) => {
const Icon = (
@@ -96,173 +104,93 @@ export function StandaloneHotelCardDialog({
)
return (
<div className={styles.facilitiesItem} key={facility.id}>
{Icon}
<Footnote color="uiTextMediumContrast">
{facility.name}
</Footnote>
{Icon && Icon}
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>{facility.name}</span>
</Typography>
</div>
)
})}
</div>
<div className={styles.pricesContainer}>
{publicPrice ||
memberPrice ||
redemptionPrice ||
voucherPrice ||
chequePrice ? (
<>
<div className={styles.priceCard}>
{redemptionPrice ? (
<Caption>
{intl.formatMessage({
defaultMessage: 'Available rates',
})}
</Caption>
) : (
<Caption type="bold">
{intl.formatMessage({
defaultMessage: 'From',
})}
</Caption>
)}
{chequePrice && (
<Subtitle type="two">
{intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: chequePrice.numberOfCheques,
currency: 'CC',
}
)}
{chequePrice.additionalPricePerStay > 0
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
' + ' +
intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: chequePrice.additionalPricePerStay,
currency: chequePrice.currency,
}
)
: null}
<Body asChild>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{voucherPrice && (
<Subtitle type="two">
{intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: voucherPrice,
currency,
}
)}
<Body asChild>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{publicPrice && !isUserLoggedIn && (
<Subtitle type="two">
{intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: publicPrice,
currency,
}
)}
<Body asChild>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{memberPrice && (
<Subtitle type="two" color="red">
{intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: memberPrice,
currency,
}
)}
<Body asChild color="red">
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{redemptionPrice && (
<HotelPointsRow
pointsPerStay={redemptionPrice}
pointsCurrency={pointsCurrency}
/>
)}
</div>
{shouldShowNotEnoughPoints ? (
<div className={styles.notEnoughPointsButton}>
<Typography variant="Body/Paragraph/mdBold">
<span>{notEnoughPointsLabel}</span>
</Typography>
</div>
) : (
<Button
asChild
theme="base"
size="small"
className={styles.button}
onClick={onClick}
{showPriceCard ? (
<>
<div className={styles.priceCard}>
<Typography variant="Body/Supporting text (caption)/smBold">
<p>
{redemptionPrice
? intl.formatMessage({ defaultMessage: 'Available rates' })
: intl.formatMessage({ defaultMessage: 'From' })}
</p>
</Typography>
{chequePrice ? (
<RoomPrice
price={chequePrice.numberOfCheques}
currency="CC"
includePerNight={false}
>
<Link
href={`${selectRate(lang)}?hotel=${operaId}`}
color="none"
keepSearchParams
>
{intl.formatMessage({
defaultMessage: 'See rooms',
})}
</Link>
</Button>
)}
</>
) : (
<NoPriceAvailableCard />
)}
</div>
{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={currency} />
) : 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}
pointsCurrency={pointsCurrency}
/>
) : null}
</div>
{shouldShowNotEnoughPoints ? (
<Typography variant="Body/Paragraph/mdBold">
<div className={styles.notEnoughPointsButton}>
{notEnoughPointsLabel}
</div>
</Typography>
) : (
<ButtonLink
href={selectRateUrl}
variant="Primary"
color="Primary"
size="Small"
onClick={onClick}
>
{intl.formatMessage({
defaultMessage: 'See rooms',
})}
</ButtonLink>
)}
</>
) : (
<NoPriceAvailableCard />
)}
</div>
</div>
)