Files
web/packages/design-system/lib/components/HotelCard/HotelDialogCard/StandaloneHotelCardDialog/index.tsx
Erik Tiekstra 4ec1e85d84 Feat/BOOK-293 button adjustments
* feat(BOOK-293): Adjusted padding of the buttons to match Figma design
* feat(BOOK-293): Updated variants for IconButton
* feat(BOOK-113): Updated focus indicators on buttons and added default focus ring color
* feat(BOOK-293): Replaced buttons inside booking widget

Approved-by: Christel Westerberg
2025-12-15 07:05:31 +00:00

207 lines
6.6 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useIntl } from 'react-intl'
import { IconButton } from '../../../IconButton'
import { MaterialIcon } from '../../../Icons/MaterialIcon'
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'
interface StandaloneHotelCardProps {
lang: Lang
data: HotelPin
isUserLoggedIn: boolean
handleClose: () => void
onClick?: () => void
pointsCurrency?: CurrencyEnum
}
export function StandaloneHotelCardDialog({
data,
lang,
handleClose,
isUserLoggedIn,
onClick,
pointsCurrency,
}: StandaloneHotelCardProps) {
const intl = useIntl()
const [imageError, setImageError] = useState(false)
const {
name,
chequePrice,
publicPrice,
memberPrice,
redemptionPrice,
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',
})}
>
<MaterialIcon icon="close" size={22} color="CurrentColor" />
</IconButton>
<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={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({
id: 'common.seeRooms',
defaultMessage: 'See rooms',
})}
</ButtonLink>
)}
</>
) : (
<NoPriceAvailableCard />
)}
</div>
</div>
)
}