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:
@@ -1,4 +1,4 @@
|
|||||||
import { notFound, redirect } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BookingErrorCodeEnum,
|
BookingErrorCodeEnum,
|
||||||
@@ -8,6 +8,7 @@ import { myStay } from "@/constants/routes/myStay"
|
|||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import GuaranteeCallback from "@/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback"
|
import GuaranteeCallback from "@/components/HotelReservation/MyStay/Ancillaries/GuaranteeCallback"
|
||||||
|
import TrackGuarantee from "@/components/HotelReservation/MyStay/GuaranteeLateArrival/GuaranteeLateArrivalCallback"
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
@@ -48,7 +49,7 @@ export default async function GuaranteePaymentCallbackPage({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
console.log(`[gla-payment-callback] redirecting to: ${myStayUrl}`)
|
console.log(`[gla-payment-callback] redirecting to: ${myStayUrl}`)
|
||||||
return redirect(myStayUrl)
|
return <TrackGuarantee status={status} redirectUrl={myStayUrl} />
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorMessage = undefined
|
let errorMessage = undefined
|
||||||
@@ -86,7 +87,15 @@ export default async function GuaranteePaymentCallbackPage({
|
|||||||
if (isAncillaryFlow) {
|
if (isAncillaryFlow) {
|
||||||
searchObject.set("ancillary", "ancillary")
|
searchObject.set("ancillary", "ancillary")
|
||||||
}
|
}
|
||||||
redirect(`${myStayUrl}&${searchObject.toString()}`)
|
|
||||||
|
return (
|
||||||
|
<TrackGuarantee
|
||||||
|
status={status}
|
||||||
|
isAncillaryFlow={!!isAncillaryFlow}
|
||||||
|
redirectUrl={`${myStayUrl}&${searchObject.toString()}`}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <LoadingSpinner />
|
return <LoadingSpinner />
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import {
|
|||||||
useAddAncillaryStore,
|
useAddAncillaryStore,
|
||||||
} from "@/stores/my-stay/add-ancillary-flow"
|
} 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"
|
import styles from "./actionButtons.module.css"
|
||||||
|
|
||||||
@@ -27,12 +29,14 @@ export default function ActionButtons({
|
|||||||
selectQuantity,
|
selectQuantity,
|
||||||
selectDeliveryTime,
|
selectDeliveryTime,
|
||||||
selectQuantityAndDeliveryTime,
|
selectQuantityAndDeliveryTime,
|
||||||
|
selectedAncillary,
|
||||||
} = useAddAncillaryStore((state) => ({
|
} = useAddAncillaryStore((state) => ({
|
||||||
currentStep: state.currentStep,
|
currentStep: state.currentStep,
|
||||||
prevStep: state.prevStep,
|
prevStep: state.prevStep,
|
||||||
selectQuantity: state.selectQuantity,
|
selectQuantity: state.selectQuantity,
|
||||||
selectDeliveryTime: state.selectDeliveryTime,
|
selectDeliveryTime: state.selectDeliveryTime,
|
||||||
selectQuantityAndDeliveryTime: state.selectQuantityAndDeliveryTime,
|
selectQuantityAndDeliveryTime: state.selectQuantityAndDeliveryTime,
|
||||||
|
selectedAncillary: state.selectedAncillary,
|
||||||
}))
|
}))
|
||||||
const isMobile = useMediaQuery("(max-width: 767px)")
|
const isMobile = useMediaQuery("(max-width: 767px)")
|
||||||
const { setError } = useFormContext()
|
const { setError } = useFormContext()
|
||||||
@@ -41,10 +45,10 @@ export default function ActionButtons({
|
|||||||
const isConfirmStep = currentStep === AncillaryStepEnum.confirmation
|
const isConfirmStep = currentStep === AncillaryStepEnum.confirmation
|
||||||
const confirmLabel = intl.formatMessage({ id: "Confirm" })
|
const confirmLabel = intl.formatMessage({ id: "Confirm" })
|
||||||
const continueLabel = intl.formatMessage({ id: "Continue" })
|
const continueLabel = intl.formatMessage({ id: "Continue" })
|
||||||
const quantityWithCard = useWatch<AncillaryFormData>({
|
const quantityWithCard = useWatch<AncillaryQuantityFormData>({
|
||||||
name: "quantityWithCard",
|
name: "quantityWithCard",
|
||||||
})
|
})
|
||||||
const quantityWithPoints = useWatch<AncillaryFormData>({
|
const quantityWithPoints = useWatch<AncillaryQuantityFormData>({
|
||||||
name: "quantityWithPoints",
|
name: "quantityWithPoints",
|
||||||
})
|
})
|
||||||
function handleNextStep() {
|
function handleNextStep() {
|
||||||
@@ -54,6 +58,11 @@ export default function ActionButtons({
|
|||||||
quantityWithPoints,
|
quantityWithPoints,
|
||||||
})
|
})
|
||||||
if (validatedQuantity.success) {
|
if (validatedQuantity.success) {
|
||||||
|
trackAddAncillary(
|
||||||
|
selectedAncillary,
|
||||||
|
quantityWithCard,
|
||||||
|
quantityWithPoints
|
||||||
|
)
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
selectQuantityAndDeliveryTime()
|
selectQuantityAndDeliveryTime()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -26,8 +26,14 @@ import { toast } from "@/components/TempDesignSystem/Toasts"
|
|||||||
import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking"
|
import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
import {
|
||||||
|
trackAncillaryFailed,
|
||||||
|
trackAncillarySuccess,
|
||||||
|
trackGlaAncillaryAttempt,
|
||||||
|
} from "@/utils/tracking/myStay"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
buildAncillaryPackages,
|
||||||
clearAncillarySessionData,
|
clearAncillarySessionData,
|
||||||
generateDeliveryOptions,
|
generateDeliveryOptions,
|
||||||
getAncillarySessionData,
|
getAncillarySessionData,
|
||||||
@@ -113,128 +119,155 @@ export default function AddAncillaryFlowModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
const addAncillary = trpc.booking.packages.useMutation({
|
const addAncillary = trpc.booking.packages.useMutation()
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
if (data) {
|
|
||||||
clearAncillarySessionData()
|
|
||||||
closeModal()
|
|
||||||
utils.booking.confirmation.invalidate({
|
|
||||||
confirmationNumber: variables.confirmationNumber,
|
|
||||||
})
|
|
||||||
|
|
||||||
toast.success(
|
const { guaranteeBooking, isLoading, handleGuaranteeError } =
|
||||||
intl.formatMessage(
|
useGuaranteeBooking({
|
||||||
{ id: "{ancillary} added to your booking!" },
|
confirmationNumber: booking.confirmationNumber,
|
||||||
{ ancillary: selectedAncillary?.title }
|
isAncillaryFlow: true,
|
||||||
)
|
})
|
||||||
)
|
|
||||||
router.refresh()
|
|
||||||
} else {
|
|
||||||
toast.error(ancillaryErrorMessage)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast.error(ancillaryErrorMessage)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const { guaranteeBooking, isLoading } = useGuaranteeBooking({
|
function validateTermsAndConditions(data: AncillaryFormData): boolean {
|
||||||
confirmationNumber: booking.confirmationNumber,
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = (data: AncillaryFormData) => {
|
|
||||||
if (!data.termsAndConditions) {
|
if (!data.termsAndConditions) {
|
||||||
formMethods.setError("termsAndConditions", {
|
formMethods.setError("termsAndConditions", {
|
||||||
message: "You must accept the terms",
|
message: "You must accept the terms",
|
||||||
})
|
})
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
setAncillarySessionData({
|
function handleAncillarySubmission(
|
||||||
formData: data,
|
data: AncillaryFormData,
|
||||||
selectedAncillary,
|
packages: {
|
||||||
})
|
code: string
|
||||||
if (booking.guaranteeInfo) {
|
quantity: number
|
||||||
const packagesToAdd = []
|
comment: string | undefined
|
||||||
if (selectedAncillary?.id && data.quantityWithCard) {
|
}[]
|
||||||
if (!isBreakfast) {
|
) {
|
||||||
packagesToAdd.push({
|
addAncillary.mutate(
|
||||||
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({
|
|
||||||
confirmationNumber: booking.confirmationNumber,
|
confirmationNumber: booking.confirmationNumber,
|
||||||
ancillaryComment: data.optionalText,
|
ancillaryComment: data.optionalText,
|
||||||
ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
|
ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
|
||||||
? data.deliveryTime
|
? data.deliveryTime
|
||||||
: undefined,
|
: undefined,
|
||||||
packages: packagesToAdd,
|
packages: packages,
|
||||||
language: lang,
|
language: lang,
|
||||||
})
|
},
|
||||||
} else {
|
{
|
||||||
const savedCreditCard = savedCreditCards?.find(
|
onSuccess: (result) => {
|
||||||
(card) => card.id === data.paymentMethod
|
if (result) {
|
||||||
)
|
trackAncillarySuccess(
|
||||||
if (booking.confirmationNumber) {
|
booking.confirmationNumber,
|
||||||
const card = savedCreditCard
|
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,
|
alias: savedCreditCard.alias,
|
||||||
expiryDate: savedCreditCard.expirationDate,
|
expiryDate: savedCreditCard.expirationDate,
|
||||||
cardType: savedCreditCard.cardType,
|
cardType: savedCreditCard.cardType,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
guaranteeBooking.mutate({
|
guaranteeBooking.mutate({
|
||||||
confirmationNumber: booking.confirmationNumber,
|
confirmationNumber: booking.confirmationNumber,
|
||||||
language: lang,
|
language: lang,
|
||||||
...(card && { card }),
|
...(card && { card }),
|
||||||
success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
success: `${guaranteeRedirectUrl}?status=success&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
||||||
error: `${guaranteeRedirectUrl}?status=error&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
error: `${guaranteeRedirectUrl}?status=error&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
||||||
cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}&ancillary=1`,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
handleGuaranteeError("No confirmation number")
|
||||||
intl.formatMessage({
|
}
|
||||||
id: "Something went wrong!",
|
}
|
||||||
})
|
|
||||||
)
|
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")
|
queryParams.delete("errorCode")
|
||||||
const savedData = getAncillarySessionData()
|
const savedData = getAncillarySessionData()
|
||||||
if (savedData?.formData) {
|
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()}`)
|
router.replace(`${pathname}?${queryParams.toString()}`)
|
||||||
}
|
}
|
||||||
}, [searchParams, pathname, formMethods, router])
|
}, [searchParams, pathname, formMethods, router, booking.guaranteeInfo])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBreakfastData(
|
setBreakfastData(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow"
|
import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow"
|
||||||
|
|
||||||
import { AncillaryCard } from "@/components/TempDesignSystem/AncillaryCard"
|
import { AncillaryCard } from "@/components/TempDesignSystem/AncillaryCard"
|
||||||
|
import { trackViewAncillary } from "@/utils/tracking/myStay"
|
||||||
|
|
||||||
import type { SelectedAncillary } from "@/types/components/myPages/myStay/ancillaries"
|
import type { SelectedAncillary } from "@/types/components/myPages/myStay/ancillaries"
|
||||||
|
|
||||||
@@ -13,8 +14,15 @@ export default function WrappedAncillaryCard({
|
|||||||
}: WrappedAncillaryProps) {
|
}: WrappedAncillaryProps) {
|
||||||
const { description, ...ancillaryWithoutDescription } = ancillary
|
const { description, ...ancillaryWithoutDescription } = ancillary
|
||||||
const selectAncillary = useAddAncillaryStore((state) => state.selectAncillary)
|
const selectAncillary = useAddAncillaryStore((state) => state.selectAncillary)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div role="button" onClick={() => selectAncillary(ancillary)}>
|
<div
|
||||||
|
role="button"
|
||||||
|
onClick={() => {
|
||||||
|
selectAncillary(ancillary)
|
||||||
|
trackViewAncillary(ancillary)
|
||||||
|
}}
|
||||||
|
>
|
||||||
<AncillaryCard ancillary={ancillaryWithoutDescription} />
|
<AncillaryCard ancillary={ancillaryWithoutDescription} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const quantitySchemaWithoutRefine = z.object({
|
|||||||
quantityWithPoints: z.number().nullable(),
|
quantityWithPoints: z.number().nullable(),
|
||||||
quantityWithCard: z.number().nullable(),
|
quantityWithCard: z.number().nullable(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const quantitySchema = z
|
export const quantitySchema = z
|
||||||
.object({})
|
.object({})
|
||||||
.merge(quantitySchemaWithoutRefine)
|
.merge(quantitySchemaWithoutRefine)
|
||||||
@@ -35,4 +36,7 @@ export const ancillaryFormSchema = z
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export type AncillaryQuantityFormData = z.output<
|
||||||
|
typeof quantitySchemaWithoutRefine
|
||||||
|
>
|
||||||
export type AncillaryFormData = z.output<typeof ancillaryFormSchema>
|
export type AncillaryFormData = z.output<typeof ancillaryFormSchema>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem
|
|||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import { trackRemoveAncillary } from "@/utils/tracking/myStay"
|
||||||
|
|
||||||
import { getBreakfastPackagesFromAncillaryFlow } from "../../utils/hasBreakfastPackage"
|
import { getBreakfastPackagesFromAncillaryFlow } from "../../utils/hasBreakfastPackage"
|
||||||
import RemoveButton from "./RemoveButton"
|
import RemoveButton from "./RemoveButton"
|
||||||
@@ -180,7 +181,14 @@ export function AddedAncillaries({
|
|||||||
confirmationNumber={booking.confirmationNumber}
|
confirmationNumber={booking.confirmationNumber}
|
||||||
code={ancillary.code}
|
code={ancillary.code}
|
||||||
title={ancillaryTitle}
|
title={ancillaryTitle}
|
||||||
onSuccess={router.refresh}
|
onSuccess={() => {
|
||||||
|
trackRemoveAncillary(
|
||||||
|
ancillary,
|
||||||
|
booking.hotelId,
|
||||||
|
booking.ancillary?.deliveryTime
|
||||||
|
)
|
||||||
|
router.refresh()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -6,8 +6,16 @@ import { useEffect } from "react"
|
|||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
|
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
import {
|
||||||
|
trackAncillaryFailed,
|
||||||
|
trackAncillarySuccess,
|
||||||
|
} from "@/utils/tracking/myStay"
|
||||||
|
|
||||||
import { clearAncillarySessionData, getAncillarySessionData } from "../utils"
|
import {
|
||||||
|
buildAncillaryPackages,
|
||||||
|
clearAncillarySessionData,
|
||||||
|
getAncillarySessionData,
|
||||||
|
} from "../utils"
|
||||||
|
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
@@ -22,15 +30,7 @@ export default function GuaranteeAncillaryHandler({
|
|||||||
}) {
|
}) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const addAncillary = trpc.booking.packages.useMutation({
|
const addAncillary = trpc.booking.packages.useMutation()
|
||||||
onSuccess: () => {
|
|
||||||
clearAncillarySessionData()
|
|
||||||
router.replace(returnUrl)
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
router.replace(`${returnUrl}&errorCode=AncillaryFailed`)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (addAncillary.isPending || addAncillary.submittedAt) {
|
if (addAncillary.isPending || addAncillary.submittedAt) {
|
||||||
@@ -44,33 +44,49 @@ export default function GuaranteeAncillaryHandler({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { formData, selectedAncillary } = sessionData
|
const { formData, selectedAncillary } = sessionData
|
||||||
const packages = []
|
const packages = buildAncillaryPackages(formData, selectedAncillary)
|
||||||
|
|
||||||
if (selectedAncillary?.id && formData.quantityWithCard) {
|
addAncillary.mutate(
|
||||||
packages.push({
|
{
|
||||||
code: selectedAncillary.id,
|
confirmationNumber,
|
||||||
quantity: formData.quantityWithCard,
|
ancillaryComment: formData.optionalText,
|
||||||
comment: formData.optionalText || undefined,
|
ancillaryDeliveryTime: selectedAncillary.requiresDeliveryTime
|
||||||
})
|
? formData.deliveryTime
|
||||||
}
|
: undefined,
|
||||||
|
packages,
|
||||||
if (selectedAncillary?.loyaltyCode && formData.quantityWithPoints) {
|
language: lang,
|
||||||
packages.push({
|
},
|
||||||
code: selectedAncillary.loyaltyCode,
|
{
|
||||||
quantity: formData.quantityWithPoints,
|
onSuccess: (data) => {
|
||||||
comment: formData.optionalText || undefined,
|
if (data) {
|
||||||
})
|
trackAncillarySuccess(
|
||||||
}
|
confirmationNumber,
|
||||||
|
packages,
|
||||||
addAncillary.mutate({
|
formData.deliveryTime,
|
||||||
confirmationNumber,
|
"room + ancillary",
|
||||||
ancillaryComment: formData.optionalText,
|
selectedAncillary
|
||||||
ancillaryDeliveryTime: selectedAncillary.requiresDeliveryTime
|
)
|
||||||
? formData.deliveryTime
|
clearAncillarySessionData()
|
||||||
: undefined,
|
router.replace(returnUrl)
|
||||||
packages,
|
} else {
|
||||||
language: lang,
|
trackAncillaryFailed(
|
||||||
})
|
packages,
|
||||||
|
formData.deliveryTime,
|
||||||
|
selectedAncillary
|
||||||
|
)
|
||||||
|
router.replace(`${returnUrl}&errorCode=AncillaryFailed`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
trackAncillaryFailed(
|
||||||
|
packages,
|
||||||
|
formData.deliveryTime,
|
||||||
|
selectedAncillary
|
||||||
|
)
|
||||||
|
router.replace(`${returnUrl}&errorCode=AncillaryFailed`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
}, [confirmationNumber, returnUrl, addAncillary, lang, router])
|
}, [confirmationNumber, returnUrl, addAncillary, lang, router])
|
||||||
|
|
||||||
return <LoadingSpinner />
|
return <LoadingSpinner />
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ export function Ancillaries({
|
|||||||
requiresDeliveryTime: false,
|
requiresDeliveryTime: false,
|
||||||
loyaltyCode: undefined,
|
loyaltyCode: undefined,
|
||||||
points: undefined,
|
points: undefined,
|
||||||
|
hotelId: Number(booking.hotelId),
|
||||||
|
categoryName: "Food",
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
@@ -135,6 +137,7 @@ export function Ancillaries({
|
|||||||
booking.rateDefinition.breakfastIncluded,
|
booking.rateDefinition.breakfastIncluded,
|
||||||
intl,
|
intl,
|
||||||
packages,
|
packages,
|
||||||
|
booking.hotelId,
|
||||||
])
|
])
|
||||||
|
|
||||||
const allAncillaries = useMemo(() => {
|
const allAncillaries = useMemo(() => {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { Ancillary } from "@/types/components/myPages/myStay/ancillaries"
|
import type {
|
||||||
|
Ancillary,
|
||||||
|
SelectedAncillary,
|
||||||
|
} from "@/types/components/myPages/myStay/ancillaries"
|
||||||
import type { AncillaryFormData } from "./AddAncillaryFlow/schema"
|
import type { AncillaryFormData } from "./AddAncillaryFlow/schema"
|
||||||
|
|
||||||
export const generateDeliveryOptions = () => {
|
export const generateDeliveryOptions = () => {
|
||||||
@@ -9,6 +12,32 @@ export const generateDeliveryOptions = () => {
|
|||||||
value: slot,
|
value: slot,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildAncillaryPackages(
|
||||||
|
data: AncillaryFormData,
|
||||||
|
ancillary: SelectedAncillary | null
|
||||||
|
) {
|
||||||
|
const packages = []
|
||||||
|
|
||||||
|
if (ancillary?.id && data.quantityWithCard) {
|
||||||
|
packages.push({
|
||||||
|
code: ancillary.id,
|
||||||
|
quantity: data.quantityWithCard,
|
||||||
|
comment: data.optionalText || undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ancillary?.loyaltyCode && data.quantityWithPoints) {
|
||||||
|
packages.push({
|
||||||
|
code: ancillary.loyaltyCode,
|
||||||
|
quantity: data.quantityWithPoints,
|
||||||
|
comment: data.optionalText || undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return packages
|
||||||
|
}
|
||||||
|
|
||||||
const ancillarySessionKey = "ancillarySessionData"
|
const ancillarySessionKey = "ancillarySessionData"
|
||||||
export const getAncillarySessionData = ():
|
export const getAncillarySessionData = ():
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
import { PaymentCallbackStatusEnum } from "@/constants/booking"
|
||||||
|
|
||||||
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
import { trackEvent } from "@/utils/tracking/base"
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildAncillaryPackages,
|
||||||
|
getAncillarySessionData,
|
||||||
|
} from "../../Ancillaries/utils"
|
||||||
|
|
||||||
|
interface TrackGuaranteeProps {
|
||||||
|
status: string
|
||||||
|
isAncillaryFlow?: boolean
|
||||||
|
redirectUrl: string
|
||||||
|
errorMessage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TrackGuarantee({
|
||||||
|
status,
|
||||||
|
isAncillaryFlow = false,
|
||||||
|
redirectUrl,
|
||||||
|
errorMessage,
|
||||||
|
}: TrackGuaranteeProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const trackAncillaryPaymentEvent = (event: string, status: string) => {
|
||||||
|
const sessionData = getAncillarySessionData()
|
||||||
|
const { formData, selectedAncillary } = sessionData || {}
|
||||||
|
|
||||||
|
const packages =
|
||||||
|
formData && selectedAncillary
|
||||||
|
? buildAncillaryPackages(formData, selectedAncillary)
|
||||||
|
: []
|
||||||
|
|
||||||
|
trackEvent({
|
||||||
|
event,
|
||||||
|
paymentInfo: { status },
|
||||||
|
ancillaries: packages.map((pkg) => ({
|
||||||
|
hotelId: selectedAncillary?.hotelId,
|
||||||
|
productId: pkg.code,
|
||||||
|
productUnits: pkg.quantity,
|
||||||
|
productPoints: selectedAncillary?.points,
|
||||||
|
productDeliveryTime: formData?.deliveryTime,
|
||||||
|
productPrice: selectedAncillary?.price,
|
||||||
|
productName: selectedAncillary?.title,
|
||||||
|
productCategory: selectedAncillary?.categoryName,
|
||||||
|
})),
|
||||||
|
lateArrivalGuarantee: "yes",
|
||||||
|
guaranteedProduct: "room + ancillary",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const trackGuaranteePaymentEvent = (event: string, status: string) => {
|
||||||
|
trackEvent({
|
||||||
|
event,
|
||||||
|
hotelInfo: {
|
||||||
|
lateArrivalGuarantee: "yes",
|
||||||
|
guaranteedProduct: "room",
|
||||||
|
},
|
||||||
|
paymentInfo: {
|
||||||
|
status,
|
||||||
|
...(errorMessage && { errorMessage }),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case PaymentCallbackStatusEnum.Success:
|
||||||
|
trackEvent({
|
||||||
|
event: "guaranteeBookingSuccess",
|
||||||
|
hotelInfo: {
|
||||||
|
lateArrivalGuarantee: "yes",
|
||||||
|
guaranteedProduct: "room",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case PaymentCallbackStatusEnum.Cancel:
|
||||||
|
isAncillaryFlow
|
||||||
|
? trackAncillaryPaymentEvent(
|
||||||
|
"GuaranteeCancelAncillary",
|
||||||
|
"glacardsavecancelled"
|
||||||
|
)
|
||||||
|
: trackGuaranteePaymentEvent(
|
||||||
|
"glaCardSaveCancelled",
|
||||||
|
"glacardsavecancelled"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
case PaymentCallbackStatusEnum.Error:
|
||||||
|
isAncillaryFlow
|
||||||
|
? trackAncillaryPaymentEvent(
|
||||||
|
"GuaranteeFailAncillary",
|
||||||
|
"glacardsavefailed"
|
||||||
|
)
|
||||||
|
: trackGuaranteePaymentEvent("glaCardSaveFailed", "glacardsavefailed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
router.replace(redirectUrl)
|
||||||
|
}, [status, isAncillaryFlow, redirectUrl, errorMessage, router])
|
||||||
|
|
||||||
|
return <LoadingSpinner />
|
||||||
|
}
|
||||||
@@ -66,10 +66,11 @@ export default function GuaranteeLateArrival({
|
|||||||
})
|
})
|
||||||
const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang)}`
|
const guaranteeRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}${guaranteeCallback(lang)}`
|
||||||
|
|
||||||
const { guaranteeBooking, isLoading } = useGuaranteeBooking({
|
const { guaranteeBooking, isLoading, handleGuaranteeError } =
|
||||||
confirmationNumber: bookedRoom.confirmationNumber,
|
useGuaranteeBooking({
|
||||||
handleBookingCompleted: router.refresh,
|
confirmationNumber: bookedRoom.confirmationNumber,
|
||||||
})
|
handleBookingCompleted: router.refresh,
|
||||||
|
})
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -83,12 +84,7 @@ export default function GuaranteeLateArrival({
|
|||||||
const savedCreditCard = savedCreditCards?.find(
|
const savedCreditCard = savedCreditCards?.find(
|
||||||
(card) => card.id === data.paymentMethod
|
(card) => card.id === data.paymentMethod
|
||||||
)
|
)
|
||||||
trackGlaSaveCardAttempt(
|
trackGlaSaveCardAttempt(bookedRoom.hotelId, savedCreditCard, "yes")
|
||||||
bookedRoom.hotelId,
|
|
||||||
data.paymentMethod,
|
|
||||||
savedCreditCard,
|
|
||||||
"yes"
|
|
||||||
)
|
|
||||||
if (bookedRoom.confirmationNumber) {
|
if (bookedRoom.confirmationNumber) {
|
||||||
const card = savedCreditCard
|
const card = savedCreditCard
|
||||||
? {
|
? {
|
||||||
@@ -106,6 +102,7 @@ export default function GuaranteeLateArrival({
|
|||||||
cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}`,
|
cancel: `${guaranteeRedirectUrl}?status=cancel&RefId=${encodeURIComponent(refId)}`,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
handleGuaranteeError("No confirmation number")
|
||||||
toast.error(
|
toast.error(
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
id: "Something went wrong!",
|
id: "Something went wrong!",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { trpc } from "@/lib/trpc/client"
|
|||||||
|
|
||||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
|
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
|
||||||
|
import { trackEvent } from "@/utils/tracking/base"
|
||||||
|
|
||||||
const maxRetries = 15
|
const maxRetries = 15
|
||||||
const retryInterval = 2000
|
const retryInterval = 2000
|
||||||
@@ -14,22 +15,39 @@ const retryInterval = 2000
|
|||||||
export function useGuaranteeBooking({
|
export function useGuaranteeBooking({
|
||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
handleBookingCompleted = () => {},
|
handleBookingCompleted = () => {},
|
||||||
|
isAncillaryFlow,
|
||||||
}: {
|
}: {
|
||||||
confirmationNumber: string
|
confirmationNumber: string
|
||||||
handleBookingCompleted?: () => void
|
handleBookingCompleted?: () => void
|
||||||
|
isAncillaryFlow?: boolean
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
|
const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
|
||||||
useState(false)
|
useState(false)
|
||||||
|
|
||||||
const handlePaymentError = useCallback(() => {
|
const handleGuaranteeError = useCallback(
|
||||||
toast.error(
|
(errorMessage?: string) => {
|
||||||
intl.formatMessage({
|
trackEvent({
|
||||||
id: "We had an issue guaranteeing your booking. Please try again.",
|
event: "glaCardSaveFailed",
|
||||||
|
hotelInfo: {
|
||||||
|
lateArrivalGuarantee: "yes",
|
||||||
|
guaranteedProduct: isAncillaryFlow ? "room + ancillary" : "room",
|
||||||
|
},
|
||||||
|
paymentInfo: {
|
||||||
|
status: "glacardsavefailed",
|
||||||
|
errorMessage,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
)
|
toast.error(
|
||||||
}, [intl])
|
intl.formatMessage({
|
||||||
|
id: "We had an issue guaranteeing your booking. Please try again.",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[intl, isAncillaryFlow]
|
||||||
|
)
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
const guaranteeBooking = trpc.booking.guarantee.useMutation({
|
const guaranteeBooking = trpc.booking.guarantee.useMutation({
|
||||||
onSuccess: (result, variables) => {
|
onSuccess: (result, variables) => {
|
||||||
@@ -43,15 +61,11 @@ export function useGuaranteeBooking({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handlePaymentError()
|
handleGuaranteeError()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: (error) => {
|
||||||
toast.error(
|
handleGuaranteeError(error.message)
|
||||||
intl.formatMessage({
|
|
||||||
id: "Something went wrong!",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -68,12 +82,12 @@ export function useGuaranteeBooking({
|
|||||||
router.push(bookingStatus.data.paymentUrl)
|
router.push(bookingStatus.data.paymentUrl)
|
||||||
setIsPollingForBookingStatus(false)
|
setIsPollingForBookingStatus(false)
|
||||||
} else if (bookingStatus.isTimeout) {
|
} else if (bookingStatus.isTimeout) {
|
||||||
handlePaymentError()
|
handleGuaranteeError("Timeout")
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
bookingStatus,
|
bookingStatus,
|
||||||
router,
|
router,
|
||||||
handlePaymentError,
|
handleGuaranteeError,
|
||||||
setIsPollingForBookingStatus,
|
setIsPollingForBookingStatus,
|
||||||
isPollingForBookingStatus,
|
isPollingForBookingStatus,
|
||||||
])
|
])
|
||||||
@@ -84,5 +98,9 @@ export function useGuaranteeBooking({
|
|||||||
!bookingStatus.data?.paymentUrl &&
|
!bookingStatus.data?.paymentUrl &&
|
||||||
!bookingStatus.isTimeout)
|
!bookingStatus.isTimeout)
|
||||||
|
|
||||||
return { guaranteeBooking, isLoading }
|
return {
|
||||||
|
guaranteeBooking,
|
||||||
|
isLoading,
|
||||||
|
handleGuaranteeError,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,7 +525,6 @@ export const bookingMutationRouter = router({
|
|||||||
|
|
||||||
const apiJson = await apiResponse.json()
|
const apiJson = await apiResponse.json()
|
||||||
|
|
||||||
console.log("apiJson", apiJson)
|
|
||||||
const verifiedData = bookingConfirmationSchema.safeParse(apiJson)
|
const verifiedData = bookingConfirmationSchema.safeParse(apiJson)
|
||||||
if (!verifiedData.success) {
|
if (!verifiedData.success) {
|
||||||
updateBookingFailCounter.add(1, {
|
updateBookingFailCounter.add(1, {
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ export const ancillaryPackagesSchema = z
|
|||||||
.object({
|
.object({
|
||||||
data: z.object({
|
data: z.object({
|
||||||
attributes: z.object({
|
attributes: z.object({
|
||||||
|
hotelId: z.number(),
|
||||||
ancillaries: z.array(ancillaryPackageSchema),
|
ancillaries: z.array(ancillaryPackageSchema),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -554,6 +555,7 @@ export const ancillaryPackagesSchema = z
|
|||||||
ancillaryContent: ancillary.ancillaryContent
|
ancillaryContent: ancillary.ancillaryContent
|
||||||
.filter((item) => item.status === "Available")
|
.filter((item) => item.status === "Available")
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
|
hotelId: data.attributes.hotelId,
|
||||||
id: item.id,
|
id: item.id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: item.descriptions.html,
|
description: item.descriptions.html,
|
||||||
@@ -565,6 +567,7 @@ export const ancillaryPackagesSchema = z
|
|||||||
points: item.variants.ancillaryLoyalty?.points,
|
points: item.variants.ancillaryLoyalty?.points,
|
||||||
loyaltyCode: item.variants.ancillaryLoyalty?.code,
|
loyaltyCode: item.variants.ancillaryLoyalty?.code,
|
||||||
requiresDeliveryTime: item.requiresDeliveryTime,
|
requiresDeliveryTime: item.requiresDeliveryTime,
|
||||||
|
categoryName: ancillary.categoryName,
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
.filter((ancillary) => ancillary.ancillaryContent.length > 0)
|
.filter((ancillary) => ancillary.ancillaryContent.length > 0)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PaymentMethodEnum } from "@/constants/booking"
|
|
||||||
|
|
||||||
import { trackEvent } from "./base"
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
import type { SelectedAncillary } from "@/types/components/myPages/myStay/ancillaries"
|
||||||
|
import type { PackageSchema } from "@/types/trpc/routers/booking/confirmation"
|
||||||
import type { CreditCard } from "@/types/user"
|
import type { CreditCard } from "@/types/user"
|
||||||
|
|
||||||
export function trackCancelStay(hotelId: string, bnr: string) {
|
export function trackCancelStay(hotelId: string, bnr: string) {
|
||||||
@@ -27,7 +27,6 @@ type LateArrivalGuarantee = "mandatory" | "yes" | "no" | "na"
|
|||||||
|
|
||||||
export function trackGlaSaveCardAttempt(
|
export function trackGlaSaveCardAttempt(
|
||||||
hotelId: string,
|
hotelId: string,
|
||||||
paymentMethod: string | null,
|
|
||||||
savedCreditCard: CreditCard | undefined,
|
savedCreditCard: CreditCard | undefined,
|
||||||
lateArrivalGuarantee: LateArrivalGuarantee
|
lateArrivalGuarantee: LateArrivalGuarantee
|
||||||
) {
|
) {
|
||||||
@@ -39,10 +38,173 @@ export function trackGlaSaveCardAttempt(
|
|||||||
guaranteedProduct: "room",
|
guaranteedProduct: "room",
|
||||||
},
|
},
|
||||||
paymentInfo: {
|
paymentInfo: {
|
||||||
isCreditCard: paymentMethod === PaymentMethodEnum.card,
|
|
||||||
isSavedCreditCard: !!savedCreditCard,
|
isSavedCreditCard: !!savedCreditCard,
|
||||||
status: "glacardsaveattempt",
|
status: "glacardsaveattempt",
|
||||||
type: savedCreditCard?.cardType,
|
type: savedCreditCard?.cardType,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function trackGlaAncillaryAttempt(
|
||||||
|
savedCreditCard: CreditCard | undefined,
|
||||||
|
packages: {
|
||||||
|
code: string
|
||||||
|
quantity: number
|
||||||
|
comment: string | undefined
|
||||||
|
}[],
|
||||||
|
selectedAncillary: SelectedAncillary | null,
|
||||||
|
deliveryTime: string | undefined
|
||||||
|
) {
|
||||||
|
trackEvent({
|
||||||
|
event: "GuaranteeAttemptAncillary",
|
||||||
|
paymentInfo: {
|
||||||
|
isSavedCreditCard: !!savedCreditCard,
|
||||||
|
status: "glacardsaveattempt",
|
||||||
|
type: savedCreditCard?.cardType,
|
||||||
|
},
|
||||||
|
ancillaries: packages.map((pkg) => ({
|
||||||
|
hotelId: selectedAncillary?.hotelId,
|
||||||
|
productId: pkg.code,
|
||||||
|
productUnits: pkg.quantity,
|
||||||
|
productPoints: selectedAncillary?.points,
|
||||||
|
productDeliveryTime: deliveryTime,
|
||||||
|
productPrice: selectedAncillary?.price,
|
||||||
|
productName: selectedAncillary?.title,
|
||||||
|
productCategory: selectedAncillary?.categoryName,
|
||||||
|
})),
|
||||||
|
lateArrivalGuarantee: "yes",
|
||||||
|
guaranteedProduct: "room + ancillary",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackAncillarySuccess(
|
||||||
|
confirmationNumber: string,
|
||||||
|
packages: {
|
||||||
|
code: string
|
||||||
|
quantity: number
|
||||||
|
comment?: string
|
||||||
|
}[],
|
||||||
|
deliveryTime: string | null | undefined,
|
||||||
|
guaranteedProduct: string,
|
||||||
|
selectedAncillary: SelectedAncillary | null,
|
||||||
|
cardType?: string,
|
||||||
|
roomTypeCode?: string
|
||||||
|
) {
|
||||||
|
trackEvent({
|
||||||
|
event: "AncillarySuccess",
|
||||||
|
hotelInfo: {
|
||||||
|
bnr: confirmationNumber,
|
||||||
|
roomTypeCode: roomTypeCode,
|
||||||
|
},
|
||||||
|
paymentInfo: {
|
||||||
|
status: "glacardsaveconfirmed",
|
||||||
|
type: cardType,
|
||||||
|
},
|
||||||
|
ancillaries: packages.map((pkg) => ({
|
||||||
|
productId: pkg.code,
|
||||||
|
productUnits: pkg.quantity,
|
||||||
|
productPoints: selectedAncillary?.points,
|
||||||
|
productDeliveryTime: deliveryTime,
|
||||||
|
productPrice: selectedAncillary?.price,
|
||||||
|
productName: selectedAncillary?.title,
|
||||||
|
productCategory: selectedAncillary?.categoryName,
|
||||||
|
})),
|
||||||
|
lateArrivalGuarantee: "yes",
|
||||||
|
guaranteedProduct: guaranteedProduct,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackAncillaryFailed(
|
||||||
|
packages: {
|
||||||
|
code: string
|
||||||
|
quantity: number
|
||||||
|
comment?: string
|
||||||
|
}[],
|
||||||
|
deliveryTime: string | null | undefined,
|
||||||
|
selectedAncillary: SelectedAncillary | null
|
||||||
|
) {
|
||||||
|
trackEvent({
|
||||||
|
event: "GuaranteeFailAncillary",
|
||||||
|
ancillaries: packages.map((pkg) => ({
|
||||||
|
productId: pkg.code,
|
||||||
|
productUnits: pkg.quantity,
|
||||||
|
productPoints: selectedAncillary?.points,
|
||||||
|
productDeliveryTime: deliveryTime,
|
||||||
|
productPrice: selectedAncillary?.price,
|
||||||
|
productName: selectedAncillary?.title,
|
||||||
|
productCategory: selectedAncillary?.categoryName,
|
||||||
|
})),
|
||||||
|
lateArrivalGuarantee: "yes",
|
||||||
|
guaranteedProduct: "ancillary",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackViewAncillary(ancillary: SelectedAncillary) {
|
||||||
|
trackEvent({
|
||||||
|
event: "viewAncillary",
|
||||||
|
ancillaries: [
|
||||||
|
{
|
||||||
|
hotelId: ancillary.hotelId,
|
||||||
|
productId: ancillary.id,
|
||||||
|
productName: ancillary.title,
|
||||||
|
productCategory: ancillary.categoryName,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackRemoveAncillary(
|
||||||
|
ancillary: PackageSchema,
|
||||||
|
hotelId: string,
|
||||||
|
deliveryTime?: string
|
||||||
|
) {
|
||||||
|
trackEvent({
|
||||||
|
event: "removeAncillary",
|
||||||
|
ancillaries: [
|
||||||
|
{
|
||||||
|
hotelId,
|
||||||
|
productId: ancillary.code,
|
||||||
|
productPrice: ancillary.totalPrice,
|
||||||
|
productPoints: ancillary.points,
|
||||||
|
productUnits: ancillary.totalUnit,
|
||||||
|
productType: ancillary.type,
|
||||||
|
productDeliveryTime: deliveryTime,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackAddAncillary(
|
||||||
|
ancillary: SelectedAncillary | null,
|
||||||
|
quantityWithCard: number | null,
|
||||||
|
quantityWithPoints: number | null
|
||||||
|
) {
|
||||||
|
const ancillaries = []
|
||||||
|
if ((quantityWithCard ?? 0) > 0) {
|
||||||
|
ancillaries.push({
|
||||||
|
hotelId: ancillary?.hotelId,
|
||||||
|
productId: ancillary?.id,
|
||||||
|
productName: ancillary?.title,
|
||||||
|
productUnits: quantityWithCard,
|
||||||
|
productPrice: ancillary?.price,
|
||||||
|
productPoints: ancillary?.points,
|
||||||
|
productCategory: ancillary?.categoryName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((quantityWithPoints ?? 0) > 0) {
|
||||||
|
ancillaries.push({
|
||||||
|
hotelId: ancillary?.hotelId,
|
||||||
|
productId: ancillary?.loyaltyCode,
|
||||||
|
productName: ancillary?.title,
|
||||||
|
productUnits: quantityWithPoints,
|
||||||
|
productPrice: ancillary?.price,
|
||||||
|
productPoints: ancillary?.points,
|
||||||
|
productCategory: ancillary?.categoryName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
trackEvent({
|
||||||
|
event: "addAncillary",
|
||||||
|
ancillaries,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user