Merged in fix/SW-1491-SW-1500-link-in-hotel-card-to-map (pull request #1707)

fix(SW-1491-SW-1500): address on hotel card should go to map, remove link on maplisting view

* fix(SW-1491-SW-1500): address on hotel card should go to map, remove link on maplisting view

* fix(SW-1491-SW-1500): fix comment

* fix(SW-1491-SW-1500): add underscore


Approved-by: Niclas Edenvin
This commit is contained in:
Bianca Widstam
2025-04-03 06:56:23 +00:00
parent 0215c8428f
commit 83aedd7dbb
6 changed files with 93 additions and 75 deletions

View File

@@ -54,13 +54,8 @@
} }
.address { .address {
display: none;
font-style: normal;
}
.addressMobile {
display: block;
font-style: normal; font-style: normal;
color: var(--Text-Tertiary);
} }
.facilities { .facilities {
@@ -149,14 +144,6 @@
width: 100%; width: 100%;
} }
.pageListing .addressMobile {
display: none;
}
.pageListing .address {
display: inline;
}
.pageListing .prices { .pageListing .prices {
width: 260px; width: 260px;
} }

View File

@@ -1,12 +1,14 @@
"use client" "use client"
import { useParams } from "next/dist/client/components/navigation" import { useParams } from "next/dist/client/components/navigation"
import { useRouter, useSearchParams } from "next/navigation"
import { memo, useCallback } from "react" import { memo, useCallback } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { HotelLogo, MaterialIcon } from "@scandic-hotels/design-system/Icons" import { HotelLogo, MaterialIcon } from "@scandic-hotels/design-system/Icons"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { selectRate } from "@/constants/routes/hotelReservation" import { selectHotelMap, selectRate } from "@/constants/routes/hotelReservation"
import { useHotelsMapStore } from "@/stores/hotels-map" import { useHotelsMapStore } from "@/stores/hotels-map"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data" import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
@@ -46,6 +48,8 @@ function HotelCard({
bookingCode = "", bookingCode = "",
}: HotelCardProps) { }: HotelCardProps) {
const params = useParams() const params = useParams()
const searchParams = useSearchParams()
const lang = params.lang as Lang const lang = params.lang as Lang
const intl = useIntl() const intl = useIntl()
const { setActiveHotelPin, setActiveHotelCard } = useHotelsMapStore() const { setActiveHotelPin, setActiveHotelCard } = useHotelsMapStore()
@@ -60,12 +64,19 @@ function HotelCard({
}, [setActiveHotelPin, setActiveHotelCard]) }, [setActiveHotelPin, setActiveHotelCard])
const amenities = hotel.detailedFacilities.slice(0, 5) const amenities = hotel.detailedFacilities.slice(0, 5)
const router = useRouter()
const classNames = hotelCardVariants({ const classNames = hotelCardVariants({
type, type,
state, state,
}) })
const handleAddressClick = (event: React.MouseEvent) => {
event.preventDefault()
setActiveHotelPin(hotel.name)
setActiveHotelCard(hotel.name)
router.push(`${selectHotelMap(lang)}?${searchParams.toString()}`)
}
const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}` const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}`
const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || []) const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
const fullPrice = const fullPrice =
@@ -101,26 +112,29 @@ function HotelCard({
</Subtitle> </Subtitle>
<div className={styles.addressContainer}> <div className={styles.addressContainer}>
<address className={styles.address}> <address className={styles.address}>
<Caption color="uiTextPlaceholder">{addressStr}</Caption> {type == HotelCardListingTypeEnum.MapListing ? (
</address> <Typography variant="Body/Supporting text (caption)/smRegular">
<address className={styles.addressMobile}> <p>{addressStr}</p>
<Caption color="burgundy" type="underline" asChild> </Typography>
) : (
<Link <Link
href={`https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`}
target="_blank"
aria-label={intl.formatMessage({
id: "Driving directions",
})}
title={intl.formatMessage({
id: "Driving directions",
})}
color="burgundy"
size="small" size="small"
color="burgundy"
variant="underscored"
onClick={handleAddressClick}
href={selectHotelMap(lang)}
keepSearchParams
aria-label={intl.formatMessage({
id: "See on map",
})}
> >
{addressStr} <Typography variant="Body/Supporting text (caption)/smRegular">
<p>{addressStr}</p>
</Typography>
</Link> </Link>
</Caption> )}
</address> </address>
<div> <div>
<Divider variant="vertical" color="subtle" /> <Divider variant="vertical" color="subtle" />
</div> </div>

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import { useSearchParams } from "next/navigation" import { useSearchParams } from "next/navigation"
import { useSession } from "next-auth/react" import { useSession } from "next-auth/react"
import { useEffect, useMemo } from "react" import { useEffect, useMemo, useRef } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useBookingCodeFilterStore } from "@/stores/bookingCode-filter" import { useBookingCodeFilterStore } from "@/stores/bookingCode-filter"
@@ -39,6 +39,7 @@ export default function HotelCardListing({
const intl = useIntl() const intl = useIntl()
const { activeHotelCard } = useHotelsMapStore() const { activeHotelCard } = useHotelsMapStore()
const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 }) const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 })
const activeCardRef = useRef<HTMLDivElement | null>(null)
const sortBy = searchParams.get("sort") ?? DEFAULT_SORT const sortBy = searchParams.get("sort") ?? DEFAULT_SORT
@@ -99,25 +100,47 @@ export default function HotelCardListing({
isSpecialRate, isSpecialRate,
]) ])
useEffect(() => {
if (activeCardRef.current && type === HotelCardListingTypeEnum.MapListing) {
activeCardRef.current.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center",
})
}
}, [activeHotelCard, type])
useEffect(() => { useEffect(() => {
setResultCount(hotels.length) setResultCount(hotels.length)
}, [hotels, setResultCount]) }, [hotels, setResultCount])
function isHotelActiveInMapView(hotelName: string): boolean {
return (
hotelName === activeHotelCard &&
type === HotelCardListingTypeEnum.MapListing
)
}
return ( return (
<section className={styles.hotelCards}> <section className={styles.hotelCards}>
{hotels?.length {hotels?.length
? hotels.map((hotel) => ( ? hotels.map((hotel) => (
<div <div
key={hotel.hotel.operaId} key={hotel.hotel.operaId}
ref={
isHotelActiveInMapView(hotel.hotel.name) ? activeCardRef : null
}
data-active={ data-active={
hotel.hotel.name === activeHotelCard ? "true" : "false" isHotelActiveInMapView(hotel.hotel.name) ? "true" : "false"
} }
> >
<HotelCard <HotelCard
hotelData={hotel} hotelData={hotel}
isUserLoggedIn={isUserLoggedIn} isUserLoggedIn={isUserLoggedIn}
state={ state={
hotel.hotel.name === activeHotelCard ? "active" : "default" isHotelActiveInMapView(hotel.hotel.name)
? "active"
: "default"
} }
type={type} type={type}
bookingCode={bookingCode} bookingCode={bookingCode}

View File

@@ -1,7 +1,3 @@
.hotelListing {
display: none;
}
.hotelListingMobile { .hotelListingMobile {
display: none; display: none;
overflow-x: auto; overflow-x: auto;
@@ -16,16 +12,9 @@
display: flex; display: flex;
} }
@media (min-width: 768px) {
.hotelListing { .hotelListing {
display: block; display: block;
width: 100%; width: 100%;
overflow-y: auto; overflow-y: auto;
padding-top: var(--Spacing-x2); padding-top: var(--Spacing-x2);
} }
.hotelListingMobile,
.hotelListingMobile[data-open="true"] {
display: none;
}
}

View File

@@ -1,5 +1,7 @@
"use client" "use client"
import { useMediaQuery } from "usehooks-ts"
import { useHotelsMapStore } from "@/stores/hotels-map" import { useHotelsMapStore } from "@/stores/hotels-map"
import HotelCardDialogListing from "@/components/HotelReservation/HotelCardDialogListing" import HotelCardDialogListing from "@/components/HotelReservation/HotelCardDialogListing"
@@ -12,17 +14,21 @@ import type { HotelListingProps } from "@/types/components/hotelReservation/sele
export default function HotelListing({ hotels }: HotelListingProps) { export default function HotelListing({ hotels }: HotelListingProps) {
const { activeHotelPin } = useHotelsMapStore() const { activeHotelPin } = useHotelsMapStore()
const isMobile = useMediaQuery("(max-width: 767px)")
return ( return (
<> <>
{isMobile ? (
<div className={styles.hotelListingMobile} data-open={!!activeHotelPin}>
<HotelCardDialogListing hotels={hotels} />
</div>
) : (
<div className={styles.hotelListing}> <div className={styles.hotelListing}>
<HotelCardListing <HotelCardListing
hotelData={hotels} hotelData={hotels}
type={HotelCardListingTypeEnum.MapListing} type={HotelCardListingTypeEnum.MapListing}
/> />
</div> </div>
<div className={styles.hotelListingMobile} data-open={!!activeHotelPin}> )}
<HotelCardDialogListing hotels={hotels} />
</div>
</> </>
) )
} }

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { useMap } from "@vis.gl/react-google-maps" import { useMap } from "@vis.gl/react-google-maps"
import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useCallback, useMemo, useRef, useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts" import { useMediaQuery } from "usehooks-ts"
@@ -52,7 +52,7 @@ export default function SelectHotelContent({
const listingContainerRef = useRef<HTMLDivElement | null>(null) const listingContainerRef = useRef<HTMLDivElement | null>(null)
const activeFilters = useHotelFilterStore((state) => state.activeFilters) const activeFilters = useHotelFilterStore((state) => state.activeFilters)
const { activeHotelCard, activeHotelPin } = useHotelsMapStore() const { activeHotelPin } = useHotelsMapStore()
const { showBackToTop, scrollToTop } = useScrollToTop({ const { showBackToTop, scrollToTop } = useScrollToTop({
threshold: 490, threshold: 490,
@@ -63,23 +63,22 @@ export default function SelectHotelContent({
(state) => state.activeCodeFilter (state) => state.activeCodeFilter
) )
const coordinates = useMemo( const coordinates = useMemo(() => {
() => if (activeHotelPin) {
isAboveMobile const hotel = hotels.find((hotel) => hotel.hotel.name === activeHotelPin)
? cityCoordinates
: { ...cityCoordinates, lat: cityCoordinates.lat - 0.006 },
[isAboveMobile, cityCoordinates]
)
useEffect(() => { if (hotel && hotel.hotel.location) {
if (listingContainerRef.current) { return {
const activeElement = lat: hotel.hotel.location.latitude,
listingContainerRef.current.querySelector(`[data-active="true"]`) lng: hotel.hotel.location.longitude,
if (activeElement) {
activeElement.scrollIntoView({ behavior: "smooth", block: "nearest" })
} }
} }
}, [activeHotelCard, activeHotelPin]) }
return isAboveMobile
? cityCoordinates
: { ...cityCoordinates, lat: cityCoordinates.lat - 0.006 }
}, [activeHotelPin, hotels, isAboveMobile, cityCoordinates])
const filteredHotelPins = useMemo(() => { const filteredHotelPins = useMemo(() => {
const updatedHotelsList = bookingCode const updatedHotelsList = bookingCode