Merged in feat/booking-flow-performance (pull request #1282)

feat: booking flow performance

* feat: booking flow performance

* Cleanup


Approved-by: Michael Zetterberg
Approved-by: Pontus Dreij
This commit is contained in:
Linus Flood
2025-02-08 10:40:42 +00:00
parent bd779a15a4
commit ebb007b7f0
10 changed files with 39 additions and 111 deletions

View File

@@ -22,6 +22,7 @@ export default function FormContent({
locations, locations,
formId, formId,
onSubmit, onSubmit,
isSearching,
}: BookingWidgetFormContentProps) { }: BookingWidgetFormContentProps) {
const intl = useIntl() const intl = useIntl()
const selectedDate = useWatch({ name: "date" }) const selectedDate = useWatch({ name: "date" })
@@ -84,6 +85,7 @@ export default function FormContent({
intent="primary" intent="primary"
theme="base" theme="base"
type="submit" type="submit"
disabled={isSearching}
> >
<Caption <Caption
color="white" color="white"

View File

@@ -1,5 +1,6 @@
"use client" "use client"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { useEffect, useState, useTransition } from "react"
import { Form as FormRAC } from "react-aria-components" import { Form as FormRAC } from "react-aria-components"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
@@ -26,6 +27,7 @@ export default function Form({
}: BookingWidgetFormProps) { }: BookingWidgetFormProps) {
const router = useRouter() const router = useRouter()
const lang = useLang() const lang = useLang()
const [isPending, startTransition] = useTransition()
const classNames = bookingWidgetVariants({ const classNames = bookingWidgetVariants({
type, type,
@@ -36,7 +38,6 @@ export default function Form({
function onSubmit(data: BookingWidgetSchema) { function onSubmit(data: BookingWidgetSchema) {
const locationData: Location = JSON.parse(decodeURIComponent(data.location)) const locationData: Location = JSON.parse(decodeURIComponent(data.location))
const bookingFlowPage = const bookingFlowPage =
locationData.type == "cities" ? selectHotel(lang) : selectRate(lang) locationData.type == "cities" ? selectHotel(lang) : selectRate(lang)
const bookingWidgetParams = convertObjToSearchParams({ const bookingWidgetParams = convertObjToSearchParams({
@@ -51,7 +52,9 @@ export default function Form({
}) })
onClose() onClose()
startTransition(() => {
router.push(`${bookingFlowPage}?${bookingWidgetParams.toString()}`) router.push(`${bookingFlowPage}?${bookingWidgetParams.toString()}`)
})
if (!data.bookingCode?.value) { if (!data.bookingCode?.value) {
setValue("bookingCode.remember", false) setValue("bookingCode.remember", false)
localStorage.removeItem("bookingCode") localStorage.removeItem("bookingCode")
@@ -72,6 +75,7 @@ export default function Form({
locations={locations} locations={locations}
formId={formId} formId={formId}
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
isSearching={isPending}
/> />
</FormRAC> </FormRAC>
</section> </section>

View File

@@ -1,8 +1,7 @@
import { import { getHeader, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
getHeader,
getLanguageSwitcher, import { auth } from "@/auth"
getName, import { isValidSession } from "@/utils/session"
} from "@/lib/trpc/memoizedRequests"
import MobileMenu from "../MobileMenu" import MobileMenu from "../MobileMenu"
@@ -12,7 +11,7 @@ export default async function MobileMenuWrapper({
// preloaded // preloaded
const languages = await getLanguageSwitcher() const languages = await getLanguageSwitcher()
const header = await getHeader() const header = await getHeader()
const user = await getName() const session = await auth()
if (!languages || !header) { if (!languages || !header) {
return null return null
@@ -22,7 +21,7 @@ export default async function MobileMenuWrapper({
<MobileMenu <MobileMenu
languageUrls={languages.urls} languageUrls={languages.urls}
topLink={header.data.topLink} topLink={header.data.topLink}
isLoggedIn={!!user} isLoggedIn={isValidSession(session)}
> >
{children} {children}
</MobileMenu> </MobileMenu>

View File

@@ -11,11 +11,10 @@ import TopMenu, { TopMenuSkeleton } from "./TopMenu"
import styles from "./header.module.css" import styles from "./header.module.css"
export default async function Header() { export default function Header() {
void getHeader() void getHeader()
void getLanguageSwitcher() void getLanguageSwitcher()
void getName() void getName()
return ( return (
<header className={styles.header}> <header className={styles.header}>
<Suspense fallback={<TopMenuSkeleton />}> <Suspense fallback={<TopMenuSkeleton />}>

View File

@@ -1,5 +0,0 @@
.hotelAlert {
max-width: var(--max-width-navigation);
margin: 0 auto;
padding-top: var(--Spacing-x-one-and-half);
}

View File

@@ -1,85 +0,0 @@
import { dt } from "@/lib/dt"
import { getRoomsAvailability } from "@/lib/trpc/memoizedRequests"
import Alert from "@/components/TempDesignSystem/Alert"
import { getIntl } from "@/i18n"
import { safeTry } from "@/utils/safeTry"
import { generateChildrenString } from "../../utils"
import { combineRoomAvailabilities } from "../utils"
import styles from "./NoRoomsAlert.module.css"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import { AlertTypeEnum } from "@/types/enums/alert"
import type { Lang } from "@/constants/languages"
type Props = {
hotelId: number
lang: Lang
adultArray: number[]
childArray?: Child[]
fromDate: Date
toDate: Date
}
export async function NoRoomsAlert({
hotelId,
fromDate,
toDate,
childArray,
adultArray,
lang,
}: Props) {
const fromDateString = dt(fromDate).format("YYYY-MM-DD")
const toDateString = dt(toDate).format("YYYY-MM-DD")
const uniqueAdultCounts = [...new Set(adultArray)]
const roomsAvailabilityPromises = uniqueAdultCounts.map((adultCount) => {
return safeTry(
getRoomsAvailability({
hotelId: hotelId,
roomStayStartDate: fromDateString,
roomStayEndDate: toDateString,
adults: adultCount,
children:
childArray && childArray.length > 0
? generateChildrenString(childArray)
: undefined,
})
)
})
const roomsAvailabilityResults = await Promise.all(roomsAvailabilityPromises)
const roomsAvailability = combineRoomAvailabilities({
availabilityResults: roomsAvailabilityResults,
})
if (!roomsAvailability) {
return null
}
const noRoomsAvailable = roomsAvailability.roomConfigurations.reduce(
(acc, room) => {
return acc && room.status === "NotAvailable"
},
true
)
if (!noRoomsAvailable) {
return null
}
const intl = await getIntl(lang)
return (
<div className={styles.hotelAlert}>
<Alert
type={AlertTypeEnum.Info}
text={intl.formatMessage({
id: "There are no rooms available that match your request",
})}
/>
</div>
)
}

View File

@@ -1,5 +1,3 @@
import { Suspense } from "react"
import { getHotel } from "@/lib/trpc/memoizedRequests" import { getHotel } from "@/lib/trpc/memoizedRequests"
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
@@ -15,7 +13,6 @@ import { getSingleDecimal } from "@/utils/numberFormatting"
import ReadMore from "../../ReadMore" import ReadMore from "../../ReadMore"
import TripAdvisorChip from "../../TripAdvisorChip" import TripAdvisorChip from "../../TripAdvisorChip"
import { NoRoomsAlert } from "./NoRoomsAlert"
import styles from "./hotelInfoCard.module.css" import styles from "./hotelInfoCard.module.css"
@@ -24,7 +21,6 @@ import type { HotelInfoCardProps } from "@/types/components/hotelReservation/sel
export default async function HotelInfoCard({ export default async function HotelInfoCard({
hotelId, hotelId,
lang, lang,
...props
}: HotelInfoCardProps) { }: HotelInfoCardProps) {
const hotelData = await getHotel({ const hotelData = await getHotel({
hotelId: hotelId.toString(), hotelId: hotelId.toString(),
@@ -119,10 +115,6 @@ export default async function HotelInfoCard({
</div> </div>
) )
})} })}
<Suspense fallback={null} key={hotelId}>
<NoRoomsAlert hotelId={hotelId} lang={lang} {...props} />
</Suspense>
</article> </article>
) )
} }

View File

@@ -6,6 +6,8 @@ import {
} from "@/lib/trpc/memoizedRequests" } from "@/lib/trpc/memoizedRequests"
import { auth } from "@/auth" import { auth } from "@/auth"
import Alert from "@/components/TempDesignSystem/Alert"
import { getIntl } from "@/i18n"
import { safeTry } from "@/utils/safeTry" import { safeTry } from "@/utils/safeTry"
import { isValidSession } from "@/utils/session" import { isValidSession } from "@/utils/session"
@@ -13,8 +15,11 @@ import { generateChildrenString } from "../../utils"
import { combineRoomAvailabilities } from "../utils" import { combineRoomAvailabilities } from "../utils"
import Rooms from "." import Rooms from "."
import styles from "./rooms.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer" import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer"
import { AlertTypeEnum } from "@/types/enums/alert"
export async function RoomsContainer({ export async function RoomsContainer({
adultArray, adultArray,
@@ -77,14 +82,24 @@ export async function RoomsContainer({
availabilityResults: roomsAvailabilityResults, availabilityResults: roomsAvailabilityResults,
}) })
const intl = await getIntl(lang)
if (packagesError) { if (packagesError) {
// TODO: Log packages error // TODO: Log packages error
console.error("[RoomsContainer] unable to fetch packages") console.error("[RoomsContainer] unable to fetch packages")
} }
if (!roomsAvailability) { if (!roomsAvailability) {
// HotelInfoCard has the logic for displaying when there are no rooms available return (
return null <div className={styles.hotelAlert}>
<Alert
type={AlertTypeEnum.Info}
text={intl.formatMessage({
id: "There are no rooms available that match your request",
})}
/>
</div>
)
} }
return ( return (

View File

@@ -48,3 +48,9 @@
opacity: 1; opacity: 1;
height: auto; height: auto;
} }
.hotelAlert {
max-width: var(--max-width-navigation);
margin: 0 auto;
padding: var(--Spacing-x-one-and-half);
}

View File

@@ -11,6 +11,7 @@ export interface BookingWidgetFormContentProps {
locations: Locations locations: Locations
formId: string formId: string
onSubmit: () => void onSubmit: () => void
isSearching: boolean
} }
export enum ActionType { export enum ActionType {