Merged in revert-pr-1925 (pull request #1927)

Revert "Feat/sw 2323 find booking (pull request #1925)"

Approved-by: Anton Gunnarsson
This commit is contained in:
Linus Flood
2025-05-02 13:05:42 +00:00
parent 87efb72ff2
commit 6979ac0c3b
69 changed files with 883 additions and 1508 deletions

View File

@@ -0,0 +1,21 @@
.main {
background-color: var(--Base-Surface-Primary-light-Normal);
display: grid;
gap: var(--Spacing-x5);
grid-template-areas: "header" "booking";
margin: 0 auto;
min-height: 100dvh;
padding-top: var(--Spacing-x5);
width: var(--max-width-page);
}
@media screen and (min-width: 1367px) {
.main {
grid-template-areas:
"header receipt"
"booking receipt";
grid-template-columns: 1fr 340px;
grid-template-rows: auto 1fr;
padding-top: var(--Spacing-x9);
}
}

View File

@@ -0,0 +1,24 @@
"use client"
import { useRef } from "react"
import Header from "@/components/HotelReservation/BookingConfirmation/Header"
import styles from "./confirmation.module.css"
import type { ConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
export default function Confirmation({
booking,
hotel,
children,
refId,
}: React.PropsWithChildren<ConfirmationProps>) {
const mainRef = useRef<HTMLElement | null>(null)
return (
<main className={styles.main} ref={mainRef}>
<Header booking={booking} hotel={hotel} mainRef={mainRef} refId={refId} />
{children}
</main>
)
}

View File

@@ -1,22 +1,15 @@
"use client"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { myStay } from "@/constants/routes/myStay"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import useLang from "@/hooks/useLang"
import type { ManageBookingProps } from "@/types/components/hotelReservation/bookingConfirmation/actions/manageBooking"
export default function ManageBooking({ refId }: ManageBookingProps) {
export default function ManageBooking({ bookingUrl }: ManageBookingProps) {
const intl = useIntl()
const lang = useLang()
const bookingUrl = `${myStay[lang]}?RefId=${refId}`
return (
<Button

View File

@@ -1,11 +1,15 @@
"use client"
import { useIntl } from "react-intl"
import { myStay } from "@/constants/routes/myStay"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import useLang from "@/hooks/useLang"
import AddToCalendar from "../../AddToCalendar"
import AddToCalendarButton from "./Actions/AddToCalendarButton"
// import DownloadInvoice from "./Actions/DownloadInvoice"
import { generateDateTime } from "./Actions/helpers"
import ManageBooking from "./Actions/ManageBooking"
@@ -18,9 +22,11 @@ import type { BookingConfirmationHeaderProps } from "@/types/components/hotelRes
export default function Header({
booking,
hotel,
// mainRef,
refId,
}: BookingConfirmationHeaderProps) {
const intl = useIntl()
const lang = useLang()
const text = intl.formatMessage({
defaultMessage:
@@ -46,6 +52,8 @@ export default function Header({
url: hotel.contactInformation.websiteUrl,
}
const bookingUrlPath = `${myStay[lang]}?RefId=${refId}`
return (
<header className={styles.header}>
<hgroup className={styles.hgroup}>
@@ -66,7 +74,9 @@ export default function Header({
hotelName={hotel.name}
renderButton={(onPress) => <AddToCalendarButton onPress={onPress} />}
/>
<ManageBooking refId={refId} />
<ManageBooking bookingUrl={bookingUrlPath} />
{/* Download Invoice will be added later (currently available on My Stay) */}
{/* <DownloadInvoice mainRef={mainRef} /> */}
</div>
</header>
)

View File

@@ -1,8 +1,9 @@
"use client"
import { useIntl } from "react-intl"
import { myStay } from "@/constants/routes/myStay"
import { homeHrefs } from "@/constants/homeHrefs"
import { myBooking } from "@/constants/myBooking"
import { env } from "@/env/client"
import useLang from "@/hooks/useLang"
@@ -12,17 +13,22 @@ import styles from "./promos.module.css"
import type { PromosProps } from "@/types/components/hotelReservation/bookingConfirmation/promos"
export default function Promos({ refId, hotelId }: PromosProps) {
export default function Promos({
confirmationNumber,
hotelId,
lastName,
}: PromosProps) {
const intl = useIntl()
const lang = useLang()
const homeUrl = homeHrefs[env.NEXT_PUBLIC_NODE_ENV][lang]
const myBookingUrl = myBooking[env.NEXT_PUBLIC_NODE_ENV][lang]
return (
<div className={styles.promos}>
<Promo
buttonText={intl.formatMessage({
defaultMessage: "View and buy add-ons",
})}
href={`${myStay[lang]}?RefId=${refId}`}
href={`${myBookingUrl}?bookingId=${confirmationNumber}&lastName=${lastName}`}
text={intl.formatMessage({
defaultMessage:
"Discover the little extra touches to make your upcoming stay even more unforgettable.",
@@ -35,7 +41,7 @@ export default function Promos({ refId, hotelId }: PromosProps) {
buttonText={intl.formatMessage({
defaultMessage: "Book another stay",
})}
href={`/${lang}?hotel=${hotelId}`}
href={`${homeUrl}?hotel=${hotelId}`}
text={intl.formatMessage({
defaultMessage:
"Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.",

View File

@@ -20,16 +20,14 @@ import { CurrencyEnum } from "@/types/enums/currency"
export function LinkedReservation({
checkInTime,
checkOutTime,
refId,
confirmationNumber,
roomIndex,
}: LinkedReservationProps) {
const lang = useLang()
const { data, refetch, isLoading } = trpc.booking.confirmation.useQuery({
refId,
const { data, refetch, isLoading } = trpc.booking.get.useQuery({
confirmationNumber,
lang,
})
const {
setRoom,
setFormattedTotalCost,
@@ -43,7 +41,6 @@ export function LinkedReservation({
totalBookingPrice: state.totalBookingPrice,
totalBookingCheques: state.totalBookingCheques,
}))
const intl = useIntl()
useEffect(() => {
@@ -82,20 +79,13 @@ export function LinkedReservation({
return <Retry handleRefetch={refetch} />
}
const { booking, room } = data
return (
<Room
checkInDate={booking.checkInDate}
checkOutDate={booking.checkOutDate}
booking={data.booking}
checkInTime={checkInTime}
checkOutTime={checkOutTime}
confirmationNumber={booking.confirmationNumber}
guaranteeInfo={booking.guaranteeInfo}
guest={booking.guest}
img={room.images[0]}
rateDefinition={booking.rateDefinition}
roomName={room.name}
img={data.room.images[0]}
roomName={data.room.name}
/>
)
}

View File

@@ -20,28 +20,24 @@ import styles from "./room.module.css"
import type { RoomProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/room"
export default function Room({
checkInDate,
checkOutDate,
booking,
checkInTime,
checkOutTime,
confirmationNumber,
guaranteeInfo,
guest,
img,
rateDefinition,
roomName,
}: RoomProps) {
const intl = useIntl()
const lang = useLang()
const guestName = `${guest.firstName} ${guest.lastName}`
const fromDate = dt(checkInDate).locale(lang)
const toDate = dt(checkOutDate).locale(lang)
const guestName = `${booking.guest.firstName} ${booking.guest.lastName}`
const fromDate = dt(booking.checkInDate).locale(lang)
const toDate = dt(booking.checkOutDate).locale(lang)
const isFlexBooking =
rateDefinition.cancellationRule ===
booking.rateDefinition.cancellationRule ===
CancellationRuleEnum.CancellableBefore6PM
const isChangeBooking =
rateDefinition.cancellationRule === CancellationRuleEnum.Changeable
booking.rateDefinition.cancellationRule === CancellationRuleEnum.Changeable
return (
<article className={styles.room}>
<header className={styles.header}>
@@ -51,11 +47,11 @@ export default function Room({
{
defaultMessage: "Booking number {value}",
},
{ value: confirmationNumber }
{ value: booking.confirmationNumber }
)}
</h2>
</Typography>
{rateDefinition.isMemberRate ? (
{booking.rateDefinition.isMemberRate ? (
<div className={styles.benefits}>
<>
<MaterialIcon
@@ -71,7 +67,7 @@ export default function Room({
</>
</div>
) : null}
{guaranteeInfo && (
{booking.guaranteeInfo && (
<div className={styles.benefits}>
<MaterialIcon
icon="check_circle"
@@ -172,7 +168,7 @@ export default function Room({
})}
</Body>
<Body color="uiTextHighContrast">
{rateDefinition.cancellationText}
{booking.rateDefinition.cancellationText}
</Body>
</li>
{isFlexBooking || isChangeBooking ? (
@@ -200,23 +196,25 @@ export default function Room({
})}
</Body>
<Body color="uiTextHighContrast">{guestName}</Body>
{guest.membershipNumber ? (
{booking.guest.membershipNumber ? (
<Body color="uiTextHighContrast">
{intl.formatMessage(
{
defaultMessage: "Friend no. {value}",
},
{
value: guest.membershipNumber,
value: booking.guest.membershipNumber,
}
)}
</Body>
) : null}
{guest.phoneNumber ? (
<Body color="uiTextHighContrast">{guest.phoneNumber}</Body>
{booking.guest.phoneNumber ? (
<Body color="uiTextHighContrast">
{booking.guest.phoneNumber}
</Body>
) : null}
{guest.email ? (
<Body color="uiTextHighContrast">{guest.email}</Body>
{booking.guest.email ? (
<Body color="uiTextHighContrast">{booking.guest.email}</Body>
) : null}
</div>
</div>

View File

@@ -9,56 +9,55 @@ import styles from "./rooms.module.css"
import type { BookingConfirmationRoomsProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms"
async function RoomTitle({ nr }: { nr: number }) {
const intl = await getIntl()
return (
<Typography variant="Title/Subtitle/md">
<h2 className={styles.roomTitle}>
{intl.formatMessage(
{
defaultMessage: "Room {roomIndex}",
},
{ roomIndex: nr }
)}
</h2>
</Typography>
)
}
export default async function Rooms({
booking,
checkInTime,
checkOutTime,
mainRoom,
linkedReservations,
}: BookingConfirmationRoomsProps) {
const { linkedReservations } = booking
const intl = await getIntl()
return (
<section className={styles.rooms}>
<div className={styles.room}>
{linkedReservations.length ? <RoomTitle nr={1} /> : null}
{linkedReservations.length ? (
<Typography variant="Title/Subtitle/md">
<h2 className={styles.roomTitle}>
{intl.formatMessage(
{
defaultMessage: "Room {roomIndex}",
},
{ roomIndex: 1 }
)}
</h2>
</Typography>
) : null}
<Room
checkInDate={booking.checkInDate}
checkOutDate={booking.checkOutDate}
booking={booking}
checkInTime={checkInTime}
checkOutTime={checkOutTime}
confirmationNumber={booking.confirmationNumber}
guaranteeInfo={booking.guaranteeInfo}
guest={booking.guest}
img={mainRoom.images[0]}
rateDefinition={booking.rateDefinition}
roomName={mainRoom.name}
/>
</div>
{linkedReservations.map((reservation, idx) => (
<div className={styles.room} key={reservation.confirmationNumber}>
<RoomTitle nr={idx + 2} />
<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}
confirmationNumber={reservation.confirmationNumber}
roomIndex={idx + 1}
/>
</div>

View File

@@ -27,7 +27,7 @@ export default function Tracking({
getTracking(
lang,
bookingConfirmation.booking,
bookingConfirmation.hotelData.hotel,
bookingConfirmation.hotel,
rooms
)

View File

@@ -67,7 +67,7 @@ function mapAncillaryPackage(
export function getTracking(
lang: Lang,
booking: BookingConfirmation["booking"],
hotel: BookingConfirmation["hotelData"]["hotel"],
hotel: BookingConfirmation["hotel"],
rooms: Room[]
) {
const arrivalDate = new Date(booking.checkInDate)

View File

@@ -0,0 +1,22 @@
.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;
}
}

View File

@@ -0,0 +1,87 @@
import { notFound } from "next/navigation"
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import { encrypt } from "@/server/routers/utils/encryption"
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 Divider from "@/components/TempDesignSystem/Divider"
import { getIntl } from "@/i18n"
import BookingConfirmationProvider from "@/providers/BookingConfirmationProvider"
import Alerts from "./Alerts"
import Confirmation from "./Confirmation"
import Tracking from "./Tracking"
import { mapRoomState } from "./utils"
import styles from "./bookingConfirmation.module.css"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
export default async function BookingConfirmation({
confirmationNumber,
}: BookingConfirmationProps) {
const bookingConfirmation = await getBookingConfirmation(confirmationNumber)
if (!bookingConfirmation) {
return notFound()
}
const { booking, hotel, room } = bookingConfirmation
if (!room) {
return notFound()
}
const refId = encrypt(
`${booking.confirmationNumber},${booking.guest.lastName}`
)
const intl = await getIntl()
return (
<BookingConfirmationProvider
bookingCode={booking.bookingCode}
currencyCode={booking.currencyCode}
fromDate={booking.checkInDate}
toDate={booking.checkOutDate}
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} refId={refId}>
<div className={styles.booking}>
<Alerts booking={booking} />
<Rooms
booking={booking}
checkInTime={hotel.hotelFacts.checkin.checkInTime}
checkOutTime={hotel.hotelFacts.checkin.checkOutTime}
mainRoom={room}
linkedReservations={booking.linkedReservations}
/>
<PaymentDetails />
<Divider color="primaryLightSubtle" />
<HotelDetails hotel={hotel} />
<Promos
confirmationNumber={booking.confirmationNumber}
hotelId={hotel.operaId}
lastName={booking.guest.lastName}
/>
<div className={styles.mobileReceipt}>
<Receipt />
</div>
</div>
<aside className={styles.aside}>
<SidePanel variant="receipt">
<Receipt />
</SidePanel>
</aside>
</Confirmation>
<Tracking bookingConfirmation={bookingConfirmation} />
</BookingConfirmationProvider>
)
}

View File

@@ -5,10 +5,10 @@ import type { IntlShape } from "react-intl"
import type { BookingConfirmationRoom } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import { CurrencyEnum } from "@/types/enums/currency"
import type { BookingSchema } from "@/types/trpc/routers/booking/confirmation"
import type { BookingConfirmationSchema } from "@/types/trpc/routers/booking/confirmation"
export function mapRoomState(
booking: BookingSchema,
booking: BookingConfirmationSchema,
room: BookingConfirmationRoom,
intl: IntlShape
) {