From d5e5b9a52630d75c60277476907f646f9963a20c Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Wed, 26 Feb 2025 12:42:54 +0000 Subject: [PATCH] Merged in feat/SW-1652-confirmation-page-multiroom (pull request #1404) feat(SW-1652): Fetching additional rooms on confirmation page * feat(SW-1652): Fetching additional rooms on confirmation page Approved-by: Tobias Johansson --- .../BookingConfirmation/Alerts/index.tsx | 50 ++++++ .../Confirmation/confirmation.module.css | 21 --- .../Confirmation/index.tsx | 71 +------- .../HotelDetails/hotelDetails.module.css | 5 - .../HotelDetails/index.tsx | 8 - .../PaymentDetails/index.tsx | 32 ++-- .../Receipt/Room/index.tsx | 130 ++++++++++++++ .../Receipt/Room/room.module.css | 42 +++++ .../Receipt/Rooms/index.tsx | 88 +++++++++ .../Receipt/Rooms/rooms.module.css | 8 + .../BookingConfirmation/Receipt/index.tsx | 167 +++--------------- .../Receipt/receipt.module.css | 44 ----- .../LinkedReservationCardSkeleton.tsx | 33 ++++ .../Rooms/LinkedReservation/index.tsx | 102 ++--------- .../linkedReservation.module.css | 19 -- .../linkedReservationCardSkeleton.module.css | 66 +++++++ .../BookingConfirmation/Rooms/index.tsx | 17 +- .../bookingConfirmation.module.css | 22 +++ .../BookingConfirmation/index.tsx | 59 ++++++- .../server/routers/booking/output.ts | 7 +- .../bookingConfirmation.ts | 9 +- .../bookingConfirmation/paymentDetails.ts | 4 +- .../bookingConfirmation/receipt.ts | 21 ++- .../bookingConfirmation/rooms.ts | 6 +- 24 files changed, 606 insertions(+), 425 deletions(-) create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Alerts/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/room.module.css create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/index.tsx create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/rooms.module.css create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/LinkedReservationCardSkeleton.tsx delete mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservation.module.css create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservationCardSkeleton.module.css create mode 100644 apps/scandic-web/components/HotelReservation/BookingConfirmation/bookingConfirmation.module.css diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Alerts/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Alerts/index.tsx new file mode 100644 index 000000000..a198f895f --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Alerts/index.tsx @@ -0,0 +1,50 @@ +"use client" + +import { useSearchParams } from "next/navigation" +import { useIntl } from "react-intl" + +import { MEMBERSHIP_FAILED_ERROR } from "@/constants/booking" + +import Alert from "@/components/TempDesignSystem/Alert" + +import type { BookingConfirmationAlertsProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" +import { AlertTypeEnum } from "@/types/enums/alert" + +export default function Alerts({ booking }: BookingConfirmationAlertsProps) { + const intl = useIntl() + const searchParams = useSearchParams() + + const membershipFailedError = + searchParams.get("errorCode") === MEMBERSHIP_FAILED_ERROR + const failedToVerifyMembership = + booking.rateDefinition.isMemberRate && !booking.guest.membershipNumber + + return ( + <> + {/* Customer has manually entered a membership number for which verification failed */} + {membershipFailedError && ( + + )} + {/* For some other reason membership could not be verified */} + {!membershipFailedError && failedToVerifyMembership && ( + + )} + + ) +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/confirmation.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/confirmation.module.css index bba79b2d0..e3d91663e 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/confirmation.module.css +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/confirmation.module.css @@ -9,18 +9,6 @@ width: var(--max-width-page); } -.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) { .main { grid-template-areas: @@ -30,13 +18,4 @@ grid-template-rows: auto 1fr; padding-top: var(--Spacing-x9); } - - .mobileReceipt { - display: none; - } - - .aside { - display: grid; - grid-area: receipt; - } } diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/index.tsx index b5884b689..10c9258db 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Confirmation/index.tsx @@ -1,88 +1,23 @@ "use client" -import { useSearchParams } from "next/navigation" import { useRef } from "react" -import { useIntl } from "react-intl" - -import { MEMBERSHIP_FAILED_ERROR } from "@/constants/booking" import Header from "@/components/HotelReservation/BookingConfirmation/Header" -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 Alert from "@/components/TempDesignSystem/Alert" -import Divider from "@/components/TempDesignSystem/Divider" import styles from "./confirmation.module.css" import type { ConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" -import { AlertTypeEnum } from "@/types/enums/alert" export default function Confirmation({ booking, hotel, - room, -}: ConfirmationProps) { - const searchParams = useSearchParams() - const intl = useIntl() + children, +}: React.PropsWithChildren) { const mainRef = useRef(null) - const membershipFailedError = - searchParams.get("errorCode") === MEMBERSHIP_FAILED_ERROR - const failedToVerifyMembership = - booking.rateDefinition.isMemberRate && !booking.guest.membershipNumber return (
-
- {/* Customer has manually entered a membership number for which verification failed */} - {membershipFailedError && ( - - )} - {/* For some other reason membership could not be verified */} - {!membershipFailedError && failedToVerifyMembership && ( - - )} - - - - - -
- -
-
- + {children}
) } diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/hotelDetails.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/hotelDetails.module.css index 484ae03e2..9f0a58a4a 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/hotelDetails.module.css +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/hotelDetails.module.css @@ -23,11 +23,6 @@ margin-top: var(--Spacing-x-half); } -.toast { - align-self: flex-start; - min-width: 300px; -} - .list { padding-left: var(--Spacing-x2); } diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/index.tsx index 75f50fbb4..69859c8c1 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/HotelDetails/index.tsx @@ -5,7 +5,6 @@ import { useIntl } from "react-intl" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" -import { Toast } from "@/components/TempDesignSystem/Toasts" import styles from "./hotelDetails.module.css" @@ -68,13 +67,6 @@ export default function HotelDetails({ {hotel.contactInformation.websiteUrl} -
- -
    -
  • {intl.formatMessage({ id: "N/A" })}
  • -
-
-
) } diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/PaymentDetails/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/PaymentDetails/index.tsx index 4353ee348..9398e1850 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/PaymentDetails/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/PaymentDetails/index.tsx @@ -1,24 +1,36 @@ -"use client" -import { useIntl } from "react-intl" - import { dt } from "@/lib/dt" +import { serverClient } from "@/lib/trpc/server" import { CreditCardAddIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" -import useLang from "@/hooks/useLang" +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" import { formatPrice } from "@/utils/numberFormatting" import styles from "./paymentDetails.module.css" import type { BookingConfirmationPaymentDetailsProps } from "@/types/components/hotelReservation/bookingConfirmation/paymentDetails" -export default function PaymentDetails({ +export default async function PaymentDetails({ booking, }: BookingConfirmationPaymentDetailsProps) { - const intl = useIntl() - const lang = useLang() + const intl = await getIntl() + const lang = getLang() + + const linkedReservations = await Promise.all( + // TODO: How to handle partial failure (e.g. one booking can't be fetched)? Need UX/UI + booking.linkedReservations.map(async (res) => { + const confirmation = await serverClient().booking.confirmation({ + confirmationNumber: res.confirmationNumber, + }) + return confirmation + }) + ) + const grandTotal = linkedReservations.reduce((acc, res) => { + return res ? acc + res.booking.totalPrice : acc + }, booking.totalPrice) return (
@@ -29,11 +41,7 @@ export default function PaymentDetails({ {intl.formatMessage( { id: "{amount} has been paid" }, { - amount: formatPrice( - intl, - booking.totalPrice, - booking.currencyCode - ), + amount: formatPrice(intl, grandTotal, booking.currencyCode), } )} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx new file mode 100644 index 000000000..963689894 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/index.tsx @@ -0,0 +1,130 @@ +"use client" + +import { useIntl } from "react-intl" + +import { CheckIcon, InfoCircleIcon } from "@/components/Icons" +import Modal from "@/components/Modal" +import Button from "@/components/TempDesignSystem/Button" +import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import { formatPrice } from "@/utils/numberFormatting" + +import styles from "./room.module.css" + +import type { BookingConfirmationReceiptRoomProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt" +import { BreakfastPackageEnum } from "@/types/enums/breakfast" + +export default function ReceiptRoom({ + booking, + room, + roomNumber, +}: BookingConfirmationReceiptRoomProps) { + const intl = useIntl() + + const breakfastPkgSelected = booking.packages.find( + (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST + ) + const breakfastPkgIncluded = booking.packages.find( + (pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST + ) + + return ( +
+ {roomNumber !== null ? ( + + {intl.formatMessage( + { id: "Room {roomIndex}" }, + { roomIndex: roomNumber } + )} + + ) : null} +
+ {room.name} + {booking.rateDefinition.isMemberRate ? ( +
+ + {formatPrice(intl, booking.roomPrice, booking.currencyCode)} + +
+ ) : ( + + {formatPrice(intl, booking.roomPrice, booking.currencyCode)} + + )} + + {intl.formatMessage( + { id: "{totalAdults, plural, one {# adult} other {# adults}}" }, + { + totalAdults: booking.adults, + } + )} + + + {booking.rateDefinition.cancellationText} + + + + {intl.formatMessage({ id: "Reservation policy" })} + + + + } + title={booking.rateDefinition.cancellationText || ""} + subtitle={ + booking.rateDefinition.cancellationRule == "CancellableBefore6PM" + ? intl.formatMessage({ id: "Pay later" }) + : intl.formatMessage({ id: "Pay now" }) + } + > +
+ {booking.rateDefinition.generalTerms?.map((info) => ( + + + {info} + + ))} +
+
+
+
+ {room.bedType.description} + + {formatPrice(intl, 0, booking.currencyCode)} + +
+
+ {intl.formatMessage({ id: "Breakfast buffet" })} + {booking.rateDefinition.breakfastIncluded ?? breakfastPkgIncluded ? ( + {intl.formatMessage({ id: "Included" })} + ) : null} + {breakfastPkgSelected ? ( + + {formatPrice( + intl, + breakfastPkgSelected.totalPrice, + breakfastPkgSelected.currency + )} + + ) : null} +
+
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/room.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/room.module.css new file mode 100644 index 000000000..5566b207b --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Room/room.module.css @@ -0,0 +1,42 @@ +.room { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-one-and-half); +} + +.roomHeader { + display: grid; + grid-template-columns: 1fr auto; +} + +.roomHeader :nth-child(n + 3) { + grid-column: 1/-1; +} + +.memberPrice { + display: flex; + gap: var(--Spacing-x1); +} + +.entry { + display: flex; + justify-content: space-between; +} + +.termsLink { + justify-self: flex-start; +} + +.terms { + padding-top: var(--Spacing-x3); +} + +.termsText:nth-child(n) { + display: flex; + align-items: center; + padding-bottom: var(--Spacing-x1); +} + +.terms .termsIcon { + padding-right: var(--Spacing-x1); +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/index.tsx new file mode 100644 index 000000000..c954a99f3 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/index.tsx @@ -0,0 +1,88 @@ +"use client" + +import { useIntl } from "react-intl" + +import { ChevronRightSmallIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" +import Divider from "@/components/TempDesignSystem/Divider" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import { formatPrice } from "@/utils/numberFormatting" + +import Room from "../Room" + +import styles from "./rooms.module.css" + +import type { BookingConfirmationReceiptRoomsProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt" + +export default function ReceiptRooms({ + booking, + room, + linkedReservations, +}: BookingConfirmationReceiptRoomsProps) { + const intl = useIntl() + + if (linkedReservations.some((reservation) => !reservation)) { + return null + } + + const grandTotal = linkedReservations.reduce((acc, reservation) => { + const reservationTotalPrice = reservation?.booking.totalPrice || 0 + return acc + reservationTotalPrice + }, booking.totalPrice) + + return ( + <> + + {linkedReservations.map((reservation, idx) => { + if (!reservation?.room) { + return null + } + return ( + + ) + })} + +
+
+ + {intl.formatMessage({ id: "Total price" })} + + + {formatPrice(intl, grandTotal, booking.currencyCode)} + +
+
+ + + {intl.formatMessage( + { id: "Approx. {value}" }, + { + value: "N/A", + } + )} + +
+
+ + ) +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/rooms.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/rooms.module.css new file mode 100644 index 000000000..585a5a26d --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/Rooms/rooms.module.css @@ -0,0 +1,8 @@ +.entry { + display: flex; + justify-content: space-between; +} + +.price button.btn { + padding: 0; +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/index.tsx index 6204e4009..3a2542d74 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/index.tsx @@ -1,169 +1,46 @@ -"use client" - import { notFound } from "next/navigation" -import { useIntl } from "react-intl" -import { - CheckIcon, - ChevronRightSmallIcon, - InfoCircleIcon, -} from "@/components/Icons" -import Modal from "@/components/Modal" -import Button from "@/components/TempDesignSystem/Button" -import Divider from "@/components/TempDesignSystem/Divider" -import Link from "@/components/TempDesignSystem/Link" -import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" +import { serverClient } from "@/lib/trpc/server" + import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" -import { formatPrice } from "@/utils/numberFormatting" +import { getIntl } from "@/i18n" + +import ReceiptRooms from "./Rooms" import styles from "./receipt.module.css" import type { BookingConfirmationReceiptProps } from "@/types/components/hotelReservation/bookingConfirmation/receipt" -import { BreakfastPackageEnum } from "@/types/enums/breakfast" -export default function Receipt({ +export default async function Receipt({ booking, room, }: BookingConfirmationReceiptProps) { - const intl = useIntl() - if (!room) { return notFound() } - const breakfastPkgSelected = booking.packages.find( - (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST - ) - const breakfastPkgIncluded = booking.packages.find( - (pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST + const intl = await getIntl() + + const linkedReservations = await Promise.all( + // TODO: How to handle partial failure (e.g. one booking can't be fetched)? Need UX/UI + booking.linkedReservations.map(async (res) => { + const confirmation = await serverClient().booking.confirmation({ + confirmationNumber: res.confirmationNumber, + }) + return confirmation + }) ) + return (
{intl.formatMessage({ id: "Booking summary" })} -
-
- {room.name} - {booking.rateDefinition.isMemberRate ? ( -
- - {formatPrice(intl, booking.roomPrice, booking.currencyCode)} - -
- ) : ( - - {formatPrice(intl, booking.roomPrice, booking.currencyCode)} - - )} - - {intl.formatMessage( - { id: "{totalAdults, plural, one {# adult} other {# adults}}" }, - { - totalAdults: booking.adults, - } - )} - - - {booking.rateDefinition.cancellationText} - - - - {intl.formatMessage({ id: "Reservation policy" })} - - - - } - title={booking.rateDefinition.cancellationText || ""} - subtitle={ - booking.rateDefinition.cancellationRule == "CancellableBefore6PM" - ? intl.formatMessage({ id: "Pay later" }) - : intl.formatMessage({ id: "Pay now" }) - } - > -
- {booking.rateDefinition.generalTerms?.map((info) => ( - - - {info} - - ))} -
-
-
-
- {room.bedType.description} - - {formatPrice(intl, 0, booking.currencyCode)} - -
-
- {intl.formatMessage({ id: "Breakfast buffet" })} - {booking.rateDefinition.breakfastIncluded ?? breakfastPkgIncluded ? ( - {intl.formatMessage({ id: "Included" })} - ) : null} - - {breakfastPkgSelected ? ( - - {formatPrice( - intl, - breakfastPkgSelected.totalPrice, - breakfastPkgSelected.currency - )} - - ) : null} -
-
- -
-
- - {intl.formatMessage({ id: "Total price" })} - - - {formatPrice(intl, booking.totalPrice, booking.currencyCode)} - -
-
- - - {intl.formatMessage( - { id: "Approx. {value}" }, - { - value: "N/A", - } - )} - -
-
+
) } diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/receipt.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/receipt.module.css index c84c727d9..097ced210 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/receipt.module.css +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Receipt/receipt.module.css @@ -4,50 +4,6 @@ gap: var(--Spacing-x-one-and-half); } -.room { - display: flex; - flex-direction: column; - gap: var(--Spacing-x-one-and-half); -} - -.roomHeader { - display: grid; - grid-template-columns: 1fr auto; -} - -.roomHeader :nth-child(n + 3) { - grid-column: 1/-1; -} - -.memberPrice { - display: flex; - gap: var(--Spacing-x1); -} - -.entry { - display: flex; - justify-content: space-between; -} - -.receipt .price button.btn { - padding: 0; -} - -.termsLink { - justify-self: flex-start; -} -.terms { - padding-top: var(--Spacing-x3); -} -.termsText:nth-child(n) { - display: flex; - align-items: center; - padding-bottom: var(--Spacing-x1); -} -.terms .termsIcon { - padding-right: var(--Spacing-x1); -} - @media screen and (min-width: 1367px) { .receipt { padding: var(--Spacing-x3); diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/LinkedReservationCardSkeleton.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/LinkedReservationCardSkeleton.tsx new file mode 100644 index 000000000..4458462f4 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/LinkedReservationCardSkeleton.tsx @@ -0,0 +1,33 @@ +import SkeletonShimmer from "@/components/SkeletonShimmer" + +import styles from "./linkedReservationCardSkeleton.module.css" + +export function LinkedReservationCardSkeleton() { + return ( +
+
+
+ +
+
+
+ + +
+
+ + + + +
+
+ + + + +
+
+
+
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/index.tsx index ab7cdc81c..89c385c18 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/index.tsx @@ -1,92 +1,22 @@ -"use client " -import { useIntl } from "react-intl" +import { serverClient } from "@/lib/trpc/server" -import { dt } from "@/lib/dt" +import Room from "../Room" -import Body from "@/components/TempDesignSystem/Text/Body" -import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" -import useLang from "@/hooks/useLang" +export async function LinkedReservation({ + confirmationNumber, +}: { + confirmationNumber: string +}) { + const confirmation = await serverClient().booking.confirmation({ + confirmationNumber, + }) -import styles from "./linkedReservation.module.css" + const room = confirmation?.room + const booking = confirmation?.booking -import type { LinkedReservationSchema } from "@/types/components/hotelReservation/bookingConfirmation/rooms" + if (!booking || !room) { + return
Something went wrong, try again
+ } -interface LinkedReservationProps { - linkedReservation: LinkedReservationSchema - roomIndex: number -} - -export function LinkedReservation({ - linkedReservation, - roomIndex, -}: LinkedReservationProps) { - const intl = useIntl() - const lang = useLang() - const { checkinDate, checkoutDate, confirmationNumber, adults, children } = - linkedReservation - - const fromDate = dt(checkinDate).locale(lang) - const toDate = dt(checkoutDate).locale(lang) - return ( -
- - {intl.formatMessage( - { - id: "Room {roomIndex}", - }, - { - roomIndex: roomIndex + 2, - } - )} - -
    -
  • - - {intl.formatMessage({ id: "Booking number" })} - - {confirmationNumber} -
  • -
  • - - {intl.formatMessage({ id: "Check-in" })} - - - {intl.formatMessage( - { id: "{checkInDate} from {checkInTime}" }, - { - checkInDate: fromDate.format("ddd, D MMM"), - checkInTime: fromDate.format("HH:mm"), - } - )} - -
  • -
  • - - {intl.formatMessage({ id: "Check-out" })} - - - {intl.formatMessage( - { id: "{checkOutDate} from {checkOutTime}" }, - { - checkOutDate: toDate.format("ddd, D MMM"), - checkOutTime: toDate.format("HH:mm"), - } - )} - -
  • -
  • - - {intl.formatMessage({ id: "Adults" })} - - {adults} -
  • -
  • - - {intl.formatMessage({ id: "Children" })} - - {children} -
  • -
-
- ) + return } diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservation.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservation.module.css deleted file mode 100644 index b97073c61..000000000 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservation.module.css +++ /dev/null @@ -1,19 +0,0 @@ -.reservation { - background-color: var(--Base-Background-Primary-Normal); - border-radius: var(--Corner-radius-Large); - display: grid; - gap: var(--Spacing-x2); - padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x3) - var(--Spacing-x2); -} - -.details { - display: grid; - gap: var(--Spacing-x-half) var(--Spacing-x3); - list-style: none; -} - -.listItem { - display: flex; - gap: var(--Spacing-x1); -} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservationCardSkeleton.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservationCardSkeleton.module.css new file mode 100644 index 000000000..7f145e167 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/LinkedReservation/linkedReservationCardSkeleton.module.css @@ -0,0 +1,66 @@ +.card { + display: flex; + flex-direction: column; + gap: var(--Spacing-x2); +} + +.content { + background-color: var(--Base-Background-Primary-Normal); + border-radius: var(--Corner-radius-Large); + 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-Medium); + 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; + } +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/index.tsx index c6ee6e745..c0c220b65 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Rooms/index.tsx @@ -1,5 +1,6 @@ -"use client" +import { Suspense } from "react" +import { LinkedReservationCardSkeleton } from "./LinkedReservation/LinkedReservationCardSkeleton" import { LinkedReservation } from "./LinkedReservation" import Room from "./Room" @@ -19,12 +20,16 @@ export default function Rooms({ img={mainRoom.images[0]} roomName={mainRoom.name} /> - {linkedReservations?.map((reservation, idx) => ( - ( + + fallback={} + > + + ))} ) diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/bookingConfirmation.module.css b/apps/scandic-web/components/HotelReservation/BookingConfirmation/bookingConfirmation.module.css new file mode 100644 index 000000000..f7555e753 --- /dev/null +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/bookingConfirmation.module.css @@ -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; + } +} diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/index.tsx b/apps/scandic-web/components/HotelReservation/BookingConfirmation/index.tsx index b8052fd4f..aa974eb37 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/index.tsx +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/index.tsx @@ -3,13 +3,24 @@ import { notFound } from "next/navigation" import { Suspense } from "react" import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" +import { serverClient } from "@/lib/trpc/server" +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 TrackingSDK from "@/components/TrackingSDK" import { getLang } from "@/i18n/serverContext" import { invertedBedTypeMap } from "../utils" +import Alerts from "./Alerts" import Confirmation from "./Confirmation" +import styles from "./bookingConfirmation.module.css" + import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" import { TrackingChannelEnum, @@ -28,9 +39,7 @@ export default async function BookingConfirmation({ if (!bookingConfirmation) { return notFound() } - const { booking, hotel, room } = bookingConfirmation - if (!room) { return notFound() } @@ -103,9 +112,53 @@ export default async function BookingConfirmation({ paymentStatus: "confirmed", } + const linkedReservations = await Promise.all( + // TODO: How to handle partial failure (e.g. one booking can't be fetched)? Need UX/UI + booking.linkedReservations.map(async (res) => { + const confirmation = await serverClient().booking.confirmation({ + confirmationNumber: res.confirmationNumber, + }) + return confirmation + }) + ) + return ( <> - + +
+ + + + + + + + +
+ + + +
+
+ +
v ?? []), hotelId: z.string(), packages: z.array(packageSchema).default([]), rateDefinition: rateDefinitionSchema, diff --git a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts index e1817b3ee..d883aab9e 100644 --- a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts +++ b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts @@ -1,5 +1,8 @@ import type { Room } from "@/types/hotel" -import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" +import type { + BookingConfirmation, + BookingConfirmationSchema, +} from "@/types/trpc/routers/booking/confirmation" export interface BookingConfirmationProps { confirmationNumber: string @@ -10,3 +13,7 @@ export interface ConfirmationProps extends BookingConfirmation { bedType: Room["roomTypes"][number] } } + +export interface BookingConfirmationAlertsProps { + booking: BookingConfirmationSchema +} \ No newline at end of file diff --git a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/paymentDetails.ts b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/paymentDetails.ts index c85b10608..39b2897e0 100644 --- a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/paymentDetails.ts +++ b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/paymentDetails.ts @@ -1,4 +1,6 @@ import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" export interface BookingConfirmationPaymentDetailsProps - extends Pick {} + extends Pick { + linkedReservations: (BookingConfirmation | null)[] +} diff --git a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/receipt.ts b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/receipt.ts index 04824d8df..a9fe1e86e 100644 --- a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/receipt.ts +++ b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/receipt.ts @@ -1,3 +1,22 @@ -import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" +import type { Room } from "@/types/hotel" +import type { + BookingConfirmation, + BookingConfirmationSchema, +} from "@/types/trpc/routers/booking/confirmation" export interface BookingConfirmationReceiptProps extends BookingConfirmation {} + +interface ReceiptRoom extends Room { + bedType: Room["roomTypes"][number] +} +export interface BookingConfirmationReceiptRoomsProps { + booking: BookingConfirmationSchema + room: ReceiptRoom + linkedReservations: (BookingConfirmation | null)[] +} + +export interface BookingConfirmationReceiptRoomProps { + booking: BookingConfirmationSchema + room: ReceiptRoom + roomNumber: number | null +} diff --git a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/rooms.ts b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/rooms.ts index db49e6d3b..67d33273b 100644 --- a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/rooms.ts +++ b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/rooms.ts @@ -2,15 +2,15 @@ import type { z } from "zod" import type { Room } from "@/types/hotel" import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" -import type { linkedReservationsSchema } from "@/server/routers/booking/output" +import type { linkedReservationSchema } from "@/server/routers/booking/output" export interface LinkedReservationSchema - extends z.output {} + extends z.output {} export interface BookingConfirmationRoomsProps extends Pick { mainRoom: Room & { bedType: Room["roomTypes"][number] } - linkedReservations?: LinkedReservationSchema[] + linkedReservations: LinkedReservationSchema[] }