Merge branch 'develop'

This commit is contained in:
Linus Flood
2024-11-07 10:20:12 +01:00
261 changed files with 5132 additions and 2106 deletions

View File

@@ -0,0 +1,75 @@
import { notFound } from "next/navigation"
import { env } from "@/env/server"
import { getLocations } from "@/lib/trpc/memoizedRequests"
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
import { getHotelReservationQueryParams } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import { MapModal } from "@/components/MapModal"
import { setLang } from "@/i18n/serverContext"
import {
fetchAvailableHotels,
generateChildrenString,
getCentralCoordinates,
getPointOfInterests,
} from "../../utils"
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>) {
if (env.HIDE_FOR_NEXT_RELEASE) {
return notFound()
}
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 pointOfInterests = getPointOfInterests(hotels)
const centralCoordinates = getCentralCoordinates(pointOfInterests)
return (
<MapModal>
<SelectHotelMap
apiKey={googleMapsApiKey}
coordinates={centralCoordinates}
pointsOfInterest={pointOfInterests}
mapId={googleMapId}
isModal={true}
/>
</MapModal>
)
}

View File

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

View File

@@ -0,0 +1,5 @@
.layout {
min-height: 100dvh;
background-color: var(--Base-Background-Primary-Normal);
position: relative;
}

View File

@@ -0,0 +1,24 @@
import { notFound } from "next/navigation"
import { env } from "@/env/server"
import styles from "./layout.module.css"
import { LangParams, LayoutArgs } from "@/types/params"
export default function HotelReservationLayout({
children,
modal,
}: React.PropsWithChildren<
LayoutArgs<LangParams> & { modal: React.ReactNode }
>) {
if (env.HIDE_FOR_NEXT_RELEASE) {
return notFound()
}
return (
<div className={styles.layout}>
{children}
{modal}
</div>
)
}

View File

@@ -2,5 +2,5 @@
display: grid;
background-color: var(--Scandic-Brand-Warm-White);
min-height: 100dvh;
grid-template-columns: 420px 1fr;
position: relative;
}

View File

@@ -1,58 +1 @@
import { env } from "@/env/server"
import {
fetchAvailableHotels,
getFiltersFromHotels,
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap"
import { setLang } from "@/i18n/serverContext"
import styles from "./page.module.css"
import {
PointOfInterest,
PointOfInterestCategoryNameEnum,
PointOfInterestGroupEnum,
} from "@/types/hotel"
import { LangParams, PageArgs } from "@/types/params"
export default async function SelectHotelMapPage({
params,
}: PageArgs<LangParams, {}>) {
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,
categoryName: PointOfInterestCategoryNameEnum.HOTEL,
group: PointOfInterestGroupEnum.LOCATION,
}))
return (
<main className={styles.main}>
<SelectHotelMap
apiKey={googleMapsApiKey}
// TODO: use correct coordinates. The city center?
coordinates={{ lat: 59.32, lng: 18.01 }}
pointsOfInterest={pointOfInterests}
mapId={googleMapId}
/>
</main>
)
}
export { default } from "../@modal/(.)map/page"

View File

@@ -1,6 +1,6 @@
.main {
display: flex;
gap: var(--Spacing-x4);
gap: var(--Spacing-x3);
padding: var(--Spacing-x4) var(--Spacing-x4) 0 var(--Spacing-x4);
background-color: var(--Scandic-Brand-Warm-White);
min-height: 100dvh;
@@ -19,8 +19,28 @@
padding: var(--Spacing-x2) var(--Spacing-x0);
}
.mapContainer {
display: none;
}
.buttonContainer {
display: flex;
gap: var(--Spacing-x2);
margin-bottom: var(--Spacing-x3);
}
.button {
flex: 1;
}
@media (min-width: 768px) {
.mapContainer {
display: block;
}
.main {
flex-direction: row;
}
.buttonContainer {
display: none;
}
}

View File

@@ -7,12 +7,15 @@ import { getLocations } from "@/lib/trpc/memoizedRequests"
import {
fetchAvailableHotels,
generateChildrenString,
getFiltersFromHotels,
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
import HotelCardListing from "@/components/HotelReservation/HotelCardListing"
import HotelFilter from "@/components/HotelReservation/SelectHotel/HotelFilter"
import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import MobileMapButtonContainer from "@/components/HotelReservation/SelectHotel/MobileMapButtonContainer"
import {
generateChildrenString,
getHotelReservationQueryParams,
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import { ChevronRightIcon } from "@/components/Icons"
import StaticMap from "@/components/Maps/StaticMap"
import Link from "@/components/TempDesignSystem/Link"
@@ -95,25 +98,28 @@ export default async function SelectHotelPage({
return (
<main className={styles.main}>
<section className={styles.section}>
<Link href={selectHotelMap[params.lang]} keepSearchParams>
<StaticMap
city={searchParams.city}
width={340}
height={180}
zoomLevel={11}
mapType="roadmap"
altText={`Map of ${searchParams.city} city center`}
/>
</Link>
<Link
className={styles.link}
color="burgundy"
href={selectHotelMap[params.lang]}
keepSearchParams
>
{intl.formatMessage({ id: "Show map" })}
<ChevronRightIcon color="burgundy" />
</Link>
<div className={styles.mapContainer}>
<Link href={selectHotelMap[params.lang]} keepSearchParams>
<StaticMap
city={searchParams.city}
width={340}
height={180}
zoomLevel={11}
mapType="roadmap"
altText={`Map of ${searchParams.city} city center`}
/>
</Link>
<Link
className={styles.link}
color="burgundy"
href={selectHotelMap[params.lang]}
keepSearchParams
>
{intl.formatMessage({ id: "Show map" })}
<ChevronRightIcon color="burgundy" />
</Link>
</div>
<MobileMapButtonContainer city={searchParams.city} />
<HotelFilter filters={filterList} />
</section>
<HotelCardListing hotelData={hotels} />

View File

@@ -1,12 +1,18 @@
import { getHotelData } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server"
import { getLang } from "@/i18n/serverContext"
import { BedTypeEnum } from "@/types/components/bookingWidget/enums"
import { AvailabilityInput } from "@/types/components/hotelReservation/selectHotel/availabilityInput"
import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import { Filter } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
import { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { AvailabilityInput } from "@/types/components/hotelReservation/selectHotel/availabilityInput"
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { Filter } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import {
type PointOfInterest,
PointOfInterestCategoryNameEnum,
PointOfInterestGroupEnum,
} from "@/types/hotel"
export async function fetchAvailableHotels(
input: AvailabilityInput
@@ -17,7 +23,7 @@ export async function fetchAvailableHotels(
const language = getLang()
const hotels = availableHotels.availability.map(async (hotel) => {
const hotelData = await serverClient().hotel.hotelData.get({
const hotelData = await getHotelData({
hotelId: hotel.hotelId.toString(),
language,
})
@@ -59,3 +65,33 @@ export function generateChildrenString(children: Child[]): string {
})
.join(",")}]`
}
export function getPointOfInterests(hotels: HotelData[]): PointOfInterest[] {
// TODO: this is just a quick transformation to get something there. May need rework
return hotels.map((hotel) => ({
coordinates: {
lat: hotel.hotelData.location.latitude,
lng: hotel.hotelData.location.longitude,
},
name: hotel.hotelData.name,
distance: hotel.hotelData.location.distanceToCentre,
categoryName: PointOfInterestCategoryNameEnum.HOTEL,
group: PointOfInterestGroupEnum.LOCATION,
}))
}
export function getCentralCoordinates(pointOfInterests: PointOfInterest[]) {
const centralCoordinates = pointOfInterests.reduce(
(acc, poi) => {
acc.lat += poi.coordinates.lat
acc.lng += poi.coordinates.lng
return acc
},
{ lat: 0, lng: 0 }
)
centralCoordinates.lat /= pointOfInterests.length
centralCoordinates.lng /= pointOfInterests.length
return centralCoordinates
}