diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx index a649e3f9f..ca2f08674 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx @@ -1,6 +1,7 @@ import { notFound, redirect } from "next/navigation" import { Suspense } from "react" +import { BookingErrorCodeEnum } from "@/constants/booking" import { selectRate } from "@/constants/routes/hotelReservation" import { getBreakfastPackages, @@ -16,8 +17,6 @@ import RoomOne from "@/components/HotelReservation/EnterDetails/Room/One" import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop" import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile" import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDetails/Tracking" -import Alert from "@/components/TempDesignSystem/Alert" -import { getIntl } from "@/i18n" import RoomProvider from "@/providers/Details/RoomProvider" import EnterDetailsProvider from "@/providers/EnterDetailsProvider" import { convertSearchParamsToObj } from "@/utils/url" @@ -25,7 +24,6 @@ import { convertSearchParamsToObj } from "@/utils/url" import styles from "./page.module.css" import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" -import { AlertTypeEnum } from "@/types/enums/alert" import type { LangParams, PageArgs } from "@/types/params" import type { Room } from "@/types/providers/details/room" @@ -71,6 +69,7 @@ export default async function DetailsPage({ // (possibly also add an error case to url?) // ------------------------------------------------------- // redirect back to select-rate if availability call fails + selectRoomParams.set("errorCode", BookingErrorCodeEnum.AvailabilityError) redirect(`${selectRate(lang)}?${selectRoomParams.toString()}`) } @@ -94,12 +93,9 @@ export default async function DetailsPage({ hotel.merchantInformationData.alternatePaymentOptions = [] } - const intl = await getIntl() - const firstRoom = rooms[0] const multirooms = rooms.slice(1) - const isRoomNotAvailable = rooms.some((room) => !room.isAvailable) return (
- {isRoomNotAvailable && ( - - )}
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/paymentAlert.module.css b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/BookingAlert/bookingAlert.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/paymentAlert.module.css rename to apps/scandic-web/components/HotelReservation/EnterDetails/Payment/BookingAlert/bookingAlert.module.css diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/BookingAlert/index.tsx similarity index 62% rename from apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/index.tsx rename to apps/scandic-web/components/HotelReservation/EnterDetails/Payment/BookingAlert/index.tsx index 4f7c395b5..ba0bb3780 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentAlert/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/BookingAlert/index.tsx @@ -5,12 +5,14 @@ import { useEffect, useRef, useState } from "react" import { useIntl } from "react-intl" import { BookingErrorCodeEnum } from "@/constants/booking" +import { selectRate } from "@/constants/routes/hotelReservation" import { useEnterDetailsStore } from "@/stores/enter-details" import Alert from "@/components/TempDesignSystem/Alert" +import useLang from "@/hooks/useLang" import useStickyPosition from "@/hooks/useStickyPosition" -import styles from "./paymentAlert.module.css" +import styles from "./bookingAlert.module.css" import { AlertTypeEnum } from "@/types/enums/alert" @@ -19,6 +21,7 @@ function useBookingErrorAlert() { (state) => state.actions.updateSeachParamString ) const intl = useIntl() + const lang = useLang() const searchParams = useSearchParams() const pathname = usePathname() @@ -31,12 +34,19 @@ function useBookingErrorAlert() { const [showAlert, setShowAlert] = useState(!!errorCode) + const selectRateReturnUrl = getSelectRateReturnUrl() + function getErrorMessage(errorCode: string | null) { switch (errorCode) { case BookingErrorCodeEnum.TransactionCancelled: return intl.formatMessage({ defaultMessage: "You have now cancelled your payment.", }) + case BookingErrorCodeEnum.AvailabilityError: + return intl.formatMessage({ + defaultMessage: + "Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.", + }) default: return intl.formatMessage({ defaultMessage: @@ -54,16 +64,39 @@ function useBookingErrorAlert() { window.history.replaceState({}, "", `${pathname}?${queryParams.toString()}`) } - return { showAlert, errorMessage, severityLevel, discardAlert, setShowAlert } + function getSelectRateReturnUrl() { + const queryParams = new URLSearchParams(searchParams.toString()) + queryParams.delete("errorCode") + return `${selectRate(lang)}?${queryParams.toString()}` + } + + return { + showAlert, + errorCode, + errorMessage, + severityLevel, + discardAlert, + setShowAlert, + selectRateReturnUrl, + } } -interface PaymentAlertProps { +interface BookingAlertProps { isVisible?: boolean } -export default function PaymentAlert({ isVisible = false }: PaymentAlertProps) { - const { showAlert, errorMessage, severityLevel, discardAlert, setShowAlert } = - useBookingErrorAlert() +export default function BookingAlert({ isVisible = false }: BookingAlertProps) { + const intl = useIntl() + + const { + showAlert, + errorCode, + errorMessage, + severityLevel, + discardAlert, + setShowAlert, + selectRateReturnUrl, + } = useBookingErrorAlert() const ref = useRef(null) const { getTopOffset } = useStickyPosition() @@ -87,6 +120,9 @@ export default function PaymentAlert({ isVisible = false }: PaymentAlertProps) { if (!showAlert) return null + const isAvailabilityError = + errorCode === BookingErrorCodeEnum.AvailabilityError + return (
) diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx index 3d8e0e3fd..8b8d3dea7 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx @@ -1,7 +1,7 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" -import { useRouter, useSearchParams } from "next/navigation" +import { usePathname, useRouter, useSearchParams } from "next/navigation" import { useCallback, useEffect, useState } from "react" import { Label } from "react-aria-components" import { FormProvider, useForm } from "react-hook-form" @@ -11,7 +11,6 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { BOOKING_CONFIRMATION_NUMBER, - BookingErrorCodeEnum, BookingStatusEnum, PAYMENT_METHOD_TITLES, PaymentMethodEnum, @@ -42,10 +41,10 @@ import { bedTypeMap } from "../../utils" import ConfirmBooking, { ConfirmBookingRedemption } from "../Confirm" import PriceChangeDialog from "../PriceChangeDialog" import { writeGlaToSessionStorage } from "./PaymentCallback/helpers" +import BookingAlert from "./BookingAlert" import GuaranteeDetails from "./GuaranteeDetails" import { hasFlexibleRate, hasPrepaidRate, isPaymentMethodEnum } from "./helpers" import MixedRatePaymentBreakdown from "./MixedRatePaymentBreakdown" -import PaymentAlert from "./PaymentAlert" import PaymentOptionsGroup from "./PaymentOptionsGroup" import { type PaymentFormData, paymentSchema } from "./schema" import TermsAndConditions from "./TermsAndConditions" @@ -71,10 +70,11 @@ export default function PaymentClient({ const router = useRouter() const lang = useLang() const intl = useIntl() + const pathname = usePathname() const searchParams = useSearchParams() const { getTopOffset } = useStickyPosition({}) - const [showPaymentAlert, setShowPaymentAlert] = useState(false) + const [showBookingAlert, setShowBookingAlert] = useState(false) const { booking, rooms, totalPrice } = useEnterDetailsStore((state) => ({ booking: state.booking, @@ -135,11 +135,14 @@ export default function PaymentClient({ onSuccess: (result) => { if (result) { if ("error" in result) { - if (result.cause === BookingErrorCodeEnum.AvailabilityError) { - window.location.reload() // reload to refetch room data because we dont know which room is unavailable - } else { - handlePaymentError(result.cause) - } + const queryParams = new URLSearchParams(searchParams.toString()) + queryParams.set("errorCode", result.cause) + window.history.replaceState( + {}, + "", + `${pathname}?${queryParams.toString()}` + ) + handlePaymentError(result.cause) return } @@ -196,7 +199,7 @@ export default function PaymentClient({ const handlePaymentError = useCallback( (errorMessage: string) => { - setShowPaymentAlert(true) + setShowBookingAlert(true) const currentPaymentMethod = methods.getValues("paymentMethod") const smsEnable = methods.getValues("smsConfirmation") @@ -480,7 +483,7 @@ export default function PaymentClient({ ? confirm : payment} - +
{ + if (!hasAvailabilityError) { + return + } + + toast.error(errorMessage) + + const newParams = new URLSearchParams(searchParams.toString()) + newParams.delete("errorCode") + window.history.replaceState({}, "", `${pathname}?${newParams.toString()}`) + }, [errorMessage, hasAvailabilityError, pathname, searchParams]) + + return null +} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx index ac63d5361..83250b678 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx @@ -12,6 +12,7 @@ import { setLang } from "@/i18n/serverContext" import { getHotelSearchDetails } from "@/utils/hotelSearchDetails" import { convertSearchParamsToObj } from "@/utils/url" +import AvailabilityError from "./AvailabilityError" import { getValidDates } from "./getValidDates" import { getTracking } from "./tracking" @@ -90,6 +91,8 @@ export default async function SelectRatePage({ hotelInfo={hotelsTrackingData} /> + + ) }