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 7a133cedc..000000000
Binary files a/apps/scandic-web/public/_static/fonts/material-symbols/rounded-112272ae.woff2 and /dev/null differ
diff --git a/apps/scandic-web/public/_static/fonts/material-symbols/rounded-a03ed056.woff2 b/apps/scandic-web/public/_static/fonts/material-symbols/rounded-a03ed056.woff2
new file mode 100644
index 000000000..84209fed1
Binary files /dev/null and b/apps/scandic-web/public/_static/fonts/material-symbols/rounded-a03ed056.woff2 differ
diff --git a/apps/scandic-web/server/routers/booking/input.ts b/apps/scandic-web/server/routers/booking/input.ts
index 6b374a91d..8a87965a1 100644
--- a/apps/scandic-web/server/routers/booking/input.ts
+++ b/apps/scandic-web/server/routers/booking/input.ts
@@ -131,6 +131,11 @@ export const cancelBookingInput = z.object({
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
})
+export const cancelManyBookingsInput = z.object({
+ confirmationNumbers: z.array(z.string()),
+ language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
+})
+
export const guaranteeBookingInput = z.object({
confirmationNumber: z.string(),
card: z
@@ -175,5 +180,15 @@ const confirmationNumberInput = z.object({
})
export const getBookingInput = confirmationNumberInput
+export const getLinkedReservationsInput = z.object({
+ lang: z.nativeEnum(Lang).optional(),
+ rooms: z.array(
+ z.object({
+ confirmationNumber: z.string(),
+ })
+ ),
+})
+
+export type LinkedReservationsInput = z.input
export const getBookingStatusInput = confirmationNumberInput
diff --git a/apps/scandic-web/server/routers/booking/mutation.ts b/apps/scandic-web/server/routers/booking/mutation.ts
index 4b50a87e9..f0d15af5a 100644
--- a/apps/scandic-web/server/routers/booking/mutation.ts
+++ b/apps/scandic-web/server/routers/booking/mutation.ts
@@ -6,6 +6,7 @@ import { router, safeProtectedServiceProcedure } from "@/server/trpc"
import {
addPackageInput,
cancelBookingInput,
+ cancelManyBookingsInput,
createBookingInput,
guaranteeBookingInput,
priceChangeInput,
@@ -13,6 +14,7 @@ import {
updateBookingInput,
} from "./input"
import { bookingConfirmationSchema, createBookingSchema } from "./output"
+import { cancelBooking } from "./utils"
export const bookingMutationRouter = router({
create: safeProtectedServiceProcedure
@@ -113,52 +115,40 @@ export const bookingMutationRouter = router({
cancel: safeProtectedServiceProcedure
.input(cancelBookingInput)
.mutation(async function ({ ctx, input }) {
- const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
+ const token = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, language } = input
+ return await cancelBooking(confirmationNumber, language, token)
+ }),
+ cancelMany: safeProtectedServiceProcedure
+ .input(cancelManyBookingsInput)
+ .mutation(async function ({ ctx, input }) {
+ const token = ctx.session?.token.access_token ?? ctx.serviceToken
+ const { confirmationNumbers, language } = input
- const cancelBookingCounter = createCounter("trpc.booking", "cancel")
- const metricsCancelBooking = cancelBookingCounter.init({
- confirmationNumber,
- language,
- })
-
- metricsCancelBooking.start()
-
- const headers = {
- Authorization: `Bearer ${accessToken}`,
- }
-
- const cancellationReason = {
- reasonCode: "WEB-CANCEL",
- reason: "WEB-CANCEL",
- }
-
- const apiResponse = await api.remove(
- api.endpoints.v1.Booking.cancel(confirmationNumber),
- {
- headers,
- body: JSON.stringify(cancellationReason),
- } as RequestInit,
- { language }
+ const responses = await Promise.allSettled(
+ confirmationNumbers.map((confirmationNumber) =>
+ cancelBooking(confirmationNumber, language, token)
+ )
)
- if (!apiResponse.ok) {
- await metricsCancelBooking.httpError(apiResponse)
- return false
+ const cancelledRoomsSuccessfully = []
+ for (const [idx, response] of responses.entries()) {
+ if (response.status === "fulfilled") {
+ if (response.value) {
+ cancelledRoomsSuccessfully.push(true)
+ continue
+ }
+ } else {
+ console.info(
+ `Cancelling booking failed for confirmationNumber: ${confirmationNumbers[idx]}`
+ )
+ console.error(response.reason)
+ }
+
+ cancelledRoomsSuccessfully.push(false)
}
- const apiJson = await apiResponse.json()
-
- const verifiedData = createBookingSchema.safeParse(apiJson)
-
- if (!verifiedData.success) {
- metricsCancelBooking.validationError(verifiedData.error)
- return null
- }
-
- metricsCancelBooking.success()
-
- return verifiedData.data
+ return cancelledRoomsSuccessfully
}),
packages: safeProtectedServiceProcedure
.input(addPackageInput)
diff --git a/apps/scandic-web/server/routers/booking/output.ts b/apps/scandic-web/server/routers/booking/output.ts
index d648e34cc..072b2681b 100644
--- a/apps/scandic-web/server/routers/booking/output.ts
+++ b/apps/scandic-web/server/routers/booking/output.ts
@@ -201,7 +201,7 @@ export const bookingConfirmationSchema = z
attributes: z.object({
adults: z.number().int(),
ancillary: ancillarySchema,
- cancellationNumber: z.string().nullable().default(""),
+ cancelationNumber: z.string().nullable().default(""),
checkInDate: z.date({ coerce: true }),
checkOutDate: z.date({ coerce: true }),
childBedPreferences: z.array(childBedPreferencesSchema).default([]),
@@ -263,4 +263,6 @@ export const bookingConfirmationSchema = z
isCancelable: !!data.links.cancel,
isModifiable: !!data.links.modify,
canModifyAncillaries: !!data.links.addAncillary,
+ // Typo from API
+ cancellationNumber: data.attributes.cancelationNumber,
}))
diff --git a/apps/scandic-web/server/routers/booking/query.ts b/apps/scandic-web/server/routers/booking/query.ts
index e39913dc0..280a49769 100644
--- a/apps/scandic-web/server/routers/booking/query.ts
+++ b/apps/scandic-web/server/routers/booking/query.ts
@@ -13,68 +13,54 @@ import {
createRefIdInput,
getBookingInput,
getBookingStatusInput,
+ getLinkedReservationsInput,
} from "./input"
-import { bookingConfirmationSchema, createBookingSchema } from "./output"
-import { getBookedHotelRoom } from "./utils"
+import { createBookingSchema } from "./output"
+import { getBookedHotelRoom, getBooking } from "./utils"
export const bookingQueryRouter = router({
get: safeProtectedServiceProcedure
.input(getBookingInput)
- .query(async function ({
- ctx,
- input: { confirmationNumber, lang: inputLang },
- }) {
+ .use(async ({ ctx, input, next }) => {
+ const lang = input.lang ?? ctx.lang
+ const token = ctx.session?.token.access_token ?? ctx.serviceToken
+ return next({
+ ctx: {
+ lang,
+ token,
+ },
+ })
+ })
+ .query(async function ({ ctx, input: { confirmationNumber } }) {
const getBookingCounter = createCounter("trpc.booking", "get")
const metricsGetBooking = getBookingCounter.init({ confirmationNumber })
metricsGetBooking.start()
- let lang = ctx.lang ?? inputLang
+ const booking = await getBooking(confirmationNumber, ctx.lang, ctx.token)
- const token = ctx.session?.token.access_token ?? ctx.serviceToken
-
- const apiResponse = await api.get(
- api.endpoints.v1.Booking.booking(confirmationNumber),
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- }
- )
-
- if (!apiResponse.ok) {
- await metricsGetBooking.httpError(apiResponse)
-
- // If the booking is not found, return null.
- // This scenario is expected to happen when a logged in user trying to access a booking that doesn't belong to them.
- if (apiResponse.status === 400) {
- return null
- }
-
- throw serverErrorByStatus(apiResponse.status, apiResponse)
- }
-
- const apiJson = await apiResponse.json()
- const booking = bookingConfirmationSchema.safeParse(apiJson)
- if (!booking.success) {
- metricsGetBooking.validationError(booking.error)
- throw badRequestError()
+ if (!booking) {
+ metricsGetBooking.dataError(
+ `Fail to get booking data for ${confirmationNumber}`,
+ { confirmationNumber }
+ )
+ return null
}
const hotelData = await getHotel(
{
- hotelId: booking.data.hotelId,
+ hotelId: booking.hotelId,
isCardOnlyPayment: false,
- language: lang,
+ language: ctx.lang,
},
ctx.serviceToken
)
if (!hotelData) {
metricsGetBooking.dataError(
- `Failed to get hotel data for ${booking.data.hotelId}`,
+ `Failed to get hotel data for ${booking.hotelId}`,
{
- hotelId: booking.data.hotelId,
+ hotelId: booking.hotelId,
}
)
@@ -85,13 +71,62 @@ export const bookingQueryRouter = router({
return {
...hotelData,
- booking: booking.data,
+ booking,
room: getBookedHotelRoom(
hotelData.roomCategories,
- booking.data.roomTypeCode
+ booking.roomTypeCode
),
}
}),
+ linkedReservations: safeProtectedServiceProcedure
+ .input(getLinkedReservationsInput)
+ .use(async ({ ctx, input, next }) => {
+ const lang = input.lang ?? ctx.lang
+ const token = ctx.session?.token.access_token ?? ctx.serviceToken
+ return next({
+ ctx: {
+ lang,
+ token,
+ },
+ })
+ })
+ .query(async function ({ ctx, input: { rooms } }) {
+ const getLinkedReservationsCounter = createCounter(
+ "trpc.booking",
+ "linkedReservations"
+ )
+ const metricsGetLinkedReservations = getLinkedReservationsCounter.init({
+ confirmationNumbers: rooms,
+ })
+
+ metricsGetLinkedReservations.start()
+
+ const linkedReservationsResult = await Promise.allSettled(
+ rooms.map((room) =>
+ getBooking(room.confirmationNumber, ctx.lang, ctx.token)
+ )
+ )
+ const linkedReservations = []
+ for (const booking of linkedReservationsResult) {
+ if (booking.status === "fulfilled") {
+ if (booking.value) {
+ linkedReservations.push(booking.value)
+ } else {
+ metricsGetLinkedReservations.dataError(
+ `Unexpected value for linked reservation`
+ )
+ }
+ } else {
+ metricsGetLinkedReservations.dataError(
+ `Failed to get linked reservation`
+ )
+ }
+ }
+
+ metricsGetLinkedReservations.success()
+
+ return linkedReservations
+ }),
status: serviceProcedure.input(getBookingStatusInput).query(async function ({
ctx,
input,
diff --git a/apps/scandic-web/server/routers/booking/utils.ts b/apps/scandic-web/server/routers/booking/utils.ts
index 3068453ae..5149a618c 100644
--- a/apps/scandic-web/server/routers/booking/utils.ts
+++ b/apps/scandic-web/server/routers/booking/utils.ts
@@ -1,5 +1,13 @@
+import * as api from "@/lib/api"
+import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
+import { createCounter } from "@/server/telemetry"
+import { toApiLang } from "@/server/utils"
+
+import { bookingConfirmationSchema, createBookingSchema } from "./output"
+
import type { Room } from "@/types/hotel"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
+import type { Lang } from "@/constants/languages"
export function getBookedHotelRoom(
rooms: Room[] | undefined,
@@ -25,3 +33,96 @@ export function getBookedHotelRoom(
bedType,
}
}
+
+export async function getBooking(
+ confirmationNumber: string,
+ lang: Lang,
+ token: string
+) {
+ const getBookingCounter = createCounter("booking", "get")
+ const metricsGetBooking = getBookingCounter.init({ confirmationNumber })
+
+ metricsGetBooking.start()
+
+ const apiResponse = await api.get(
+ api.endpoints.v1.Booking.booking(confirmationNumber),
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ { language: toApiLang(lang) }
+ )
+
+ if (!apiResponse.ok) {
+ await metricsGetBooking.httpError(apiResponse)
+
+ // If the booking is not found, return null.
+ // This scenario is expected to happen when a logged in user trying to access a booking that doesn't belong to them.
+ if (apiResponse.status === 400) {
+ return null
+ }
+
+ throw serverErrorByStatus(apiResponse.status, apiResponse)
+ }
+
+ const apiJson = await apiResponse.json()
+ const booking = bookingConfirmationSchema.safeParse(apiJson)
+ if (!booking.success) {
+ metricsGetBooking.validationError(booking.error)
+ throw badRequestError()
+ }
+
+ metricsGetBooking.success()
+
+ return booking.data
+}
+
+export async function cancelBooking(
+ confirmationNumber: string,
+ language: string,
+ token: string
+) {
+ const cancellationReason = {
+ reasonCode: "WEB-CANCEL",
+ reason: "WEB-CANCEL",
+ }
+
+ const cancelBookingCounter = createCounter("booking", "cancel")
+ const metricsCancelBooking = cancelBookingCounter.init({
+ cancellationReason,
+ confirmationNumber,
+ language,
+ })
+
+ metricsCancelBooking.start()
+
+ const headers = {
+ Authorization: `Bearer ${token}`,
+ }
+
+ const apiResponse = await api.remove(
+ api.endpoints.v1.Booking.cancel(confirmationNumber),
+ {
+ headers,
+ body: JSON.stringify(cancellationReason),
+ } as RequestInit,
+ { language }
+ )
+
+ if (!apiResponse.ok) {
+ await metricsCancelBooking.httpError(apiResponse)
+ return false
+ }
+
+ const apiJson = await apiResponse.json()
+ const verifiedData = createBookingSchema.safeParse(apiJson)
+ if (!verifiedData.success) {
+ metricsCancelBooking.validationError(verifiedData.error)
+ return null
+ }
+
+ metricsCancelBooking.success()
+
+ return verifiedData.data
+}
diff --git a/apps/scandic-web/server/routers/hotels/utils.ts b/apps/scandic-web/server/routers/hotels/utils.ts
index 0a22c90bc..5b1c16354 100644
--- a/apps/scandic-web/server/routers/hotels/utils.ts
+++ b/apps/scandic-web/server/routers/hotels/utils.ts
@@ -1031,6 +1031,7 @@ export async function getRoomsAvailability(
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotel(hotelId),
{
+ cache: undefined, // overwrite default
headers: {
Authorization: `Bearer ${token}`,
},
diff --git a/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts b/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts
index bed6f315e..a2724988f 100644
--- a/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts
+++ b/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts
@@ -2,7 +2,7 @@ import { produce } from "immer"
import { useContext } from "react"
import { create, useStore } from "zustand"
-import { clearAncillarySessionData } from "@/components/HotelReservation/MyStay/Ancillaries/utils"
+import { clearAncillarySessionData } from "@/components/HotelReservation/MyStay/utils/ancillaries"
import { AddAncillaryContext } from "@/contexts/AddAncillary"
import type {
@@ -49,18 +49,18 @@ export interface AddAncillaryState {
selectedCategory: string
selectCategory: (category: string) => void
ancillariesBySelectedCategory: Ancillary["ancillaryContent"]
- openModal: VoidFunction
- closeModal: VoidFunction
- prevStep: VoidFunction
+ openModal: () => void
+ closeModal: () => void
+ prevStep: () => void
breakfastData: BreakfastData | null
setBreakfastData: (breakfastData: BreakfastData | null) => void
isBreakfast: boolean
isOpen: boolean
selectedAncillary: SelectedAncillary | null
selectAncillary: (ancillary: SelectedAncillary) => void
- selectQuantity: VoidFunction
- selectDeliveryTime: VoidFunction
- selectQuantityAndDeliveryTime: VoidFunction
+ selectQuantity: () => void
+ selectDeliveryTime: () => void
+ selectQuantityAndDeliveryTime: () => void
}
function findAncillaryByCategory(
diff --git a/apps/scandic-web/stores/my-stay/helpers.ts b/apps/scandic-web/stores/my-stay/helpers.ts
new file mode 100644
index 000000000..4f3edb746
--- /dev/null
+++ b/apps/scandic-web/stores/my-stay/helpers.ts
@@ -0,0 +1,77 @@
+import { formatPrice } from "@/utils/numberFormatting"
+
+import type { IntlShape } from "react-intl"
+
+import { CurrencyEnum } from "@/types/enums/currency"
+import type { Room } from "@/types/stores/my-stay"
+
+export function calculateTotalPrice(
+ rooms: Room[],
+ currency: CurrencyEnum,
+ intl: IntlShape,
+ allRoomsAreCancelled: boolean
+) {
+ const totals = rooms.reduce(
+ (total, room) => {
+ if (!allRoomsAreCancelled && room.isCancelled) {
+ return total
+ }
+
+ if (room.cheques) {
+ total.cheques = total.cheques + room.cheques
+ }
+ if (room.vouchers) {
+ total.vouchers = total.vouchers + room.vouchers
+ }
+ if (room.totalPoints) {
+ total.points = total.points + room.totalPoints
+ }
+ if (room.totalPrice) {
+ total.cash = total.cash + room.totalPrice
+ }
+ return total
+ },
+ {
+ cash: 0,
+ cheques: 0,
+ points: 0,
+ vouchers: 0,
+ }
+ )
+
+ let totalPrice = ""
+ if (totals.cheques) {
+ totalPrice = `${totals.cheques} ${CurrencyEnum.CC}`
+ }
+ if (totals.points) {
+ const appendTotalPrice = totalPrice ? `${totalPrice} + ` : ""
+ totalPrice = `${appendTotalPrice}${totals.points} ${CurrencyEnum.POINTS}`
+ }
+ if (totals.vouchers) {
+ const appendTotalPrice = totalPrice ? `${totalPrice} + ` : ""
+ totalPrice = `${appendTotalPrice}${totals.vouchers} ${CurrencyEnum.Voucher}`
+ }
+ if (totals.cash) {
+ const appendTotalPrice = totalPrice ? `${totalPrice} + ` : ""
+ const cashPrice = formatPrice(intl, totals.cash, currency)
+ totalPrice = `${appendTotalPrice}${cashPrice}`
+ }
+
+ return totalPrice
+}
+
+export function calculateTotalPoints(
+ rooms: Room[],
+ allRoomsAreCancelled: boolean
+) {
+ return rooms.reduce((total, room) => {
+ if (!allRoomsAreCancelled && room.isCancelled) {
+ return total
+ }
+ return total + room.totalPoints
+ }, 0)
+}
+
+export function isAllRoomsCancelled(rooms: Room[]) {
+ return !rooms.some((room) => room.isCancelled === false)
+}
diff --git a/apps/scandic-web/stores/my-stay/index.ts b/apps/scandic-web/stores/my-stay/index.ts
new file mode 100644
index 000000000..fd6544ebc
--- /dev/null
+++ b/apps/scandic-web/stores/my-stay/index.ts
@@ -0,0 +1,106 @@
+"use client"
+import { produce } from "immer"
+import { useContext } from "react"
+import { create, useStore } from "zustand"
+
+import { getBookedHotelRoom } from "@/server/routers/booking/utils"
+
+import { mapRoomDetails } from "@/components/HotelReservation/MyStay/utils/mapRoomDetails"
+import { MyStayContext } from "@/contexts/MyStay"
+
+import {
+ calculateTotalPoints,
+ calculateTotalPrice,
+ isAllRoomsCancelled,
+} from "./helpers"
+
+import type { InitialState, MyStayState } from "@/types/stores/my-stay"
+
+export function createMyStayStore({
+ breakfastPackages,
+ hotel,
+ intl,
+ refId,
+ roomCategories,
+ rooms,
+ savedCreditCards,
+}: InitialState) {
+ const rates = {
+ change: intl.formatMessage({
+ defaultMessage: "Free rebooking",
+ }),
+ flex: intl.formatMessage({
+ defaultMessage: "Free cancellation",
+ }),
+ save: intl.formatMessage({
+ defaultMessage: "Non-refundable",
+ }),
+ }
+
+ const mappedRooms = rooms.map((booking, idx) => {
+ const room = getBookedHotelRoom(roomCategories, booking.roomTypeCode)
+ return mapRoomDetails({
+ booking,
+ rates,
+ room,
+ roomNumber: idx + 1,
+ })
+ })
+ const bookedRoom = mappedRooms[0]
+
+ const allRoomsAreCancelled = isAllRoomsCancelled(mappedRooms)
+
+ const totalPoints = calculateTotalPoints(mappedRooms, allRoomsAreCancelled)
+
+ const totalPrice = calculateTotalPrice(
+ mappedRooms,
+ bookedRoom.currencyCode,
+ intl,
+ allRoomsAreCancelled
+ )
+
+ const mainRoom = mappedRooms.find((r) => r.mainRoom) ?? bookedRoom
+
+ return create()((set) => {
+ return {
+ allRoomsAreCancelled,
+ bookedRoom,
+ breakfastPackages,
+ hotel,
+ mainRoom,
+ manageStay: false,
+ refId,
+ rooms: mappedRooms,
+ savedCreditCards,
+ totalPoints,
+ totalPrice,
+
+ actions: {
+ closeManageStay() {
+ return set(
+ produce((state: MyStayState) => {
+ state.manageStay = false
+ })
+ )
+ },
+ openManageStay() {
+ return set(
+ produce((state: MyStayState) => {
+ state.manageStay = true
+ })
+ )
+ },
+ },
+ }
+ })
+}
+
+export function useMyStayStore(selector: (store: MyStayState) => T) {
+ const store = useContext(MyStayContext)
+
+ if (!store) {
+ throw new Error("useMyStayStore must be used within MyStayProvider")
+ }
+
+ return useStore(store, selector)
+}
diff --git a/apps/scandic-web/stores/my-stay/manageStayStore.ts b/apps/scandic-web/stores/my-stay/manageStayStore.ts
deleted file mode 100644
index 8430a248f..000000000
--- a/apps/scandic-web/stores/my-stay/manageStayStore.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { create } from "zustand"
-
-type ActiveView =
- | "actionPanel"
- | "cancelStay"
- | "modifyStay"
- | "guaranteeLateArrival"
-
-interface ManageStayState {
- isOpen: boolean
- activeView: ActiveView
- currentStep: number
- isLoading: boolean
- actions: {
- setIsOpen: (isOpen: boolean) => void
- setActiveView: (view: ActiveView) => void
- setCurrentStep: (step: number) => void
- setIsLoading: (isLoading: boolean) => void
- handleForward: () => void
- handleCloseView: () => void
- handleCloseModal: () => void
- }
-}
-
-export const useManageStayStore = create((set) => ({
- isOpen: false,
- activeView: "actionPanel",
- currentStep: 1,
- isLoading: false,
- actions: {
- setIsOpen: (isOpen) => set({ isOpen }),
- setActiveView: (activeView) => set({ activeView }),
- setCurrentStep: (currentStep) => set({ currentStep }),
- setIsLoading: (isLoading) => set({ isLoading }),
- handleForward: () =>
- set((state) => ({ currentStep: state.currentStep + 1 })),
- handleCloseView: () =>
- set({
- currentStep: 1,
- isLoading: false,
- activeView: "actionPanel",
- }),
- handleCloseModal: () =>
- set({
- currentStep: 1,
- isOpen: false,
- activeView: "actionPanel",
- }),
- },
-}))
diff --git a/apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts b/apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts
deleted file mode 100644
index 221608f7f..000000000
--- a/apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-import { create } from "zustand"
-
-import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
-import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
-import type { RoomPrice } from "@/types/components/hotelReservation/enterDetails/details"
-import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
-import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
-import { CurrencyEnum } from "@/types/enums/currency"
-import type { Packages } from "@/types/requests/packages"
-import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
-
-export type Room = Pick<
- BookingConfirmation["booking"],
- | "hotelId"
- | "adults"
- | "checkInDate"
- | "checkOutDate"
- | "childrenAges"
- | "createDateTime"
- | "rateDefinition"
- | "guaranteeInfo"
- | "linkedReservations"
- | "confirmationNumber"
- | "cancellationNumber"
- | "bookingCode"
- | "cheques"
- | "vouchers"
- | "isCancelable"
- | "multiRoom"
- | "canChangeDate"
- | "guest"
- | "roomTypeCode"
- | "currencyCode"
- | "vatPercentage"
- | "roomPoints"
- | "totalPrice"
- | "totalPriceExVat"
- | "vatAmount"
-> & {
- roomName: string
- roomNumber: number | null
- isCancelled: boolean
- childrenInRoom: Child[]
- childrenAsString: string
- terms: string | null
- packages: Packages | null
- bedType: BedTypeSchema
- roomPrice: RoomPrice
- breakfast: BreakfastPackage | null
- mainRoom: boolean
- priceType: PriceTypeEnum
-}
-
-interface MyStayRoomDetailsState {
- bookedRoom: Room
- linkedReservationRooms: Room[]
- actions: {
- addBookedRoom: (room: Room) => void
- updateBookedRoom: (room: Room) => void
- addLinkedReservationRoom: (room: Room) => void
- updateLinkedReservationRoom: (room: Room) => void
- }
-}
-
-export const useMyStayRoomDetailsStore = create(
- (set) => ({
- bookedRoom: {
- hotelId: "",
- roomTypeCode: "",
- adults: 0,
- childrenAges: [],
- checkInDate: new Date(),
- checkOutDate: new Date(),
- confirmationNumber: "",
- cancellationNumber: null,
- bookingCode: null,
- cheques: 0,
- vouchers: 0,
- currencyCode: CurrencyEnum.Unknown,
- guest: {
- email: "",
- firstName: "",
- lastName: "",
- membershipNumber: "",
- phoneNumber: "",
- countryCode: "",
- },
- rateDefinition: {
- breakfastIncluded: false,
- cancellationRule: null,
- cancellationText: null,
- generalTerms: [],
- isMemberRate: false,
- mustBeGuaranteed: false,
- rateCode: "",
- title: null,
- },
- roomPoints: 0,
- roomPrice: {
- perNight: {
- requested: {
- price: 0,
- currency: CurrencyEnum.Unknown,
- },
- local: {
- price: 0,
- currency: CurrencyEnum.Unknown,
- },
- },
- perStay: {
- requested: {
- price: 0,
- currency: CurrencyEnum.Unknown,
- },
- local: {
- price: 0,
- currency: CurrencyEnum.Unknown,
- },
- },
- },
- vatPercentage: 0,
- vatAmount: 0,
- totalPriceExVat: 0,
- totalPrice: 0,
- createDateTime: new Date(),
- canChangeDate: false,
- multiRoom: false,
- mainRoom: false,
- roomName: "",
- roomNumber: null,
- isCancelled: false,
- childrenInRoom: [],
- childrenAsString: "",
- terms: null,
- packages: null,
- bedType: {
- description: "",
- roomTypeCode: "",
- },
- breakfast: null,
- linkedReservations: [],
- isCancelable: false,
- priceType: PriceTypeEnum.money,
- },
- linkedReservationRooms: [],
- actions: {
- addBookedRoom: (room) => {
- set({ bookedRoom: room })
- },
- updateBookedRoom: (room) => {
- set({ bookedRoom: room })
- },
- addLinkedReservationRoom: (room) => {
- set((state) => {
- // Check if room exists in bookedRooms
- const existsInBookedRoom =
- state.bookedRoom.confirmationNumber === room.confirmationNumber
-
- if (existsInBookedRoom) {
- return state
- }
-
- // Check if room with this ID already exists in linkedReservationRooms
- const existingIndex = state.linkedReservationRooms.findIndex(
- (r) => r.confirmationNumber === room.confirmationNumber
- )
- let newRooms = [...state.linkedReservationRooms]
-
- if (existingIndex >= 0) {
- // Update existing room
- newRooms[existingIndex] = room
- } else {
- // Add new room
- newRooms.push(room)
- }
-
- return {
- linkedReservationRooms: newRooms,
- }
- })
- },
- updateLinkedReservationRoom: (room) => {
- set((state) => {
- const existingIndex = state.linkedReservationRooms.findIndex(
- (r) => r.confirmationNumber === room.confirmationNumber
- )
- let newRooms = [...state.linkedReservationRooms]
- if (existingIndex >= 0) {
- newRooms[existingIndex] = room
- }
- return {
- linkedReservationRooms: newRooms,
- }
- })
- },
- },
- })
-)
diff --git a/apps/scandic-web/stores/my-stay/myStayTotalPrice.ts b/apps/scandic-web/stores/my-stay/myStayTotalPrice.ts
deleted file mode 100644
index 533018a1b..000000000
--- a/apps/scandic-web/stores/my-stay/myStayTotalPrice.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { create } from "zustand"
-
-import { CurrencyEnum } from "@/types/enums/currency"
-
-interface RoomPrice {
- id: string
- totalPrice: number
- currencyCode: CurrencyEnum
- isMainBooking?: boolean
- roomPoints: number
-}
-
-interface MyStayTotalPriceState {
- currencyCode: CurrencyEnum
- rooms: RoomPrice[]
- totalCheques: number
- totalPoints: number
- totalPrice: number | null
- totalVouchers: number
- actions: {
- // Add a single room price
- addRoomPrice: (room: RoomPrice) => void
- }
-}
-
-export const useMyStayTotalPriceStore = create(
- (set) => ({
- rooms: [],
- totalPrice: null,
- totalPoints: 0,
- totalCheques: 0,
- totalVouchers: 0,
- currencyCode: CurrencyEnum.Unknown,
- actions: {
- addRoomPrice: (room) => {
- set((state) => {
- // Check if room with this ID already exists
- const existingIndex = state.rooms.findIndex((r) => r.id === room.id)
- let newRooms = [...state.rooms]
-
- if (existingIndex >= 0) {
- // Update existing room
- newRooms[existingIndex] = room
- } else {
- // Add new room
- newRooms.push(room)
- }
-
- // Get currency from main booking or first room
- const mainRoom = newRooms.find((r) => r.isMainBooking) || newRooms[0]
- const currencyCode = mainRoom?.currencyCode ?? CurrencyEnum.Unknown
-
- // Calculate total (only same currency for now)
- const total = newRooms.reduce((sum, r) => {
- if (r.currencyCode === currencyCode) {
- return sum + r.totalPrice
- }
- return sum
- }, 0)
-
- const totalPoints = newRooms.reduce((sum, r) => {
- return sum + (r.roomPoints ?? 0)
- }, 0)
-
- return {
- rooms: newRooms,
- totalPrice: total,
- currencyCode,
- totalPoints,
- }
- })
- },
- },
- })
-)
diff --git a/apps/scandic-web/stores/sidepeek.ts b/apps/scandic-web/stores/sidepeek.ts
index 777e82544..85736ed48 100644
--- a/apps/scandic-web/stores/sidepeek.ts
+++ b/apps/scandic-web/stores/sidepeek.ts
@@ -3,14 +3,14 @@ import { create } from "zustand"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import type { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
-import type { User } from "@/types/user"
+import type { SafeUser } from "@/types/user"
interface SidePeekState {
activeSidePeek: SidePeekEnum | null
hotelId: string | null
roomTypeCode: string | null
showCTA: boolean
- user: User | null
+ user: SafeUser
confirmationNumber: string
openSidePeek: ({
key,
@@ -24,7 +24,7 @@ interface SidePeekState {
hotelId: string
roomTypeCode?: string
showCTA?: boolean
- user?: User
+ user?: SafeUser
confirmationNumber?: string
}) => void
closeSidePeek: () => void
diff --git a/apps/scandic-web/types/components/blocks/surprises.ts b/apps/scandic-web/types/components/blocks/surprises.ts
index 5bb9e2459..397cb5e51 100644
--- a/apps/scandic-web/types/components/blocks/surprises.ts
+++ b/apps/scandic-web/types/components/blocks/surprises.ts
@@ -17,7 +17,7 @@ export interface CardProps extends React.PropsWithChildren {
export interface InitialProps {
totalSurprises: number
- onOpen: VoidFunction
+ onOpen: () => void
}
export interface SlideProps {
@@ -25,5 +25,5 @@ export interface SlideProps {
}
export interface HeaderProps extends React.PropsWithChildren {
- onClose: VoidFunction
+ onClose: () => void
}
diff --git a/apps/scandic-web/types/components/datepicker.ts b/apps/scandic-web/types/components/datepicker.ts
index 60d5f03a7..f75770e07 100644
--- a/apps/scandic-web/types/components/datepicker.ts
+++ b/apps/scandic-web/types/components/datepicker.ts
@@ -1,17 +1,11 @@
-import type { Locale } from "date-fns"
import type { DateRange } from "react-day-picker"
-import type { Lang } from "@/constants/languages"
-
export interface DatePickerFormProps {
name?: string
}
-type LangWithoutEn = Lang.da | Lang.de | Lang.fi | Lang.no | Lang.sv
-
interface DatePickerProps {
close: () => void
- locales: Record
startMonth?: Date
hideHeader?: boolean
}
diff --git a/apps/scandic-web/types/components/header/headerLink.ts b/apps/scandic-web/types/components/header/headerLink.ts
index 0da0fe649..4370d048d 100644
--- a/apps/scandic-web/types/components/header/headerLink.ts
+++ b/apps/scandic-web/types/components/header/headerLink.ts
@@ -6,5 +6,5 @@ export interface HeaderLinkProps extends React.PropsWithChildren {
href: LinkProps["href"]
iconName: IconName | null
iconSize?: number
- onClick?: VoidFunction
+ onClick?: () => void
}
diff --git a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts
index 40cf94a7f..609fe53d2 100644
--- a/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts
+++ b/apps/scandic-web/types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts
@@ -12,7 +12,8 @@ export interface BookingConfirmationRoom extends Room {
bedType: Room["roomTypes"][number]
}
-export interface ConfirmationProps extends BookingConfirmation {
+export interface ConfirmationProps
+ extends Pick {
room: BookingConfirmationRoom
refId: string
}
diff --git a/apps/scandic-web/types/components/hotelReservation/myStay/cancelStay.ts b/apps/scandic-web/types/components/hotelReservation/myStay/cancelStay.ts
index 20faabb33..d65e7d00b 100644
--- a/apps/scandic-web/types/components/hotelReservation/myStay/cancelStay.ts
+++ b/apps/scandic-web/types/components/hotelReservation/myStay/cancelStay.ts
@@ -1,23 +1,22 @@
import { z } from "zod"
import type { Hotel } from "@/types/hotel"
-import type { Room } from "@/stores/my-stay/myStayRoomDetailsStore"
export const cancelStaySchema = z.object({
rooms: z.array(
z.object({
- checked: z.boolean().optional(),
+ checked: z.boolean(),
confirmationNumber: z.string(),
})
),
})
export interface CancelStayProps {
- hotel: Hotel
handleCloseModal: () => void
+ hotel: Hotel
}
-export type CancelStayFormValues = z.infer
+export type CancelStayFormValues = z.output
export interface RoomDetails {
id: string
@@ -43,17 +42,3 @@ export interface StayDetails {
adultsText: string
childrenText: string
}
-
-export interface CancelStayConfirmationProps {
- hotel: Hotel
- stayDetails: StayDetails
-}
-
-export interface FinalConfirmationProps {
- stayDetails: StayDetails
-}
-
-export interface PriceContainerProps {
- roomDetails: Room
- stayDetails: StayDetails
-}
diff --git a/apps/scandic-web/types/components/hotelReservation/myStay/modifyDate.ts b/apps/scandic-web/types/components/hotelReservation/myStay/changeDates.ts
similarity index 62%
rename from apps/scandic-web/types/components/hotelReservation/myStay/modifyDate.ts
rename to apps/scandic-web/types/components/hotelReservation/myStay/changeDates.ts
index 92490382e..ad4c3df84 100644
--- a/apps/scandic-web/types/components/hotelReservation/myStay/modifyDate.ts
+++ b/apps/scandic-web/types/components/hotelReservation/myStay/changeDates.ts
@@ -2,12 +2,12 @@ import { z } from "zod"
import { Lang } from "@/constants/languages"
-export const modifyDateSchema = z.object({
+export const changeDatesSchema = z.object({
checkInDate: z.string(),
checkOutDate: z.string(),
})
-export type ModifyDateSchema = z.infer
+export type ChangeDatesSchema = z.output
export interface QueryInput {
hotelId: string
@@ -33,6 +33,12 @@ export const DEFAULT_QUERY_INPUT: QueryInput = {
lang: Lang.en,
}
-export interface ModifyStayProps {
- isLoggedIn: boolean
+export interface ChangeDatesStepsProps {
+ closeModal: () => void
+}
+
+export interface ChangeDatesFormProps {
+ checkAvailability: (fromDate: string, toDate: string) => Promise
+ closeModal: () => void
+ noAvailability: boolean
}
diff --git a/apps/scandic-web/types/components/hotelReservation/toggleSidePeekProps.ts b/apps/scandic-web/types/components/hotelReservation/toggleSidePeekProps.ts
index 42734b8bc..3e63b9e0e 100644
--- a/apps/scandic-web/types/components/hotelReservation/toggleSidePeekProps.ts
+++ b/apps/scandic-web/types/components/hotelReservation/toggleSidePeekProps.ts
@@ -1,10 +1,10 @@
-import type { User } from "@/types/user"
+import type { SafeUser } from "@/types/user"
export type ToggleSidePeekProps = {
hotelId: string
roomTypeCode?: string
intent?: "text" | "textInverted"
title?: string
- user?: User
+ user?: SafeUser
confirmationNumber?: string
}
diff --git a/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts b/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts
index d47879ad0..6e372a5bd 100644
--- a/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts
+++ b/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts
@@ -70,6 +70,6 @@ export interface StepsProps {
export interface ActionButtonsProps {
isPriceDetailsOpen: boolean
- togglePriceDetails: VoidFunction
+ togglePriceDetails: () => void
isSubmitting: boolean
}
diff --git a/apps/scandic-web/types/components/sidePeeks/bookedRoomSidePeek.ts b/apps/scandic-web/types/components/sidePeeks/bookedRoomSidePeek.ts
index dbdefadce..69e0d785b 100644
--- a/apps/scandic-web/types/components/sidePeeks/bookedRoomSidePeek.ts
+++ b/apps/scandic-web/types/components/sidePeeks/bookedRoomSidePeek.ts
@@ -1,12 +1,12 @@
import type { Room } from "@/types/hotel"
-import type { User } from "@/types/user"
+import type { SafeUser } from "@/types/user"
import type { SidePeekEnum } from "../hotelReservation/sidePeek"
export type BookedRoomSidePeekProps = {
room: Room
activeSidePeek: SidePeekEnum | null
close: () => void
- user: User | null
+ user: SafeUser
confirmationNumber: string
}
diff --git a/apps/scandic-web/types/contexts/my-stay.ts b/apps/scandic-web/types/contexts/my-stay.ts
new file mode 100644
index 000000000..18ed9499c
--- /dev/null
+++ b/apps/scandic-web/types/contexts/my-stay.ts
@@ -0,0 +1,3 @@
+import type { createMyStayStore } from "@/stores/my-stay"
+
+export type MyStayStore = ReturnType
diff --git a/apps/scandic-web/types/hotel.ts b/apps/scandic-web/types/hotel.ts
index 7e2d0cd3c..b499f6efa 100644
--- a/apps/scandic-web/types/hotel.ts
+++ b/apps/scandic-web/types/hotel.ts
@@ -62,6 +62,8 @@ export type RestaurantOpeningHoursDay = z.output<
typeof openingHoursDetailsSchema
>
export type Room = ReturnType
+export type RoomCategory = Room
+export type RoomCategories = RoomCategory[]
export type PoiMapMarkersProps = {
activePoi?: string | null
diff --git a/apps/scandic-web/types/stores/my-stay.ts b/apps/scandic-web/types/stores/my-stay.ts
new file mode 100644
index 000000000..a9492ec2b
--- /dev/null
+++ b/apps/scandic-web/types/stores/my-stay.ts
@@ -0,0 +1,89 @@
+import type { IntlShape } from "react-intl"
+
+import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
+import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
+import type { RoomPrice } from "@/types/components/hotelReservation/enterDetails/details"
+import type { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
+import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
+import type { Packages } from "@/types/components/myPages/myStay/ancillaries"
+import type { Hotel, Room as HotelRoom, RoomCategories } from "@/types/hotel"
+import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
+import type { CreditCard } from "@/types/user"
+
+export type Room = Pick<
+ BookingConfirmation["booking"],
+ | "adults"
+ | "bookingCode"
+ | "canChangeDate"
+ | "cancellationNumber"
+ | "checkInDate"
+ | "checkOutDate"
+ | "cheques"
+ | "childrenAges"
+ | "confirmationNumber"
+ | "createDateTime"
+ | "currencyCode"
+ | "guaranteeInfo"
+ | "guest"
+ | "hotelId"
+ | "isCancelable"
+ | "linkedReservations"
+ | "multiRoom"
+ | "rateDefinition"
+ | "reservationStatus"
+ | "roomPoints"
+ | "roomTypeCode"
+ | "totalPoints"
+ | "totalPrice"
+ | "totalPriceExVat"
+ | "vatAmount"
+ | "vatPercentage"
+ | "vouchers"
+> & {
+ bedType: BedTypeSchema
+ breakfast: Omit | null
+ childrenAsString: string
+ childrenInRoom: Child[]
+ isCancelled: boolean
+ mainRoom: boolean
+ packages: Packages | null
+ priceType: PriceTypeEnum
+ rate: string
+ room: (HotelRoom & { bedType: HotelRoom["roomTypes"][number] }) | null
+ roomName: string
+ roomNumber: number
+ roomPrice: RoomPrice
+ terms: string | null
+}
+
+export type BookingRoom = BookingConfirmation["booking"]
+
+interface Actions {
+ closeManageStay: () => void
+ openManageStay: () => void
+}
+
+export interface MyStayState {
+ actions: Actions
+ allRoomsAreCancelled: boolean
+ bookedRoom: Room
+ breakfastPackages: Packages | null
+ hotel: Hotel
+ mainRoom: Room
+ manageStay: boolean
+ refId: string
+ rooms: Room[]
+ savedCreditCards: CreditCard[] | null
+ totalPoints: number
+ totalPrice: string
+}
+
+export interface InitialState
+ extends Pick<
+ MyStayState,
+ "breakfastPackages" | "hotel" | "refId" | "savedCreditCards"
+ > {
+ intl: IntlShape
+ roomCategories: RoomCategories
+ rooms: BookingRoom[]
+}
diff --git a/apps/scandic-web/types/stores/rates.ts b/apps/scandic-web/types/stores/rates.ts
index c7baeac12..182ef4683 100644
--- a/apps/scandic-web/types/stores/rates.ts
+++ b/apps/scandic-web/types/stores/rates.ts
@@ -22,10 +22,10 @@ interface Actions {
appendRegularRates: (
roomConfigurations: RoomConfiguration[] | undefined
) => void
- closeSection: VoidFunction
- modifyRate: VoidFunction
+ closeSection: () => void
+ modifyRate: () => void
removeSelectedPackage: (code: PackageEnum) => void
- removeSelectedPackages: VoidFunction
+ removeSelectedPackages: () => void
selectFilter: (filter: BookingCodeFilterEnum) => void
selectPackages: (codes: PackageEnum[]) => void
selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void
diff --git a/apps/scandic-web/types/trpc/routers/booking/confirmation.ts b/apps/scandic-web/types/trpc/routers/booking/confirmation.ts
index 1da9d2387..4b6e6f5eb 100644
--- a/apps/scandic-web/types/trpc/routers/booking/confirmation.ts
+++ b/apps/scandic-web/types/trpc/routers/booking/confirmation.ts
@@ -1,6 +1,6 @@
import type { z } from "zod"
-import type { Hotel, Room } from "@/types/hotel"
+import type { HotelData, Room } from "@/types/hotel"
import type {
bookingConfirmationSchema,
packageSchema,
@@ -11,9 +11,8 @@ export interface BookingConfirmationSchema
export interface PackageSchema extends z.output {}
-export interface BookingConfirmation {
+export interface BookingConfirmation extends HotelData {
booking: BookingConfirmationSchema
- hotel: Hotel
room:
| (Room & {
bedType: Room["roomTypes"][number]
diff --git a/packages/design-system/lib/components/RateCard/Modal/modal.ts b/packages/design-system/lib/components/RateCard/Modal/modal.ts
index 1a0fcca6b..5fdb306d4 100644
--- a/packages/design-system/lib/components/RateCard/Modal/modal.ts
+++ b/packages/design-system/lib/components/RateCard/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/packages/design-system/lib/fonts.css b/packages/design-system/lib/fonts.css
index dcfcdf893..036360d2d 100644
--- a/packages/design-system/lib/fonts.css
+++ b/packages/design-system/lib/fonts.css
@@ -269,7 +269,7 @@
font-style: normal;
font-weight: 400;
font-display: block;
- src: url(/_static/fonts/material-symbols/rounded-112272ae.woff2)
+ src: url(/_static/fonts/material-symbols/rounded-a03ed056.woff2)
format('woff2');
}
diff --git a/packages/design-system/public/_static/fonts/material-symbols/rounded-112272ae.woff2 b/packages/design-system/public/_static/fonts/material-symbols/rounded-112272ae.woff2
deleted file mode 100644
index 7a133cedc..000000000
Binary files a/packages/design-system/public/_static/fonts/material-symbols/rounded-112272ae.woff2 and /dev/null differ
diff --git a/packages/design-system/public/_static/fonts/material-symbols/rounded-a03ed056.woff2 b/packages/design-system/public/_static/fonts/material-symbols/rounded-a03ed056.woff2
new file mode 100644
index 000000000..84209fed1
Binary files /dev/null and b/packages/design-system/public/_static/fonts/material-symbols/rounded-a03ed056.woff2 differ
diff --git a/scripts/material-symbols-update.mjs b/scripts/material-symbols-update.mjs
index 1bfb80633..22839bf0e 100644
--- a/scripts/material-symbols-update.mjs
+++ b/scripts/material-symbols-update.mjs
@@ -90,6 +90,7 @@ const icons = [
'download',
'dresser',
'edit',
+ 'edit_calendar',
'edit_square',
'electric_bike',
'electric_car',
@@ -163,6 +164,7 @@ const icons = [
'pets',
'phone',
'pool',
+ 'print',
'radio',
'recommend',
'redeem',
@@ -191,6 +193,7 @@ const icons = [
'star',
'straighten',
'styler',
+ 'support_agent',
'swipe',
'sync_saved_locally',
'table_bar',