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

View File

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

View File

@@ -71,10 +71,6 @@
gap: var(--Spacing-x-half); gap: var(--Spacing-x-half);
} }
.button {
min-width: 160px;
}
.specialAlerts { .specialAlerts {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -87,6 +83,22 @@
gap: var(--Spacing-x-one-and-half); 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 { .strikedText {
text-decoration: line-through; text-decoration: line-through;
} }
@@ -97,6 +109,28 @@
border-radius: var(--Corner-radius-md); 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) { @media screen and (min-width: 768px) and (max-width: 1024px) {
.imageContainer { .imageContainer {
height: 180px; height: 180px;
@@ -140,7 +174,7 @@
margin-bottom: var(--Spacing-x-one-and-half); margin-bottom: var(--Spacing-x-one-and-half);
} }
.pageListing .button { .pageListing .fakeButton {
width: 100%; width: 100%;
} }

View File

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

View File

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