diff --git a/components/HotelReservation/HotelCard/hotelCard.module.css b/components/HotelReservation/HotelCard/hotelCard.module.css index 74569c12f..95ea52ad2 100644 --- a/components/HotelReservation/HotelCard/hotelCard.module.css +++ b/components/HotelReservation/HotelCard/hotelCard.module.css @@ -12,6 +12,10 @@ width: 100%; } +.card.active { + border: 1px solid var(--Base-Border-Hover); +} + .imageContainer { grid-area: image; position: relative; diff --git a/components/HotelReservation/HotelCard/index.tsx b/components/HotelReservation/HotelCard/index.tsx index 9a08d0425..3c5dc72ff 100644 --- a/components/HotelReservation/HotelCard/index.tsx +++ b/components/HotelReservation/HotelCard/index.tsx @@ -17,12 +17,13 @@ import { hotelCardVariants } from "./variants" import styles from "./hotelCard.module.css" -import { HotelCardListingType } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" +import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps" export default function HotelCard({ hotel, - type = HotelCardListingType.PageListing, + type = HotelCardListingTypeEnum.PageListing, + state = "default", }: HotelCardProps) { const intl = useIntl() @@ -33,6 +34,7 @@ export default function HotelCard({ const classNames = hotelCardVariants({ type, + state, }) return ( diff --git a/components/HotelReservation/HotelCard/variants.ts b/components/HotelReservation/HotelCard/variants.ts index e71cc44a1..7a136e74b 100644 --- a/components/HotelReservation/HotelCard/variants.ts +++ b/components/HotelReservation/HotelCard/variants.ts @@ -8,8 +8,13 @@ export const hotelCardVariants = cva(styles.card, { pageListing: styles.pageListing, mapListing: styles.mapListing, }, + state: { + active: styles.active, + default: styles.default, + }, }, defaultVariants: { type: "pageListing", + state: "default", }, }) diff --git a/components/HotelReservation/HotelCardDialog/hotelCardDialog.module.css b/components/HotelReservation/HotelCardDialog/hotelCardDialog.module.css index 13e295588..dce743fa1 100644 --- a/components/HotelReservation/HotelCardDialog/hotelCardDialog.module.css +++ b/components/HotelReservation/HotelCardDialog/hotelCardDialog.module.css @@ -10,30 +10,39 @@ .dialogContainer { border: 1px solid var(--Base-Border-Subtle); border-radius: var(--Corner-radius-Medium); - width: 402px; - min-height: 227px; + min-width: 334px; 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; +} + +.closeIcon { + position: absolute; + top: 7px; + right: 7px; } .imageContainer { position: relative; - min-height: 227px; - width: 177px; -} - -.tripAdvisor { - display: none; + min-width: 177px; } .imageContainer img { object-fit: cover; } +.tripAdvisor { + position: absolute; + display: block; + left: 7px; + top: 7px; +} + .content { width: 100%; + min-width: 201px; padding: var(--Spacing-x-one-and-half); gap: var(--Spacing-x1); display: flex; @@ -65,6 +74,13 @@ color: var(--Base-Text-Subtle-light-Normal); } -.button { +.content .button { margin-top: auto; } + +@media (min-width: 768px) { + .facilities, + .memberPrice { + display: none; + } +} diff --git a/components/HotelReservation/HotelCardDialog/index.tsx b/components/HotelReservation/HotelCardDialog/index.tsx index 9f121cf95..bb5fb7b48 100644 --- a/components/HotelReservation/HotelCardDialog/index.tsx +++ b/components/HotelReservation/HotelCardDialog/index.tsx @@ -3,6 +3,7 @@ import { useIntl } from "react-intl" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" +import { CloseLargeIcon } from "@/components/Icons" import TripAdvisorIcon from "@/components/Icons/TripAdvisor" import Image from "@/components/Image" import Button from "@/components/TempDesignSystem/Button" @@ -13,17 +14,19 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./hotelCardDialog.module.css" -import { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" +import { HotelCardDialogProps } from "@/types/components/hotelReservation/selectHotel/map" export default function HotelCardDialog({ pin, isOpen, -}: { - isOpen: boolean - pin: HotelPin -}) { + handleClose, +}: HotelCardDialogProps) { const intl = useIntl() + if (!pin) { + return null + } + const { name, publicPrice, @@ -34,15 +37,20 @@ export default function HotelCardDialog({ ratings, } = pin + const firstImage = images[0]?.imageSizes?.small + const altText = images[0]?.metaData?.altText + return (
+
- {images[0].metaData.altText} + {altText}
@@ -58,7 +66,9 @@ export default function HotelCardDialog({ return (
{IconComponent && } - {facility.name} + + {facility.name} +
) })} @@ -72,7 +82,7 @@ export default function HotelCardDialog({ {memberPrice && ( - + {memberPrice} {currency} /{intl.formatMessage({ id: "night" })} diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx index 9a7b4eecf..cba64921b 100644 --- a/components/HotelReservation/HotelCardListing/index.tsx +++ b/components/HotelReservation/HotelCardListing/index.tsx @@ -9,13 +9,14 @@ import HotelCard from "../HotelCard" import styles from "./hotelCardListing.module.css" import { - HotelCardListingProps, - HotelCardListingType, + type HotelCardListingProps, + HotelCardListingTypeEnum, } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" export default function HotelCardListing({ hotelData, - type = HotelCardListingType.PageListing, + type = HotelCardListingTypeEnum.PageListing, + state = "default", }: HotelCardListingProps) { const searchParams = useSearchParams() @@ -36,7 +37,12 @@ export default function HotelCardListing({
{hotels?.length ? ( hotels.map((hotel) => ( - + )) ) : ( No hotels found diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css b/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css index 68f018601..253df80d6 100644 --- a/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css @@ -5,8 +5,8 @@ @media (min-width: 768px) { .hotelListing { display: block; - width: 420px; - padding: var(--Spacing-x3) var(--Spacing-x4); + width: 100%; overflow-y: auto; + padding-top: var(--Spacing-x2); } } diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx index 2f5d9cb0b..15a31c35c 100644 --- a/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx @@ -4,15 +4,19 @@ import HotelCardListing from "@/components/HotelReservation/HotelCardListing" import styles from "./hotelListing.module.css" -import { HotelCardListingType } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" +import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { HotelListingProps } from "@/types/components/hotelReservation/selectHotel/map" -export default function HotelListing({ hotels }: HotelListingProps) { +export default function HotelListing({ + hotels, + cardState = "default", +}: HotelListingProps) { return (
) diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx index 82887ea02..a238410be 100644 --- a/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx @@ -1,7 +1,7 @@ "use client" import { APIProvider } from "@vis.gl/react-google-maps" import { useRouter, useSearchParams } from "next/navigation" -import { useState } from "react" +import { useEffect, useState } from "react" import { useIntl } from "react-intl" import { selectHotel } from "@/constants/routes/hotelReservation" @@ -30,6 +30,29 @@ export default function SelectHotelMap({ const lang = useLang() const intl = useIntl() const [activeHotelPin, setActiveHotelPin] = useState(null) + const [showBackToTop, setShowBackToTop] = useState(false) + + useEffect(() => { + const hotelListingElement = document.querySelector( + `.${styles.listingContainer}` + ) + if (!hotelListingElement) return + + const handleScroll = () => { + const hasScrolledPast = hotelListingElement.scrollTop > 490 + setShowBackToTop(hasScrolledPast) + } + + hotelListingElement.addEventListener("scroll", handleScroll) + return () => hotelListingElement.removeEventListener("scroll", handleScroll) + }, []) + + function scrollToTop() { + const hotelListingElement = document.querySelector( + `.${styles.listingContainer}` + ) + hotelListingElement?.scrollTo({ top: 0, behavior: "smooth" }) + } function handleModalDismiss() { router.back() @@ -54,25 +77,41 @@ export default function SelectHotelMap({ return (
-
- - Filter and sort - {/* TODO: Add filter and sort button */} +
+
+ + Filter and sort + {/* TODO: Add filter and sort button */} +
+ + {showBackToTop && ( + + )}
- void -}) { - function toggleActiveHotelPin(pinName: string) { - onActiveHotelPinChange?.(activeHotelPin === pinName ? null : pinName) +}: HotelListingMapContentProps) { + const [hoveredHotelPin, setHoveredHotelPin] = useState(null) + + function toggleActiveHotelPin(pinName: string | null) { + if (onActiveHotelPinChange) { + onActiveHotelPinChange(activeHotelPin === pinName ? null : pinName) + setHoveredHotelPin(null) + } + } + + function isPinActiveOrHovered(pinName: string) { + return activeHotelPin === pinName || hoveredHotelPin === pinName } return (
- {hotelPins.map((pin) => ( - onActiveHotelPinChange?.(pin.name)} - onMouseLeave={() => onActiveHotelPinChange?.(null)} - onClick={() => toggleActiveHotelPin(pin.name)} - > - - - { + const isActiveOrHovered = isPinActiveOrHovered(pin.name) + return ( + setHoveredHotelPin(pin.name)} + onMouseLeave={() => setHoveredHotelPin(null)} + onClick={() => + toggleActiveHotelPin( + activeHotelPin === pin.name ? null : pin.name + ) + } > - - - - void }) => { + event.stopPropagation() + if (activeHotelPin === pin.name) { + toggleActiveHotelPin(null) + } + }} + pin={pin} + /> + + - - {pin.memberPrice} {pin.currency} + + - - - - ))} + + + {pin.memberPrice} {pin.currency} + + + + + ) + })}
) } diff --git a/components/Maps/InteractiveMap/index.tsx b/components/Maps/InteractiveMap/index.tsx index 01681c268..2c858383c 100644 --- a/components/Maps/InteractiveMap/index.tsx +++ b/components/Maps/InteractiveMap/index.tsx @@ -18,7 +18,6 @@ export default function InteractiveMap({ activePoi, hotelPins, activeHotelPin, - hotels, mapId, closeButton, onActivePoiChange, @@ -56,7 +55,6 @@ export default function InteractiveMap({ activeHotelPin={activeHotelPin} hotelPins={hotelPins} onActiveHotelPinChange={onActiveHotelPinChange} - hotels={hotels} /> )} {pointsOfInterest && ( diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 6a253deb6..f387e7bfb 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -36,6 +36,7 @@ "At the hotel": "På hotellet", "Attractions": "Attraktioner", "Back to scandichotels.com": "Tilbage til scandichotels.com", + "Back to top": "Tilbage til top", "Bar": "Bar", "Based on availability": "Baseret på tilgængelighed", "Bed type": "Seng type", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index a26b4b751..fc30690c6 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -36,6 +36,7 @@ "At the hotel": "Im Hotel", "Attraction": "Attraktion", "Back to scandichotels.com": "Zurück zu scandichotels.com", + "Back to top": "Zurück zur Spitze", "Bar": "Bar", "Based on availability": "Je nach Verfügbarkeit", "Bed type": "Bettentyp", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 48f808528..c19df6f3b 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -38,6 +38,7 @@ "At the hotel": "At the hotel", "Attractions": "Attractions", "Back to scandichotels.com": "Back to scandichotels.com", + "Back to top": "Back to top", "Bar": "Bar", "Based on availability": "Based on availability", "Bed": "Bed", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 8abb46922..cc608b619 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -36,6 +36,7 @@ "At the hotel": "Hotellissa", "Attractions": "Nähtävyydet", "Back to scandichotels.com": "Takaisin scandichotels.com", + "Back to top": "Takaisin ylös", "Bar": "Bar", "Based on availability": "Saatavuuden mukaan", "Bed type": "Vuodetyyppi", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index fcbe82391..365f30364 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -36,6 +36,7 @@ "At the hotel": "På hotellet", "Attractions": "Attraksjoner", "Back to scandichotels.com": "Tilbake til scandichotels.com", + "Back to top": "Tilbake til toppen", "Bar": "Bar", "Based on availability": "Basert på tilgjengelighet", "Bed type": "Seng type", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 6d22fdea0..17d277374 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -36,6 +36,7 @@ "At the hotel": "På hotellet", "Attractions": "Sevärdheter", "Back to scandichotels.com": "Tillbaka till scandichotels.com", + "Back to top": "Tillbaka till toppen", "Bar": "Bar", "Based on availability": "Baserat på tillgänglighet", "Bed type": "Sängtyp", diff --git a/types/components/hotelPage/map/interactiveMap.ts b/types/components/hotelPage/map/interactiveMap.ts index 64d50dc15..218b9f882 100644 --- a/types/components/hotelPage/map/interactiveMap.ts +++ b/types/components/hotelPage/map/interactiveMap.ts @@ -12,7 +12,6 @@ export interface InteractiveMapProps { activePoi?: PointOfInterest["name"] | null hotelPins?: HotelPin[] activeHotelPin?: HotelPin["name"] | null - hotels?: HotelData[] mapId: string closeButton: ReactElement onActivePoiChange?: (poi: PointOfInterest["name"] | null) => void diff --git a/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts b/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts index 6da39e356..99f0c15a6 100644 --- a/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts +++ b/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts @@ -2,14 +2,15 @@ import { HotelsAvailabilityPrices } from "@/server/routers/hotels/output" import { Hotel } from "@/types/hotel" -export enum HotelCardListingType { +export enum HotelCardListingTypeEnum { MapListing = "mapListing", PageListing = "pageListing", } export type HotelCardListingProps = { hotelData: HotelData[] - type?: HotelCardListingType + type?: HotelCardListingTypeEnum + state?: "default" | "active" } export type HotelData = { diff --git a/types/components/hotelReservation/selectHotel/hotelCardProps.ts b/types/components/hotelReservation/selectHotel/hotelCardProps.ts index 655ed0274..17e0f664c 100644 --- a/types/components/hotelReservation/selectHotel/hotelCardProps.ts +++ b/types/components/hotelReservation/selectHotel/hotelCardProps.ts @@ -1,6 +1,10 @@ -import { HotelCardListingType, HotelData } from "./hotelCardListingProps" +import { + HotelCardListingTypeEnum, + type HotelData, +} from "./hotelCardListingProps" export type HotelCardProps = { hotel: HotelData - type?: HotelCardListingType + type?: HotelCardListingTypeEnum + state?: "default" | "active" } diff --git a/types/components/hotelReservation/selectHotel/map.ts b/types/components/hotelReservation/selectHotel/map.ts index e3630295d..a7ed3628e 100644 --- a/types/components/hotelReservation/selectHotel/map.ts +++ b/types/components/hotelReservation/selectHotel/map.ts @@ -12,6 +12,7 @@ import type { Coordinates } from "@/types/components/maps/coordinates" export interface HotelListingProps { hotels: HotelData[] + cardState?: "default" | "active" // pointsOfInterest: PointOfInterest[] // activePoi: PointOfInterest["name"] | null // onActivePoiChange: (poi: PointOfInterest["name"] | null) => void @@ -42,3 +43,15 @@ export type HotelPin = { amenities: Filter[] ratings: number | null } + +export interface HotelListingMapContentProps { + activeHotelPin?: HotelPin["name"] | null + hotelPins: HotelPin[] + onActiveHotelPinChange?: (pinName: string | null) => void +} + +export interface HotelCardDialogProps { + isOpen: boolean + pin: HotelPin + handleClose: (event: { stopPropagation: () => void }) => void +}