diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/index.tsx index 6c8690d39..1b67d6bfd 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/index.tsx @@ -1,7 +1,10 @@ "use client" import { useSession } from "next-auth/react" +import { useState } from "react" import { useIntl } from "react-intl" +import { IconButton } from "@scandic-hotels/design-system/IconButton" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" import { selectRate } from "@/constants/routes/hotelReservation" @@ -11,31 +14,31 @@ import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import useLang from "@/hooks/useLang" import { isValidClientSession } from "@/utils/clientSession" import HotelPointsRow from "../../HotelCard/HotelPointsRow" import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard" import HotelCardDialogImage from "../HotelCardDialogImage" -import styles from "../hotelCardDialog.module.css" +import styles from "./listingHotelCardDialog.module.css" import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" -import type { Lang } from "@/constants/languages" interface ListingHotelCardProps { data: HotelPin - lang: Lang - imageError: boolean - setImageError: (error: boolean) => void + handleClose: () => void } export default function ListingHotelCardDialog({ data, - lang, - imageError, - setImageError, + handleClose, }: ListingHotelCardProps) { const intl = useIntl() + const lang = useLang() + + const [imageError, setImageError] = useState(false) + const { data: session } = useSession() const isUserLoggedIn = isValidClientSession(session) const { @@ -57,161 +60,174 @@ export default function ListingHotelCardDialog({ const altText = images[0]?.metaData?.altText return ( -
-
- -
-
- {name} -
-
- {amenities.map((facility) => ( -
- -
- ))} -
-
-
- - {publicPrice || - memberPrice || - redemptionPrice || - voucherPrice || - chequePrice ? ( -
-
- {redemptionPrice ? ( - - {intl.formatMessage({ - defaultMessage: "Available rates", - })} - - ) : ( - - {intl.formatMessage({ - defaultMessage: "Per night from", - })} - - )} -
- {publicPrice && !isUserLoggedIn && memberPrice ? ( - <> - - {publicPrice} {currency} - - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - {memberPrice && /} - - ) : ( - bookingCode && - publicPrice && ( - - {publicPrice} {currency} - - ) - )} - {memberPrice && ( - - {intl.formatMessage( - { - defaultMessage: "{price} {currency}", - }, - { - price: memberPrice, - currency, - } - )} - - )} - {redemptionPrice && ( - - )} - {chequePrice && ( - - {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} - - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - - / - {intl.formatMessage({ - defaultMessage: "night", - })} - - - - )} - {voucherPrice && ( - - {intl.formatMessage( - { - defaultMessage: "{price} {currency}", - }, - { - price: voucherPrice, - currency, - } - )} - - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - - / - {intl.formatMessage({ - defaultMessage: "night", - })} - - - - )} +
+ + + +
+
+ +
+
+ {name} +
+
+ {amenities.map((facility) => ( +
+ +
+ ))}
-
- ) : ( - - )} + + {publicPrice || + memberPrice || + redemptionPrice || + voucherPrice || + chequePrice ? ( +
+
+ {redemptionPrice ? ( + + {intl.formatMessage({ + defaultMessage: "Available rates", + })} + + ) : ( + + {intl.formatMessage({ + defaultMessage: "Per night from", + })} + + )} +
+ {publicPrice && !isUserLoggedIn && memberPrice ? ( + <> + + {publicPrice} {currency} + + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + {memberPrice && /} + + ) : ( + bookingCode && + publicPrice && ( + + {publicPrice} {currency} + + ) + )} + {memberPrice && ( + + {intl.formatMessage( + { + defaultMessage: "{price} {currency}", + }, + { + price: memberPrice, + currency, + } + )} + + )} + {redemptionPrice && ( + + )} + {chequePrice && ( + + {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} + + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + + / + {intl.formatMessage({ + defaultMessage: "night", + })} + + + + )} + {voucherPrice && ( + + {intl.formatMessage( + { + defaultMessage: "{price} {currency}", + }, + { + price: voucherPrice, + currency, + } + )} + + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} + + / + {intl.formatMessage({ + defaultMessage: "night", + })} + + + + )} +
+
+ +
+ ) : ( + + )} +
) } diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/listingHotelCardDialog.module.css b/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/listingHotelCardDialog.module.css new file mode 100644 index 000000000..2c6acc251 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/listingHotelCardDialog.module.css @@ -0,0 +1,76 @@ +.container { + border: 1px solid var(--Base-Border-Subtle); + border-radius: var(--Corner-radius-md); + min-width: 358px; + background: var(--Base-Surface-Primary-light-Normal); + box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1); + position: relative; +} + +.content { + padding: var(--Space-x15); + display: flex; + flex-direction: column; + gap: var(--Space-x15); +} + +.header { + display: flex; + gap: var(--Space-x15); +} + +.name { + height: 48px; + max-width: 180px; + margin-bottom: var(--Space-x05); + display: flex; + align-items: center; +} + +.facilities { + display: flex; + gap: 0 var(--Space-x15); +} + +.priceCard { + border-radius: var(--Corner-radius-md); + padding: var(--Space-x05) var(--Space-x1); + background: var(--Base-Surface-Secondary-light-Normal); + margin-top: var(--Space-x1); +} + +.prices { + display: flex; + flex-direction: column; + gap: var(--Space-x1); + justify-content: space-between; +} +.bottomContainer { + display: flex; + border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); + padding-top: var(--Space-x2); + padding-bottom: var(--Space-x05); +} + +.pricesContainer { + display: flex; + flex-direction: column; + flex: 1; + height: 44px; +} + +.listingPrices { + display: flex; + flex-direction: row; + gap: var(--Space-x1); +} + +.content .button { + margin-top: auto; +} + +.closeButton { + position: absolute; + top: 8px; + right: 8px; +} diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog/index.tsx index c7f9250d5..46b3db235 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog/index.tsx @@ -1,7 +1,11 @@ "use client" import { useSession } from "next-auth/react" +import { useState } from "react" import { useIntl } from "react-intl" +import { IconButton } from "@scandic-hotels/design-system/IconButton" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" + import { selectRate } from "@/constants/routes/hotelReservation" import { FacilityToIcon } from "@/components/ContentType/HotelPage/data" @@ -11,6 +15,7 @@ import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import useLang from "@/hooks/useLang" import { isValidClientSession } from "@/utils/clientSession" import { trackEvent } from "@/utils/tracking/base" @@ -18,25 +23,22 @@ import HotelPointsRow from "../../HotelCard/HotelPointsRow" import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard" import HotelCardDialogImage from "../HotelCardDialogImage" -import styles from "../hotelCardDialog.module.css" +import styles from "./standaloneHotelCardDialog.module.css" import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" -import type { Lang } from "@/constants/languages" interface StandaloneHotelCardProps { data: HotelPin - lang: Lang - imageError: boolean - setImageError: (error: boolean) => void + handleClose: () => void } export default function StandaloneHotelCardDialog({ data, - lang, - imageError, - setImageError, + handleClose, }: StandaloneHotelCardProps) { const intl = useIntl() + const lang = useLang() + const [imageError, setImageError] = useState(false) const { data: session } = useSession() const isUserLoggedIn = isValidClientSession(session) const { @@ -57,7 +59,18 @@ export default function StandaloneHotelCardDialog({ const altText = images[0]?.metaData?.altText return ( - <> +
+ + +
-
-
- {name} -
+
+ {name}
{amenities.slice(0, 3).map((facility) => { @@ -246,6 +257,6 @@ export default function StandaloneHotelCardDialog({ )}
- +
) } diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog/standaloneHotelCardDialog.module.css b/apps/scandic-web/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog/standaloneHotelCardDialog.module.css new file mode 100644 index 000000000..d5419bd13 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog/standaloneHotelCardDialog.module.css @@ -0,0 +1,67 @@ +.container { + flex-direction: row; + display: flex; + position: relative; + background: var(--Base-Surface-Primary-light-Normal); + box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1); +} +.content { + width: 100%; + max-width: 220px; + padding: var(--Space-x15); + display: flex; + flex-direction: column; +} + +.name { + height: 48px; + max-width: 180px; + margin-bottom: var(--Space-x05); + display: flex; + align-items: center; + padding-right: var(--Space-x1); +} + +.facilities { + display: flex; + flex-wrap: wrap; + gap: 0 var(--Space-x1); +} + +.facilitiesItem { + display: flex; + align-items: center; + gap: var(--Space-x05); +} + +.prices { + display: flex; + flex-direction: column; + gap: var(--Space-x1); + justify-content: space-between; +} + +.priceCard { + border-radius: var(--Corner-radius-md); + padding: var(--Space-x05) var(--Space-x1); + background: var(--Base-Surface-Secondary-light-Normal); + margin-top: var(--Space-x1); +} + +.pricesContainer { + display: flex; + flex-direction: column; + gap: var(--Space-x1); + justify-content: space-between; +} + +.content .button { + margin-top: auto; +} + +.closeButton { + position: absolute; + top: 8px; + right: 8px; + z-index: 1; +} diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/hotelCardDialog.module.css b/apps/scandic-web/components/HotelReservation/HotelCardDialog/hotelCardDialog.module.css deleted file mode 100644 index 4079bda17..000000000 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialog/hotelCardDialog.module.css +++ /dev/null @@ -1,154 +0,0 @@ -.dialog { - padding-bottom: var(--Spacing-x1); - bottom: 0; - left: 50%; - transform: translateX(-50%); - border: none; - background: transparent; -} - -.dialogContainer { - border: 1px solid var(--Base-Border-Subtle); - border-radius: var(--Corner-radius-md); - min-width: 402px; - background: var(--Base-Surface-Primary-light-Normal); - box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1); - flex-direction: row; - display: flex; - position: relative; -} - -.dialogContainer[data-type="listing"] { - min-width: 358px; -} - -.dialogContainer[data-type="listing"] .header { - display: flex; - flex-direction: row; - gap: var(--Spacing-x-one-and-half); -} - -.name { - height: 48px; - max-width: 180px; - margin-bottom: var(--Spacing-x-half); - display: flex; - align-items: center; -} - -.closeIcon { - position: absolute; - top: 8px; - right: 8px; -} -.content { - width: 100%; - min-width: 220px; - padding: var(--Spacing-x-one-and-half); - display: flex; - flex-direction: column; -} - -.dialogContainer[data-type="listing"] .content { - gap: var(--Spacing-x-one-and-half); -} - -.facilities { - display: flex; - flex-wrap: wrap; - gap: 0 var(--Spacing-x1); -} - -.dialogContainer[data-type="listing"] .facilities { - display: flex; - flex-wrap: nowrap; - overflow: hidden; - white-space: nowrap; - position: relative; - padding-right: 20px; - gap: 0 var(--Spacing-x-one-and-half); - max-width: 242px; -} -.dialogContainer[data-type="listing"] .facilities::after { - content: ""; - position: absolute; - top: 0; - right: 0; - width: 20px; - height: 100%; - background: linear-gradient( - to left, - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); - pointer-events: none; -} - -.facilitiesItem { - display: flex; - align-items: center; - gap: var(--Spacing-x-half); -} - -.dialogContainer[data-type="listing"] .facilitiesItem { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: var(--Spacing-x-half); -} - -.priceCard { - border-radius: var(--Corner-radius-md); - padding: var(--Spacing-x-half) var(--Spacing-x1); - background: var(--Base-Surface-Secondary-light-Normal); - margin-top: var(--Spacing-x1); -} - -.prices { - display: flex; - flex-direction: column; - gap: var(--Spacing-x1); - justify-content: space-between; -} - -.bottomContainer { - display: flex; - flex-direction: row; - border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); - padding-top: var(--Spacing-x2); - padding-bottom: var(--Spacing-x-half); -} - -.pricesContainer { - display: flex; - flex-direction: column; - gap: var(--Spacing-x1); - justify-content: space-between; -} - -.dialogContainer[data-type="listing"] .pricesContainer { - flex: 1; - height: 44px; - gap: 0; - justify-content: flex-start; -} - -.listingPrices { - display: flex; - flex-direction: row; - gap: var(--Spacing-x1); -} - -.perNight { - color: var(--Base-Text-Subtle-light-Normal); -} - -.content .button { - margin-top: auto; -} - -@media (min-width: 768px) { - .dialog { - bottom: 32px; - } -} diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCardDialog/index.tsx deleted file mode 100644 index dac5c8194..000000000 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialog/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -"use client" - -import { useParams } from "next/navigation" -import { useState } from "react" - -import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" - -import ListingHotelCardDialog from "./ListingHotelCardDialog" -import StandaloneHotelCardDialog from "./StandaloneHotelCardDialog" - -import styles from "./hotelCardDialog.module.css" - -import type { HotelCardDialogProps } from "@/types/components/hotelReservation/selectHotel/map" -import type { Lang } from "@/constants/languages" - -export default function HotelCardDialog({ - data, - isOpen, - type = "standalone", - handleClose, -}: HotelCardDialogProps) { - const params = useParams() - const lang = params.lang as Lang - const [imageError, setImageError] = useState(false) - - if (!data) { - return null - } - - return ( - -
-
- -
- - {type === "standalone" ? ( - - ) : ( - - )} -
-
- ) -} diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/hotelCardDialogListing.module.css b/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/hotelCardDialogListing.module.css index cb682c385..f81aab94f 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/hotelCardDialogListing.module.css +++ b/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/hotelCardDialogListing.module.css @@ -1,6 +1,5 @@ .hotelCardDialogListing { display: flex; - flex-direction: row; gap: var(--Spacing-x1); align-items: flex-end; overflow-x: scroll; @@ -17,13 +16,7 @@ transform: translateZ(0); } -.hotelCardDialogListing > div { +.hotelCard { height: 100%; scroll-snap-align: center; } - -.hotelCardDialogListing dialog { - position: relative; - padding: 0; - margin: 0; -} diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/index.tsx index 977023df4..72cb43f25 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/index.tsx +++ b/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/index.tsx @@ -5,7 +5,7 @@ import { useIntl } from "react-intl" import { useHotelsMapStore } from "@/stores/hotels-map" -import HotelCardDialog from "../HotelCardDialog" +import ListingHotelCardDialog from "../HotelCardDialog/ListingHotelCardDialog" import { getHotelPins } from "./utils" import styles from "./hotelCardDialogListing.module.css" @@ -127,13 +127,9 @@ export default function HotelCardDialogListing({ key={data.name} ref={isActive ? activeCardRef : null} data-name={data.name} + className={styles.hotelCard} > - +
) })} diff --git a/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/hotelListingMapContent.module.css b/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/hotelListingMapContent.module.css index 0cc6a8fb7..141715e91 100644 --- a/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/hotelListingMapContent.module.css +++ b/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/hotelListingMapContent.module.css @@ -1,28 +1,3 @@ .advancedMarker { height: 32px; } - -.dialogContainer { - display: none; -} - -.card { - display: none; - position: absolute; - bottom: 32px; - left: 50%; - transform: translateX(-50%); - width: 402px; - height: 181px; - background-color: var(--Base-Surface-Primary-light-Normal); -} - -.card.active { - display: block; -} - -@media (min-width: 768px) { - .dialogContainer { - display: block; - } -} diff --git a/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/index.tsx b/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/index.tsx index e87be81dc..e7ef85531 100644 --- a/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/index.tsx +++ b/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/index.tsx @@ -1,12 +1,14 @@ import { AdvancedMarker, AdvancedMarkerAnchorPoint, + InfoWindow, } from "@vis.gl/react-google-maps" import { useCallback } from "react" +import { useMediaQuery } from "usehooks-ts" import { useHotelsMapStore } from "@/stores/hotels-map" -import HotelCardDialog from "@/components/HotelReservation/HotelCardDialog" +import StandaloneHotelCardDialog from "@/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog" import { trackEvent } from "@/utils/tracking/base" import HotelPin from "./HotelPin" @@ -18,6 +20,7 @@ import type { HotelListingMapContentProps } from "@/types/components/hotelReserv function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) { const { activeHotel, hoveredHotel, activate, deactivate, engage, disengage } = useHotelsMapStore() + const isDesktop = useMediaQuery("(min-width: 768px)") const toggleActiveHotelPin = useCallback( (pinName: string | null, hotelId: string) => { @@ -69,17 +72,22 @@ function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) { onMouseLeave={() => disengage()} onClick={() => toggleActiveHotelPin(pin.name, pin.operaId)} > -
- void }) => { - event.stopPropagation() - deactivate() - disengage() - }} - data={pin} - /> -
+ {isActiveOrHovered && isDesktop && ( + + { + deactivate() + disengage() + }} + /> + + )}