feat(SW-1780): Hotel address now leads to map view with hotel active
Approved-by: Michael Zetterberg Approved-by: Matilda Landström
This commit is contained in:
@@ -24,6 +24,7 @@ export default function ButtonLink({
|
|||||||
color,
|
color,
|
||||||
size,
|
size,
|
||||||
typography,
|
typography,
|
||||||
|
wrapping,
|
||||||
className,
|
className,
|
||||||
href,
|
href,
|
||||||
target,
|
target,
|
||||||
@@ -38,7 +39,7 @@ export default function ButtonLink({
|
|||||||
variant,
|
variant,
|
||||||
color,
|
color,
|
||||||
size,
|
size,
|
||||||
|
wrapping,
|
||||||
typography,
|
typography,
|
||||||
className,
|
className,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,6 +34,11 @@
|
|||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
padding: var(--Spacing-x-quarter) var(--Spacing-x1);
|
padding: var(--Spacing-x-quarter) var(--Spacing-x1);
|
||||||
border-radius: var(--Corner-radius-Small);
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
color: var(--Text-Interactive-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotelName {
|
||||||
|
color: var(--Text-Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
@@ -44,15 +49,26 @@
|
|||||||
.captions {
|
.captions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
|
color: var(--Text-Tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.addressButton {
|
||||||
|
background-color: transparent;
|
||||||
|
border-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--Text-Interactive-Secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--Text-Interactive-Hover-Secondary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.amenityList {
|
.amenityList {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Spacing-x-one-and-half);
|
gap: var(--Spacing-x-one-and-half);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
color: var(--UI-Text-Medium-contrast);
|
color: var(--Text-Secondary);
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
|
||||||
font-size: var(--typography-Caption-Underline-fontSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.amenityItem {
|
.amenityItem {
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import Link from "next/link"
|
|
||||||
import { useCallback, useEffect, useRef } from "react"
|
import { useCallback, useEffect, useRef } from "react"
|
||||||
|
import { Button as AriaButton } from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
|
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
|
||||||
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
|
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
||||||
|
|
||||||
|
import ButtonLink from "@/components/ButtonLink"
|
||||||
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 Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
||||||
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
||||||
import { getSingleDecimal } from "@/utils/numberFormatting"
|
import { getSingleDecimal } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
@@ -27,9 +26,11 @@ export default function HotelListItem(data: DestinationPagesHotelData) {
|
|||||||
const { hotel, url } = data
|
const { hotel, url } = data
|
||||||
const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
|
const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
|
||||||
const amenities = hotel.detailedFacilities.slice(0, 5)
|
const amenities = hotel.detailedFacilities.slice(0, 5)
|
||||||
|
const address = `${hotel.address.streetAddress}, ${hotel.address.city}`
|
||||||
|
|
||||||
const itemRef = useRef<HTMLElement>(null)
|
const itemRef = useRef<HTMLElement>(null)
|
||||||
const { setHoveredMarker, activeMarker } = useDestinationPageHotelsMapStore()
|
const { setHoveredMarker, activeMarker, setActiveMarker } =
|
||||||
|
useDestinationPageHotelsMapStore()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeMarker === hotel.id) {
|
if (activeMarker === hotel.id) {
|
||||||
@@ -74,10 +75,12 @@ export default function HotelListItem(data: DestinationPagesHotelData) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{hotel.tripadvisor && (
|
{hotel.tripadvisor && (
|
||||||
<div className={styles.tripAdvisor}>
|
<Typography variant="Title/Overline/sm">
|
||||||
<TripadvisorIcon color="Icon/Interactive/Default" />
|
<div className={styles.tripAdvisor}>
|
||||||
<Caption color="burgundy">{hotel.tripadvisor}</Caption>
|
<TripadvisorIcon color="CurrentColor" />
|
||||||
</div>
|
<span>{hotel.tripadvisor}</span>
|
||||||
|
</div>
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
@@ -85,50 +88,63 @@ export default function HotelListItem(data: DestinationPagesHotelData) {
|
|||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<HotelLogoIcon hotelId={hotel.id} hotelType={hotel.hotelType} />
|
<HotelLogoIcon hotelId={hotel.id} hotelType={hotel.hotelType} />
|
||||||
</div>
|
</div>
|
||||||
<Subtitle type="one" asChild>
|
<Typography variant="Title/Subtitle/lg">
|
||||||
<h3>{hotel.name}</h3>
|
<h3 className={styles.hotelName}>{hotel.name}</h3>
|
||||||
</Subtitle>
|
</Typography>
|
||||||
<div className={styles.captions}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Caption color="uiTextPlaceholder">
|
<div className={styles.captions}>
|
||||||
{hotel.address.streetAddress}
|
<Typography variant="Link/sm">
|
||||||
</Caption>
|
<AriaButton
|
||||||
<Divider variant="vertical" color="beige" />
|
className={styles.addressButton}
|
||||||
<Caption color="uiTextPlaceholder">
|
onPress={() => setActiveMarker(hotel.id)}
|
||||||
{intl.formatMessage(
|
>
|
||||||
{
|
{address}
|
||||||
defaultMessage: "{number} km to city center",
|
</AriaButton>
|
||||||
},
|
</Typography>
|
||||||
{
|
<Divider variant="vertical" color="beige" />
|
||||||
number: getSingleDecimal(
|
<p>
|
||||||
hotel.location.distanceToCentre / 1000
|
{intl.formatMessage(
|
||||||
),
|
{
|
||||||
}
|
defaultMessage: "{number} km to city center",
|
||||||
)}
|
},
|
||||||
</Caption>
|
{
|
||||||
</div>
|
number: getSingleDecimal(
|
||||||
|
hotel.location.distanceToCentre / 1000
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles.amenityList}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
{amenities.map((amenity) => {
|
<ul className={styles.amenityList}>
|
||||||
const Icon = (
|
{amenities.map((amenity) => {
|
||||||
<FacilityToIcon id={amenity.id} color="Icon/Default" size={20} />
|
return (
|
||||||
)
|
<li className={styles.amenityItem} key={amenity.id}>
|
||||||
return (
|
<FacilityToIcon
|
||||||
<li className={styles.amenityItem} key={amenity.id}>
|
id={amenity.id}
|
||||||
{Icon && Icon}
|
color="CurrentColor"
|
||||||
<span className={styles.amenityName}>{amenity.name}</span>
|
size={20}
|
||||||
</li>
|
/>
|
||||||
)
|
{amenity.name}
|
||||||
})}
|
</li>
|
||||||
</ul>
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</Typography>
|
||||||
{url && (
|
{url && (
|
||||||
<div className={styles.ctaWrapper}>
|
<div className={styles.ctaWrapper}>
|
||||||
<Button intent="tertiary" theme="base" size="small" asChild>
|
<ButtonLink
|
||||||
<Link href={url}>
|
href={url}
|
||||||
{intl.formatMessage({
|
variant="Tertiary"
|
||||||
defaultMessage: "See hotel details",
|
color="Primary"
|
||||||
})}
|
size="Small"
|
||||||
</Link>
|
>
|
||||||
</Button>
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "See hotel details",
|
||||||
|
})}
|
||||||
|
</ButtonLink>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,6 +28,11 @@
|
|||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
padding: var(--Spacing-x-quarter) var(--Spacing-x1);
|
padding: var(--Spacing-x-quarter) var(--Spacing-x1);
|
||||||
border-radius: var(--Corner-radius-Small);
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
color: var(--Text-Interactive-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotelName {
|
||||||
|
color: var(--Text-Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
@@ -38,15 +43,22 @@
|
|||||||
.captions {
|
.captions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
|
color: var(--Text-Tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.addressLink {
|
||||||
|
color: var(--Text-Interactive-Secondary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--Text-Interactive-Hover-Secondary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.amenityList {
|
.amenityList {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Spacing-x-one-and-half);
|
gap: var(--Spacing-x-one-and-half);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
color: var(--UI-Text-Medium-contrast);
|
color: var(--Text-Secondary);
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
|
||||||
font-size: var(--typography-Caption-Underline-fontSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.amenityItem {
|
.amenityItem {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import Link from "next/link"
|
import NextLink from "next/link"
|
||||||
import { useParams } from "next/navigation"
|
import { useParams } from "next/navigation"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
@@ -12,12 +12,10 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
|||||||
|
|
||||||
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
||||||
|
|
||||||
|
import ButtonLink from "@/components/ButtonLink"
|
||||||
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 Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
||||||
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
||||||
import { getSingleDecimal } from "@/utils/numberFormatting"
|
import { getSingleDecimal } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
@@ -34,6 +32,8 @@ export default function HotelListingItem(data: DestinationPagesHotelData) {
|
|||||||
const amenities = hotel.detailedFacilities.slice(0, 5)
|
const amenities = hotel.detailedFacilities.slice(0, 5)
|
||||||
const [mapUrl, setMapUrl] = useState<string | null>(null)
|
const [mapUrl, setMapUrl] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const address = `${hotel.address.streetAddress}, ${hotel.address.city}`
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const url = new URL(window.location.href)
|
const url = new URL(window.location.href)
|
||||||
url.searchParams.set("view", "map")
|
url.searchParams.set("view", "map")
|
||||||
@@ -55,85 +55,106 @@ export default function HotelListingItem(data: DestinationPagesHotelData) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{hotel.tripadvisor && (
|
{hotel.tripadvisor && (
|
||||||
<div className={styles.tripAdvisor}>
|
<Typography variant="Title/Overline/sm">
|
||||||
<TripadvisorIcon color="Icon/Interactive/Default" />
|
<div className={styles.tripAdvisor}>
|
||||||
<Caption color="burgundy">{hotel.tripadvisor}</Caption>
|
<TripadvisorIcon color="CurrentColor" />
|
||||||
</div>
|
<span>{hotel.tripadvisor}</span>
|
||||||
|
</div>
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.intro}>
|
<div className={styles.intro}>
|
||||||
<HotelLogoIcon hotelId={hotel.id} hotelType={hotel.hotelType} />
|
<HotelLogoIcon hotelId={hotel.id} hotelType={hotel.hotelType} />
|
||||||
<Subtitle type="one" asChild>
|
<Typography variant="Title/Subtitle/lg">
|
||||||
<h3>{hotel.name}</h3>
|
<h3 className={styles.hotelName}>{hotel.name}</h3>
|
||||||
</Subtitle>
|
</Typography>
|
||||||
<div className={styles.captions}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Caption color="uiTextPlaceholder">
|
<div className={styles.captions}>
|
||||||
{hotel.address.streetAddress}
|
{mapUrl ? (
|
||||||
</Caption>
|
<>
|
||||||
<Divider variant="vertical" color="beige" />
|
<Typography variant="Link/sm">
|
||||||
<Caption color="uiTextPlaceholder">
|
<NextLink
|
||||||
{intl.formatMessage(
|
onClick={() => setActiveMarker(hotel.id)}
|
||||||
{
|
href={mapUrl}
|
||||||
defaultMessage: "{number} km to city center",
|
className={styles.addressLink}
|
||||||
},
|
aria-label={intl.formatMessage({
|
||||||
{
|
defaultMessage: "See on map",
|
||||||
number: getSingleDecimal(
|
})}
|
||||||
hotel.location.distanceToCentre / 1000
|
>
|
||||||
),
|
{address}
|
||||||
}
|
</NextLink>
|
||||||
)}
|
</Typography>
|
||||||
</Caption>
|
<Divider variant="vertical" color="beige" />
|
||||||
</div>
|
</>
|
||||||
|
) : null}
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "{number} km to city center",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: getSingleDecimal(
|
||||||
|
hotel.location.distanceToCentre / 1000
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
{hotel.hotelDescription ? (
|
{hotel.hotelDescription ? (
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<p>{hotel.hotelDescription}</p>
|
<p>{hotel.hotelDescription}</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
<ul className={styles.amenityList}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
{amenities.map((amenity) => {
|
<ul className={styles.amenityList}>
|
||||||
const Icon = (
|
{amenities.map((amenity) => {
|
||||||
<FacilityToIcon id={amenity.id} color="Icon/Default" size={20} />
|
return (
|
||||||
)
|
<li className={styles.amenityItem} key={amenity.id}>
|
||||||
return (
|
<FacilityToIcon
|
||||||
<li className={styles.amenityItem} key={amenity.id}>
|
id={amenity.id}
|
||||||
{Icon && Icon}
|
color="CurrentColor"
|
||||||
{amenity.name}
|
size={20}
|
||||||
</li>
|
/>
|
||||||
)
|
{amenity.name}
|
||||||
})}
|
</li>
|
||||||
</ul>
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</Typography>
|
||||||
{mapUrl && (
|
{mapUrl && (
|
||||||
<Button intent="text" variant="icon" theme="base" asChild>
|
<ButtonLink
|
||||||
<Link
|
href={mapUrl}
|
||||||
href={mapUrl}
|
scroll={true}
|
||||||
scroll={true}
|
variant="Text"
|
||||||
onClick={() => setActiveMarker(hotel.id)}
|
color="Primary"
|
||||||
>
|
size="Medium"
|
||||||
{intl.formatMessage({
|
wrapping={false}
|
||||||
defaultMessage: "See on map",
|
onClick={() => setActiveMarker(hotel.id)}
|
||||||
})}
|
>
|
||||||
<MaterialIcon
|
{intl.formatMessage({
|
||||||
icon="chevron_right"
|
defaultMessage: "See on map",
|
||||||
size={20}
|
})}
|
||||||
color="CurrentColor"
|
<MaterialIcon icon="chevron_right" size={24} color="CurrentColor" />
|
||||||
/>
|
</ButtonLink>
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
{url && (
|
{url && (
|
||||||
<>
|
<>
|
||||||
<Divider variant="horizontal" color="primaryLightSubtle" />
|
<Divider variant="horizontal" color="primaryLightSubtle" />
|
||||||
|
|
||||||
<div className={styles.ctaWrapper}>
|
<div className={styles.ctaWrapper}>
|
||||||
<Button intent="tertiary" theme="base" size="small" asChild>
|
<ButtonLink
|
||||||
<Link href={url}>
|
href={url}
|
||||||
{intl.formatMessage({
|
variant="Tertiary"
|
||||||
defaultMessage: "See hotel details",
|
color="Primary"
|
||||||
})}
|
size="Small"
|
||||||
</Link>
|
>
|
||||||
</Button>
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "See hotel details",
|
||||||
|
})}
|
||||||
|
</ButtonLink>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -519,9 +519,7 @@ export async function getHotelsByHotelIds({
|
|||||||
const data: DestinationPagesHotelData = {
|
const data: DestinationPagesHotelData = {
|
||||||
hotel: {
|
hotel: {
|
||||||
id: hotel.id,
|
id: hotel.id,
|
||||||
galleryImages: hotel.galleryImages?.length
|
galleryImages: hotel.galleryImages,
|
||||||
? [hotel.galleryImages[0]]
|
|
||||||
: [],
|
|
||||||
name: hotel.name,
|
name: hotel.name,
|
||||||
tripadvisor: hotel.ratings?.tripAdvisor?.rating,
|
tripadvisor: hotel.ratings?.tripAdvisor?.rating,
|
||||||
detailedFacilities: hotel.detailedFacilities || [],
|
detailedFacilities: hotel.detailedFacilities || [],
|
||||||
|
|||||||
Reference in New Issue
Block a user