diff --git a/src/constants/analytics.js b/src/constants/analytics.js index 00088baa7..e156de11b 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -15,6 +15,7 @@ const GaCategories = { APPOINTMENT: "Appointment", SERVICE_LOCATION: "service-location", CONFIRMATION_CLICKED: "confirmation clicked", + RECAL_DISCLAIMER: "Recalibration disclaimer", }; const GaActions = { @@ -29,6 +30,7 @@ const GaActions = { MORE_LOCATIONS_CLICKED: "more_locations_clicked", YES: "yes", NO: "no", + ACKNOWLEDGED: "Acknowledged", }; const GaLabels = { diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js index 2b626df18..2b6171075 100644 --- a/src/constants/schedule-constants.js +++ b/src/constants/schedule-constants.js @@ -11,6 +11,8 @@ const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN"; +const RECAL_ACK_YES = "Yes"; + const RouteCodeFlags = { ALL_DAY_DROP_OFF: "ALL DAY DROP OFF", OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF", @@ -31,4 +33,5 @@ export { PRICING_BY_DAY_PART_TYPE, RouteCodeFlags, cmsWidgetFieldMappings, + RECAL_ACK_YES, }; diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 2fc0c88eb..3afd4df24 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -111,6 +111,7 @@ const storeActions = { SAVE_PAYPAL_TOKEN: "savePaypalToken", SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount", SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn", + SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling", SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected", SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable", diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 5167581e0..700f527c2 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -69,6 +69,7 @@ const storeMutations = { LOCK_TOKEN: "updateLockToken", UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount", UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn", + UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "updateIsRecalAcknowledgedForScheduling", UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected", UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable", UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal", diff --git a/src/helpers/recal-helper.js b/src/helpers/recal-helper.js index 2794bbcf6..2a3281185 100644 --- a/src/helpers/recal-helper.js +++ b/src/helpers/recal-helper.js @@ -3,6 +3,14 @@ import { partTypeStrings } from "@/constants/part-type-strings"; const recalPartTypes = [partTypeStrings.RECALIBRATION, partTypeStrings.ADAS_RECALIBRATION]; +export function anyPartWithRequiresRecalFlag(lineItems) { + if (!lineItems) { + return false; + } + + return lineItems.glassParts?.some((li) => li.requiresRecalibration === true); +} + export function isRecalPartOrHasChildRecalPart(lineItem) { if (lineItem.childParts && lineItem.childParts.length > 0) { return isRecalPart(lineItem) || containsRecalParts(lineItem.childParts); diff --git a/src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue b/src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue new file mode 100644 index 000000000..e1c823cfe --- /dev/null +++ b/src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue @@ -0,0 +1,223 @@ +// This modal is used to acknowledge the recal disclaimer during scheduling. // It will trigger when +there is a glass part with requiresRecalibration = true // but there is no recalibration part in +lineItems. This mostly occurs when // a zip code that does mobile only is selected but the vehicle +requires // recalibration. + + + + diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 87b542b80..9b4187053 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -6,6 +6,12 @@ modalWidgetName="MobileFirstModalWidget" ref="mobileFirstModal" @confirm-appointment="updateTimeSlotandNavigateForward" /> +
@@ -190,6 +196,7 @@ import shopLocation from "@/layouts/schedule/shop-location/shop-location"; import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue"; import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue"; import mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-modal.vue"; +import recalAckModal from "@/layouts/schedule/recal-ack-modal/recal-ack-modal.vue"; import shopQuestion from "@/layouts/service-location/shop-question/shop-question"; import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup"; @@ -216,6 +223,7 @@ import { RouteCodeFlags, PREMIUM_FEE_PART_TYPE, PRICING_BY_DAY_PART_TYPE, + RECAL_ACK_YES, } from "@/constants/schedule-constants"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; @@ -242,6 +250,7 @@ import { isDropOffRouteCode, getTodayDate, } from "@/layouts/schedule/helpers/schedule-helper"; +import { containsRecalParts, anyPartWithRequiresRecalFlag } from "@/helpers/recal-helper"; import { DAYS_OF_WEEK } from "@/digital-components/date-picker/mixins/constants"; import { required } from "@/helpers/validation-rules"; @@ -430,6 +439,7 @@ export default { name: "schedule", data() { return { + isRecalAcknowledgedForScheduling: this.getIsRecalAcknowledgedForSchedulingFromStore(), selectedDate: this.getSelectedDateFromStore(), selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(), mobilePremiumAppointmentFee: null, @@ -1233,6 +1243,9 @@ export default { this.isShowMobileFirstAppt && this.hideLoadingModal(); }, + getIsRecalAcknowledgedForSchedulingFromStore() { + return store.getters.order.isRecalAcknowledgedForScheduling; + }, getScheduleApiResponse, getServiceZipCtuCodeFromStore() { return store.getters.order.serviceLocation.zipCodeCtu; @@ -1351,7 +1364,25 @@ export default { hideLoadingModal() { this.$refs.loadingModal.hideModal(); }, + async closeRecalAcknowledgementPopup() { + this.showRecalAcknowledgementPopup = false; + }, async forwardButtonAction() { + const showRecalAcknowledgementModal = await this.showRecalAcknowledgementModal(); + if ( + showRecalAcknowledgementModal && + this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES + ) { + this.showRecalAcknowledgementPopup = true; + return; + } + + this.dispatchStoreAction( + storeActions.SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING, + this.isRecalAcknowledgedForScheduling, + false + ); + this.navigatingForward = true; var ctu = this.zipCodeCtu; if (this.appointmentType === AppointmentTypeStrings.MOBILE) { @@ -1752,6 +1783,26 @@ export default { setSelectedDateToFirstAvailable() { this.selectedDate = this.getFirstAvailableDate(); }, + async showRecalAcknowledgementModal() { + const order = this.$store.getters.order; + const containsRecalPart = containsRecalParts(order.lineItems); + const hasPartWithRecalRequirement = anyPartWithRequiresRecalFlag(order.lineItems); + + // if any of the glass parts contain an item with the requiresRecalibration flag set to true + // and the order does not contain a recalibration part, show the recalibration acknowledgement modal + if ( + !containsRecalPart && + hasPartWithRecalRequirement && + this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES + ) { + this.$refs.navbar.removeLoader(); + this.$refs.recalAckModal.openModal(); + return true; + } + + this.closeRecalAcknowledgementPopup(); + return false; + }, async showMobileFirstModal() { if (!this.selectedRouteCodeData?.routeCode) { const pmMobileDays = experimentMixin.methods.getSettingValue( @@ -1875,6 +1926,10 @@ export default { this.updateTimeSlot(timeSlotObj); this.forwardButtonAction(); }, + updateRecalAcknowledgedAndNavigateForward(isAcknowledged) { + this.isRecalAcknowledgedForScheduling = isAcknowledged; + this.forwardButtonAction(); + }, }, watch: { appointmentTypeFromAppointmentTypeQuestion: { @@ -1920,7 +1975,7 @@ export default { textBlock, timeSlotQuestion, mobileFirstModal, - + recalAckModal, alert, serviceZipModalQuestion, appointmentTypeQuestion, diff --git a/src/store/index.js b/src/store/index.js index 8faf39f73..e9b379b56 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -190,6 +190,7 @@ const getDefaultState = () => { lockToken: null, settledTenderAmount: 0, isRecalAckOptIn: false, + isRecalAcknowledgedForScheduling: "", isMSRFeeApplicable: false, }, applicationUser: { @@ -343,6 +344,9 @@ export const mutations = { updateIsRecalAckOptIn(state, isRecalAckOptIn) { state.order.isRecalAckOptIn = isRecalAckOptIn; }, + updateIsRecalAcknowledgedForScheduling(state, isRecalAcknowledgedForScheduling) { + state.order.isRecalAcknowledgedForScheduling = isRecalAcknowledgedForScheduling; + }, updateIsOemGlassSelected(state, isOemGlassSelected) { state.order.damage.installOemGlass = isOemGlassSelected; }, @@ -2965,6 +2969,13 @@ export const actions = { context.commit(storeMutations.UPDATE_IS_RECAL_ACK_OPT_IN, isRecalAckOptIn); }, + saveIsRecalAcknowledgedForScheduling(context, isRecalAcknowledgedForScheduling) { + context.commit( + storeMutations.UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING, + isRecalAcknowledgedForScheduling + ); + }, + saveIsOemGlassSelected(context, isOemGlassSelected) { context.commit(storeMutations.UPDATE_IS_OEM_GLASS_SELECTED, isOemGlassSelected); }, diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss index 0c275ad51..76035c6ca 100644 --- a/src/styles/ux-variables.scss +++ b/src/styles/ux-variables.scss @@ -27,6 +27,8 @@ $red-600: #ac160b; $red-700: #840900; $red-800: #5b0600; $red-900: #330300; +$red-1000: #a80019; +$red-1100: #791710; // Greens $green-100: #e3f2ea;