feat(SW-2358): Use personal token if logged in * feat(SW-2903): Use personal token if logged in * Avoid encoding values in cookie * Fix tests Approved-by: Anton Gunnarsson
246 lines
7.6 KiB
TypeScript
246 lines
7.6 KiB
TypeScript
import { cookies } from "next/headers"
|
|
import { notFound } from "next/navigation"
|
|
|
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
|
import { dt } from "@scandic-hotels/common/dt"
|
|
import * as maskValue from "@scandic-hotels/common/utils/maskValue"
|
|
import ScandicLogoIcon from "@scandic-hotels/design-system/Icons/ScandicLogoIcon"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { parseRefId } from "@scandic-hotels/trpc/utils/refId"
|
|
|
|
import {
|
|
findBooking,
|
|
getAncillaryPackages,
|
|
getBookingConfirmation,
|
|
getProfileSafely,
|
|
} from "@/lib/trpc/memoizedRequests"
|
|
|
|
import AdditionalInfoForm, {
|
|
type AdditionalInfoCookieValue,
|
|
} from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
|
|
import { getIntl } from "@/i18n"
|
|
import { isLoggedInUser } from "@/utils/isLoggedInUser"
|
|
|
|
import accessBooking, {
|
|
ACCESS_GRANTED,
|
|
ERROR_BAD_REQUEST,
|
|
ERROR_UNAUTHORIZED,
|
|
} from "../accessBooking"
|
|
import Footer from "./Footer"
|
|
import Specification from "./Specification"
|
|
import Total from "./Total"
|
|
import Tracking from "./tracking"
|
|
|
|
import styles from "./receipt.module.css"
|
|
|
|
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
|
|
|
export async function Receipt({ refId }: { refId: string }) {
|
|
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) {
|
|
const values = JSON.parse(bv) as AdditionalInfoCookieValue
|
|
const firstName = values.firstName
|
|
const email = values.email
|
|
|
|
if (firstName && email) {
|
|
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 { hotel, room, booking } = bookingConfirmation
|
|
const user = await getProfileSafely()
|
|
const intl = await getIntl()
|
|
|
|
const access = accessBooking(booking.guest, lastName, user, bv)
|
|
|
|
if (access === ACCESS_GRANTED) {
|
|
const ancillaryPackages = await getAncillaryPackages({
|
|
fromDate: dt(booking.checkInDate).format("YYYY-MM-DD"),
|
|
hotelId: hotel.operaId,
|
|
toDate: dt(booking.checkOutDate).format("YYYY-MM-DD"),
|
|
})
|
|
|
|
const currency =
|
|
booking.currencyCode !== CurrencyEnum.POINTS
|
|
? booking.currencyCode
|
|
: (booking.ancillaries.find((a) => a.currency !== CurrencyEnum.POINTS)
|
|
?.currency ??
|
|
booking.packages.find((p) => p.currency !== CurrencyEnum.POINTS)
|
|
?.currency)
|
|
|
|
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 { booking: maskedBooking } = maskedBookingConfirmation
|
|
|
|
return (
|
|
<main className={styles.main}>
|
|
<div>
|
|
<ScandicLogoIcon width="89px" height="19px" />
|
|
<div className={styles.addresses}>
|
|
<div>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<div>{hotel.name}</div>
|
|
</Typography>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<div>
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{`${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`}
|
|
</div>
|
|
</Typography>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<div className={`${styles.tertiary} ${styles.addressMargin}`}>
|
|
{hotel.contactInformation.email}
|
|
</div>
|
|
</Typography>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<div className={styles.tertiary}>
|
|
{hotel.contactInformation.phoneNumber}
|
|
</div>
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.rightColumn}>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
<div>{`${maskedBooking.guest.firstName} ${maskedBooking.guest.lastName}`}</div>
|
|
</Typography>
|
|
{maskedBooking.guest.membershipNumber && (
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
<div>{`${intl.formatMessage({
|
|
defaultMessage: "Member",
|
|
})} ${maskedBooking.guest.membershipNumber}`}</div>
|
|
</Typography>
|
|
)}
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<div className={`${styles.tertiary} ${styles.addressMargin}`}>
|
|
{maskedBooking.guest.email}
|
|
</div>
|
|
</Typography>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
<div className={styles.tertiary}>
|
|
{maskedBooking.guest.phoneNumber}
|
|
</div>
|
|
</Typography>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Total booking={maskedBooking} currency={currency} />
|
|
<Specification
|
|
ancillaryPackages={ancillaryPackages}
|
|
booking={maskedBooking}
|
|
currency={currency}
|
|
/>
|
|
|
|
<hr className={styles.divider} />
|
|
|
|
<Footer booking={maskedBooking} room={room} />
|
|
<Tracking />
|
|
</main>
|
|
)
|
|
}
|
|
|
|
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>
|
|
)
|
|
}
|