From 76ee5e97bfee73e18ce31894c4dfd02128750ea5 Mon Sep 17 00:00:00 2001 From: Anton Gunnarsson Date: Fri, 30 Jan 2026 14:29:49 +0000 Subject: [PATCH] Merged in chore/improve-my-stay-load-times (pull request #3514) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: Improve My Stay load times * Restructure My Stay page to avoid data fetching waterfalls Approved-by: Linus Flood Approved-by: Matilda Landström --- .../HotelReservation/MyStay/index.tsx | 513 ++++++++++-------- 1 file changed, 294 insertions(+), 219 deletions(-) diff --git a/apps/scandic-web/components/HotelReservation/MyStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/index.tsx index 68a6d1e08..2152a0b9a 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/index.tsx @@ -27,6 +27,7 @@ import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/Addi import accessBooking, { ACCESS_GRANTED, ERROR_BAD_REQUEST, + ERROR_NOT_FOUND, ERROR_UNAUTHORIZED, } from "@/components/HotelReservation/MyStay/accessBooking" import { Ancillaries } from "@/components/HotelReservation/MyStay/Ancillaries" @@ -74,39 +75,23 @@ async function MyStay(props: { notFound() } - const { confirmationNumber, lastName } = parseRefId(refId) - - const isLoggedIn = await isLoggedInUser() - const cookieStore = await cookies() const bv = cookieStore.get("bv")?.value - let bookingConfirmation - if (isLoggedIn) { - bookingConfirmation = await getBookingConfirmation(refId) - } else if (bv) { - logger.debug(`MyStay: bv`, bv) - const { - firstName, - email, - confirmationNumber: bvConfirmationNo, - } = JSON.parse(bv) as AdditionalInfoCookieValue - if (firstName && email && bvConfirmationNo === confirmationNumber) { - bookingConfirmation = await findBooking( - confirmationNumber, - lastName, - firstName, - email - ) - } else { - return ( - - ) - } - } else { + const { confirmationNumber, lastName } = parseRefId(refId) + const isLoggedIn = await isLoggedInUser() + const [{ error, bookingConfirmation }, user] = await Promise.all([ + getOrFindBookingConfirmation({ + refId, + isLoggedIn, + confirmationNumber, + lastName, + bv, + }), + getProfileSafely(), + ]) + + if (error === "MISSING_INFO") { return ( + ) + case ERROR_NOT_FOUND.code: + return notFound() + case ERROR_BAD_REQUEST.code: + return ( + + ) + case ERROR_UNAUTHORIZED.code: { + if (!bv) return notFound() - const access = accessBooking(booking.guest, lastName, user, bv) - - if (access === ACCESS_GRANTED) { - const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD") - const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD") - - const linkedReservationsPromise = getLinkedReservations(booking.refId) - - const packagesInput = { - adults: booking.adults, - children: booking.childrenAges.length, - endDate: toDate, - hotelId: hotel.operaId, - lang, - startDate: fromDate, - packageCodes: [ - BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST, - BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST, - BreakfastPackageEnum.FREE_CHILD_BREAKFAST, - ], - } - const supportedCards = hotel.merchantInformationData.cards - const savedPaymentCardsInput = { supportedCards } - - const hasBreakfastPackage = booking.packages.find( - (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST - ) - const breakfastIncluded = booking.rateDefinition.breakfastIncluded - const shouldFetchBreakfastPackages = - !hasBreakfastPackage && !breakfastIncluded - if (shouldFetchBreakfastPackages) { - void getPackages(packagesInput) - } - const isOwnBooking = user?.email === booking.guest.email - if (user && isOwnBooking) { - void getSavedPaymentCardsSafely(savedPaymentCardsInput) - } - - let breakfastPackages = null - if (shouldFetchBreakfastPackages) { - breakfastPackages = await getPackages(packagesInput) - } - let savedCreditCards = null - if (user && isOwnBooking) { - savedCreditCards = await getSavedPaymentCardsSafely( - savedPaymentCardsInput + return ( + ) } - let ancillaryPackagesPromise = null - if (booking.showAncillaries) { - ancillaryPackagesPromise = getAncillaryPackages({ + default: + const _exhaustiveCheck: never = code + throw new Error(`Unknown access code: ${code}`) + } +} + +async function MyStayPage({ + bookingConfirmation, + user, + lang, + isWebview, +}: { + bookingConfirmation: BookingConfirmation + user: SafeUser | null + lang: Lang + isWebview: boolean +}) { + const intl = await getIntl() + + const { additionalData, booking, hotel, roomCategories } = bookingConfirmation + + const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD") + const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD") + + const packagesInput = { + adults: booking.adults, + children: booking.childrenAges.length, + endDate: toDate, + hotelId: hotel.operaId, + lang, + startDate: fromDate, + packageCodes: [ + BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST, + BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST, + BreakfastPackageEnum.FREE_CHILD_BREAKFAST, + ], + } + const supportedCards = hotel.merchantInformationData.cards + const savedPaymentCardsInput = { supportedCards } + + const hasBreakfastPackage = booking.packages.find( + (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST + ) + const breakfastIncluded = booking.rateDefinition.breakfastIncluded + const shouldFetchBreakfastPackages = + !hasBreakfastPackage && !breakfastIncluded + + const isOwnBooking = user?.email === booking.guest.email + const shouldGetCards = user && isOwnBooking + + const [breakfastPackages, savedCreditCards] = await Promise.all([ + shouldFetchBreakfastPackages ? getPackages(packagesInput) : noop(), + shouldGetCards + ? getSavedPaymentCardsSafely(savedPaymentCardsInput) + : noop(), + ]) + + const imageSrc = + hotel.hotelContent.images.src || + additionalData.gallery?.heroImages[0]?.src || + hotel.galleryImages[0]?.src + + const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" + const promoUrl = new URL(`${baseUrl}/${lang}/`) + const hotelUrl = new URL(`${baseUrl}${bookingConfirmation.url}/`) + promoUrl.searchParams.set("hotel", hotel.operaId) + + const maskedBookingConfirmation = maskBookingConfirmation(bookingConfirmation) + const maskedUser = isOwnBooking ? maskUser(user) : null + + const hotelWithFilteredAlerts = { + ...hotel, + specialAlerts: filterOverlappingDates( + hotel.specialAlerts, + dt.utc(fromDate), + dt.utc(toDate) + ), + } + + const linkedReservationsPromise = getLinkedReservations(booking.refId) + const ancillaryPackagesPromise = booking.showAncillaries + ? getAncillaryPackages({ fromDate, hotelId: hotel.operaId, toDate, }) - } + : null - const imageSrc = - hotel.hotelContent.images.src || - additionalData.gallery?.heroImages[0]?.src || - hotel.galleryImages[0]?.src - - const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" - const promoUrl = new URL(`${baseUrl}/${lang}/`) - const hotelUrl = new URL(`${baseUrl}${bookingConfirmation.url}/`) - - promoUrl.searchParams.set("hotel", hotel.operaId) - - const maskedBookingConfirmation = { - ...bookingConfirmation, - booking: { - ...bookingConfirmation.booking, - guest: { - ...bookingConfirmation.booking.guest, - email: maskValue.email(bookingConfirmation.booking.guest.email), - phoneNumber: maskValue.phone( - bookingConfirmation.booking.guest.phoneNumber ?? "" - ), - }, - }, - } satisfies BookingConfirmation - - const maskedUser = - user && isOwnBooking - ? ({ - ...user, - email: maskValue.email(user.email), - phoneNumber: maskValue.phone(user.phoneNumber ?? ""), - } satisfies SafeUser) - : null - - hotel.specialAlerts = filterOverlappingDates( - hotel.specialAlerts, - dt.utc(fromDate), - dt.utc(toDate) - ) - - return ( - -
-
-
- {imageSrc && ( - {hotel.name} - )} -
-
-
-
- -
- {booking.showAncillaries && ancillaryPackagesPromise && ( - - )} - - - - - - {!isWebview && ( - - )} -
-
-
- ) - } - - if (access === ERROR_BAD_REQUEST) { - return ( - - ) - } - - if (access === ERROR_UNAUTHORIZED) { - if (bv) { - const { firstName, email } = JSON.parse(bv) as AdditionalInfoCookieValue - - return ( -
-
- +
+
+
+ {imageSrc && ( + {hotelWithFilteredAlerts.name} + )} +
+
+
+
+
-
- ) - } else { - } - } + {booking.showAncillaries && ancillaryPackagesPromise && ( + + )} - return notFound() + + + + + {!isWebview && ( + + )} +
+
+ + ) +} + +function RenderFindMyBookingForm({ + bv, + lastName, + confirmationNumber, +}: { + bv: string + lastName: string + confirmationNumber: string +}) { + const { firstName, email } = JSON.parse(bv) as AdditionalInfoCookieValue + + return ( +
+
+ +
+
+ ) } function RenderAdditionalInfoForm({ @@ -343,3 +346,75 @@ function RenderAdditionalInfoForm({ ) } + +function maskUser(user: SafeUser | null): SafeUser | null { + if (!user) return null + + return { + ...user, + email: maskValue.email(user.email), + phoneNumber: maskValue.phone(user.phoneNumber ?? ""), + } +} + +function maskBookingConfirmation( + bookingConfirmation: BookingConfirmation +): BookingConfirmation { + return { + ...bookingConfirmation, + booking: { + ...bookingConfirmation.booking, + guest: { + ...bookingConfirmation.booking.guest, + email: maskValue.email(bookingConfirmation.booking.guest.email), + phoneNumber: maskValue.phone( + bookingConfirmation.booking.guest.phoneNumber ?? "" + ), + }, + }, + } +} + +async function getOrFindBookingConfirmation({ + refId, + confirmationNumber, + lastName, + isLoggedIn, + bv, +}: { + refId: string + confirmationNumber: string + lastName: string + isLoggedIn: boolean + bv?: string +}) { + if (isLoggedIn) + return { bookingConfirmation: await getBookingConfirmation(refId) } as const + + if (!bv) return { error: "MISSING_INFO", bookingConfirmation: null } as const + + logger.debug(`MyStay: bv`, bv) + const { + firstName, + email, + confirmationNumber: bvConfirmationNo, + } = JSON.parse(bv) as AdditionalInfoCookieValue + + if (!firstName || !email || bvConfirmationNo !== confirmationNumber) { + return { error: "MISSING_INFO", bookingConfirmation: null } as const + } + + return { + bookingConfirmation: await findBooking( + confirmationNumber, + lastName, + firstName, + email + ), + } as const +} + +// Helper function to handle conditional Promise.all calls +async function noop() { + return Promise.resolve(null) +}