Merged in fix/STAY-2-GLA-cancelled (pull request #3109)
Fix/STAY-2 GLA cancelled * fix: show toast on cancelling GLA flow * fix: show the ancillary GLA errors as inline alerts Approved-by: Bianca Widstam Approved-by: Erik Tiekstra
This commit is contained in:
@@ -29,6 +29,7 @@ import type { ConfirmationStepProps } from "@/types/components/myPages/myStay/an
|
|||||||
export default function ConfirmationStep({
|
export default function ConfirmationStep({
|
||||||
savedCreditCards,
|
savedCreditCards,
|
||||||
user,
|
user,
|
||||||
|
error,
|
||||||
}: ConfirmationStepProps) {
|
}: ConfirmationStepProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
@@ -138,14 +139,19 @@ export default function ConfirmationStep({
|
|||||||
</PaymentOptionsGroup>
|
</PaymentOptionsGroup>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Alert
|
{error ? (
|
||||||
type={AlertTypeEnum.Info}
|
<Alert type={error.type} text={error.message} />
|
||||||
text={intl.formatMessage({
|
) : (
|
||||||
id: "addAncillary.confirmationStep.guaranteeAddCard",
|
<Alert
|
||||||
defaultMessage:
|
type={AlertTypeEnum.Info}
|
||||||
"By adding a card you also guarantee your room booking for late arrival.",
|
text={intl.formatMessage({
|
||||||
})}
|
id: "addAncillary.confirmationStep.guaranteeAddCard",
|
||||||
/>
|
defaultMessage:
|
||||||
|
"By adding a card you also guarantee your room booking for late arrival.",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<SelectPaymentMethod
|
<SelectPaymentMethod
|
||||||
paymentMethods={(savedCreditCards ?? []).map((card) => ({
|
paymentMethods={(savedCreditCards ?? []).map((card) => ({
|
||||||
...card,
|
...card,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SelectQuantityStep from "../SelectQuantityStep"
|
|||||||
|
|
||||||
import type { StepsProps } from "@/types/components/myPages/myStay/ancillaries"
|
import type { StepsProps } from "@/types/components/myPages/myStay/ancillaries"
|
||||||
|
|
||||||
export default function Desktop({ user, savedCreditCards }: StepsProps) {
|
export default function Desktop({ user, savedCreditCards, error }: StepsProps) {
|
||||||
const currentStep = useAddAncillaryStore((state) => state.currentStep)
|
const currentStep = useAddAncillaryStore((state) => state.currentStep)
|
||||||
if (currentStep === AncillaryStepEnum.selectAncillary) {
|
if (currentStep === AncillaryStepEnum.selectAncillary) {
|
||||||
return <SelectAncillaryStep />
|
return <SelectAncillaryStep />
|
||||||
@@ -22,5 +22,11 @@ export default function Desktop({ user, savedCreditCards }: StepsProps) {
|
|||||||
return <DeliveryMethodStep />
|
return <DeliveryMethodStep />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ConfirmationStep savedCreditCards={savedCreditCards} user={user} />
|
return (
|
||||||
|
<ConfirmationStep
|
||||||
|
savedCreditCards={savedCreditCards}
|
||||||
|
user={user}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import SelectQuantityStep from "../SelectQuantityStep"
|
|||||||
|
|
||||||
import type { StepsProps } from "@/types/components/myPages/myStay/ancillaries"
|
import type { StepsProps } from "@/types/components/myPages/myStay/ancillaries"
|
||||||
|
|
||||||
export default function Mobile({ user, savedCreditCards }: StepsProps) {
|
export default function Mobile({ user, savedCreditCards, error }: StepsProps) {
|
||||||
const { currentStep, selectedAncillary } = useAddAncillaryStore((state) => ({
|
const { currentStep, selectedAncillary } = useAddAncillaryStore((state) => ({
|
||||||
currentStep: state.currentStep,
|
currentStep: state.currentStep,
|
||||||
selectedAncillary: state.selectedAncillary,
|
selectedAncillary: state.selectedAncillary,
|
||||||
@@ -23,5 +23,11 @@ export default function Mobile({ user, savedCreditCards }: StepsProps) {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return <ConfirmationStep savedCreditCards={savedCreditCards} user={user} />
|
return (
|
||||||
|
<ConfirmationStep
|
||||||
|
savedCreditCards={savedCreditCards}
|
||||||
|
user={user}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,11 @@ import Modal from "@scandic-hotels/design-system/Modal"
|
|||||||
import { toast } from "@scandic-hotels/design-system/Toast"
|
import { toast } from "@scandic-hotels/design-system/Toast"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
|
|
||||||
|
|
||||||
import { isWebview } from "@/constants/routes/webviews"
|
import { isWebview } from "@/constants/routes/webviews"
|
||||||
import { env } from "@/env/client"
|
import { env } from "@/env/client"
|
||||||
import {
|
import {
|
||||||
AncillaryStepEnum,
|
AncillaryStepEnum,
|
||||||
type BreakfastData,
|
|
||||||
useAddAncillaryStore,
|
useAddAncillaryStore,
|
||||||
} from "@/stores/my-stay/add-ancillary-flow"
|
} from "@/stores/my-stay/add-ancillary-flow"
|
||||||
|
|
||||||
@@ -41,16 +39,23 @@ import {
|
|||||||
trackGlaAncillaryAttempt,
|
trackGlaAncillaryAttempt,
|
||||||
} from "@/utils/tracking/myStay"
|
} from "@/utils/tracking/myStay"
|
||||||
|
|
||||||
|
import { isAncillaryError } from "../../../utils"
|
||||||
import { type AncillaryFormData, ancillaryFormSchema } from "../schema"
|
import { type AncillaryFormData, ancillaryFormSchema } from "../schema"
|
||||||
import ActionButtons from "./ActionButtons"
|
import ActionButtons from "./ActionButtons"
|
||||||
import PriceDetails from "./PriceDetails"
|
import PriceDetails from "./PriceDetails"
|
||||||
import Steps from "./Steps"
|
import Steps from "./Steps"
|
||||||
|
import {
|
||||||
|
buildBreakfastPackages,
|
||||||
|
calculateBreakfastData,
|
||||||
|
getErrorMessage,
|
||||||
|
} from "./utils"
|
||||||
|
|
||||||
import styles from "./addAncillaryFlowModal.module.css"
|
import styles from "./addAncillaryFlowModal.module.css"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AddAncillaryFlowModalProps,
|
AddAncillaryFlowModalProps,
|
||||||
Packages,
|
AncillaryErrorMessage,
|
||||||
|
AncillaryItem,
|
||||||
} from "@/types/components/myPages/myStay/ancillaries"
|
} from "@/types/components/myPages/myStay/ancillaries"
|
||||||
|
|
||||||
export default function AddAncillaryFlowModal({
|
export default function AddAncillaryFlowModal({
|
||||||
@@ -81,6 +86,8 @@ export default function AddAncillaryFlowModal({
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
const [isPriceDetailsOpen, setIsPriceDetailsOpen] = useState(false)
|
const [isPriceDetailsOpen, setIsPriceDetailsOpen] = useState(false)
|
||||||
|
const [errorMessage, setErrorMessage] =
|
||||||
|
useState<AncillaryErrorMessage | null>(null)
|
||||||
const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang, isWebview(pathname))}`
|
const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang, isWebview(pathname))}`
|
||||||
const deliveryTimeOptions = generateDeliveryOptions()
|
const deliveryTimeOptions = generateDeliveryOptions()
|
||||||
|
|
||||||
@@ -94,7 +101,7 @@ export default function AddAncillaryFlowModal({
|
|||||||
quantityWithPoints: null,
|
quantityWithPoints: null,
|
||||||
quantityWithCard:
|
quantityWithCard:
|
||||||
!user || hasInsufficientPoints || isBreakfast ? 1 : null,
|
!user || hasInsufficientPoints || isBreakfast ? 1 : null,
|
||||||
deliveryTime: defaultDeliveryTime,
|
deliveryTime: booking.ancillary?.deliveryTime ?? defaultDeliveryTime,
|
||||||
optionalText: "",
|
optionalText: "",
|
||||||
termsAndConditions: false,
|
termsAndConditions: false,
|
||||||
paymentMethod: booking.guaranteeInfo
|
paymentMethod: booking.guaranteeInfo
|
||||||
@@ -127,16 +134,6 @@ export default function AddAncillaryFlowModal({
|
|||||||
const { guaranteeBooking, isLoading, handleGuaranteeError } =
|
const { guaranteeBooking, isLoading, handleGuaranteeError } =
|
||||||
useGuaranteeBooking(booking.refId, true, booking.hotelId)
|
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(
|
function handleAncillarySubmission(
|
||||||
data: AncillaryFormData,
|
data: AncillaryFormData,
|
||||||
packages: {
|
packages: {
|
||||||
@@ -206,8 +203,10 @@ export default function AddAncillaryFlowModal({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
function handleGuaranteePayment(
|
||||||
function handleGuaranteePayment(data: AncillaryFormData, packages: any) {
|
data: AncillaryFormData,
|
||||||
|
packages: AncillaryItem[]
|
||||||
|
) {
|
||||||
const savedCreditCard = savedCreditCards?.find(
|
const savedCreditCard = savedCreditCards?.find(
|
||||||
(card) => card.id === data.paymentMethod
|
(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) => {
|
const onSubmit = (data: AncillaryFormData) => {
|
||||||
if (!validateTermsAndConditions(data)) return
|
|
||||||
|
|
||||||
const packagesToAdd = !isBreakfast
|
const packagesToAdd = !isBreakfast
|
||||||
? buildAncillaryPackages(data, selectedAncillary)
|
? buildAncillaryPackages(data, selectedAncillary)
|
||||||
: breakfastData
|
: breakfastData
|
||||||
@@ -300,14 +272,9 @@ export default function AddAncillaryFlowModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const errorCode = searchParams.get("errorCode")
|
if (isAncillaryError(searchParams)) {
|
||||||
const ancillary = searchParams.get("ancillary")
|
const errorCode = searchParams.get("errorCode")
|
||||||
if ((errorCode && ancillary) || errorCode === "AncillaryFailed") {
|
|
||||||
const queryParams = new URLSearchParams(searchParams.toString())
|
const queryParams = new URLSearchParams(searchParams.toString())
|
||||||
if (ancillary) {
|
|
||||||
queryParams.delete("ancillary")
|
|
||||||
}
|
|
||||||
queryParams.delete("errorCode")
|
|
||||||
const savedData = getAncillarySessionData()
|
const savedData = getAncillarySessionData()
|
||||||
if (savedData?.formData) {
|
if (savedData?.formData) {
|
||||||
const updatedFormData = {
|
const updatedFormData = {
|
||||||
@@ -318,9 +285,13 @@ export default function AddAncillaryFlowModal({
|
|||||||
}
|
}
|
||||||
formMethods.reset(updatedFormData)
|
formMethods.reset(updatedFormData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setErrorMessage(getErrorMessage(intl, errorCode))
|
||||||
|
queryParams.delete("ancillary")
|
||||||
|
queryParams.delete("errorCode")
|
||||||
router.replace(`${pathname}?${queryParams.toString()}`)
|
router.replace(`${pathname}?${queryParams.toString()}`)
|
||||||
}
|
}
|
||||||
}, [searchParams, pathname, formMethods, router, booking.guaranteeInfo])
|
}, [searchParams, pathname, formMethods, router, booking.guaranteeInfo, intl])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBreakfastData(
|
setBreakfastData(
|
||||||
@@ -424,7 +395,11 @@ export default function AddAncillaryFlowModal({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Steps user={user} savedCreditCards={savedCreditCards} />
|
<Steps
|
||||||
|
user={user}
|
||||||
|
savedCreditCards={savedCreditCards}
|
||||||
|
error={errorMessage}
|
||||||
|
/>
|
||||||
{currentStep === AncillaryStepEnum.selectAncillary ? null : (
|
{currentStep === AncillaryStepEnum.selectAncillary ? null : (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
@@ -520,61 +495,3 @@ function BreakfastPriceList() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
.list {
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: start;
|
align-items: flex-start;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
gap: var(--Space-x15);
|
gap: var(--Space-x15);
|
||||||
|
|||||||
@@ -69,3 +69,9 @@ export function hasModifiableRate(cancellationRule: string | null): boolean {
|
|||||||
cancellationRule === CancellationRuleEnum.Changeable
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useCallback, useEffect } from "react"
|
import { useCallback, useEffect, useRef } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { toast } from "@scandic-hotels/design-system/Toast"
|
import { toast } from "@scandic-hotels/design-system/Toast"
|
||||||
import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode"
|
import { BookingErrorCodeEnum } from "@scandic-hotels/trpc/enums/bookingErrorCode"
|
||||||
|
|
||||||
|
import { isAncillaryError } from "@/components/HotelReservation/MyStay/utils"
|
||||||
|
|
||||||
export function useGuaranteePaymentFailedToast() {
|
export function useGuaranteePaymentFailedToast() {
|
||||||
|
const hasRunOnce = useRef(false)
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
@@ -16,11 +19,11 @@ export function useGuaranteePaymentFailedToast() {
|
|||||||
const getErrorMessage = useCallback(
|
const getErrorMessage = useCallback(
|
||||||
(errorCode: string | null) => {
|
(errorCode: string | null) => {
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case "AncillaryFailed":
|
case BookingErrorCodeEnum.TransactionCancelled:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "guaranteePayment.ancillaryFailed",
|
id: "guaranteePayment.cancelled",
|
||||||
defaultMessage:
|
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:
|
default:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
@@ -34,25 +37,33 @@ export function useGuaranteePaymentFailedToast() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const errorCode = searchParams.get("errorCode")
|
// To prevent multiple toasts in strict mode
|
||||||
const errorMessage = getErrorMessage(errorCode)
|
if (hasRunOnce.current) {
|
||||||
if (!errorCode || errorCode === BookingErrorCodeEnum.TransactionCancelled)
|
|
||||||
return
|
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 =
|
const toastType =
|
||||||
errorCode === BookingErrorCodeEnum.TransactionCancelled
|
errorCode === BookingErrorCodeEnum.TransactionCancelled
|
||||||
? "warning"
|
? "warning"
|
||||||
: "error"
|
: "error"
|
||||||
toast[toastType](errorMessage)
|
|
||||||
|
|
||||||
const ancillary = searchParams.get("ancillary")
|
toast[toastType](errorMessage)
|
||||||
if ((errorCode && ancillary) || errorCode === "AncillaryFailed") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryParams = new URLSearchParams(searchParams.toString())
|
const queryParams = new URLSearchParams(searchParams.toString())
|
||||||
queryParams.delete("errorCode")
|
queryParams.delete("errorCode")
|
||||||
|
|
||||||
router.push(`${pathname}?${queryParams.toString()}`)
|
router.push(`${pathname}?${queryParams.toString()}`)
|
||||||
|
hasRunOnce.current = true
|
||||||
}, [searchParams, pathname, router, getErrorMessage])
|
}, [searchParams, pathname, router, getErrorMessage])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
|
||||||
import type {
|
import type {
|
||||||
ancillaryPackagesSchema,
|
ancillaryPackagesSchema,
|
||||||
packagesSchema,
|
packagesSchema,
|
||||||
@@ -24,6 +25,12 @@ export interface AddedAncillariesProps {
|
|||||||
booking: Room
|
booking: Room
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AncillaryItem {
|
||||||
|
code: string
|
||||||
|
quantity: number
|
||||||
|
comment: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export interface AddAncillaryFlowModalProps {
|
export interface AddAncillaryFlowModalProps {
|
||||||
booking: Room
|
booking: Room
|
||||||
packages: Packages | null
|
packages: Packages | null
|
||||||
@@ -34,15 +41,20 @@ export interface AddAncillaryFlowModalProps {
|
|||||||
export interface SelectQuantityStepProps {
|
export interface SelectQuantityStepProps {
|
||||||
user: User | null
|
user: User | null
|
||||||
}
|
}
|
||||||
|
export interface AncillaryErrorMessage {
|
||||||
|
type: AlertTypeEnum
|
||||||
|
message: string
|
||||||
|
}
|
||||||
export interface ConfirmationStepProps {
|
export interface ConfirmationStepProps {
|
||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
user: User | null
|
user: User | null
|
||||||
|
error: AncillaryErrorMessage | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StepsProps {
|
export interface StepsProps {
|
||||||
user: User | null
|
user: User | null
|
||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
|
error: AncillaryErrorMessage | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionButtonsProps {
|
export interface ActionButtonsProps {
|
||||||
|
|||||||
Reference in New Issue
Block a user