Merged in feat/SW-1997-tracking-gla-my-stay-ancillaries (pull request #1657)
Feat/SW-1997 tracking gla my stay ancillaries * feat(SW-1996): tracking gla my stay * feat(SW-1996): update gla tracking * feat(SW-1996): fix comment * feat(SW-1997): add tracking for gla my stay and ancillaries * feat(SW-1997): rebase master * feat(SW-1997): fix duplicate import * feat(SW-1997): add hotelId and category for ancillaries, and add more tracking * feat(SW-1997): remove commments and fix spelling mistake * feat(SW-1997): if addAncillary failed, but guarantee is successful, default to card in booking Approved-by: Niclas Edenvin
This commit is contained in:
@@ -10,7 +10,9 @@ import {
|
||||
useAddAncillaryStore,
|
||||
} from "@/stores/my-stay/add-ancillary-flow"
|
||||
|
||||
import { type AncillaryFormData, quantitySchema } from "../../schema"
|
||||
import { trackAddAncillary } from "@/utils/tracking/myStay"
|
||||
|
||||
import { type AncillaryQuantityFormData,quantitySchema } from "../../schema"
|
||||
|
||||
import styles from "./actionButtons.module.css"
|
||||
|
||||
@@ -27,12 +29,14 @@ export default function ActionButtons({
|
||||
selectQuantity,
|
||||
selectDeliveryTime,
|
||||
selectQuantityAndDeliveryTime,
|
||||
selectedAncillary,
|
||||
} = useAddAncillaryStore((state) => ({
|
||||
currentStep: state.currentStep,
|
||||
prevStep: state.prevStep,
|
||||
selectQuantity: state.selectQuantity,
|
||||
selectDeliveryTime: state.selectDeliveryTime,
|
||||
selectQuantityAndDeliveryTime: state.selectQuantityAndDeliveryTime,
|
||||
selectedAncillary: state.selectedAncillary,
|
||||
}))
|
||||
const isMobile = useMediaQuery("(max-width: 767px)")
|
||||
const { setError } = useFormContext()
|
||||
@@ -41,10 +45,10 @@ export default function ActionButtons({
|
||||
const isConfirmStep = currentStep === AncillaryStepEnum.confirmation
|
||||
const confirmLabel = intl.formatMessage({ id: "Confirm" })
|
||||
const continueLabel = intl.formatMessage({ id: "Continue" })
|
||||
const quantityWithCard = useWatch<AncillaryFormData>({
|
||||
const quantityWithCard = useWatch<AncillaryQuantityFormData>({
|
||||
name: "quantityWithCard",
|
||||
})
|
||||
const quantityWithPoints = useWatch<AncillaryFormData>({
|
||||
const quantityWithPoints = useWatch<AncillaryQuantityFormData>({
|
||||
name: "quantityWithPoints",
|
||||
})
|
||||
function handleNextStep() {
|
||||
@@ -54,6 +58,11 @@ export default function ActionButtons({
|
||||
quantityWithPoints,
|
||||
})
|
||||
if (validatedQuantity.success) {
|
||||
trackAddAncillary(
|
||||
selectedAncillary,
|
||||
quantityWithCard,
|
||||
quantityWithPoints
|
||||
)
|
||||
if (isMobile) {
|
||||
selectQuantityAndDeliveryTime()
|
||||
} else {
|
||||
|
||||
@@ -26,8 +26,14 @@ import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
import {
|
||||
trackAncillaryFailed,
|
||||
trackAncillarySuccess,
|
||||
trackGlaAncillaryAttempt,
|
||||
} from "@/utils/tracking/myStay"
|
||||
|
||||
import {
|
||||
buildAncillaryPackages,
|
||||
clearAncillarySessionData,
|
||||
generateDeliveryOptions,
|
||||
getAncillarySessionData,
|
||||
@@ -113,128 +119,155 @@ export default function AddAncillaryFlowModal({
|
||||
}
|
||||
|
||||
const utils = trpc.useUtils()
|
||||
const addAncillary = trpc.booking.packages.useMutation({
|
||||
onSuccess: (data, variables) => {
|
||||
if (data) {
|
||||
clearAncillarySessionData()
|
||||
closeModal()
|
||||
utils.booking.confirmation.invalidate({
|
||||
confirmationNumber: variables.confirmationNumber,
|
||||
})
|
||||
const addAncillary = trpc.booking.packages.useMutation()
|
||||
|
||||
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, handleGuaranteeError } =
|
||||
useGuaranteeBooking({
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
isAncillaryFlow: true,
|
||||
})
|
||||
|
||||
const { guaranteeBooking, isLoading } = useGuaranteeBooking({
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
})
|
||||
|
||||
const onSubmit = (data: AncillaryFormData) => {
|
||||
function validateTermsAndConditions(data: AncillaryFormData): boolean {
|
||||
if (!data.termsAndConditions) {
|
||||
formMethods.setError("termsAndConditions", {
|
||||
message: "You must accept the terms",
|
||||
})
|
||||
return
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
setAncillarySessionData({
|
||||
formData: data,
|
||||
selectedAncillary,
|
||||
})
|
||||
if (booking.guaranteeInfo) {
|
||||
const packagesToAdd = []
|
||||
if (selectedAncillary?.id && data.quantityWithCard) {
|
||||
if (!isBreakfast) {
|
||||
packagesToAdd.push({
|
||||
code: selectedAncillary.id,
|
||||
quantity: data.quantityWithCard,
|
||||
comment: data.optionalText || undefined,
|
||||
})
|
||||
} else {
|
||||
if (!breakfastData) {
|
||||
toast.error(
|
||||
intl.formatMessage({
|
||||
id: "Something went wrong!",
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
packagesToAdd.push({
|
||||
code: BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
|
||||
quantity: breakfastData.nrOfAdults,
|
||||
comment: data.optionalText || undefined,
|
||||
})
|
||||
packagesToAdd.push({
|
||||
code: BreakfastPackageEnum.ANCILLARY_CHILD_PAYING_BREAKFAST,
|
||||
quantity: breakfastData.nrOfPayingChildren,
|
||||
comment: data.optionalText || undefined,
|
||||
})
|
||||
packagesToAdd.push({
|
||||
code: BreakfastPackageEnum.FREE_CHILD_BREAKFAST,
|
||||
quantity: breakfastData.nrOfFreeChildren,
|
||||
comment: data.optionalText || undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedAncillary?.loyaltyCode && data.quantityWithPoints) {
|
||||
packagesToAdd.push({
|
||||
code: selectedAncillary.loyaltyCode,
|
||||
quantity: data.quantityWithPoints,
|
||||
comment: data.optionalText || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
addAncillary.mutate({
|
||||
function handleAncillarySubmission(
|
||||
data: AncillaryFormData,
|
||||
packages: {
|
||||
code: string
|
||||
quantity: number
|
||||
comment: string | undefined
|
||||
}[]
|
||||
) {
|
||||
addAncillary.mutate(
|
||||
{
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
ancillaryComment: data.optionalText,
|
||||
ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
|
||||
? data.deliveryTime
|
||||
: undefined,
|
||||
packages: packagesToAdd,
|
||||
packages: packages,
|
||||
language: lang,
|
||||
})
|
||||
} else {
|
||||
const savedCreditCard = savedCreditCards?.find(
|
||||
(card) => card.id === data.paymentMethod
|
||||
)
|
||||
if (booking.confirmationNumber) {
|
||||
const card = savedCreditCard
|
||||
? {
|
||||
},
|
||||
{
|
||||
onSuccess: (result) => {
|
||||
if (result) {
|
||||
trackAncillarySuccess(
|
||||
booking.confirmationNumber,
|
||||
packages,
|
||||
data.deliveryTime,
|
||||
"ancillary",
|
||||
selectedAncillary,
|
||||
booking.guaranteeInfo?.cardType,
|
||||
booking.roomTypeCode
|
||||
)
|
||||
toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "{ancillary} added to your booking!" },
|
||||
{ ancillary: selectedAncillary?.title }
|
||||
)
|
||||
)
|
||||
clearAncillarySessionData()
|
||||
closeModal()
|
||||
utils.booking.confirmation.invalidate({
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
})
|
||||
router.refresh()
|
||||
} else {
|
||||
trackAncillaryFailed(packages, data.deliveryTime, selectedAncillary)
|
||||
toast.error(ancillaryErrorMessage)
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
trackAncillaryFailed(packages, data.deliveryTime, selectedAncillary)
|
||||
toast.error(ancillaryErrorMessage)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function handleGuaranteePayment(data: AncillaryFormData, packages: any) {
|
||||
const savedCreditCard = savedCreditCards?.find(
|
||||
(card) => card.id === data.paymentMethod
|
||||
)
|
||||
trackGlaAncillaryAttempt(
|
||||
savedCreditCard,
|
||||
packages,
|
||||
selectedAncillary,
|
||||
data.deliveryTime
|
||||
)
|
||||
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!",
|
||||
})
|
||||
)
|
||||
}
|
||||
: 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 {
|
||||
handleGuaranteeError("No confirmation number")
|
||||
}
|
||||
}
|
||||
|
||||
function buildBreakfastPackages(
|
||||
data: AncillaryFormData,
|
||||
breakfastData: BreakfastData
|
||||
) {
|
||||
return [
|
||||
{
|
||||
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,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const onSubmit = (data: AncillaryFormData) => {
|
||||
if (!validateTermsAndConditions(data)) return
|
||||
|
||||
setAncillarySessionData({
|
||||
formData: data,
|
||||
selectedAncillary,
|
||||
})
|
||||
const packagesToAdd = !isBreakfast
|
||||
? buildAncillaryPackages(data, selectedAncillary)
|
||||
: breakfastData
|
||||
? buildBreakfastPackages(data, breakfastData)
|
||||
: []
|
||||
|
||||
if (isBreakfast && !breakfastData) {
|
||||
toast.error(intl.formatMessage({ id: "Something went wrong!" }))
|
||||
return
|
||||
}
|
||||
|
||||
if (booking.guaranteeInfo) {
|
||||
handleAncillarySubmission(data, packagesToAdd)
|
||||
} else {
|
||||
handleGuaranteePayment(data, packagesToAdd)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,11 +282,17 @@ export default function AddAncillaryFlowModal({
|
||||
queryParams.delete("errorCode")
|
||||
const savedData = getAncillarySessionData()
|
||||
if (savedData?.formData) {
|
||||
formMethods.reset(savedData.formData)
|
||||
const updatedFormData = {
|
||||
...savedData.formData,
|
||||
paymentMethod: booking?.guaranteeInfo
|
||||
? PaymentMethodEnum.card
|
||||
: savedData.formData.paymentMethod,
|
||||
}
|
||||
formMethods.reset(updatedFormData)
|
||||
}
|
||||
router.replace(`${pathname}?${queryParams.toString()}`)
|
||||
}
|
||||
}, [searchParams, pathname, formMethods, router])
|
||||
}, [searchParams, pathname, formMethods, router, booking.guaranteeInfo])
|
||||
|
||||
useEffect(() => {
|
||||
setBreakfastData(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow"
|
||||
|
||||
import { AncillaryCard } from "@/components/TempDesignSystem/AncillaryCard"
|
||||
import { trackViewAncillary } from "@/utils/tracking/myStay"
|
||||
|
||||
import type { SelectedAncillary } from "@/types/components/myPages/myStay/ancillaries"
|
||||
|
||||
@@ -13,8 +14,15 @@ export default function WrappedAncillaryCard({
|
||||
}: WrappedAncillaryProps) {
|
||||
const { description, ...ancillaryWithoutDescription } = ancillary
|
||||
const selectAncillary = useAddAncillaryStore((state) => state.selectAncillary)
|
||||
|
||||
return (
|
||||
<div role="button" onClick={() => selectAncillary(ancillary)}>
|
||||
<div
|
||||
role="button"
|
||||
onClick={() => {
|
||||
selectAncillary(ancillary)
|
||||
trackViewAncillary(ancillary)
|
||||
}}
|
||||
>
|
||||
<AncillaryCard ancillary={ancillaryWithoutDescription} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ const quantitySchemaWithoutRefine = z.object({
|
||||
quantityWithPoints: z.number().nullable(),
|
||||
quantityWithCard: z.number().nullable(),
|
||||
})
|
||||
|
||||
export const quantitySchema = z
|
||||
.object({})
|
||||
.merge(quantitySchemaWithoutRefine)
|
||||
@@ -35,4 +36,7 @@ export const ancillaryFormSchema = z
|
||||
}
|
||||
)
|
||||
|
||||
export type AncillaryQuantityFormData = z.output<
|
||||
typeof quantitySchemaWithoutRefine
|
||||
>
|
||||
export type AncillaryFormData = z.output<typeof ancillaryFormSchema>
|
||||
|
||||
Reference in New Issue
Block a user