From ec087a3d15fafb38f9a8313f89e6cfc0517a9713 Mon Sep 17 00:00:00 2001
From: Simon Emanuelsson
Date: Fri, 25 Apr 2025 14:08:14 +0200
Subject: [PATCH] feat: refactor of my stay
---
.../gla-payment-callback/page.tsx | 2 +-
.../hotelreservation/my-stay/layout.tsx | 14 -
.../hotelreservation/my-stay/page.module.css | 75 ++++
.../hotelreservation/my-stay/page.tsx | 233 ++++++++++-
.../hotelreservation/my-stay/layout.tsx | 14 -
.../hotelreservation/my-stay/page.module.css | 75 ++++
.../webview/hotelreservation/my-stay/page.tsx | 233 ++++++++++-
.../Rewards/Redeem/ConfirmClose.tsx | 2 +-
.../components/DatePicker/Range/Desktop.tsx | 3 +-
.../components/DatePicker/Range/Mobile.tsx | 3 +-
.../components/DatePicker/Single/Desktop.tsx | 3 +-
.../components/DatePicker/Single/Mobile.tsx | 3 +-
.../components/DatePicker/index.tsx | 13 -
.../components/DatePicker/locales.ts | 11 +
.../PriceDetails/index.tsx | 2 -
.../EnterDetails/Confirm/index.tsx | 4 +-
.../EnterDetails/Payment/PaymentClient.tsx | 4 +-
.../Payment => }/MySavedCards/index.tsx | 2 +-
.../MySavedCards/mySavedCards.module.css | 0
.../Steps/ConfirmationStep/index.tsx | 8 +-
.../Steps/DeliveryDetailsStep/index.tsx | 2 +-
.../AddAncillaryFlowModal/index.tsx | 19 +-
.../Ancillaries/GuaranteeCallback/index.tsx | 11 +-
.../MyStay/GuaranteeLateArrival/index.tsx | 239 -----------
.../MyStay/GuestDetails/Details.tsx | 315 ++++++++++++++
.../MyStay/GuestDetails/index.tsx | 324 +--------------
.../HotelReservation/MyStay/Header/index.tsx | 11 +-
.../Actions/AddToCalendarButton.tsx | 40 --
.../CancelStayPriceContainer/index.tsx | 40 --
.../Actions/CancelStay/Confirmation/index.tsx | 119 ------
.../CancelStay/FinalConfirmation/index.tsx | 36 --
.../Actions/CancelStay/hooks/useCancelStay.ts | 178 --------
.../ActionPanel/Actions/CancelStay/index.tsx | 136 ------
.../ActionPanel/Actions/CancelStay/utils.ts | 162 --------
.../Confirmation/confirmation.module.css | 32 --
.../Actions/ModifyStay/Confirmation/index.tsx | 165 --------
.../Actions/ModifyStay/hooks/useModifyStay.ts | 176 --------
.../ActionPanel/Actions/ModifyStay/index.tsx | 202 ---------
.../ActionPanel/actionPanel.module.css | 79 ----
.../MyStay/ManageStay/ActionPanel/index.tsx | 255 ------------
.../MyStay/ManageStay/ActionPanel/utils.ts | 42 --
.../MyStay/ManageStay/index.tsx | 102 -----
.../MyStay/ManageStay/manangeStay.module.css | 3 -
.../MyStay/MultiRoom/PriceType.tsx | 62 ---
.../HotelReservation/MyStay/Points/index.tsx | 38 --
.../HotelReservation/MyStay/Price/index.tsx | 39 --
.../MyStay/PriceDetails/index.tsx | 26 +-
.../MyStay/PriceDetails/mapToPrice.ts | 2 +-
.../index.tsx => PriceType/Cheques.tsx} | 14 +-
.../MyStay/PriceType/Points.tsx | 42 ++
.../MyStay/PriceType/Price/index.tsx | 21 +
.../{ => PriceType}/Price/price.module.css | 0
.../MyStay/PriceType/Vouchers.tsx | 42 ++
.../{PriceType.tsx => PriceType/index.tsx} | 43 +-
.../Actions/Cancelled/CustomerSupport.tsx | 19 +
.../Actions/Cancelled/cancelled.module.css | 18 +
.../ReferenceCard/Actions/Cancelled/index.tsx | 21 +
.../customerSupport.module.css | 30 ++
.../Actions/CustomerSupportModal/index.tsx | 78 ++++
.../AddToCalendar/AddToCalendarButton.tsx | 43 ++
.../Actions/AddToCalendar/button.module.css | 18 +
.../Actions/AddToCalendar/index.tsx | 57 +++
.../ManageStay/Actions/CancelStay/Alerts.tsx | 46 +++
.../Steps/CancelStayPriceContainer.tsx | 78 ++++
.../Steps/Confirmation/Multiroom/index.tsx | 111 +++++
.../Multiroom/multiroom.module.css | 74 ++++
.../Confirmation/confirmation.module.css | 9 +
.../CancelStay/Steps/Confirmation/index.tsx | 127 ++++++
.../finalConfirmation.module.css | 9 +
.../Steps/FinalConfirmation/index.tsx | 170 ++++++++
.../Actions/CancelStay/Steps/index.tsx | 60 +++
.../Actions/CancelStay/cancelStay.module.css | 0
.../ManageStay/Actions/CancelStay/index.tsx | 28 ++
.../ChangeDates/Alerts/CannotChangeDate.tsx | 42 ++
.../ChangeDates/Alerts/MultiRoomBooking.tsx | 42 ++
.../ChangeDates/Alerts/NotMainRoom.tsx | 42 ++
.../Actions/ChangeDates/Alerts/index.tsx | 31 ++
.../Steps/Confirmation/PriceAndDate/index.tsx | 64 +++
.../PriceAndDate/priceAndDate.module.css | 18 +
.../Confirmation/confirmation.module.css | 11 +
.../ChangeDates/Steps/Confirmation/index.tsx | 183 ++++++++
.../ChangeDates/Steps/Form/Alerts/Error.tsx | 21 +
.../Steps/Form/Alerts/NoAvailability.tsx | 21 +
.../CalendarButton/calendarButton.module.css | 0
.../Form}/NewDates/CalendarButton/index.tsx | 0
.../Steps/Form}/NewDates/index.tsx | 108 ++---
.../Steps/Form}/NewDates/newDates.module.css | 0
.../Actions/ChangeDates/Steps/Form/index.tsx | 85 ++++
.../Actions/ChangeDates/Steps/index.tsx | 136 ++++++
.../ManageStay/Actions/ChangeDates/index.tsx | 49 +++
.../Actions/CustomerSupport/index.tsx | 18 +
.../Form/form.module.css} | 88 ++--
.../GuaranteeLateArrival/Form/index.tsx | 194 +++++++++
.../GuaranteeLateArrival/Form}/schema.ts | 0
.../Actions/GuaranteeLateArrival/index.tsx | 69 ++++
.../Actions/ViewAndPrintReceipt/index.tsx | 56 +++
.../ViewAndPrintReceipt/view.module.css | 7 +
.../ManageStay/Actions/actions.module.css | 5 +
.../NotCancelled/ManageStay/Actions/index.tsx | 21 +
.../NotCancelled/ManageStay/Actions/utils.ts | 7 +
.../NotCancelled/ManageStay/Info/index.tsx | 44 ++
.../ManageStay/Info/info.module.css | 29 ++
.../Actions/NotCancelled/ManageStay/index.tsx | 65 +++
.../ManageStay/manageStay.module.css | 52 +++
.../Actions/NotCancelled/index.tsx | 34 ++
.../NotCancelled/notCancelled.module.css | 9 +
.../ReferenceCard/Actions/actions.module.css | 12 +
.../MyStay/ReferenceCard/Actions/index.tsx | 16 +
.../BookingCode/bookingCode.module.css} | 6 +-
.../ReferenceCard/BookingCode/index.tsx | 41 ++
.../Cancellations/cancellations.module.css | 16 +
.../ReferenceCard/Cancellations/index.tsx | 43 ++
.../ReferenceCard/Dates/dates.module.css | 15 +
.../MyStay/ReferenceCard/Dates/index.tsx | 53 +++
.../GuaranteeInfo/guaranteeInfo.module.css | 15 +
.../ReferenceCard/GuaranteeInfo/index.tsx | 43 ++
.../ReferenceCard/Guests/guests.module.css | 20 +
.../MyStay/ReferenceCard/Guests/index.tsx | 61 +++
.../Modal/Button/button.module.css | 14 +
.../ReferenceCard/Modal/Button/index.tsx | 31 ++
.../Modal/ModalContent/Body/body.module.css | 15 +
.../Modal/ModalContent/Body/index.tsx | 5 +
.../ModalContent/Footer/footer.module.css | 7 +
.../Modal/ModalContent/Footer/index.tsx | 64 +++
.../ModalContent/Header/header.module.css | 15 +
.../Modal/ModalContent/Header/index.tsx | 24 ++
.../Modal/ModalContent/index.tsx | 15 +
.../ModalContent/modalContent.module.css | 4 +
.../MyStay/ReferenceCard/Modal/index.tsx | 17 +
.../ReferenceCard/Modal/modal.module.css | 70 ++++
.../PriceContainer/index.tsx | 16 +-
.../PriceContainer/priceContainer.module.css | 0
.../MyStay/ReferenceCard/Reference/index.tsx | 49 +++
.../Reference/reference.module.css | 10 +
.../ReferenceCard/ReferenceCardSkeleton.tsx | 35 --
.../MyStay/ReferenceCard/Room/index.tsx | 43 ++
.../MyStay/ReferenceCard/Room/room.module.css | 15 +
.../MyStay/ReferenceCard/index.tsx | 391 +-----------------
.../ReferenceCard/referenceCard.module.css | 75 +---
.../MultiRoom/MultiRoomSkeleton.tsx | 0
.../{ => Rooms}/MultiRoom/ToggleSidePeek.tsx | 0
.../MyStay/{ => Rooms}/MultiRoom/index.tsx | 245 ++++-------
.../MultiRoom/multiRoom.module.css | 0
.../MultiRoom/toggleSidePeek.module.css | 0
.../{ => Rooms}/SingleRoom/ToggleSidePeek.tsx | 0
.../MyStay/{ => Rooms}/SingleRoom/index.tsx | 221 +++++-----
.../{ => Rooms}/SingleRoom/room.module.css | 18 +
.../MyStay/Rooms/TotalPrice.tsx | 26 ++
.../MyStay/Rooms/TotalPrice/index.tsx | 80 ----
.../HotelReservation/MyStay/Rooms/index.tsx | 74 +---
.../MyStay/SingleRoom/PriceType.tsx | 63 ---
.../index.tsx => TrackGuarantee.tsx} | 20 +-
.../HotelReservation/MyStay/index.tsx | 192 ---------
.../HotelReservation/MyStay/myStay.module.css | 75 +---
.../utils.ts => utils/ancillaries.ts} | 2 +-
.../MyStay/utils/mapRoomDetails.ts | 186 +++++----
.../Payment => }/PaymentOption/index.tsx | 0
.../PaymentOption/paymentOption.module.css | 0
.../PaymentOption/paymentOption.ts | 0
.../PriceDetailsTable/Breakfast.tsx | 2 +-
.../PriceDetailsTable/index.tsx | 2 +-
.../RoomPackageFilter/Form/index.tsx | 2 +-
.../HotelReservation/SidePeek/index.tsx | 23 +-
.../components/ImageGallery/index.tsx | 2 +-
apps/scandic-web/components/Modal/modal.ts | 2 +-
.../ParkingPrices/index.tsx | 1 +
.../SidePeeks/BookedRoomSidePeek/index.tsx | 102 +++--
.../TempDesignSystem/Form/Checkbox/index.tsx | 9 +-
apps/scandic-web/contexts/MyStay.ts | 5 +
.../hooks/booking/useGuaranteeBooking.ts | 24 +-
apps/scandic-web/lib/dt.ts | 2 +
.../lib/trpc/memoizedRequests/index.ts | 7 +
.../providers/AddAncillaryProvider.tsx | 2 +-
apps/scandic-web/providers/MyStay.tsx | 110 +++++
.../material-symbols/rounded-112272ae.woff2 | Bin 34088 -> 0 bytes
.../material-symbols/rounded-a03ed056.woff2 | Bin 0 -> 34644 bytes
.../server/routers/booking/input.ts | 15 +
.../server/routers/booking/mutation.ts | 70 ++--
.../server/routers/booking/output.ts | 4 +-
.../server/routers/booking/query.ts | 117 ++++--
.../server/routers/booking/utils.ts | 101 +++++
.../server/routers/hotels/utils.ts | 1 +
.../stores/my-stay/add-ancillary-flow.ts | 14 +-
apps/scandic-web/stores/my-stay/helpers.ts | 77 ++++
apps/scandic-web/stores/my-stay/index.ts | 106 +++++
.../stores/my-stay/manageStayStore.ts | 50 ---
.../stores/my-stay/myStayRoomDetailsStore.ts | 198 ---------
.../stores/my-stay/myStayTotalPrice.ts | 75 ----
apps/scandic-web/stores/sidepeek.ts | 6 +-
.../types/components/blocks/surprises.ts | 4 +-
.../types/components/datepicker.ts | 6 -
.../types/components/header/headerLink.ts | 2 +-
.../bookingConfirmation.ts | 3 +-
.../hotelReservation/myStay/cancelStay.ts | 21 +-
.../myStay/{modifyDate.ts => changeDates.ts} | 14 +-
.../hotelReservation/toggleSidePeekProps.ts | 4 +-
.../components/myPages/myStay/ancillaries.ts | 2 +-
.../sidePeeks/bookedRoomSidePeek.ts | 4 +-
apps/scandic-web/types/contexts/my-stay.ts | 3 +
apps/scandic-web/types/hotel.ts | 2 +
apps/scandic-web/types/stores/my-stay.ts | 89 ++++
apps/scandic-web/types/stores/rates.ts | 6 +-
.../trpc/routers/booking/confirmation.ts | 5 +-
.../lib/components/RateCard/Modal/modal.ts | 2 +-
packages/design-system/lib/fonts.css | 2 +-
.../material-symbols/rounded-112272ae.woff2 | Bin 34088 -> 0 bytes
.../material-symbols/rounded-a03ed056.woff2 | Bin 0 -> 34644 bytes
scripts/material-symbols-update.mjs | 3 +
208 files changed, 5458 insertions(+), 4569 deletions(-)
delete mode 100644 apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/layout.tsx
create mode 100644 apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.module.css
delete mode 100644 apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/layout.tsx
create mode 100644 apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.module.css
create mode 100644 apps/scandic-web/components/DatePicker/locales.ts
rename apps/scandic-web/components/HotelReservation/{EnterDetails/Payment => }/MySavedCards/index.tsx (93%)
rename apps/scandic-web/components/HotelReservation/{EnterDetails/Payment => }/MySavedCards/mySavedCards.module.css (100%)
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/Details.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/AddToCalendarButton.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/CancelStayPriceContainer/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/Confirmation/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/FinalConfirmation/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/hooks/useCancelStay.ts
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/utils.ts
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/confirmation.module.css
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/hooks/useModifyStay.ts
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/actionPanel.module.css
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/utils.ts
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ManageStay/manangeStay.module.css
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/PriceType.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/Points/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/Price/index.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{Cheques/index.tsx => PriceType/Cheques.tsx} (63%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/PriceType/Points.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/PriceType/Price/index.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{ => PriceType}/Price/price.module.css (100%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{PriceType.tsx => PriceType/index.tsx} (59%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/CustomerSupport.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/cancelled.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/customerSupport.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/AddToCalendarButton.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/button.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Alerts.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/CancelStayPriceContainer.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/confirmation.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/finalConfirmation.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/index.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{ManageStay/ActionPanel => ReferenceCard/Actions/NotCancelled/ManageStay}/Actions/CancelStay/cancelStay.module.css (100%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/CannotChangeDate.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/MultiRoomBooking.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/NotMainRoom.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/priceAndDate.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/confirmation.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/Error.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/NoAvailability.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{ManageStay/ActionPanel/Actions/ModifyStay => ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form}/NewDates/CalendarButton/calendarButton.module.css (100%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ManageStay/ActionPanel/Actions/ModifyStay => ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form}/NewDates/CalendarButton/index.tsx (100%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ManageStay/ActionPanel/Actions/ModifyStay => ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form}/NewDates/index.tsx (64%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ManageStay/ActionPanel/Actions/ModifyStay => ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form}/NewDates/newDates.module.css (100%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CustomerSupport/index.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{GuaranteeLateArrival/guaranteeLateArrival.module.css => ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/form.module.css} (54%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/index.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{GuaranteeLateArrival => ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form}/schema.ts (100%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/view.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/actions.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/utils.ts
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/info.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/manageStay.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/notCancelled.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/actions.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/index.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{Rooms/TotalPrice/totalPrice.module.css => ReferenceCard/BookingCode/bookingCode.module.css} (50%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/BookingCode/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/cancellations.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/dates.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/guaranteeInfo.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/guests.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/button.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/body.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/footer.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/header.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/modalContent.module.css
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/modal.module.css
rename apps/scandic-web/components/HotelReservation/MyStay/{ManageStay/ActionPanel => ReferenceCard}/PriceContainer/index.tsx (92%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ManageStay/ActionPanel => ReferenceCard}/PriceContainer/priceContainer.module.css (100%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/reference.module.css
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/ReferenceCardSkeleton.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/index.tsx
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/room.module.css
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/MultiRoom/MultiRoomSkeleton.tsx (100%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/MultiRoom/ToggleSidePeek.tsx (100%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/MultiRoom/index.tsx (55%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/MultiRoom/multiRoom.module.css (100%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/MultiRoom/toggleSidePeek.module.css (100%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/SingleRoom/ToggleSidePeek.tsx (100%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/SingleRoom/index.tsx (74%)
rename apps/scandic-web/components/HotelReservation/MyStay/{ => Rooms}/SingleRoom/room.module.css (99%)
create mode 100644 apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice/index.tsx
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/PriceType.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{GuaranteeLateArrival/GuaranteeLateArrivalCallback/index.tsx => TrackGuarantee.tsx} (90%)
delete mode 100644 apps/scandic-web/components/HotelReservation/MyStay/index.tsx
rename apps/scandic-web/components/HotelReservation/MyStay/{Ancillaries/utils.ts => utils/ancillaries.ts} (94%)
rename apps/scandic-web/components/HotelReservation/{EnterDetails/Payment => }/PaymentOption/index.tsx (100%)
rename apps/scandic-web/components/HotelReservation/{EnterDetails/Payment => }/PaymentOption/paymentOption.module.css (100%)
rename apps/scandic-web/components/HotelReservation/{EnterDetails/Payment => }/PaymentOption/paymentOption.ts (100%)
create mode 100644 apps/scandic-web/contexts/MyStay.ts
create mode 100644 apps/scandic-web/providers/MyStay.tsx
delete mode 100644 apps/scandic-web/public/_static/fonts/material-symbols/rounded-112272ae.woff2
create mode 100644 apps/scandic-web/public/_static/fonts/material-symbols/rounded-a03ed056.woff2
create mode 100644 apps/scandic-web/stores/my-stay/helpers.ts
create mode 100644 apps/scandic-web/stores/my-stay/index.ts
delete mode 100644 apps/scandic-web/stores/my-stay/manageStayStore.ts
delete mode 100644 apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts
delete mode 100644 apps/scandic-web/stores/my-stay/myStayTotalPrice.ts
rename apps/scandic-web/types/components/hotelReservation/myStay/{modifyDate.ts => changeDates.ts} (62%)
create mode 100644 apps/scandic-web/types/contexts/my-stay.ts
create mode 100644 apps/scandic-web/types/stores/my-stay.ts
delete mode 100644 packages/design-system/public/_static/fonts/material-symbols/rounded-112272ae.woff2
create mode 100644 packages/design-system/public/_static/fonts/material-symbols/rounded-a03ed056.woff2
diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx
index 5c4a83629..571a44d89 100644
--- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx
+++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/gla-payment-callback/page.tsx
@@ -8,7 +8,7 @@ import { myStay } from "@/constants/routes/myStay"
import { serverClient } from "@/lib/trpc/server"
import GuaranteeCallback from "@/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback"
-import TrackGuarantee from "@/components/HotelReservation/MyStay/GuaranteeLateArrival/GuaranteeLateArrivalCallback"
+import TrackGuarantee from "@/components/HotelReservation/MyStay/TrackGuarantee"
import LoadingSpinner from "@/components/LoadingSpinner"
import type { LangParams, PageArgs } from "@/types/params"
diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/layout.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/layout.tsx
deleted file mode 100644
index c7bc50ba5..000000000
--- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/layout.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import SidePeek from "@/components/HotelReservation/SidePeek"
-
-import type { LangParams, LayoutArgs } from "@/types/params"
-
-export default function HotelReservationLayout({
- children,
-}: React.PropsWithChildren>) {
- return (
-
- {children}
-
-
- )
-}
diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.module.css b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.module.css
new file mode 100644
index 000000000..774114c7d
--- /dev/null
+++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.module.css
@@ -0,0 +1,75 @@
+.main {
+ background-color: var(--Base-Surface-Primary-light-Normal);
+}
+
+.imageContainer {
+ position: absolute;
+ width: 100%;
+ height: 480px;
+}
+
+.blurOverlay {
+ position: absolute;
+ inset: 0;
+ backdrop-filter: blur(12px);
+ pointer-events: none;
+ z-index: 1;
+ mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, transparent 100%);
+ background: linear-gradient(
+ to bottom,
+ rgba(0, 0, 0, 0.5) 0%,
+ transparent 100%
+ );
+}
+
+.image {
+ object-fit: cover;
+ object-position: center;
+}
+
+.headerContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x4);
+}
+
+.content {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 80px;
+ margin: 0 auto;
+ position: relative;
+ z-index: 2;
+ padding-bottom: var(--Spacing-x3);
+}
+
+.form {
+ max-width: 640px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: var(--Spacing-x5) 0;
+}
+
+.section {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x2);
+ padding: 0 var(--Spacing-x2);
+}
+
+.logIn {
+ padding: var(--Spacing-x9) var(--Spacing-x2);
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x2);
+ align-items: center;
+ color: var(--Scandic-Grey-100);
+}
+
+@media (min-width: 768px) {
+ .content {
+ width: var(--max-width-content);
+ padding-bottom: 160px;
+ }
+}
diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx
index aa1ec16aa..3308028c0 100644
--- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx
+++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx
@@ -1,21 +1,234 @@
+import { cookies } from "next/headers"
import { notFound } from "next/navigation"
-import { Suspense } from "react"
-import { MyStay } from "@/components/HotelReservation/MyStay"
-import { MyStaySkeleton } from "@/components/HotelReservation/MyStay/myStaySkeleton"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+import { env } from "@/env/server"
+import { dt } from "@/lib/dt"
+import {
+ getAncillaryPackages,
+ getBookingConfirmation,
+ getLinkedReservations,
+ getPackages,
+ getProfileSafely,
+ getSavedPaymentCardsSafely,
+} from "@/lib/trpc/memoizedRequests"
+import { decrypt } from "@/server/routers/utils/encryption"
+
+import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
+import accessBooking, {
+ ACCESS_GRANTED,
+ ERROR_BAD_REQUEST,
+ ERROR_UNAUTHORIZED,
+} from "@/components/HotelReservation/MyStay/accessBooking"
+import { Ancillaries } from "@/components/HotelReservation/MyStay/Ancillaries"
+import BookingSummary from "@/components/HotelReservation/MyStay/BookingSummary"
+import { Header } from "@/components/HotelReservation/MyStay/Header"
+import Promo from "@/components/HotelReservation/MyStay/Promo"
+import { ReferenceCard } from "@/components/HotelReservation/MyStay/ReferenceCard"
+import Rooms from "@/components/HotelReservation/MyStay/Rooms"
+import SidePeek from "@/components/HotelReservation/SidePeek"
+import Image from "@/components/Image"
+import { getIntl } from "@/i18n"
+import { setLang } from "@/i18n/serverContext"
+import MyStayProvider from "@/providers/MyStay"
+import { getCurrentWebUrl } from "@/utils/url"
+
+import styles from "./page.module.css"
+
+import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import type { LangParams, PageArgs } from "@/types/params"
-export default function MyStayPage({
+export default async function MyStay({
+ params,
searchParams,
}: PageArgs) {
- if (!searchParams.RefId) {
+ setLang(params.lang)
+ const refId = searchParams.RefId
+
+ if (!refId) {
notFound()
}
- return (
- }>
-
-
- )
+ const value = decrypt(refId)
+ if (!value) {
+ return notFound()
+ }
+
+ const [confirmationNumber, lastName] = value.split(",")
+ const bookingConfirmation = await getBookingConfirmation(confirmationNumber)
+ if (!bookingConfirmation) {
+ return notFound()
+ }
+
+ const { additionalData, booking, hotel, roomCategories } = bookingConfirmation
+
+ const user = await getProfileSafely()
+ const bv = cookies().get("bv")?.value
+ const intl = await getIntl()
+
+ const access = accessBooking(booking.guest, lastName, user, bv)
+
+ if (access === ACCESS_GRANTED) {
+ const lang = params.lang
+ const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
+ const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
+
+ const linkedReservationsPromise = getLinkedReservations({
+ rooms: booking.linkedReservations,
+ })
+
+ const packagesInput = {
+ adults: booking.adults,
+ children: booking.childrenAges.length,
+ endDate: toDate,
+ hotelId: hotel.operaId,
+ lang,
+ startDate: fromDate,
+ packageCodes: [
+ BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
+ BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
+ BreakfastPackageEnum.FREE_CHILD_BREAKFAST,
+ ],
+ }
+ const supportedCards = hotel.merchantInformationData.cards
+ const savedPaymentCardsInput = { supportedCards }
+
+ const hasBreakfastPackage = booking.packages.find(
+ (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
+ )
+ const breakfastIncluded = booking.rateDefinition.breakfastIncluded
+ const shouldFetchBreakfastPackages =
+ !hasBreakfastPackage && !breakfastIncluded
+ if (shouldFetchBreakfastPackages) {
+ void getPackages(packagesInput)
+ }
+ void getSavedPaymentCardsSafely(savedPaymentCardsInput)
+
+ const ancillaryPackages = await getAncillaryPackages({
+ fromDate,
+ hotelId: hotel.operaId,
+ toDate,
+ })
+
+ let breakfastPackages = null
+ if (shouldFetchBreakfastPackages) {
+ breakfastPackages = await getPackages(packagesInput)
+ }
+ const savedCreditCards = await getSavedPaymentCardsSafely(
+ savedPaymentCardsInput
+ )
+
+ const imageSrc =
+ hotel.hotelContent.images.imageSizes.large ??
+ additionalData.gallery?.heroImages[0]?.imageSizes.large ??
+ hotel.galleryImages[0]?.imageSizes.large
+
+ const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
+ const promoUrl = env.HIDE_FOR_NEXT_RELEASE
+ ? new URL(getCurrentWebUrl({ path: "/", lang }))
+ : new URL(`${baseUrl}/${lang}/`)
+
+ promoUrl.searchParams.set("hotel", hotel.operaId)
+
+ return (
+
+
+
+
+ {imageSrc && (
+
+ )}
+
+
+
+
+
+
+ {booking.showAncillaries && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ if (access === ERROR_BAD_REQUEST) {
+ return (
+
+
+
+ )
+ }
+
+ if (access === ERROR_UNAUTHORIZED) {
+ return (
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "You need to be logged in to view your booking",
+ })}
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage:
+ "And you need to be logged in with the same member account that made the booking.",
+ })}
+
+
+
+
+ )
+ }
+
+ return notFound()
}
diff --git a/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/layout.tsx b/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/layout.tsx
deleted file mode 100644
index 97481c9bf..000000000
--- a/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/layout.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import SidePeek from "@/components/HotelReservation/SidePeek"
-
-import type { LangParams, LayoutArgs } from "@/types/params"
-
-export default function HotelReservationLayout({
- children,
-}: React.PropsWithChildren>) {
- return (
- <>
- {children}
-
- >
- )
-}
diff --git a/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.module.css b/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.module.css
new file mode 100644
index 000000000..774114c7d
--- /dev/null
+++ b/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.module.css
@@ -0,0 +1,75 @@
+.main {
+ background-color: var(--Base-Surface-Primary-light-Normal);
+}
+
+.imageContainer {
+ position: absolute;
+ width: 100%;
+ height: 480px;
+}
+
+.blurOverlay {
+ position: absolute;
+ inset: 0;
+ backdrop-filter: blur(12px);
+ pointer-events: none;
+ z-index: 1;
+ mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, transparent 100%);
+ background: linear-gradient(
+ to bottom,
+ rgba(0, 0, 0, 0.5) 0%,
+ transparent 100%
+ );
+}
+
+.image {
+ object-fit: cover;
+ object-position: center;
+}
+
+.headerContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x4);
+}
+
+.content {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 80px;
+ margin: 0 auto;
+ position: relative;
+ z-index: 2;
+ padding-bottom: var(--Spacing-x3);
+}
+
+.form {
+ max-width: 640px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: var(--Spacing-x5) 0;
+}
+
+.section {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x2);
+ padding: 0 var(--Spacing-x2);
+}
+
+.logIn {
+ padding: var(--Spacing-x9) var(--Spacing-x2);
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x2);
+ align-items: center;
+ color: var(--Scandic-Grey-100);
+}
+
+@media (min-width: 768px) {
+ .content {
+ width: var(--max-width-content);
+ padding-bottom: 160px;
+ }
+}
diff --git a/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx b/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx
index aa1ec16aa..e083c1cde 100644
--- a/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx
+++ b/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx
@@ -1,21 +1,234 @@
+import { cookies } from "next/headers"
import { notFound } from "next/navigation"
-import { Suspense } from "react"
-import { MyStay } from "@/components/HotelReservation/MyStay"
-import { MyStaySkeleton } from "@/components/HotelReservation/MyStay/myStaySkeleton"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+import { env } from "@/env/server"
+import { dt } from "@/lib/dt"
+import {
+ getAncillaryPackages,
+ getBookingConfirmation,
+ getLinkedReservations,
+ getPackages,
+ getProfileSafely,
+ getSavedPaymentCardsSafely,
+} from "@/lib/trpc/memoizedRequests"
+import { decrypt } from "@/server/routers/utils/encryption"
+
+import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm"
+import accessBooking, {
+ ACCESS_GRANTED,
+ ERROR_BAD_REQUEST,
+ ERROR_UNAUTHORIZED,
+} from "@/components/HotelReservation/MyStay/accessBooking"
+import { Ancillaries } from "@/components/HotelReservation/MyStay/Ancillaries"
+import BookingSummary from "@/components/HotelReservation/MyStay/BookingSummary"
+import { Header } from "@/components/HotelReservation/MyStay/Header"
+import Promo from "@/components/HotelReservation/MyStay/Promo"
+import { ReferenceCard } from "@/components/HotelReservation/MyStay/ReferenceCard"
+import Rooms from "@/components/HotelReservation/MyStay/Rooms"
+import SidePeek from "@/components/HotelReservation/SidePeek"
+import Image from "@/components/Image"
+import { getIntl } from "@/i18n"
+import { setLang } from "@/i18n/serverContext"
+import MyStayProvider from "@/providers/MyStay"
+import { getCurrentWebUrl } from "@/utils/url"
+
+import styles from "./page.module.css"
+
+import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import type { LangParams, PageArgs } from "@/types/params"
-export default function MyStayPage({
+export default async function MyStay({
+ params,
searchParams,
}: PageArgs) {
- if (!searchParams.RefId) {
+ setLang(params.lang)
+ const refId = searchParams.RefId
+
+ if (!refId) {
notFound()
}
- return (
- }>
-
-
- )
+ const value = decrypt(refId)
+ if (!value) {
+ return notFound()
+ }
+
+ const [confirmationNumber, lastName] = value.split(",")
+ const bookingConfirmation = await getBookingConfirmation(confirmationNumber)
+ if (!bookingConfirmation) {
+ return notFound()
+ }
+
+ const { additionalData, booking, hotel, roomCategories } = bookingConfirmation
+
+ const user = await getProfileSafely()
+ const bv = cookies().get("bv")?.value
+ const intl = await getIntl()
+
+ const access = accessBooking(booking.guest, lastName, user, bv)
+
+ if (access === ACCESS_GRANTED) {
+ const lang = params.lang
+ const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
+ const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
+
+ const linkedReservationsPromise = getLinkedReservations({
+ rooms: booking.linkedReservations,
+ })
+
+ const packagesInput = {
+ adults: booking.adults,
+ children: booking.childrenAges.length,
+ endDate: toDate,
+ hotelId: hotel.operaId,
+ lang,
+ startDate: fromDate,
+ packageCodes: [
+ BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
+ BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
+ BreakfastPackageEnum.FREE_CHILD_BREAKFAST,
+ ],
+ }
+ const supportedCards = hotel.merchantInformationData.cards
+ const savedPaymentCardsInput = { supportedCards }
+
+ const hasBreakfastPackage = booking.packages.find(
+ (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
+ )
+ const breakfastIncluded = booking.rateDefinition.breakfastIncluded
+ const alreadyHasABreakfastSelection =
+ !hasBreakfastPackage && !breakfastIncluded
+ if (alreadyHasABreakfastSelection) {
+ void getPackages(packagesInput)
+ }
+ void getSavedPaymentCardsSafely(savedPaymentCardsInput)
+
+ const ancillaryPackages = await getAncillaryPackages({
+ fromDate,
+ hotelId: hotel.operaId,
+ toDate,
+ })
+
+ let breakfastPackages = null
+ if (alreadyHasABreakfastSelection) {
+ breakfastPackages = await getPackages(packagesInput)
+ }
+ const savedCreditCards = await getSavedPaymentCardsSafely(
+ savedPaymentCardsInput
+ )
+
+ const imageSrc =
+ hotel.hotelContent.images.imageSizes.large ??
+ additionalData.gallery?.heroImages[0]?.imageSizes.large ??
+ hotel.galleryImages[0]?.imageSizes.large
+
+ const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
+ const promoUrl = env.HIDE_FOR_NEXT_RELEASE
+ ? new URL(getCurrentWebUrl({ path: "/", lang }))
+ : new URL(`${baseUrl}/${lang}/`)
+
+ promoUrl.searchParams.set("hotel", hotel.operaId)
+
+ return (
+
+
+
+
+ {imageSrc && (
+
+ )}
+
+
+
+
+
+
+ {booking.showAncillaries && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ if (access === ERROR_BAD_REQUEST) {
+ return (
+
+
+
+ )
+ }
+
+ if (access === ERROR_UNAUTHORIZED) {
+ return (
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "You need to be logged in to view your booking",
+ })}
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage:
+ "And you need to be logged in with the same member account that made the booking.",
+ })}
+
+
+
+
+ )
+ }
+
+ return notFound()
}
diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Rewards/Redeem/ConfirmClose.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Rewards/Redeem/ConfirmClose.tsx
index b1f9ad451..9417ead55 100644
--- a/apps/scandic-web/components/Blocks/DynamicContent/Rewards/Redeem/ConfirmClose.tsx
+++ b/apps/scandic-web/components/Blocks/DynamicContent/Rewards/Redeem/ConfirmClose.tsx
@@ -8,7 +8,7 @@ import useRedeemFlow from "./useRedeemFlow"
import styles from "./redeem.module.css"
-export function ConfirmClose({ close }: { close: VoidFunction }) {
+export function ConfirmClose({ close }: { close: () => void }) {
const intl = useIntl()
const { setRedeemStep } = useRedeemFlow()
diff --git a/apps/scandic-web/components/DatePicker/Range/Desktop.tsx b/apps/scandic-web/components/DatePicker/Range/Desktop.tsx
index 4941dd3c4..ff1b235c8 100644
--- a/apps/scandic-web/components/DatePicker/Range/Desktop.tsx
+++ b/apps/scandic-web/components/DatePicker/Range/Desktop.tsx
@@ -15,6 +15,8 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
+import { locales } from "../locales"
+
import styles from "./desktop.module.css"
import classNames from "react-day-picker/style.module.css"
@@ -23,7 +25,6 @@ import type { DatePickerRangeProps } from "@/types/components/datepicker"
export default function DatePickerRangeDesktop({
close,
handleOnSelect,
- locales,
selectedRange,
}: DatePickerRangeProps) {
const lang = useLang()
diff --git a/apps/scandic-web/components/DatePicker/Range/Mobile.tsx b/apps/scandic-web/components/DatePicker/Range/Mobile.tsx
index 2b678c64c..0079b095d 100644
--- a/apps/scandic-web/components/DatePicker/Range/Mobile.tsx
+++ b/apps/scandic-web/components/DatePicker/Range/Mobile.tsx
@@ -12,6 +12,8 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
+import { locales } from "../locales"
+
import styles from "./mobile.module.css"
import classNames from "react-day-picker/style.module.css"
@@ -20,7 +22,6 @@ import type { DatePickerRangeProps } from "@/types/components/datepicker"
export default function DatePickerRangeMobile({
close,
handleOnSelect,
- locales,
selectedRange,
}: DatePickerRangeProps) {
const lang = useLang()
diff --git a/apps/scandic-web/components/DatePicker/Single/Desktop.tsx b/apps/scandic-web/components/DatePicker/Single/Desktop.tsx
index a249ed8c2..3f1f7e74f 100644
--- a/apps/scandic-web/components/DatePicker/Single/Desktop.tsx
+++ b/apps/scandic-web/components/DatePicker/Single/Desktop.tsx
@@ -15,6 +15,8 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
+import { locales } from "../locales"
+
import styles from "./desktop.module.css"
import classNames from "react-day-picker/style.module.css"
@@ -23,7 +25,6 @@ import type { DatePickerSingleProps } from "@/types/components/datepicker"
export default function DatePickerSingleDesktop({
close,
handleOnSelect,
- locales,
selectedDate,
startMonth,
}: DatePickerSingleProps) {
diff --git a/apps/scandic-web/components/DatePicker/Single/Mobile.tsx b/apps/scandic-web/components/DatePicker/Single/Mobile.tsx
index aad227952..f224f7a7c 100644
--- a/apps/scandic-web/components/DatePicker/Single/Mobile.tsx
+++ b/apps/scandic-web/components/DatePicker/Single/Mobile.tsx
@@ -10,6 +10,8 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
+import { locales } from "../locales"
+
import styles from "./mobile.module.css"
import classNames from "react-day-picker/style.module.css"
@@ -18,7 +20,6 @@ import type { DatePickerSingleProps } from "@/types/components/datepicker"
export default function DatePickerSingleMobile({
close,
handleOnSelect,
- locales,
selectedDate,
hideHeader,
}: DatePickerSingleProps) {
diff --git a/apps/scandic-web/components/DatePicker/index.tsx b/apps/scandic-web/components/DatePicker/index.tsx
index 0bd89e004..95fe4c72b 100644
--- a/apps/scandic-web/components/DatePicker/index.tsx
+++ b/apps/scandic-web/components/DatePicker/index.tsx
@@ -1,11 +1,8 @@
"use client"
-
-import { da, de, fi, nb, sv } from "date-fns/locale"
import { useCallback, useEffect, useRef, useState } from "react"
import { useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
-import { Lang } from "@/constants/languages"
import { dt } from "@/lib/dt"
import Body from "@/components/TempDesignSystem/Text/Body"
@@ -20,14 +17,6 @@ import type { DateRange } from "react-day-picker"
import type { DatePickerFormProps } from "@/types/components/datepicker"
-const locales = {
- [Lang.da]: da,
- [Lang.de]: de,
- [Lang.fi]: fi,
- [Lang.no]: nb,
- [Lang.sv]: sv,
-}
-
export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
const lang = useLang()
const intl = useIntl()
@@ -163,7 +152,6 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
(
(total, room) => {
if (!room) {
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Confirm/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Confirm/index.tsx
index 9897f8cd8..9ee4bdc3d 100644
--- a/apps/scandic-web/components/HotelReservation/EnterDetails/Confirm/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Confirm/index.tsx
@@ -10,13 +10,13 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import { PaymentMethodEnum } from "@/constants/booking"
+import MySavedCards from "@/components/HotelReservation/MySavedCards"
+import PaymentOption from "@/components/HotelReservation/PaymentOption"
import Modal from "@/components/Modal"
import Divider from "@/components/TempDesignSystem/Divider"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import { trackPaymentSectionOpen } from "@/utils/tracking/booking"
-import MySavedCards from "../Payment/MySavedCards"
-import PaymentOption from "../Payment/PaymentOption"
import PaymentOptionsGroup from "../Payment/PaymentOptionsGroup"
import TermsAndConditions from "../Payment/TermsAndConditions"
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx
index 924ab2e85..769745f2e 100644
--- a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx
+++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx
@@ -22,6 +22,8 @@ import { env } from "@/env/client"
import { trpc } from "@/lib/trpc/client"
import { useEnterDetailsStore } from "@/stores/enter-details"
+import MySavedCards from "@/components/HotelReservation/MySavedCards"
+import PaymentOption from "@/components/HotelReservation/PaymentOption"
import LoadingSpinner from "@/components/LoadingSpinner"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
@@ -40,9 +42,7 @@ import { writeGlaToSessionStorage } from "./PaymentCallback/helpers"
import GuaranteeDetails from "./GuaranteeDetails"
import { hasFlexibleRate, hasPrepaidRate, isPaymentMethodEnum } from "./helpers"
import MixedRatePaymentBreakdown from "./MixedRatePaymentBreakdown"
-import MySavedCards from "./MySavedCards"
import PaymentAlert from "./PaymentAlert"
-import PaymentOption from "./PaymentOption"
import PaymentOptionsGroup from "./PaymentOptionsGroup"
import { type PaymentFormData, paymentSchema } from "./schema"
import TermsAndConditions from "./TermsAndConditions"
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/MySavedCards/index.tsx b/apps/scandic-web/components/HotelReservation/MySavedCards/index.tsx
similarity index 93%
rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/MySavedCards/index.tsx
rename to apps/scandic-web/components/HotelReservation/MySavedCards/index.tsx
index a90330637..8d2bca089 100644
--- a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/MySavedCards/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MySavedCards/index.tsx
@@ -5,8 +5,8 @@ import {
type PaymentMethodEnum,
} from "@/constants/booking"
+import PaymentOptionsGroup from "../EnterDetails/Payment/PaymentOptionsGroup"
import PaymentOption from "../PaymentOption"
-import PaymentOptionsGroup from "../PaymentOptionsGroup"
import styles from "./mySavedCards.module.css"
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/MySavedCards/mySavedCards.module.css b/apps/scandic-web/components/HotelReservation/MySavedCards/mySavedCards.module.css
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/MySavedCards/mySavedCards.module.css
rename to apps/scandic-web/components/HotelReservation/MySavedCards/mySavedCards.module.css
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/ConfirmationStep/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/ConfirmationStep/index.tsx
index 7eebb833d..e52f18e24 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/ConfirmationStep/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/ConfirmationStep/index.tsx
@@ -12,9 +12,9 @@ import {
import { dt } from "@/lib/dt"
import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow"
-import MySavedCards from "@/components/HotelReservation/EnterDetails/Payment/MySavedCards"
-import PaymentOption from "@/components/HotelReservation/EnterDetails/Payment/PaymentOption"
import PaymentOptionsGroup from "@/components/HotelReservation/EnterDetails/Payment/PaymentOptionsGroup"
+import MySavedCards from "@/components/HotelReservation/MySavedCards"
+import PaymentOption from "@/components/HotelReservation/PaymentOption"
import Alert from "@/components/TempDesignSystem/Alert"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import Link from "@/components/TempDesignSystem/Link"
@@ -144,8 +144,8 @@ export default function ConfirmationStep({
label={
savedCreditCards?.length
? intl.formatMessage({
- defaultMessage: "OTHER",
- })
+ defaultMessage: "OTHER",
+ })
: undefined
}
>
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/DeliveryDetailsStep/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/DeliveryDetailsStep/index.tsx
index b3a134832..78b1a0c28 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/DeliveryDetailsStep/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/Steps/DeliveryDetailsStep/index.tsx
@@ -1,6 +1,6 @@
import { useIntl } from "react-intl"
-import { generateDeliveryOptions } from "@/components/HotelReservation/MyStay/Ancillaries/utils"
+import { generateDeliveryOptions } from "@/components/HotelReservation/MyStay/utils/ancillaries"
import Input from "@/components/TempDesignSystem/Form/Input"
import Select from "@/components/TempDesignSystem/Form/Select"
import Body from "@/components/TempDesignSystem/Text/Body"
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/index.tsx
index c4314d3c9..c8236285b 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/index.tsx
@@ -19,6 +19,13 @@ import {
useAddAncillaryStore,
} from "@/stores/my-stay/add-ancillary-flow"
+import {
+ buildAncillaryPackages,
+ clearAncillarySessionData,
+ generateDeliveryOptions,
+ getAncillarySessionData,
+ setAncillarySessionData,
+} from "@/components/HotelReservation/MyStay/utils/ancillaries"
import Image from "@/components/Image"
import LoadingSpinner from "@/components/LoadingSpinner"
import Modal from "@/components/Modal"
@@ -33,13 +40,6 @@ import {
trackGlaAncillaryAttempt,
} from "@/utils/tracking/myStay"
-import {
- buildAncillaryPackages,
- clearAncillarySessionData,
- generateDeliveryOptions,
- getAncillarySessionData,
- setAncillarySessionData,
-} from "../../utils"
import { type AncillaryFormData, ancillaryFormSchema } from "../schema"
import ActionButtons from "./ActionButtons"
import PriceDetails from "./PriceDetails"
@@ -124,10 +124,7 @@ export default function AddAncillaryFlowModal({
const addAncillary = trpc.booking.packages.useMutation()
const { guaranteeBooking, isLoading, handleGuaranteeError } =
- useGuaranteeBooking({
- confirmationNumber: booking.confirmationNumber,
- isAncillaryFlow: true,
- })
+ useGuaranteeBooking(booking.confirmationNumber, true)
function validateTermsAndConditions(data: AncillaryFormData): boolean {
if (!data.termsAndConditions) {
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback/index.tsx
index e82f7e339..4c2c1677c 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback/index.tsx
@@ -5,18 +5,17 @@ import { useEffect } from "react"
import { trpc } from "@/lib/trpc/client"
+import {
+ buildAncillaryPackages,
+ clearAncillarySessionData,
+ getAncillarySessionData,
+} from "@/components/HotelReservation/MyStay/utils/ancillaries"
import LoadingSpinner from "@/components/LoadingSpinner"
import {
trackAncillaryFailed,
trackAncillarySuccess,
} from "@/utils/tracking/myStay"
-import {
- buildAncillaryPackages,
- clearAncillarySessionData,
- getAncillarySessionData,
-} from "../utils"
-
import type { Lang } from "@/constants/languages"
export default function GuaranteeAncillaryHandler({
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx
deleted file mode 100644
index bd37a9f77..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/index.tsx
+++ /dev/null
@@ -1,239 +0,0 @@
-"use client"
-
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useRouter } from "next/navigation"
-import { FormProvider, useForm } from "react-hook-form"
-import { useIntl } from "react-intl"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import { PaymentMethodEnum } from "@/constants/booking"
-import {
- bookingTermsAndConditions,
- privacyPolicy,
-} from "@/constants/currentWebHrefs"
-import { guaranteeCallback } from "@/constants/routes/hotelReservation"
-import { env } from "@/env/client"
-import { useManageStayStore } from "@/stores/my-stay/manageStayStore"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import LoadingSpinner from "@/components/LoadingSpinner"
-import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
-import Divider from "@/components/TempDesignSystem/Divider"
-import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
-import Link from "@/components/TempDesignSystem/Link"
-import Body from "@/components/TempDesignSystem/Text/Body"
-import Caption from "@/components/TempDesignSystem/Text/Caption"
-import { toast } from "@/components/TempDesignSystem/Toasts"
-import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking"
-import useLang from "@/hooks/useLang"
-import { formatPrice } from "@/utils/numberFormatting"
-import { trackGlaSaveCardAttempt } from "@/utils/tracking/myStay"
-
-import MySavedCards from "../../EnterDetails/Payment/MySavedCards"
-import PaymentOption from "../../EnterDetails/Payment/PaymentOption"
-import PaymentOptionsGroup from "../../EnterDetails/Payment/PaymentOptionsGroup"
-import { type GuaranteeFormData, paymentSchema } from "./schema"
-
-import styles from "./guaranteeLateArrival.module.css"
-
-import type { CreditCard } from "@/types/user"
-
-export interface GuaranteeLateArrivalProps {
- savedCreditCards: CreditCard[] | null
- refId: string
-}
-
-export default function GuaranteeLateArrival({
- savedCreditCards,
- refId,
-}: GuaranteeLateArrivalProps) {
- const intl = useIntl()
- const lang = useLang()
- const router = useRouter()
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const {
- actions: { handleCloseView, handleCloseModal },
- } = useManageStayStore()
-
- const methods = useForm({
- defaultValues: {
- paymentMethod: savedCreditCards?.length
- ? savedCreditCards[0].id
- : PaymentMethodEnum.card,
- termsAndConditions: false,
- },
- mode: "all",
- reValidateMode: "onChange",
- resolver: zodResolver(paymentSchema),
- })
- const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang)}`
-
- const { guaranteeBooking, isLoading, handleGuaranteeError } =
- useGuaranteeBooking({
- confirmationNumber: bookedRoom.confirmationNumber,
- handleBookingCompleted: router.refresh,
- })
-
- if (isLoading) {
- return (
-
-
-
- )
- }
-
- const handleGuaranteeLateArrival = (data: GuaranteeFormData) => {
- const savedCreditCard = savedCreditCards?.find(
- (card) => card.id === data.paymentMethod
- )
- trackGlaSaveCardAttempt(bookedRoom.hotelId, savedCreditCard, "yes")
- if (bookedRoom.confirmationNumber) {
- const card = savedCreditCard
- ? {
- alias: savedCreditCard.alias,
- expiryDate: savedCreditCard.expirationDate,
- cardType: savedCreditCard.cardType,
- }
- : undefined
- guaranteeBooking.mutate({
- confirmationNumber: bookedRoom.confirmationNumber,
- language: lang,
- ...(card !== undefined && { card }),
- success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}`,
- error: `${guaranteeRedirectUrl}?status=error&RefId=${encodeURIComponent(refId)}`,
- cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}`,
- })
- } else {
- handleGuaranteeError("No confirmation number")
- toast.error(
- intl.formatMessage({
- defaultMessage: "Something went wrong!",
- })
- )
- }
- }
-
- return (
-
-
-
- {intl.formatMessage({
- defaultMessage:
- "Planning to arrive after 18.00? Secure your room by guaranteeing it with a credit card. Without the guarantee and in case of no-show, the room might be reallocated after 18:00.",
- })}
-
-
- {intl.formatMessage({
- defaultMessage:
- "In case of no-show you will be charged for the first night.",
- })}
-
- {savedCreditCards?.length ? (
-
- ) : null}
-
-
-
-
-
-
- {intl.formatMessage(
- {
- defaultMessage:
- "By guaranteeing with any of the payment methods available, I accept the terms for this stay and the general Terms & Conditions , and understand Scandic will process my personal data for this stay in accordance with Scandic's Privacy Policy . I accept Scandic requiring a valid credit card during my visit in case anything is left unpaid.",
- },
- {
- termsAndConditionsLink: (str) => (
-
- {str}
-
- ),
- privacyPolicyLink: (str) => (
-
- {str}
-
- ),
- }
- )}
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "I accept the terms and conditions",
- })}
-
-
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Guarantee cost",
- })}
-
-
- {intl.formatMessage({
- defaultMessage:
- "Your card will only be used for authorisation",
- })}
-
-
-
-
- {formatPrice(intl, 0, bookedRoom.currencyCode)}
-
-
- >
- }
- primaryAction={{
- label: intl.formatMessage({
- defaultMessage: "Guarantee",
- }),
- onClick: methods.handleSubmit(handleGuaranteeLateArrival),
- intent: "primary",
- }}
- secondaryAction={{
- label: intl.formatMessage({
- defaultMessage: "Back",
- }),
- onClick: handleCloseView,
- intent: "text",
- }}
- />
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/Details.tsx b/apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/Details.tsx
new file mode 100644
index 000000000..ef6eebe81
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/Details.tsx
@@ -0,0 +1,315 @@
+"use client"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useRouter } from "next/navigation"
+import { useState } from "react"
+import { Dialog } from "react-aria-components"
+import { FormProvider, useForm } from "react-hook-form"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { trpc } from "@/lib/trpc/client"
+
+import MembershipLevelIcon from "@/components/Levels/Icon"
+import Modal from "@/components/Modal"
+import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
+import Button from "@/components/TempDesignSystem/Button"
+import { toast } from "@/components/TempDesignSystem/Toasts"
+import useLang from "@/hooks/useLang"
+
+import ModifyContact from "../ModifyContact"
+
+import styles from "./guestDetails.module.css"
+
+import {
+ type ModifyContactSchema,
+ modifyContactSchema,
+} from "@/types/components/hotelReservation/myStay/modifyContact"
+import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
+import type { Room } from "@/types/stores/my-stay"
+import type { SafeUser } from "@/types/user"
+
+interface DetailsProps {
+ booking: Room
+ user: SafeUser
+}
+
+export default function Details({ booking, user }: DetailsProps) {
+ const intl = useIntl()
+ const lang = useLang()
+ const router = useRouter()
+ const utils = trpc.useUtils()
+ const [currentStep, setCurrentStep] = useState(MODAL_STEPS.INITIAL)
+ const [isLoading, setIsLoading] = useState(false)
+
+ const [isModifyGuestDetailsOpen, setIsModifyGuestDetailsOpen] =
+ useState(false)
+
+ const form = useForm({
+ resolver: zodResolver(modifyContactSchema),
+ defaultValues: {
+ firstName: booking.guest.firstName,
+ lastName: booking.guest.lastName,
+ email: booking.guest.email,
+ phoneNumber: booking.guest.phoneNumber,
+ countryCode: booking.guest.countryCode,
+ },
+ })
+
+ const isFirstStep = currentStep === MODAL_STEPS.INITIAL
+
+ const isMemberBooking =
+ booking.guest.membershipNumber === user?.membership?.membershipNumber
+
+ const updateGuest = trpc.booking.update.useMutation({
+ onMutate: () => setIsLoading(true),
+ onSuccess: (data) => {
+ if (data) {
+ utils.booking.get.invalidate({
+ confirmationNumber: data.confirmationNumber,
+ })
+
+ toast.success(
+ intl.formatMessage({
+ defaultMessage: "Guest details updated",
+ })
+ )
+ setIsModifyGuestDetailsOpen(false)
+ setCurrentStep(MODAL_STEPS.INITIAL)
+ } else {
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Failed to update guest details",
+ })
+ )
+ }
+ },
+ onError: () => {
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Failed to update guest details",
+ })
+ )
+ },
+ onSettled: () => {
+ setIsLoading(false)
+ },
+ })
+
+ async function onSubmit(data: ModifyContactSchema) {
+ updateGuest.mutate({
+ confirmationNumber: booking.confirmationNumber,
+ guest: {
+ email: data.email,
+ phoneNumber: data.phoneNumber,
+ countryCode: data.countryCode,
+ },
+ })
+ }
+
+ function handleModifyMemberDetails() {
+ const expirationTime = Date.now() + 10 * 60 * 1000
+ sessionStorage.setItem(
+ "myStayReturnRoute",
+ JSON.stringify({
+ path: window.location.href,
+ expiry: expirationTime,
+ })
+ )
+ router.push(`/${lang}/scandic-friends/my-pages/profile/edit`)
+ }
+
+ return (
+
+ {isMemberBooking && user.membership && (
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Your member tier",
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Total points",
+ })}
+
+
+
+
+
+ {user.membership.currentPoints}
+
+
+
+ )}
+
+
+
+ {booking.guest.firstName} {booking.guest.lastName}
+
+
+ {isMemberBooking && user.membership && (
+
+
+ {intl.formatMessage(
+ {
+ defaultMessage: "Member no. {nr}",
+ },
+ {
+ nr: user.membership.membershipNumber,
+ }
+ )}
+
+
+ )}
+
+
+ {booking.guest.email}
+
+
+ {booking.guest.phoneNumber}
+
+
+
+
+ {booking.guest.email}
+
+
+ {booking.guest.phoneNumber}
+
+
+
+ {isMemberBooking ? (
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Modify guest details",
+ })}
+
+
+
+ ) : (
+ <>
+
+ setIsModifyGuestDetailsOpen(!isModifyGuestDetailsOpen)
+ }
+ disabled={booking.isCancelled}
+ size="small"
+ >
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Modify guest details",
+ })}
+
+
+
+ {isModifyGuestDetailsOpen && (
+
+
+ {({ close }) => (
+
+ setIsModifyGuestDetailsOpen(false)}
+ content={
+ booking.guest && (
+
+ )
+ }
+ primaryAction={{
+ label: isFirstStep
+ ? intl.formatMessage({
+ defaultMessage: "Save updates",
+ })
+ : intl.formatMessage({
+ defaultMessage: "Confirm",
+ }),
+ onClick: isFirstStep
+ ? () => setCurrentStep(MODAL_STEPS.CONFIRMATION)
+ : () => form.handleSubmit(onSubmit)(),
+ disabled: !form.formState.isValid || isLoading,
+ intent: isFirstStep ? "secondary" : "primary",
+ }}
+ secondaryAction={{
+ label: isFirstStep
+ ? intl.formatMessage({
+ defaultMessage: "Back",
+ })
+ : intl.formatMessage({
+ defaultMessage: "Cancel",
+ }),
+ onClick: () => {
+ close()
+ setCurrentStep(MODAL_STEPS.INITIAL)
+ },
+ }}
+ />
+
+ )}
+
+
+ )}
+ >
+ )}
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/index.tsx
index 10d2fee9b..7f786c59f 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/GuestDetails/index.tsx
@@ -1,326 +1,22 @@
"use client"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { Dialog } from "react-aria-components"
-import { FormProvider, useForm } from "react-hook-form"
-import { useIntl } from "react-intl"
+import { useMyStayStore } from "@/stores/my-stay"
-import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
-import { Typography } from "@scandic-hotels/design-system/Typography"
+import Details from "./Details"
-import { trpc } from "@/lib/trpc/client"
-import { type Room } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import MembershipLevelIcon from "@/components/Levels/Icon"
-import Modal from "@/components/Modal"
-import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
-import Button from "@/components/TempDesignSystem/Button"
-import { toast } from "@/components/TempDesignSystem/Toasts"
-import useLang from "@/hooks/useLang"
-
-import ModifyContact from "../ModifyContact"
-
-import styles from "./guestDetails.module.css"
-
-import {
- type ModifyContactSchema,
- modifyContactSchema,
-} from "@/types/components/hotelReservation/myStay/modifyContact"
-import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
-import type { User } from "@/types/user"
+import type { Room } from "@/types/stores/my-stay"
+import type { SafeUser } from "@/types/user"
interface GuestDetailsProps {
- user: User | null
- booking: Room
- updateRoom: (room: Room) => void
+ selectedRoom?: Room
+ user: SafeUser
}
export default function GuestDetails({
+ selectedRoom,
user,
- booking,
- updateRoom,
}: GuestDetailsProps) {
- const intl = useIntl()
- const lang = useLang()
- const router = useRouter()
- const [currentStep, setCurrentStep] = useState(MODAL_STEPS.INITIAL)
- const [isLoading, setIsLoading] = useState(false)
+ const booking = useMyStayStore((state) => state.bookedRoom)
+ const room = selectedRoom ? selectedRoom : booking
- const [isModifyGuestDetailsOpen, setIsModifyGuestDetailsOpen] =
- useState(false)
-
- const form = useForm({
- resolver: zodResolver(modifyContactSchema),
- defaultValues: {
- firstName: booking.guest.firstName,
- lastName: booking.guest.lastName,
- email: booking.guest.email,
- phoneNumber: booking.guest.phoneNumber,
- countryCode: booking.guest.countryCode,
- },
- })
-
- const isFirstStep = currentStep === MODAL_STEPS.INITIAL
-
- const isMemberBooking =
- booking.guest.membershipNumber === user?.membership?.membershipNumber
-
- const updateGuest = trpc.booking.update.useMutation({
- onMutate: () => setIsLoading(true),
- onSuccess: (data) => {
- if (!data) {
- toast.error(
- intl.formatMessage({
- defaultMessage: "Failed to update guest details",
- })
- )
-
- return
- }
- updateRoom({
- ...booking,
- guest: {
- ...booking.guest,
- email: data.guest.email,
- phoneNumber: data.guest.phoneNumber,
- countryCode: data.guest.countryCode,
- },
- })
-
- toast.success(
- intl.formatMessage({
- defaultMessage: "Guest details updated",
- })
- )
- setIsModifyGuestDetailsOpen(false)
- setCurrentStep(MODAL_STEPS.INITIAL)
- },
- onError: () => {
- toast.error(
- intl.formatMessage({
- defaultMessage: "Failed to update guest details",
- })
- )
- },
- onSettled: () => {
- setIsLoading(false)
- },
- })
-
- async function onSubmit(data: ModifyContactSchema) {
- updateGuest.mutate({
- confirmationNumber: booking.confirmationNumber,
- guest: {
- email: data.email,
- phoneNumber: data.phoneNumber,
- countryCode: data.countryCode,
- },
- })
- }
-
- function handleModifyMemberDetails() {
- const expirationTime = Date.now() + 10 * 60 * 1000
- sessionStorage.setItem(
- "myStayReturnRoute",
- JSON.stringify({
- path: window.location.href,
- expiry: expirationTime,
- })
- )
- router.push(`/${lang}/scandic-friends/my-pages/profile/edit`)
- }
-
- return (
-
- {isMemberBooking && user.membership && (
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Your member tier",
- })}
-
-
-
-
-
-
-
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Total points",
- })}
-
-
-
-
-
- {user.membership.currentPoints}
-
-
-
- )}
-
-
-
- {booking.guest.firstName} {booking.guest.lastName}
-
-
- {isMemberBooking && user.membership && (
-
-
- {intl.formatMessage(
- {
- defaultMessage: "Member no. {nr}",
- },
- {
- nr: user.membership.membershipNumber,
- }
- )}
-
-
- )}
-
-
- {booking.guest.email}
-
-
- {booking.guest.phoneNumber}
-
-
-
-
- {booking.guest.email}
-
-
- {booking.guest.phoneNumber}
-
-
-
- {isMemberBooking ? (
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Modify guest details",
- })}
-
-
-
- ) : (
- <>
-
- setIsModifyGuestDetailsOpen(!isModifyGuestDetailsOpen)
- }
- disabled={booking.isCancelled}
- size="small"
- >
-
-
-
- {intl.formatMessage({
- defaultMessage: "Modify guest details",
- })}
-
-
-
- {isModifyGuestDetailsOpen && (
-
-
- {({ close }) => (
-
- setIsModifyGuestDetailsOpen(false)}
- content={
- booking.guest && (
-
- )
- }
- primaryAction={{
- label: isFirstStep
- ? intl.formatMessage({
- defaultMessage: "Save updates",
- })
- : intl.formatMessage({
- defaultMessage: "Confirm",
- }),
- onClick: isFirstStep
- ? () => setCurrentStep(MODAL_STEPS.CONFIRMATION)
- : () => form.handleSubmit(onSubmit)(),
- disabled: !form.formState.isValid || isLoading,
- intent: isFirstStep ? "secondary" : "primary",
- }}
- secondaryAction={{
- label: isFirstStep
- ? intl.formatMessage({
- defaultMessage: "Back",
- })
- : intl.formatMessage({
- defaultMessage: "Cancel",
- }),
- onClick: () => {
- close()
- setCurrentStep(MODAL_STEPS.INITIAL)
- },
- }}
- />
-
- )}
-
-
- )}
- >
- )}
-
- )
+ return
}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Header/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Header/index.tsx
index cfbf7f8b5..6b940b9c6 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Header/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Header/index.tsx
@@ -4,9 +4,12 @@ import { getIntl } from "@/i18n"
import styles from "./header.module.css"
-import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
+import type { Hotel } from "@/types/hotel"
-export async function Header({ hotel }: Pick) {
+export async function Header({
+ cityName,
+ name,
+}: Pick) {
const intl = await getIntl()
return (
@@ -20,8 +23,8 @@ export async function Header({ hotel }: Pick) {
" "
}
- {hotel.name}
- {hotel.cityName}
+ {name}
+ {cityName}
)
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/AddToCalendarButton.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/AddToCalendarButton.tsx
deleted file mode 100644
index 7574b7591..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/AddToCalendarButton.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-"use client"
-
-import { useIntl } from "react-intl"
-
-import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
-
-import Button from "@/components/TempDesignSystem/Button"
-import { trackMyStayPageLink } from "@/utils/tracking"
-
-import styles from "../actionPanel.module.css"
-
-export default function AddToCalendarButton({
- onPress,
- disabled,
-}: {
- onPress: () => void
- disabled?: boolean
-}) {
- const intl = useIntl()
-
- const handleAddToCalendar = () => {
- trackMyStayPageLink("add to calendar")
- onPress()
- }
-
- return (
-
- {intl.formatMessage({
- defaultMessage: "Add to calendar",
- })}
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/CancelStayPriceContainer/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/CancelStayPriceContainer/index.tsx
deleted file mode 100644
index 9dcc58892..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/CancelStayPriceContainer/index.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useFormContext } from "react-hook-form"
-import { useIntl } from "react-intl"
-
-import PriceContainer from "../../../PriceContainer"
-import { useCheckedRoomsCounts } from "../utils"
-
-import type {
- CancelStayFormValues,
- PriceContainerProps,
-} from "@/types/components/hotelReservation/myStay/cancelStay"
-
-export default function CancelStayPriceContainer({
- roomDetails,
- stayDetails,
-}: PriceContainerProps) {
- const intl = useIntl()
-
- const { getValues } = useFormContext()
- const formRooms = getValues("rooms")
-
- const checkedRoomsDetails = useCheckedRoomsCounts(
- roomDetails,
- formRooms,
- intl
- )
-
- return (
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/Confirmation/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/Confirmation/index.tsx
deleted file mode 100644
index 6e4804b84..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/Confirmation/index.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-"use client"
-
-import { useFormContext } from "react-hook-form"
-import { useIntl } from "react-intl"
-
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
-import Body from "@/components/TempDesignSystem/Text/Body"
-import Caption from "@/components/TempDesignSystem/Text/Caption"
-
-import CancelStayPriceContainer from "../CancelStayPriceContainer"
-
-import styles from "../cancelStay.module.css"
-
-import type {
- CancelStayConfirmationProps,
- CancelStayFormValues,
-} from "@/types/components/hotelReservation/myStay/cancelStay"
-
-export function CancelStayConfirmation({
- hotel,
- stayDetails,
-}: CancelStayConfirmationProps) {
- const intl = useIntl()
- const { watch } = useFormContext()
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
-
- const { multiRoom } = bookedRoom
-
- return (
- <>
-
-
- {intl.formatMessage(
- {
- defaultMessage:
- "Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.",
- },
- {
- hotel: hotel.name,
- checkInDate: stayDetails.checkInDate,
- checkOutDate: stayDetails.checkOutDate,
- }
- )}
-
-
- {intl.formatMessage({
- defaultMessage: "No charges were made.",
- })}
-
-
- {multiRoom && (
- <>
-
- {intl.formatMessage({
- defaultMessage: "Select rooms",
- })}
-
-
-
- {watch("rooms").map((room, index) => {
- // Find room details from store by confirmationNumber
- const roomDetail =
- linkedReservationRooms.find(
- (detail) =>
- detail.confirmationNumber === room.confirmationNumber
- ) ?? bookedRoom
-
- return (
-
-
-
-
- {intl.formatMessage(
- {
- defaultMessage: "Room {roomIndex}",
- },
- {
- roomIndex: index + 1,
- }
- )}
-
- {roomDetail && (
- <>
-
- {roomDetail.roomName}
-
- >
- )}
-
-
-
- )
- })}
-
- >
- )}
- {watch("rooms").some((room) => room.checked) && (
-
- )}
- >
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/FinalConfirmation/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/FinalConfirmation/index.tsx
deleted file mode 100644
index 5995c88f5..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/FinalConfirmation/index.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useIntl } from "react-intl"
-
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import Body from "@/components/TempDesignSystem/Text/Body"
-
-import CancelStayPriceContainer from "../CancelStayPriceContainer"
-
-import styles from "../cancelStay.module.css"
-
-import type { FinalConfirmationProps } from "@/types/components/hotelReservation/myStay/cancelStay"
-
-export function FinalConfirmation({ stayDetails }: FinalConfirmationProps) {
- const intl = useIntl()
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
-
- return (
- <>
-
-
- {intl.formatMessage({
- defaultMessage:
- "Are you sure you want to continue with the cancellation?",
- })}
-
-
- {bookedRoom && (
-
- )}
- >
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/hooks/useCancelStay.ts b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/hooks/useCancelStay.ts
deleted file mode 100644
index 110ab453d..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/hooks/useCancelStay.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import { useIntl } from "react-intl"
-
-import { trpc } from "@/lib/trpc/client"
-import { useManageStayStore } from "@/stores/my-stay/manageStayStore"
-import {
- type Room,
- useMyStayRoomDetailsStore,
-} from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import { toast } from "@/components/TempDesignSystem/Toasts"
-import useLang from "@/hooks/useLang"
-import { trackCancelStay } from "@/utils/tracking"
-
-import type {
- CancelStayFormValues,
- CancelStayProps,
-} from "@/types/components/hotelReservation/myStay/cancelStay"
-
-interface UseCancelStayProps extends Omit {
- checkedRooms: CancelStayFormValues["rooms"]
-}
-
-export default function useCancelStay({
- handleCloseModal,
- checkedRooms,
-}: UseCancelStayProps) {
- const intl = useIntl()
- const lang = useLang()
- const {
- actions: { setIsLoading },
- } = useManageStayStore()
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
-
- const updateBookedRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.updateBookedRoom
- )
-
- const updateLinkedReservationRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.updateLinkedReservationRoom
- )
-
- const cancelStay = trpc.booking.cancel.useMutation({
- onMutate: () => setIsLoading(true),
- })
-
- async function handleCancelStay() {
- if (!bookedRoom.confirmationNumber) {
- toast.error(
- intl.formatMessage({
- defaultMessage: "Something went wrong. Please try again later.",
- })
- )
- return
- }
-
- setIsLoading(true)
-
- try {
- const results = []
- const errors = []
-
- for (const room of checkedRooms) {
- let targetRoom: Room | undefined
-
- // Check if this is the main booked room
- if (room.confirmationNumber === bookedRoom.confirmationNumber) {
- targetRoom = bookedRoom
- }
- // Check if this is a linked reservation room
- else {
- targetRoom = linkedReservationRooms.find(
- (r) => r.confirmationNumber === room.confirmationNumber
- )
- }
-
- if (!targetRoom?.confirmationNumber) {
- errors.push(room.confirmationNumber)
- continue
- }
-
- try {
- const response = await cancelStay.mutateAsync({
- confirmationNumber: targetRoom.confirmationNumber,
- language: lang,
- })
-
- if (response) {
- results.push(room.confirmationNumber)
- const cancelledRoom = response.rooms.find(
- (r) => r.confirmationNumber === targetRoom?.confirmationNumber
- )
-
- if (cancelledRoom) {
- if (
- targetRoom.confirmationNumber === bookedRoom.confirmationNumber
- ) {
- // Update main booked room
- updateBookedRoom({
- ...bookedRoom,
- isCancelled: true,
- cancellationNumber: cancelledRoom.cancellationNumber,
- })
- } else {
- // Update linked reservation room
- updateLinkedReservationRoom({
- ...targetRoom,
- isCancelled: true,
- cancellationNumber: cancelledRoom.cancellationNumber,
- })
- }
-
- trackCancelStay(
- bookedRoom.hotelId,
- cancelledRoom.confirmationNumber
- )
- }
- } else {
- errors.push(room.confirmationNumber)
- }
- } catch (error) {
- console.error(
- `Error cancelling room ${targetRoom.confirmationNumber}:`,
- error
- )
- errors.push(room.confirmationNumber)
- }
- }
-
- // Show appropriate toast based on results
- if (results.length > 0 && errors.length === 0) {
- // All selected rooms cancelled successfully
- toast.success(
- intl.formatMessage(
- {
- defaultMessage:
- "Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out",
- },
- { currency: bookedRoom.currencyCode }
- )
- )
- } else if (results.length > 0 && errors.length > 0) {
- // Some rooms cancelled, some failed
- toast.warning(
- intl.formatMessage({
- defaultMessage:
- "Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.",
- })
- )
- } else {
- // No rooms cancelled successfully
- toast.error(
- intl.formatMessage({
- defaultMessage: "Something went wrong. Please try again later.",
- })
- )
- }
-
- handleCloseModal()
- } catch (error) {
- console.error("Error in handleCancelStay:", error)
- toast.error(
- intl.formatMessage({
- defaultMessage: "Something went wrong. Please try again later.",
- })
- )
- } finally {
- setIsLoading(false)
- }
- }
-
- return {
- handleCancelStay,
- }
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/index.tsx
deleted file mode 100644
index 5cc5808ad..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/index.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-"use client"
-
-import { zodResolver } from "@hookform/resolvers/zod"
-import { FormProvider, useForm } from "react-hook-form"
-import { useIntl } from "react-intl"
-
-import { useManageStayStore } from "@/stores/my-stay/manageStayStore"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
-import Alert from "@/components/TempDesignSystem/Alert"
-import useLang from "@/hooks/useLang"
-
-import useCancelStay from "./hooks/useCancelStay"
-import { CancelStayConfirmation } from "./Confirmation"
-import { FinalConfirmation } from "./FinalConfirmation"
-import { formatStayDetails, getDefaultRooms } from "./utils"
-
-import {
- type CancelStayFormValues,
- cancelStaySchema,
-} from "@/types/components/hotelReservation/myStay/cancelStay"
-import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
-import { AlertTypeEnum } from "@/types/enums/alert"
-import type { Hotel } from "@/types/hotel"
-
-interface CancelStayProps {
- hotel: Hotel
-}
-
-export default function CancelStay({ hotel }: CancelStayProps) {
- const intl = useIntl()
- const lang = useLang()
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
-
- const form = useForm({
- resolver: zodResolver(cancelStaySchema),
- defaultValues: {
- rooms: getDefaultRooms(bookedRoom),
- },
- })
- const {
- currentStep,
- isLoading,
- actions: { handleForward, handleCloseView, handleCloseModal },
- } = useManageStayStore()
-
- const { rooms } = form.watch()
-
- const { handleCancelStay } = useCancelStay({
- handleCloseModal,
- checkedRooms: rooms.filter((room) => room.checked),
- })
-
- const isFirstStep = currentStep === MODAL_STEPS.INITIAL
- const stayDetails = formatStayDetails({ bookedRoom, lang, intl })
-
- function getModalCopy() {
- if (isFirstStep) {
- return {
- title: intl.formatMessage({
- defaultMessage: "Cancel stay",
- }),
- primaryLabel: intl.formatMessage({
- defaultMessage: "Cancel stay",
- }),
- secondaryLabel: intl.formatMessage({
- defaultMessage: "Back",
- }),
- }
- } else {
- return {
- title: intl.formatMessage({
- defaultMessage: "Confirm cancellation",
- }),
- primaryLabel: intl.formatMessage({
- defaultMessage: "Confirm cancellation",
- }),
- secondaryLabel: intl.formatMessage({
- defaultMessage: "Don't cancel",
- }),
- }
- }
- }
-
- function getModalContent() {
- if (bookedRoom && isFirstStep)
- return
-
- if (bookedRoom && !isFirstStep)
- return
-
- if (!bookedRoom && isFirstStep)
- return (
-
- )
- }
-
- const isFormValid = rooms?.some((room) => room.checked)
-
- return (
-
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/utils.ts b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/utils.ts
deleted file mode 100644
index ce05d8ff7..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/utils.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-import { dt } from "@/lib/dt"
-
-import type { IntlShape } from "react-intl"
-
-import type { CancelStayFormValues } from "@/types/components/hotelReservation/myStay/cancelStay"
-import type { Room } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-export function getDefaultRooms(room: Room) {
- const { multiRoom, confirmationNumber, linkedReservations = [] } = room
-
- if (!multiRoom) {
- return [{ id: "1", checked: true, confirmationNumber }]
- }
-
- const mainRoom = { id: "1", checked: false, confirmationNumber }
- const linkedRooms = linkedReservations.map((reservation, index) => ({
- id: `${index + 2}`,
- checked: false,
- confirmationNumber: reservation.confirmationNumber,
- }))
-
- return [mainRoom, ...linkedRooms]
-}
-
-export function formatStayDetails({
- bookedRoom,
- lang,
- intl,
-}: {
- bookedRoom: Room
- lang: string
- intl: IntlShape
-}) {
- const {
- multiRoom,
- adults,
- childrenAges,
- linkedReservations,
- checkInDate,
- checkOutDate,
- } = bookedRoom
-
- const totalAdults = multiRoom
- ? linkedReservations.reduce((acc, reservation) => {
- return acc + reservation.adults
- }, adults)
- : adults
- const totalChildren = multiRoom
- ? linkedReservations.reduce((acc, reservation) => {
- return acc + reservation.children
- }, childrenAges.length)
- : childrenAges.length
-
- const checkInDateFormatted = dt(checkInDate)
- .locale(lang)
- .format("dddd D MMM YYYY")
- const checkOutDateFormatted = dt(checkOutDate)
- .locale(lang)
- .format("dddd D MMM YYYY")
- const diff = dt(checkOutDate)
- .startOf("day")
- .diff(dt(checkInDate).startOf("day"), "days")
-
- const nightsText = intl.formatMessage(
- {
- defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
- },
- { totalNights: diff }
- )
- const adultsText = intl.formatMessage(
- {
- defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
- },
- { totalAdults: totalAdults }
- )
- const childrenText = intl.formatMessage(
- {
- defaultMessage:
- "{totalChildren, plural, one {# child} other {# children}}",
- },
- { totalChildren: totalChildren }
- )
-
- return {
- checkInDate: checkInDateFormatted,
- checkOutDate: checkOutDateFormatted,
- nightsText,
- adultsText,
- childrenText,
- totalChildren,
- }
-}
-
-function getMatchedRooms(
- roomDetails: Room,
- checkedConfirmationNumbers: string[]
-) {
- let matchedRooms = []
-
- // Main booking
- if (checkedConfirmationNumbers.includes(roomDetails.confirmationNumber)) {
- matchedRooms.push({
- adults: roomDetails.adults,
- children: roomDetails.childrenAges.length,
- })
- }
-
- // Linked reservations
- if (roomDetails.linkedReservations) {
- roomDetails.linkedReservations.forEach((reservation) => {
- if (checkedConfirmationNumbers.includes(reservation.confirmationNumber))
- matchedRooms.push({
- adults: reservation.adults,
- children: reservation.children,
- })
- })
- }
-
- return matchedRooms
-}
-
-function calculateTotals(matchedRooms: { adults: number; children: number }[]) {
- const totalAdults = matchedRooms.reduce((sum, room) => sum + room.adults, 0)
- const totalChildren = matchedRooms.reduce(
- (sum, room) => sum + room.children,
- 0
- )
- return { totalAdults, totalChildren }
-}
-
-export const useCheckedRoomsCounts = (
- roomDetails: Room,
- formRooms: CancelStayFormValues["rooms"],
- intl: IntlShape
-) => {
- const checkedFormRooms = formRooms.filter((room) => room.checked)
- const checkedConfirmationNumbers = checkedFormRooms
- .map((room) => room.confirmationNumber)
- .filter(
- (confirmationNumber): confirmationNumber is string =>
- confirmationNumber !== null && confirmationNumber !== undefined
- )
-
- const matchedRooms = getMatchedRooms(roomDetails, checkedConfirmationNumbers)
- const { totalAdults, totalChildren } = calculateTotals(matchedRooms)
-
- const adultsText = intl.formatMessage(
- {
- defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
- },
- { totalAdults: totalAdults }
- )
- const childrenText = intl.formatMessage(
- {
- defaultMessage:
- "{totalChildren, plural, one {# child} other {# children}}",
- },
- { totalChildren: totalChildren }
- )
-
- return { adultsText, childrenText, totalChildren }
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/confirmation.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/confirmation.module.css
deleted file mode 100644
index e17771a89..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/confirmation.module.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.container {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
-}
-
-.dateComparison {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
-}
-
-.dateGroup {
- display: flex;
- flex-direction: column;
-}
-
-.dateHeader {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
-}
-
-.dates {
- display: flex;
- flex-direction: column;
-}
-
-.date {
- display: flex;
- justify-content: space-between;
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/index.tsx
deleted file mode 100644
index 64c73b90f..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/Confirmation/index.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { useFormContext } from "react-hook-form"
-import { useIntl } from "react-intl"
-
-import { dt } from "@/lib/dt"
-import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
-
-import PriceContainer from "@/components/HotelReservation/MyStay/ManageStay/ActionPanel/PriceContainer"
-import Divider from "@/components/TempDesignSystem/Divider"
-import Body from "@/components/TempDesignSystem/Text/Body"
-import Caption from "@/components/TempDesignSystem/Text/Caption"
-import useLang from "@/hooks/useLang"
-
-import styles from "./confirmation.module.css"
-
-interface ConfirmationProps {
- oldPrice: number
- newPrice: number
- stayDetails: {
- checkInDate: string
- checkOutDate: string
- nightsText: string
- adultsText: string
- childrenText: string
- totalChildren?: number
- }
-}
-
-export default function Confirmation({
- oldPrice,
- newPrice,
- stayDetails,
-}: ConfirmationProps) {
- const intl = useIntl()
- const lang = useLang()
- const { getValues } = useFormContext()
- const { currencyCode } = useMyStayTotalPriceStore()
-
- const formValues = getValues()
-
- const originalCheckIn = dt(stayDetails.checkInDate)
- .locale(lang)
- .format("dddd, DD MMM, YYYY")
- const originalCheckOut = dt(stayDetails.checkOutDate)
- .locale(lang)
- .format("dddd, DD MMM, YYYY")
- const newCheckIn = dt(formValues.checkInDate)
- .locale(lang)
- .format("dddd, DD MMM, YYYY")
- const newCheckOut = dt(formValues.checkOutDate)
- .locale(lang)
- .format("dddd, DD MMM, YYYY")
-
- const diff = dt(newCheckOut)
- .startOf("day")
- .diff(dt(newCheckIn).startOf("day"), "days")
-
- const nightsText = intl.formatMessage(
- {
- defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
- },
- { totalNights: diff }
- )
-
- return (
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Old dates",
- })}
-
-
- {oldPrice} {currencyCode}
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Check-in",
- })}
-
- {originalCheckIn}
-
-
-
- {intl.formatMessage({
- defaultMessage: "Check-out",
- })}
-
- {originalCheckOut}
-
-
-
-
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "New dates",
- })}
-
-
- {newPrice} {currencyCode}
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Check-in",
- })}
-
- {newCheckIn}
-
-
-
- {intl.formatMessage({
- defaultMessage: "Check-out",
- })}
-
- {newCheckOut}
-
-
-
-
-
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/hooks/useModifyStay.ts b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/hooks/useModifyStay.ts
deleted file mode 100644
index aa9563f77..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/hooks/useModifyStay.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import { useIntl } from "react-intl"
-
-import { trpc } from "@/lib/trpc/client"
-import { useManageStayStore } from "@/stores/my-stay/manageStayStore"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import { toast } from "@/components/TempDesignSystem/Toasts"
-import useLang from "@/hooks/useLang"
-
-import type { UseFormGetValues } from "react-hook-form"
-
-import type { ModifyDateSchema } from "@/types/components/hotelReservation/myStay/modifyDate"
-
-interface UseModifyStayOptions {
- isLoggedIn?: boolean
- getFormValues: UseFormGetValues
- handleCloseModal: () => void
-}
-
-export default function useModifyStay({
- isLoggedIn,
- getFormValues,
- handleCloseModal,
-}: UseModifyStayOptions) {
- const intl = useIntl()
- const lang = useLang()
- const {
- actions: { setIsLoading },
- } = useManageStayStore()
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
-
- const updateBookedRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.updateBookedRoom
- )
-
- const utils = trpc.useUtils()
-
- const updateBooking = trpc.booking.update.useMutation({
- onMutate: () => setIsLoading(true),
- onSuccess: (updatedBooking) => {
- if (!updatedBooking) {
- toast.error(
- intl.formatMessage({
- defaultMessage: "Failed to update your stay",
- })
- )
- return
- }
- // Update room details with server response data
- updateBookedRoom({
- ...bookedRoom,
- checkInDate: updatedBooking.checkInDate,
- checkOutDate: updatedBooking.checkOutDate,
- })
-
- toast.success(
- intl.formatMessage({
- defaultMessage: "Your stay was updated",
- })
- )
- handleCloseModal()
- },
- onError: () => {
- toast.error(
- intl.formatMessage({
- defaultMessage: "Failed to update your stay",
- })
- )
- },
- onSettled: () => {
- setIsLoading(false)
- },
- })
-
- async function checkAvailability() {
- const formValues = getFormValues()
-
- if (!formValues.checkInDate || !formValues.checkOutDate) {
- toast.error(
- intl.formatMessage({
- defaultMessage: "Please select dates",
- })
- )
- return { success: false }
- }
-
- setIsLoading(true)
-
- try {
- const availabilityResults = []
- let totalNewPrice = 0
-
- try {
- const data = await utils.hotel.availability.myStay.fetch({
- booking: {
- fromDate: formValues.checkInDate,
- toDate: formValues.checkOutDate,
- hotelId: bookedRoom.hotelId,
- room: {
- adults: bookedRoom.adults,
- bookingCode: bookedRoom.bookingCode ?? undefined,
- childrenInRoom: bookedRoom.childrenInRoom,
- rateCode: bookedRoom.rateDefinition.rateCode,
- roomTypeCode: bookedRoom.roomTypeCode,
- },
- },
- lang,
- })
-
- if (!data?.selectedRoom || data.selectedRoom.roomsLeft <= 0) {
- return { success: false, noAvailability: true }
- }
- let roomPrice = 0
- if (isLoggedIn && "member" in data.product && data.product.member) {
- roomPrice = data.product.member.localPrice.pricePerStay
- } else if ("public" in data.product && data.product.public) {
- roomPrice = data.product.public.localPrice.pricePerStay
- } else if (
- "corporateCheque" in data.product &&
- data.product.corporateCheque.localPrice.additionalPricePerStay
- ) {
- roomPrice =
- data.product.corporateCheque.localPrice.additionalPricePerStay
- } else if (
- "redemption" in data.product &&
- data.product.redemption.localPrice.additionalPricePerStay
- ) {
- roomPrice = data.product.redemption.localPrice.additionalPricePerStay
- }
- totalNewPrice += roomPrice
- availabilityResults.push(data)
- } catch (error) {
- console.error("Error checking room availability:", error)
- return { success: false, error: true }
- }
-
- return {
- success: true,
- newRoomPrice: totalNewPrice,
- results: availabilityResults,
- }
- } catch (error) {
- console.error("Error checking availability:", error)
- return { success: false, error: true }
- } finally {
- setIsLoading(false)
- }
- }
-
- async function handleModifyStay() {
- const formValues = getFormValues()
- setIsLoading(true)
-
- try {
- await updateBooking.mutateAsync({
- confirmationNumber: bookedRoom.confirmationNumber,
- checkInDate: formValues.checkInDate,
- checkOutDate: formValues.checkOutDate,
- })
- } catch (error) {
- console.error("Error modifying stay:", error)
- toast.error(
- intl.formatMessage({
- defaultMessage: "Failed to update your stay. Please try again later.",
- })
- )
- setIsLoading(false)
- }
- }
-
- return {
- checkAvailability,
- handleModifyStay,
- }
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/index.tsx
deleted file mode 100644
index 2ebe106a0..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/index.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-"use client"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useEffect, useState } from "react"
-import { FormProvider, useForm } from "react-hook-form"
-import { useIntl } from "react-intl"
-
-import { dt } from "@/lib/dt"
-import { useManageStayStore } from "@/stores/my-stay/manageStayStore"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
-import Alert from "@/components/TempDesignSystem/Alert"
-import useLang from "@/hooks/useLang"
-
-import { formatStayDetails } from "../CancelStay/utils"
-import useModifyStay from "./hooks/useModifyStay"
-import Confirmation from "./Confirmation"
-import NewDates from "./NewDates"
-
-import {
- type ModifyDateSchema,
- modifyDateSchema,
- type ModifyStayProps,
-} from "@/types/components/hotelReservation/myStay/modifyDate"
-import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
-import { AlertTypeEnum } from "@/types/enums/alert"
-
-export default function ModifyStay({ isLoggedIn }: ModifyStayProps) {
- const intl = useIntl()
- const lang = useLang()
-
- const [error, setError] = useState(false)
- const [noAvailability, setNoAvailability] = useState(false)
- const [newRoomPrice, setNewRoomPrice] = useState(0)
-
- const form = useForm({
- resolver: zodResolver(modifyDateSchema),
- defaultValues: {
- checkInDate: "",
- checkOutDate: "",
- },
- })
-
- const {
- currentStep,
- isLoading,
- actions: { handleCloseView, handleCloseModal, setCurrentStep },
- } = useManageStayStore()
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
-
- const stayDetails = formatStayDetails({ bookedRoom, lang, intl })
-
- const isFirstStep = currentStep === MODAL_STEPS.INITIAL
-
- const {
- multiRoom,
- checkInDate,
- checkOutDate,
- mainRoom,
- roomPrice,
- canChangeDate,
- } = bookedRoom
-
- const { checkAvailability, handleModifyStay } = useModifyStay({
- isLoggedIn,
- getFormValues: form.getValues,
- handleCloseModal,
- })
-
- async function onCheckAvailability() {
- setError(false)
- setNoAvailability(false)
-
- const result = await checkAvailability()
-
- if (result.success) {
- setNewRoomPrice(result.newRoomPrice ?? 0)
- setCurrentStep(MODAL_STEPS.CONFIRMATION)
- } else {
- if (result.noAvailability) {
- setNoAvailability(true)
- }
- if (result.error) {
- setError(true)
- }
- }
- }
-
- useEffect(() => {
- form.setValue("checkInDate", dt(checkInDate).format("YYYY-MM-DD"))
- form.setValue("checkOutDate", dt(checkOutDate).format("YYYY-MM-DD"))
- }, [checkInDate, checkOutDate, form])
-
- function getModalContent() {
- if (bookedRoom && isFirstStep && multiRoom) {
- return (
-
- )
- }
- if (mainRoom && !canChangeDate) {
- return (
-
- )
- }
- if (mainRoom && isFirstStep)
- return (
-
- )
-
- if (mainRoom && !isFirstStep)
- return (
-
- )
-
- if (!mainRoom && isFirstStep)
- return (
-
- )
- }
-
- return (
-
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/actionPanel.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/actionPanel.module.css
deleted file mode 100644
index 77553883e..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/actionPanel.module.css
+++ /dev/null
@@ -1,79 +0,0 @@
-.actionPanel {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x3);
- padding: var(--Spacing-x3);
- width: 100%;
-}
-
-.menu {
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
-}
-
-.actionPanel .menu .button,
-.actionLink {
- width: 100%;
- color: var(--Scandic-Brand-Burgundy);
- justify-content: space-between !important;
- padding: var(--Spacing-x1) 0 !important;
-}
-
-.actionLink {
- font-weight: 500;
- display: flex;
-}
-
-.actionPanel .menu .button:disabled {
- color: var(--Scandic-Grey-40);
-}
-
-.disabledLink {
- color: var(--Scandic-Grey-40);
- display: flex;
- justify-content: space-between;
- padding: var(--Spacing-x1) 0;
- width: 100%;
-}
-.disabledLink:hover {
- cursor: not-allowed;
-}
-
-.info {
- width: 100%;
- background-color: var(--Base-Background-Primary-Normal);
- padding: var(--Spacing-x3);
- text-align: right;
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
- align-items: flex-end;
-}
-
-.tag {
- text-transform: uppercase;
- font-size: 12px;
- font-weight: 600;
- color: var(--Main-Red-60);
- font-family: var(--typography-Caption-Labels-fontFamily);
-}
-
-.link {
- margin-top: auto;
-}
-
-@media (min-width: 1367px) {
- .actionPanel {
- flex-direction: row;
- }
-
- .menu {
- width: 432px;
- }
-
- .info {
- width: 256px;
- }
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/index.tsx
deleted file mode 100644
index f3d906e2a..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/index.tsx
+++ /dev/null
@@ -1,255 +0,0 @@
-"use client"
-import { useIntl } from "react-intl"
-
-import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import { CancellationRuleEnum } from "@/constants/booking"
-import { customerService } from "@/constants/currentWebHrefs"
-import { preliminaryReceipt } from "@/constants/routes/myStay"
-import { useManageStayStore } from "@/stores/my-stay/manageStayStore"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import AddToCalendar from "@/components/HotelReservation/AddToCalendar"
-import { generateDateTime } from "@/components/HotelReservation/BookingConfirmation/Header/Actions/helpers"
-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 Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
-import useLang from "@/hooks/useLang"
-import { trackMyStayPageLink } from "@/utils/tracking"
-
-import AddToCalendarButton from "./Actions/AddToCalendarButton"
-import {
- checkCancelable,
- checkCanDownloadInvoice,
- checkDateModifiable,
- checkGuaranteeable,
- isDatetimePast,
-} from "./utils"
-
-import styles from "./actionPanel.module.css"
-
-import type { EventAttributes } from "ics"
-
-import type { Hotel } from "@/types/hotel"
-
-interface ActionPanelProps {
- hotel: Hotel
-}
-
-export default function ActionPanel({ hotel }: ActionPanelProps) {
- const intl = useIntl()
- const lang = useLang()
- const {
- actions: { setActiveView },
- } = useManageStayStore()
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
-
- const {
- confirmationNumber,
- checkInDate,
- checkOutDate,
- createDateTime,
- canChangeDate,
- priceType,
- } = bookedRoom
-
- const datetimeIsInThePast = isDatetimePast(checkInDate)
-
- const isDateModifyable = checkDateModifiable(
- canChangeDate,
- datetimeIsInThePast,
- bookedRoom.isCancelled,
- priceType === "points"
- )
-
- const isCancelable = checkCancelable(
- bookedRoom.isCancelable,
- datetimeIsInThePast,
- linkedReservationRooms
- )
-
- const isGuaranteeable = checkGuaranteeable(
- !!bookedRoom.guaranteeInfo,
- bookedRoom.isCancelled,
- datetimeIsInThePast
- )
-
- const canDownloadInvoice = checkCanDownloadInvoice(
- bookedRoom.isCancelled,
- bookedRoom.rateDefinition.cancellationRule ===
- CancellationRuleEnum.CancellableBefore6PM
- )
-
- const calendarEvent: EventAttributes = {
- busyStatus: "FREE",
- categories: ["booking", "hotel", "stay"],
- created: generateDateTime(createDateTime),
- description: hotel.hotelContent.texts.descriptions?.medium,
- end: generateDateTime(checkOutDate),
- endInputType: "utc",
- geo: {
- lat: hotel.location.latitude,
- lon: hotel.location.longitude,
- },
- location: `${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city} ${hotel.address.country}`,
- start: generateDateTime(checkInDate),
- startInputType: "utc",
- status: "CONFIRMED",
- title: hotel.name,
- url: hotel.contactInformation.websiteUrl,
- }
-
- const handleModifyStay = () => {
- trackMyStayPageLink("modify dates")
- setActiveView("modifyStay")
- }
-
- const handleCancelStay = () => {
- trackMyStayPageLink("cancel booking")
- setActiveView("cancelStay")
- }
-
- const handleDownloadInvoice = () => {
- trackMyStayPageLink("download invoice")
- }
-
- const handleGuaranteeLateArrival = () => {
- trackMyStayPageLink("guarantee late arrival")
- setActiveView("guaranteeLateArrival")
- }
-
- const handleCustomerSupport = () => {
- trackMyStayPageLink("customer support")
- }
-
- return (
-
-
-
- {intl.formatMessage({
- defaultMessage: "Modify dates",
- })}
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Guarantee late arrival",
- })}
-
-
-
-
(
-
- )}
- />
- {canDownloadInvoice ? (
-
- {intl.formatMessage({
- defaultMessage: "Download invoice",
- })}
-
-
- ) : (
-
-
-
- {intl.formatMessage({
- defaultMessage: "Download invoice",
- })}
-
-
-
-
-
- )}
-
-
- {intl.formatMessage({
- defaultMessage: "Cancel stay",
- })}
-
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Reference number",
- })}
-
-
- {confirmationNumber}
-
-
-
-
- {hotel.name}
-
-
- {hotel.address.streetAddress}
-
-
- {hotel.address.city}
-
-
-
- {hotel.contactInformation.phoneNumber}
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Customer support",
- })}
-
-
-
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/utils.ts b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/utils.ts
deleted file mode 100644
index f4d1389be..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/utils.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { dt } from "@/lib/dt"
-
-import type { Room } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-export function isDatetimePast(date: Date) {
- return dt(date).hour(18).minute(0).second(0).isBefore(dt(), "seconds")
-}
-
-export function checkDateModifiable(
- canChangeDate: boolean,
- datetimeIsInThePast: boolean,
- isCancelled: boolean,
- isRewardNight: boolean
-) {
- return canChangeDate && !datetimeIsInThePast && !isCancelled && !isRewardNight
-}
-
-export function checkCancelable(
- isCancelable: boolean,
- datetimeIsInThePast: boolean,
- linkedReservationRooms: Room[]
-) {
- const hasAnyCancelableRoom =
- isCancelable || linkedReservationRooms.some((room) => room.isCancelable)
-
- return hasAnyCancelableRoom && !datetimeIsInThePast
-}
-
-export function checkGuaranteeable(
- guaranteeInfo: boolean,
- isCancelled: boolean,
- datetimeIsInThePast: boolean
-) {
- return !guaranteeInfo && !isCancelled && !datetimeIsInThePast
-}
-
-export function checkCanDownloadInvoice(
- isCancelled: boolean,
- isFlexBooking: boolean
-) {
- return !isCancelled && !isFlexBooking
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/index.tsx
deleted file mode 100644
index 32b2dbe2d..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/index.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-"use client"
-
-import { useIntl } from "react-intl"
-
-import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
-
-import { useManageStayStore } from "@/stores/my-stay/manageStayStore"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import Modal from "@/components/Modal"
-import Button from "@/components/TempDesignSystem/Button"
-
-import GuaranteeLateArrival from "../GuaranteeLateArrival"
-import CancelStay from "./ActionPanel/Actions/CancelStay"
-import ModifyStay from "./ActionPanel/Actions/ModifyStay"
-import ActionPanel from "./ActionPanel"
-
-import styles from "./manangeStay.module.css"
-
-import type { Hotel } from "@/types/hotel"
-import { type CreditCard } from "@/types/user"
-
-interface ManageStayProps {
- hotel: Hotel
- savedCreditCards: CreditCard[] | null
- refId: string
- isLoggedIn: boolean
-}
-
-export default function ManageStay({
- hotel,
- savedCreditCards,
- refId,
- isLoggedIn,
-}: ManageStayProps) {
- const intl = useIntl()
- const {
- isOpen,
- activeView,
- actions: { setIsOpen, handleCloseModal },
- } = useManageStayStore()
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
-
- const allRoomsCancelled =
- linkedReservationRooms.every((room) => room.isCancelled) &&
- bookedRoom.isCancelled
-
- function renderContent() {
- switch (activeView) {
- case "cancelStay":
- return
- case "modifyStay":
- return
- case "guaranteeLateArrival":
- return (
-
- )
- default:
- return
- }
- }
-
- return (
- <>
- setIsOpen(true)}
- size="small"
- disabled={allRoomsCancelled}
- className={styles.manageStayButton}
- >
- {intl.formatMessage({
- defaultMessage: "Manage stay",
- })}
-
-
- {isOpen && (
-
- {renderContent()}
-
- )}
- >
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/manangeStay.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/manangeStay.module.css
deleted file mode 100644
index 1bfddecda..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/manangeStay.module.css
+++ /dev/null
@@ -1,3 +0,0 @@
-button.manageStayButton {
- color: var(--Text-Inverted);
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/PriceType.tsx b/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/PriceType.tsx
deleted file mode 100644
index 6ff89815c..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/PriceType.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-"use client"
-import { useIntl } from "react-intl"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import Cheques from "../Cheques"
-import Points from "../Points"
-import Price from "../Price"
-
-import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
-import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
-
-interface PriceTypeProps
- extends Pick<
- BookingConfirmation["booking"],
- "cheques" | "rateDefinition" | "roomPoints" | "totalPrice" | "vouchers"
- > {
- isCancelled: boolean
- priceType: PriceTypeEnum
-}
-
-export default function PriceType({
- cheques,
- isCancelled,
- priceType,
- rateDefinition,
- roomPoints,
- totalPrice,
- vouchers,
-}: PriceTypeProps) {
- const intl = useIntl()
-
- switch (priceType) {
- case PriceTypeEnum.cheque:
- return
- case PriceTypeEnum.money:
- return (
-
- )
- case PriceTypeEnum.points:
- return
- case PriceTypeEnum.voucher:
- return (
-
-
- {intl.formatMessage(
- {
- defaultMessage: "{count} voucher",
- },
- { count: vouchers }
- )}
-
-
- )
- default:
- return null
- }
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Points/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Points/index.tsx
deleted file mode 100644
index ae8a39cdb..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/Points/index.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-"use client"
-
-import { useIntl } from "react-intl"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import SkeletonShimmer from "@/components/SkeletonShimmer"
-
-import type { Variant } from "../Rooms/TotalPrice"
-
-export default function Points({
- points,
- variant,
-}: {
- points: number | null
- variant: Variant
-}) {
- const intl = useIntl()
-
- if (points === null) {
- return
- }
-
- return (
-
-
- {intl.formatNumber(points)}
- {
- /* eslint-disable-next-line formatjs/no-literal-string-in-jsx */
- " "
- }
- {intl.formatMessage({
- defaultMessage: "Points",
- })}
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Price/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Price/index.tsx
deleted file mode 100644
index e7c99079d..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/Price/index.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-"use client"
-
-import { useIntl } from "react-intl"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
-
-import SkeletonShimmer from "@/components/SkeletonShimmer"
-import { formatPrice } from "@/utils/numberFormatting"
-
-import styles from "./price.module.css"
-
-import type { Variant } from "../Rooms/TotalPrice"
-
-export default function Price({
- price,
- variant,
- isMember,
-}: {
- price: number | null
- variant: Variant
- isMember?: boolean
-}) {
- const intl = useIntl()
- const currencyCode = useMyStayTotalPriceStore((state) => state.currencyCode)
-
- if (price === null) {
- return
- }
-
- return (
-
-
- {formatPrice(intl, price, currencyCode)}
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/index.tsx
index a69b9f65b..fbe08622f 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/index.tsx
@@ -1,6 +1,6 @@
"use client"
import { dt } from "@/lib/dt"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
+import { useMyStayStore } from "@/stores/my-stay"
import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal"
@@ -9,19 +9,17 @@ import { calculateTotalPrice, mapToPrice } from "./mapToPrice"
import styles from "./priceDetails.module.css"
export default function PriceDetails() {
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
-
- const rooms = [bookedRoom, ...linkedReservationRooms]
- .filter((room) => !room.isCancelled)
- .map((room) => ({
- ...room,
- breakfastIncluded: room.rateDefinition.breakfastIncluded,
- price: mapToPrice(room),
- roomType: room.roomName,
- }))
+ const { bookedRoom, rooms } = useMyStayStore((state) => ({
+ bookedRoom: state.bookedRoom,
+ rooms: state.rooms
+ .filter((room) => !room.isCancelled)
+ .map((room) => ({
+ ...room,
+ breakfastIncluded: room.rateDefinition.breakfastIncluded,
+ price: mapToPrice(room),
+ roomType: room.roomName,
+ })),
+ }))
const bookingCode =
rooms.find((room) => room.bookingCode)?.bookingCode ?? undefined
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/mapToPrice.ts b/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/mapToPrice.ts
index 114710931..814f1aa73 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/mapToPrice.ts
+++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceDetails/mapToPrice.ts
@@ -3,7 +3,7 @@ import { dt } from "@/lib/dt"
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
import type { Price } from "@/types/components/hotelReservation/price"
import { CurrencyEnum } from "@/types/enums/currency"
-import type { Room } from "@/stores/my-stay/myStayRoomDetailsStore"
+import type { Room } from "@/types/stores/my-stay"
export function mapToPrice(room: Room) {
switch (room.priceType) {
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Cheques/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Cheques.tsx
similarity index 63%
rename from apps/scandic-web/components/HotelReservation/MyStay/Cheques/index.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/PriceType/Cheques.tsx
index acab2e125..1ce2acf11 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Cheques/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Cheques.tsx
@@ -3,7 +3,7 @@ import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
-import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
+import { useMyStayStore } from "@/stores/my-stay"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import { formatPrice } from "@/utils/numberFormatting"
@@ -12,16 +12,18 @@ import { CurrencyEnum } from "@/types/enums/currency"
export default function Cheques({
cheques,
+ isCancelled,
price,
}: {
cheques: number
+ isCancelled: boolean
price: number
}) {
const intl = useIntl()
- const currencyCode = useMyStayTotalPriceStore((state) => state.currencyCode)
+ const currency = useMyStayStore((state) => state.bookedRoom.currencyCode)
if (!cheques) {
- return
+ return
}
const totalPrice = formatPrice(
@@ -29,12 +31,12 @@ export default function Cheques({
cheques,
CurrencyEnum.CC,
price,
- currencyCode
+ currency
)
return (
-
- {totalPrice}
+
+ {isCancelled ? {totalPrice} : totalPrice}
)
}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Points.tsx b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Points.tsx
new file mode 100644
index 000000000..da37f1f92
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Points.tsx
@@ -0,0 +1,42 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import SkeletonShimmer from "@/components/SkeletonShimmer"
+import { formatPrice } from "@/utils/numberFormatting"
+
+import { CurrencyEnum } from "@/types/enums/currency"
+
+export default function Points({
+ isCancelled,
+ points,
+ price,
+}: {
+ isCancelled: boolean
+ points: number
+ price: number
+}) {
+ const intl = useIntl()
+ const currency = useMyStayStore((state) => state.bookedRoom.currencyCode)
+
+ if (!points) {
+ return
+ }
+
+ const totalPrice = formatPrice(
+ intl,
+ points,
+ CurrencyEnum.POINTS,
+ price,
+ currency
+ )
+
+ return (
+
+ {isCancelled ? {totalPrice} : totalPrice}
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Price/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Price/index.tsx
new file mode 100644
index 000000000..e3cbb3b3c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Price/index.tsx
@@ -0,0 +1,21 @@
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import styles from "./price.module.css"
+
+export default function Price({
+ isCancelled,
+ isMember,
+ price,
+}: {
+ isCancelled: boolean
+ isMember?: boolean
+ price: string
+}) {
+ return (
+
+
+ {isCancelled ? {price} : price}
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Price/price.module.css b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Price/price.module.css
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/Price/price.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/PriceType/Price/price.module.css
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx
new file mode 100644
index 000000000..1c60a1a0c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/Vouchers.tsx
@@ -0,0 +1,42 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import SkeletonShimmer from "@/components/SkeletonShimmer"
+import { formatPrice } from "@/utils/numberFormatting"
+
+import { CurrencyEnum } from "@/types/enums/currency"
+
+export default function Vouchers({
+ isCancelled,
+ price,
+ vouchers,
+}: {
+ isCancelled: boolean
+ price?: number
+ vouchers: number
+}) {
+ const intl = useIntl()
+ const currency = useMyStayStore((state) => state.bookedRoom.currencyCode)
+
+ if (!vouchers) {
+ return
+ }
+
+ const totalPrice = formatPrice(
+ intl,
+ vouchers,
+ CurrencyEnum.Voucher,
+ price,
+ currency
+ )
+
+ return (
+
+ {isCancelled ? {totalPrice} : totalPrice}
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/PriceType.tsx b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/index.tsx
similarity index 59%
rename from apps/scandic-web/components/HotelReservation/MyStay/PriceType.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/PriceType/index.tsx
index baf37c582..04a7ed7bb 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/PriceType.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/PriceType/index.tsx
@@ -1,11 +1,9 @@
"use client"
-import { useIntl } from "react-intl"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
import Cheques from "./Cheques"
import Points from "./Points"
import Price from "./Price"
+import Vouchers from "./Vouchers"
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
@@ -15,12 +13,14 @@ interface PriceTypeProps
BookingConfirmation["booking"],
"cheques" | "rateDefinition" | "roomPoints" | "totalPrice" | "vouchers"
> {
+ formattedTotalPrice: string
isCancelled: boolean
priceType: PriceTypeEnum
}
export default function PriceType({
cheques,
+ formattedTotalPrice,
isCancelled,
priceType,
rateDefinition,
@@ -28,33 +28,38 @@ export default function PriceType({
totalPrice,
vouchers,
}: PriceTypeProps) {
- const intl = useIntl()
-
switch (priceType) {
case PriceTypeEnum.cheque:
- return
+ return (
+
+ )
case PriceTypeEnum.money:
return (
)
case PriceTypeEnum.points:
- return
+ return (
+
+ )
case PriceTypeEnum.voucher:
return (
-
-
- {intl.formatMessage(
- {
- defaultMessage: "{count} voucher",
- },
- { count: vouchers }
- )}
-
-
+
)
default:
return null
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/CustomerSupport.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/CustomerSupport.tsx
new file mode 100644
index 000000000..9ccaecb9f
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/CustomerSupport.tsx
@@ -0,0 +1,19 @@
+"use client"
+import { DialogTrigger } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import CustomerSupportModal from "@/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal"
+import Button from "@/components/TempDesignSystem/Button"
+
+export default function CustomerSupport() {
+ const intl = useIntl()
+
+ return (
+
+
+ {intl.formatMessage({ defaultMessage: "Customer Support" })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/cancelled.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/cancelled.module.css
new file mode 100644
index 000000000..630fa03ce
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/cancelled.module.css
@@ -0,0 +1,18 @@
+div a.link {
+ align-items: center;
+ background-color: var(--Component-Button-Brand-Tertiary-Fill-Default);
+ border: 2px solid var(--Component-Button-Brand-Tertiary-Border-Default);
+ border-radius: var(--Corner-radius-rounded);
+ color: var(--Text-Inverted);
+ cursor: pointer;
+ display: flex;
+ gap: var(--Space-x1);
+ height: 48px;
+ justify-content: center;
+ padding: var(--Space-x2) var(--Space-x4);
+ transition: background-color 200ms ease;
+
+ &:hover {
+ background-color: var(--Component-Button-Brand-Tertiary-Fill-Hover);
+ }
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/index.tsx
new file mode 100644
index 000000000..5bb109db6
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Cancelled/index.tsx
@@ -0,0 +1,21 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import Link from "@/components/TempDesignSystem/Link"
+
+import CustomerSupport from "./CustomerSupport"
+
+import styles from "./cancelled.module.css"
+
+export default function Cancelled() {
+ const intl = useIntl()
+ return (
+ <>
+ {/* (S) TODO - Link to where?? */}
+
+ {intl.formatMessage({ defaultMessage: "Rebook" })}
+
+
+ >
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/customerSupport.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/customerSupport.module.css
new file mode 100644
index 000000000..c0b9a6d5f
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/customerSupport.module.css
@@ -0,0 +1,30 @@
+.links {
+ display: grid;
+ gap: var(--Space-x05);
+}
+
+.link {
+ align-items: center;
+ background: var(--Surface-Feedback-Information);
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ border-radius: var(--Corner-radius-Medium);
+ color: var(--Text-Interactive-Default);
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x1);
+ padding: var(--Space-x3);
+ /* text-decoration: none; */
+ text-decoration-line: underline;
+ text-decoration-style: solid;
+ text-decoration-skip-ink: none;
+ text-decoration-thickness: auto;
+ text-underline-offset: auto;
+ text-underline-position: from-font;
+}
+
+@media screen and (min-width: 768px) {
+ .links {
+ gap: var(--Space-x3);
+ grid-template-columns: 1fr 1fr;
+ }
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/index.tsx
new file mode 100644
index 000000000..ba4e86abd
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal/index.tsx
@@ -0,0 +1,78 @@
+"use client"
+import Link from "next/link"
+import { Dialog } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+
+import styles from "./customerSupport.module.css"
+
+export default function CustomerSupportModal() {
+ const intl = useIntl()
+ const { email, phone } = useMyStayStore((state) => ({
+ email: state.hotel.contactInformation.email,
+ phone: state.hotel.contactInformation.phoneNumber,
+ }))
+
+ const title = intl.formatMessage({ defaultMessage: "Customer service" })
+ const contact = intl.formatMessage(
+ {
+ defaultMessage:
+ "Please call {phone} or email us at {email} for assistance with your order.",
+ },
+ { email, phone }
+ )
+
+ return (
+
+
+ {({ close }) => (
+
+
+
+ {contact}
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Make a call",
+ })}
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Send an email",
+ })}
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Back" })}
+
+
+ {intl.formatMessage({ defaultMessage: "Close" })}
+
+
+
+ )}
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/AddToCalendarButton.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/AddToCalendarButton.tsx
new file mode 100644
index 000000000..92ff68496
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/AddToCalendarButton.tsx
@@ -0,0 +1,43 @@
+"use client"
+
+import { Button as ButtonRAC } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { trackMyStayPageLink } from "@/utils/tracking"
+
+import styles from "./button.module.css"
+
+export default function AddToCalendarButton({
+ disabled,
+ onPress,
+}: {
+ disabled?: boolean
+ onPress: () => void
+}) {
+ const intl = useIntl()
+
+ function handleAddToCalendar() {
+ trackMyStayPageLink("add to calendar")
+ onPress()
+ }
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Add to calendar",
+ })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/button.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/button.module.css
new file mode 100644
index 000000000..e6b2fafcf
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/button.module.css
@@ -0,0 +1,18 @@
+.button {
+ align-items: center;
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ gap: var(--Space-x1);
+ padding: var(--Space-x1) 0;
+ width: 100%;
+
+ &:disabled {
+ color: var(--Scandic-Grey-40);
+ }
+}
+
+.text {
+ color: var(--Text-Interactive-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/index.tsx
new file mode 100644
index 000000000..1ea0e7e07
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/AddToCalendar/index.tsx
@@ -0,0 +1,57 @@
+"use client"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import AddToCalendar from "@/components/HotelReservation/AddToCalendar"
+import { generateDateTime } from "@/components/HotelReservation/BookingConfirmation/Header/Actions/helpers"
+
+import { dateHasPassed } from "../utils"
+import AddToCalendarButton from "./AddToCalendarButton"
+
+import type { EventAttributes } from "ics"
+
+export default function AddToCalendarAction() {
+ const { checkInDate, checkOutDate, createDateTime, hotel } = useMyStayStore(
+ (state) => ({
+ checkInDate: state.bookedRoom.checkInDate,
+ checkOutDate: state.bookedRoom.checkOutDate,
+ createDateTime: state.bookedRoom.createDateTime,
+ hotel: state.hotel,
+ })
+ )
+
+ const calendarEvent: EventAttributes = {
+ busyStatus: "FREE",
+ categories: ["booking", "hotel", "stay"],
+ created: generateDateTime(createDateTime),
+ description: hotel.hotelContent.texts.descriptions?.medium,
+ end: generateDateTime(checkOutDate),
+ endInputType: "utc",
+ geo: {
+ lat: hotel.location.latitude,
+ lon: hotel.location.longitude,
+ },
+ location: `${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city} ${hotel.address.country}`,
+ start: generateDateTime(checkInDate),
+ startInputType: "utc",
+ status: "CONFIRMED",
+ title: hotel.name,
+ url: hotel.contactInformation.websiteUrl,
+ }
+
+ const disabled = dateHasPassed(
+ checkInDate,
+ hotel.hotelFacts.checkin.checkInTime
+ )
+
+ return (
+ (
+
+ )}
+ />
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Alerts.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Alerts.tsx
new file mode 100644
index 000000000..8afe7f741
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Alerts.tsx
@@ -0,0 +1,46 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import Alert from "@/components/TempDesignSystem/Alert"
+
+import { AlertTypeEnum } from "@/types/enums/alert"
+
+interface AlertsProps extends React.PropsWithChildren {
+ closeModal: () => void
+}
+
+export default function Alerts({ children, closeModal }: AlertsProps) {
+ const intl = useIntl()
+ const mainRoom = useMyStayStore((state) => state.bookedRoom)
+
+ if (!mainRoom) {
+ const title = intl.formatMessage({ defaultMessage: "Cancel stay" })
+ return (
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Back" })}
+
+
+
+ )
+ }
+
+ return <>{children}>
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/CancelStayPriceContainer.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/CancelStayPriceContainer.tsx
new file mode 100644
index 000000000..5b9affdb8
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/CancelStayPriceContainer.tsx
@@ -0,0 +1,78 @@
+"use client"
+import { useWatch } from "react-hook-form"
+import { useIntl } from "react-intl"
+
+import { dt } from "@/lib/dt"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import PriceContainer from "@/components/HotelReservation/MyStay/ReferenceCard/PriceContainer"
+import { formatPrice } from "@/utils/numberFormatting"
+
+import type { CancelStayFormValues } from "@/types/components/hotelReservation/myStay/cancelStay"
+
+export default function CancelStayPriceContainer() {
+ const intl = useIntl()
+
+ const { bookedRoom, nights, rooms } = useMyStayStore((state) => ({
+ bookedRoom: state.bookedRoom,
+ nights: dt(state.bookedRoom.checkOutDate)
+ .startOf("day")
+ .diff(dt(state.bookedRoom.checkInDate).startOf("day"), "days"),
+ rooms: state.rooms,
+ }))
+ const formRooms = useWatch({ name: "rooms" })
+
+ if (!Array.isArray(formRooms)) {
+ return null
+ }
+
+ const { totalAdults, totalChildren } = formRooms.reduce(
+ (total, formRoom) => {
+ if (formRoom.checked) {
+ const room = rooms.find(
+ (r) => r.confirmationNumber === formRoom.confirmationNumber
+ )
+ if (room) {
+ total.totalAdults = total.totalAdults + room.adults
+ if (room.childrenInRoom.length) {
+ total.totalChildren =
+ total.totalChildren + room.childrenInRoom.length
+ }
+ }
+ }
+ return total
+ },
+ { totalAdults: 0, totalChildren: 0 }
+ )
+
+ const adultsText = intl.formatMessage(
+ {
+ defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
+ },
+ { totalAdults: totalAdults }
+ )
+ const childrenText = intl.formatMessage(
+ {
+ defaultMessage:
+ "{totalChildren, plural, one {# child} other {# children}}",
+ },
+ { totalChildren: totalChildren }
+ )
+ const nightsText = intl.formatMessage(
+ {
+ defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
+ },
+ { totalNights: nights }
+ )
+
+ return (
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/index.tsx
new file mode 100644
index 000000000..4c3dbab3c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/index.tsx
@@ -0,0 +1,111 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
+
+import styles from "./multiroom.module.css"
+
+import type { Room } from "@/types/stores/my-stay"
+
+export default function Multiroom() {
+ const intl = useIntl()
+ const rooms = useMyStayStore((state) => state.rooms)
+ const notCancelableRooms = rooms.filter((r) => !r.isCancelable)
+ const cancelableRooms = rooms.filter((r) => !r.isCancelled && r.isCancelable)
+ const isSingleRoom = rooms.length === 1
+
+ if (isSingleRoom) {
+ return null
+ }
+
+ const myRooms = intl.formatMessage({ defaultMessage: "My rooms" })
+ const selectRoom = intl.formatMessage({
+ defaultMessage: "Select room",
+ })
+ const cannotBeCancelled = intl.formatMessage({
+ defaultMessage: "Cannot be cancelled",
+ })
+
+ if (notCancelableRooms.length) {
+ return (
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "This stay has multiple terms.",
+ })}
+
+
+
+
+
+
+
+ )
+ }
+
+ return
+}
+
+interface ListProps {
+ disabled?: boolean
+ rooms: Room[]
+ title: string
+}
+
+function List({ disabled = false, rooms, title }: ListProps) {
+ const intl = useIntl()
+ const refMsg = intl.formatMessage({ defaultMessage: "Ref" })
+ return (
+
+
+ {title}
+
+
+
+ {rooms.map((room) => {
+ const roomNumber = room.roomNumber
+ return (
+
+
+
+
+
+
+ {intl.formatMessage(
+ {
+ defaultMessage: "Room {roomIndex}",
+ },
+ {
+ roomIndex: roomNumber,
+ }
+ )}
+
+
+
+
+ {room.roomName}
+
+
+ {/* eslint-disable formatjs/no-literal-string-in-jsx */}
+
+ {refMsg}: {room.confirmationNumber}
+
+
+
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css
new file mode 100644
index 000000000..8ed5b8e2c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css
@@ -0,0 +1,74 @@
+.wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x3);
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x5);
+}
+
+.rooms {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x1);
+}
+
+.list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x1);
+ list-style: none;
+ margin: 0;
+ padding: var(--Space-x05) 0 0;
+}
+
+.checkbox {
+ background: var(--Background-Primary);
+ border: 2px solid transparent;
+ border-radius: var(--Corner-radius-md);
+ padding: var(--Space-x2) var(--Space-x15);
+}
+
+.checkbox:has(input:checked) {
+ border-color: var(--Border-Interactive-Selected);
+}
+
+.checkbox:has(input:checked) span[class*="checkbox_checkbox_"] {
+ background-color: var(--Surface-UI-Fill-Active);
+}
+
+.checkbox:has(input:disabled) {
+ background-color: var(--Surface-UI-Fill-Disabled);
+ border: 1px solid var(--Border-Interactive-Disabled);
+ cursor: not-allowed;
+}
+
+.checkbox:has(input:disabled) .chip {
+ background-color: var(--Surface-UI-Fill-Disabled);
+ border: 1px solid var(--Text-Interactive-Disabled);
+}
+
+.checkbox:has(input:disabled) p {
+ color: var(--Text-Interactive-Disabled);
+}
+
+.room {
+ align-items: center;
+ display: grid;
+ gap: var(--Space-x1);
+ grid-template-columns: auto 1fr auto;
+ width: 100%;
+}
+
+.chip {
+ background-color: var(--Surface-Brand-Accent-Default);
+ border-radius: var(--Corner-radius-sm);
+ padding: var(--Space-x1);
+}
+
+.chipText {
+ color: var(--Text-Heading);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/confirmation.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/confirmation.module.css
new file mode 100644
index 000000000..8650139bf
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/confirmation.module.css
@@ -0,0 +1,9 @@
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x5);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/index.tsx
new file mode 100644
index 000000000..2b1894a18
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/index.tsx
@@ -0,0 +1,127 @@
+"use client"
+import { useFormContext, useWatch } from "react-hook-form"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { dt } from "@/lib/dt"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import useLang from "@/hooks/useLang"
+
+import CancelStayPriceContainer from "../CancelStayPriceContainer"
+import Multiroom from "./Multiroom"
+
+import styles from "./confirmation.module.css"
+
+import type { CancelStayFormValues } from "@/types/components/hotelReservation/myStay/cancelStay"
+
+interface CancelStayConfirmationProps {
+ closeModal: () => void
+ onSubmit: (data: CancelStayFormValues) => void
+}
+
+export default function CancelStayConfirmation({
+ closeModal,
+ onSubmit,
+}: CancelStayConfirmationProps) {
+ const intl = useIntl()
+ const lang = useLang()
+ const { handleSubmit } = useFormContext()
+ const formRooms = useWatch({ name: "rooms" })
+
+ const { fromDate, hotel, isCancelable, rate, toDate } = useMyStayStore(
+ (state) => ({
+ fromDate: state.bookedRoom.checkInDate,
+ hotel: state.hotel,
+ isCancelable: state.bookedRoom.isCancelable,
+ rate: state.bookedRoom.rate,
+ toDate: state.bookedRoom.checkOutDate,
+ })
+ )
+
+ const checkInDate = dt(fromDate).locale(lang).format("dddd D MMM YYYY")
+ const checkOutDate = dt(toDate).locale(lang).format("dddd D MMM YYYY")
+
+ const title = intl.formatMessage({ defaultMessage: "Cancel booking" })
+ const primaryLabel = intl.formatMessage({
+ defaultMessage: "Cancel stay",
+ })
+ const secondaryLabel = intl.formatMessage({
+ defaultMessage: "Back",
+ })
+
+ const notCancelableText = intl.formatMessage(
+ {
+ defaultMessage:
+ "Your stay has been booked with {rate} terms which unfortunately doesn’t allow for cancellation.",
+ },
+ {
+ rate,
+ strong: (str) => {str} ,
+ }
+ )
+
+ const text = intl.formatMessage(
+ {
+ defaultMessage:
+ "Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.",
+ },
+ {
+ checkInDate,
+ checkOutDate,
+ hotel: hotel.name,
+ strong: (str) => {str} ,
+ }
+ )
+
+ const isValid = Array.isArray(formRooms)
+ ? formRooms.some((r) => r.checked)
+ : false
+
+ return (
+
+
+
+
+ {isCancelable ? text : notCancelableText}
+
+
+
+
+
+
+
+
+ {secondaryLabel}
+
+ {isCancelable ? (
+
+ {primaryLabel}
+
+ ) : (
+
+ {intl.formatMessage({ defaultMessage: "Close" })}
+
+ )}
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/finalConfirmation.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/finalConfirmation.module.css
new file mode 100644
index 000000000..fd87f381b
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/finalConfirmation.module.css
@@ -0,0 +1,9 @@
+.toastContainer {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x05);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/index.tsx
new file mode 100644
index 000000000..3c5cbca8c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/FinalConfirmation/index.tsx
@@ -0,0 +1,170 @@
+"use client"
+import { useWatch } from "react-hook-form"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { trpc } from "@/lib/trpc/client"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import { toast } from "@/components/TempDesignSystem/Toasts"
+import useLang from "@/hooks/useLang"
+
+import CancelStayPriceContainer from "../CancelStayPriceContainer"
+
+import styles from "./finalConfirmation.module.css"
+
+import type { CancelStayFormValues } from "@/types/components/hotelReservation/myStay/cancelStay"
+
+interface FinalConfirmationProps {
+ closeModal: () => void
+}
+
+export default function FinalConfirmation({
+ closeModal,
+}: FinalConfirmationProps) {
+ const intl = useIntl()
+ const lang = useLang()
+ const utils = trpc.useUtils()
+ const formRooms = useWatch({ name: "rooms" })
+ const { bookedRoom, rooms } = useMyStayStore((state) => ({
+ bookedRoom: state.bookedRoom,
+ rooms: state.rooms,
+ }))
+
+ const cancelledStayMsg = intl.formatMessage({
+ defaultMessage: "Your stay was cancelled",
+ })
+ const sorryMsg = intl.formatMessage({
+ defaultMessage: "We’re sorry that things didn’t work out.",
+ })
+
+ const cancelBookingsMutation = trpc.booking.cancelMany.useMutation({
+ onSuccess(data, variables) {
+ const allCancellationsWentThrough = data.every((cancelled) => cancelled)
+ if (allCancellationsWentThrough) {
+ if (data.length === rooms.length) {
+ toast.success(
+
+
+ {cancelledStayMsg}
+
+
+ {sorryMsg}
+
+
+ )
+ } else {
+ const cancelledRooms = rooms.filter((r) =>
+ variables.confirmationNumbers.includes(r.confirmationNumber)
+ )
+ for (const cancelledRoom of cancelledRooms) {
+ toast.success(
+
+
+
+
+ {intl.formatMessage(
+ { defaultMessage: "{roomName} room was cancelled" },
+ { roomName: cancelledRoom.roomName }
+ )}
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage:
+ "Your Stay is still active with the other room",
+ })}
+
+
+
+ )
+ }
+ }
+ } else {
+ toast.warning(
+ intl.formatMessage({
+ defaultMessage:
+ "Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.",
+ })
+ )
+ }
+
+ utils.booking.get.invalidate({
+ confirmationNumber: bookedRoom.confirmationNumber,
+ })
+ utils.booking.linkedReservations.invalidate({
+ lang,
+ rooms: bookedRoom.linkedReservations,
+ })
+ closeModal()
+ },
+ onError() {
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Something went wrong. Please try again later.",
+ })
+ )
+ },
+ })
+
+ function cancelBooking() {
+ if (Array.isArray(formRooms)) {
+ const confirmationNumbersToCancel = formRooms
+ .filter((r) => r.checked)
+ .map((r) => r.confirmationNumber)
+ if (confirmationNumbersToCancel.length) {
+ cancelBookingsMutation.mutate({
+ confirmationNumbers: confirmationNumbersToCancel,
+ language: lang,
+ })
+ }
+ } else {
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Something went wrong. Please try again later.",
+ })
+ )
+ }
+ }
+
+ const confirm = intl.formatMessage({
+ defaultMessage: "Confirm cancellation",
+ })
+ const dontCancel = intl.formatMessage({
+ defaultMessage: "Don't cancel",
+ })
+ const text = intl.formatMessage({
+ defaultMessage: "Are you sure you want to continue with the cancellation?",
+ })
+ const title = intl.formatMessage({
+ defaultMessage: "Cancel booking",
+ })
+
+ return (
+
+
+
+ {text}
+
+
+
+
+
+
+
+ {dontCancel}
+
+
+ {confirm}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/index.tsx
new file mode 100644
index 000000000..c0b110c32
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/index.tsx
@@ -0,0 +1,60 @@
+"use client"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useState } from "react"
+import { FormProvider, useForm } from "react-hook-form"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import CancelStayConfirmation from "./Confirmation"
+import FinalConfirmation from "./FinalConfirmation"
+
+import {
+ type CancelStayFormValues,
+ cancelStaySchema,
+} from "@/types/components/hotelReservation/myStay/cancelStay"
+
+interface StepsProps {
+ closeModal: () => void
+}
+
+export default function Steps({ closeModal }: StepsProps) {
+ const [confirm, setConfirm] = useState(false)
+ const rooms = useMyStayStore((state) => state.rooms)
+
+ const methods = useForm({
+ mode: "onSubmit",
+ reValidateMode: "onChange",
+ resolver: zodResolver(cancelStaySchema),
+ values: {
+ rooms: rooms.map((room, idx) => ({
+ // Single room booking
+ checked: rooms.length === 1,
+ confirmationNumber: room.confirmationNumber,
+ id: idx + 1,
+ })),
+ },
+ })
+
+ function handleSubmit(data: CancelStayFormValues) {
+ const checkedRooms = data.rooms.filter((r) => r.checked)
+ if (checkedRooms.length) {
+ setConfirm(true)
+ }
+ }
+
+ const stepOne = !confirm
+ const stepTwo = confirm
+ return (
+
+ {/* Step 1 */}
+ {stepOne ? (
+
+ ) : null}
+ {/* Step 2 */}
+ {stepTwo ? : null}
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/cancelStay.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/cancelStay.module.css
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/CancelStay/cancelStay.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/cancelStay.module.css
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/index.tsx
new file mode 100644
index 000000000..339a34efe
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/index.tsx
@@ -0,0 +1,28 @@
+"use client"
+import { Dialog, DialogTrigger } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+
+import Alerts from "./Alerts"
+import Steps from "./Steps"
+
+export default function CancelStay() {
+ const intl = useIntl()
+ return (
+
+
+ {intl.formatMessage({ defaultMessage: "Cancel stay" })}
+
+
+
+ {({ close }) => (
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/CannotChangeDate.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/CannotChangeDate.tsx
new file mode 100644
index 000000000..1482b6014
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/CannotChangeDate.tsx
@@ -0,0 +1,42 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import Alert from "@/components/TempDesignSystem/Alert"
+
+import { AlertTypeEnum } from "@/types/enums/alert"
+
+export default function CannotChangeDate({
+ closeModal,
+}: {
+ closeModal: () => void
+}) {
+ const intl = useIntl()
+ return (
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Back" })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/MultiRoomBooking.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/MultiRoomBooking.tsx
new file mode 100644
index 000000000..b9aa33069
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/MultiRoomBooking.tsx
@@ -0,0 +1,42 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import Alert from "@/components/TempDesignSystem/Alert"
+
+import { AlertTypeEnum } from "@/types/enums/alert"
+
+export default function MultiRoomBooking({
+ closeModal,
+}: {
+ closeModal: () => void
+}) {
+ const intl = useIntl()
+ return (
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Back" })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/NotMainRoom.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/NotMainRoom.tsx
new file mode 100644
index 000000000..153722593
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/NotMainRoom.tsx
@@ -0,0 +1,42 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import Alert from "@/components/TempDesignSystem/Alert"
+
+import { AlertTypeEnum } from "@/types/enums/alert"
+
+export default function NotMainRoom({
+ closeModal,
+}: {
+ closeModal: () => void
+}) {
+ const intl = useIntl()
+ return (
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Back" })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/index.tsx
new file mode 100644
index 000000000..10d552583
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Alerts/index.tsx
@@ -0,0 +1,31 @@
+"use client"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import CannotChangeDate from "./CannotChangeDate"
+import MultiRoomBooking from "./MultiRoomBooking"
+import NotMainRoom from "./NotMainRoom"
+
+export default function Alerts({
+ children,
+ closeModal,
+}: React.PropsWithChildren<{ closeModal: () => void }>) {
+ const { canChangeDate, mainRoom, multiRoom } = useMyStayStore((state) => ({
+ canChangeDate: state.bookedRoom.canChangeDate,
+ mainRoom: state.bookedRoom.mainRoom,
+ multiRoom: state.bookedRoom.multiRoom,
+ }))
+
+ if (multiRoom) {
+ return
+ }
+
+ if (!mainRoom) {
+ return
+ }
+
+ if (!canChangeDate) {
+ return
+ }
+
+ return <>{children}>
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/index.tsx
new file mode 100644
index 000000000..70f29f450
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/index.tsx
@@ -0,0 +1,64 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import styles from "./priceAndDate.module.css"
+
+interface PriceAndDateProps {
+ checkInDate: string
+ checkOutDate: string
+ label: string
+ price: string
+ striked?: boolean
+}
+
+export default function PriceAndDate({
+ checkInDate,
+ checkOutDate,
+ label,
+ price,
+ striked = false,
+}: PriceAndDateProps) {
+ const intl = useIntl()
+
+ const checkInMsg = intl.formatMessage({
+ defaultMessage: "Check-in",
+ })
+ const checkOutMsg = intl.formatMessage({
+ defaultMessage: "Check-out",
+ })
+
+ return (
+
+
+
+ {label}
+
+
+ {price}
+
+
+
+
+ {checkInMsg}
+
+
+
+ {striked ? {checkInDate} : checkInDate}
+
+
+
+
+
+ {checkOutMsg}
+
+
+
+ {striked ? {checkOutDate} : checkOutDate}
+
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/priceAndDate.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/priceAndDate.module.css
new file mode 100644
index 000000000..2a15f4968
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/PriceAndDate/priceAndDate.module.css
@@ -0,0 +1,18 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x1);
+}
+
+.item {
+ display: flex;
+ justify-content: space-between;
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
+
+.textSecondary {
+ color: var(--Text-Secondary);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/confirmation.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/confirmation.module.css
new file mode 100644
index 000000000..053bf66a8
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/confirmation.module.css
@@ -0,0 +1,11 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x2);
+}
+
+.dateComparison {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x2);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/index.tsx
new file mode 100644
index 000000000..8d6a65b2a
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Confirmation/index.tsx
@@ -0,0 +1,183 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { dt } from "@/lib/dt"
+import { trpc } from "@/lib/trpc/client"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import PriceContainer from "@/components/HotelReservation/MyStay/ReferenceCard/PriceContainer"
+import Divider from "@/components/TempDesignSystem/Divider"
+import { toast } from "@/components/TempDesignSystem/Toasts"
+import useLang from "@/hooks/useLang"
+
+import PriceAndDate from "./PriceAndDate"
+
+import styles from "./confirmation.module.css"
+
+import type { Lang } from "@/constants/languages"
+
+interface ConfirmationProps {
+ checkInDate: string
+ checkOutDate: string
+ closeModal: () => void
+ newPrice: string
+}
+
+function formatDate(date: Date | string, lang: Lang) {
+ return dt(date).locale(lang).format("dddd, DD MMM, YYYY")
+}
+
+export default function Confirmation({
+ checkInDate,
+ checkOutDate,
+ closeModal,
+ newPrice,
+}: ConfirmationProps) {
+ const intl = useIntl()
+ const lang = useLang()
+ const utils = trpc.useUtils()
+ const { bookedRoom, oldPrice, totalAdults, totalChildren } = useMyStayStore(
+ (state) => ({
+ bookedRoom: state.bookedRoom,
+ oldPrice: state.totalPrice,
+ totalAdults: state.rooms.reduce(
+ (total, room) => total + (room.isCancelled ? 0 : room.adults),
+ 0
+ ),
+ totalChildren: state.rooms.reduce(
+ (total, room) =>
+ total + (room.isCancelled ? 0 : room.childrenInRoom.length),
+ 0
+ ),
+ })
+ )
+
+ const updateBooking = trpc.booking.update.useMutation({
+ onSuccess: (updatedBooking) => {
+ if (updatedBooking) {
+ utils.booking.get.invalidate({
+ confirmationNumber: updatedBooking.confirmationNumber,
+ })
+
+ toast.success(
+ intl.formatMessage({
+ defaultMessage: "Your stay was updated",
+ })
+ )
+
+ closeModal()
+ } else {
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Failed to update your stay",
+ })
+ )
+ }
+ },
+ onError: () => {
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Failed to update your stay",
+ })
+ )
+ },
+ })
+
+ function handleModifyStay() {
+ updateBooking.mutate({
+ confirmationNumber: bookedRoom.confirmationNumber,
+ checkInDate,
+ checkOutDate,
+ })
+ }
+
+ const originalCheckIn = formatDate(bookedRoom.checkInDate, lang)
+ const originalCheckOut = formatDate(bookedRoom.checkOutDate, lang)
+ const newCheckIn = formatDate(checkInDate, lang)
+ const newCheckOut = formatDate(checkOutDate, lang)
+
+ const nights = dt(newCheckOut)
+ .startOf("day")
+ .diff(dt(newCheckIn).startOf("day"), "days")
+
+ const nightsText = intl.formatMessage(
+ {
+ defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
+ },
+ { totalNights: nights }
+ )
+ const newDatesLabel = intl.formatMessage({
+ defaultMessage: "New dates",
+ })
+ const oldDatesLabel = intl.formatMessage({
+ defaultMessage: "Old dates",
+ })
+ const title = intl.formatMessage({
+ defaultMessage: "Confirm date change",
+ })
+ const totalDueMsg = intl.formatMessage({
+ defaultMessage: "Total due",
+ })
+ const adultsText = intl.formatMessage(
+ {
+ defaultMessage: "{totalAdults, plural, one {# adult} other {# adults}}",
+ },
+ { totalAdults: totalAdults }
+ )
+ const childrenText = intl.formatMessage(
+ {
+ defaultMessage:
+ "{totalChildren, plural, one {# child} other {# children}}",
+ },
+ { totalChildren: totalChildren }
+ )
+
+ return (
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Back" })}
+
+
+ {intl.formatMessage({ defaultMessage: "Confirm" })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/Error.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/Error.tsx
new file mode 100644
index 000000000..0a37d635d
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/Error.tsx
@@ -0,0 +1,21 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import Alert from "@/components/TempDesignSystem/Alert"
+
+import { AlertTypeEnum } from "@/types/enums/alert"
+
+export default function Error() {
+ const intl = useIntl()
+ return (
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/NoAvailability.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/NoAvailability.tsx
new file mode 100644
index 000000000..96b8098ec
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/Alerts/NoAvailability.tsx
@@ -0,0 +1,21 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import Alert from "@/components/TempDesignSystem/Alert"
+
+import { AlertTypeEnum } from "@/types/enums/alert"
+
+export default function NoAvailability() {
+ const intl = useIntl()
+ return (
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/CalendarButton/calendarButton.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/CalendarButton/calendarButton.module.css
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/CalendarButton/calendarButton.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/CalendarButton/calendarButton.module.css
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/CalendarButton/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/CalendarButton/index.tsx
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/CalendarButton/index.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/CalendarButton/index.tsx
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/index.tsx
similarity index 64%
rename from apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/index.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/index.tsx
index f8c0701cc..b0040e5bf 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/index.tsx
@@ -1,16 +1,15 @@
-import { da, de, fi, nb, sv } from "date-fns/locale"
-import { useEffect, useState } from "react"
+"use client"
+import { useState } from "react"
import { createPortal } from "react-dom"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
-import { Lang } from "@/constants/languages"
import { dt } from "@/lib/dt"
+import { useMyStayStore } from "@/stores/my-stay"
import DatePickerSingleDesktop from "@/components/DatePicker/Single/Desktop"
import DatePickerSingleMobile from "@/components/DatePicker/Single/Mobile"
import Modal from "@/components/Modal"
-import Alert from "@/components/TempDesignSystem/Alert"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import useLang from "@/hooks/useLang"
@@ -20,55 +19,33 @@ import styles from "./newDates.module.css"
import type { DateRange } from "react-day-picker"
-import { AlertTypeEnum } from "@/types/enums/alert"
-import type { Room } from "@/stores/my-stay/myStayRoomDetailsStore"
+export default function NewDates() {
+ const { checkInDate, checkOutDate } = useMyStayStore((state) => ({
+ checkInDate: state.mainRoom.checkInDate,
+ checkOutDate: state.mainRoom.checkOutDate,
+ }))
-const locales = {
- [Lang.da]: da,
- [Lang.de]: de,
- [Lang.fi]: fi,
- [Lang.no]: nb,
- [Lang.sv]: sv,
-}
-
-interface NewDatesProps {
- mainRoom: Room
- noAvailability: boolean
- error: boolean
-}
-
-export default function NewDates({
- mainRoom,
- noAvailability,
- error,
-}: NewDatesProps) {
const [showCheckInDatePicker, setShowCheckInDatePicker] = useState(false)
const [showCheckOutDatePicker, setShowCheckOutDatePicker] = useState(false)
const [selectedDates, setSelectedDates] = useState(() => ({
- from: dt(mainRoom.checkInDate).startOf("day").toDate(),
- to: dt(mainRoom.checkOutDate).startOf("day").toDate(),
+ from: dt(checkInDate).startOf("day").toDate(),
+ to: dt(checkOutDate).startOf("day").toDate(),
}))
const intl = useIntl()
const lang = useLang()
const { setValue } = useFormContext()
- // Initialize form values on mount
- useEffect(() => {
- setValue("checkInDate", dt(mainRoom.checkInDate).format("YYYY-MM-DD"))
- setValue("checkOutDate", dt(mainRoom.checkOutDate).format("YYYY-MM-DD"))
- }, [mainRoom.checkInDate, mainRoom.checkOutDate, setValue])
-
// Calculate default number of days between check-in and check-out
- const defaultDaysBetween = dt(mainRoom.checkOutDate)
+ const defaultDaysBetween = dt(checkOutDate)
.startOf("day")
- .diff(dt(mainRoom.checkInDate).startOf("day"), "days")
+ .diff(dt(checkInDate).startOf("day"), "days")
function showCheckInPicker() {
// Update selected dates before showing picker
setSelectedDates((prev) => ({
- from: prev.from ?? dt(mainRoom.checkInDate).startOf("day").toDate(),
- to: prev.to ?? dt(mainRoom.checkOutDate).startOf("day").toDate(),
+ from: prev.from ?? dt(checkInDate).startOf("day").toDate(),
+ to: prev.to ?? dt(checkOutDate).startOf("day").toDate(),
}))
setShowCheckInDatePicker(true)
setShowCheckOutDatePicker(false)
@@ -77,8 +54,8 @@ export default function NewDates({
function showCheckOutPicker() {
// Update selected dates before showing picker
setSelectedDates((prev) => ({
- from: prev.from ?? dt(mainRoom.checkInDate).startOf("day").toDate(),
- to: prev.to ?? dt(mainRoom.checkOutDate).startOf("day").toDate(),
+ from: prev.from ?? dt(checkInDate).startOf("day").toDate(),
+ to: prev.to ?? dt(checkOutDate).startOf("day").toDate(),
}))
setShowCheckOutDatePicker(true)
setShowCheckInDatePicker(false)
@@ -126,30 +103,11 @@ export default function NewDates({
setValue("checkOutDate", newCheckOut.format("YYYY-MM-DD"))
}
+ const fromDate = selectedDates.from ?? dt(checkInDate).toDate()
+ const toDate = selectedDates.to ?? dt(checkOutDate).toDate()
+
return (
<>
- {noAvailability && (
-
- )}
- {error && (
-
- )}
@@ -190,21 +148,13 @@ export default function NewDates({
setShowCheckInDatePicker(false)}
handleOnSelect={handleCheckInDateSelect}
- locales={locales}
- selectedDate={
- selectedDates.from ?? dt(mainRoom.checkInDate).toDate()
- }
- startMonth={
- selectedDates.from ?? dt(mainRoom.checkInDate).toDate()
- }
+ selectedDate={fromDate}
+ startMonth={fromDate}
/>
setShowCheckInDatePicker(false)}
handleOnSelect={handleCheckInDateSelect}
- locales={locales}
- selectedDate={
- selectedDates.from ?? dt(mainRoom.checkInDate).toDate()
- }
+ selectedDate={fromDate}
hideHeader
/>
,
@@ -220,21 +170,13 @@ export default function NewDates({
setShowCheckOutDatePicker(false)}
handleOnSelect={handleCheckOutDateSelect}
- locales={locales}
- selectedDate={
- selectedDates.to ?? dt(mainRoom.checkOutDate).toDate()
- }
- startMonth={
- selectedDates.to ?? dt(mainRoom.checkOutDate).toDate()
- }
+ selectedDate={toDate}
+ startMonth={toDate}
/>
setShowCheckOutDatePicker(false)}
handleOnSelect={handleCheckOutDateSelect}
- locales={locales}
- selectedDate={
- selectedDates.to ?? dt(mainRoom.checkOutDate).toDate()
- }
+ selectedDate={toDate}
hideHeader
/>
,
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/newDates.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/newDates.module.css
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/Actions/ModifyStay/NewDates/newDates.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/NewDates/newDates.module.css
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/index.tsx
new file mode 100644
index 000000000..c65908fcd
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/Form/index.tsx
@@ -0,0 +1,85 @@
+"use client"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { FormProvider, useForm } from "react-hook-form"
+import { useIntl } from "react-intl"
+
+import { dt } from "@/lib/dt"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+import { toast } from "@/components/TempDesignSystem/Toasts"
+
+import NoAvailability from "./Alerts/NoAvailability"
+import NewDates from "./NewDates"
+
+import {
+ type ChangeDatesFormProps,
+ type ChangeDatesSchema,
+ changeDatesSchema,
+} from "@/types/components/hotelReservation/myStay/changeDates"
+
+export default function Form({
+ checkAvailability,
+ closeModal,
+ noAvailability,
+}: ChangeDatesFormProps) {
+ const intl = useIntl()
+
+ const { checkInDate, checkOutDate } = useMyStayStore((state) => ({
+ checkInDate: state.bookedRoom.checkInDate,
+ checkOutDate: state.bookedRoom.checkOutDate,
+ }))
+
+ const methods = useForm({
+ defaultValues: {
+ checkInDate: dt(checkInDate).format("YYYY-MM-DD"),
+ checkOutDate: dt(checkOutDate).format("YYYY-MM-DD"),
+ },
+ resolver: zodResolver(changeDatesSchema),
+ })
+
+ async function handleSubmit(values: ChangeDatesSchema) {
+ if (values.checkInDate && values.checkOutDate) {
+ await checkAvailability(values.checkInDate, values.checkOutDate)
+ } else {
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Please select dates",
+ })
+ )
+ }
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx
new file mode 100644
index 000000000..96338dacf
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/Steps/index.tsx
@@ -0,0 +1,136 @@
+"use client"
+import { useSession } from "next-auth/react"
+import { useState } from "react"
+import { useIntl } from "react-intl"
+
+import { trpc } from "@/lib/trpc/client"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import { sumPackages } from "@/components/HotelReservation/utils"
+import useLang from "@/hooks/useLang"
+import { isValidClientSession } from "@/utils/clientSession"
+import { formatPrice } from "@/utils/numberFormatting"
+
+import Confirmation from "./Confirmation"
+import Form from "./Form"
+
+import type { ChangeDatesStepsProps } from "@/types/components/hotelReservation/myStay/changeDates"
+import { CurrencyEnum } from "@/types/enums/currency"
+
+interface Dates {
+ fromDate: string
+ toDate: string
+}
+
+export default function Steps({ closeModal }: ChangeDatesStepsProps) {
+ const { data: session } = useSession()
+ const isLoggedIn = isValidClientSession(session)
+ const intl = useIntl()
+ const lang = useLang()
+ const utils = trpc.useUtils()
+ const [dates, setDates] = useState(null)
+ const [newPrice, setNewPrice] = useState(null)
+ const [noAvailability, setNoAvailability] = useState(false)
+
+ const { breakfast, currencyCode, hotelId, packages, room } = useMyStayStore(
+ (state) => ({
+ breakfast: state.bookedRoom.breakfast,
+ currencyCode: state.bookedRoom.currencyCode,
+ hotelId: state.bookedRoom.hotelId,
+ packages: state.bookedRoom.packages ?? [],
+ room: {
+ adults: state.bookedRoom.adults,
+ bookingCode: state.bookedRoom.bookingCode ?? undefined,
+ childrenInRoom: state.bookedRoom.childrenInRoom,
+ rateCode: state.bookedRoom.rateDefinition.rateCode,
+ roomTypeCode: state.bookedRoom.roomTypeCode,
+ },
+ })
+ )
+
+ async function checkAvailability(fromDate: string, toDate: string) {
+ setNoAvailability(false)
+
+ const data = await utils.hotel.availability.myStay.fetch({
+ booking: { fromDate, hotelId, room, toDate },
+ lang,
+ })
+
+ if (!data || !data.selectedRoom || !data.selectedRoom.roomsLeft) {
+ setNoAvailability(true)
+ return
+ }
+
+ setDates({ fromDate, toDate })
+
+ const pkgsSum = sumPackages(packages)
+ const extraPrice = pkgsSum.price + (breakfast?.localPrice.totalPrice || 0)
+ if (isLoggedIn && "member" in data.product && data.product.member) {
+ const { currency, pricePerStay } = data.product.member.localPrice
+ setNewPrice(formatPrice(intl, pricePerStay + extraPrice, currency))
+ } else if ("public" in data.product && data.product.public) {
+ const { currency, pricePerStay } = data.product.public.localPrice
+ setNewPrice(formatPrice(intl, pricePerStay + extraPrice, currency))
+ } else if (
+ "corporateCheque" in data.product &&
+ data.product.corporateCheque.localPrice.additionalPricePerStay
+ ) {
+ const { additionalPricePerStay, currency, numberOfCheques } =
+ data.product.corporateCheque.localPrice
+ setNewPrice(
+ formatPrice(
+ intl,
+ numberOfCheques,
+ CurrencyEnum.CC,
+ additionalPricePerStay + extraPrice,
+ currency?.toString() ?? pkgsSum.currency ?? currencyCode
+ )
+ )
+ } else if (
+ "redemption" in data.product &&
+ data.product.redemption.localPrice.additionalPricePerStay
+ ) {
+ const { additionalPricePerStay, currency, pointsPerStay } =
+ data.product.redemption.localPrice
+ setNewPrice(
+ formatPrice(
+ intl,
+ pointsPerStay,
+ CurrencyEnum.POINTS,
+ additionalPricePerStay + extraPrice,
+ currency?.toString() ?? pkgsSum.currency ?? currencyCode
+ )
+ )
+ }
+ }
+
+ function goBackToSelectDates() {
+ setNewPrice(null)
+ setDates(null)
+ setNoAvailability(false)
+ }
+
+ const hasNewDate = newPrice && dates
+
+ const stepOne = !hasNewDate
+ const stepTwo = hasNewDate
+ return (
+ <>
+ {stepOne ? (
+
+ ) : null}
+ {stepTwo ? (
+
+ ) : null}
+ >
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/index.tsx
new file mode 100644
index 000000000..064f45ecf
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ChangeDates/index.tsx
@@ -0,0 +1,49 @@
+"use client"
+import { Dialog, DialogTrigger } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+
+import { dateHasPassed } from "../utils"
+import Alerts from "./Alerts"
+import Steps from "./Steps"
+
+export default function ChangeDates() {
+ const intl = useIntl()
+
+ const { canChangeDate, checkInDate, checkInTime, isCancelled, priceType } =
+ useMyStayStore((state) => ({
+ canChangeDate: state.bookedRoom.canChangeDate,
+ checkInDate: state.bookedRoom.checkInDate,
+ checkInTime: state.hotel.hotelFacts.checkin.checkInTime,
+ isCancelled: state.bookedRoom.isCancelled,
+ priceType: state.bookedRoom.priceType,
+ }))
+
+ const isRewardNight = priceType === "points"
+ const isDisabled =
+ canChangeDate &&
+ !isCancelled &&
+ !isRewardNight &&
+ dateHasPassed(checkInDate, checkInTime)
+
+ const text = intl.formatMessage({ defaultMessage: "Change dates" })
+ return (
+
+
+ {text}
+
+
+
+ {({ close }) => (
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CustomerSupport/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CustomerSupport/index.tsx
new file mode 100644
index 000000000..1a3798eef
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CustomerSupport/index.tsx
@@ -0,0 +1,18 @@
+"use client"
+import { DialogTrigger } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import CustomerSupportModal from "@/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal"
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+
+export default function CustomerSupport() {
+ const intl = useIntl()
+ return (
+
+
+ {intl.formatMessage({ defaultMessage: "Customer support" })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/guaranteeLateArrival.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/form.module.css
similarity index 54%
rename from apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/guaranteeLateArrival.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/form.module.css
index ec20a477a..94d862acd 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/guaranteeLateArrival.module.css
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/form.module.css
@@ -1,52 +1,3 @@
-.card {
- display: flex;
- align-items: center;
- gap: var(--Spacing-x1);
- padding: var(--Spacing-x2) var(--Spacing-x-one-and-half);
- border-radius: var(--Corner-radius-Medium);
- background-color: var(--Base-Surface-Subtle-Normal);
-}
-
-.addCreditCard {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
-}
-
-.guaranteeCost {
- display: flex;
- justify-content: flex-end;
- padding: var(--Spacing-x2);
- align-items: flex-end;
- gap: var(--Spacing-x3);
- border-radius: var(--Corner-radius-Medium);
- background-color: var(--Base-Surface-Subtle-Normal);
-}
-
-.guaranteeCostText {
- display: flex;
- flex-direction: column;
-}
-
-.termsAndConditions {
- display: grid;
- gap: var(--Spacing-x2);
- color: var(--Text-Secondary);
-}
-
-.section {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
-}
-
-.paymentOptionContainer {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x-one-and-half);
-}
-
.loading {
display: flex;
align-items: center;
@@ -56,3 +7,42 @@
height: 640px;
max-height: 100%;
}
+
+.form {
+ display: grid;
+ gap: var(--Spacing-x3);
+}
+
+.termsAndConditions {
+ color: var(--Text-Secondary);
+ display: grid;
+ gap: var(--Spacing-x2);
+}
+
+.termsAndConditions .checkbox span {
+ align-items: flex-start;
+}
+
+.guaranteeCost {
+ align-items: center;
+ background-color: var(--Base-Surface-Subtle-Normal);
+ border-radius: var(--Corner-radius-Medium);
+ display: flex;
+ gap: var(--Spacing-x3);
+ justify-content: flex-end;
+ padding: var(--Spacing-x2);
+}
+
+.guaranteeCostText {
+ align-items: flex-end;
+ display: flex;
+ flex-direction: column;
+}
+
+.baseTextHighContrast {
+ color: var(--Base-Text-High-contrast);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/index.tsx
new file mode 100644
index 000000000..2f7ee30cb
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/index.tsx
@@ -0,0 +1,194 @@
+"use client"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { FormProvider, useForm } from "react-hook-form"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { PaymentMethodEnum } from "@/constants/booking"
+import {
+ bookingTermsAndConditions,
+ privacyPolicy,
+} from "@/constants/currentWebHrefs"
+import { guaranteeCallback } from "@/constants/routes/hotelReservation"
+import { env } from "@/env/client"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import PaymentOptionsGroup from "@/components/HotelReservation/EnterDetails/Payment/PaymentOptionsGroup"
+import MySavedCards from "@/components/HotelReservation/MySavedCards"
+import PaymentOption from "@/components/HotelReservation/PaymentOption"
+import LoadingSpinner from "@/components/LoadingSpinner"
+import Divider from "@/components/TempDesignSystem/Divider"
+import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
+import Link from "@/components/TempDesignSystem/Link"
+import { toast } from "@/components/TempDesignSystem/Toasts"
+import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking"
+import useLang from "@/hooks/useLang"
+import { formatPrice } from "@/utils/numberFormatting"
+import { trackGlaSaveCardAttempt } from "@/utils/tracking/myStay"
+
+import { type GuaranteeFormData, paymentSchema } from "./schema"
+
+import styles from "./form.module.css"
+
+export default function Form() {
+ const intl = useIntl()
+ const lang = useLang()
+
+ const { confirmationNumber, currencyCode, hotelId, refId, savedCreditCards } =
+ useMyStayStore((state) => ({
+ confirmationNumber: state.bookedRoom.confirmationNumber,
+ currencyCode: state.bookedRoom.currencyCode,
+ hotelId: state.bookedRoom.hotelId,
+ refId: state.refId,
+ savedCreditCards: state.savedCreditCards,
+ }))
+
+ const methods = useForm({
+ defaultValues: {
+ paymentMethod: savedCreditCards?.length
+ ? savedCreditCards[0].id
+ : PaymentMethodEnum.card,
+ termsAndConditions: false,
+ },
+ mode: "all",
+ reValidateMode: "onChange",
+ resolver: zodResolver(paymentSchema),
+ })
+
+ const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang)}`
+
+ const { guaranteeBooking, isLoading, handleGuaranteeError } =
+ useGuaranteeBooking(confirmationNumber)
+
+ if (isLoading) {
+ return (
+
+
+
+ )
+ }
+
+ function handleGuaranteeLateArrival(data: GuaranteeFormData) {
+ const savedCreditCard = savedCreditCards?.find(
+ (card) => card.id === data.paymentMethod
+ )
+ trackGlaSaveCardAttempt(hotelId, savedCreditCard, "yes")
+ if (confirmationNumber) {
+ const card = savedCreditCard
+ ? {
+ alias: savedCreditCard.alias,
+ expiryDate: savedCreditCard.expirationDate,
+ cardType: savedCreditCard.cardType,
+ }
+ : undefined
+ guaranteeBooking.mutate({
+ confirmationNumber,
+ language: lang,
+ ...(card && { card }),
+ success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}`,
+ error: `${guaranteeRedirectUrl}?status=error&RefId=${encodeURIComponent(refId)}`,
+ cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}`,
+ })
+ } else {
+ handleGuaranteeError("No confirmation number")
+ toast.error(
+ intl.formatMessage({
+ defaultMessage: "Something went wrong!",
+ })
+ )
+ }
+ }
+
+ const guaranteeMsg = intl.formatMessage(
+ {
+ defaultMessage:
+ "By guaranteeing with any of the payment methods available, I accept the terms for this stay and the general Terms & Conditions , and understand Scandic will process my personal data for this stay in accordance with Scandic's Privacy Policy . I accept Scandic requiring a valid credit card during my visit in case anything is left unpaid.",
+ },
+ {
+ termsAndConditionsLink: (str) => (
+
+ {str}
+
+ ),
+ privacyPolicyLink: (str) => (
+
+ {str}
+
+ ),
+ }
+ )
+
+ return (
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/schema.ts b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/schema.ts
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/schema.ts
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/Form/schema.ts
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/index.tsx
new file mode 100644
index 000000000..9f24f003a
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/GuaranteeLateArrival/index.tsx
@@ -0,0 +1,69 @@
+"use client"
+import { Dialog, DialogTrigger } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+
+import { dateHasPassed } from "../utils"
+import Form from "./Form"
+
+export default function GuaranteeLateArrival() {
+ const intl = useIntl()
+
+ const { checkInDate, checkInTime, guaranteeInfo, isCancelled } =
+ useMyStayStore((state) => ({
+ checkInDate: state.bookedRoom.checkInDate,
+ checkInTime: state.hotel.hotelFacts.checkin.checkInTime,
+ guaranteeInfo: state.bookedRoom.guaranteeInfo,
+ isCancelled: state.bookedRoom.isCancelled,
+ }))
+
+ const guaranteeable =
+ !guaranteeInfo && !isCancelled && !dateHasPassed(checkInDate, checkInTime)
+
+ if (!guaranteeable) {
+ return null
+ }
+
+ const arriveLateMsg = intl.formatMessage({
+ defaultMessage:
+ "Planning to arrive after 18.00? Secure your room by guaranteeing it with a credit card. Without the guarantee and in case of no-show, the room might be reallocated after 18:00.",
+ })
+ const text = intl.formatMessage({
+ defaultMessage: "Guarantee late arrival",
+ })
+
+ return (
+
+ {text}
+
+
+ {({ close }) => (
+
+
+
+ {arriveLateMsg}
+
+
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Back" })}
+
+
+ {intl.formatMessage({ defaultMessage: "Guarantee" })}
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/index.tsx
new file mode 100644
index 000000000..f316bf689
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/index.tsx
@@ -0,0 +1,56 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { CancellationRuleEnum } from "@/constants/booking"
+import { preliminaryReceipt } from "@/constants/routes/myStay"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Link from "@/components/TempDesignSystem/Link"
+import useLang from "@/hooks/useLang"
+import { trackMyStayPageLink } from "@/utils/tracking"
+
+import styles from "./view.module.css"
+
+export default function ViewAndPrintReceipt() {
+ const intl = useIntl()
+ const lang = useLang()
+ const canDownloadInvoice = useMyStayStore(
+ (state) =>
+ !state.bookedRoom.isCancelled &&
+ !(
+ state.bookedRoom.rateDefinition.cancellationRule ===
+ CancellationRuleEnum.CancellableBefore6PM
+ )
+ )
+
+ if (!canDownloadInvoice) {
+ return null
+ }
+
+ function trackClick() {
+ trackMyStayPageLink("download invoice")
+ }
+
+ const printMsg = intl.formatMessage({
+ defaultMessage: "View and print receipt",
+ })
+
+ return (
+
+
+
+
+ {printMsg}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/view.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/view.module.css
new file mode 100644
index 000000000..bee715a3b
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/ViewAndPrintReceipt/view.module.css
@@ -0,0 +1,7 @@
+.download {
+ align-items: center;
+ color: var(--Text-Interactive-Default);
+ display: flex;
+ gap: var(--Space-x1);
+ padding: var(--Space-x1) 0;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/actions.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/actions.module.css
new file mode 100644
index 000000000..2cf353a24
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/actions.module.css
@@ -0,0 +1,5 @@
+.list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/index.tsx
new file mode 100644
index 000000000..0600fd4c7
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/index.tsx
@@ -0,0 +1,21 @@
+import AddToCalendar from "./AddToCalendar"
+import CancelStay from "./CancelStay"
+import ChangeDates from "./ChangeDates"
+import CustomerSupport from "./CustomerSupport"
+import GuaranteeLateArrival from "./GuaranteeLateArrival"
+import ViewAndPrintReceipt from "./ViewAndPrintReceipt"
+
+import styles from "./actions.module.css"
+
+export default function Actions() {
+ return (
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/utils.ts b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/utils.ts
new file mode 100644
index 000000000..42cfb4097
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/utils.ts
@@ -0,0 +1,7 @@
+import { dt } from "@/lib/dt"
+
+export function dateHasPassed(date: Date, time: string) {
+ const hour = dt(time, "HH:mm").hour()
+ const minute = dt(time, "HH:mm").minute()
+ return dt(date).hour(hour).minute(minute).isBefore(dt(), "minutes")
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/index.tsx
new file mode 100644
index 000000000..9b5e36402
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/index.tsx
@@ -0,0 +1,44 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import styles from "./info.module.css"
+
+export default function Info() {
+ const intl = useIntl()
+ const text = intl.formatMessage({ defaultMessage: "Booking number" })
+
+ const { address, confirmationNumber, hotelName, phoneNumber } =
+ useMyStayStore((state) => ({
+ address: state.hotel.address,
+ confirmationNumber: state.bookedRoom.confirmationNumber,
+ hotelName: state.hotel.name,
+ phoneNumber: state.hotel.contactInformation.phoneNumber,
+ }))
+
+ return (
+
+
+
+ {text}
+
+
+
+ {confirmationNumber}
+
+
+
+
+
+ {hotelName}
+ {address.streetAddress}
+ {address.city}
+ {phoneNumber}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/info.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/info.module.css
new file mode 100644
index 000000000..911d67310
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Info/info.module.css
@@ -0,0 +1,29 @@
+.container {
+ align-items: flex-start;
+ background-color: var(--Surface-Primary-OnSurface-Default);
+ border-radius: var(--Corner-radius-md);
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x2);
+ justify-content: center;
+ padding: var(--Space-x15) var(--Space-x3);
+}
+
+.booking {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x05);
+}
+
+.text {
+ color: var(--Text-Default);
+}
+
+.confirmationNumber {
+ color: var(--Text-Heading);
+}
+
+.address {
+ display: flex;
+ flex-direction: column;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/index.tsx
new file mode 100644
index 000000000..b4471d3fc
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/index.tsx
@@ -0,0 +1,65 @@
+"use client"
+import {
+ Button as ButtonRAC,
+ Dialog,
+ DialogTrigger,
+} from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Modal from "@/components/HotelReservation/MyStay/ReferenceCard/Modal"
+
+import Actions from "./Actions"
+import Info from "./Info"
+
+import styles from "./manageStay.module.css"
+
+export default function ManageStay() {
+ const intl = useIntl()
+ const allRoomsAreCancelled = useMyStayStore(
+ (state) => state.allRoomsAreCancelled
+ )
+
+ const color = allRoomsAreCancelled
+ ? "Icon/Interactive/Disabled"
+ : "Icon/Inverted"
+
+ const manageStay = intl.formatMessage({
+ defaultMessage: "Manage stay",
+ })
+
+ return (
+
+
+
+ {manageStay}
+
+
+
+
+
+ {({ close }) => (
+ <>
+
+
+ {manageStay}
+
+
+
+
+
+
+ >
+ )}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/manageStay.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/manageStay.module.css
new file mode 100644
index 000000000..eb1c294cb
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/manageStay.module.css
@@ -0,0 +1,52 @@
+.trigger {
+ align-items: center;
+ background-color: var(--Component-Button-Brand-Tertiary-Fill-Default);
+ border: 2px solid var(--Component-Button-Brand-Tertiary-Border-Default);
+ border-radius: var(--Corner-radius-rounded);
+ color: var(--Text-Inverted);
+ cursor: pointer;
+ display: flex;
+ gap: var(--Space-x1);
+ height: 48px;
+ justify-content: center;
+ padding: var(--Space-x2) var(--Space-x4);
+ transition: background-color 200ms ease;
+
+ &:hover {
+ background-color: var(--Component-Button-Brand-Tertiary-Fill-Hover);
+ }
+
+ &:disabled {
+ background-color: var(--Component-Button-Brand-Tertiary-Fill-Disabled);
+ cursor: not-allowed;
+ }
+}
+
+.dialog {
+ display: grid;
+ gap: var(--Space-x3);
+}
+
+.header {
+ align-items: center;
+ display: flex;
+ gap: var(--Space-x2);
+ justify-content: space-between;
+}
+
+.title {
+ color: var(--Text-Default);
+}
+
+.close {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+}
+
+.content {
+ display: grid;
+ gap: var(--Space-x3);
+ grid-template-columns: 1fr 1fr;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/index.tsx
new file mode 100644
index 000000000..d0a9014f7
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/index.tsx
@@ -0,0 +1,34 @@
+"use client"
+import Link from "next/link"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import ManageStay from "./ManageStay"
+
+import styles from "./notCancelled.module.css"
+
+export default function NotCancelled() {
+ const intl = useIntl()
+ const location = useMyStayStore((state) => state.hotel.location)
+
+ const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${location.latitude},${location.longitude}`
+ return (
+ <>
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Find us",
+ })}
+
+
+
+
+ >
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/notCancelled.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/notCancelled.module.css
new file mode 100644
index 000000000..279f6bc24
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/notCancelled.module.css
@@ -0,0 +1,9 @@
+.link {
+ align-items: center;
+ border: 2px solid var(--Component-Button-Brand-Secondary-Border-Default);
+ border-radius: var(--Corner-radius-rounded);
+ color: var(--Text-Interactive-Default);
+ display: flex;
+ justify-content: center;
+ text-decoration: none;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/actions.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/actions.module.css
new file mode 100644
index 000000000..96af8da99
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/actions.module.css
@@ -0,0 +1,12 @@
+.actionArea {
+ display: grid;
+ gap: var(--Spacing-x2);
+}
+
+@media (min-width: 768px) {
+ .actionArea {
+ gap: var(--Spacing-x2);
+ grid-template-columns: 1fr 1fr;
+ padding-top: var(--Spacing-x3);
+ }
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/index.tsx
new file mode 100644
index 000000000..504a67c86
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/index.tsx
@@ -0,0 +1,16 @@
+"use client"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Cancelled from "./Cancelled"
+import NotCancelled from "./NotCancelled"
+
+import styles from "./actions.module.css"
+
+export default function Actions() {
+ const isCancelled = useMyStayStore((state) => state.bookedRoom.isCancelled)
+ return (
+
+ {isCancelled ? : }
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice/totalPrice.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/BookingCode/bookingCode.module.css
similarity index 50%
rename from apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice/totalPrice.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/BookingCode/bookingCode.module.css
index b28e47db1..8e6197099 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice/totalPrice.module.css
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/BookingCode/bookingCode.module.css
@@ -1,5 +1,5 @@
-.totalPrice {
- display: flex;
+.row {
align-items: center;
- gap: 10px;
+ display: flex;
+ justify-content: space-between;
}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/BookingCode/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/BookingCode/index.tsx
new file mode 100644
index 000000000..4230d4885
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/BookingCode/index.tsx
@@ -0,0 +1,41 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import IconChip from "@/components/TempDesignSystem/IconChip"
+
+import styles from "./bookingCode.module.css"
+
+export default function BookingCode() {
+ const intl = useIntl()
+ const bookingCode = useMyStayStore((state) => state.bookedRoom.bookingCode)
+
+ if (!bookingCode) {
+ return null
+ }
+
+ return (
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Booking code",
+ })}
+
+
+
+
}
+ >
+
+ {bookingCode}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/cancellations.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/cancellations.module.css
new file mode 100644
index 000000000..6ae0066a8
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/cancellations.module.css
@@ -0,0 +1,16 @@
+.row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
+
+.label {
+ align-items: center;
+ display: flex;
+ gap: var(--Space-x1);
+}
+
+.row .textDefault {
+ color: var(--Text-Default);
+ text-transform: capitalize;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/index.tsx
new file mode 100644
index 000000000..5e60ca1be
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Cancellations/index.tsx
@@ -0,0 +1,43 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import styles from "./cancellations.module.css"
+
+export default function Cancellations() {
+ const intl = useIntl()
+ const rooms = useMyStayStore((state) => state.rooms)
+ const cancelledRooms = rooms.filter((r) => r.isCancelled).length
+
+ if (!cancelledRooms) {
+ return null
+ }
+
+ const totalRoomsMsg = intl.formatMessage(
+ {
+ defaultMessage: "{totalRooms, plural, one {# room} other {# rooms}}",
+ },
+ { totalRooms: cancelledRooms }
+ )
+
+ return (
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Cancellations" })}
+
+
+
+
+
+ {totalRoomsMsg}
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/dates.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/dates.module.css
new file mode 100644
index 000000000..fe9adce2d
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/dates.module.css
@@ -0,0 +1,15 @@
+.row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
+
+.label {
+ align-items: center;
+ display: flex;
+ gap: var(--Space-x1);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/index.tsx
new file mode 100644
index 000000000..5b436d4aa
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Dates/index.tsx
@@ -0,0 +1,53 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { dt } from "@/lib/dt"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import useLang from "@/hooks/useLang"
+
+import styles from "./dates.module.css"
+
+export default function Dates() {
+ const intl = useIntl()
+ const lang = useLang()
+ const { checkInDate, checkOutDate } = useMyStayStore((state) => ({
+ checkInDate: state.bookedRoom.checkInDate,
+ checkOutDate: state.bookedRoom.checkOutDate,
+ }))
+
+ const from = dt(checkInDate).locale(lang).format("D MMM")
+ const fromYear = dt(checkInDate).year()
+ const to = dt(checkOutDate).locale(lang).format("D MMM")
+ const toYear = dt(checkOutDate).year()
+
+ const isSameYear = fromYear === toYear
+
+ const stayFrom = isSameYear ? from : `${from}, ${fromYear}`
+ const stayTo = `${to}, ${toYear}`
+
+ return (
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Dates",
+ })}
+
+
+
+
+
+
+ {/* eslint-disable formatjs/no-literal-string-in-jsx */}
+ {stayFrom} → {stayTo}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/guaranteeInfo.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/guaranteeInfo.module.css
new file mode 100644
index 000000000..fe9adce2d
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/guaranteeInfo.module.css
@@ -0,0 +1,15 @@
+.row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
+
+.label {
+ align-items: center;
+ display: flex;
+ gap: var(--Space-x1);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/index.tsx
new file mode 100644
index 000000000..f260c0756
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/GuaranteeInfo/index.tsx
@@ -0,0 +1,43 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import styles from "./guaranteeInfo.module.css"
+
+export default function GuaranteeInfo() {
+ const intl = useIntl()
+ const { allRoomsAreCancelled, guaranteeInfo } = useMyStayStore((state) => ({
+ allRoomsAreCancelled: state.allRoomsAreCancelled,
+ guaranteeInfo: state.bookedRoom.guaranteeInfo,
+ }))
+
+ if (allRoomsAreCancelled || !guaranteeInfo) {
+ return null
+ }
+
+ return (
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Late arrival",
+ })}
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Check-in after 18:00",
+ })}
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/guests.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/guests.module.css
new file mode 100644
index 000000000..c5d9fe824
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/guests.module.css
@@ -0,0 +1,20 @@
+.row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
+
+.label {
+ align-items: center;
+ display: flex;
+ gap: var(--Space-x1);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
+
+.row p.guests {
+ color: var(--Text-Default);
+ text-transform: capitalize;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/index.tsx
new file mode 100644
index 000000000..0a59ff680
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Guests/index.tsx
@@ -0,0 +1,61 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import styles from "./guests.module.css"
+
+export default function Guests() {
+ const intl = useIntl()
+ const rooms = useMyStayStore((state) => state.rooms)
+
+ const adults = rooms.reduce((acc, room) => acc + room.adults, 0)
+
+ const children = rooms.reduce(
+ (acc, room) => acc + (room.childrenAges?.length ?? 0),
+ 0
+ )
+
+ const adultsMsg = intl.formatMessage(
+ {
+ defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
+ },
+ { adults }
+ )
+
+ const childrenMsg = intl.formatMessage(
+ {
+ defaultMessage: "{children, plural, one {# child} other {# children}}",
+ },
+ { children }
+ )
+
+ const adultsOnlyMsg = adultsMsg
+ const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(" · ")
+
+ let guests = ""
+ if (children > 0) {
+ guests = adultsAndChildrenMsg
+ } else {
+ guests = adultsOnlyMsg
+ }
+
+ return (
+
+
+
+
+
+ {intl.formatMessage({ defaultMessage: "Guests" })}
+
+
+
+
+ {guests}
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/button.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/button.module.css
new file mode 100644
index 000000000..a88172b13
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/button.module.css
@@ -0,0 +1,14 @@
+.button {
+ align-items: center;
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ gap: var(--Space-x1);
+ padding: var(--Space-x1) 0;
+ width: 100%;
+}
+
+.text {
+ color: var(--Text-Interactive-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/index.tsx
new file mode 100644
index 000000000..08d04a02f
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/Button/index.tsx
@@ -0,0 +1,31 @@
+"use client"
+import { Button as ButtonRAC } from "react-aria-components"
+
+import {
+ MaterialIcon
+,type
+ MaterialIconProps} from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import styles from "./button.module.css"
+
+
+interface ButtonProps extends React.PropsWithChildren {
+ icon: MaterialIconProps["icon"]
+ isDisabled?: boolean
+}
+
+export default function Button({
+ children,
+ icon,
+ isDisabled = false,
+}: ButtonProps) {
+ return (
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/body.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/body.module.css
new file mode 100644
index 000000000..125e21d86
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/body.module.css
@@ -0,0 +1,15 @@
+.content {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x3);
+ max-height: 70vh;
+ overflow-y: auto;
+ width: 100%;
+}
+
+@media screen and (min-width: 768px) {
+ .content {
+ width: 640px;
+ max-width: 100%;
+ }
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/index.tsx
new file mode 100644
index 000000000..0558f3f4b
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Body/index.tsx
@@ -0,0 +1,5 @@
+import styles from "./body.module.css"
+
+export default function Body({ children }: React.PropsWithChildren) {
+ return {children}
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/footer.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/footer.module.css
new file mode 100644
index 000000000..a4a66ca3c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/footer.module.css
@@ -0,0 +1,7 @@
+.footer {
+ border-top: 1px solid var(--Base-Border-Subtle);
+ display: flex;
+ justify-content: space-between;
+ padding-top: var(--Spacing-x3);
+ width: 100%;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/index.tsx
new file mode 100644
index 000000000..8947c186c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Footer/index.tsx
@@ -0,0 +1,64 @@
+import Button from "@/components/TempDesignSystem/Button"
+
+import styles from "./footer.module.css"
+
+import type { ButtonHTMLAttributes, PropsWithChildren } from "react"
+import type { ButtonProps as ReactAriaButtonProps } from "react-aria-components"
+
+import type { ButtonProps as _ButtonProps } from "@/components/TempDesignSystem/Button/button"
+
+export default function Footer({ children }: PropsWithChildren) {
+ return
+}
+
+interface ButtonProps extends PropsWithChildren {
+ intent?: _ButtonProps["intent"]
+ onClick?: ReactAriaButtonProps["onPress"]
+ type?: ButtonHTMLAttributes["type"]
+}
+
+interface PrimaryButtonProps extends ButtonProps {
+ disabled?: boolean
+ form?: string
+}
+
+Footer.Primary = function PrimaryButton({
+ children,
+ disabled = false,
+ form,
+ intent = "primary",
+ onClick,
+ type = "button",
+}: PrimaryButtonProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+Footer.Secondary = function SecondaryButton({
+ children,
+ intent = "text",
+ onClick,
+ type = "button",
+}: ButtonProps) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/header.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/header.module.css
new file mode 100644
index 000000000..814fc100d
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/header.module.css
@@ -0,0 +1,15 @@
+.header {
+ display: grid;
+ gap: var(--Space-x05) var(--Space-x2);
+ grid-template-columns: 1fr auto;
+}
+
+.close {
+ align-items: center;
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ padding: 0;
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/index.tsx
new file mode 100644
index 000000000..039e1a681
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/Header/index.tsx
@@ -0,0 +1,24 @@
+import { Button as ButtonRAC } from "react-aria-components"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+
+import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
+
+import styles from "./header.module.css"
+
+interface HeaderProps extends React.PropsWithChildren {
+ handleClose: () => void
+ title: string
+}
+
+export default function Header({ children, handleClose, title }: HeaderProps) {
+ return (
+
+ {title}
+
+
+
+ {children}
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/index.tsx
new file mode 100644
index 000000000..4a237afaf
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/index.tsx
@@ -0,0 +1,15 @@
+import Body from "./Body"
+import Footer from "./Footer"
+import Header from "./Header"
+
+import styles from "./modalContent.module.css"
+
+import type { PropsWithChildren } from "react"
+
+export default function ModalContent({ children }: PropsWithChildren) {
+ return {children}
+}
+
+ModalContent.Body = Body
+ModalContent.Footer = Footer
+ModalContent.Header = Header
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/modalContent.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/modalContent.module.css
new file mode 100644
index 000000000..37a8f2d91
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/ModalContent/modalContent.module.css
@@ -0,0 +1,4 @@
+.container {
+ display: grid;
+ gap: var(--Space-x3);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/index.tsx
new file mode 100644
index 000000000..82be1e27c
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/index.tsx
@@ -0,0 +1,17 @@
+import { Modal as ModalRAC, ModalOverlay } from "react-aria-components"
+
+import Button from "./Button"
+import ModalContent from "./ModalContent"
+
+import styles from "./modal.module.css"
+
+export default function Modal({ children }: React.PropsWithChildren) {
+ return (
+
+ {children}
+
+ )
+}
+
+Modal.Button = Button
+Modal.Content = ModalContent
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/modal.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/modal.module.css
new file mode 100644
index 000000000..f796f38c9
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Modal/modal.module.css
@@ -0,0 +1,70 @@
+.overlay {
+ background: rgba(0, 0, 0, 0.4);
+ bottom: 0;
+ left: 0;
+ position: fixed;
+ right: 0;
+ top: 0;
+ width: 100dvw;
+ z-index: var(--default-modal-overlay-z-index);
+
+ &[data-entering] {
+ animation: overlay-fade 200ms;
+ }
+
+ &[data-exiting] {
+ animation: overlay-fade 150ms reverse ease-in;
+ }
+}
+
+@keyframes overlay-fade {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+.modal {
+ background: var(--UI-Input-Controls-Surface-Normal);
+ border-top-left-radius: var(--Corner-radius-Large);
+ border-top-right-radius: var(--Corner-radius-Large);
+ max-height: 95dvh;
+ overflow-y: auto;
+ padding: var(--Space-x3);
+ position: absolute;
+ z-index: var(--default-modal-z-index);
+
+ &[data-entering] {
+ animation: modal-anim 200ms;
+ }
+
+ &[data-exiting] {
+ animation: modal-anim 150ms reverse ease-in;
+ }
+}
+
+@keyframes modal-anim {
+ from {
+ transform: translateY(100%);
+ }
+
+ to {
+ transform: translateY(0);
+ }
+}
+
+@media screen and (min-width: 768px) {
+ .overlay {
+ align-items: center;
+ display: flex;
+ justify-content: center;
+ }
+
+ .modal {
+ border-radius: var(--Corner-radius-Large);
+ width: min(690px, 100dvw);
+ }
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/PriceContainer/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/PriceContainer/index.tsx
similarity index 92%
rename from apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/PriceContainer/index.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/PriceContainer/index.tsx
index 0934653b1..f4e2ea81e 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/PriceContainer/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/PriceContainer/index.tsx
@@ -4,22 +4,20 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./priceContainer.module.css"
interface PriceContainerProps {
- text: string
- price: number
- currencyCode: string
- nightsText: string
adultsText: string
childrenText: string
+ nightsText: string
+ price: string
+ text: string
totalChildren?: number
}
export default function PriceContainer({
- text,
- price,
- currencyCode,
- nightsText,
adultsText,
childrenText,
+ nightsText,
+ price,
+ text,
totalChildren = 0,
}: PriceContainerProps) {
return (
@@ -37,7 +35,7 @@ export default function PriceContainer({
- {price} {currencyCode}
+ {price}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/PriceContainer/priceContainer.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/PriceContainer/priceContainer.module.css
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/ManageStay/ActionPanel/PriceContainer/priceContainer.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/PriceContainer/priceContainer.module.css
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/index.tsx
new file mode 100644
index 000000000..0a08f7700
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/index.tsx
@@ -0,0 +1,49 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import Divider from "@/components/TempDesignSystem/Divider"
+
+import styles from "./reference.module.css"
+
+export default function Reference() {
+ const intl = useIntl()
+ const { cancellationNumber, confirmationNumber, isCancelled, rooms } =
+ useMyStayStore((state) => ({
+ cancellationNumber: state.bookedRoom.cancellationNumber,
+ confirmationNumber: state.bookedRoom.confirmationNumber,
+ isCancelled: state.bookedRoom.isCancelled,
+ rooms: state.rooms,
+ }))
+
+ if (rooms.length > 1) {
+ return null
+ }
+
+ const title = isCancelled
+ ? intl.formatMessage({
+ defaultMessage: "Cancellation number",
+ })
+ : intl.formatMessage({
+ defaultMessage: "Booking number",
+ })
+
+ return (
+ <>
+
+
+ {title}
+
+
+
+ {isCancelled ? {cancellationNumber} : confirmationNumber}
+
+
+
+
+ >
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/reference.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/reference.module.css
new file mode 100644
index 000000000..196d88f5d
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Reference/reference.module.css
@@ -0,0 +1,10 @@
+.row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ padding-bottom: var(--Space-x1);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/ReferenceCardSkeleton.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/ReferenceCardSkeleton.tsx
deleted file mode 100644
index 6ea19e693..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/ReferenceCardSkeleton.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import SkeletonShimmer from "@/components/SkeletonShimmer"
-import Divider from "@/components/TempDesignSystem/Divider"
-
-import styles from "./referenceCard.module.css"
-
-export default function ReferenceCardSkeleton() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/index.tsx
new file mode 100644
index 000000000..de1c5bcce
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/index.tsx
@@ -0,0 +1,43 @@
+"use client"
+import { useIntl } from "react-intl"
+
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { useMyStayStore } from "@/stores/my-stay"
+
+import styles from "./room.module.css"
+
+export default function Room() {
+ const intl = useIntl()
+ const { bookedRoom, rooms } = useMyStayStore((state) => ({
+ bookedRoom: state.bookedRoom,
+ rooms: state.rooms,
+ }))
+
+ const roomMsg = intl.formatMessage({
+ defaultMessage: "Room",
+ })
+ const roomsMsg = intl.formatMessage({
+ defaultMessage: "Rooms",
+ })
+
+ const room =
+ rooms.length > 1 ? `${rooms.length} ${roomsMsg}` : bookedRoom.roomName
+ const title = rooms.length > 1 ? roomsMsg : roomMsg
+
+ return (
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/room.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/room.module.css
new file mode 100644
index 000000000..fe9adce2d
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Room/room.module.css
@@ -0,0 +1,15 @@
+.row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
+
+.label {
+ align-items: center;
+ display: flex;
+ gap: var(--Space-x1);
+}
+
+.textDefault {
+ color: var(--Text-Default);
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/index.tsx
index d223eb15f..38d336df2 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/index.tsx
@@ -1,397 +1,48 @@
"use client"
-
-import { useEffect } from "react"
import { useIntl } from "react-intl"
-import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
-import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
-import { BookingStatusEnum } from "@/constants/booking"
-import { dt } from "@/lib/dt"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
-
-import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
-import IconChip from "@/components/TempDesignSystem/IconChip"
-import Link from "@/components/TempDesignSystem/Link"
-import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { useGuaranteePaymentFailedToast } from "@/hooks/booking/useGuaranteePaymentFailedToast"
-import useLang from "@/hooks/useLang"
-import ManageStay from "../ManageStay"
import TotalPrice from "../Rooms/TotalPrice"
-import { mapRoomDetails } from "../utils/mapRoomDetails"
-import ReferenceCardSkeleton from "./ReferenceCardSkeleton"
+import Actions from "./Actions"
+import BookingCode from "./BookingCode"
+import Cancellations from "./Cancellations"
+import Dates from "./Dates"
+import GuaranteeInfo from "./GuaranteeInfo"
+import Guests from "./Guests"
+import Reference from "./Reference"
+import Room from "./Room"
import styles from "./referenceCard.module.css"
-import type { Hotel, Room } from "@/types/hotel"
-import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
-import type { CreditCard } from "@/types/user"
-
-interface ReferenceCardProps {
- booking: BookingConfirmation["booking"]
- hotel: Hotel
- room:
- | (Room & {
- bedType: Room["roomTypes"][number]
- })
- | null
- savedCreditCards: CreditCard[] | null
- refId: string
- isLoggedIn: boolean
-}
-
-export function ReferenceCard({
- booking,
- hotel,
- room,
- savedCreditCards,
- refId,
- isLoggedIn,
-}: ReferenceCardProps) {
+export function ReferenceCard() {
const intl = useIntl()
- const lang = useLang()
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
- const addBookedRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.addBookedRoom
- )
- const addRoomPrice = useMyStayTotalPriceStore(
- (state) => state.actions.addRoomPrice
- )
-
- // Initialize store with server data
- useEffect(() => {
- // Add price and details for booked room (main room or single room)
- addRoomPrice({
- id: booking.confirmationNumber,
- totalPrice:
- booking.reservationStatus === BookingStatusEnum.Cancelled
- ? 0
- : booking.totalPrice,
- currencyCode: booking.currencyCode,
- isMainBooking: true,
- roomPoints: booking.roomPoints,
- })
- addBookedRoom(
- mapRoomDetails({
- booking,
- room,
- roomNumber: 1,
- })
- )
- }, [booking, room, addBookedRoom, addRoomPrice])
-
useGuaranteePaymentFailedToast()
-
- if (!bookedRoom.roomNumber) return
-
- const {
- confirmationNumber,
- cancellationNumber,
- checkInDate,
- checkOutDate,
- isCancelled,
- bookingCode,
- rateDefinition,
- priceType,
- } = bookedRoom
-
- const isMultiRoom = bookedRoom.linkedReservations.length > 0
-
- const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
-
- const allRooms = [bookedRoom, ...linkedReservationRooms]
-
- const adults = allRooms
- .filter((room) => !room.isCancelled)
- .reduce((acc, room) => acc + room.adults, 0)
-
- const children = allRooms
- .filter((room) => !room.isCancelled)
- .reduce((acc, room) => acc + (room.childrenAges?.length ?? 0), 0)
-
- const cancelledRooms = allRooms.filter((room) => room.isCancelled).length
- const allRoomsCancelled = allRooms.every((room) => room.isCancelled)
-
- const adultsMsg = intl.formatMessage(
- {
- defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
- },
- {
- adults: adults,
- }
- )
-
- const childrenMsg = intl.formatMessage(
- {
- defaultMessage: "{children, plural, one {# child} other {# children}}",
- },
- {
- children: children,
- }
- )
-
- const cancelledRoomsMsg = intl.formatMessage(
- {
- defaultMessage: "{rooms, plural, one {# room} other {# rooms}}",
- },
- {
- rooms: cancelledRooms,
- }
- )
-
- const roomCancelledRoomsMsg = intl.formatMessage({
- defaultMessage: "Room cancelled",
- })
-
- const roomsMsg = intl.formatMessage(
- {
- defaultMessage: "{rooms, plural, one {# room} other {# rooms}}",
- },
- {
- rooms: allRooms.filter((room) => !room.isCancelled).length,
- }
- )
- const adultsOnlyMsg = adultsMsg
- const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
- const adultsAndRoomsMsg = [adultsMsg, roomsMsg].join(", ")
- const adultsAndChildrenAndRoomsMsg = [adultsMsg, childrenMsg, roomsMsg].join(
- ", "
- )
-
return (
- {!isMultiRoom && (
- <>
-
-
- {intl.formatMessage({
- defaultMessage: "Reference",
- })}
-
-
- {isCancelled && !isMultiRoom
- ? intl.formatMessage({
- defaultMessage: "Cancellation number",
- })
- : intl.formatMessage({
- defaultMessage: "Reference number",
- })}
-
-
- {isCancelled && !isMultiRoom
- ? cancellationNumber
- : confirmationNumber}
-
-
+
+
+
+
+
+
+
-
- >
- )}
-
- {!allRoomsCancelled && (
-
-
-
- {intl.formatMessage({
- defaultMessage: "Guests",
- })}
-
-
-
-
- {allRooms.length > 1
- ? children > 0
- ? adultsAndChildrenAndRoomsMsg
- : adultsAndRoomsMsg
- : children > 0
- ? adultsAndChildrenMsg
- : adultsOnlyMsg}
-
-
-
- )}
- {allRooms.some((room) => room.isCancelled) && (
-
-
-
- {intl.formatMessage({
- defaultMessage: "Cancellation",
- })}
-
-
-
-
- {isMultiRoom
- ? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
- `${cancelledRoomsMsg} ${intl.formatMessage({
- defaultMessage: "cancelled",
- })}`
- : roomCancelledRoomsMsg}
-
-
-
- )}
- {!allRoomsCancelled && (
- <>
-
-
-
- {intl.formatMessage({
- defaultMessage: "Check-in",
- })}
-
-
-
-
- {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
- {`${dt(checkInDate).locale(lang).format("dddd, D MMMM")} ${intl.formatMessage(
- {
- defaultMessage: "from",
- }
- )} ${hotel.hotelFacts.checkin.checkInTime}`}
-
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Check-out",
- })}
-
-
-
-
- {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
- {`${dt(checkOutDate).locale(lang).format("dddd, D MMMM")} ${intl.formatMessage(
- {
- defaultMessage: "until",
- }
- )} ${hotel.hotelFacts.checkin.checkOutTime}`}
-
-
-
- >
- )}
-
- {booking.guaranteeInfo && !allRoomsCancelled && (
- <>
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Booking guaranteed.",
- })}
-
- {/* eslint-disable formatjs/no-literal-string-in-jsx */}{" "}
- {/* eslint-enable formatjs/no-literal-string-in-jsx */}
- {intl.formatMessage({
- defaultMessage:
- "Your stay remains available for check-in after 18:00.",
- })}
-
-
-
-
- >
- )}
-
-
-
+
+
{intl.formatMessage({
defaultMessage: "Total",
})}
-
+
- {bookingCode && (
-
-
-
- {intl.formatMessage({
- defaultMessage: "Booking code",
- })}
-
-
-
- }
- >
- {intl.formatMessage(
- {
- defaultMessage: "Booking code : {value}",
- },
- {
- value: bookingCode,
- strong: (text) => (
-
- {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
- {text}
-
- ),
- }
- )}
-
-
-
- )}
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Get directions",
- })}
-
-
-
- {isMultiRoom && (
-
-
- {intl.formatMessage({
- defaultMessage: "Multi-room stay",
- })}
-
-
- )}
-
-
-
- {rateDefinition.generalTerms.map((term) => (
-
- {term}
- {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
- {term.endsWith(".") ? " " : ". "}
-
- ))}
-
-
+
+
)
}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/referenceCard.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/referenceCard.module.css
index b496cbbdf..ed766d2ea 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/referenceCard.module.css
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/referenceCard.module.css
@@ -1,72 +1,19 @@
.referenceCard {
- width: var(--max-width-content);
- max-width: 588px;
- margin: 0 auto;
- padding: var(--Spacing-x3);
- border-radius: var(--Corner-radius-Large);
background-color: var(--Base-Surface-Primary-light-Normal);
+ border-radius: var(--Corner-radius-Large);
box-shadow: var(--popup-box-shadow);
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x1);
+ margin: 0 auto;
+ max-width: 588px;
+ padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x4);
+ width: var(--max-width-content);
}
-.referenceRow {
+.row {
+ align-items: center;
display: flex;
justify-content: space-between;
- align-items: center;
- padding-bottom: var(--Spacing-x-one-and-half);
-}
-
-.divider {
- margin-bottom: var(--Spacing-x-one-and-half);
-}
-
-.cancelledRooms {
- color: var(--Scandic-Brand-Scandic-Red);
-}
-
-.actionArea {
- display: flex;
- gap: var(--Spacing-x2);
- margin: var(--Spacing-x4) 0 var(--Spacing-x3);
-}
-
-.note {
- text-align: center;
- width: 80%;
- margin: 0 auto;
-}
-
-.cancelledNote {
- color: var(--UI-Text-Placeholder);
-}
-
-.titleDesktop {
- display: none;
-}
-
-.guaranteed {
- align-items: flex-start;
- border-radius: var(--Corner-radius-Medium);
- display: flex;
- background-color: var(--Surface-Feedback-Succes);
- gap: var(--Spacing-x1);
- padding: var(--Spacing-x1);
- margin-bottom: var(--Space-x1);
-}
-
-.guaranteedText {
- color: var(--Surface-Feedback-Succes-Accent);
-}
-
-@media (min-width: 768px) {
- .actionArea {
- gap: var(--Spacing-x3);
- }
-
- .titleMobile {
- display: none;
- }
-
- .titleDesktop {
- display: block;
- }
+ padding-top: var(--Space-x1);
}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/MultiRoomSkeleton.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/MultiRoomSkeleton.tsx
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/MultiRoomSkeleton.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/MultiRoomSkeleton.tsx
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/ToggleSidePeek.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/ToggleSidePeek.tsx
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/ToggleSidePeek.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/ToggleSidePeek.tsx
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/index.tsx
similarity index 55%
rename from apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/index.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/index.tsx
index 6f2cf7363..365bbcc46 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/MultiRoom/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/index.tsx
@@ -1,136 +1,59 @@
"use client"
-import { use, useEffect } from "react"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
-import { BookingStatusEnum } from "@/constants/booking"
import { dt } from "@/lib/dt"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
+import { IconForFeatureCode } from "@/components/HotelReservation/utils"
import Image from "@/components/Image"
import Divider from "@/components/TempDesignSystem/Divider"
import IconChip from "@/components/TempDesignSystem/IconChip"
import useLang from "@/hooks/useLang"
+import { formatPrice } from "@/utils/numberFormatting"
-import { IconForFeatureCode } from "../../utils"
-import { hasModifiableRate } from "../utils"
-import { hasBreakfastPackageFromBookingFlow } from "../utils/hasBreakfastPackage"
-import { mapRoomDetails } from "../utils/mapRoomDetails"
-import MultiRoomSkeleton from "./MultiRoomSkeleton"
-import PriceType from "./PriceType"
+import PriceType from "../../PriceType"
+import { hasModifiableRate } from "../../utils"
import ToggleSidePeek from "./ToggleSidePeek"
import styles from "./multiRoom.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
-import type { Room } from "@/types/hotel"
-import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
-import type { User } from "@/types/user"
+import type { Room } from "@/types/stores/my-stay"
+import type { SafeUser } from "@/types/user"
interface MultiRoomProps {
- booking?: BookingConfirmation["booking"]
- room?:
- | (Room & {
- bedType: Room["roomTypes"][number]
- })
- | null
- bookingPromise?: Promise
- index?: number
- user: User | null
+ booking: Room
+ roomNr: number
+ user: SafeUser
}
-export default function MultiRoom({
- room: initialRoom,
- booking: initialBooking,
- bookingPromise,
- index,
- user,
-}: MultiRoomProps) {
+export default function MultiRoom({ booking, roomNr, user }: MultiRoomProps) {
const intl = useIntl()
const lang = useLang()
- const addRoomPrice = useMyStayTotalPriceStore(
- (state) => state.actions.addRoomPrice
- )
-
- const addLinkedReservationRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.addLinkedReservationRoom
- )
-
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
-
- const allRooms = [bookedRoom, ...linkedReservationRooms]
-
- // Resolve promise data directly without setState
- let bookingInfo = initialBooking
- let roomInfo = initialRoom
-
- if (bookingPromise) {
- const promiseData = use(bookingPromise)
- if (promiseData) {
- bookingInfo = promiseData.booking
- roomInfo = promiseData.room
- }
- }
- const isBookingCancelled =
- bookingInfo?.reservationStatus === BookingStatusEnum.Cancelled
-
- const multiRoom = allRooms.find(
- (room) => room.confirmationNumber === bookingInfo?.confirmationNumber
- )
-
- // Update stores when data is available
- useEffect(() => {
- if (bookingInfo) {
- addRoomPrice({
- id: bookingInfo.confirmationNumber,
- totalPrice: isBookingCancelled ? 0 : bookingInfo.totalPrice,
- currencyCode: bookingInfo.currencyCode,
- isMainBooking: false,
- roomPoints: bookingInfo.roomPoints,
- })
-
- // Add room details to the store
- addLinkedReservationRoom(
- mapRoomDetails({
- booking: bookingInfo,
- room: roomInfo ?? null,
- roomNumber: index !== undefined ? index + 2 : 1,
- })
- )
- }
- }, [
- bookingInfo,
- roomInfo,
- index,
- isBookingCancelled,
- addRoomPrice,
- addLinkedReservationRoom,
- ])
-
- if (!multiRoom?.roomNumber) return
const {
adults,
+ breakfast,
+ cancellationNumber,
checkInDate,
cheques,
childrenAges,
confirmationNumber,
- cancellationNumber,
+ currencyCode,
hotelId,
- roomPoints,
packages,
rateDefinition,
+ room,
+ roomName,
+ roomPoints,
isCancelled,
priceType,
+ roomTypeCode,
vouchers,
totalPrice,
- } = multiRoom
+ } = booking
const fromDate = dt(checkInDate).locale(lang)
@@ -155,10 +78,27 @@ export default function MultiRoom({
const adultsOnlyMsg = adultsMsg
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
+ const formattedTotalPrice = formatPrice(intl, totalPrice, currencyCode)
+
+ let breakfastPrice = intl.formatMessage({
+ defaultMessage: "No breakfast",
+ })
+ if (rateDefinition.breakfastIncluded) {
+ breakfastPrice = intl.formatMessage({
+ defaultMessage: "Included",
+ })
+ } else if (breakfast) {
+ breakfastPrice = formatPrice(
+ intl,
+ breakfast.localPrice.totalPrice,
+ breakfast.localPrice.currency
+ )
+ }
+
return (
- {roomInfo?.name}
+ {roomName}
{isCancelled ? (
@@ -189,7 +129,7 @@ export default function MultiRoom({
defaultMessage: "Room {roomIndex}",
},
{
- roomIndex: index !== undefined ? index + 2 : 1,
+ roomIndex: roomNr,
}
)}
@@ -229,8 +169,8 @@ export default function MultiRoom({
@@ -243,30 +183,30 @@ export default function MultiRoom({
item.code as RoomPackageCodeEnum
)
) && (
-
- {packages
- .filter((item) =>
- Object.values(RoomPackageCodeEnum).includes(
- item.code as RoomPackageCodeEnum
- )
+
+ {packages
+ .filter((item) =>
+ Object.values(RoomPackageCodeEnum).includes(
+ item.code as RoomPackageCodeEnum
)
- .map((item) => {
- return (
-
-
-
- )
- })}
-
- )}
+ )
+ .map((item) => {
+ return (
+
+
+
+ )
+ })}
+
+ )}
@@ -285,18 +225,20 @@ export default function MultiRoom({
-
-
-
- {intl.formatMessage({
- defaultMessage: "Terms",
- })}
-
-
-
- {rateDefinition.cancellationText}
-
-
+ {rateDefinition.cancellationText ? (
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Terms",
+ })}
+
+
+
+ {rateDefinition.cancellationText}
+
+
+ ) : null}
{hasModifiableRate(rateDefinition.cancellationRule) && (
@@ -315,31 +257,21 @@ export default function MultiRoom({
)}
-
-
-
- {intl.formatMessage({
- defaultMessage: "Breakfast",
- })}
-
-
-
-
-
- {hasBreakfastPackageFromBookingFlow(
- packages?.map((pkg) => ({
- code: pkg.code,
- })) ?? []
- )
- ? intl.formatMessage({
- defaultMessage: "Included",
- })
- : intl.formatMessage({
- defaultMessage: "Not included",
+ {breakfastPrice !== null && (
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Breakfast",
})}
-
-
-
+
+
+
+
+ {breakfastPrice}
+
+
+ )}
@@ -351,6 +283,7 @@ export default function MultiRoom({
state.bookedRoom)
- const updateBookedRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.updateBookedRoom
- )
+ const {
+ adults,
+ bookingCode,
+ breakfast,
+ checkInDate,
+ cheques,
+ childrenAges,
+ confirmationNumber,
+ formattedTotalPrice,
+ hotel,
+ isCancelled,
+ packages,
+ priceType,
+ rateDefinition,
+ roomName,
+ roomNumber,
+ roomPoints,
+ roomTypeCode,
+ totalPrice,
+ vouchers,
+ } = useMyStayStore((state) => ({
+ adults: state.bookedRoom.adults,
+ bookingCode: state.bookedRoom.bookingCode,
+ breakfast: state.bookedRoom.breakfast,
+ checkInDate: state.bookedRoom.checkInDate,
+ cheques: state.bookedRoom.cheques,
+ childrenAges: state.bookedRoom.childrenAges,
+ confirmationNumber: state.bookedRoom.confirmationNumber,
+ formattedTotalPrice: state.totalPrice,
+ hotel: state.hotel,
+ isCancelled: state.bookedRoom.isCancelled,
+ packages: state.bookedRoom.packages,
+ priceType: state.bookedRoom.priceType,
+ rateDefinition: state.bookedRoom.rateDefinition,
+ roomName: state.bookedRoom.roomName,
+ roomNumber: state.bookedRoom.roomNumber,
+ roomPoints: state.bookedRoom.roomPoints,
+ roomTypeCode: state.bookedRoom.roomTypeCode,
+ totalPrice: state.bookedRoom.totalPrice,
+ vouchers: state.bookedRoom.vouchers,
+ }))
- if (!bookedRoom.roomNumber) {
+ if (!roomNumber) {
return (
@@ -53,23 +88,7 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
)
}
- const fromDate = dt(bookedRoom.checkInDate).locale(lang)
-
- const {
- adults,
- bookingCode,
- breakfast,
- cheques,
- childrenAges,
- confirmationNumber,
- isCancelled,
- packages,
- priceType,
- rateDefinition,
- roomPoints,
- totalPrice,
- vouchers,
- } = bookedRoom
+ const fromDate = dt(checkInDate).locale(lang)
const mainBedWidthValueMsg = intl.formatMessage(
{
@@ -117,23 +136,24 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
)
)
- const breakfastText = rateDefinition.breakfastIncluded
- ? intl.formatMessage({
+ let breakfastPrice = null
+ if (rateDefinition.breakfastIncluded) {
+ breakfastPrice = intl.formatMessage({
defaultMessage: "Included",
})
- : breakfast
- ? formatPrice(
- intl,
- breakfast.localPrice.totalPrice,
- breakfast.localPrice.currency
- )
- : null
+ } else if (breakfast) {
+ breakfastPrice = formatPrice(
+ intl,
+ breakfast.localPrice.totalPrice,
+ breakfast.localPrice.currency
+ )
+ }
return (
- {bookedRoom.roomName}
+ {roomName}
@@ -145,7 +165,7 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
defaultMessage: "Room {roomIndex}",
},
{
- roomIndex: bookedRoom.roomNumber,
+ roomIndex: roomNumber,
}
)}
@@ -169,7 +189,7 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
@@ -183,32 +203,32 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
item.code as RoomPackageCodeEnum
)
) && (
-
- {packages
- .filter((item) =>
- Object.values(RoomPackageCodeEnum).includes(
- item.code as RoomPackageCodeEnum
- )
+
+ {packages
+ .filter((item) =>
+ Object.values(RoomPackageCodeEnum).includes(
+ item.code as RoomPackageCodeEnum
)
- .map((item) => {
- return (
-
-
-
- )
- })}
-
- )}
+ )
+ .map((item) => {
+ return (
+
+
+
+ )
+ })}
+
+ )}
@@ -240,29 +260,31 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
-
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "Terms",
- })}
-
-
-
-
-
-
- {rateDefinition.cancellationText}
-
-
+ {rateDefinition.cancellationText ? (
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Terms",
+ })}
+
+
+
+
+
+
+ {rateDefinition.cancellationText}
+
+
+
-
+ ) : null}
{hasModifiableRate(rateDefinition.cancellationRule) && (
@@ -296,7 +318,7 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
)}
- {breakfastText !== null && (
+ {breakfastPrice !== null && (
- {breakfastText}
+ {breakfastPrice}
@@ -368,22 +390,18 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
{bedType.mainBed.description}
{bedType.mainBed.widthRange.min ===
- bedType.mainBed.widthRange.max
+ bedType.mainBed.widthRange.max
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
- ` (${mainBedWidthValueMsg})`
+ ` (${mainBedWidthValueMsg})`
: // eslint-disable-next-line formatjs/no-literal-string-in-jsx
- ` (${mainBedWidthRangeMsg})`}
+ ` (${mainBedWidthRangeMsg})`}
-
+
@@ -421,9 +439,10 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
-
+
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/room.module.css b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/SingleRoom/room.module.css
similarity index 99%
rename from apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/room.module.css
rename to apps/scandic-web/components/HotelReservation/MyStay/Rooms/SingleRoom/room.module.css
index 805089901..ee54a0f64 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/room.module.css
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/SingleRoom/room.module.css
@@ -31,6 +31,7 @@
flex-direction: column;
overflow: hidden;
}
+
.content {
display: grid;
gap: var(--Spacing-x2);
@@ -160,6 +161,7 @@
}
.price {
+ align-items: center;
display: flex;
gap: var(--Spacing-x1);
justify-content: space-between;
@@ -187,51 +189,63 @@
background-color: transparent;
padding: 0;
}
+
.roomName {
padding: 0;
}
+
.roomHeader {
justify-content: space-between;
align-items: center;
flex-direction: row;
padding: 0;
}
+
.sidePeek {
display: block;
}
+
.booking {
border-radius: var(--Corner-radius-Large);
background-color: var(--Base-Background-Primary-Normal);
}
+
.content {
padding: var(--Spacing-x2);
grid-template-columns: 3fr 2fr;
width: var(--max-width-content);
}
+
.packages {
top: 620px;
left: 25px;
}
+
.imageContainer {
height: 640px;
}
+
.image {
height: 100%;
border-radius: var(--Corner-radius-Medium);
}
+
.bookingDetails {
padding: 0;
}
+
.row {
border-bottom: 1px solid var(--Base-Border-Subtle);
flex-direction: row;
align-items: center;
justify-content: space-between;
}
+
.rowTitle svg {
width: 20px;
height: 20px;
}
+
.bookingInformation {
flex-direction: row;
justify-content: space-between;
@@ -241,17 +255,21 @@
border-radius: 0;
border: none;
}
+
.priceDetails {
margin: 0 0 0 auto;
width: auto;
align-items: flex-end;
}
+
.price {
justify-content: flex-end;
}
+
.guestDetailsMobileWrapper {
display: none;
}
+
.guestDetailsDesktopWrapper {
display: block;
margin-top: auto;
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice.tsx
new file mode 100644
index 000000000..3d5451000
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice.tsx
@@ -0,0 +1,26 @@
+"use client"
+import { useMyStayStore } from "@/stores/my-stay"
+
+import PriceType from "../PriceType"
+
+import type { PriceType as _PriceType } from "@/types/components/hotelReservation/myStay/myStay"
+
+export default function TotalPrice() {
+ const { bookedRoom, formattedTotalPrice } = useMyStayStore((state) => ({
+ bookedRoom: state.bookedRoom,
+ formattedTotalPrice: state.totalPrice,
+ }))
+
+ return (
+
+ )
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice/index.tsx
deleted file mode 100644
index fd4308ce0..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/TotalPrice/index.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-"use client"
-
-import { useIntl } from "react-intl"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
-
-import Points from "../../Points"
-import Price from "../../Price"
-
-import styles from "./totalPrice.module.css"
-
-import type { PriceType } from "@/types/components/hotelReservation/myStay/myStay"
-
-export type Variant =
- | "Title/Subtitle/lg"
- | "Title/Subtitle/md"
- | "Body/Paragraph/mdBold"
-
-interface TotalPriceProps {
- variant: Variant
- type?: PriceType
-}
-
-export default function TotalPrice({
- variant,
- type = "money",
-}: TotalPriceProps) {
- const totalPrice = useMyStayTotalPriceStore((state) => state.totalPrice)
- const totalPoints = useMyStayTotalPriceStore((state) => state.totalPoints)
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
-
- const { vouchers, cheques } = bookedRoom
-
- const intl = useIntl()
- if (type === "money") {
- return
- }
-
- if (type === "voucher") {
- return (
-
-
- {intl.formatMessage(
- {
- defaultMessage: "{count} voucher",
- },
- { count: vouchers }
- )}
-
-
- )
- }
-
- if (type === "cheque") {
- return (
-
-
- {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
- {cheques} CC +
-
-
-
- )
- }
-
- if (totalPrice && totalPrice > 0 && type === "points") {
- return (
-
- {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
-
+{" "}
-
-
- )
- }
-
- return
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/index.tsx
index cc308e55c..e895f3e81 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/index.tsx
@@ -1,54 +1,37 @@
-import { Suspense } from "react"
+"use client"
+import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
-import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
+import { useMyStayStore } from "@/stores/my-stay"
-import { getIntl } from "@/i18n"
-
-import MultiRoom from "../MultiRoom"
-import MultiRoomSkeleton from "../MultiRoom/MultiRoomSkeleton"
import PriceDetails from "../PriceDetails"
-import { SingleRoom } from "../SingleRoom"
-import { getPriceType } from "../utils/getPriceType"
+import MultiRoom from "./MultiRoom"
+import SingleRoom from "./SingleRoom"
import TotalPrice from "./TotalPrice"
import styles from "./rooms.module.css"
-import { type Hotel, type Room } from "@/types/hotel"
-import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
-import type { User } from "@/types/user"
+import type { SafeUser } from "@/types/user"
interface RoomsProps {
- booking: BookingConfirmation["booking"]
- room:
- | (Room & {
- bedType: Room["roomTypes"][number]
- })
- | null
- hotel: Hotel
- user: User | null
+ user: SafeUser
}
-export default async function Rooms({
- booking,
- room,
- hotel,
- user,
-}: RoomsProps) {
- const intl = await getIntl()
+export default function Rooms({ user }: RoomsProps) {
+ const intl = useIntl()
+ const { allRoomsAreCancelled, room, rooms } = useMyStayStore((state) => ({
+ allRoomsAreCancelled: state.allRoomsAreCancelled,
+ hotel: state.hotel,
+ room: state.bookedRoom.room,
+ rooms: state.rooms,
+ }))
if (!room) {
return null
}
- const linkedBookingPromises = booking.linkedReservations
- ? booking.linkedReservations.map((linkedBooking) => {
- return getBookingConfirmation(linkedBooking.confirmationNumber)
- })
- : []
-
- const isMultiRoom = booking.linkedReservations.length > 0
+ const isMultiRoom = rooms.length > 1
return (
@@ -66,24 +49,16 @@ export default async function Rooms({
) : (
-
- {booking.linkedReservations.map((linkedRes, index) => (
+ {rooms.map((booking, index) => (
- }>
-
-
+
))}
@@ -101,17 +76,10 @@ export default async function Rooms({
{":"}
-
+
-
+ {allRoomsAreCancelled ? null : }
)}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/PriceType.tsx b/apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/PriceType.tsx
deleted file mode 100644
index f16f0b9a1..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/SingleRoom/PriceType.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-"use client"
-import { useIntl } from "react-intl"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
-
-import Cheques from "../Cheques"
-import Points from "../Points"
-import Price from "../Price"
-
-import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
-
-export default function PriceType() {
- const intl = useIntl()
- const {
- cheques,
- isCancelled,
- priceType,
- rateDefinition,
- roomPoints,
- totalPrice,
- vouchers,
- } = useMyStayRoomDetailsStore((state) => ({
- cheques: state.bookedRoom.cheques,
- isCancelled: state.bookedRoom.isCancelled,
- priceType: state.bookedRoom.priceType,
- rateDefinition: state.bookedRoom.rateDefinition,
- roomPoints: state.bookedRoom.roomPoints,
- totalPrice: state.bookedRoom.totalPrice,
- vouchers: state.bookedRoom.vouchers,
- }))
-
- switch (priceType) {
- case PriceTypeEnum.cheque:
- return
- case PriceTypeEnum.money:
- return (
-
- )
- case PriceTypeEnum.points:
- return
- case PriceTypeEnum.voucher:
- return (
-
-
- {intl.formatMessage(
- {
- defaultMessage: "{count} voucher",
- },
- { count: vouchers }
- )}
-
-
- )
- default:
- return null
- }
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/GuaranteeLateArrivalCallback/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/TrackGuarantee.tsx
similarity index 90%
rename from apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/GuaranteeLateArrivalCallback/index.tsx
rename to apps/scandic-web/components/HotelReservation/MyStay/TrackGuarantee.tsx
index 598c86507..d64afa4bd 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/GuaranteeLateArrival/GuaranteeLateArrivalCallback/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/TrackGuarantee.tsx
@@ -16,7 +16,7 @@ import { buildAncillaries } from "@/utils/tracking/myStay"
import {
buildAncillaryPackages,
getAncillarySessionData,
-} from "../../Ancillaries/utils"
+} from "@/components/HotelReservation/MyStay/utils/ancillaries"
interface TrackGuaranteeProps {
status: string
@@ -93,21 +93,21 @@ export default function TrackGuarantee({
case PaymentCallbackStatusEnum.Cancel:
isAncillaryFlow
? trackAncillaryPaymentEvent(
- "GuaranteeCancelAncillary",
- "glacardsavecancelled"
- )
+ "GuaranteeCancelAncillary",
+ "glacardsavecancelled"
+ )
: trackGuaranteePaymentEvent(
- "glaCardSaveCancelled",
- "glacardsavecancelled"
- )
+ "glaCardSaveCancelled",
+ "glacardsavecancelled"
+ )
break
case PaymentCallbackStatusEnum.Error:
isAncillaryFlow
? trackAncillaryPaymentEvent(
- "GuaranteeFailAncillary",
- "glacardsavefailed"
- )
+ "GuaranteeFailAncillary",
+ "glacardsavefailed"
+ )
: trackGuaranteePaymentEvent("glaCardSaveFailed", "glacardsavefailed")
break
}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/index.tsx
deleted file mode 100644
index 677a42488..000000000
--- a/apps/scandic-web/components/HotelReservation/MyStay/index.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-import { cookies } from "next/headers"
-import { notFound } from "next/navigation"
-
-import { Typography } from "@scandic-hotels/design-system/Typography"
-
-import { env } from "@/env/server"
-import { dt } from "@/lib/dt"
-import {
- getAncillaryPackages,
- getBookingConfirmation,
- getPackages,
- getProfileSafely,
- getSavedPaymentCardsSafely,
-} from "@/lib/trpc/memoizedRequests"
-import { decrypt } from "@/server/routers/utils/encryption"
-
-import Image from "@/components/Image"
-import { getIntl } from "@/i18n"
-import { getLang } from "@/i18n/serverContext"
-import { getCurrentWebUrl } from "@/utils/url"
-
-import AdditionalInfoForm from "../FindMyBooking/AdditionalInfoForm"
-import accessBooking, {
- ACCESS_GRANTED,
- ERROR_BAD_REQUEST,
- ERROR_UNAUTHORIZED,
-} from "./accessBooking"
-import { Ancillaries } from "./Ancillaries"
-import BookingSummary from "./BookingSummary"
-import { Header } from "./Header"
-import Promo from "./Promo"
-import { ReferenceCard } from "./ReferenceCard"
-import Rooms from "./Rooms"
-
-import styles from "./myStay.module.css"
-
-import { BreakfastPackageEnum } from "@/types/enums/breakfast"
-
-export async function MyStay({ refId }: { refId: string }) {
- const value = decrypt(refId)
- if (!value) {
- return notFound()
- }
- const [confirmationNumber, lastName] = value.split(",")
- const bookingConfirmation = await getBookingConfirmation(confirmationNumber)
- if (!bookingConfirmation) {
- return notFound()
- }
-
- const { booking, hotel, additionalData, room } = bookingConfirmation
- const user = await getProfileSafely()
- const bv = cookies().get("bv")?.value
- const intl = await getIntl()
-
- const access = accessBooking(booking.guest, lastName, user, bv)
-
- if (access === ACCESS_GRANTED) {
- const lang = getLang()
- const ancillaryPackages = await getAncillaryPackages({
- fromDate: dt(booking.checkInDate).format("YYYY-MM-DD"),
- hotelId: hotel.operaId,
- toDate: dt(booking.checkOutDate).format("YYYY-MM-DD"),
- })
-
- const packages = await getPackages({
- startDate: dt(booking.checkInDate).format("YYYY-MM-DD"),
- hotelId: hotel.operaId,
- endDate: dt(booking.checkOutDate).format("YYYY-MM-DD"),
- adults: booking.adults,
- children: booking.childrenAges.length,
- packageCodes: [
- BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
- BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
- BreakfastPackageEnum.FREE_CHILD_BREAKFAST,
- ],
- lang,
- })
-
- const supportedCards = hotel.merchantInformationData.cards
- const savedCreditCards = await getSavedPaymentCardsSafely({
- supportedCards,
- })
-
- const imageSrc =
- hotel.hotelContent.images.imageSizes.large ??
- additionalData.gallery?.heroImages[0]?.imageSizes.large ??
- hotel.galleryImages[0]?.imageSizes.large
-
- const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
- const promoUrl = env.HIDE_FOR_NEXT_RELEASE
- ? new URL(getCurrentWebUrl({ path: "/", lang, baseUrl }))
- : new URL(`${baseUrl}/${lang}/`)
-
- promoUrl.searchParams.set("hotel", hotel.operaId)
-
- return (
-
-
-
- {imageSrc && (
-
- )}
-
-
-
-
-
-
- {booking.showAncillaries && (
-
- )}
-
-
-
-
-
-
-
- )
- }
-
- if (access === ERROR_BAD_REQUEST) {
- return (
-
-
-
- )
- }
-
- if (access === ERROR_UNAUTHORIZED) {
- return (
-
-
-
-
- {intl.formatMessage({
- defaultMessage: "You need to be logged in to view your booking",
- })}
-
-
-
-
- {intl.formatMessage({
- defaultMessage:
- "And you need to be logged in with the same member account that made the booking.",
- })}
-
-
-
-
- )
- }
-
- return notFound()
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/myStay.module.css b/apps/scandic-web/components/HotelReservation/MyStay/myStay.module.css
index 86c76c1fb..9ac3f70e9 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/myStay.module.css
+++ b/apps/scandic-web/components/HotelReservation/MyStay/myStay.module.css
@@ -1,38 +1,3 @@
-.main {
- background-color: var(--Base-Surface-Primary-light-Normal);
-}
-
-.imageContainer {
- position: absolute;
- width: 100%;
- height: 480px;
-}
-
-.blurOverlay {
- position: absolute;
- inset: 0;
- backdrop-filter: blur(12px);
- pointer-events: none;
- z-index: 1;
- mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, transparent 100%);
- background: linear-gradient(
- to bottom,
- rgba(0, 0, 0, 0.5) 0%,
- transparent 100%
- );
-}
-
-.image {
- object-fit: cover;
- object-position: center;
-}
-
-.headerContainer {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x4);
-}
-
.content {
width: 100%;
display: flex;
@@ -44,13 +9,6 @@
padding-bottom: var(--Spacing-x3);
}
-.form {
- max-width: 640px;
- margin-left: auto;
- margin-right: auto;
- padding: var(--Spacing-x5) 0;
-}
-
.headerSkeleton {
display: flex;
flex-direction: column;
@@ -59,6 +17,12 @@
padding: var(--Spacing-x6) var(--Spacing-x2) 0;
}
+.cardSkeleton {
+ max-width: 100%;
+ margin: -30px auto 0;
+ padding: 0 var(--Spacing-x2);
+}
+
.section {
display: flex;
flex-direction: column;
@@ -66,39 +30,12 @@
padding: 0 var(--Spacing-x2);
}
-.cardSkeleton {
- max-width: 100%;
- margin: -30px auto 0;
- padding: 0 var(--Spacing-x2);
-}
-
.ancillariesSkeleton {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
}
-.paymentDetailsSkeleton {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
-}
-
-.hotelDetailsSkeleton {
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
-}
-
-.logIn {
- padding: var(--Spacing-x9) var(--Spacing-x2);
- display: flex;
- flex-direction: column;
- gap: var(--Spacing-x2);
- align-items: center;
- color: var(--Scandic-Grey-100);
-}
-
@media (min-width: 768px) {
.content {
width: var(--max-width-content);
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/utils.ts b/apps/scandic-web/components/HotelReservation/MyStay/utils/ancillaries.ts
similarity index 94%
rename from apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/utils.ts
rename to apps/scandic-web/components/HotelReservation/MyStay/utils/ancillaries.ts
index 97cd3bc9f..46d294ad5 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/utils.ts
+++ b/apps/scandic-web/components/HotelReservation/MyStay/utils/ancillaries.ts
@@ -2,7 +2,7 @@ import type {
Ancillary,
SelectedAncillary,
} from "@/types/components/myPages/myStay/ancillaries"
-import type { AncillaryFormData } from "./AddAncillaryFlow/schema"
+import type { AncillaryFormData } from "@/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/schema"
export const generateDeliveryOptions = () => {
const timeSlots = ["16:00-17:00", "17:00-18:00", "18:00-19:00", "19:00-20:00"]
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts b/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts
index 1672a9939..185075a63 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts
+++ b/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts
@@ -1,27 +1,29 @@
-import { BookingStatusEnum } from "@/constants/booking"
+import { BookingStatusEnum, CancellationRuleEnum } from "@/constants/booking"
import { dt } from "@/lib/dt"
import { formatChildBedPreferences } from "../utils"
import { convertToChildType } from "./convertToChildType"
import { getPriceType } from "./getPriceType"
-import { getBreakfastPackagesFromBookingFlow } from "./hasBreakfastPackage"
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import { PackageTypeEnum } from "@/types/enums/packages"
+import type { RateEnum } from "@/types/enums/rate"
import type { Room } from "@/types/hotel"
+import type { Room as MyStayRoom } from "@/types/stores/my-stay"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
-import type { Room as MyStayRoom } from "@/stores/my-stay/myStayRoomDetailsStore"
interface MapRoomDetailsParams {
booking: BookingConfirmation["booking"]
+ rates: Record
room: (Room & { bedType: Room["roomTypes"][number] }) | null
roomNumber: number
}
export function mapRoomDetails({
booking,
+ rates,
room,
roomNumber,
}: MapRoomDetailsParams): MyStayRoom {
@@ -29,45 +31,29 @@ export function mapRoomDetails({
.startOf("day")
.diff(dt(booking.checkInDate).startOf("day"), "days")
- const breakfastPackages = getBreakfastPackagesFromBookingFlow(
- booking.packages
- )
- const featuresPackages = booking.packages.filter(
- (pkg) =>
- pkg.code === RoomPackageCodeEnum.PET_ROOM ||
- pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM ||
- pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
+ const validBreakfastPackages: string[] = [
+ BreakfastPackageEnum.REGULAR_BREAKFAST,
+ BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
+ BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
+ ]
+ const breakfastPackage = booking.packages.find((pkg) =>
+ validBreakfastPackages.includes(pkg.code)
)
- const breakfast: BreakfastPackage | null = breakfastPackages?.length
- ? {
- code: BreakfastPackageEnum.REGULAR_BREAKFAST,
- description: breakfastPackages[0].description,
- localPrice: {
- currency: breakfastPackages[0].currency,
- price: breakfastPackages.reduce(
- (acc, curr) => acc + curr.unitPrice,
- 0
- ),
- totalPrice: breakfastPackages.reduce(
- (acc, curr) => acc + curr.totalPrice,
- 0
- ),
- },
- requestedPrice: {
- currency: breakfastPackages[0].currency,
- price: breakfastPackages.reduce(
- (acc, curr) => acc + curr.unitPrice,
- 0
- ),
- totalPrice: breakfastPackages.reduce(
- (acc, curr) => acc + curr.totalPrice,
- 0
- ),
- },
- packageType: PackageTypeEnum.BreakfastAdult,
- }
- : null
+ // We don't get `requestedPrice` in packages
+ const breakfast: Omit | null =
+ breakfastPackage
+ ? {
+ code: breakfastPackage.code,
+ description: breakfastPackage.description,
+ localPrice: {
+ currency: breakfastPackage.currency,
+ price: breakfastPackage.unitPrice,
+ totalPrice: breakfastPackage.totalPrice,
+ },
+ packageType: PackageTypeEnum.BreakfastAdult,
+ }
+ : null
const isCancelled = booking.reservationStatus === BookingStatusEnum.Cancelled
@@ -87,55 +73,80 @@ export function mapRoomDetails({
booking.vouchers
)
+ let rate = ""
+ if (booking.rateDefinition.cancellationRule) {
+ switch (booking.rateDefinition.cancellationRule) {
+ case CancellationRuleEnum.CancellableBefore6PM:
+ rate = rates.flex
+ break
+ case CancellationRuleEnum.Changeable:
+ rate = rates.change
+ break
+ case CancellationRuleEnum.NonCancellable:
+ rate = rates.save
+ break
+ }
+ }
+
+ const featuresPackages = booking.packages.filter(
+ (pkg) =>
+ pkg.code === RoomPackageCodeEnum.PET_ROOM ||
+ pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM ||
+ pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
+ )
+
+ const packages = featuresPackages.map((pkg) => ({
+ code: pkg.code as RoomPackageCodeEnum,
+ description: pkg.description,
+ inventories: [],
+ itemCode: "",
+ localPrice: {
+ currency: pkg.currency,
+ price: pkg.unitPrice,
+ totalPrice: pkg.totalPrice,
+ },
+ requestedPrice: {
+ currency: pkg.currency,
+ price: pkg.unitPrice,
+ totalPrice: pkg.totalPrice,
+ },
+ }))
+
return {
- hotelId: booking.hotelId,
- roomTypeCode: booking.roomTypeCode,
adults: booking.adults,
- childrenAges: booking.childrenAges,
- checkInDate: booking.checkInDate,
- checkOutDate: booking.checkOutDate,
- confirmationNumber: booking.confirmationNumber,
- cancellationNumber: booking.cancellationNumber,
- createDateTime: booking.createDateTime,
- rateDefinition: booking.rateDefinition,
- guaranteeInfo: booking.guaranteeInfo,
- linkedReservations: booking.linkedReservations,
- bookingCode: booking.bookingCode,
- cheques: booking.cheques,
- vouchers: booking.vouchers,
- isCancelable: booking.isCancelable,
- multiRoom: booking.multiRoom,
- canChangeDate: booking.canChangeDate,
- guest: booking.guest,
- currencyCode: booking.currencyCode,
- vatPercentage: booking.vatPercentage,
- mainRoom: booking.mainRoom,
- roomName: room?.name ?? "",
- roomNumber,
- isCancelled,
- childrenInRoom,
- childrenAsString,
- terms: booking.rateDefinition.cancellationText,
- packages: featuresPackages.map((pkg) => ({
- code: pkg.code as RoomPackageCodeEnum,
- description: pkg.description,
- inventories: [],
- itemCode: "",
- localPrice: {
- currency: pkg.currency,
- price: pkg.unitPrice,
- totalPrice: pkg.totalPrice,
- },
- requestedPrice: {
- currency: pkg.currency,
- price: pkg.unitPrice,
- totalPrice: pkg.totalPrice,
- },
- })),
bedType: {
description: room?.bedType.mainBed.description ?? "",
roomTypeCode: room?.bedType.code ?? "",
},
+ bookingCode: booking.bookingCode,
+ breakfast,
+ canChangeDate: booking.canChangeDate,
+ cancellationNumber: booking.cancellationNumber,
+ checkInDate: booking.checkInDate,
+ checkOutDate: booking.checkOutDate,
+ cheques: booking.cheques,
+ childrenAges: booking.childrenAges,
+ childrenAsString,
+ childrenInRoom,
+ confirmationNumber: booking.confirmationNumber,
+ createDateTime: booking.createDateTime,
+ currencyCode: booking.currencyCode,
+ guaranteeInfo: booking.guaranteeInfo,
+ guest: booking.guest,
+ hotelId: booking.hotelId,
+ isCancelable: booking.isCancelable,
+ isCancelled,
+ linkedReservations: booking.linkedReservations,
+ mainRoom: booking.mainRoom,
+ multiRoom: booking.multiRoom,
+ packages,
+ priceType,
+ rate,
+ rateDefinition: booking.rateDefinition,
+ reservationStatus: booking.reservationStatus,
+ room,
+ roomName: room?.name ?? "",
+ roomNumber,
roomPoints: booking.roomPoints,
roomPrice: {
perNight: {
@@ -153,10 +164,13 @@ export function mapRoomDetails({
requested: undefined,
},
},
- totalPriceExVat: booking.totalPriceExVat,
+ roomTypeCode: booking.roomTypeCode,
+ terms: booking.rateDefinition.cancellationText,
+ totalPoints: booking.totalPoints,
totalPrice: booking.totalPrice,
+ totalPriceExVat: booking.totalPriceExVat,
vatAmount: booking.vatAmount,
- breakfast,
- priceType,
+ vatPercentage: booking.vatPercentage,
+ vouchers: booking.vouchers,
}
}
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentOption/index.tsx b/apps/scandic-web/components/HotelReservation/PaymentOption/index.tsx
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentOption/index.tsx
rename to apps/scandic-web/components/HotelReservation/PaymentOption/index.tsx
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.module.css b/apps/scandic-web/components/HotelReservation/PaymentOption/paymentOption.module.css
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.module.css
rename to apps/scandic-web/components/HotelReservation/PaymentOption/paymentOption.module.css
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.ts b/apps/scandic-web/components/HotelReservation/PaymentOption/paymentOption.ts
similarity index 100%
rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.ts
rename to apps/scandic-web/components/HotelReservation/PaymentOption/paymentOption.ts
diff --git a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx
index 6bdc7498c..2f4cff893 100644
--- a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx
+++ b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/Breakfast.tsx
@@ -12,7 +12,7 @@ import type { Child } from "@/types/components/hotelReservation/selectRate/selec
interface BreakfastProps {
adults: number
- breakfast: BreakfastPackage | false | undefined | null
+ breakfast: Omit | false | undefined | null
breakfastIncluded: boolean
childrenInRoom: Child[] | undefined
currency: string
diff --git a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx
index b801b14c1..b2dbf9d68 100644
--- a/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/PriceDetailsModal/PriceDetailsTable/index.tsx
@@ -42,7 +42,7 @@ type RoomPrice =
export interface Room {
adults: number
bedType: BedTypeSchema | undefined
- breakfast: BreakfastPackage | false | undefined | null
+ breakfast: Omit | false | undefined | null
breakfastIncluded: boolean
childrenInRoom: Child[] | undefined
packages: Packages | null
diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx
index 654306d7c..f91b44ff8 100644
--- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsHeader/RoomPackageFilter/Form/index.tsx
@@ -19,7 +19,7 @@ import styles from "./form.module.css"
import type { PackageEnum } from "@/types/requests/packages"
import type { FormValues } from "./formValues"
-export default function Form({ close }: { close: VoidFunction }) {
+export default function Form({ close }: { close: () => void }) {
const intl = useIntl()
const lang = useLang()
const utils = trpc.useUtils()
diff --git a/apps/scandic-web/components/HotelReservation/SidePeek/index.tsx b/apps/scandic-web/components/HotelReservation/SidePeek/index.tsx
index 5f39a877d..177abf728 100644
--- a/apps/scandic-web/components/HotelReservation/SidePeek/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/SidePeek/index.tsx
@@ -10,14 +10,21 @@ import RoomSidePeek from "@/components/SidePeeks/RoomSidePeek"
import useLang from "@/hooks/useLang"
export default function HotelReservationSidePeek() {
- const activeSidePeek = useSidePeekStore((state) => state.activeSidePeek)
- const hotelId = useSidePeekStore((state) => state.hotelId)
- const confirmationNumber = useSidePeekStore(
- (state) => state.confirmationNumber
- )
- const roomTypeCode = useSidePeekStore((state) => state.roomTypeCode)
- const showCTA = useSidePeekStore((state) => state.showCTA)
- const user = useSidePeekStore((state) => state.user)
+ const {
+ activeSidePeek,
+ confirmationNumber,
+ hotelId,
+ roomTypeCode,
+ showCTA,
+ user,
+ } = useSidePeekStore((state) => ({
+ activeSidePeek: state.activeSidePeek,
+ confirmationNumber: state.confirmationNumber,
+ hotelId: state.hotelId,
+ roomTypeCode: state.roomTypeCode,
+ showCTA: state.showCTA,
+ user: state.user,
+ }))
const close = useSidePeekStore((state) => state.closeSidePeek)
const lang = useLang()
diff --git a/apps/scandic-web/components/ImageGallery/index.tsx b/apps/scandic-web/components/ImageGallery/index.tsx
index 5e0850743..93a0e3f0a 100644
--- a/apps/scandic-web/components/ImageGallery/index.tsx
+++ b/apps/scandic-web/components/ImageGallery/index.tsx
@@ -1,5 +1,6 @@
"use client"
+import { cx } from "class-variance-authority"
import { memo, useState } from "react"
import { Button } from "react-aria-components"
import { useIntl } from "react-intl"
@@ -13,7 +14,6 @@ import Lightbox from "@/components/Lightbox"
import styles from "./imageGallery.module.css"
import type { ImageGalleryProps } from "@/types/components/imageGallery"
-import { cx } from "class-variance-authority"
function ImageGallery({
images,
diff --git a/apps/scandic-web/components/Modal/modal.ts b/apps/scandic-web/components/Modal/modal.ts
index f49c6102a..a86764a88 100644
--- a/apps/scandic-web/components/Modal/modal.ts
+++ b/apps/scandic-web/components/Modal/modal.ts
@@ -9,7 +9,7 @@ export enum AnimationStateEnum {
export type AnimationState = keyof typeof AnimationStateEnum
export type ModalProps = {
- onAnimationComplete?: VoidFunction
+ onAnimationComplete?: () => void
title?: string
subtitle?: string
withActions?: boolean
diff --git a/apps/scandic-web/components/ParkingInformation/ParkingPrices/index.tsx b/apps/scandic-web/components/ParkingInformation/ParkingPrices/index.tsx
index 1d29b4a01..30cebc3c6 100644
--- a/apps/scandic-web/components/ParkingInformation/ParkingPrices/index.tsx
+++ b/apps/scandic-web/components/ParkingInformation/ParkingPrices/index.tsx
@@ -52,6 +52,7 @@ export default function ParkingPrices({
{intl.formatMessage({ defaultMessage: "From" })}
+ {/* eslint-disable formatjs/no-literal-string-in-jsx */}
{`${parking.startTime}-${parking.endTime}`}
diff --git a/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx b/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx
index 4badbb548..b9a980ecd 100644
--- a/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx
+++ b/apps/scandic-web/components/SidePeeks/BookedRoomSidePeek/index.tsx
@@ -5,12 +5,11 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { dt } from "@/lib/dt"
-import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
+import { useMyStayStore } from "@/stores/my-stay"
import GuestDetails from "@/components/HotelReservation/MyStay/GuestDetails"
-import Price from "@/components/HotelReservation/MyStay/Price"
+import PriceType from "@/components/HotelReservation/MyStay/PriceType"
import { hasModifiableRate } from "@/components/HotelReservation/MyStay/utils"
-import { hasBreakfastPackageFromBookingFlow } from "@/components/HotelReservation/MyStay/utils/hasBreakfastPackage"
import ImageGallery from "@/components/ImageGallery"
import Accordion from "@/components/TempDesignSystem/Accordion"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
@@ -18,6 +17,7 @@ import IconChip from "@/components/TempDesignSystem/IconChip"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import useLang from "@/hooks/useLang"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
+import { formatPrice } from "@/utils/numberFormatting"
import RoomDetails from "./RoomDetails"
@@ -36,40 +36,32 @@ export default function BookedRoomSidePeek({
}: BookedRoomSidePeekProps) {
const intl = useIntl()
const lang = useLang()
- const bookedRoom = useMyStayRoomDetailsStore((state) => state.bookedRoom)
- const linkedReservationRooms = useMyStayRoomDetailsStore(
- (state) => state.linkedReservationRooms
- )
- const updateBookedRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.updateBookedRoom
- )
- const updateLinkedReservationRoom = useMyStayRoomDetailsStore(
- (state) => state.actions.updateLinkedReservationRoom
- )
+ const rooms = useMyStayStore((state) => state.rooms)
- const allRooms = [bookedRoom, ...linkedReservationRooms]
-
- const matchingRoomBooking = allRooms.find(
+ const bookingRoom = rooms.find(
(r) => r.confirmationNumber === confirmationNumber
)
- if (!matchingRoomBooking) {
+ if (!bookingRoom) {
return null
}
const {
- roomNumber,
- cancellationNumber,
adults,
- childrenInRoom,
- terms,
- packages,
bedType,
- checkInDate,
bookingCode,
- roomPrice,
+ breakfast,
+ cancellationNumber,
+ checkInDate,
+ childrenInRoom,
+ currencyCode,
isCancelled,
- } = matchingRoomBooking
+ packages,
+ rateDefinition,
+ roomNumber,
+ terms,
+ totalPrice,
+ } = bookingRoom
const fromDate = dt(checkInDate).locale(lang)
@@ -96,6 +88,24 @@ export default function BookedRoomSidePeek({
const adultsOnlyMsg = adultsMsg
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
+
+ const formattedTotalPrice = formatPrice(intl, totalPrice, currencyCode)
+
+ let breakfastPrice = intl.formatMessage({
+ defaultMessage: "No breakfast",
+ })
+ if (rateDefinition.breakfastIncluded) {
+ breakfastPrice = intl.formatMessage({
+ defaultMessage: "Included",
+ })
+ } else if (breakfast) {
+ breakfastPrice = formatPrice(
+ intl,
+ breakfast.localPrice.totalPrice,
+ breakfast.localPrice.currency
+ )
+ }
+
return (
- {hasModifiableRate(
- matchingRoomBooking.rateDefinition.cancellationRule
- ) && (
+ {hasModifiableRate(rateDefinition.cancellationRule) && (
@@ -256,20 +264,7 @@ export default function BookedRoomSidePeek({
-
- {packages &&
- hasBreakfastPackageFromBookingFlow(
- packages.map((pkg) => ({
- code: pkg.code,
- }))
- )
- ? intl.formatMessage({
- defaultMessage: "Included",
- })
- : intl.formatMessage({
- defaultMessage: "Not included",
- })}
-
+ {breakfastPrice}
@@ -338,9 +333,15 @@ export default function BookedRoomSidePeek({
-
@@ -368,16 +369,7 @@ export default function BookedRoomSidePeek({
)}
-
+
>(function Checkbox(
- { className, name, children, registerOptions, hideError, topAlign = false },
+ {
+ className = "",
+ name,
+ children,
+ registerOptions,
+ hideError,
+ topAlign = false,
+ },
ref
) {
const { control } = useFormContext()
diff --git a/apps/scandic-web/contexts/MyStay.ts b/apps/scandic-web/contexts/MyStay.ts
new file mode 100644
index 000000000..c5f0c1bf4
--- /dev/null
+++ b/apps/scandic-web/contexts/MyStay.ts
@@ -0,0 +1,5 @@
+import { createContext } from "react"
+
+import type { MyStayStore } from "@/types/contexts/my-stay"
+
+export const MyStayContext = createContext(null)
diff --git a/apps/scandic-web/hooks/booking/useGuaranteeBooking.ts b/apps/scandic-web/hooks/booking/useGuaranteeBooking.ts
index 4c9b83303..7e77da181 100644
--- a/apps/scandic-web/hooks/booking/useGuaranteeBooking.ts
+++ b/apps/scandic-web/hooks/booking/useGuaranteeBooking.ts
@@ -12,15 +12,10 @@ import { trackEvent } from "@/utils/tracking/base"
const maxRetries = 15
const retryInterval = 2000
-export function useGuaranteeBooking({
- confirmationNumber,
- handleBookingCompleted = () => {},
- isAncillaryFlow,
-}: {
- confirmationNumber: string
- handleBookingCompleted?: () => void
- isAncillaryFlow?: boolean
-}) {
+export function useGuaranteeBooking(
+ confirmationNumber: string,
+ isAncillaryFlow = false
+) {
const intl = useIntl()
const router = useRouter()
const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
@@ -51,15 +46,13 @@ export function useGuaranteeBooking({
const utils = trpc.useUtils()
const guaranteeBooking = trpc.booking.guarantee.useMutation({
- onSuccess: (result, variables) => {
+ onSuccess: (result) => {
if (result) {
if (result.reservationStatus == BookingStatusEnum.BookingCompleted) {
- handleBookingCompleted()
+ utils.booking.get.invalidate({ confirmationNumber })
} else {
setIsPollingForBookingStatus(true)
- utils.booking.status.invalidate({
- confirmationNumber: variables.confirmationNumber,
- })
+ utils.booking.status.invalidate({ confirmationNumber })
}
} else {
handleGuaranteeError()
@@ -81,6 +74,7 @@ export function useGuaranteeBooking({
useEffect(() => {
if (bookingStatus?.data?.paymentUrl && isPollingForBookingStatus) {
router.push(bookingStatus.data.paymentUrl)
+ utils.booking.get.invalidate({ confirmationNumber })
setIsPollingForBookingStatus(false)
} else if (bookingStatus.isTimeout) {
handleGuaranteeError("Timeout")
@@ -91,6 +85,8 @@ export function useGuaranteeBooking({
handleGuaranteeError,
setIsPollingForBookingStatus,
isPollingForBookingStatus,
+ confirmationNumber,
+ utils.booking.get,
])
const isLoading =
diff --git a/apps/scandic-web/lib/dt.ts b/apps/scandic-web/lib/dt.ts
index e52f7cf57..5f8a90eac 100644
--- a/apps/scandic-web/lib/dt.ts
+++ b/apps/scandic-web/lib/dt.ts
@@ -6,6 +6,7 @@ import "dayjs/locale/sv"
import d from "dayjs"
import nb from "dayjs/locale/nb"
import advancedFormat from "dayjs/plugin/advancedFormat"
+import customParseFormat from "dayjs/plugin/customParseFormat"
import duration from "dayjs/plugin/duration"
import isSameOrAfter from "dayjs/plugin/isSameOrAfter"
import isSameOrBefore from "dayjs/plugin/isSameOrBefore"
@@ -33,5 +34,6 @@ d.extend(utc)
d.extend(isSameOrAfter)
d.extend(isSameOrBefore)
d.extend(duration)
+d.extend(customParseFormat)
export const dt = d
diff --git a/apps/scandic-web/lib/trpc/memoizedRequests/index.ts b/apps/scandic-web/lib/trpc/memoizedRequests/index.ts
index 72f0f8723..9dd7eb0ba 100644
--- a/apps/scandic-web/lib/trpc/memoizedRequests/index.ts
+++ b/apps/scandic-web/lib/trpc/memoizedRequests/index.ts
@@ -17,6 +17,7 @@ import type {
HotelInput,
} from "@/types/trpc/routers/hotel/hotel"
import type { Lang } from "@/constants/languages"
+import type { LinkedReservationsInput } from "@/server/routers/booking/input"
import type { GetHotelsByCSFilterInput } from "@/server/routers/hotels/input"
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
@@ -140,6 +141,12 @@ export const getBookingConfirmation = cache(
}
)
+export const getLinkedReservations = cache(
+ async function getMemoizedLinkedReservations(input: LinkedReservationsInput) {
+ return serverClient().booking.linkedReservations(input)
+ }
+)
+
export const getCityCoordinates = cache(
async function getMemoizedCityCoordinates(input: CityCoordinatesInput) {
return serverClient().hotel.map.city(input)
diff --git a/apps/scandic-web/providers/AddAncillaryProvider.tsx b/apps/scandic-web/providers/AddAncillaryProvider.tsx
index c8590767d..58645ea46 100644
--- a/apps/scandic-web/providers/AddAncillaryProvider.tsx
+++ b/apps/scandic-web/providers/AddAncillaryProvider.tsx
@@ -6,7 +6,7 @@ import {
createAddAncillaryStore,
} from "@/stores/my-stay/add-ancillary-flow"
-import { getAncillarySessionData } from "@/components/HotelReservation/MyStay/Ancillaries/utils"
+import { getAncillarySessionData } from "@/components/HotelReservation/MyStay/utils/ancillaries"
import { AddAncillaryContext } from "@/contexts/AddAncillary"
import type { Ancillaries } from "@/types/components/myPages/myStay/ancillaries"
diff --git a/apps/scandic-web/providers/MyStay.tsx b/apps/scandic-web/providers/MyStay.tsx
new file mode 100644
index 000000000..6a3fa6e2c
--- /dev/null
+++ b/apps/scandic-web/providers/MyStay.tsx
@@ -0,0 +1,110 @@
+"use client"
+import { notFound } from "next/navigation"
+import { use, useRef } from "react"
+import { useIntl } from "react-intl"
+
+import { trpc } from "@/lib/trpc/client"
+import { createMyStayStore } from "@/stores/my-stay"
+
+import { MyStaySkeleton } from "@/components/HotelReservation/MyStay/myStaySkeleton"
+import { MyStayContext } from "@/contexts/MyStay"
+
+import type { Packages } from "@/types/components/myPages/myStay/ancillaries"
+import type { MyStayStore } from "@/types/contexts/my-stay"
+import type { RoomCategories } from "@/types/hotel"
+import type {
+ BookingConfirmation,
+ BookingConfirmationSchema,
+} from "@/types/trpc/routers/booking/confirmation"
+import type { CreditCard } from "@/types/user"
+import type { Lang } from "@/constants/languages"
+
+interface MyStayProviderProps {
+ bookingConfirmation: BookingConfirmation
+ breakfastPackages: Packages | null
+ lang: Lang
+ linkedReservationsPromise: Promise
+ refId: string
+ roomCategories: RoomCategories
+ savedCreditCards: CreditCard[] | null
+}
+
+export default function MyStayProvider({
+ bookingConfirmation,
+ breakfastPackages,
+ children,
+ lang,
+ linkedReservationsPromise,
+ refId,
+ roomCategories,
+ savedCreditCards,
+}: React.PropsWithChildren) {
+ const storeRef = useRef()
+ const intl = useIntl()
+
+ const { data, error, isFetching, isFetchedAfterMount } =
+ trpc.booking.get.useQuery(
+ {
+ confirmationNumber: bookingConfirmation.booking.confirmationNumber,
+ lang,
+ },
+ {
+ initialData: bookingConfirmation,
+ refetchOnMount: false,
+ refetchOnWindowFocus: false,
+ }
+ )
+
+ // We need this two-step business since `use` must resolve
+ // the promise passed from the server whereas `useQuery`
+ // needs to own the data when in the client so that invalidations
+ // actually triggers a refetch of the data
+ const linkedReservationsResponses = use(linkedReservationsPromise)
+ const {
+ data: linkedReservations,
+ error: linkedReservationsError,
+ isFetching: linkedReservationsIsFetching,
+ isFetchedAfterMount: linkedReservationsIsFetchedAfterMount,
+ } = trpc.booking.linkedReservations.useQuery(
+ {
+ lang,
+ rooms: bookingConfirmation.booking.linkedReservations,
+ },
+ {
+ initialData: linkedReservationsResponses,
+ refetchOnMount: false,
+ refetchOnWindowFocus: false,
+ }
+ )
+
+ if (isFetching || linkedReservationsIsFetching) {
+ return
+ }
+
+ if (!data || error || linkedReservationsError) {
+ return notFound()
+ }
+
+ const rooms = [data.booking, ...linkedReservations]
+
+ const hasInvalidatedQueryAndRefetched =
+ (isFetchedAfterMount && data) ||
+ (linkedReservationsIsFetchedAfterMount && linkedReservations)
+ if (!storeRef.current || hasInvalidatedQueryAndRefetched) {
+ storeRef.current = createMyStayStore({
+ breakfastPackages,
+ hotel: bookingConfirmation.hotel,
+ intl,
+ refId,
+ roomCategories,
+ rooms,
+ savedCreditCards,
+ })
+ }
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/scandic-web/public/_static/fonts/material-symbols/rounded-112272ae.woff2 b/apps/scandic-web/public/_static/fonts/material-symbols/rounded-112272ae.woff2
deleted file mode 100644
index 7a133cedc3569e23abcb4d049a6ee45f55d470b1..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 34088
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