"use client" import { usePathname, useSearchParams } from "next/navigation" import { useCallback, useEffect, useRef, useState } from "react" import { useIntl } from "react-intl" import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert" import { selectRate } from "@scandic-hotels/common/constants/routes/hotelReservation" import { useSessionId } from "@scandic-hotels/common/hooks/useSessionId" import useStickyPosition from "@scandic-hotels/common/hooks/useStickyPosition" import { Alert } from "@scandic-hotels/design-system/Alert" import { trackNoAvailability } from "@scandic-hotels/tracking/NoAvailabilityTracking" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode" import useLang from "../../../../hooks/useLang" import { useEnterDetailsStore } from "../../../../stores/enter-details" import { mapPackageToLabel } from "../../../../utils/getRoomFeatureDescription" import styles from "./bookingAlert.module.css" function useBookingErrorAlert() { const updateSearchParams = useEnterDetailsStore( (state) => state.actions.updateSeachParamString ) const intl = useIntl() const lang = useLang() const searchParams = useSearchParams() const pathname = usePathname() const errorCode = searchParams.get("errorCode") const errorMessage = getErrorMessage(errorCode) const severityLevel = errorCode === BookingErrorCodeEnum.TransactionCancelled ? AlertTypeEnum.Warning : AlertTypeEnum.Alarm const [showAlert, setShowAlert] = useState(!!errorCode) const selectRateReturnUrl = getSelectRateReturnUrl() useEffect(() => { setShowAlert(!!errorCode) }, [errorCode]) function getErrorMessage(errorCode: string | null) { switch (errorCode) { case BookingErrorCodeEnum.TransactionCancelled: return intl.formatMessage({ id: "enterDetails.bookingAlert.transactionCancelled", defaultMessage: "You have now cancelled your payment.", }) case BookingErrorCodeEnum.AvailabilityError: case BookingErrorCodeEnum.NoAvailabilityForRateAndRoomType: return intl.formatMessage({ id: "error.availabilityErrorMessage", defaultMessage: "Unfortunately, one of the rooms you selected is sold out. Please choose another room to proceed.", }) default: return intl.formatMessage({ id: "enterDetails.bookingAlert.genericError", defaultMessage: "We had an issue processing your booking. Please try again. No charges have been made.", }) } } function discardAlert() { setShowAlert(false) const queryParams = new URLSearchParams(searchParams.toString()) queryParams.delete("errorCode") updateSearchParams(queryParams.toString()) window.history.replaceState({}, "", `${pathname}?${queryParams.toString()}`) } 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 BookingAlertProps { isVisible?: boolean } export default function BookingAlert({ isVisible = false }: BookingAlertProps) { const intl = useIntl() const { showAlert, errorCode, errorMessage, severityLevel, discardAlert, setShowAlert, selectRateReturnUrl, } = useBookingErrorAlert() const trackNoAvailabilityError = useNoAvailabilityTracking() const isAvailabilityError = errorCode === BookingErrorCodeEnum.AvailabilityError || errorCode === BookingErrorCodeEnum.NoAvailabilityForRateAndRoomType const ref = useRef(null) const { getTopOffset } = useStickyPosition() useEffect(() => { if (isVisible) { setShowAlert(true) } }, [isVisible, setShowAlert]) useEffect(() => { const el = ref.current if (showAlert && el) { window.scrollTo({ top: el.offsetTop - getTopOffset(), behavior: "smooth", }) } }, [showAlert, getTopOffset]) useEffect(() => { if (showAlert && isAvailabilityError) { trackNoAvailabilityError() } }, [showAlert, isAvailabilityError, trackNoAvailabilityError]) if (!showAlert) return null return (
) } function useNoAvailabilityTracking() { const { fromDate, toDate, hotelId, bookingCode, searchType, rooms } = useEnterDetailsStore((state) => state.booking) const hotelName = useEnterDetailsStore((state) => state.hotelName) const lang = useLang() const sessionId = useSessionId() const specialRoomType = rooms ?.map((room) => { const packages = room.packages ?.map((pkg) => mapPackageToLabel(pkg)) .join(",") return packages ?? "" }) .join("|") const track = useCallback( () => trackNoAvailability({ specialRoomType, lang, searchTerm: hotelName, fromDate, toDate, hotelId, noOfRooms: rooms.length, searchType: "hotel", rewardNight: searchType === SEARCH_TYPE_REDEMPTION ? "yes" : "no", bookingCode: bookingCode ?? "n/a", pageId: "details", pageName: "hotelreservation|details", pageType: "bookingenterdetailspage", siteSections: "hotelreservation|details", sessionId, domain: typeof window !== "undefined" ? window.location.host : "", }), [ specialRoomType, lang, hotelName, fromDate, toDate, hotelId, rooms.length, searchType, bookingCode, sessionId, ] ) return track }