fix(BOOK-418): Refactored StandaloneHotelCardDialog and fixed pricing issue when using redemption or booking codes

Approved-by: Bianca Widstam
This commit is contained in:
Erik Tiekstra
2025-10-20 10:40:38 +00:00
parent 710309b7eb
commit 3e3a7fc423
12 changed files with 605 additions and 412 deletions

View File

@@ -0,0 +1,25 @@
import { usePathname, useSearchParams } from "next/navigation"
import { useMemo } from "react"
export function useUrlWithSearchParam(
searchParam: { key: string; value: string },
path?: string | null
) {
const { key, value } = searchParam
const pathname = path ?? usePathname()
const searchParams = useSearchParams()
return useMemo(() => {
const newUrl = new URL(pathname, window.location.origin)
// Preserve existing search params
searchParams.forEach((val, key) => {
newUrl.searchParams.set(key, val)
})
// Set or override the specific search param
newUrl.searchParams.set(key, value)
return newUrl
}, [pathname, searchParams, key, value])
}

View File

@@ -33,6 +33,7 @@ export function HotelCardDialogImage({
src={imageSrc} src={imageSrc}
alt={altText || ''} alt={altText || ''}
fill fill
sizes={position === 'top' ? '200px' : '450px'}
onError={() => setImageError(true)} onError={() => setImageError(true)}
/> />
)} )}

View File

@@ -1,7 +1,9 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite' import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { StandaloneHotelCardDialog } from './index' import { StandaloneHotelCardDialog } from './index'
import { Lang } from '@scandic-hotels/common/constants/language'
import { fn } from 'storybook/test' import { fn } from 'storybook/test'
import { hotelPins } from '../../../Map/InteractiveMap/storybookData'
const meta: Meta<typeof StandaloneHotelCardDialog> = { const meta: Meta<typeof StandaloneHotelCardDialog> = {
title: 'Components/StandaloneHotelCardDialog', title: 'Components/StandaloneHotelCardDialog',
@@ -15,29 +17,10 @@ type Story = StoryObj<typeof StandaloneHotelCardDialog>
export const Default: Story = { export const Default: Story = {
args: { args: {
data: { lang: Lang.en,
name: 'Hotel Name', isUserLoggedIn: false,
image: { onClick: fn(),
url: 'img/img2.jpg', data: hotelPins[0],
alt: 'Alt text',
},
coordinates: {
lat: 0,
lng: 0,
},
chequePrice: null,
publicPrice: 100,
memberPrice: 200,
redemptionPrice: null,
voucherPrice: null,
rateType: null,
currency: 'SEK',
amenities: [],
ratings: { tripAdvisor: 5 },
operaId: '123',
facilityIds: [],
hasEnoughPoints: false,
},
handleClose: fn(), handleClose: fn(),
}, },
} }

View File

@@ -3,30 +3,27 @@
import { useState } from 'react' import { useState } from 'react'
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl'
import { selectRate } from '@scandic-hotels/common/constants/routes/hotelReservation'
import Body from '../../../Body'
import Caption from '../../../Caption'
import Footnote from '../../../Footnote'
import { IconButton } from '../../../IconButton' import { IconButton } from '../../../IconButton'
import { MaterialIcon } from '../../../Icons/MaterialIcon' import { MaterialIcon } from '../../../Icons/MaterialIcon'
import Link from '../../../Link'
import { OldDSButton as Button } from '../../../OldDSButton'
import Subtitle from '../../../Subtitle'
import { Typography } from '../../../Typography' import { Typography } from '../../../Typography'
import { HotelCardDialogImage } from '../../HotelCardDialogImage' import { HotelCardDialogImage } from '../../HotelCardDialogImage'
import { NoPriceAvailableCard } from '../../NoPriceAvailableCard' import { NoPriceAvailableCard } from '../../NoPriceAvailableCard'
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
import { Lang } from '@scandic-hotels/common/constants/language' import { Lang } from '@scandic-hotels/common/constants/language'
import { selectRate } from '@scandic-hotels/common/constants/routes/hotelReservation'
import { useUrlWithSearchParam } from '@scandic-hotels/common/hooks/useUrlWithSearchParam'
import ButtonLink from '../../../ButtonLink'
import { FacilityToIcon } from '../../../FacilityToIcon' import { FacilityToIcon } from '../../../FacilityToIcon'
import { HotelPin } from '../../../Map/types' import { HotelPin } from '../../../Map/types'
import { HotelPointsRow } from '../../HotelPointsRow' import { HotelPointsRow } from '../../HotelPointsRow'
import { RoomPrice } from '../../RoomPrice'
import styles from './standaloneHotelCardDialog.module.css' import styles from './standaloneHotelCardDialog.module.css'
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
interface StandaloneHotelCardProps { interface StandaloneHotelCardProps {
data: HotelPin
lang: Lang lang: Lang
data: HotelPin
isUserLoggedIn: boolean isUserLoggedIn: boolean
handleClose: () => void handleClose: () => void
onClick?: () => void onClick?: () => void
@@ -63,6 +60,17 @@ export function StandaloneHotelCardDialog({
}) })
const shouldShowNotEnoughPoints = redemptionPrice && !hasEnoughPoints const shouldShowNotEnoughPoints = redemptionPrice && !hasEnoughPoints
const selectRateUrl = useUrlWithSearchParam(
{ key: 'hotel', value: operaId },
selectRate(lang)
)
const showPriceCard = !!(
publicPrice ||
memberPrice ||
redemptionPrice ||
voucherPrice ||
chequePrice
)
return ( return (
<div className={styles.container}> <div className={styles.container}>
@@ -86,9 +94,9 @@ export function StandaloneHotelCardDialog({
position="left" position="left"
/> />
<div className={styles.content}> <div className={styles.content}>
<div className={styles.name}> <Typography variant="Body/Paragraph/mdBold">
<Body textTransform="bold">{name}</Body> <h4 className={styles.name}>{name}</h4>
</div> </Typography>
<div className={styles.facilities}> <div className={styles.facilities}>
{amenities.slice(0, 3).map((facility) => { {amenities.slice(0, 3).map((facility) => {
const Icon = ( const Icon = (
@@ -96,173 +104,93 @@ export function StandaloneHotelCardDialog({
) )
return ( return (
<div className={styles.facilitiesItem} key={facility.id}> <div className={styles.facilitiesItem} key={facility.id}>
{Icon} {Icon && Icon}
<Footnote color="uiTextMediumContrast"> <Typography variant="Body/Supporting text (caption)/smRegular">
{facility.name} <span>{facility.name}</span>
</Footnote> </Typography>
</div> </div>
) )
})} })}
</div> </div>
<div className={styles.pricesContainer}> {showPriceCard ? (
{publicPrice || <>
memberPrice || <div className={styles.priceCard}>
redemptionPrice || <Typography variant="Body/Supporting text (caption)/smBold">
voucherPrice || <p>
chequePrice ? ( {redemptionPrice
<> ? intl.formatMessage({ defaultMessage: 'Available rates' })
<div className={styles.priceCard}> : intl.formatMessage({ defaultMessage: 'From' })}
{redemptionPrice ? ( </p>
<Caption> </Typography>
{intl.formatMessage({ {chequePrice ? (
defaultMessage: 'Available rates', <RoomPrice
})} price={chequePrice.numberOfCheques}
</Caption> currency="CC"
) : ( includePerNight={false}
<Caption type="bold">
{intl.formatMessage({
defaultMessage: 'From',
})}
</Caption>
)}
{chequePrice && (
<Subtitle type="two">
{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}
<Body asChild>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{voucherPrice && (
<Subtitle type="two">
{intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: voucherPrice,
currency,
}
)}
<Body asChild>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{publicPrice && !isUserLoggedIn && (
<Subtitle type="two">
{intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: publicPrice,
currency,
}
)}
<Body asChild>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{memberPrice && (
<Subtitle type="two" color="red">
{intl.formatMessage(
{
defaultMessage: '{price} {currency}',
},
{
price: memberPrice,
currency,
}
)}
<Body asChild color="red">
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>
/
{intl.formatMessage({
defaultMessage: 'night',
})}
</span>
</Body>
</Subtitle>
)}
{redemptionPrice && (
<HotelPointsRow
pointsPerStay={redemptionPrice}
pointsCurrency={pointsCurrency}
/>
)}
</div>
{shouldShowNotEnoughPoints ? (
<div className={styles.notEnoughPointsButton}>
<Typography variant="Body/Paragraph/mdBold">
<span>{notEnoughPointsLabel}</span>
</Typography>
</div>
) : (
<Button
asChild
theme="base"
size="small"
className={styles.button}
onClick={onClick}
> >
<Link {chequePrice.additionalPricePerStay > 0 ? (
href={`${selectRate(lang)}?hotel=${operaId}`} <>
color="none" <Typography variant="Body/Paragraph/mdBold">
keepSearchParams {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
> <span> + </span>
{intl.formatMessage({ </Typography>
defaultMessage: 'See rooms', <Typography variant="Title/Subtitle/md">
})} <span>{chequePrice.additionalPricePerStay}</span>
</Link> </Typography>
</Button> <Typography variant="Body/Paragraph/mdBold">
)} <span> {chequePrice.currency}</span>
</> </Typography>
) : ( </>
<NoPriceAvailableCard /> ) : null}
)} </RoomPrice>
</div> ) : null}
{voucherPrice ? (
<RoomPrice price={voucherPrice} currency={currency} />
) : null}
{/* Show public price if:
1) user is not logged in, or
2) user is logged in but no member price is available (use of booking codes)
*/}
{publicPrice && (!isUserLoggedIn || !memberPrice) ? (
<RoomPrice price={publicPrice} currency={currency} />
) : null}
{memberPrice ? (
<RoomPrice
className={styles.memberPrice}
price={memberPrice}
currency={currency}
/>
) : null}
{redemptionPrice ? (
<HotelPointsRow
pointsPerStay={redemptionPrice}
pointsCurrency={pointsCurrency}
/>
) : null}
</div>
{shouldShowNotEnoughPoints ? (
<Typography variant="Body/Paragraph/mdBold">
<div className={styles.notEnoughPointsButton}>
{notEnoughPointsLabel}
</div>
</Typography>
) : (
<ButtonLink
href={selectRateUrl}
variant="Primary"
color="Primary"
size="Small"
onClick={onClick}
>
{intl.formatMessage({
defaultMessage: 'See rooms',
})}
</ButtonLink>
)}
</>
) : (
<NoPriceAvailableCard />
)}
</div> </div>
</div> </div>
) )

View File

@@ -1,25 +1,30 @@
.container { .container {
flex-direction: row;
display: flex;
position: relative; position: relative;
background: var(--Base-Surface-Primary-light-Normal); display: flex;
background-color: var(--Surface-Primary-Default);
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1); box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
} }
.closeButton {
position: absolute;
top: var(--Space-x05);
right: var(--Space-x05);
z-index: 1;
}
.content { .content {
width: 100%; width: 225px;
max-width: 220px;
padding: var(--Space-x15); padding: var(--Space-x15);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--Space-x1);
} }
.name { .name {
height: 48px; height: 48px;
max-width: 180px; width: 180px;
margin-bottom: var(--Space-x05);
display: flex; display: flex;
align-items: center; align-items: center;
padding-right: var(--Space-x1);
} }
.facilities { .facilities {
@@ -32,40 +37,23 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--Space-x05); gap: var(--Space-x05);
} color: var(--Text-Secondary);
.prices {
display: flex;
flex-direction: column;
gap: var(--Space-x1);
justify-content: space-between;
} }
.priceCard { .priceCard {
border-radius: var(--Corner-radius-md); border-radius: var(--Corner-radius-md);
padding: var(--Space-x05) var(--Space-x1); padding: var(--Space-x05) var(--Space-x1);
background: var(--Base-Surface-Secondary-light-Normal); background: var(--Base-Surface-Secondary-light-Normal);
margin-top: var(--Space-x1);
} }
.pricesContainer { .memberPrice {
display: flex; color: var(--Text-Accent-Primary);
flex-direction: column;
gap: var(--Space-x1);
justify-content: space-between;
} }
.content .button { .content .seeRoomsButton {
margin-top: auto; margin-top: auto;
} }
.closeButton {
position: absolute;
top: 8px;
right: 8px;
z-index: 1;
}
.notEnoughPointsButton { .notEnoughPointsButton {
border-radius: var(--Corner-radius-rounded); border-radius: var(--Corner-radius-rounded);
border-width: 2px; border-width: 2px;

View File

@@ -1,6 +1,3 @@
.poinstRow { .roomPrice {
display: flex;
gap: var(--Space-x1);
align-items: baseline;
color: var(--Text-Default); color: var(--Text-Default);
} }

View File

@@ -1,10 +1,10 @@
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl'
import Caption from '../../Caption' import { RoomPrice } from '../../HotelCard/RoomPrice'
import Subtitle from '../../Subtitle' import { Typography } from '../../Typography'
import styles from './hotelPointsRow.module.css'
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency' import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
import styles from './hotelPointsRow.module.css'
export type PointsRowProps = { export type PointsRowProps = {
pointsPerStay: number pointsPerStay: number
@@ -21,27 +21,28 @@ export function HotelPointsRow({
const intl = useIntl() const intl = useIntl()
return ( return (
<div className={styles.poinstRow}> <RoomPrice
<Subtitle type="two" color="uiTextHighContrast"> className={styles.roomPrice}
{pointsPerStay} price={pointsPerStay}
</Subtitle> currency={
<Caption color="uiTextHighContrast"> pointsCurrency ?? intl.formatMessage({ defaultMessage: 'Points' })
{pointsCurrency ?? }
intl.formatMessage({ includePerNight={false}
defaultMessage: 'Points', >
})}
</Caption>
{additionalPricePerStay ? ( {additionalPricePerStay ? (
<> <>
{'+'} <Typography variant="Body/Paragraph/mdBold">
<Subtitle type="two" color="uiTextHighContrast"> {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{additionalPricePerStay} <span> + </span>
</Subtitle> </Typography>
<Caption color="uiTextHighContrast"> <Typography variant="Title/Subtitle/md">
{additionalPriceCurrency} <span>{additionalPricePerStay}</span>
</Caption> </Typography>
<Typography variant="Body/Paragraph/mdBold">
<span> {additionalPriceCurrency}</span>
</Typography>
</> </>
) : null} ) : null}
</div> </RoomPrice>
) )
} }

View File

@@ -0,0 +1,36 @@
import { useIntl } from 'react-intl'
import { Typography } from '../../Typography'
interface RoomPriceProps extends React.HTMLAttributes<HTMLParagraphElement> {
price: number
currency: string
includePerNight?: boolean
}
export function RoomPrice({
price,
currency,
children,
includePerNight = true,
...props
}: RoomPriceProps) {
const intl = useIntl()
return (
<p {...props}>
<Typography variant="Title/Subtitle/md">
<span>{price}</span>
</Typography>
<Typography variant="Body/Paragraph/mdBold">
<span> {currency}</span>
</Typography>
{children}
{includePerNight ? (
<Typography variant="Body/Supporting text (caption)/smRegular">
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>/{intl.formatMessage({ defaultMessage: 'night' })}</span>
</Typography>
) : null}
</p>
)
}

View File

@@ -5,12 +5,13 @@ import {
} from '@vis.gl/react-google-maps' } from '@vis.gl/react-google-maps'
import { useMediaQuery } from 'usehooks-ts' import { useMediaQuery } from 'usehooks-ts'
import { HotelPin } from './HotelPin' import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
import { Lang } from '@scandic-hotels/common/constants/language'
import { useIntl } from 'react-intl'
import { StandaloneHotelCardDialog } from '../../../HotelCard/HotelDialogCard/StandaloneHotelCardDialog'
import type { HotelPin as HotelPinType } from '../../types' import type { HotelPin as HotelPinType } from '../../types'
import styles from './hotelListingMapContent.module.css' import styles from './hotelListingMapContent.module.css'
import { StandaloneHotelCardDialog } from '../../../HotelCard/HotelDialogCard/StandaloneHotelCardDialog' import { HotelPin } from './HotelPin'
import { Lang } from '@scandic-hotels/common/constants/language'
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
export type HotelListingMapContentProps = { export type HotelListingMapContentProps = {
hotelPins: HotelPinType[] hotelPins: HotelPinType[]
@@ -36,6 +37,7 @@ export function HotelListingMapContent({
onClickHotel, onClickHotel,
pointsCurrency, pointsCurrency,
}: HotelListingMapContentProps) { }: HotelListingMapContentProps) {
const intl = useIntl()
const isDesktop = useMediaQuery('(min-width: 900px)') const isDesktop = useMediaQuery('(min-width: 900px)')
const toggleActiveHotelPin = ( const toggleActiveHotelPin = (
@@ -62,6 +64,10 @@ export function HotelListingMapContent({
pin.chequePrice?.numberOfCheques ?? pin.chequePrice?.numberOfCheques ??
null null
const pinCurrency = pin.redemptionPrice
? intl.formatMessage({ defaultMessage: 'Points' })
: pin.currency
const hotelAdditionalPrice = pin.chequePrice const hotelAdditionalPrice = pin.chequePrice
? pin.chequePrice.additionalPricePerStay ? pin.chequePrice.additionalPricePerStay
: undefined : undefined
@@ -114,7 +120,7 @@ export function HotelListingMapContent({
<HotelPin <HotelPin
isActive={isActiveOrHovered} isActive={isActiveOrHovered}
hotelPrice={hotelPrice} hotelPrice={hotelPrice}
currency={pin.currency} currency={pinCurrency}
hotelAdditionalPrice={hotelAdditionalPrice} hotelAdditionalPrice={hotelAdditionalPrice}
hotelAdditionalCurrency={hotelAdditionalCurrency} hotelAdditionalCurrency={hotelAdditionalCurrency}
/> />

View File

@@ -1,10 +1,11 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite' import type { Meta, StoryObj } from '@storybook/nextjs-vite'
// import { expect, fn } from 'storybook/test' // import { expect, fn } from 'storybook/test'
import { InteractiveMap } from '.'
import { Lang } from '@scandic-hotels/common/constants/language' import { Lang } from '@scandic-hotels/common/constants/language'
import { APIProvider } from '@vis.gl/react-google-maps' import { APIProvider } from '@vis.gl/react-google-maps'
import { useState } from 'react' import { useState } from 'react'
import { InteractiveMap } from '.'
import { hotelPins } from './storybookData'
const meta: Meta<typeof InteractiveMap> = { const meta: Meta<typeof InteractiveMap> = {
title: 'Components/Map/Interactive Map', title: 'Components/Map/Interactive Map',
@@ -19,159 +20,7 @@ type Story = StoryObj<typeof InteractiveMap>
export const PrimaryDefault: Story = { export const PrimaryDefault: Story = {
args: { args: {
lang: Lang.en, lang: Lang.en,
hotelPins: [ hotelPins,
{
coordinates: {
lat: 59.331303,
lng: 18.065542,
},
name: 'Downtown Camper by Scandic',
chequePrice: null,
publicPrice: 1100,
memberPrice: 1067,
redemptionPrice: null,
voucherPrice: null,
rateType: 'Regular',
currency: 'SEK',
amenities: [
{
filter: 'Hotel facilities',
icon: 'Pool',
id: 1831,
name: 'Pool',
public: true,
sortOrder: 7000,
slug: 'pool',
},
{
filter: 'Hotel facilities',
icon: 'Restaurant',
id: 1383,
name: 'Restaurant',
public: true,
sortOrder: 6000,
slug: 'restaurant',
},
{
filter: 'None',
icon: 'KayaksForLoan',
id: 162585,
name: 'Kayaks for loan',
public: true,
sortOrder: 5000,
slug: 'kayaks-for-loan',
},
{
filter: 'Hotel facilities',
icon: 'None',
id: 239348,
name: 'Rooftop bar',
public: false,
sortOrder: 4000,
slug: 'rooftop-bar',
},
{
filter: 'None',
icon: 'BikesForLoan',
id: 5550,
name: 'Bikes for loan',
public: true,
sortOrder: 3000,
slug: 'bikes-for-loan',
},
],
ratings: {
tripAdvisor: 4.4,
},
operaId: '879',
facilityIds: [
1831, 1383, 162585, 239348, 5550, 162586, 5806, 1014, 1835, 1829,
1379, 1382, 162587, 1017, 1378, 1408, 1833, 971, 1834, 162584, 1381,
229144, 267806,
],
hasEnoughPoints: false,
image: {
alt: 'Bar of Downtown Camper by Scandic in Stockholm',
url: 'https://images-test.scandichotels.com/publishedmedia/z68596isempb61xm2ns9/Scandic_Downtown_Camper_spa_wellness_the_nest_swim.jpg',
},
},
{
coordinates: {
lat: 59.33469,
lng: 18.061586,
},
name: 'Haymarket by Scandic',
chequePrice: null,
publicPrice: null,
memberPrice: 9999,
redemptionPrice: null,
voucherPrice: null,
rateType: 'Regular',
currency: 'SEK',
amenities: [
{
filter: 'Hotel facilities',
icon: 'Restaurant',
id: 1383,
name: 'Restaurant',
public: true,
sortOrder: 6000,
slug: 'restaurant',
},
{
filter: 'None',
icon: 'None',
id: 5806,
name: 'Meeting / conference facilities',
public: true,
sortOrder: 1500,
slug: 'meeting-conference-facilities',
},
{
filter: 'Hotel facilities',
icon: 'Bar',
id: 1014,
name: 'Bar',
public: true,
sortOrder: 1401,
slug: 'bar',
},
{
filter: 'Hotel facilities',
icon: 'PetFriendlyRooms',
id: 1835,
name: 'Pet-friendly rooms',
public: true,
sortOrder: 1201,
slug: 'pet-friendly-rooms',
},
{
filter: 'Hotel facilities',
icon: 'Gym',
id: 1829,
name: 'Gym',
public: true,
sortOrder: 1101,
slug: 'gym',
},
],
ratings: {
tripAdvisor: 4.1,
},
operaId: '890',
facilityIds: [
1383, 5806, 1014, 1835, 1829, 1382, 162587, 1017, 1833, 971, 1834,
1381, 1406, 1913, 345180, 375885,
],
hasEnoughPoints: false,
image: {
alt: 'Bar',
url: 'https://images-test.scandichotels.com/publishedmedia/6wobp0j1ocvoopy1dmce/haymarket-by-scandic-bar-pauls_-3-.jpg',
},
},
],
isUserLoggedIn: false, isUserLoggedIn: false,
coordinates: { coordinates: {
lat: 59.32644916839965, lat: 59.32644916839965,

View File

@@ -16,9 +16,9 @@ import PoiMapMarkers from './PoiMapMarkers'
import styles from './interactiveMap.module.css' import styles from './interactiveMap.module.css'
import { HotelPin, MarkerInfo, PointOfInterest } from '../types'
import { Lang } from '@scandic-hotels/common/constants/language'
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency' import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
import { Lang } from '@scandic-hotels/common/constants/language'
import { HotelPin, MarkerInfo, PointOfInterest } from '../types'
export type InteractiveMapProps = { export type InteractiveMapProps = {
lang: Lang lang: Lang

View File

@@ -0,0 +1,379 @@
import { CurrencyEnum } from '@scandic-hotels/common/constants/currency'
import { HotelPin } from '../types'
export const hotelPins: HotelPin[] = [
{
coordinates: {
lat: 59.331303,
lng: 18.065542,
},
name: 'Downtown Camper by Scandic',
chequePrice: null,
publicPrice: 1100,
memberPrice: 1067,
redemptionPrice: null,
voucherPrice: null,
rateType: 'Regular',
currency: 'SEK',
amenities: [
{
filter: 'Hotel facilities',
icon: 'Pool',
id: 1831,
name: 'Pool',
public: true,
sortOrder: 7000,
slug: 'pool',
},
{
filter: 'Hotel facilities',
icon: 'Restaurant',
id: 1383,
name: 'Restaurant',
public: true,
sortOrder: 6000,
slug: 'restaurant',
},
{
filter: 'None',
icon: 'KayaksForLoan',
id: 162585,
name: 'Kayaks for loan',
public: true,
sortOrder: 5000,
slug: 'kayaks-for-loan',
},
{
filter: 'Hotel facilities',
icon: 'None',
id: 239348,
name: 'Rooftop bar',
public: false,
sortOrder: 4000,
slug: 'rooftop-bar',
},
{
filter: 'None',
icon: 'BikesForLoan',
id: 5550,
name: 'Bikes for loan',
public: true,
sortOrder: 3000,
slug: 'bikes-for-loan',
},
],
ratings: {
tripAdvisor: 4.4,
},
operaId: '879',
facilityIds: [
1831, 1383, 162585, 239348, 5550, 162586, 5806, 1014, 1835, 1829, 1379,
1382, 162587, 1017, 1378, 1408, 1833, 971, 1834, 162584, 1381, 229144,
267806,
],
hasEnoughPoints: false,
image: {
alt: 'Bar of Downtown Camper by Scandic in Stockholm',
url: 'https://images-test.scandichotels.com/publishedmedia/p0ffjkfyfnx502knqlxo/Scandic_Downtown_Camper_spa_wellness_the_nest_swim.jpg',
},
},
{
coordinates: {
lat: 59.33469,
lng: 18.061586,
},
name: 'Haymarket by Scandic',
chequePrice: null,
publicPrice: null,
memberPrice: 9999,
redemptionPrice: null,
voucherPrice: null,
rateType: 'Regular',
currency: 'SEK',
amenities: [
{
filter: 'Hotel facilities',
icon: 'Restaurant',
id: 1383,
name: 'Restaurant',
public: true,
sortOrder: 6000,
slug: 'restaurant',
},
{
filter: 'None',
icon: 'None',
id: 5806,
name: 'Meeting / conference facilities',
public: true,
sortOrder: 1500,
slug: 'meeting-conference-facilities',
},
{
filter: 'Hotel facilities',
icon: 'Bar',
id: 1014,
name: 'Bar',
public: true,
sortOrder: 1401,
slug: 'bar',
},
{
filter: 'Hotel facilities',
icon: 'PetFriendlyRooms',
id: 1835,
name: 'Pet-friendly rooms',
public: true,
sortOrder: 1201,
slug: 'pet-friendly-rooms',
},
{
filter: 'Hotel facilities',
icon: 'Gym',
id: 1829,
name: 'Gym',
public: true,
sortOrder: 1101,
slug: 'gym',
},
],
ratings: {
tripAdvisor: 4.1,
},
operaId: '890',
facilityIds: [
1383, 5806, 1014, 1835, 1829, 1382, 162587, 1017, 1833, 971, 1834, 1381,
1406, 1913, 345180, 375885,
],
hasEnoughPoints: false,
image: {
alt: 'Bar',
url: 'https://images-test.scandichotels.com/publishedmedia/lr2b7r655xl0sjcbgxt9/haymarket-by-scandic-bar-pauls_-3-.jpg',
},
},
{
coordinates: {
lat: 59.337166,
lng: 18.072765,
},
name: 'Scandic Anglais',
chequePrice: {
numberOfCheques: 1,
additionalPricePerStay: 500,
currency: CurrencyEnum.SEK,
},
publicPrice: null,
memberPrice: null,
redemptionPrice: null,
voucherPrice: null,
rateType: 'Regular',
currency: 'CC',
amenities: [
{
filter: 'Hotel facilities',
icon: 'Restaurant',
id: 1383,
name: 'Restaurant',
public: true,
sortOrder: 6000,
slug: 'restaurant',
},
{
filter: 'Hotel facilities',
icon: 'None',
id: 239348,
name: 'Rooftop bar',
public: false,
sortOrder: 4000,
slug: 'rooftop-bar',
},
{
filter: 'None',
icon: 'BikesForLoan',
id: 5550,
name: 'Bikes for loan',
public: true,
sortOrder: 3000,
slug: 'bikes-for-loan',
},
{
filter: 'None',
icon: 'None',
id: 5806,
name: 'Meeting / conference facilities',
public: true,
sortOrder: 1500,
slug: 'meeting-conference-facilities',
},
{
filter: 'Hotel facilities',
icon: 'Bar',
id: 1014,
name: 'Bar',
public: true,
sortOrder: 1401,
slug: 'bar',
},
],
ratings: {
tripAdvisor: 3.6,
},
operaId: '810',
facilityIds: [
1383, 239348, 5550, 5806, 1014, 1835, 1829, 1379, 2665, 1382, 162587,
1017, 1408, 1833, 971, 1834, 1405, 1406, 956, 1913,
],
hasEnoughPoints: false,
image: {
alt: 'lobby at scandic anglais in stockholm',
url: 'https://images-test.scandichotels.com/publishedmedia/wi0lzgxhbm4vmfguf7wm/scandic-anglais-lobby6.jpg',
},
},
{
coordinates: {
lat: 59.33099,
lng: 18.05926,
},
name: 'Scandic Continental',
chequePrice: null,
publicPrice: null,
memberPrice: null,
redemptionPrice: 65000,
voucherPrice: null,
rateType: 'Regular',
currency: 'Points',
amenities: [
{
filter: 'None',
icon: 'BikesForLoan',
id: 5550,
name: 'Bikes for loan',
public: true,
sortOrder: 3000,
slug: 'bikes-for-loan',
},
{
filter: 'None',
icon: 'None',
id: 5806,
name: 'Meeting / conference facilities',
public: true,
sortOrder: 1500,
slug: 'meeting-conference-facilities',
},
{
filter: 'Hotel facilities',
icon: 'PetFriendlyRooms',
id: 1835,
name: 'Pet-friendly rooms',
public: true,
sortOrder: 1201,
slug: 'pet-friendly-rooms',
},
{
filter: 'Hotel facilities',
icon: 'Gym',
id: 1829,
name: 'Gym',
public: true,
sortOrder: 1101,
slug: 'gym',
},
{
filter: 'Hotel facilities',
icon: 'Sauna',
id: 1379,
name: 'Sauna',
public: true,
sortOrder: 1001,
slug: 'sauna',
},
],
ratings: {
tripAdvisor: 4.2,
},
operaId: '811',
facilityIds: [
5550, 5806, 1835, 1829, 1379, 2665, 1606, 1382, 1017, 1378, 1408, 1833,
971, 1834, 162583, 1406, 1607, 1911, 1913, 229144,
],
hasEnoughPoints: false,
image: {
alt: 'Exterior',
url: 'https://images-test.scandichotels.com/publishedmedia/xnvuuzvogv7dzt3nmxit/Scandic_Continental_Exterior_Vasagatan_Day.jpg',
},
},
{
coordinates: {
lat: 59.323063,
lng: 18.069921,
},
name: 'Scandic Gamla Stan',
chequePrice: null,
publicPrice: null,
memberPrice: null,
redemptionPrice: null,
voucherPrice: 1,
rateType: 'Regular',
currency: 'Voucher',
amenities: [
{
filter: 'Hotel facilities',
icon: 'PetFriendlyRooms',
id: 1835,
name: 'Pet-friendly rooms',
public: true,
sortOrder: 1201,
slug: 'pet-friendly-rooms',
},
{
filter: 'Hotel facilities',
icon: 'OutdoorTerrace',
id: 1382,
name: 'Outdoor terrace',
public: true,
sortOrder: 550,
slug: 'outdoor-terrace',
},
{
filter: 'None',
icon: 'Shop',
id: 1408,
name: 'Scandic Shop 24 hrs',
public: true,
sortOrder: 301,
slug: 'scandic-shop-24-hrs',
},
{
filter: 'None',
icon: 'FreeWiFi',
id: 1833,
name: 'Free WiFi',
public: true,
sortOrder: 250,
slug: 'free-wifi',
},
{
filter: 'None',
icon: 'LaundryService',
id: 1834,
name: 'Laundry service',
public: true,
sortOrder: 199,
slug: 'laundry-service',
},
],
ratings: {
tripAdvisor: 4.2,
},
operaId: '821',
facilityIds: [
1835, 1382, 1408, 1833, 1834, 1405, 345180, 332224, 375885, 238849,
],
hasEnoughPoints: false,
image: {
alt: 'Facade Entrance Scandic Gamla Stan Stockholm',
url: 'https://images-test.scandichotels.com/publishedmedia/9attpqkkfy6uwc6keyvg/Scandic-Gamla-Stan-Exterior-facade.jpg',
},
},
]