Merged in chore/SW-2878-extract-booking-confirmation-pag (pull request #2779)

Chore/SW-2878 extract booking confirmation pag

* chore(SW-2878): Moved booking confirmation page to booking-flow package

* chore(SW-2878): Fixed promo styles as per design

* chore(SW-2878): Kept tiny duplicate function to avoid export from booking-flow package


Approved-by: Anton Gunnarsson
This commit is contained in:
Hrishikesh Vaipurkar
2025-09-10 07:50:48 +00:00
parent c6da0fb8cb
commit a5790ee454
77 changed files with 410 additions and 371 deletions

View File

@@ -1,12 +1,8 @@
import { cookies } from "next/headers"
import { notFound, redirect } from "next/navigation"
import { BookingConfirmationPage as BookingConfirmationPagePrimitive } from "@scandic-hotels/booking-flow/pages/BookingConfirmationPage"
import { decrypt } from "@scandic-hotels/trpc/utils/encryption"
import { MEMBERSHIP_FAILED_ERROR } from "@/constants/booking"
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import BookingConfirmation from "@/components/HotelReservation/BookingConfirmation"
import Tracking from "@/components/HotelReservation/BookingConfirmation/Tracking"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import type { LangParams, PageArgs } from "@/types/params"
@@ -14,35 +10,20 @@ export default async function BookingConfirmationPage(
props: PageArgs<LangParams, { RefId?: string }>
) {
const searchParams = await props.searchParams
const params = await props.params
const refId = searchParams.RefId
if (!refId) {
notFound()
}
const cookieStore = await cookies()
const sig = cookieStore.get("bcsig")?.value
if (!sig) {
redirect(`/${params.lang}`)
}
const expire = Number(decrypt(sig))
const now = Math.floor(Date.now() / 1000)
if (typeof expire === "number" && !isNaN(expire) && now > expire) {
redirect(`/${params.lang}`)
}
void getBookingConfirmation(refId)
const membershipFailedError =
searchParams.errorCode === MEMBERSHIP_FAILED_ERROR
const lang = await getLang()
const intl = await getIntl()
return (
<BookingConfirmation
refId={refId}
membershipFailedError={membershipFailedError}
<BookingConfirmationPagePrimitive
intl={intl}
lang={lang}
searchParams={searchParams}
renderTracking={(props) => (
<Tracking
bookingConfirmation={props.bookingConfirmation}
refId={props.refId}
/>
)}
/>
)
}

View File

@@ -1,11 +1,11 @@
import { notFound } from "next/navigation"
import { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/booking"
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
import { logger } from "@scandic-hotels/common/logger"
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode"
import { PaymentCallbackStatusEnum } from "@/constants/booking"
import { serverClient } from "@/lib/trpc/server"
import GuaranteeCallback from "@/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback"

View File

@@ -1,5 +1,6 @@
import { notFound } from "next/navigation"
import { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/booking"
import {
bookingConfirmation,
details,
@@ -11,7 +12,6 @@ import { getBooking } from "@scandic-hotels/trpc/routers/booking/utils"
import { encrypt } from "@scandic-hotels/trpc/utils/encryption"
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
import { PaymentCallbackStatusEnum } from "@/constants/booking"
import { serverClient } from "@/lib/trpc/server"
import { auth } from "@/auth"

View File

@@ -1,67 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import Body from "@scandic-hotels/design-system/Body"
import Link from "@scandic-hotels/design-system/Link"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import styles from "./hotelDetails.module.css"
import type { BookingConfirmationHotelDetailsProps } from "@/types/components/hotelReservation/bookingConfirmation/hotelDetails"
export default function HotelDetails({
hotel,
}: BookingConfirmationHotelDetailsProps) {
const intl = useIntl()
return (
<div className={styles.container}>
<div className={styles.details}>
<Subtitle color="uiTextHighContrast" type="two">
{intl.formatMessage({
defaultMessage: "Hotel details",
})}
</Subtitle>
<div className={styles.hotel}>
<Body color="uiTextHighContrast">{hotel.name}</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage(
{
defaultMessage: "{streetAddress}, {zipCode} {city}",
},
{
streetAddress: hotel.address.streetAddress,
zipCode: hotel.address.zipCode,
city: hotel.address.city,
}
)}
</Body>
<Body asChild color="uiTextHighContrast">
<Link
className={styles.link}
href={`tel:${hotel.contactInformation.phoneNumber}`}
>
{hotel.contactInformation.phoneNumber}
</Link>
</Body>
</div>
</div>
<div className={styles.contact}>
<Link
className={styles.link}
color="Text/Interactive/Secondary"
href={`mailto:${hotel.contactInformation.email}`}
>
{hotel.contactInformation.email}
</Link>
<Link
className={styles.link}
color="Text/Interactive/Secondary"
href={hotel.contactInformation.websiteUrl}
>
{hotel.contactInformation.websiteUrl}
</Link>
</div>
</div>
)
}

View File

@@ -1,48 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
import Body from "@scandic-hotels/design-system/Body"
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import styles from "./paymentDetails.module.css"
export default function PaymentDetails() {
const intl = useIntl()
const { rooms, formattedTotalCost } = useBookingConfirmationStore(
(state) => ({
rooms: state.rooms,
formattedTotalCost: state.formattedTotalCost,
})
)
const hasAllRoomsLoaded = rooms.every((room) => room)
return (
<div className={styles.details}>
<Subtitle color="uiTextHighContrast" type="two">
{intl.formatMessage({
defaultMessage: "Payment details",
})}
</Subtitle>
<div className={styles.payment}>
{hasAllRoomsLoaded ? (
<Body color="uiTextHighContrast">
{intl.formatMessage(
{
defaultMessage: "Total cost: {amount}",
},
{
amount: formattedTotalCost,
}
)}
</Body>
) : (
<SkeletonShimmer width={"100%"} />
)}
</div>
</div>
)
}

View File

@@ -1,26 +0,0 @@
import Body from "@scandic-hotels/design-system/Body"
import Link from "@scandic-hotels/design-system/Link"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Title from "@scandic-hotels/design-system/Title"
import styles from "./promo.module.css"
import type { PromoProps } from "@/types/components/hotelReservation/bookingConfirmation/promo"
export default function Promo({ buttonText, href, text, title }: PromoProps) {
return (
<Link className={styles.link} color="none" href={href}>
<article className={styles.promo}>
<Title color="white" level="h4">
{title}
</Title>
<Body className={styles.text} color="white" textAlign="center">
{text}
</Body>
<Button asChild intent="primary" size="small" theme="primaryStrong">
<div>{buttonText}</div>
</Button>
</article>
</Link>
)
}

View File

@@ -1,29 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import Body from "@scandic-hotels/design-system/Body"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import styles from "./retry.module.css"
import type { RetryProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/linkedReservation"
export default function Retry({ handleRefetch }: RetryProps) {
const intl = useIntl()
return (
<div className={styles.retry}>
<Body>
{intl.formatMessage({
defaultMessage: "Something went wrong!",
})}
</Body>
<Button size={"small"} onPress={handleRefetch}>
{intl.formatMessage({
defaultMessage: "Try again",
})}
</Button>
</div>
)
}

View File

@@ -2,6 +2,7 @@ import { createHash } from "crypto"
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
import { invertedBedTypeMap } from "@scandic-hotels/booking-flow/utils/SelectRate"
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { RateEnum } from "@scandic-hotels/common/constants/rate"
import {
@@ -14,8 +15,6 @@ import {
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { CancellationRuleEnum } from "@/constants/booking"
import { readPaymentInfoFromSessionStorage } from "@/components/HotelReservation/EnterDetails/Payment/helpers"
import { getSpecialRoomType } from "@/utils/specialRoomType"

View File

@@ -4,10 +4,10 @@ import { useRouter } from "next/navigation"
import { useEffect } from "react"
import { serializeBookingSearchParams } from "@scandic-hotels/booking-flow/utils/url"
import { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/booking"
import { trackEvent } from "@scandic-hotels/common/tracking/base"
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
import { PaymentCallbackStatusEnum } from "@/constants/booking"
import { detailsStorageName } from "@/stores/enter-details"
import { trackPaymentEvent } from "@/utils/tracking"

View File

@@ -3,11 +3,10 @@
import { useRouter } from "next/navigation"
import { useEffect } from "react"
import { MEMBERSHIP_FAILED_ERROR } from "@scandic-hotels/common/constants/booking"
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
import { BookingStatusEnum } from "@scandic-hotels/trpc/enums/bookingStatus"
import { MEMBERSHIP_FAILED_ERROR } from "@/constants/booking"
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
import TimeoutSpinner from "./TimeoutSpinner"

View File

@@ -9,6 +9,7 @@ import {
} from "react-aria-components"
import { useIntl } from "react-intl"
import { getFeatureDescription } from "@scandic-hotels/booking-flow/utils/getRoomFeatureDescription"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import Body from "@scandic-hotels/design-system/Body"
import Caption from "@scandic-hotels/design-system/Caption"
@@ -17,8 +18,6 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import { getFeatureDescription } from "@/components/HotelReservation/utils/getRoomFeatureDescription"
import styles from "./priceChangeSummary.module.css"
import type { RoomState } from "@/types/stores/enter-details"

View File

@@ -1,8 +1,8 @@
"use client"
import { useEnterDetailsStore } from "@/stores/enter-details"
import { SidePanel } from "@scandic-hotels/booking-flow/components/SidePanel"
import SidePanel from "@/components/HotelReservation/SidePanel"
import { useEnterDetailsStore } from "@/stores/enter-details"
import SummaryUI from "./UI"

View File

@@ -1,6 +1,7 @@
import { cx } from "class-variance-authority"
import { useIntl } from "react-intl"
import { getFeatureDescription } from "@scandic-hotels/booking-flow/utils/getRoomFeatureDescription"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Button } from "@scandic-hotels/design-system/Button"
@@ -10,8 +11,6 @@ import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
import { getFeatureDescription } from "@/components/HotelReservation/utils/getRoomFeatureDescription"
import Breakfast from "./Breakfast"
import styles from "./room.module.css"

View File

@@ -5,7 +5,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./promo.module.css"
import type { PromoProps } from "@/types/components/hotelReservation/bookingConfirmation/promo"
import type { PromoProps } from "@scandic-hotels/booking-flow/types/components/promo/promoProps"
export default function Promo({
buttonText,

View File

@@ -1,12 +1,12 @@
"use client"
import { useIntl } from "react-intl"
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import { preliminaryReceipt } from "@scandic-hotels/common/constants/routes/myStay"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Link from "@scandic-hotels/design-system/Link"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { CancellationRuleEnum } from "@/constants/booking"
import { useMyStayStore } from "@/stores/my-stay"
import useLang from "@/hooks/useLang"

View File

@@ -2,6 +2,7 @@
import { useIntl } from "react-intl"
import { IconForFeatureCode } from "@scandic-hotels/booking-flow/utils/SelectRate"
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import { changeOrCancelDateFormat } from "@scandic-hotels/common/constants/dateFormats"
import { RateEnum } from "@scandic-hotels/common/constants/rate"
import { dt } from "@scandic-hotels/common/dt"
@@ -15,8 +16,6 @@ import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { CancellationRuleEnum } from "@/constants/booking"
import useRateTitles from "@/hooks/booking/useRateTitles"
import useLang from "@/hooks/useLang"

View File

@@ -1,11 +1,10 @@
import { useIntl } from "react-intl"
import { getFeatureDescription } from "@scandic-hotels/booking-flow/utils/getRoomFeatureDescription"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { useMyStayStore } from "@/stores/my-stay"
import { getFeatureDescription } from "@/components/HotelReservation/utils/getRoomFeatureDescription"
import Row from "./Row"
export default function Packages() {

View File

@@ -1,12 +1,12 @@
import { useIntl } from "react-intl"
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import { RateEnum } from "@scandic-hotels/common/constants/rate"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { CancellationRuleEnum } from "@/constants/booking"
import { useMyStayStore } from "@/stores/my-stay"
import useRateTitles from "@/hooks/booking/useRateTitles"

View File

@@ -3,11 +3,10 @@
import { useRouter } from "next/navigation"
import { useEffect } from "react"
import { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/booking"
import { trackEvent } from "@scandic-hotels/common/tracking/base"
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
import { PaymentCallbackStatusEnum } from "@/constants/booking"
import {
clearGlaSessionStorage,
readGlaFromSessionStorage,

View File

@@ -1,7 +1,6 @@
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum"
import { CancellationRuleEnum } from "@/constants/booking"
export function formatChildBedPreferences({
childrenAges,
childBedPreferences,

View File

@@ -1,11 +1,10 @@
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import { dt } from "@scandic-hotels/common/dt"
import { BookingStatusEnum } from "@scandic-hotels/trpc/enums/bookingStatus"
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
import { PackageTypeEnum } from "@scandic-hotels/trpc/enums/packages"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { CancellationRuleEnum } from "@/constants/booking"
import { convertToChildType } from "../../utils/convertToChildType"
import { getPriceType } from "../../utils/getPriceType"
import { formatChildBedPreferences } from "../utils"

View File

@@ -1,5 +1,6 @@
import { useIntl } from "react-intl"
import { getFeatureDescription } from "@scandic-hotels/booking-flow/utils/getRoomFeatureDescription"
import { sumPackages } from "@scandic-hotels/booking-flow/utils/SelectRate"
import { changeOrCancelDateFormat } from "@scandic-hotels/common/constants/dateFormats"
import { dt } from "@scandic-hotels/common/dt"
@@ -17,7 +18,6 @@ import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import GuestDetails from "@/components/HotelReservation/MyStay/GuestDetails"
import PriceType from "@/components/HotelReservation/MyStay/PriceType"
import { hasModifiableRate } from "@/components/HotelReservation/MyStay/utils"
import { getFeatureDescription } from "@/components/HotelReservation/utils/getRoomFeatureDescription"
import useLang from "@/hooks/useLang"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"

View File

@@ -1,11 +1,11 @@
import { type NextMiddleware, NextResponse } from "next/server"
import { SEARCHTYPE } from "@scandic-hotels/common/constants/booking"
import { login } from "@scandic-hotels/common/constants/routes/handleAuth"
import { findLang } from "@scandic-hotels/common/utils/languages"
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
import { SEARCHTYPE } from "@/constants/booking"
import { getPublicNextURL } from "@/server/utils"
import { auth } from "@/auth"

View File

@@ -1,5 +0,0 @@
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
export interface BookingConfirmationHotelDetailsProps {
hotel: BookingConfirmation["hotel"]
}

View File

@@ -1,3 +0,0 @@
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
export interface PromosProps extends Pick<BookingConfirmation, "booking"> {}

View File

@@ -1,7 +0,0 @@
import type { Room } from "@scandic-hotels/booking-flow/types/stores/booking-confirmation"
export interface BookingConfirmationReceiptRoomProps {
room: Room
roomNumber: number
roomCount: number
}

View File

@@ -1,11 +0,0 @@
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { Room } from "@scandic-hotels/trpc/types/hotel"
export interface BookingConfirmationRoomsProps
extends Pick<BookingConfirmation, "booking"> {
mainRoom: Room & {
bedType: Room["roomTypes"][number]
}
checkInTime: string
checkOutTime: string
}

View File

@@ -1,10 +0,0 @@
export interface LinkedReservationProps {
checkInTime: string
checkOutTime: string
refId: string
roomIndex: number
}
export interface RetryProps {
handleRefetch: () => void
}

View File

@@ -1,9 +0,0 @@
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
export interface RoomProps {
booking: BookingConfirmation["booking"]
checkInTime: string
checkOutTime: string
img?: NonNullable<BookingConfirmation["room"]>["images"][number]
roomName: NonNullable<BookingConfirmation["room"]>["name"]
}

View File

@@ -1,6 +0,0 @@
import type { VariantProps } from "class-variance-authority"
import type { sidePanelVariants } from "@/components/HotelReservation/SidePanel/variants"
export interface SidePanelProps
extends VariantProps<typeof sidePanelVariants> {}

View File

@@ -0,0 +1,74 @@
"use client"
import { useIntl } from "react-intl"
import Link from "@scandic-hotels/design-system/Link"
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./hotelDetails.module.css"
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
export function HotelDetails({
hotel,
}: {
hotel: BookingConfirmation["hotel"]
}) {
const intl = useIntl()
return (
<div className={styles.container}>
<div className={styles.details}>
<Typography variant={"Title/Subtitle/md"}>
<h3>
{intl.formatMessage({
defaultMessage: "Hotel details",
})}
</h3>
</Typography>
<Typography variant={"Body/Paragraph/mdRegular"}>
<div className={styles.hotel}>
<p>{hotel.name}</p>
<p>
{intl.formatMessage(
{
defaultMessage: "{streetAddress}, {zipCode} {city}",
},
{
streetAddress: hotel.address.streetAddress,
zipCode: hotel.address.zipCode,
city: hotel.address.city,
}
)}
</p>
<p>
<Link
className={styles.link}
href={`tel:${hotel.contactInformation.phoneNumber}`}
>
{hotel.contactInformation.phoneNumber}
</Link>
</p>
</div>
</Typography>
</div>
<div className={styles.contact}>
<Link
className={styles.link}
color="Text/Interactive/Secondary"
href={`mailto:${hotel.contactInformation.email}`}
textDecoration={"underline"}
>
{hotel.contactInformation.email}
</Link>
<Link
className={styles.link}
color="Text/Interactive/Secondary"
href={hotel.contactInformation.websiteUrl}
textDecoration={"underline"}
>
{hotel.contactInformation.websiteUrl}
</Link>
</div>
</div>
)
}

View File

@@ -0,0 +1,52 @@
"use client"
import { useIntl } from "react-intl"
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useBookingConfirmationStore } from "../../../stores/booking-confirmation"
import styles from "./paymentDetails.module.css"
export function PaymentDetails() {
const intl = useIntl()
const { rooms, formattedTotalCost } = useBookingConfirmationStore(
(state) => ({
rooms: state.rooms,
formattedTotalCost: state.formattedTotalCost,
})
)
const hasAllRoomsLoaded = rooms.every((room) => room)
return (
<div className={styles.details}>
<Typography variant={"Title/Subtitle/md"}>
<h2>
{intl.formatMessage({
defaultMessage: "Payment details",
})}
</h2>
</Typography>
<div className={styles.payment}>
{hasAllRoomsLoaded ? (
<Typography variant={"Body/Paragraph/mdRegular"}>
<p>
{intl.formatMessage(
{
defaultMessage: "Total cost: {amount}",
},
{
amount: formattedTotalCost,
}
)}
</p>
</Typography>
) : (
<SkeletonShimmer width={"100%"} />
)}
</div>
</div>
)
}

View File

@@ -1,12 +1,12 @@
"use client"
import PriceDetailsModal from "@scandic-hotels/booking-flow/components/PriceDetailsModal"
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { dt } from "@scandic-hotels/common/dt"
import PriceDetailsModal from "../../../components/PriceDetailsModal"
import { useBookingConfirmationStore } from "../../../stores/booking-confirmation"
import { mapToPrice } from "./mapToPrice"
import type { Price } from "@/types/components/hotelReservation/price"
import type { Price } from "../../../types/price"
export default function PriceDetails() {
const { bookingCode, currency, fromDate, rooms, vat, toDate } =

View File

@@ -7,9 +7,10 @@ import {
packageSchema,
} from "@scandic-hotels/trpc/routers/hotels/schemas/packages"
import type { Room } from "@scandic-hotels/booking-flow/types/stores/booking-confirmation"
import type { Package } from "@scandic-hotels/trpc/types/packages"
import type { Room } from "../../../types/stores/booking-confirmation"
export function mapToPrice(rooms: (Room | null)[], nights: number) {
return rooms
.filter((room): room is Room => !!room)

View File

@@ -0,0 +1,25 @@
import { Button } from "@scandic-hotels/design-system/Button"
import Link from "@scandic-hotels/design-system/Link"
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./promo.module.css"
import type { PromoProps } from "../../../../types/components/promo/promoProps"
export function Promo({ buttonText, href, text, title }: PromoProps) {
return (
<Link className={styles.link} color="none" href={href}>
<article className={styles.promo}>
<Typography variant={"Title/smLowCase"}>
<h4>{title}</h4>
</Typography>
<Typography variant={"Body/Paragraph/mdRegular"}>
<p className={styles.text}>{text}</p>
</Typography>
<Button size="Small" variant={"Secondary"} color={"Inverted"} wrapping>
<div>{buttonText}</div>
</Button>
</article>
</Link>
)
}

View File

@@ -4,6 +4,7 @@
background-repeat: no-repeat;
background-size: cover;
border-radius: var(--Medium, 8px);
color: var(--Text-Brand-OnPrimary-2-Heading);
display: flex;
flex: 1 0 320px;
flex-direction: column;
@@ -37,4 +38,5 @@
.text {
max-width: 400px;
text-align: center;
}

View File

@@ -4,17 +4,18 @@ import { useIntl } from "react-intl"
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
import useLang from "@/hooks/useLang"
import Promo from "./Promo"
import useLang from "../../../hooks/useLang"
import { Promo } from "./Promo"
import styles from "./promos.module.css"
import type { AdditionalInfoCookieValue } from "@scandic-hotels/booking-flow/types/components/findMyBooking/additionalInfoCookieValue"
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { PromosProps } from "@/types/components/hotelReservation/bookingConfirmation/promos"
import type { AdditionalInfoCookieValue } from "../../../types/components/findMyBooking/additionalInfoCookieValue"
export default function Promos({ booking }: PromosProps) {
export interface PromosProps extends Pick<BookingConfirmation, "booking"> {}
export function Promos({ booking }: PromosProps) {
const intl = useIntl()
const lang = useLang()
const { refId, confirmationNumber, hotelId } = booking

View File

@@ -3,7 +3,7 @@
import { cx } from "class-variance-authority"
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Button } from "@scandic-hotels/design-system/Button"
import { Divider } from "@scandic-hotels/design-system/Divider"
@@ -12,18 +12,22 @@ import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum"
import { CancellationRuleEnum } from "@/constants/booking"
import { getFeatureDescription } from "@/components/HotelReservation/utils/getRoomFeatureDescription"
import { useBookingConfirmationStore } from "../../../../stores/booking-confirmation"
import { getFeatureDescription } from "../../../../utils/getRoomFeatureDescription"
import Breakfast from "./Breakfast"
import RoomSkeletonLoader from "./RoomSkeletonLoader"
import styles from "./room.module.css"
import type { BookingConfirmationReceiptRoomProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt"
import type { Room } from "../../../../types/stores/booking-confirmation"
export default function ReceiptRoom({
type BookingConfirmationReceiptRoomProps = {
room: Room
roomNumber: number
roomCount: number
}
export function ReceiptRoom({
room,
roomNumber,
roomCount,

View File

@@ -3,12 +3,12 @@
import { cx } from "class-variance-authority"
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
import { BookingCodeChip } from "@scandic-hotels/design-system/BookingCodeChip"
import { Divider } from "@scandic-hotels/design-system/Divider"
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useBookingConfirmationStore } from "../../../../stores/booking-confirmation"
import PriceDetails from "../../PriceDetails"
import styles from "./totalPrice.module.css"

View File

@@ -2,21 +2,20 @@
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
import { longDateFormat } from "@scandic-hotels/common/constants/dateFormats"
import { dt } from "@scandic-hotels/common/dt"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import useLang from "@/hooks/useLang"
import Room from "./Room"
import useLang from "../../../hooks/useLang"
import { useBookingConfirmationStore } from "../../../stores/booking-confirmation"
import { ReceiptRoom as Room } from "./Room"
import TotalPrice from "./TotalPrice"
import styles from "./receipt.module.css"
export default function Receipt() {
export function Receipt() {
const lang = useLang()
const intl = useIntl()
const { rooms, fromDate, toDate } = useBookingConfirmationStore((state) => ({

View File

@@ -0,0 +1,33 @@
"use client"
import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./retry.module.css"
export interface RetryProps {
handleRefetch: () => void
}
export default function Retry({ handleRefetch }: RetryProps) {
const intl = useIntl()
return (
<div className={styles.retry}>
<Typography variant={"Body/Paragraph/mdRegular"}>
<p>
{intl.formatMessage({
defaultMessage: "Something went wrong!",
})}
</p>
</Typography>
<Button size={"Small"} onPress={handleRefetch}>
{intl.formatMessage({
defaultMessage: "Try again",
})}
</Button>
</div>
)
}

View File

@@ -3,19 +3,23 @@
import { useEffect } from "react"
import { useIntl } from "react-intl"
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { trpc } from "@scandic-hotels/trpc/client"
import useLang from "@/hooks/useLang"
import useLang from "../../../../hooks/useLang"
import { useBookingConfirmationStore } from "../../../../stores/booking-confirmation"
import { mapRoomState } from "../../utils"
import Room from "../Room"
import { Room } from "../Room"
import { LinkedReservationCardSkeleton } from "./LinkedReservationCardSkeleton"
import Retry from "./Retry"
import type { LinkedReservationProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/linkedReservation"
export interface LinkedReservationProps {
checkInTime: string
checkOutTime: string
refId: string
roomIndex: number
}
export function LinkedReservation({
checkInTime,

View File

@@ -2,28 +2,34 @@
import { useIntl } from "react-intl"
import { RoomDetailsSidePeek } from "@scandic-hotels/booking-flow/components/RoomDetailsSidePeek"
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
import {
changeOrCancelDateFormat,
longDateFormat,
} from "@scandic-hotels/common/constants/dateFormats"
import { dt } from "@scandic-hotels/common/dt"
import Caption from "@scandic-hotels/design-system/Caption"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Image from "@scandic-hotels/design-system/Image"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { CancellationRuleEnum } from "@/constants/booking"
import useLang from "@/hooks/useLang"
import { RoomDetailsSidePeek } from "../../../../components/RoomDetailsSidePeek"
import useLang from "../../../../hooks/useLang"
import { useBookingConfirmationStore } from "../../../../stores/booking-confirmation"
import styles from "./room.module.css"
import type { RoomProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/room"
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
export default function Room({
export interface RoomProps {
booking: BookingConfirmation["booking"]
checkInTime: string
checkOutTime: string
img?: NonNullable<BookingConfirmation["room"]>["images"][number]
roomName: NonNullable<BookingConfirmation["room"]>["name"]
}
export function Room({
booking,
checkInTime,
checkOutTime,
@@ -67,11 +73,13 @@ export default function Room({
icon="check_circle"
size={20}
/>
<Caption>
{intl.formatMessage({
defaultMessage: "Membership benefits applied",
})}
</Caption>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
{intl.formatMessage({
defaultMessage: "Membership benefits applied",
})}
</p>
</Typography>
</>
</div>
) : null}

View File

@@ -1,21 +1,33 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getIntl } from "@/i18n"
import { LinkedReservation } from "./LinkedReservation"
import Room from "./Room"
import { Room } from "./Room"
import styles from "./rooms.module.css"
import type { BookingConfirmationRoomsProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms"
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { Room as RoomProp } from "@scandic-hotels/trpc/types/hotel"
export default async function Rooms({
export interface BookingConfirmationRoomsProps
extends Pick<BookingConfirmation, "booking"> {
mainRoom: RoomProp & {
bedType: RoomProp["roomTypes"][number]
}
checkInTime: string
checkOutTime: string
}
export function Rooms({
booking,
checkInTime,
checkOutTime,
mainRoom,
}: BookingConfirmationRoomsProps) {
const intl = await getIntl()
const intl = useIntl()
return (
<section className={styles.rooms}>

View File

@@ -1,36 +1,42 @@
import { notFound } from "next/navigation"
import { Confirmation } from "@scandic-hotels/booking-flow/components/BookingConfirmation/Confirmation"
import BookingConfirmationProvider from "@scandic-hotels/booking-flow/providers/BookingConfirmationProvider"
import { filterOverlappingDates } from "@scandic-hotels/booking-flow/utils/SelectRate"
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
import { dt } from "@scandic-hotels/common/dt"
import { Alert } from "@scandic-hotels/design-system/Alert"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import HotelDetails from "@/components/HotelReservation/BookingConfirmation/HotelDetails"
import PaymentDetails from "@/components/HotelReservation/BookingConfirmation/PaymentDetails"
import Promos from "@/components/HotelReservation/BookingConfirmation/Promos"
import Receipt from "@/components/HotelReservation/BookingConfirmation/Receipt"
import Rooms from "@/components/HotelReservation/BookingConfirmation/Rooms"
import SidePanel from "@/components/HotelReservation/SidePanel"
import { getIntl } from "@/i18n"
import Tracking from "./Tracking"
import { BookingConfirmationProvider } from "../../providers/BookingConfirmationProvider"
import { getBookingConfirmation } from "../../trpc/memoizedRequests/getBookingConfirmation"
import { filterOverlappingDates } from "../../utils/SelectRate"
import { SidePanel } from "../SidePanel"
import { Confirmation } from "./Confirmation"
import { HotelDetails } from "./HotelDetails"
import { PaymentDetails } from "./PaymentDetails"
import { Promos } from "./Promos"
import { Receipt } from "./Receipt"
import { Rooms } from "./Rooms"
import { mapRoomState } from "./utils"
import styles from "./bookingConfirmation.module.css"
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { IntlShape } from "react-intl"
type BookingConfirmationProps = {
intl: IntlShape
refId: string
membershipFailedError: boolean
renderTracking: (trackingProps: {
bookingConfirmation: BookingConfirmation
refId: string
}) => React.ReactNode
}
export default async function BookingConfirmation({
export async function BookingConfirmation({
intl,
refId,
membershipFailedError,
renderTracking,
}: BookingConfirmationProps) {
const bookingConfirmation = await getBookingConfirmation(refId)
@@ -44,7 +50,6 @@ export default async function BookingConfirmation({
return notFound()
}
const intl = await getIntl()
return (
<BookingConfirmationProvider
bookingCode={booking.bookingCode}
@@ -106,7 +111,8 @@ export default async function BookingConfirmation({
</SidePanel>
</aside>
</Confirmation>
<Tracking bookingConfirmation={bookingConfirmation} refId={refId} />
{renderTracking({ bookingConfirmation, refId })}
</BookingConfirmationProvider>
)
}

View File

@@ -4,12 +4,13 @@ import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
import type { BookingConfirmationRoom } from "@scandic-hotels/booking-flow/types/components/bookingConfirmation/bookingConfirmation"
import type {
BookingConfirmationSchema,
PackageSchema,
} from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { BookingConfirmationRoom } from "../../types/components/bookingConfirmation/bookingConfirmation"
export function mapRoomState(
booking: BookingConfirmationSchema,
room: BookingConfirmationRoom,

View File

@@ -21,7 +21,7 @@ import Room from "../Room"
import styles from "./summaryContent.module.css"
import type { Price } from "../../../../../../contexts/SelectRate/getTotalPrice"
import type { Price } from "../../../../../../types/price"
export type SelectRateSummaryProps = {
isUserLoggedIn: boolean

View File

@@ -2,9 +2,11 @@ import { sidePanelVariants } from "./variants"
import styles from "./sidePanel.module.css"
import type { SidePanelProps } from "@/types/components/hotelReservation/sidePanel"
import type { VariantProps } from "class-variance-authority"
export default function SidePanel({
interface SidePanelProps extends VariantProps<typeof sidePanelVariants> {}
export function SidePanel({
children,
variant,
}: React.PropsWithChildren<SidePanelProps>) {

View File

@@ -31,13 +31,14 @@ import { clearRooms } from "./clearRooms"
import { DebugButton } from "./DebugButton"
import { findUnavailableSelectedRooms } from "./findUnavailableSelectedRooms"
import { getSelectedPackages } from "./getSelectedPackages"
import { getTotalPrice, type Price } from "./getTotalPrice"
import { getTotalPrice } from "./getTotalPrice"
import { includeRoomInfo } from "./includeRoomInfo"
import { isRateSelected as isRateSelected_Inner } from "./isRateSelected"
import type { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
import type { SelectRateBooking } from "../../types/components/selectRate/selectRate"
import type { Price } from "../../types/price"
import type {
AvailabilityWithRoomInfo,
DefaultRoomPackage,

View File

@@ -5,21 +5,9 @@ import { sumPackages, sumPackagesRequestedPrice } from "../../utils/SelectRate"
import type { RedemptionProduct } from "@scandic-hotels/trpc/types/roomAvailability"
import type { Price } from "../../types/price"
import type { AvailabilityWithRoomInfo, Rate, RoomPackage } from "./types"
type TPrice = {
additionalPrice?: number
additionalPriceCurrency?: CurrencyEnum
currency: CurrencyEnum
price: number
regularPrice?: number
}
export type Price = {
requested?: TPrice
local: TPrice
}
type SelectedRate = {
roomConfiguration: AvailabilityWithRoomInfo | null
rate: Rate | undefined

View File

@@ -1,13 +1,12 @@
import { type RouterOutput } from "@scandic-hotels/trpc/client"
import { type Price } from "./getTotalPrice"
import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import type { RoomsAvailabilityOutputSchema } from "@scandic-hotels/trpc/types/availability"
import type { PackageEnum } from "@scandic-hotels/trpc/types/packages"
import type { RoomConfiguration } from "@scandic-hotels/trpc/types/roomAvailability"
import type { BookingCodeFilterEnum } from "../../stores/bookingCode-filter"
import type { Price } from "../../types/price"
export type SelectRateContext = {
hotel: QueryData<RouterOutput["hotel"]["get"]>

View File

@@ -0,0 +1,62 @@
import { cookies } from "next/headers"
import { notFound, redirect } from "next/navigation"
import { MEMBERSHIP_FAILED_ERROR } from "@scandic-hotels/common/constants/booking"
import { decrypt } from "@scandic-hotels/trpc/utils/encryption"
import { BookingConfirmation } from "../components/BookingConfirmation"
import { getBookingConfirmation } from "../trpc/memoizedRequests/getBookingConfirmation"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { BookingConfirmation as BookingConfirmationType } from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { IntlShape } from "react-intl"
import type { NextSearchParams } from "../types"
export async function BookingConfirmationPage({
intl,
lang,
searchParams,
renderTracking,
}: {
intl: IntlShape
lang: Lang
searchParams: NextSearchParams
renderTracking: (trackingProps: {
bookingConfirmation: BookingConfirmationType
refId: string
}) => React.ReactNode
}) {
const refId = searchParams.RefId?.toString()
if (!refId) {
notFound()
}
const cookieStore = await cookies()
const sig = cookieStore.get("bcsig")?.value
if (!sig) {
redirect(`/${lang}`)
}
const expire = Number(decrypt(sig))
const now = Math.floor(Date.now() / 1000)
if (typeof expire === "number" && !isNaN(expire) && now > expire) {
redirect(`/${lang}`)
}
void getBookingConfirmation(refId)
const membershipFailedError =
searchParams.errorCode === MEMBERSHIP_FAILED_ERROR
return (
<BookingConfirmation
intl={intl}
refId={refId}
membershipFailedError={membershipFailedError}
renderTracking={renderTracking}
/>
)
}

View File

@@ -12,7 +12,7 @@ import { createBookingConfirmationStore } from "../stores/booking-confirmation"
import type { BookingConfirmationStore } from "../types/contexts/booking-confirmation"
import type { BookingConfirmationProviderProps } from "../types/providers/booking-confirmation"
export default function BookingConfirmationProvider({
export function BookingConfirmationProvider({
bookingCode,
children,
currencyCode,

View File

@@ -0,0 +1,10 @@
import { cache } from "react"
import { serverClient } from "../../trpc"
export const getBookingConfirmation = cache(
async function getMemoizedBookingConfirmation(refId: string) {
const caller = await serverClient()
return caller.booking.get({ refId })
}
)

View File

@@ -1,4 +1,4 @@
export interface PromoProps {
export type PromoProps = {
buttonText: string
href: string
text: string

View File

@@ -37,6 +37,7 @@
"./components/SelectHotelMap": "./lib/components/SelectHotel/SelectHotelMap/index.tsx",
"./components/SelectRate": "./lib/components/SelectRate/index.tsx",
"./components/SelectRate/RoomsContainer/RateSummary/utils": "./lib/components/SelectRate/RoomsContainer/RateSummary/utils.ts",
"./components/SidePanel": "./lib/components/SidePanel/index.tsx",
"./components/SidePeekAccordions/BreakfastAccordionItem": "./lib/components/SidePeekAccordions/BreakfastAccordionItem.tsx",
"./components/SidePeekAccordions/CheckInCheckOutAccordionItem": "./lib/components/SidePeekAccordions/CheckInCheckOutAccordionItem.tsx",
"./components/SidePeekAccordions/ParkingAccordionItem": "./lib/components/SidePeekAccordions/ParkingAccordionItem.tsx",
@@ -54,11 +55,13 @@
"./stores/bookingCode-filter": "./lib/stores/bookingCode-filter.ts",
"./stores/hotels-map": "./lib/stores/hotels-map.ts",
"./stores/booking-confirmation": "./lib/stores/booking-confirmation/index.ts",
"./types/components/selectRate/selectRate": "./lib/types/components/selectRate/selectRate.ts",
"./types/components/bookingConfirmation/bookingConfirmation": "./lib/types/components/bookingConfirmation/bookingConfirmation.ts",
"./types/components/findMyBooking/additionalInfoCookieValue": "./lib/types/components/findMyBooking/additionalInfoCookieValue.ts",
"./types/components/promo/promoProps": "./lib/types/components/promo/promoProps.ts",
"./types/components/selectRate/selectRate": "./lib/types/components/selectRate/selectRate.ts",
"./types/stores/rates": "./lib/types/stores/rates.ts",
"./types/stores/booking-confirmation": "./lib/types/stores/booking-confirmation.ts",
"./utils/getRoomFeatureDescription": "./lib/utils/getRoomFeatureDescription.ts",
"./utils/isSameBooking": "./lib/utils/isSameBooking.ts",
"./utils/url": "./lib/utils/url.ts",
"./utils/SelectRate": "./lib/utils/SelectRate/index.tsx",

View File

@@ -12,6 +12,7 @@
},
"exports": {
"./constants/alert": "./constants/alert.ts",
"./constants/booking": "./constants/booking.ts",
"./constants/currency": "./constants/currency.ts",
"./constants/dateFormats": "./constants/dateFormats.ts",
"./constants/facilities": "./constants/facilities.ts",