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:
@@ -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}>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user