"use client" import { zodResolver } from "@hookform/resolvers/zod" import { usePathname, useRouter, useSearchParams } from "next/navigation" import { useEffect, useState } from "react" 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 { guaranteeCallback } from "@/constants/routes/hotelReservation" import { env } from "@/env/client" import { trpc } from "@/lib/trpc/client" import { AncillaryStepEnum, useAddAncillaryStore, } from "@/stores/my-stay/add-ancillary-flow" import Image from "@/components/Image" import LoadingSpinner from "@/components/LoadingSpinner" import Modal from "@/components/Modal" import Divider from "@/components/TempDesignSystem/Divider" import { toast } from "@/components/TempDesignSystem/Toasts" import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking" import useLang from "@/hooks/useLang" import { formatPrice } from "@/utils/numberFormatting" import { clearAncillarySessionData, generateDeliveryOptions, getAncillarySessionData, setAncillarySessionData, } from "../../utils" import { type AncillaryFormData, ancillaryFormSchema } from "../schema" import ActionButtons from "./ActionButtons" import PriceDetails from "./PriceDetails" import Steps from "./Steps" import styles from "./addAncillaryFlowModal.module.css" import type { AddAncillaryFlowModalProps } from "@/types/components/myPages/myStay/ancillaries" import { BreakfastPackageEnum } from "@/types/enums/breakfast" export default function AddAncillaryFlowModal({ booking, user, savedCreditCards, refId, }: AddAncillaryFlowModalProps) { const { currentStep, selectedAncillary, closeModal } = useAddAncillaryStore( (state) => ({ currentStep: state.currentStep, selectedAncillary: state.selectedAncillary, closeModal: state.closeModal, }) ) const intl = useIntl() const lang = useLang() const router = useRouter() const searchParams = useSearchParams() const pathname = usePathname() const [isPriceDetailsOpen, setIsPriceDetailsOpen] = useState(false) const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang)}` const deliveryTimeOptions = generateDeliveryOptions() const defaultDeliveryTime = deliveryTimeOptions[0].value const formMethods = useForm({ defaultValues: { quantityWithPoints: null, quantityWithCard: user ? null : 1, deliveryTime: defaultDeliveryTime, optionalText: "", termsAndConditions: false, paymentMethod: booking.guaranteeInfo ? PaymentMethodEnum.card : savedCreditCards?.length ? savedCreditCards[0].id : PaymentMethodEnum.card, }, mode: "onChange", reValidateMode: "onChange", resolver: zodResolver(ancillaryFormSchema), }) const ancillaryErrorMessage = intl.formatMessage( { id: "Something went wrong. {ancillary} could not be added to your booking!", }, { ancillary: selectedAncillary?.title } ) function togglePriceDetails() { setIsPriceDetailsOpen((isOpen) => !isOpen) } const utils = trpc.useUtils() const addAncillary = trpc.booking.packages.useMutation({ onSuccess: (data, variables) => { if (data) { clearAncillarySessionData() closeModal() utils.booking.confirmation.invalidate({ confirmationNumber: variables.confirmationNumber, }) toast.success( intl.formatMessage( { id: "{ancillary} added to your booking!" }, { ancillary: selectedAncillary?.title } ) ) router.refresh() } else { toast.error(ancillaryErrorMessage) } }, onError: () => { toast.error(ancillaryErrorMessage) }, }) const { guaranteeBooking, isLoading } = useGuaranteeBooking({ confirmationNumber: booking.confirmationNumber, }) const onSubmit = (data: AncillaryFormData) => { if (!data.termsAndConditions) { formMethods.setError("termsAndConditions", { message: "You must accept the terms", }) return } setAncillarySessionData({ formData: data, selectedAncillary, }) if (booking.guaranteeInfo) { const packages = [] if (selectedAncillary?.id && data.quantityWithCard) { packages.push({ code: selectedAncillary.id, quantity: data.quantityWithCard, comment: data.optionalText || undefined, }) } if (selectedAncillary?.loyaltyCode && data.quantityWithPoints) { packages.push({ code: selectedAncillary.loyaltyCode, quantity: data.quantityWithPoints, comment: data.optionalText || undefined, }) } addAncillary.mutate({ confirmationNumber: booking.confirmationNumber, ancillaryComment: data.optionalText, ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime ? data.deliveryTime : undefined, packages, language: lang, }) } else { const savedCreditCard = savedCreditCards?.find( (card) => card.id === data.paymentMethod ) if (booking.confirmationNumber) { const card = savedCreditCard ? { alias: savedCreditCard.alias, expiryDate: savedCreditCard.expirationDate, cardType: savedCreditCard.cardType, } : undefined guaranteeBooking.mutate({ confirmationNumber: booking.confirmationNumber, language: lang, ...(card && { card }), success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}&ancillary=1`, error: `${guaranteeRedirectUrl}?status=error&RefId=${encodeURIComponent(refId)}&ancillary=1`, cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}&ancillary=1`, }) } else { toast.error( intl.formatMessage({ id: "Something went wrong!", }) ) } } } useEffect(() => { const errorCode = searchParams.get("errorCode") const ancillary = searchParams.get("ancillary") if ((errorCode && ancillary) || errorCode === "AncillaryFailed") { const queryParams = new URLSearchParams(searchParams.toString()) if (ancillary) { queryParams.delete("ancillary") } queryParams.delete("errorCode") const savedData = getAncillarySessionData() if (savedData?.formData) { formMethods.reset(savedData.formData) } router.replace(`${pathname}?${queryParams.toString()}`) } }, [searchParams, pathname, formMethods, router]) if (isLoading) { return (
) } const modalTitle = currentStep === AncillaryStepEnum.selectAncillary ? intl.formatMessage({ id: "Upgrade your stay" }) : selectedAncillary?.title return (
{selectedAncillary && ( <>
{selectedAncillary.title}
{currentStep !== AncillaryStepEnum.confirmation && (

{formatPrice( intl, selectedAncillary.price.totalPrice, selectedAncillary.price.currency )}

{selectedAncillary.points && (

{intl.formatMessage( { id: "{value} points" }, { value: selectedAncillary.points, } )}

)}
{selectedAncillary.description && (

)}
)} )}
{/* TODO: Remove the berakfast check when add breakfast is implemented */} {currentStep === AncillaryStepEnum.selectAncillary || selectedAncillary?.id === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST ? null : (
)}
) }