import { cookies } from "next/headers" import { notFound, redirect } from "next/navigation" import { BookingFlowConfig } from "@scandic-hotels/booking-flow/BookingFlowConfig" import { filterOverlappingDates } from "@scandic-hotels/booking-flow/utils/SelectRate" import { findMyBookingRoutes } from "@scandic-hotels/common/constants/routes/findMyBookingRoutes" import { dt } from "@scandic-hotels/common/dt" import { logger } from "@scandic-hotels/common/logger" import * as maskValue from "@scandic-hotels/common/utils/maskValue" import Image from "@scandic-hotels/design-system/Image" import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast" import { parseRefId } from "@scandic-hotels/trpc/utils/refId" import { bookingFlowConfig } from "@/constants/bookingFlowConfig" import { env } from "@/env/server" import { findBooking, getAncillaryPackages, getBookingConfirmation, getLinkedReservations, getPackages, getProfileSafely, getSavedPaymentCardsSafely, } from "@/lib/trpc/memoizedRequests" import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm" import accessBooking, { ACCESS_GRANTED, ERROR_BAD_REQUEST, ERROR_NOT_FOUND, ERROR_UNAUTHORIZED, } from "@/components/HotelReservation/MyStay/accessBooking" import { Ancillaries } from "@/components/HotelReservation/MyStay/Ancillaries" import BookingSummary from "@/components/HotelReservation/MyStay/BookingSummary" import { Header } from "@/components/HotelReservation/MyStay/Header" import Promo from "@/components/HotelReservation/MyStay/Promo" import { ReferenceCard } from "@/components/HotelReservation/MyStay/ReferenceCard" import MultiRoom from "@/components/HotelReservation/MyStay/Rooms/MultiRoom" import SingleRoom from "@/components/HotelReservation/MyStay/Rooms/SingleRoom" import { getIntl } from "@/i18n" import MyStayProvider from "@/providers/MyStay" import { isLoggedInUser } from "@/utils/isLoggedInUser" import FindMyBooking from "../FindMyBooking" import { FindMyBookingErrorEnum } from "../FindMyBooking/utils" import styles from "./index.module.css" import type { AdditionalInfoCookieValue } from "@scandic-hotels/booking-flow/types/components/findMyBooking/additionalInfoCookieValue" import type { Lang } from "@scandic-hotels/common/constants/language" import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation" import type { SafeUser } from "@/types/user" export default function MyStayWrapper(props: { refId?: string lang: Lang isWebview?: boolean }) { return ( ) } async function MyStay(props: { refId?: string lang: Lang isWebview?: boolean }) { const { refId, lang, isWebview } = props if (!refId) { notFound() } const cookieStore = await cookies() const bv = cookieStore.get("bv")?.value 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 ( ) } if (!bookingConfirmation) { redirect( `${findMyBookingRoutes[lang]}?error=${FindMyBookingErrorEnum.BOOKING_NOT_FOUND}` ) } const { booking } = bookingConfirmation const { code } = accessBooking(booking.guest, lastName, user, bv) switch (code) { case ACCESS_GRANTED.code: return ( ) case ERROR_NOT_FOUND.code: return notFound() case ERROR_BAD_REQUEST.code: return ( ) case ERROR_UNAUTHORIZED.code: { if (!bv) return notFound() return ( ) } 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 return ( {imageSrc && ( )} {booking.showAncillaries && ancillaryPackagesPromise && ( )} {!isWebview && ( )} ) } function RenderFindMyBookingForm({ bv, lastName, confirmationNumber, }: { bv: string lastName: string confirmationNumber: string }) { const { firstName, email } = JSON.parse(bv) as AdditionalInfoCookieValue return ( ) } function RenderAdditionalInfoForm({ confirmationNumber, lastName, }: { confirmationNumber: string lastName: string }) { return ( ) } 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) }