Merged in fix/SW-1524-clickable-area (pull request #2125)

Fix/SW-1524: Enter details- expand clickable area

* fix(SW-1524): make whole price area clickable

* fix(SW-1524): add div as fake button


Approved-by: Bianca Widstam
Approved-by: Erik Tiekstra
This commit is contained in:
Matilda Landström
2025-05-21 07:38:27 +00:00
parent e48e90dee4
commit 6be599e687
5 changed files with 98 additions and 48 deletions

View File

@@ -1,3 +1,4 @@
import { cx } from "class-variance-authority"
import { useIntl } from "react-intl"
import Divider from "@/components/TempDesignSystem/Divider"
@@ -13,6 +14,7 @@ import { RateTypeEnum } from "@/types/enums/rateType"
export default function HotelPriceCard({
productTypePrices,
isMemberPrice = false,
className,
}: PriceCardProps) {
const intl = useIntl()
const isRegularOrPublicPromotionRate =
@@ -20,7 +22,7 @@ export default function HotelPriceCard({
productTypePrices.rateType === RateTypeEnum.PublicPromotion
return (
<dl className={styles.priceCard}>
<dl className={cx(styles.priceCard, className)}>
{isRegularOrPublicPromotionRate &&
(isMemberPrice ? (
<div className={styles.priceRow}>

View File

@@ -1,8 +1,7 @@
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./noPriceAvailable.module.css"
@@ -11,15 +10,15 @@ export default function NoPriceAvailableCard() {
return (
<div className={styles.priceCard}>
<div className={styles.noRooms}>
<div>
<MaterialIcon icon="error" color="Icon/Interactive/Accent" />
</div>
<Caption color="uiTextHighContrast">
{intl.formatMessage({
defaultMessage:
"There are no rooms available that match your request.",
})}
</Caption>
<MaterialIcon icon="error" color="Icon/Feedback/Error" />
<Typography variant="Body/Paragraph/mdRegular">
<span>
{intl.formatMessage({
defaultMessage:
"There are no rooms available that match your request.",
})}
</span>
</Typography>
</div>
</div>
)

View File

@@ -71,10 +71,6 @@
gap: var(--Spacing-x-half);
}
.button {
min-width: 160px;
}
.specialAlerts {
display: flex;
flex-direction: column;
@@ -87,6 +83,22 @@
gap: var(--Spacing-x-one-and-half);
}
.link:hover {
.fakeButton {
background-color: var(--Component-Button-Brand-Primary-Fill-Hover);
border-color: var(--Component-Button-Brand-Primary-Border-Hover);
color: var(--Component-Button-Brand-Primary-On-fill-Hover);
}
.priceCard {
background: linear-gradient(
0deg,
var(--Surface-Primary-Hover) 0%,
var(--Surface-Primary-Hover) 100%
);
}
}
.strikedText {
text-decoration: line-through;
}
@@ -97,6 +109,28 @@
border-radius: var(--Corner-radius-md);
}
.fakeButton {
min-width: 160px;
border-radius: var(--Corner-radius-rounded);
border-width: 2px;
border-style: solid;
display: flex;
align-items: center;
justify-content: center;
gap: var(--Space-x05);
padding: 10px var(--Space-x2);
background-color: var(--Component-Button-Brand-Primary-Fill-Default);
border-color: var(--Component-Button-Brand-Primary-Border-Default);
color: var(--Component-Button-Brand-Primary-On-fill-Default);
}
.fakeButton.disabled {
background-color: var(--Component-Button-Brand-Primary-Fill-Disabled);
border-color: var(--Component-Button-Brand-Primary-Border-Disabled);
color: var(--Component-Button-Brand-Primary-On-fill-Disabled);
}
@media screen and (min-width: 768px) and (max-width: 1024px) {
.imageContainer {
height: 180px;
@@ -140,7 +174,7 @@
margin-bottom: var(--Spacing-x-one-and-half);
}
.pageListing .button {
.pageListing .fakeButton {
width: 100%;
}

View File

@@ -1,5 +1,6 @@
"use client"
import { cx } from "class-variance-authority"
import { useParams } from "next/dist/client/components/navigation"
import { useRouter, useSearchParams } from "next/navigation"
import { memo } from "react"
@@ -14,7 +15,6 @@ import { useHotelsMapStore } from "@/stores/hotels-map"
import BookingCodeChip from "@/components/BookingCodeChip"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
import ImageGallery from "@/components/ImageGallery"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
@@ -79,6 +79,8 @@ function HotelCard({
defaultMessage: "Not enough points",
})
const isDisabled = price?.redemptions?.length && hasInsufficientPoints
return (
<article
className={classNames}
@@ -172,7 +174,10 @@ function HotelCard({
sidePeekKey={SidePeekEnum.hotelDetails}
/>
</section>
<div className={styles.prices}>
<PricesWrapper
href={`${selectRate(lang)}?hotel=${hotel.operaId}`}
isClickable={availability.productType && !isDisabled}
>
{!availability.productType ? (
<NoPriceAvailableCard />
) : (
@@ -187,11 +192,15 @@ function HotelCard({
!price?.member ||
(bookingCode && !fullPrice)) &&
price?.public && (
<HotelPriceCard productTypePrices={price.public} />
<HotelPriceCard
productTypePrices={price.public}
className={styles.priceCard}
/>
)}
{availability.productType.member && (
<HotelPriceCard
productTypePrices={availability.productType.member}
className={styles.priceCard}
isMemberPrice
/>
)}
@@ -222,47 +231,52 @@ function HotelCard({
))}
</div>
) : null}
{price?.redemptions?.length && hasInsufficientPoints ? (
{isDisabled ? (
<Tooltip
arrow="left"
position="bottom"
text={notEnoughPointsLabel}
>
<Button
theme="base"
intent="primary"
size="small"
className={styles.button}
disabled
>
{notEnoughPointsLabel}
</Button>
<div className={cx(styles.fakeButton, styles.disabled)}>
<Typography variant="Body/Paragraph/mdBold">
<span>{notEnoughPointsLabel}</span>
</Typography>
</div>
</Tooltip>
) : (
<Button
asChild
theme="base"
intent="primary"
size="small"
className={styles.button}
>
<Link
href={`${selectRate(lang)}?hotel=${hotel.operaId}`}
color="none"
keepSearchParams
>
{intl.formatMessage({
defaultMessage: "See rooms",
})}
</Link>
</Button>
<div className={styles.fakeButton}>
<Typography variant="Body/Paragraph/mdBold">
<span>
{intl.formatMessage({
defaultMessage: "See rooms",
})}
</span>
</Typography>
</div>
)}
</>
)}
</div>
</PricesWrapper>
</div>
</article>
)
}
interface PricesWrapperProps {
href: string
isClickable?: boolean
children: React.ReactNode
}
function PricesWrapper({ href, isClickable, children }: PricesWrapperProps) {
const content = <div className={styles.prices}>{children}</div>
return isClickable ? (
<Link href={href} color="none" className={styles.link} keepSearchParams>
{content}
</Link>
) : (
content
)
}
export default memo(HotelCard)

View File

@@ -7,6 +7,7 @@ import type {
export type PriceCardProps = {
productTypePrices: ProductTypePrices
isMemberPrice?: boolean
className?: string
}
export type PointsRowProps = {