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:
@@ -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}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
.contact,
|
||||
.container,
|
||||
.details,
|
||||
.hotel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
gap: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.details {
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.contact,
|
||||
.hotel {
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.list {
|
||||
padding-left: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.link {
|
||||
word-break: break-all;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
.details,
|
||||
.payment {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.details {
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.payment {
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.details button.btn {
|
||||
align-self: flex-start;
|
||||
margin-top: var(--Spacing-x-half);
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
"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 { mapToPrice } from "./mapToPrice"
|
||||
|
||||
import type { Price } from "@/types/components/hotelReservation/price"
|
||||
|
||||
export default function PriceDetails() {
|
||||
const { bookingCode, currency, fromDate, rooms, vat, toDate } =
|
||||
useBookingConfirmationStore((state) => ({
|
||||
bookingCode: state.bookingCode ?? undefined,
|
||||
currency: state.currencyCode,
|
||||
fromDate: state.fromDate,
|
||||
rooms: state.rooms,
|
||||
toDate: state.toDate,
|
||||
vat: state.vat,
|
||||
}))
|
||||
|
||||
if (!rooms[0]) {
|
||||
return null
|
||||
}
|
||||
|
||||
const checkInDate = dt(fromDate).format("YYYY-MM-DD")
|
||||
const checkOutDate = dt(toDate).format("YYYY-MM-DD")
|
||||
const nights = dt(toDate)
|
||||
.startOf("day")
|
||||
.diff(dt(fromDate).startOf("day"), "days")
|
||||
|
||||
const totalPrice = rooms.reduce<Price>(
|
||||
(total, room) => {
|
||||
if (!room) {
|
||||
return total
|
||||
}
|
||||
|
||||
if (room.cheques) {
|
||||
// CorporateCheque Booking
|
||||
total.local.currency = CurrencyEnum.CC
|
||||
total.local.price = total.local.price + room.cheques
|
||||
} else if (room.roomPoints) {
|
||||
// Redemption Booking
|
||||
total.local.currency = CurrencyEnum.POINTS
|
||||
total.local.price = total.local.price + room.roomPoints
|
||||
} else if (room.vouchers) {
|
||||
// Vouchers Booking
|
||||
total.local.currency = CurrencyEnum.Voucher
|
||||
total.local.price = total.local.price + room.vouchers
|
||||
} else {
|
||||
// Price Booking
|
||||
total.local.price = total.local.price + room.totalPrice
|
||||
}
|
||||
|
||||
// Corporate Cheque
|
||||
if (room.cheques) {
|
||||
if (room.roomPrice) {
|
||||
total.local.additionalPrice =
|
||||
(total.local.additionalPrice || 0) + room.totalPrice
|
||||
total.local.additionalPriceCurrency = currency
|
||||
} else {
|
||||
const pkgsSum = room.packages.reduce(
|
||||
(total, pkg) => total + pkg.totalPrice,
|
||||
0
|
||||
)
|
||||
total.local.additionalPrice =
|
||||
(total.local.additionalPrice || 0) + pkgsSum
|
||||
const pkgsCurrency = room.packages.find(
|
||||
(pkg) => pkg.currency
|
||||
)?.currency
|
||||
if (!total.local.additionalPriceCurrency) {
|
||||
total.local.additionalPriceCurrency = pkgsCurrency ?? currency
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Redemption
|
||||
if (room.roomPoints) {
|
||||
if (room.roomPrice) {
|
||||
total.local.additionalPrice =
|
||||
(total.local.additionalPrice || 0) + room.totalPrice
|
||||
total.local.additionalPriceCurrency = currency
|
||||
} else {
|
||||
const pkgsSum = room.packages.reduce(
|
||||
(total, pkg) => total + pkg.totalPrice,
|
||||
0
|
||||
)
|
||||
total.local.additionalPrice =
|
||||
(total.local.additionalPrice || 0) + pkgsSum
|
||||
const pkgsCurrency = room.packages.find(
|
||||
(pkg) => pkg.currency
|
||||
)?.currency
|
||||
if (!total.local.additionalPriceCurrency) {
|
||||
total.local.additionalPriceCurrency = pkgsCurrency ?? currency
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Voucher
|
||||
if (room.vouchers && room.packages) {
|
||||
const pkgsSum = room.packages.reduce(
|
||||
(total, pkg) => total + pkg.totalPrice,
|
||||
0
|
||||
)
|
||||
total.local.additionalPrice =
|
||||
(total.local.additionalPrice || 0) + pkgsSum
|
||||
const pkgsCurrency = room.packages.find((pkg) => pkg.currency)?.currency
|
||||
if (!total.local.additionalPriceCurrency) {
|
||||
total.local.additionalPriceCurrency = pkgsCurrency ?? currency
|
||||
}
|
||||
}
|
||||
|
||||
return total
|
||||
},
|
||||
{
|
||||
local: {
|
||||
currency,
|
||||
price: 0,
|
||||
},
|
||||
requested: undefined,
|
||||
}
|
||||
)
|
||||
|
||||
const mappedRooms = mapToPrice(rooms, nights)
|
||||
|
||||
return (
|
||||
<PriceDetailsModal
|
||||
bookingCode={bookingCode}
|
||||
fromDate={checkInDate}
|
||||
rooms={mappedRooms}
|
||||
toDate={checkOutDate}
|
||||
totalPrice={totalPrice}
|
||||
vat={vat}
|
||||
defaultCurrency={currency}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
|
||||
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
|
||||
import { PackageTypeEnum } from "@scandic-hotels/trpc/enums/packages"
|
||||
import {
|
||||
type BreakfastPackage,
|
||||
breakfastPackageSchema,
|
||||
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"
|
||||
|
||||
export function mapToPrice(rooms: (Room | null)[], nights: number) {
|
||||
return rooms
|
||||
.filter((room): room is Room => !!room)
|
||||
.map((room) => {
|
||||
let price
|
||||
if (room.cheques) {
|
||||
price = {
|
||||
corporateCheque: {
|
||||
additionalPricePerStay: room.roomPrice ? room.roomPrice : undefined,
|
||||
currency: room.roomPrice ? room.currencyCode : undefined,
|
||||
numberOfCheques: room.cheques,
|
||||
},
|
||||
}
|
||||
} else if (room.roomPoints) {
|
||||
price = {
|
||||
redemption: {
|
||||
additionalPricePerStay: room.roomPrice ? room.roomPrice : undefined,
|
||||
currency: room.roomPrice ? room.currencyCode : undefined,
|
||||
pointsPerNight: room.roomPoints / nights,
|
||||
pointsPerStay: room.roomPoints,
|
||||
},
|
||||
}
|
||||
} else if (room.vouchers) {
|
||||
price = {
|
||||
voucher: {
|
||||
numberOfVouchers: room.vouchers,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
price = {
|
||||
regular: {
|
||||
currency: room.currencyCode,
|
||||
pricePerNight: room.roomPrice / nights,
|
||||
pricePerStay: room.roomPrice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let breakfast:
|
||||
| false
|
||||
| undefined
|
||||
| Omit<BreakfastPackage, "requestedPrice">
|
||||
if (room.breakfast) {
|
||||
const breakfastPackage = breakfastPackageSchema.safeParse({
|
||||
code: room.breakfast?.code,
|
||||
description: room.breakfast?.description,
|
||||
localPrice: {
|
||||
currency: room.breakfast?.currency,
|
||||
price: room.breakfast?.unitPrice,
|
||||
totalPrice: room.breakfast?.totalPrice,
|
||||
},
|
||||
packageType:
|
||||
room.breakfast?.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
||||
? PackageTypeEnum.BreakfastAdult
|
||||
: "",
|
||||
requestedPrice: {
|
||||
currency: room.breakfast?.currency,
|
||||
price: room.breakfast?.unitPrice,
|
||||
totalPrice: room.breakfast?.totalPrice,
|
||||
},
|
||||
})
|
||||
if (breakfastPackage.success) {
|
||||
breakfast = breakfastPackage.data
|
||||
}
|
||||
}
|
||||
|
||||
if (!room.breakfastIncluded && !breakfast) {
|
||||
breakfast = false
|
||||
}
|
||||
|
||||
const packages = room.roomFeatures
|
||||
?.map((featPkg) => {
|
||||
const pkg = packageSchema.safeParse({
|
||||
code: featPkg.code,
|
||||
description: featPkg.description,
|
||||
inventories: [],
|
||||
localPrice: {
|
||||
currency: featPkg.currency,
|
||||
price: featPkg.unitPrice,
|
||||
totalPrice: featPkg.totalPrice,
|
||||
},
|
||||
requestedPrice: {
|
||||
currency: featPkg.currency,
|
||||
price: featPkg.unitPrice,
|
||||
totalPrice: featPkg.totalPrice,
|
||||
},
|
||||
})
|
||||
if (pkg.success) {
|
||||
return pkg.data
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter((pkg): pkg is Package => !!pkg)
|
||||
|
||||
return {
|
||||
...room,
|
||||
adults: room.adults,
|
||||
bedType: {
|
||||
description: room.bedDescription,
|
||||
roomTypeCode: room.roomTypeCode || "",
|
||||
type: room.bedType,
|
||||
},
|
||||
breakfast,
|
||||
breakfastIncluded: room.rateDefinition.breakfastIncluded,
|
||||
childrenInRoom: room.childrenAges?.map((age) => ({
|
||||
age,
|
||||
bed: ChildBedMapEnum.UNKNOWN,
|
||||
})),
|
||||
packages,
|
||||
price,
|
||||
roomType: room.name,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
.promo {
|
||||
align-items: center;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: var(--Medium, 8px);
|
||||
display: flex;
|
||||
flex: 1 0 320px;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
height: 320px;
|
||||
justify-content: center;
|
||||
padding: var(--Spacing-x4) var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.link:nth-of-type(1) .promo {
|
||||
background-image:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0.36) 37.88%,
|
||||
rgba(0, 0, 0, 0.75) 100%
|
||||
),
|
||||
url("/_static/img/Scandic_Park_Party_Lipstick.jpg");
|
||||
}
|
||||
|
||||
.link:nth-of-type(2) .promo {
|
||||
background-image:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0.36) 37.88%,
|
||||
rgba(0, 0, 0, 0.75) 100%
|
||||
),
|
||||
url("/_static/img/Scandic_Family_Breakfast.jpg");
|
||||
}
|
||||
|
||||
.text {
|
||||
max-width: 400px;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
"use client"
|
||||
import { useEffect } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
||||
|
||||
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 { PromosProps } from "@/types/components/hotelReservation/bookingConfirmation/promos"
|
||||
|
||||
export default function Promos({ booking }: PromosProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const { refId, confirmationNumber, hotelId } = booking
|
||||
const { email, firstName, lastName } = booking.guest
|
||||
useEffect(() => {
|
||||
// Setting the `bv` cookie allows direct access to My stay without prompting for more information.
|
||||
const value: AdditionalInfoCookieValue = {
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
confirmationNumber,
|
||||
}
|
||||
document.cookie = `bv=${JSON.stringify(value)}; Path=/; Max-Age=600; Secure; SameSite=Strict`
|
||||
}, [confirmationNumber, email, firstName, lastName])
|
||||
|
||||
const myStayURL = `${myStay[lang]}?RefId=${encodeURIComponent(refId)}`
|
||||
return (
|
||||
<div className={styles.promos}>
|
||||
<Promo
|
||||
buttonText={intl.formatMessage({
|
||||
defaultMessage: "View and buy add-ons",
|
||||
})}
|
||||
href={myStayURL}
|
||||
text={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Discover the little extra touches to make your upcoming stay even more unforgettable.",
|
||||
})}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Spice things up",
|
||||
})}
|
||||
/>
|
||||
<Promo
|
||||
buttonText={intl.formatMessage({
|
||||
defaultMessage: "Book another stay",
|
||||
})}
|
||||
href={`/${lang}?hotel=${hotelId}`}
|
||||
text={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.",
|
||||
})}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Book your next stay",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
.promos {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x5) 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.promos {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
.entry {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.textDefault {
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.textSecondary {
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import styles from "./breakfast.module.css"
|
||||
|
||||
import type { PackageSchema } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
||||
|
||||
interface BreakfastProps {
|
||||
breakfast: PackageSchema | false | undefined
|
||||
breakfastIncluded: boolean
|
||||
guests: string
|
||||
}
|
||||
|
||||
export default function Breakfast({
|
||||
breakfast,
|
||||
breakfastIncluded,
|
||||
guests,
|
||||
}: BreakfastProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
const breakfastBuffet = intl.formatMessage({
|
||||
defaultMessage: "Breakfast buffet",
|
||||
})
|
||||
|
||||
if (breakfastIncluded || breakfast) {
|
||||
const price = breakfast
|
||||
? formatPrice(intl, breakfast.totalPrice, breakfast.currency)
|
||||
: intl.formatMessage({ defaultMessage: "Included" })
|
||||
return (
|
||||
<div className={styles.entry}>
|
||||
<div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.textDefault}>{breakfastBuffet}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p className={styles.textSecondary}>{guests}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.textDefault}>{price}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (breakfast === false) {
|
||||
const noBreakfast = intl.formatMessage({ defaultMessage: "No breakfast" })
|
||||
return (
|
||||
<div className={styles.entry}>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.textDefault}>{breakfastBuffet}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.textDefault}>{noBreakfast}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
||||
|
||||
import styles from "./roomSkeletonLoader.module.css"
|
||||
|
||||
export default function RoomSkeletonLoader() {
|
||||
return (
|
||||
<div className={styles.room}>
|
||||
<SkeletonShimmer />
|
||||
<SkeletonShimmer width={"15%"} />
|
||||
<SkeletonShimmer width={"30%"} />
|
||||
<SkeletonShimmer width={"40%"} />
|
||||
<SkeletonShimmer />
|
||||
<SkeletonShimmer />
|
||||
<SkeletonShimmer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { cx } from "class-variance-authority"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { useBookingConfirmationStore } from "@scandic-hotels/booking-flow/stores/booking-confirmation"
|
||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||
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 { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum"
|
||||
|
||||
import { CancellationRuleEnum } from "@/constants/booking"
|
||||
|
||||
import { getFeatureDescription } from "@/components/HotelReservation/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"
|
||||
|
||||
export default function ReceiptRoom({
|
||||
room,
|
||||
roomNumber,
|
||||
roomCount,
|
||||
}: BookingConfirmationReceiptRoomProps) {
|
||||
const intl = useIntl()
|
||||
const { currencyCode, isVatCurrency } = useBookingConfirmationStore(
|
||||
(state) => ({
|
||||
currencyCode: state.currencyCode,
|
||||
isVatCurrency: state.isVatCurrency,
|
||||
})
|
||||
)
|
||||
|
||||
if (!room) {
|
||||
return <RoomSkeletonLoader />
|
||||
}
|
||||
|
||||
const childBedCrib = room.childBedPreferences.find(
|
||||
(c) => c.bedType === ChildBedTypeEnum.Crib
|
||||
)
|
||||
|
||||
const childBedExtraBed = room.childBedPreferences.find(
|
||||
(c) => c.bedType === ChildBedTypeEnum.ExtraBed
|
||||
)
|
||||
|
||||
const adultsMsg = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
|
||||
},
|
||||
{ totalAdults: room.adults }
|
||||
)
|
||||
|
||||
const guestsParts = [adultsMsg]
|
||||
if (room.childrenAges?.length) {
|
||||
const childrenMsg = intl.formatMessage(
|
||||
{
|
||||
defaultMessage:
|
||||
"{totalChildren, plural, one {# child} other {# children}}",
|
||||
},
|
||||
{ totalChildren: room.childrenAges.length }
|
||||
)
|
||||
guestsParts.push(childrenMsg)
|
||||
}
|
||||
|
||||
const guests = guestsParts.join(", ")
|
||||
const showDiscounted = room.rateDefinition.isMemberRate
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.room}>
|
||||
<div>
|
||||
{roomCount > 1 ? (
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<p className={styles.roomTitle}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Room {roomIndex}",
|
||||
},
|
||||
{
|
||||
roomIndex: roomNumber,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</Typography>
|
||||
) : null}
|
||||
<div className={styles.entry}>
|
||||
<div>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p>{room.name}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div className={styles.additionalInformation}>
|
||||
<p>{guestsParts.join(", ")}</p>
|
||||
<p>{room.rateDefinition.cancellationText}</p>
|
||||
</div>
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<div className={styles.prices}>
|
||||
<p
|
||||
className={cx(styles.price, {
|
||||
[styles.discounted]: showDiscounted,
|
||||
})}
|
||||
>
|
||||
{room.formattedRoomCost}
|
||||
</p>
|
||||
{/* TODO: add original price, we're currently not receiving this value from API */}
|
||||
</div>
|
||||
</Typography>
|
||||
</div>
|
||||
{room.rateDefinition.generalTerms ? (
|
||||
<div className={styles.ctaWrapper}>
|
||||
<Modal
|
||||
trigger={
|
||||
<Button
|
||||
className={styles.termsButton}
|
||||
variant="Text"
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
wrapping={false}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Reservation policy",
|
||||
})}
|
||||
<MaterialIcon
|
||||
icon="chevron_right"
|
||||
size={20}
|
||||
color="CurrentColor"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
title={
|
||||
(isVatCurrency
|
||||
? room.rateDefinition.cancellationText
|
||||
: room.rateDefinition.title) || ""
|
||||
}
|
||||
subtitle={
|
||||
room.rateDefinition.cancellationRule ===
|
||||
CancellationRuleEnum.CancellableBefore6PM
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Pay later",
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Pay now",
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className={styles.terms}>
|
||||
{room.rateDefinition.generalTerms?.map((info) => (
|
||||
<Typography
|
||||
key={info}
|
||||
className={styles.termsText}
|
||||
variant="Body/Paragraph/mdRegular"
|
||||
>
|
||||
<span>
|
||||
<MaterialIcon
|
||||
icon="check"
|
||||
color="Icon/Feedback/Success"
|
||||
size={20}
|
||||
className={styles.termsIcon}
|
||||
/>
|
||||
{info}
|
||||
</span>
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{room.roomFeatures
|
||||
? room.roomFeatures.map((feature) => (
|
||||
<div className={styles.entry} key={feature.code}>
|
||||
<div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{getFeatureDescription(
|
||||
feature.code,
|
||||
feature.description,
|
||||
intl
|
||||
)}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(intl, feature.totalPrice, feature.currency)}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
<div className={styles.entry}>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>{room.bedDescription}</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{formatPrice(intl, 0, currencyCode)}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
{childBedCrib ? (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<div className={styles.entry}>
|
||||
<div>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Crib (child) × {count}",
|
||||
},
|
||||
{ count: childBedCrib.quantity }
|
||||
)}
|
||||
</p>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p className={styles.uiTextHighContrast}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Subject to availability",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.prices}>
|
||||
<span className={styles.price}>
|
||||
{formatPrice(intl, 0, currencyCode)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Typography>
|
||||
) : null}
|
||||
{childBedExtraBed ? (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<div className={styles.entry}>
|
||||
<div>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Extra bed (child) × {count}",
|
||||
},
|
||||
{
|
||||
count: childBedExtraBed.quantity,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.prices}>
|
||||
<span className={styles.price}>
|
||||
{formatPrice(intl, 0, currencyCode)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Typography>
|
||||
) : null}
|
||||
<Breakfast
|
||||
breakfast={room.breakfast}
|
||||
breakfastIncluded={room.breakfastIncluded}
|
||||
guests={guests}
|
||||
/>
|
||||
</div>
|
||||
<Divider color="Border/Divider/Subtle" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
.room {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x15);
|
||||
overflow-y: auto;
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.roomTitle,
|
||||
.additionalInformation {
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.terms {
|
||||
margin-top: var(--Space-x3);
|
||||
margin-bottom: var(--Space-x3);
|
||||
}
|
||||
.termsText:nth-child(n) {
|
||||
display: flex;
|
||||
margin-bottom: var(--Space-x1);
|
||||
}
|
||||
|
||||
.terms .termsIcon {
|
||||
margin-right: var(--Space-x1);
|
||||
}
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-half);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.prices {
|
||||
justify-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: var(--Text-Default);
|
||||
|
||||
&.discounted {
|
||||
color: var(--Text-Accent-Primary);
|
||||
}
|
||||
}
|
||||
|
||||
.strikeThroughRate {
|
||||
text-decoration: line-through;
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.ctaWrapper {
|
||||
margin-top: var(--Space-x15);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.room {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x1);
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
"use client"
|
||||
|
||||
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 PriceDetails from "../../PriceDetails"
|
||||
|
||||
import styles from "./totalPrice.module.css"
|
||||
|
||||
export default function TotalPrice() {
|
||||
const intl = useIntl()
|
||||
const { rooms, formattedTotalCost } = useBookingConfirmationStore(
|
||||
(state) => ({
|
||||
bookingCode: state.bookingCode,
|
||||
rooms: state.rooms,
|
||||
formattedTotalCost: state.formattedTotalCost,
|
||||
})
|
||||
)
|
||||
|
||||
const hasAllRoomsLoaded = rooms.every((room) => room)
|
||||
const bookingCode = rooms.find((room) => room?.bookingCode)?.bookingCode
|
||||
const isMemberRate = rooms.some((room) => room?.rateDefinition.isMemberRate)
|
||||
const showDiscounted = bookingCode || isMemberRate
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider color="Border/Divider/Subtle" />
|
||||
<div className={styles.price}>
|
||||
<div className={styles.entry}>
|
||||
<div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "<b>Total price</b> (incl VAT)",
|
||||
},
|
||||
{
|
||||
b: (str) => (
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span>{str}</span>
|
||||
</Typography>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</Typography>
|
||||
{/* TODO: Add approx price, we're currently not receiving this value from API */}
|
||||
</div>
|
||||
<div className={styles.prices}>
|
||||
{hasAllRoomsLoaded ? (
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span
|
||||
className={cx(styles.price, {
|
||||
[styles.discounted]: showDiscounted,
|
||||
})}
|
||||
>
|
||||
{formattedTotalCost}
|
||||
</span>
|
||||
</Typography>
|
||||
) : (
|
||||
<SkeletonShimmer width={"25%"} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.ctaWrapper}>
|
||||
{hasAllRoomsLoaded ? (
|
||||
<PriceDetails />
|
||||
) : (
|
||||
<SkeletonShimmer width={"100%"} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{bookingCode && <BookingCodeChip bookingCode={bookingCode} alignCenter />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
.entry {
|
||||
display: flex;
|
||||
gap: var(--Space-x05);
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--Space-x15);
|
||||
}
|
||||
|
||||
.prices {
|
||||
justify-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: var(--Text-Default);
|
||||
|
||||
&.discounted {
|
||||
color: var(--Text-Accent-Primary);
|
||||
}
|
||||
}
|
||||
|
||||
.strikeThroughRate {
|
||||
text-decoration: line-through;
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.approxPrice {
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.ctaWrapper {
|
||||
margin-top: var(--Space-x15);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
"use client"
|
||||
|
||||
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 TotalPrice from "./TotalPrice"
|
||||
|
||||
import styles from "./receipt.module.css"
|
||||
|
||||
export default function Receipt() {
|
||||
const lang = useLang()
|
||||
const intl = useIntl()
|
||||
const { rooms, fromDate, toDate } = useBookingConfirmationStore((state) => ({
|
||||
rooms: state.rooms,
|
||||
fromDate: state.fromDate,
|
||||
toDate: state.toDate,
|
||||
}))
|
||||
|
||||
const totalNights = dt(toDate).diff(fromDate, "days")
|
||||
|
||||
const nights = intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
|
||||
},
|
||||
{ totalNights }
|
||||
)
|
||||
|
||||
const filteredRooms = rooms.filter(
|
||||
(room): room is NonNullable<typeof room> => !!room
|
||||
)
|
||||
|
||||
return (
|
||||
<section className={styles.receipt}>
|
||||
<header>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h3 className={styles.heading}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Booking summary",
|
||||
})}
|
||||
</h3>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p className={styles.dates}>
|
||||
{dt(fromDate).locale(lang).format(longDateFormat[lang])}
|
||||
<MaterialIcon icon="arrow_forward" size={15} color="CurrentColor" />
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
{dt(toDate).locale(lang).format(longDateFormat[lang])} ({nights})
|
||||
</p>
|
||||
</Typography>
|
||||
</header>
|
||||
|
||||
<Divider color="Border/Divider/Subtle" />
|
||||
|
||||
{filteredRooms.map((room, idx) => (
|
||||
<Room
|
||||
key={room ? room.confirmationNumber : `loader-${idx}`}
|
||||
room={room}
|
||||
roomNumber={idx + 1}
|
||||
roomCount={rooms.length}
|
||||
/>
|
||||
))}
|
||||
|
||||
<TotalPrice />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
.receipt {
|
||||
display: grid;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.dates {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Space-x1);
|
||||
justify-content: flex-start;
|
||||
color: var(--Text-Accent-Secondary);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.receipt {
|
||||
padding: var(--Space-x3);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
||||
|
||||
import styles from "./linkedReservationCardSkeleton.module.css"
|
||||
|
||||
export function LinkedReservationCardSkeleton() {
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.img}>
|
||||
<SkeletonShimmer height={"204px"} width={"100%"} />
|
||||
</div>
|
||||
<div className={styles.roomDetails}>
|
||||
<div className={styles.roomName}>
|
||||
<SkeletonShimmer height={"24px"} width={"130px"} />
|
||||
<SkeletonShimmer height={"20px"} width={"140px"} />
|
||||
</div>
|
||||
<div className={styles.details}>
|
||||
<SkeletonShimmer height={"20px"} width={"300px"} />
|
||||
<SkeletonShimmer height={"20px"} width={"300px"} />
|
||||
<SkeletonShimmer height={"20px"} width={"300px"} />
|
||||
<SkeletonShimmer height={"20px"} width={"300px"} />
|
||||
</div>
|
||||
<div className={styles.guest}>
|
||||
<SkeletonShimmer height={"20px"} width={"100px"} />
|
||||
<SkeletonShimmer height={"20px"} width={"200px"} />
|
||||
<SkeletonShimmer height={"20px"} width={"150px"} />
|
||||
<SkeletonShimmer height={"20px"} width={"300px"} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client"
|
||||
|
||||
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 { mapRoomState } from "../../utils"
|
||||
import Room from "../Room"
|
||||
import { LinkedReservationCardSkeleton } from "./LinkedReservationCardSkeleton"
|
||||
import Retry from "./Retry"
|
||||
|
||||
import type { LinkedReservationProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/linkedReservation"
|
||||
|
||||
export function LinkedReservation({
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
refId,
|
||||
roomIndex,
|
||||
}: LinkedReservationProps) {
|
||||
const lang = useLang()
|
||||
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
|
||||
refId,
|
||||
lang,
|
||||
})
|
||||
const {
|
||||
setRoom,
|
||||
setFormattedTotalCost,
|
||||
currencyCode,
|
||||
totalBookingPrice,
|
||||
totalBookingCheques,
|
||||
} = useBookingConfirmationStore((state) => ({
|
||||
setRoom: state.actions.setRoom,
|
||||
setFormattedTotalCost: state.actions.setFormattedTotalCost,
|
||||
currencyCode: state.currencyCode,
|
||||
totalBookingPrice: state.totalBookingPrice,
|
||||
totalBookingCheques: state.totalBookingCheques,
|
||||
}))
|
||||
const intl = useIntl()
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.room) {
|
||||
const roomData = mapRoomState(data.booking, data.room, intl)
|
||||
setRoom(roomData, roomIndex)
|
||||
|
||||
const formattedTotalCost = totalBookingCheques
|
||||
? formatPrice(
|
||||
intl,
|
||||
totalBookingCheques,
|
||||
CurrencyEnum.CC,
|
||||
totalBookingPrice,
|
||||
currencyCode
|
||||
)
|
||||
: formatPrice(intl, totalBookingPrice, currencyCode)
|
||||
|
||||
setFormattedTotalCost(formattedTotalCost)
|
||||
}
|
||||
}, [
|
||||
data,
|
||||
roomIndex,
|
||||
intl,
|
||||
setRoom,
|
||||
totalBookingCheques,
|
||||
totalBookingPrice,
|
||||
currencyCode,
|
||||
setFormattedTotalCost,
|
||||
])
|
||||
|
||||
if (isLoading) {
|
||||
return <LinkedReservationCardSkeleton />
|
||||
}
|
||||
|
||||
if (!data?.room) {
|
||||
return <Retry handleRefetch={refetch} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Room
|
||||
booking={data.booking}
|
||||
checkInTime={checkInTime}
|
||||
checkOutTime={checkOutTime}
|
||||
img={data.room.images[0]}
|
||||
roomName={data.room.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: var(--Background-Primary);
|
||||
border-radius: var(--Corner-radius-lg);
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x3)
|
||||
var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.img {
|
||||
border-radius: var(--Corner-radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.roomDetails {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.roomName {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
grid-column: 1/-1;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.guest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.content {
|
||||
gap: var(--Spacing-x3);
|
||||
grid-template-columns: auto 1fr;
|
||||
padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x2)
|
||||
var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.img {
|
||||
min-width: 306px;
|
||||
}
|
||||
.roomDetails {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.guest {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.retry {
|
||||
background-color: var(--Background-Primary);
|
||||
border-radius: var(--Corner-radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x3)
|
||||
var(--Spacing-x2);
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
"use client"
|
||||
|
||||
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 {
|
||||
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 styles from "./room.module.css"
|
||||
|
||||
import type { RoomProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/room"
|
||||
|
||||
export default function Room({
|
||||
booking,
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
img,
|
||||
roomName,
|
||||
}: RoomProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const { roomCategories } = useBookingConfirmationStore((state) => ({
|
||||
roomCategories: state.roomCategories,
|
||||
}))
|
||||
const room = getHotelRoom(roomCategories, booking.roomTypeCode)
|
||||
|
||||
const guestName = `${booking.guest.firstName} ${booking.guest.lastName}`
|
||||
const fromDate = dt(booking.checkInDate).locale(lang)
|
||||
const toDate = dt(booking.checkOutDate).locale(lang)
|
||||
|
||||
const isFlexBooking =
|
||||
booking.rateDefinition.cancellationRule ===
|
||||
CancellationRuleEnum.CancellableBefore6PM
|
||||
const isChangeBooking =
|
||||
booking.rateDefinition.cancellationRule === CancellationRuleEnum.Changeable
|
||||
return (
|
||||
<article className={styles.room}>
|
||||
<header className={styles.header}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h2>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Booking number {value}",
|
||||
},
|
||||
{ value: booking.confirmationNumber }
|
||||
)}
|
||||
</h2>
|
||||
</Typography>
|
||||
{booking.rateDefinition.isMemberRate ? (
|
||||
<div className={styles.benefits}>
|
||||
<>
|
||||
<MaterialIcon
|
||||
color="Icon/Feedback/Success"
|
||||
icon="check_circle"
|
||||
size={20}
|
||||
/>
|
||||
<Caption>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Membership benefits applied",
|
||||
})}
|
||||
</Caption>
|
||||
</>
|
||||
</div>
|
||||
) : null}
|
||||
{booking.guaranteeInfo && (
|
||||
<div className={styles.benefits}>
|
||||
<MaterialIcon
|
||||
icon="check_circle"
|
||||
color="Icon/Feedback/Success"
|
||||
size={20}
|
||||
/>
|
||||
<Typography
|
||||
variant="Body/Supporting text (caption)/smBold"
|
||||
className={styles.guaranteeText}
|
||||
>
|
||||
<p>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Booking guaranteed.",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
{/* eslint-disable formatjs/no-literal-string-in-jsx */}{" "}
|
||||
{/* eslint-enable formatjs/no-literal-string-in-jsx */}
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p>
|
||||
{intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Your room will remain available for check-in even after 18:00.",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
<div className={styles.booking}>
|
||||
<Image
|
||||
alt={img?.metaData.altText ?? ""}
|
||||
className={styles.img}
|
||||
focalPoint={{ x: 50, y: 50 }}
|
||||
height={204}
|
||||
src={img?.imageSizes.medium ?? ""}
|
||||
style={{ borderRadius: "var(--Corner-radius-md)" }}
|
||||
title={img?.metaData.title || img?.metaData.title_En || ""}
|
||||
width={204}
|
||||
/>
|
||||
<div className={styles.roomDetails}>
|
||||
<div className={styles.roomName}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h2>{roomName}</h2>
|
||||
</Typography>
|
||||
{room && (
|
||||
<RoomDetailsSidePeek
|
||||
hotelId={booking.hotelId}
|
||||
room={room}
|
||||
roomTypeCode={booking.roomTypeCode}
|
||||
buttonVariant="primary"
|
||||
triggerLabel={intl.formatMessage({
|
||||
defaultMessage: "View room details",
|
||||
})}
|
||||
wrapping={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<ul className={styles.details}>
|
||||
<li className={styles.listItem}>
|
||||
<p className={styles.label}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Check-in",
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{checkInDate} from {checkInTime}",
|
||||
},
|
||||
{
|
||||
checkInDate: fromDate.format(longDateFormat[lang]),
|
||||
checkInTime: checkInTime,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</li>
|
||||
<li className={styles.listItem}>
|
||||
<p className={styles.label}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Check-out",
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
{`${toDate.format(longDateFormat[lang])}, ${checkOutTime}`}
|
||||
</p>
|
||||
</li>
|
||||
<li className={styles.listItem}>
|
||||
<p className={styles.label}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Cancellation policy",
|
||||
})}
|
||||
</p>
|
||||
<p>{booking.rateDefinition.cancellationText}</p>
|
||||
</li>
|
||||
{isFlexBooking || isChangeBooking ? (
|
||||
<li className={styles.listItem}>
|
||||
<p className={styles.label}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Change or cancel",
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Until {time}, {date}",
|
||||
},
|
||||
{
|
||||
time: "18:00",
|
||||
date: fromDate.format(changeOrCancelDateFormat[lang]),
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</Typography>
|
||||
<div className={styles.guest}>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.label}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Guest",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p data-hj-suppress>{guestName}</p>
|
||||
</Typography>
|
||||
{booking.guest.membershipNumber ? (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p data-hj-suppress>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Friend no. {value}",
|
||||
},
|
||||
{
|
||||
value: booking.guest.membershipNumber,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</Typography>
|
||||
) : null}
|
||||
{booking.guest.phoneNumber ? (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p data-hj-suppress>{booking.guest.phoneNumber}</p>
|
||||
</Typography>
|
||||
) : null}
|
||||
{booking.guest.email ? (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p data-hj-suppress>{booking.guest.email}</p>
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
.room {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.benefits {
|
||||
align-items: center;
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-md);
|
||||
display: flex;
|
||||
gap: var(--Spacing-x1);
|
||||
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
|
||||
width: min(max-content, 100%);
|
||||
}
|
||||
|
||||
.guaranteeText {
|
||||
color: var(--Text-Feedback-Succes-Accent);
|
||||
}
|
||||
|
||||
.booking {
|
||||
background-color: var(--Background-Primary);
|
||||
border-radius: var(--Corner-radius-lg);
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x3)
|
||||
var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.roomDetails {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.roomName {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--Spacing-x-half);
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x-half) var(--Spacing-x3);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
justify-content: space-between;
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
|
||||
.guest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--Text-Tertiary);
|
||||
}
|
||||
|
||||
.details p:nth-of-type(even) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1366px) {
|
||||
.details {
|
||||
padding-bottom: var(--Space-x1);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.header {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.details p:nth-of-type(even) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.img {
|
||||
width: 204px;
|
||||
}
|
||||
|
||||
.booking {
|
||||
gap: var(--Spacing-x3);
|
||||
grid-template-columns: auto 1fr;
|
||||
padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x2)
|
||||
var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.roomDetails {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.guest {
|
||||
align-items: flex-end;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import { LinkedReservation } from "./LinkedReservation"
|
||||
import Room from "./Room"
|
||||
|
||||
import styles from "./rooms.module.css"
|
||||
|
||||
import type { BookingConfirmationRoomsProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms"
|
||||
|
||||
export default async function Rooms({
|
||||
booking,
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
mainRoom,
|
||||
}: BookingConfirmationRoomsProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<section className={styles.rooms}>
|
||||
<div className={styles.room}>
|
||||
{booking.linkedReservations.length ? (
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h2 className={styles.roomTitle}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Room {roomIndex}",
|
||||
},
|
||||
{ roomIndex: 1 }
|
||||
)}
|
||||
</h2>
|
||||
</Typography>
|
||||
) : null}
|
||||
<Room
|
||||
booking={booking}
|
||||
checkInTime={checkInTime}
|
||||
checkOutTime={checkOutTime}
|
||||
img={mainRoom.images[0]}
|
||||
roomName={mainRoom.name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{booking.linkedReservations.map((reservation, idx) => (
|
||||
<div className={styles.room} key={reservation.confirmationNumber}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h2 className={styles.roomTitle}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Room {roomIndex}",
|
||||
},
|
||||
{ roomIndex: idx + 2 }
|
||||
)}
|
||||
</h2>
|
||||
</Typography>
|
||||
<LinkedReservation
|
||||
checkInTime={checkInTime}
|
||||
checkOutTime={checkOutTime}
|
||||
refId={reservation.refId}
|
||||
roomIndex={idx + 1}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
.rooms {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x5);
|
||||
}
|
||||
|
||||
.room {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x025);
|
||||
}
|
||||
|
||||
.roomTitle {
|
||||
color: var(--Text-Tertiary);
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
.booking {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x5);
|
||||
grid-area: booking;
|
||||
padding-bottom: var(--Spacing-x9);
|
||||
}
|
||||
|
||||
.aside {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.mobileReceipt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aside {
|
||||
display: grid;
|
||||
grid-area: receipt;
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
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 { mapRoomState } from "./utils"
|
||||
|
||||
import styles from "./bookingConfirmation.module.css"
|
||||
|
||||
type BookingConfirmationProps = {
|
||||
refId: string
|
||||
membershipFailedError: boolean
|
||||
}
|
||||
|
||||
export default async function BookingConfirmation({
|
||||
refId,
|
||||
membershipFailedError,
|
||||
}: BookingConfirmationProps) {
|
||||
const bookingConfirmation = await getBookingConfirmation(refId)
|
||||
|
||||
if (!bookingConfirmation) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const { booking, hotel, room, roomCategories } = bookingConfirmation
|
||||
|
||||
if (!room) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const intl = await getIntl()
|
||||
return (
|
||||
<BookingConfirmationProvider
|
||||
bookingCode={booking.bookingCode}
|
||||
currencyCode={booking.currencyCode}
|
||||
fromDate={booking.checkInDate}
|
||||
toDate={booking.checkOutDate}
|
||||
roomCategories={roomCategories}
|
||||
rooms={[
|
||||
mapRoomState(booking, room, intl),
|
||||
// null represents "known but not yet fetched rooms" and is used to render placeholders correctly
|
||||
...Array(booking.linkedReservations.length).fill(null),
|
||||
]}
|
||||
vat={booking.vatPercentage}
|
||||
>
|
||||
<Confirmation booking={booking} hotel={hotel} room={room}>
|
||||
<div className={styles.booking}>
|
||||
{membershipFailedError && (
|
||||
<Alert
|
||||
type={AlertTypeEnum.Info}
|
||||
heading={intl.formatMessage({
|
||||
defaultMessage: "Failed to verify membership",
|
||||
})}
|
||||
text={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.",
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<Rooms
|
||||
booking={booking}
|
||||
checkInTime={hotel.hotelFacts.checkin.checkInTime}
|
||||
checkOutTime={hotel.hotelFacts.checkin.checkOutTime}
|
||||
mainRoom={room}
|
||||
/>
|
||||
<PaymentDetails />
|
||||
<Divider color="Border/Divider/Subtle" />
|
||||
<HotelDetails hotel={hotel} />
|
||||
{filterOverlappingDates(
|
||||
hotel.specialAlerts,
|
||||
dt(booking.checkInDate),
|
||||
dt(booking.checkOutDate)
|
||||
).map((alert) => (
|
||||
<div key={alert.id}>
|
||||
<Alert
|
||||
type={alert.type}
|
||||
heading={alert.heading}
|
||||
text={alert.text}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Promos booking={booking} />
|
||||
<div className={styles.mobileReceipt}>
|
||||
<Receipt />
|
||||
</div>
|
||||
</div>
|
||||
<aside className={styles.aside}>
|
||||
<SidePanel variant="receipt">
|
||||
<Receipt />
|
||||
</SidePanel>
|
||||
</aside>
|
||||
</Confirmation>
|
||||
<Tracking bookingConfirmation={bookingConfirmation} refId={refId} />
|
||||
</BookingConfirmationProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { type IntlShape } from "react-intl"
|
||||
|
||||
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"
|
||||
|
||||
export function mapRoomState(
|
||||
booking: BookingConfirmationSchema,
|
||||
room: BookingConfirmationRoom,
|
||||
intl: IntlShape
|
||||
) {
|
||||
const breakfastPackage = booking.packages.find(
|
||||
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
||||
)
|
||||
const breakfastIncluded =
|
||||
booking.packages.some(
|
||||
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||
) || booking.rateDefinition.breakfastIncluded
|
||||
|
||||
let breakfast: false | undefined | PackageSchema
|
||||
if (breakfastPackage) {
|
||||
breakfast = breakfastPackage
|
||||
} else if (!breakfastIncluded) {
|
||||
breakfast = false
|
||||
}
|
||||
|
||||
let formattedRoomCost = formatPrice(
|
||||
intl,
|
||||
booking.roomPrice,
|
||||
booking.currencyCode
|
||||
)
|
||||
if (booking.roomPoints) {
|
||||
formattedRoomCost = formatPrice(
|
||||
intl,
|
||||
booking.roomPoints,
|
||||
CurrencyEnum.POINTS,
|
||||
booking.roomPrice,
|
||||
booking.currencyCode
|
||||
)
|
||||
} else if (booking.cheques) {
|
||||
formattedRoomCost = formatPrice(
|
||||
intl,
|
||||
booking.cheques,
|
||||
CurrencyEnum.CC,
|
||||
booking.roomPrice,
|
||||
booking.currencyCode
|
||||
)
|
||||
} else if (booking.vouchers) {
|
||||
formattedRoomCost = formatPrice(
|
||||
intl,
|
||||
booking.vouchers,
|
||||
CurrencyEnum.Voucher
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
adults: booking.adults,
|
||||
bedDescription: room.bedType.description,
|
||||
bedType: room.bedType.mainBed.type,
|
||||
bookingCode: booking.bookingCode,
|
||||
breakfast,
|
||||
breakfastIncluded,
|
||||
cheques: booking.cheques,
|
||||
childrenAges: booking.childrenAges,
|
||||
childBedPreferences: booking.childBedPreferences,
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
currencyCode: booking.currencyCode,
|
||||
formattedRoomCost,
|
||||
fromDate: booking.checkInDate,
|
||||
name: room.name,
|
||||
packages: booking.packages,
|
||||
rateDefinition: booking.rateDefinition,
|
||||
rateCodeType: booking.bookingType,
|
||||
refId: booking.refId,
|
||||
roomFeatures: booking.packages.filter((p) => p.type === "RoomFeature"),
|
||||
roomPoints: booking.roomPoints,
|
||||
roomPrice: booking.roomPrice,
|
||||
roomTypeCode: booking.roomTypeCode,
|
||||
toDate: booking.checkOutDate,
|
||||
totalPrice: booking.totalPrice,
|
||||
totalPriceExVat: booking.totalPriceExVat,
|
||||
vatAmount: booking.vatAmount,
|
||||
vouchers: booking.vouchers,
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { sidePanelVariants } from "./variants"
|
||||
|
||||
import styles from "./sidePanel.module.css"
|
||||
|
||||
import type { SidePanelProps } from "@/types/components/hotelReservation/sidePanel"
|
||||
|
||||
export default function SidePanel({
|
||||
children,
|
||||
variant,
|
||||
}: React.PropsWithChildren<SidePanelProps>) {
|
||||
const classNames = sidePanelVariants({ variant })
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<div className={styles.hider} />
|
||||
<div className={styles.wrapper}>{children}</div>
|
||||
<div className={styles.shadow} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
.sidePanel,
|
||||
.hider,
|
||||
.shadow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.sidePanel {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin-top: calc(0px - var(--Spacing-x9));
|
||||
}
|
||||
|
||||
.hider {
|
||||
display: block;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.receipt .hider {
|
||||
background-color: transparent;
|
||||
height: 150px;
|
||||
margin-top: -78px;
|
||||
top: -40px;
|
||||
}
|
||||
|
||||
.summary .hider {
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
height: 40px;
|
||||
margin-top: var(--Spacing-x4);
|
||||
top: calc(var(--booking-widget-desktop-height) - 6px);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
border-radius: var(--Corner-radius-lg) var(--Corner-radius-lg) 0 0;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-bottom: none;
|
||||
margin-top: calc(0px - var(--Spacing-x9));
|
||||
position: sticky;
|
||||
top: calc(
|
||||
var(--booking-widget-desktop-height) + var(--Spacing-x2) +
|
||||
var(--Spacing-x-half)
|
||||
);
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-style: solid;
|
||||
border-bottom: none;
|
||||
border-top: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./sidePanel.module.css"
|
||||
|
||||
export const sidePanelVariants = cva(styles.sidePanel, {
|
||||
variants: {
|
||||
variant: {
|
||||
receipt: styles.receipt,
|
||||
summary: styles.summary,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "summary",
|
||||
},
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
||||
|
||||
import type { IntlShape } from "react-intl"
|
||||
|
||||
export function getFeatureDescription(
|
||||
code: string,
|
||||
description: string,
|
||||
intl: IntlShape
|
||||
): string {
|
||||
const roomFeatureDescriptions: Record<string, string> = {
|
||||
[RoomPackageCodeEnum.ACCESSIBILITY_ROOM]: intl.formatMessage({
|
||||
defaultMessage: "Accessible room",
|
||||
}),
|
||||
[RoomPackageCodeEnum.ALLERGY_ROOM]: intl.formatMessage({
|
||||
defaultMessage: "Allergy-friendly room",
|
||||
}),
|
||||
[RoomPackageCodeEnum.PET_ROOM]: intl.formatMessage({
|
||||
defaultMessage: "Pet-friendly room",
|
||||
}),
|
||||
}
|
||||
|
||||
return roomFeatureDescriptions[code] ?? description
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export const SEARCHTYPE = "searchtype"
|
||||
|
||||
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"
|
||||
|
||||
export enum CancellationRuleEnum {
|
||||
CancellableBefore6PM = "CancellableBefore6PM",
|
||||
NonCancellable = "NonCancellable",
|
||||
Changeable = "Changeable",
|
||||
}
|
||||
|
||||
export enum PaymentCallbackStatusEnum {
|
||||
Success = "success",
|
||||
Error = "error",
|
||||
Cancel = "cancel",
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
||||
|
||||
export interface BookingConfirmationHotelDetailsProps {
|
||||
hotel: BookingConfirmation["hotel"]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export interface PromoProps {
|
||||
buttonText: string
|
||||
href: string
|
||||
text: string
|
||||
title: string
|
||||
image?: {
|
||||
imageSizes: {
|
||||
large: string
|
||||
medium: string
|
||||
small: string
|
||||
tiny: string
|
||||
}
|
||||
metaData: {
|
||||
altText: string
|
||||
altText_En: string
|
||||
copyRight: string
|
||||
title: string
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
|
||||
|
||||
export interface PromosProps extends Pick<BookingConfirmation, "booking"> {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface LinkedReservationProps {
|
||||
checkInTime: string
|
||||
checkOutTime: string
|
||||
refId: string
|
||||
roomIndex: number
|
||||
}
|
||||
|
||||
export interface RetryProps {
|
||||
handleRefetch: () => void
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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> {}
|
||||
Reference in New Issue
Block a user