Merged in fix/STAY-73-opt-in-email (pull request #3028)

Fix/STAY-73 opt in email

* fix: let user opt-in for modification email when adding ancillaries

* fix: add toast when successfully removing an ancillary


Approved-by: Erik Tiekstra
Approved-by: Elin Svedin
This commit is contained in:
Christel Westerberg
2025-10-29 12:45:18 +00:00
parent 333636c81a
commit 377c8886ad
8 changed files with 53 additions and 4 deletions

View File

@@ -20,6 +20,8 @@ import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow"
import useLang from "@/hooks/useLang"
import { trackUpdatePaymentMethod } from "@/utils/tracking"
import { ancillaryError } from "../../../schema"
import styles from "./confirmationStep.module.css"
import type { ConfirmationStepProps } from "@/types/components/myPages/myStay/ancillaries"
@@ -219,6 +221,12 @@ export default function ConfirmationStep({
<Checkbox
name="termsAndConditions"
registerOptions={{ required: true }}
errorCodeMessages={{
[ancillaryError.TERMS_NOT_ACCEPTED]: intl.formatMessage({
id: "common.mustAcceptTermsError",
defaultMessage: "You must accept the terms and conditions",
}),
}}
>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>
@@ -229,6 +237,17 @@ export default function ConfirmationStep({
</span>
</Typography>
</Checkbox>
<Checkbox name="emailOptOut">
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>
{intl.formatMessage({
id: "addAncillary.confirmationStep.optInForEmail",
defaultMessage:
"I want to receive an updated booking confirmation email reflecting this add-on",
})}
</span>
</Typography>
</Checkbox>
</div>
</div>
)

View File

@@ -97,6 +97,7 @@ export default function AddAncillaryFlowModal({
deliveryTime: defaultDeliveryTime,
optionalText: "",
termsAndConditions: false,
optInEmail: false,
paymentMethod: booking.guaranteeInfo
? PaymentMethodEnum.card
: savedCreditCards?.length
@@ -152,6 +153,7 @@ export default function AddAncillaryFlowModal({
ancillaryDeliveryTime: selectedAncillary?.requiresDeliveryTime
? data.deliveryTime
: undefined,
emailOptOut: !data.optInEmail,
packages: packages,
language: lang,
},

View File

@@ -7,6 +7,11 @@ const quantitySchemaWithoutRefine = z.object({
quantityWithCard: z.number().nullable(),
})
export const ancillaryError = {
TERMS_NOT_ACCEPTED: "TERMS_NOT_ACCEPTED",
MIN_QUANTITY_NOT_REACHED: "MIN_QUANTITY_NOT_REACHED",
} as const
export const quantitySchema = z
.object({})
.merge(quantitySchemaWithoutRefine)
@@ -14,7 +19,7 @@ export const quantitySchema = z
(data) =>
(data.quantityWithPoints ?? 0) > 0 || (data.quantityWithCard ?? 0) > 0,
{
message: "You must select at least one quantity",
message: ancillaryError.MIN_QUANTITY_NOT_REACHED,
path: ["quantityWithCard"],
}
)
@@ -23,7 +28,10 @@ export const ancillaryFormSchema = z
.object({
deliveryTime: z.string(),
optionalText: z.string(),
termsAndConditions: z.boolean(),
termsAndConditions: z
.boolean()
.refine((value) => value === true, ancillaryError.TERMS_NOT_ACCEPTED),
optInEmail: z.boolean(),
paymentMethod: nullableStringValidator,
})
.merge(quantitySchemaWithoutRefine)
@@ -31,7 +39,7 @@ export const ancillaryFormSchema = z
(data) =>
(data.quantityWithPoints ?? 0) > 0 || (data.quantityWithCard ?? 0) > 0,
{
message: "You must select at least one quantity",
message: ancillaryError.MIN_QUANTITY_NOT_REACHED,
path: ["quantityWithCard"],
}
)

View File

@@ -76,6 +76,13 @@ export default function RemoveButton({
if (!data) {
throw new Error()
}
toast.success(
intl.formatMessage({
id: "myStay.removeAncillary.success",
defaultMessage:
"The product has now been removed from your booking.",
})
)
utils.booking.get.invalidate({
refId: refId,
})

View File

@@ -56,6 +56,7 @@ export default function GuaranteeAncillaryHandler({
ancillaryDeliveryTime: selectedAncillary.requiresDeliveryTime
? formData.deliveryTime
: undefined,
emailOptOut: !formData.optInEmail,
packages,
language: lang,
},

View File

@@ -8,11 +8,22 @@ import { signupErrors } from "@scandic-hotels/trpc/routers/user/schemas"
import { editProfileErrors } from "@/components/Forms/Edit/Profile/schema"
import { findMyBookingErrors } from "@/components/HotelReservation/FindMyBooking/schema"
import { ancillaryError } from "@/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/schema"
import type { IntlShape } from "react-intl"
export function getErrorMessage(intl: IntlShape, errorCode?: string) {
switch (errorCode) {
case ancillaryError.MIN_QUANTITY_NOT_REACHED:
return intl.formatMessage({
id: "addAncillary.selectQuantityStep.minQuantityNotReached",
defaultMessage: "You must select at least one quantity",
})
case ancillaryError.TERMS_NOT_ACCEPTED:
return intl.formatMessage({
id: "addAncillary.confirmationStep.termsAndConditionsNotice",
defaultMessage: "You must accept the terms and conditions to proceed",
})
case findMyBookingErrors.BOOKING_NUMBER_INVALID:
return intl.formatMessage({
id: "error.invalidBookingNumber",

View File

@@ -14,6 +14,7 @@ export const addPackageInput = z.object({
comment: z.string().optional(),
})
),
emailOptOut: z.boolean(),
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
})

View File

@@ -145,7 +145,7 @@ export const bookingMutationRouter = router({
headers,
body: body,
},
{ language }
{ language, emailOptOut: input.emailOptOut }
)
if (!apiResponse.ok) {