fix(SW-1111) Moved map to a page instead of intercepted route

This commit is contained in:
Pontus Dreij
2024-12-05 15:23:02 +01:00
parent 098e35d8d7
commit 69fa5b9b31
17 changed files with 140 additions and 155 deletions

View File

@@ -1,5 +0,0 @@
import LoadingSpinner from "@/components/LoadingSpinner"
export default function LoadingModal() {
return <LoadingSpinner />
}

View File

@@ -1,79 +0,0 @@
import { notFound } from "next/navigation"
import { env } from "@/env/server"
import { getCityCoordinates, getLocations } from "@/lib/trpc/memoizedRequests"
import { getHotelPins } from "@/components/HotelReservation/HotelCardDialogListing/utils"
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
import {
generateChildrenString,
getHotelReservationQueryParams,
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import { MapModal } from "@/components/MapModal"
import { setLang } from "@/i18n/serverContext"
import { fetchAvailableHotels, getFiltersFromHotels } from "../../utils"
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import type { LangParams, PageArgs } from "@/types/params"
export default async function SelectHotelMapPage({
params,
searchParams,
}: PageArgs<LangParams, SelectHotelSearchParams>) {
setLang(params.lang)
const locations = await getLocations()
if (!locations || "error" in locations) {
return null
}
const city = locations.data.find(
(location) =>
location.name.toLowerCase() === searchParams.city.toLowerCase()
)
if (!city) return notFound()
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
const selectHotelParams = new URLSearchParams(searchParams)
const selectHotelParamsObject =
getHotelReservationQueryParams(selectHotelParams)
const adults = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms
const children = selectHotelParamsObject.room[0].child
? generateChildrenString(selectHotelParamsObject.room[0].child)
: undefined // TODO: Handle multiple rooms
const hotels = await fetchAvailableHotels({
cityId: city.id,
roomStayStartDate: searchParams.fromDate,
roomStayEndDate: searchParams.toDate,
adults,
children,
})
const validHotels = hotels.filter(
(hotel): hotel is HotelData => hotel !== null
)
const hotelPins = getHotelPins(validHotels)
const filterList = getFiltersFromHotels(validHotels)
const cityCoordinates = await getCityCoordinates({
city: city.name,
hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress },
})
return (
<MapModal>
<SelectHotelMap
apiKey={googleMapsApiKey}
hotelPins={hotelPins}
mapId={googleMapId}
hotels={validHotels}
filterList={filterList}
cityCoordinates={cityCoordinates}
/>
</MapModal>
)
}

View File

@@ -1,3 +0,0 @@
export default function Default() {
return null
}

View File

@@ -1,3 +0,0 @@
export default function Page() {
return null
}

View File

@@ -2,4 +2,11 @@
min-height: 100dvh;
background-color: var(--Base-Background-Primary-Normal);
position: relative;
z-index: var(--header-z-index);
}
@media screen and (min-width: 768px) {
.layout {
z-index: 0;
}
}

View File

@@ -4,14 +4,6 @@ import { LangParams, LayoutArgs } from "@/types/params"
export default function HotelReservationLayout({
children,
modal,
}: React.PropsWithChildren<
LayoutArgs<LangParams> & { modal: React.ReactNode }
>) {
return (
<div className={styles.layout}>
{children}
{modal}
</div>
)
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
return <div className={styles.layout}>{children}</div>
}

View File

@@ -0,0 +1,20 @@
"use client"
import { useEffect } from "react"
import styles from "../layout.module.css"
import { LangParams, LayoutArgs } from "@/types/params"
export default function HotelReservationLayout({
children,
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
useEffect(() => {
document.body.style.overflow = "hidden"
return () => {
document.body.style.overflow = ""
}
}, [])
return <div className={styles.layout}>{children}</div>
}

View File

@@ -1 +1,79 @@
export { default } from "../@modal/(.)map/page"
import { notFound } from "next/navigation"
import { env } from "@/env/server"
import { getCityCoordinates, getLocations } from "@/lib/trpc/memoizedRequests"
import { getHotelPins } from "@/components/HotelReservation/HotelCardDialogListing/utils"
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
import {
generateChildrenString,
getHotelReservationQueryParams,
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import { MapContainer } from "@/components/MapContainer"
import { setLang } from "@/i18n/serverContext"
import { fetchAvailableHotels, getFiltersFromHotels } from "../utils"
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import type { LangParams, PageArgs } from "@/types/params"
export default async function SelectHotelMapPage({
params,
searchParams,
}: PageArgs<LangParams, SelectHotelSearchParams>) {
setLang(params.lang)
const locations = await getLocations()
if (!locations || "error" in locations) {
return null
}
const city = locations.data.find(
(location) =>
location.name.toLowerCase() === searchParams.city.toLowerCase()
)
if (!city) return notFound()
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
const selectHotelParams = new URLSearchParams(searchParams)
const selectHotelParamsObject =
getHotelReservationQueryParams(selectHotelParams)
const adults = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms
const children = selectHotelParamsObject.room[0].child
? generateChildrenString(selectHotelParamsObject.room[0].child)
: undefined // TODO: Handle multiple rooms
const hotels = await fetchAvailableHotels({
cityId: city.id,
roomStayStartDate: searchParams.fromDate,
roomStayEndDate: searchParams.toDate,
adults,
children,
})
const validHotels = hotels.filter(
(hotel): hotel is HotelData => hotel !== null
)
const hotelPins = getHotelPins(validHotels)
const filterList = getFiltersFromHotels(validHotels)
const cityCoordinates = await getCityCoordinates({
city: city.name,
hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress },
})
return (
<MapContainer>
<SelectHotelMap
apiKey={googleMapsApiKey}
hotelPins={hotelPins}
mapId={googleMapId}
hotels={validHotels}
filterList={filterList}
cityCoordinates={cityCoordinates}
/>
</MapContainer>
)
}

View File

@@ -3,7 +3,6 @@ import "@scandic-hotels/design-system/style.css"
import Script from "next/script"
import { env } from "@/env/server"
import TrpcProvider from "@/lib/trpc/Provider"
import TokenRefresher from "@/components/Auth/TokenRefresher"

View File

@@ -23,7 +23,7 @@ export default function List({
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
index={initialIndex + index}
key={location.id}
key={location.id + index}
location={location}
/>
))}

View File

@@ -26,6 +26,7 @@ import type { SearchProps } from "@/types/components/search"
import type { Location } from "@/types/trpc/routers/hotel/locations"
const name = "search"
export default function Search({ locations }: SearchProps) {
const { register, setValue, trigger, unregister } =
useFormContext<BookingWidgetSchema>()
@@ -166,7 +167,6 @@ export default function Search({ locations }: SearchProps) {
onInputValueChange={(inputValue) => dispatchInputValue(inputValue)}
>
{({
closeMenu,
getInputProps,
getItemProps,
getLabelProps,
@@ -207,9 +207,6 @@ export default function Search({ locations }: SearchProps) {
id: "Destinations & hotels",
}),
...register(name, {
onBlur: function () {
closeMenu()
},
onChange: handleOnChange,
}),
type: "search",

View File

@@ -1,6 +1,6 @@
"use client"
import { APIProvider } from "@vis.gl/react-google-maps"
import { useRouter, useSearchParams } from "next/navigation"
import { useSearchParams } from "next/navigation"
import { useEffect, useRef, useState } from "react"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
@@ -11,6 +11,7 @@ import { CloseIcon, CloseLargeIcon } from "@/components/Icons"
import InteractiveMap from "@/components/Maps/InteractiveMap"
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import useLang from "@/hooks/useLang"
import FilterAndSortModal from "../FilterAndSortModal"
@@ -29,7 +30,7 @@ export default function SelectHotelMap({
cityCoordinates,
}: SelectHotelMapProps) {
const searchParams = useSearchParams()
const router = useRouter()
const lang = useLang()
const intl = useIntl()
const isAboveMobile = useMediaQuery("(min-width: 768px)")
@@ -82,25 +83,18 @@ export default function SelectHotelMap({
hotelListingElement?.scrollTo({ top: 0, behavior: "smooth" })
}
function handlePageRedirect() {
const newUrl = `${selectHotel(lang)}?${searchParams.toString()}`
if (window.history.length > 1) {
router.back()
} else {
router.push(newUrl)
}
}
const closeButton = (
<Button
intent="inverted"
size="small"
theme="base"
className={styles.closeButton}
onClick={handlePageRedirect}
asChild
>
<CloseIcon color="burgundy" />
{intl.formatMessage({ id: "Close the map" })}
<Link href={selectHotel(lang)} keepSearchParams>
<CloseIcon color="burgundy" />
{intl.formatMessage({ id: "Close the map" })}
</Link>
</Button>
)
return (
@@ -113,10 +107,12 @@ export default function SelectHotelMap({
size="small"
variant="icon"
wrapping
onClick={handlePageRedirect}
className={styles.filterContainerCloseButton}
asChild
>
<CloseLargeIcon />
<Link href={selectHotel(lang)} keepSearchParams>
<CloseLargeIcon />
</Link>
</Button>
<FilterAndSortModal filters={filterList} />
</div>
@@ -126,7 +122,7 @@ export default function SelectHotelMap({
setActiveHotelPin={setActiveHotelPin}
/>
{showBackToTop && (
<BackToTopButton position="right" onClick={scrollToTop} />
<BackToTopButton position="left" onClick={scrollToTop} />
)}
</div>
<InteractiveMap

View File

@@ -14,10 +14,6 @@
justify-content: space-between;
align-items: center;
position: relative;
top: 0;
left: 0;
right: 0;
z-index: 10;
background-color: var(--Base-Surface-Secondary-light-Normal);
padding: 0 var(--Spacing-x2);
height: 44px;

View File

@@ -1,29 +1,18 @@
"use client"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useRef, useState } from "react"
import { Dialog, Modal } from "react-aria-components"
import { debounce } from "@/utils/debounce"
import styles from "./mapModal.module.css"
export function MapModal({ children }: { children: React.ReactNode }) {
const router = useRouter()
export function MapContainer({ children }: { children: React.ReactNode }) {
const [mapHeight, setMapHeight] = useState("0px")
const [mapTop, setMapTop] = useState("0px")
const [isOpen, setIsOpen] = useState(true)
const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0)
const rootDiv = useRef<HTMLDivElement | null>(null)
const handleOnOpenChange = (open: boolean) => {
setIsOpen(open)
if (!open) {
router.back()
}
}
// Calculate the height of the map based on the viewport height from the start-point (below the header and booking widget)
const handleMapHeight = useCallback(() => {
const topPosition = rootDiv.current?.getBoundingClientRect().top ?? 0
@@ -66,19 +55,17 @@ export function MapModal({ children }: { children: React.ReactNode }) {
return (
<div className={styles.wrapper} ref={rootDiv}>
<Modal isOpen={isOpen} onOpenChange={handleOnOpenChange}>
<Dialog
style={
{
"--hotel-map-height": mapHeight,
"--hotel-map-top": mapTop,
} as React.CSSProperties
}
className={styles.dynamicMap}
>
{children}
</Dialog>
</Modal>
<div
style={
{
"--hotel-map-height": mapHeight,
"--hotel-map-top": mapTop,
} as React.CSSProperties
}
className={styles.dynamicMap}
>
{children}
</div>
</div>
)
}

View File

@@ -1,7 +1,7 @@
.dynamicMap {
--hotel-map-height: 100dvh;
--hotel-map-top: 0px;
position: absolute;
position: fixed;
top: var(--hotel-map-top);
left: 0;
height: var(--hotel-map-height);

View File

@@ -2,7 +2,7 @@ import {
AdvancedMarker,
AdvancedMarkerAnchorPoint,
} from "@vis.gl/react-google-maps"
import { memo, useCallback, useState } from "react"
import { useCallback, useState } from "react"
import HotelCardDialog from "@/components/HotelReservation/HotelCardDialog"
import Body from "@/components/TempDesignSystem/Text/Body"
@@ -84,6 +84,7 @@ function HotelListingMapContent({
color={isActiveOrHovered ? "burgundy" : "white"}
/>
</span>
<Body
asChild
color={isActiveOrHovered ? "white" : "baseTextHighContrast"}

View File

@@ -1,5 +1,7 @@
.sitewideAlert {
width: 100%;
z-index: var(--header-z-index);
position: relative;
}
.alarm {