From 03fa79867013531e2425824df86f137165e17f8a Mon Sep 17 00:00:00 2001 From: Niclas Edenvin Date: Wed, 11 Sep 2024 13:33:26 +0200 Subject: [PATCH] Use the interactive map on the map page --- .../select-hotel/map/page.module.css | 6 ++ .../select-hotel/map/page.tsx | 53 +++++++++++ .../hotelreservation/select-hotel/page.tsx | 92 +++++-------------- .../hotelreservation/select-hotel/utils.ts | 43 +++++++++ .../SelectHotel/HotelFilter/index.tsx | 2 +- .../SelectHotelMap/HotelListing/index.tsx | 11 +++ .../SelectHotel/SelectHotelMap/index.tsx | 57 ++++++++++++ .../SelectHotelMap/selectHotelMap.module.css | 5 + components/TempDesignSystem/Link/index.tsx | 19 +++- components/TempDesignSystem/Link/link.ts | 1 + constants/routes/hotelReservation.js | 20 ++++ .../{hotelFiltersProps.ts => hotelFilters.ts} | 8 ++ .../hotelReservation/selectHotel/map.ts | 15 +++ 13 files changed, 259 insertions(+), 73 deletions(-) create mode 100644 app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.module.css create mode 100644 app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx create mode 100644 app/[lang]/(live)/(public)/hotelreservation/select-hotel/utils.ts create mode 100644 components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx create mode 100644 components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx create mode 100644 components/HotelReservation/SelectHotel/SelectHotelMap/selectHotelMap.module.css rename types/components/hotelReservation/selectHotel/{hotelFiltersProps.ts => hotelFilters.ts} (51%) create mode 100644 types/components/hotelReservation/selectHotel/map.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.module.css new file mode 100644 index 000000000..ec9b9ce4f --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.module.css @@ -0,0 +1,6 @@ +.main { + display: grid; + background-color: var(--Scandic-Brand-Warm-White); + min-height: 100dvh; + grid-template-columns: 420px 1fr; +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx new file mode 100644 index 000000000..1c66c16cb --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx @@ -0,0 +1,53 @@ +import { env } from "@/env/server" + +import { + fetchAvailableHotels, + getFiltersFromHotels, +} from "@/app/[lang]/(live)/(public)/hotelreservation/select-hotel/utils" +import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap" +import { setLang } from "@/i18n/serverContext" + +import styles from "./page.module.css" + +import { PointOfInterest } from "@/types/hotel" +import { LangParams, PageArgs } from "@/types/params" + +export default async function SelectHotelMapPage({ + params, +}: PageArgs) { + const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID + const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY + setLang(params.lang) + + const hotels = await fetchAvailableHotels({ + cityId: "8ec4bba3-1c38-4606-82d1-bbe3f6738e54", + roomStayStartDate: "2024-11-02", + roomStayEndDate: "2024-11-03", + adults: 1, + }) + + const filters = getFiltersFromHotels(hotels) + + // TODO: this is just a quick transformation to get something there. May need rework + const pointOfInterests: PointOfInterest[] = hotels.map((hotel) => ({ + coordinates: { + lat: hotel.hotelData.location.latitude, + lng: hotel.hotelData.location.longitude, + }, + name: hotel.hotelData.name, + distance: hotel.hotelData.location.distanceToCentre, + category: "Hotel", + })) + + return ( +
+ +
+ ) +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx index 63facf6d8..b6548122c 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx @@ -1,45 +1,21 @@ -import { serverClient } from "@/lib/trpc/server" +import { selectHotelMap } from "@/constants/routes/hotelReservation" +import { + fetchAvailableHotels, + getFiltersFromHotels, +} from "@/app/[lang]/(live)/(public)/hotelreservation/select-hotel/utils" import HotelCardListing from "@/components/HotelReservation/HotelCardListing" import HotelFilter from "@/components/HotelReservation/SelectHotel/HotelFilter" import { ChevronRightIcon } from "@/components/Icons" import StaticMap from "@/components/Maps/StaticMap" import Link from "@/components/TempDesignSystem/Link" import { getIntl } from "@/i18n" -import { getLang, setLang } from "@/i18n/serverContext" +import { setLang } from "@/i18n/serverContext" import styles from "./page.module.css" -import { AvailabilityInput } from "@/types/components/hotelReservation/selectHotel/availabilityInput" -import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import { LangParams, PageArgs } from "@/types/params" -async function getAvailableHotels( - input: AvailabilityInput -): Promise { - const getAvailableHotels = await serverClient().hotel.availability.get(input) - - if (!getAvailableHotels) throw new Error() - - const { availability } = getAvailableHotels - - const hotels = availability.map(async (hotel) => { - const hotelData = await serverClient().hotel.hotelData.get({ - hotelId: hotel.hotelId.toString(), - language: getLang(), - }) - - if (!hotelData) throw new Error() - - return { - hotelData: hotelData.data.attributes, - price: hotel.bestPricePerNight, - } - }) - - return await Promise.all(hotels) -} - export default async function SelectHotelPage({ params, }: PageArgs) { @@ -48,54 +24,34 @@ export default async function SelectHotelPage({ const tempSearchTerm = "Stockholm" const intl = await getIntl() - const hotels = await getAvailableHotels({ + const hotels = await fetchAvailableHotels({ cityId: "8ec4bba3-1c38-4606-82d1-bbe3f6738e54", roomStayStartDate: "2024-11-02", roomStayEndDate: "2024-11-03", adults: 1, }) - const filters = hotels.flatMap((data) => data.hotelData.detailedFacilities) - - const filterIds = [...new Set(filters.map((data) => data.id))] - const filterList: { - name: string - id: number - applyToAllHotels: boolean - public: boolean - icon: string - sortOrder: number - code?: string - iconName?: string - }[] = filterIds - .map((id) => filters.find((find) => find.id === id)) - .filter( - ( - filter - ): filter is { - name: string - id: number - applyToAllHotels: boolean - public: boolean - icon: string - sortOrder: number - code?: string - iconName?: string - } => filter !== undefined - ) + const filterList = getFiltersFromHotels(hotels) return (
- - + + + + {intl.formatMessage({ id: "Show map" })} diff --git a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/utils.ts b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/utils.ts new file mode 100644 index 000000000..d8447c71b --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/utils.ts @@ -0,0 +1,43 @@ +import { serverClient } from "@/lib/trpc/server" + +import { getLang } from "@/i18n/serverContext" + +import { AvailabilityInput } from "@/types/components/hotelReservation/selectHotel/availabilityInput" +import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" +import { Filter } from "@/types/components/hotelReservation/selectHotel/hotelFilters" + +export async function fetchAvailableHotels( + input: AvailabilityInput +): Promise { + const availableHotels = await serverClient().hotel.availability.get(input) + + if (!availableHotels) throw new Error() + + const language = getLang() + const hotels = availableHotels.availability.map(async (hotel) => { + const hotelData = await serverClient().hotel.hotelData.get({ + hotelId: hotel.hotelId.toString(), + language, + }) + + if (!hotelData) throw new Error() + + return { + hotelData: hotelData.data.attributes, + price: hotel.bestPricePerNight, + } + }) + + return await Promise.all(hotels) +} + +export function getFiltersFromHotels(hotels: HotelData[]) { + const filters = hotels.flatMap((hotel) => hotel.hotelData.detailedFacilities) + + const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))] + const filterList: Filter[] = uniqueFilterIds + .map((filterId) => filters.find((filter) => filter.id === filterId)) + .filter((filter): filter is Filter => filter !== undefined) + + return filterList +} diff --git a/components/HotelReservation/SelectHotel/HotelFilter/index.tsx b/components/HotelReservation/SelectHotel/HotelFilter/index.tsx index f10191cec..ad32427c0 100644 --- a/components/HotelReservation/SelectHotel/HotelFilter/index.tsx +++ b/components/HotelReservation/SelectHotel/HotelFilter/index.tsx @@ -4,7 +4,7 @@ import { useIntl } from "react-intl" import styles from "./hotelFilter.module.css" -import { HotelFiltersProps } from "@/types/components/hotelReservation/selectHotel/hotelFiltersProps" +import { HotelFiltersProps } from "@/types/components/hotelReservation/selectHotel/hotelFilters" export default function HotelFilter({ filters }: HotelFiltersProps) { const intl = useIntl() diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx new file mode 100644 index 000000000..4bcdb2d68 --- /dev/null +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx @@ -0,0 +1,11 @@ +"use client" + +import { HotelListingProps } from "@/types/components/hotelReservation/selectHotel/map" + +// TODO: This component is copied from +// components/ContentType/HotelPage/Map/DynamicMap/Sidebar. +// Look at that for inspiration on how to do the interaction with the map. + +export default function HotelListing({}: HotelListingProps) { + return
Hotel listing TBI
+} diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx new file mode 100644 index 000000000..b14538812 --- /dev/null +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx @@ -0,0 +1,57 @@ +"use client" +import { APIProvider } from "@vis.gl/react-google-maps" +import { useState } from "react" +import { useIntl } from "react-intl" + +import { selectHotel } from "@/constants/routes/hotelReservation" + +import { CloseIcon } from "@/components/Icons" +import InteractiveMap from "@/components/Maps/InteractiveMap" +import Button from "@/components/TempDesignSystem/Button" +import Link from "@/components/TempDesignSystem/Link" +import useLang from "@/hooks/useLang" + +import HotelListing from "./HotelListing" + +import styles from "./selectHotelMap.module.css" + +import { SelectHotelMapProps } from "@/types/components/hotelReservation/selectHotel/map" + +export default function SelectHotelMap({ + apiKey, + coordinates, + pointsOfInterest, + mapId, +}: SelectHotelMapProps) { + const lang = useLang() + const intl = useIntl() + const [activePoi, setActivePoi] = useState(null) + + const closeButton = ( + + ) + return ( + + + + + ) +} diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/selectHotelMap.module.css b/components/HotelReservation/SelectHotel/SelectHotelMap/selectHotelMap.module.css new file mode 100644 index 000000000..54f685aa8 --- /dev/null +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/selectHotelMap.module.css @@ -0,0 +1,5 @@ +.closeButton { + pointer-events: initial; + box-shadow: var(--button-box-shadow); + gap: var(--Spacing-x-half); +} diff --git a/components/TempDesignSystem/Link/index.tsx b/components/TempDesignSystem/Link/index.tsx index 23cf15e46..2301ce932 100644 --- a/components/TempDesignSystem/Link/index.tsx +++ b/components/TempDesignSystem/Link/index.tsx @@ -1,7 +1,7 @@ "use client" import NextLink from "next/link" -import { usePathname, useRouter } from "next/navigation" -import { startTransition, useCallback } from "react" +import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { startTransition, useCallback, useMemo } from "react" import useRouterTransitionStore from "@/stores/router-transition" @@ -24,9 +24,14 @@ export default function Link({ variant, trackingId, onClick, + /** + * Decides if the link should include the current search params in the URL + */ + keepSearchParams, ...props }: LinkProps) { const currentPageSlug = usePathname() + const searchParams = useSearchParams() let isActive = active || currentPageSlug === href if (partialMatch && !isActive) { @@ -44,6 +49,12 @@ export default function Link({ const router = useRouter() + const fullUrl = useMemo(() => { + const search = + keepSearchParams && searchParams.size ? `?${searchParams}` : "" + return `${href}${search}` + }, [href, searchParams, keepSearchParams]) + const startRouterTransition = useRouterTransitionStore( (state) => state.startRouterTransition ) @@ -75,10 +86,10 @@ export default function Link({ trackPageViewStart() startTransition(() => { startRouterTransition() - router.push(href, { scroll }) + router.push(fullUrl, { scroll }) }) }} - href={href} + href={fullUrl} id={trackingId} {...props} /> diff --git a/components/TempDesignSystem/Link/link.ts b/components/TempDesignSystem/Link/link.ts index 85cb492c6..a68362457 100644 --- a/components/TempDesignSystem/Link/link.ts +++ b/components/TempDesignSystem/Link/link.ts @@ -10,4 +10,5 @@ export interface LinkProps partialMatch?: boolean prefetch?: boolean trackingId?: string + keepSearchParams?: boolean } diff --git a/constants/routes/hotelReservation.js b/constants/routes/hotelReservation.js index ba0d6d4b6..72d298965 100644 --- a/constants/routes/hotelReservation.js +++ b/constants/routes/hotelReservation.js @@ -8,4 +8,24 @@ export const hotelReservation = { de: "/de/hotelreservierung", } +// TODO: Translate paths +export const selectHotel = { + en: `${hotelReservation.en}/select-hotel`, + sv: `${hotelReservation.sv}/select-hotel`, + no: `${hotelReservation.no}/select-hotel`, + fi: `${hotelReservation.fi}/select-hotel`, + da: `${hotelReservation.da}/select-hotel`, + de: `${hotelReservation.de}/select-hotel`, +} + +// TODO: Translate paths +export const selectHotelMap = { + en: `${selectHotel.en}/map`, + sv: `${selectHotel.sv}/map`, + no: `${selectHotel.no}/map`, + fi: `${selectHotel.fi}/map`, + da: `${selectHotel.da}/map`, + de: `${selectHotel.de}/map`, +} + export const bookingFlow = [...Object.values(hotelReservation)] diff --git a/types/components/hotelReservation/selectHotel/hotelFiltersProps.ts b/types/components/hotelReservation/selectHotel/hotelFilters.ts similarity index 51% rename from types/components/hotelReservation/selectHotel/hotelFiltersProps.ts rename to types/components/hotelReservation/selectHotel/hotelFilters.ts index 1be84f63d..18177641f 100644 --- a/types/components/hotelReservation/selectHotel/hotelFiltersProps.ts +++ b/types/components/hotelReservation/selectHotel/hotelFilters.ts @@ -3,3 +3,11 @@ import { Hotel } from "@/types/hotel" export type HotelFiltersProps = { filters: Hotel["detailedFacilities"] } + +export type Filter = { + name: string + id: number + public: boolean + sortOrder: number + code?: string +} diff --git a/types/components/hotelReservation/selectHotel/map.ts b/types/components/hotelReservation/selectHotel/map.ts new file mode 100644 index 000000000..9d223d24d --- /dev/null +++ b/types/components/hotelReservation/selectHotel/map.ts @@ -0,0 +1,15 @@ +import { Coordinates } from "@/types/components/maps/coordinates" +import type { PointOfInterest } from "@/types/hotel" + +export interface HotelListingProps { + // pointsOfInterest: PointOfInterest[] + // activePoi: PointOfInterest["name"] | null + // onActivePoiChange: (poi: PointOfInterest["name"] | null) => void +} + +export interface SelectHotelMapProps { + apiKey: string + coordinates: Coordinates + pointsOfInterest: PointOfInterest[] + mapId: string +}