Feature/select rate vertical data flow * add fix from SW-2666 * use translations for room packages * move types to it's own file * Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/select-rate-vertical-data-flow * merge * feature/select-rate: double rate for campaing rates * revert NODE_ENV check in Cookiebot script * revert testing values * fix(SW-3171): fix all filter selected in price details * fix(SW-3166): multiroom anchoring when changing filter * fix(SW-3172): check hotelType, show correct breakfast message * Merge branch 'feature/select-rate-vertical-data-flow' of bitbucket.org:scandic-swap/web into feature/select-rate-vertical-data-flow * fix: show special needs icons for subsequent roomTypes SW-3167 * fix: Display strike through text when logged in SW-3168 * fix: Reinstate the scrollToView behaviour when selecting a rate SW-3169 * merge * . * PR fixes * fix: don't return notFound() * . * always include defaults for room packages * merge * merge * merge * Remove floating h1 for new select-rate Approved-by: Anton Gunnarsson
334 lines
9.9 KiB
TypeScript
334 lines
9.9 KiB
TypeScript
import { cookies } from "next/headers"
|
|
import { notFound } from "next/navigation"
|
|
|
|
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 { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
|
|
import { parseRefId } from "@scandic-hotels/trpc/utils/refId"
|
|
|
|
import { env } from "@/env/server"
|
|
import {
|
|
findBooking,
|
|
getAncillaryPackages,
|
|
getBookingConfirmation,
|
|
getLinkedReservations,
|
|
getPackages,
|
|
getProfileSafely,
|
|
getSavedPaymentCardsSafely,
|
|
} from "@/lib/trpc/memoizedRequests"
|
|
|
|
import AdditionalInfoForm, {
|
|
type AdditionalInfoCookieValue,
|
|
} from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
|
|
import accessBooking, {
|
|
ACCESS_GRANTED,
|
|
ERROR_BAD_REQUEST,
|
|
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 SidePeek from "@/components/HotelReservation/SidePeek"
|
|
import { getIntl } from "@/i18n"
|
|
import MyStayProvider from "@/providers/MyStay"
|
|
import { isLoggedInUser } from "@/utils/isLoggedInUser"
|
|
|
|
import { getHotelAlertsForBookingDates } from "../utils"
|
|
|
|
import styles from "./index.module.css"
|
|
|
|
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 async function MyStay(props: {
|
|
refId?: string
|
|
lang: Lang
|
|
isWebview?: boolean
|
|
}) {
|
|
const { refId, lang, isWebview } = props
|
|
|
|
if (!refId) {
|
|
notFound()
|
|
}
|
|
|
|
const { confirmationNumber, lastName } = parseRefId(refId)
|
|
if (!confirmationNumber) {
|
|
return notFound()
|
|
}
|
|
|
|
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.info(`MyStay: bv`, bv)
|
|
const values = JSON.parse(bv) as AdditionalInfoCookieValue
|
|
const firstName = values.firstName
|
|
const email = values.email
|
|
const bvConfirmationNo = values.confirmationNumber
|
|
|
|
if (firstName && email && bvConfirmationNo === confirmationNumber) {
|
|
bookingConfirmation = await findBooking(
|
|
confirmationNumber,
|
|
lastName,
|
|
firstName,
|
|
email
|
|
)
|
|
} else {
|
|
return (
|
|
<RenderAdditionalInfoForm
|
|
confirmationNumber={confirmationNumber}
|
|
lastName={lastName}
|
|
/>
|
|
)
|
|
}
|
|
} else {
|
|
return (
|
|
<RenderAdditionalInfoForm
|
|
confirmationNumber={confirmationNumber}
|
|
lastName={lastName}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (!bookingConfirmation) {
|
|
return notFound()
|
|
}
|
|
|
|
const { additionalData, booking, hotel, roomCategories } = bookingConfirmation
|
|
|
|
const user = await getProfileSafely()
|
|
|
|
const intl = await getIntl()
|
|
|
|
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
|
|
)
|
|
}
|
|
let ancillaryPackagesPromise = null
|
|
if (booking.showAncillaries) {
|
|
ancillaryPackagesPromise = getAncillaryPackages({
|
|
fromDate,
|
|
hotelId: hotel.operaId,
|
|
toDate,
|
|
})
|
|
}
|
|
|
|
const imageSrc =
|
|
hotel.hotelContent.images.imageSizes.large ??
|
|
additionalData.gallery?.heroImages[0]?.imageSizes.large ??
|
|
hotel.galleryImages[0]?.imageSizes.large
|
|
|
|
const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
|
|
const promoUrl = new URL(`${baseUrl}/${lang}/`)
|
|
|
|
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 = getHotelAlertsForBookingDates(
|
|
hotel.specialAlerts,
|
|
dt(fromDate),
|
|
dt(toDate)
|
|
)
|
|
|
|
return (
|
|
<MyStayProvider
|
|
bookingConfirmation={maskedBookingConfirmation}
|
|
breakfastPackages={breakfastPackages}
|
|
lang={lang}
|
|
linkedReservationsPromise={linkedReservationsPromise}
|
|
refId={booking.refId}
|
|
roomCategories={roomCategories}
|
|
savedCreditCards={savedCreditCards}
|
|
isLoggedIn={isLoggedIn}
|
|
>
|
|
<main className={styles.main}>
|
|
<div className={styles.imageContainer}>
|
|
<div className={styles.blurOverlay} />
|
|
{imageSrc && (
|
|
<Image
|
|
className={styles.image}
|
|
src={imageSrc}
|
|
alt={hotel.name}
|
|
fill
|
|
/>
|
|
)}
|
|
</div>
|
|
<div className={styles.content}>
|
|
<div className={styles.headerContainer}>
|
|
<Header cityName={hotel.cityName} name={hotel.name} />
|
|
<ReferenceCard />
|
|
</div>
|
|
{booking.showAncillaries && ancillaryPackagesPromise && (
|
|
<Ancillaries
|
|
ancillariesPromise={ancillaryPackagesPromise}
|
|
packages={breakfastPackages}
|
|
user={maskedUser}
|
|
savedCreditCards={savedCreditCards}
|
|
/>
|
|
)}
|
|
|
|
<SingleRoom user={maskedUser} />
|
|
<MultiRoom user={maskedUser} />
|
|
|
|
<BookingSummary hotel={hotel} />
|
|
{!isWebview && (
|
|
<Promo
|
|
title={intl.formatMessage({
|
|
defaultMessage: "Book your next stay",
|
|
})}
|
|
text={intl.formatMessage({
|
|
defaultMessage:
|
|
"Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.",
|
|
})}
|
|
buttonText={intl.formatMessage({
|
|
defaultMessage: "Explore Scandic hotels",
|
|
})}
|
|
href={promoUrl.toString()}
|
|
image={hotel.hotelContent.images}
|
|
/>
|
|
)}
|
|
</div>
|
|
</main>
|
|
<SidePeek />
|
|
</MyStayProvider>
|
|
)
|
|
}
|
|
|
|
if (access === ERROR_BAD_REQUEST) {
|
|
return (
|
|
<main className={styles.main}>
|
|
<div className={styles.form}>
|
|
<AdditionalInfoForm
|
|
confirmationNumber={confirmationNumber}
|
|
lastName={lastName}
|
|
/>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
if (access === ERROR_UNAUTHORIZED) {
|
|
return (
|
|
<main className={styles.main}>
|
|
<div className={styles.logIn}>
|
|
<Typography variant="Title/md">
|
|
<h1>
|
|
{intl.formatMessage({
|
|
defaultMessage: "You need to be logged in to view your booking",
|
|
})}
|
|
</h1>
|
|
</Typography>
|
|
<Typography variant="Body/Lead text">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage:
|
|
"And you need to be logged in with the same member account that made the booking.",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
return notFound()
|
|
}
|
|
|
|
function RenderAdditionalInfoForm({
|
|
confirmationNumber,
|
|
lastName,
|
|
}: {
|
|
confirmationNumber: string
|
|
lastName: string
|
|
}) {
|
|
return (
|
|
<main className={styles.main}>
|
|
<div className={styles.form}>
|
|
<AdditionalInfoForm
|
|
confirmationNumber={confirmationNumber}
|
|
lastName={lastName}
|
|
/>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|