(null)
const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang, isWebview(pathname))}`
const deliveryTimeOptions = generateDeliveryOptions()
@@ -94,7 +101,7 @@ export default function AddAncillaryFlowModal({
quantityWithPoints: null,
quantityWithCard:
!user || hasInsufficientPoints || isBreakfast ? 1 : null,
- deliveryTime: defaultDeliveryTime,
+ deliveryTime: booking.ancillary?.deliveryTime ?? defaultDeliveryTime,
optionalText: "",
termsAndConditions: false,
paymentMethod: booking.guaranteeInfo
@@ -127,16 +134,6 @@ export default function AddAncillaryFlowModal({
const { guaranteeBooking, isLoading, handleGuaranteeError } =
useGuaranteeBooking(booking.refId, true, booking.hotelId)
- function validateTermsAndConditions(data: AncillaryFormData): boolean {
- if (!data.termsAndConditions) {
- formMethods.setError("termsAndConditions", {
- message: "You must accept the terms",
- })
- return false
- }
- return true
- }
-
function handleAncillarySubmission(
data: AncillaryFormData,
packages: {
@@ -206,8 +203,10 @@ export default function AddAncillaryFlowModal({
)
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- function handleGuaranteePayment(data: AncillaryFormData, packages: any) {
+ function handleGuaranteePayment(
+ data: AncillaryFormData,
+ packages: AncillaryItem[]
+ ) {
const savedCreditCard = savedCreditCards?.find(
(card) => card.id === data.paymentMethod
)
@@ -239,34 +238,7 @@ export default function AddAncillaryFlowModal({
}
}
- function buildBreakfastPackages(
- data: AncillaryFormData,
- breakfastData: BreakfastData
- ) {
- const packages = [
- {
- code: BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
- quantity: breakfastData.nrOfAdults,
- comment: data.optionalText || undefined,
- },
- {
- code: BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
- quantity: breakfastData.nrOfPayingChildren,
- comment: data.optionalText || undefined,
- },
- {
- code: BreakfastPackageEnum.FREE_CHILD_BREAKFAST,
- quantity: breakfastData.nrOfFreeChildren,
- comment: data.optionalText || undefined,
- },
- ]
-
- return packages.filter((pkg) => pkg.quantity > 0)
- }
-
const onSubmit = (data: AncillaryFormData) => {
- if (!validateTermsAndConditions(data)) return
-
const packagesToAdd = !isBreakfast
? buildAncillaryPackages(data, selectedAncillary)
: breakfastData
@@ -300,14 +272,9 @@ export default function AddAncillaryFlowModal({
}
useEffect(() => {
- const errorCode = searchParams.get("errorCode")
- const ancillary = searchParams.get("ancillary")
- if ((errorCode && ancillary) || errorCode === "AncillaryFailed") {
+ if (isAncillaryError(searchParams)) {
+ const errorCode = searchParams.get("errorCode")
const queryParams = new URLSearchParams(searchParams.toString())
- if (ancillary) {
- queryParams.delete("ancillary")
- }
- queryParams.delete("errorCode")
const savedData = getAncillarySessionData()
if (savedData?.formData) {
const updatedFormData = {
@@ -318,9 +285,13 @@ export default function AddAncillaryFlowModal({
}
formMethods.reset(updatedFormData)
}
+
+ setErrorMessage(getErrorMessage(intl, errorCode))
+ queryParams.delete("ancillary")
+ queryParams.delete("errorCode")
router.replace(`${pathname}?${queryParams.toString()}`)
}
- }, [searchParams, pathname, formMethods, router, booking.guaranteeInfo])
+ }, [searchParams, pathname, formMethods, router, booking.guaranteeInfo, intl])
useEffect(() => {
setBreakfastData(
@@ -424,7 +395,11 @@ export default function AddAncillaryFlowModal({
)}
>
)}
-
+
{currentStep === AncillaryStepEnum.selectAncillary ? null : (
)
}
-
-/**
- * This function calculates some breakfast data in the store.
- * It is used in various places in the add flow, but only needs
- * to be calculated once.
- */
-function calculateBreakfastData(
- isBreakfast: boolean,
- packages: Packages | null,
- nrOfAdults: number,
- childrenAges: number[],
- nrOfNights: number
-): BreakfastData | null {
- if (!isBreakfast) {
- return null
- }
-
- const { nrOfPayingChildren, nrOfFreeChildren } = childrenAges.reduce(
- (total, childAge) => {
- if (childAge >= 4) {
- total.nrOfPayingChildren = total.nrOfPayingChildren + 1
- } else {
- total.nrOfFreeChildren = total.nrOfFreeChildren + 1
- }
- return total
- },
- { nrOfPayingChildren: 0, nrOfFreeChildren: 0 }
- )
-
- const adultPackage = packages?.find(
- (p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
- )
- const childPackage = packages?.find(
- (p) => p.code === BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST
- )
- const priceAdult = adultPackage?.localPrice.price
- const priceChild = childPackage?.localPrice.price
- const currency =
- adultPackage?.localPrice.currency ?? childPackage?.localPrice.currency
-
- if (
- typeof priceAdult !== "number" ||
- typeof priceChild !== "number" ||
- typeof currency !== "string"
- ) {
- return null
- } else {
- return {
- nrOfAdults,
- nrOfPayingChildren,
- nrOfFreeChildren,
- nrOfNights,
- priceAdult,
- priceChild,
- currency,
- }
- }
-}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/utils.ts b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/utils.ts
new file mode 100644
index 000000000..b9df784a3
--- /dev/null
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/AddAncillaryFlowModal/utils.ts
@@ -0,0 +1,136 @@
+import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
+import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode"
+import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
+
+import type { Packages } from "@scandic-hotels/trpc/types/packages"
+import type { IntlShape } from "react-intl"
+
+import type { AncillaryErrorMessage } from "@/types/components/myPages/myStay/ancillaries"
+import type { BreakfastData } from "@/stores/my-stay/add-ancillary-flow"
+import type { AncillaryFormData } from "../schema"
+
+/**
+ * This function calculates some breakfast data in the store.
+ * It is used in various places in the add flow, but only needs
+ * to be calculated once.
+ */
+export function calculateBreakfastData(
+ isBreakfast: boolean,
+ packages: Packages | null,
+ nrOfAdults: number,
+ childrenAges: number[],
+ nrOfNights: number
+): BreakfastData | null {
+ if (!isBreakfast || !packages) {
+ return null
+ }
+
+ const { nrOfPayingChildren, nrOfFreeChildren } = childrenAges.reduce(
+ (total, childAge) => {
+ if (childAge >= 4) {
+ total.nrOfPayingChildren = total.nrOfPayingChildren + 1
+ } else {
+ total.nrOfFreeChildren = total.nrOfFreeChildren + 1
+ }
+ return total
+ },
+ { nrOfPayingChildren: 0, nrOfFreeChildren: 0 }
+ )
+
+ const adultPackage = packages.find(
+ (p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
+ )
+ const childPackage = packages.find(
+ (p) => p.code === BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST
+ )
+ const priceAdult = adultPackage?.localPrice.price
+ const priceChild = childPackage?.localPrice.price
+ const currency =
+ adultPackage?.localPrice.currency ?? childPackage?.localPrice.currency
+
+ if (
+ typeof priceAdult !== "number" ||
+ typeof priceChild !== "number" ||
+ typeof currency !== "string"
+ ) {
+ return null
+ } else {
+ return {
+ nrOfAdults,
+ nrOfPayingChildren,
+ nrOfFreeChildren,
+ nrOfNights,
+ priceAdult,
+ priceChild,
+ currency,
+ }
+ }
+}
+
+export function buildBreakfastPackages(
+ data: AncillaryFormData,
+ breakfastData: BreakfastData
+) {
+ const packages = [
+ {
+ code: BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
+ quantity: breakfastData.nrOfAdults,
+ comment: data.optionalText || undefined,
+ },
+ {
+ code: BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
+ quantity: breakfastData.nrOfPayingChildren,
+ comment: data.optionalText || undefined,
+ },
+ {
+ code: BreakfastPackageEnum.FREE_CHILD_BREAKFAST,
+ quantity: breakfastData.nrOfFreeChildren,
+ comment: data.optionalText || undefined,
+ },
+ ]
+
+ return packages.filter((pkg) => pkg.quantity > 0)
+}
+
+export function getErrorMessage(
+ intl: IntlShape,
+ errorCode: string | null
+): AncillaryErrorMessage {
+ switch (errorCode) {
+ case BookingErrorCodeEnum.TransactionFailed:
+ return {
+ message: intl.formatMessage({
+ id: "guaranteePayment.failed",
+ defaultMessage:
+ "We had an issue guaranteeing your booking. Please try again.",
+ }),
+ type: AlertTypeEnum.Alarm,
+ }
+ case BookingErrorCodeEnum.TransactionCancelled:
+ return {
+ message: intl.formatMessage({
+ id: "guaranteePayment.cancelled",
+ defaultMessage:
+ "You have cancelled the payment. Your booking is not guaranteed.",
+ }),
+ type: AlertTypeEnum.Warning,
+ }
+ case "AncillaryFailed":
+ return {
+ message: intl.formatMessage({
+ id: "guaranteePayment.ancillaryFailed",
+ defaultMessage:
+ "The product could not be added. Your booking is guaranteed. Please try again.",
+ }),
+ type: AlertTypeEnum.Alarm,
+ }
+ default:
+ return {
+ message: intl.formatMessage({
+ id: "guaranteePayment.genericError",
+ defaultMessage: "Something went wrong! Please try again later.",
+ }),
+ type: AlertTypeEnum.Alarm,
+ }
+ }
+}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Upcoming/ManageStay/Actions/actions.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Upcoming/ManageStay/Actions/actions.module.css
index d66a6408e..b85d492b8 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Upcoming/ManageStay/Actions/actions.module.css
+++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/Upcoming/ManageStay/Actions/actions.module.css
@@ -1,7 +1,7 @@
.list {
display: flex;
flex-direction: column;
- align-items: start;
+ align-items: flex-start;
margin: 0;
padding: 0;
gap: var(--Space-x15);
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/utils.ts b/apps/scandic-web/components/HotelReservation/MyStay/utils.ts
index 44960d523..872ae730d 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/utils.ts
+++ b/apps/scandic-web/components/HotelReservation/MyStay/utils.ts
@@ -69,3 +69,9 @@ export function hasModifiableRate(cancellationRule: string | null): boolean {
cancellationRule === CancellationRuleEnum.Changeable
)
}
+
+export function isAncillaryError(searchParams: URLSearchParams): boolean {
+ const errorCode = searchParams.get("errorCode")
+ const ancillary = searchParams.get("ancillary")
+ return Boolean((errorCode && ancillary) || errorCode === "AncillaryFailed")
+}
diff --git a/apps/scandic-web/hooks/booking/useGuaranteePaymentFailedToast.ts b/apps/scandic-web/hooks/booking/useGuaranteePaymentFailedToast.ts
index 921d1b7da..45e4c3978 100644
--- a/apps/scandic-web/hooks/booking/useGuaranteePaymentFailedToast.ts
+++ b/apps/scandic-web/hooks/booking/useGuaranteePaymentFailedToast.ts
@@ -1,13 +1,16 @@
"use client"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
-import { useCallback, useEffect } from "react"
+import { useCallback, useEffect, useRef } from "react"
import { useIntl } from "react-intl"
import { toast } from "@scandic-hotels/design-system/Toast"
import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode"
+import { isAncillaryError } from "@/components/HotelReservation/MyStay/utils"
+
export function useGuaranteePaymentFailedToast() {
+ const hasRunOnce = useRef(false)
const intl = useIntl()
const searchParams = useSearchParams()
const pathname = usePathname()
@@ -16,11 +19,11 @@ export function useGuaranteePaymentFailedToast() {
const getErrorMessage = useCallback(
(errorCode: string | null) => {
switch (errorCode) {
- case "AncillaryFailed":
+ case BookingErrorCodeEnum.TransactionCancelled:
return intl.formatMessage({
- id: "guaranteePayment.ancillaryFailed",
+ id: "guaranteePayment.cancelled",
defaultMessage:
- "The product could not be added. Your booking is guaranteed. Please try again.",
+ "You have cancelled the payment. Your booking is not guaranteed.",
})
default:
return intl.formatMessage({
@@ -34,25 +37,33 @@ export function useGuaranteePaymentFailedToast() {
)
useEffect(() => {
- const errorCode = searchParams.get("errorCode")
- const errorMessage = getErrorMessage(errorCode)
- if (!errorCode || errorCode === BookingErrorCodeEnum.TransactionCancelled)
+ // To prevent multiple toasts in strict mode
+ if (hasRunOnce.current) {
return
+ }
+ const errorCode = searchParams.get("errorCode")
+ if (!errorCode) {
+ return
+ }
+ // Ancillary errors are handled in AddAncillaryFlowModal
+ if (isAncillaryError(searchParams)) {
+ hasRunOnce.current = true
+ return
+ }
+
+ const errorMessage = getErrorMessage(errorCode)
const toastType =
errorCode === BookingErrorCodeEnum.TransactionCancelled
? "warning"
: "error"
- toast[toastType](errorMessage)
- const ancillary = searchParams.get("ancillary")
- if ((errorCode && ancillary) || errorCode === "AncillaryFailed") {
- return
- }
+ toast[toastType](errorMessage)
const queryParams = new URLSearchParams(searchParams.toString())
queryParams.delete("errorCode")
router.push(`${pathname}?${queryParams.toString()}`)
+ hasRunOnce.current = true
}, [searchParams, pathname, router, getErrorMessage])
}
diff --git a/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts b/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts
index 5bc241b5f..424e2f77b 100644
--- a/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts
+++ b/apps/scandic-web/types/components/myPages/myStay/ancillaries.ts
@@ -1,3 +1,4 @@
+import type { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
import type {
ancillaryPackagesSchema,
packagesSchema,
@@ -24,6 +25,12 @@ export interface AddedAncillariesProps {
booking: Room
}
+export interface AncillaryItem {
+ code: string
+ quantity: number
+ comment: string | undefined
+}
+
export interface AddAncillaryFlowModalProps {
booking: Room
packages: Packages | null
@@ -34,15 +41,20 @@ export interface AddAncillaryFlowModalProps {
export interface SelectQuantityStepProps {
user: User | null
}
-
+export interface AncillaryErrorMessage {
+ type: AlertTypeEnum
+ message: string
+}
export interface ConfirmationStepProps {
savedCreditCards: CreditCard[] | null
user: User | null
+ error: AncillaryErrorMessage | null
}
export interface StepsProps {
user: User | null
savedCreditCards: CreditCard[] | null
+ error: AncillaryErrorMessage | null
}
export interface ActionButtonsProps {