Merged in chore/improve-my-stay-load-times (pull request #3514)
chore: Improve My Stay load times * Restructure My Stay page to avoid data fetching waterfalls Approved-by: Linus Flood Approved-by: Matilda Landström
This commit is contained in:
@@ -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(
|
||||
const { confirmationNumber, lastName } = parseRefId(refId)
|
||||
const isLoggedIn = await isLoggedInUser()
|
||||
const [{ error, bookingConfirmation }, user] = await Promise.all([
|
||||
getOrFindBookingConfirmation({
|
||||
refId,
|
||||
isLoggedIn,
|
||||
confirmationNumber,
|
||||
lastName,
|
||||
firstName,
|
||||
email
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<RenderAdditionalInfoForm
|
||||
confirmationNumber={confirmationNumber}
|
||||
lastName={lastName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
bv,
|
||||
}),
|
||||
getProfileSafely(),
|
||||
])
|
||||
|
||||
if (error === "MISSING_INFO") {
|
||||
return (
|
||||
<RenderAdditionalInfoForm
|
||||
confirmationNumber={confirmationNumber}
|
||||
@@ -121,20 +106,64 @@ async function MyStay(props: {
|
||||
)
|
||||
}
|
||||
|
||||
const { additionalData, booking, hotel, roomCategories } = bookingConfirmation
|
||||
const { booking } = bookingConfirmation
|
||||
|
||||
const user = await getProfileSafely()
|
||||
const { code } = accessBooking(booking.guest, lastName, user, bv)
|
||||
|
||||
switch (code) {
|
||||
case ACCESS_GRANTED.code:
|
||||
return (
|
||||
<MyStayPage
|
||||
bookingConfirmation={bookingConfirmation}
|
||||
user={user}
|
||||
lang={lang}
|
||||
isWebview={!!isWebview}
|
||||
/>
|
||||
)
|
||||
case ERROR_NOT_FOUND.code:
|
||||
return notFound()
|
||||
case ERROR_BAD_REQUEST.code:
|
||||
return (
|
||||
<RenderAdditionalInfoForm
|
||||
confirmationNumber={confirmationNumber}
|
||||
lastName={lastName}
|
||||
/>
|
||||
)
|
||||
case ERROR_UNAUTHORIZED.code: {
|
||||
if (!bv) return notFound()
|
||||
|
||||
return (
|
||||
<RenderFindMyBookingForm
|
||||
bv={bv}
|
||||
lastName={lastName}
|
||||
confirmationNumber={confirmationNumber}
|
||||
/>
|
||||
)
|
||||
}
|
||||
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 access = accessBooking(booking.guest, lastName, user, bv)
|
||||
const { additionalData, booking, hotel, roomCategories } = bookingConfirmation
|
||||
|
||||
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,
|
||||
@@ -157,32 +186,16 @@ async function MyStay(props: {
|
||||
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
|
||||
)
|
||||
}
|
||||
let ancillaryPackagesPromise = null
|
||||
if (booking.showAncillaries) {
|
||||
ancillaryPackagesPromise = getAncillaryPackages({
|
||||
fromDate,
|
||||
hotelId: hotel.operaId,
|
||||
toDate,
|
||||
})
|
||||
}
|
||||
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 ||
|
||||
@@ -192,37 +205,28 @@ async function MyStay(props: {
|
||||
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 maskedBookingConfirmation = maskBookingConfirmation(bookingConfirmation)
|
||||
const maskedUser = isOwnBooking ? maskUser(user) : null
|
||||
|
||||
const maskedUser =
|
||||
user && isOwnBooking
|
||||
? ({
|
||||
...user,
|
||||
email: maskValue.email(user.email),
|
||||
phoneNumber: maskValue.phone(user.phoneNumber ?? ""),
|
||||
} satisfies SafeUser)
|
||||
: null
|
||||
|
||||
hotel.specialAlerts = filterOverlappingDates(
|
||||
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
|
||||
|
||||
return (
|
||||
<MyStayProvider
|
||||
@@ -233,7 +237,7 @@ async function MyStay(props: {
|
||||
refId={booking.refId}
|
||||
roomCategories={roomCategories}
|
||||
savedCreditCards={savedCreditCards}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isLoggedIn={!!user}
|
||||
>
|
||||
<main className={styles.main}>
|
||||
<div className={styles.imageContainer}>
|
||||
@@ -242,14 +246,17 @@ async function MyStay(props: {
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={imageSrc}
|
||||
alt={hotel.name}
|
||||
alt={hotelWithFilteredAlerts.name}
|
||||
fill
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.headerContainer}>
|
||||
<Header cityName={hotel.cityName} name={hotel.name} />
|
||||
<Header
|
||||
cityName={hotelWithFilteredAlerts.cityName}
|
||||
name={hotelWithFilteredAlerts.name}
|
||||
/>
|
||||
<ReferenceCard />
|
||||
</div>
|
||||
{booking.showAncillaries && ancillaryPackagesPromise && (
|
||||
@@ -264,7 +271,10 @@ async function MyStay(props: {
|
||||
<SingleRoom user={maskedUser} />
|
||||
<MultiRoom user={maskedUser} />
|
||||
|
||||
<BookingSummary hotelUrl={hotelUrl.toString()} hotel={hotel} />
|
||||
<BookingSummary
|
||||
hotelUrl={hotelUrl.toString()}
|
||||
hotel={hotelWithFilteredAlerts}
|
||||
/>
|
||||
{!isWebview && (
|
||||
<Promo
|
||||
title={intl.formatMessage({
|
||||
@@ -281,26 +291,24 @@ async function MyStay(props: {
|
||||
defaultMessage: "Explore Scandic hotels",
|
||||
})}
|
||||
href={promoUrl.toString()}
|
||||
image={hotel.hotelContent.images}
|
||||
image={hotelWithFilteredAlerts.hotelContent.images}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</MyStayProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (access === ERROR_BAD_REQUEST) {
|
||||
return (
|
||||
<RenderAdditionalInfoForm
|
||||
confirmationNumber={confirmationNumber}
|
||||
lastName={lastName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (access === ERROR_UNAUTHORIZED) {
|
||||
if (bv) {
|
||||
function RenderFindMyBookingForm({
|
||||
bv,
|
||||
lastName,
|
||||
confirmationNumber,
|
||||
}: {
|
||||
bv: string
|
||||
lastName: string
|
||||
confirmationNumber: string
|
||||
}) {
|
||||
const { firstName, email } = JSON.parse(bv) as AdditionalInfoCookieValue
|
||||
|
||||
return (
|
||||
@@ -318,11 +326,6 @@ async function MyStay(props: {
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
return notFound()
|
||||
}
|
||||
|
||||
function RenderAdditionalInfoForm({
|
||||
@@ -343,3 +346,75 @@ function RenderAdditionalInfoForm({
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user