Merged in SW-3270-move-interactive-map-to-design-system-or-booking-flow (pull request #2681)

SW-3270 move interactive map to design system or booking flow

* wip

* wip

* merge

* wip

* add support for locales in design-system

* add story for HotelCard

* setup alias

* .

* remove tracking from design-system for hotelcard

* pass isUserLoggedIn

* export design-system-new-deprecated.css from design-system

* Add HotelMarkerByType to Storybook

* Add interactive map to Storybook

* fix reactintl in vitest

* rename env variables

* .

* fix background colors

* add storybook stories for <Link />

* merge

* fix tracking for when clicking 'See rooms' in InteractiveMap

* Merge branch 'master' of bitbucket.org:scandic-swap/web into SW-3270-move-interactive-map-to-design-system-or-booking-flow

* remove deprecated comment


Approved-by: Anton Gunnarsson
This commit is contained in:
Joakim Jäderberg
2025-08-25 11:26:16 +00:00
parent 4f8c51298f
commit c54c1ec540
139 changed files with 2511 additions and 1557 deletions
@@ -0,0 +1,32 @@
.pin {
display: flex;
justify-content: center;
align-items: center;
padding: var(--Spacing-x-half) var(--Spacing-x1) var(--Spacing-x-half)
var(--Spacing-x-half);
border: 2px solid var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Corner-radius-rounded);
background-color: var(--Base-Surface-Primary-light-Normal);
box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.1);
gap: var(--Spacing-x1);
width: max-content;
}
.pin.active {
background-color: var(--Primary-Dark-Surface-Normal);
color: var(--Base-Surface-Primary-light-Normal);
}
.pinIcon {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: var(--Surface-Brand-Primary-2-Default);
}
.pin.active .pinIcon {
background: var(--Base-Surface-Primary-light-Normal);
}
@@ -0,0 +1,60 @@
import { useIntl } from 'react-intl'
import { formatPrice } from '@scandic-hotels/common/utils/numberFormatting'
import { MaterialIcon } from '@scandic-hotels/design-system/Icons/MaterialIcon'
import { Typography } from '@scandic-hotels/design-system/Typography'
import HotelMarker from '../../../Markers/HotelMarker'
import styles from './hotelPin.module.css'
interface HotelPinProps {
isActive: boolean
hotelPrice: number | null
currency: string
hotelAdditionalPrice?: number
hotelAdditionalCurrency?: string
}
export function HotelPin({
isActive,
hotelPrice,
currency,
hotelAdditionalPrice,
hotelAdditionalCurrency,
}: HotelPinProps) {
const intl = useIntl()
const isNotAvailable = !hotelPrice
return (
<div
className={`${styles.pin} ${isActive ? styles.active : ''}`}
data-hotelpin
>
<span className={styles.pinIcon}>
{isNotAvailable ? (
<MaterialIcon
icon="calendar_clock"
size={16}
color={isActive ? 'Icon/Interactive/Default' : 'Icon/Inverted'}
/>
) : (
<HotelMarker width={16} color={isActive ? 'burgundy' : 'white'} />
)}
</span>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{isNotAvailable
? '—'
: formatPrice(
intl,
hotelPrice,
currency,
hotelAdditionalPrice,
hotelAdditionalCurrency
)}
</p>
</Typography>
</div>
)
}
@@ -0,0 +1,3 @@
.advancedMarker {
height: 32px;
}
@@ -0,0 +1,122 @@
import {
AdvancedMarker,
AdvancedMarkerAnchorPoint,
InfoWindow,
} from '@vis.gl/react-google-maps'
import { useMediaQuery } from 'usehooks-ts'
import { HotelPin } from './HotelPin'
import type { HotelPin as HotelPinType } from '../../types'
import styles from './hotelListingMapContent.module.css'
import { StandaloneHotelCardDialog } from '../../../HotelCard/HotelDialogCard/StandaloneHotelCardDialog'
import { Lang } from '@scandic-hotels/common/constants/language'
export type HotelListingMapContentProps = {
hotelPins: HotelPinType[]
activeHotel?: string | null
hoveredHotel?: string | null
lang: Lang
isUserLoggedIn: boolean
onClickHotel?: (hotelId: string) => void
setActiveHotel?: (args: { hotelName: string; hotelId: string } | null) => void
setHoveredHotel?: (
args: { hotelName: string; hotelId: string } | null
) => void
}
export function HotelListingMapContent({
hotelPins,
activeHotel,
hoveredHotel,
isUserLoggedIn,
setActiveHotel,
setHoveredHotel,
lang,
onClickHotel,
}: HotelListingMapContentProps) {
const isDesktop = useMediaQuery('(min-width: 768px)')
const toggleActiveHotelPin = (
args: { hotelName: string; hotelId: string } | null
) => {
if (!args) {
setActiveHotel?.(null)
return
}
setActiveHotel?.({ hotelName: args.hotelName, hotelId: args.hotelId })
}
return (
<div>
{hotelPins.map((pin) => {
const isActiveOrHovered =
activeHotel === pin.name || hoveredHotel === pin.name
const hotelPrice =
pin.memberPrice ??
pin.publicPrice ??
pin.redemptionPrice ??
pin.voucherPrice ??
pin.chequePrice?.numberOfCheques ??
null
const hotelAdditionalPrice = pin.chequePrice
? pin.chequePrice.additionalPricePerStay
: undefined
const hotelAdditionalCurrency = pin.chequePrice
? pin.chequePrice.currency?.toString()
: undefined
return (
<AdvancedMarker
key={pin.name}
className={styles.advancedMarker}
position={pin.coordinates}
anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
zIndex={isActiveOrHovered ? 2 : 0}
onMouseEnter={() => {
setHoveredHotel?.({ hotelName: pin.name, hotelId: pin.operaId })
}}
onMouseLeave={() => {
setHoveredHotel?.(null)
}}
onClick={() =>
toggleActiveHotelPin({
hotelName: pin.name,
hotelId: pin.operaId,
})
}
>
{isActiveOrHovered && isDesktop && (
<InfoWindow
position={pin.coordinates}
pixelOffset={[0, -24]}
headerDisabled={true}
shouldFocus={false}
>
<StandaloneHotelCardDialog
data={pin}
lang={lang}
isUserLoggedIn={isUserLoggedIn}
handleClose={() => {
setActiveHotel?.(null)
setHoveredHotel?.(null)
}}
onClick={() => {
onClickHotel?.(pin.operaId)
}}
/>
</InfoWindow>
)}
<HotelPin
isActive={isActiveOrHovered}
hotelPrice={hotelPrice}
currency={pin.currency}
hotelAdditionalPrice={hotelAdditionalPrice}
hotelAdditionalCurrency={hotelAdditionalCurrency}
/>
</AdvancedMarker>
)
})}
</div>
)
}