From 6699c49eee21d4ed7e49ca5ad2d683abd194b4f1 Mon Sep 17 00:00:00 2001 From: credelinghuys Date: Fri, 27 Feb 2026 10:15:23 -0500 Subject: [PATCH 1/7] CASH-1684: MSR fee not covered by insurance alert --- src/assets/img/icons/alert-circle-yellow.svg | 3 ++ src/helpers/cms-content-helper.js | 11 +++++ src/layouts/schedule/schedule.vue | 42 ++++++++++++++++++-- src/ux-components/alert/alert.vue | 21 ++++++++++ 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/assets/img/icons/alert-circle-yellow.svg diff --git a/src/assets/img/icons/alert-circle-yellow.svg b/src/assets/img/icons/alert-circle-yellow.svg new file mode 100644 index 000000000..904aefb98 --- /dev/null +++ b/src/assets/img/icons/alert-circle-yellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index 727add296..450875d0f 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -343,3 +343,14 @@ export function splitCMSCopyOnParagraphTag(copy) { export function splitCMSCopyOnBR(copy) { return copy.split("
"); } + +// This function takes in a number and returns it formatted as USD currency, with or without cents depending on if the number is an integer or not. +export function formatToUSDollar(amount) { + const isInteger = amount % 1 === 0; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: isInteger ? 0 : 2, + maximumFractionDigits: 2, + }).format(amount); +} diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index adc85ef2a..07330b5d6 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -74,6 +74,16 @@ cmsWidgetName="AlertNoShopsWidget" v-if="displayNoShopsAlert" alertClass="alert-warning" /> +

Info Icon + Warning Icon {{ alertHeadline }}


@@ -63,7 +69,9 @@ export default { name: "alert", props: { showInfoIcon: Boolean, + showWarningIcon: Boolean, showHorizontalRow: Boolean, + hasBorder: Boolean, isDismissible: Boolean, alertClass: String, /* @@ -96,6 +104,9 @@ export default { infoIcon() { return require(`@/assets/img/icons/info-circle-blue.svg`); }, + warningIcon() { + return require(`@/assets/img/icons/alert-circle-yellow.svg`); + }, pageQueryString() { return applicationConfig.PAGE_QUERYSTRING; }, @@ -211,12 +222,19 @@ export default { background-color: $yellow-100; .alert-heading { color: $yellow-600; + + .warning-icon { + height: 1rem; + } } svg { fill: $yellow-600; width: 1rem; height: 1rem; } + hr { + border-color: $yellow-800; + } } &.alert-success { background-color: $green-100; @@ -229,6 +247,9 @@ export default { height: 1rem; } } + &.bordered { + border: 1px solid; + } & p { font-size: 0.875rem; } From 664a7d289d7d7e612dc9eb21c47187496066e9a4 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 4 Mar 2026 07:59:51 -0500 Subject: [PATCH 2/7] CASH-2378: add experiment constants --- src/constants/experiments.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/constants/experiments.js b/src/constants/experiments.js index c76b1ea1c..f8691fc46 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -5,6 +5,7 @@ const experimentUniverses = { IGQ_SkipQuote: "NextGen_IGQSkipToInsurance", AFTERPAY_BREAKOUT_DISPLAY: "AfterpayBreakoutDisplay", MOBILE_FIRST_APPOINTMENT: "MobileFirstAppointment", + MULTI_LOCATION_POPUP: "MultiLocationPopup", }; const experimentSettings = { @@ -40,6 +41,10 @@ const experimentSettings = { SHOW_MOBILE_FIRST_POPUP_REPLACE_WITHOUT_MSR: "Show_Mobile_First_Popup_Replace_Without_MSR", SHOW_MOBILE_FIRST_POPUP_REPLACE_WITH_MSR: "Show_Mobile_First_Popup_Replace_With_MSR", OFFER_OEM: "OfferOem", + SHOW_MULTI_LOCATION_APPT: "ShowMultiLocationAppt", + SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION: "Show_NoAvailableDaysMultiLocation", + SHOW_NO_PM_DAYS_MULTI_LOCATION: "Show_NoPMDaysMultiLocation", + SHOW_PM_DAYS_MULTI_LOCATION: "Show_PMDaysMultiLocation", USE_ADYEN_PAYMENT: "UseAdyenPayment", SHOW_UNVERIFIED_DEDUCT_ENTRY: "ShowUnverifiedDeducEntry", SERVICE_PACKAGE_ORDER: "ServicePackageOrder", From 0c32eecdccf47ae54fd6852c0430a9d456cf8e69 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 4 Mar 2026 08:00:39 -0500 Subject: [PATCH 3/7] CASH-2378: create new radio button for multi location modal usage --- .../button-question/button-question.vue | 3 + .../multi-location-radio.spec.js | 121 +++++++ .../multi-location-radio.vue | 309 ++++++++++++++++++ 3 files changed, 433 insertions(+) create mode 100644 src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.spec.js create mode 100644 src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.vue diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index 9862633ad..417c88809 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -226,6 +226,7 @@ export default { let classes; switch (this.buttonTypeString) { case "timeSlotModalListButton": + case "multiLocationRadioButton": case "listButton": classes = "w-100"; break; @@ -265,6 +266,8 @@ export default { classes += " radio-button-container"; } else if (this.buttonTypeString == "servicePackageRadio") { classes = "package-wrapper col"; + } else if (this.buttonTypeString == "multiLocationRadioButton") { + classes = "multi-location-wrapper col"; } if (this.buttonTypeString == "listCard" && this.buttonsInfo.length > 2) { diff --git a/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.spec.js b/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.spec.js new file mode 100644 index 000000000..f305d4e2e --- /dev/null +++ b/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.spec.js @@ -0,0 +1,121 @@ +import { mount } from "@vue/test-utils"; +import multiLocationRadio from "./multi-location-radio"; +import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; + +describe("multi-location-radio.vue", () => { + it("Should include buttonLabel in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"])); + }); + + it("Should include buttonLabelSubCopy in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"])); + }); + it("Should include buttonFooterCopy in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonFooterCopy"])); + }); + it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a
  • ...
  • (x5)
buttonBodyCopy", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + // Act + const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy); + + // Assert + expect(results.length).toBe(5); + }); + it("Should get strings from getArrayOfListItemsFromRawCmsCopy without
    or
  • tags when provided with a
    • ...
    buttonBodyCopy", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy); + + // Assert + const fileredResults = results.filter((result) => { + return ( + result.includes("
      ") || + result.includes("
    ") || + result.includes("
  • ") || + result.includes("
  • ") + ); + }); + expect(fileredResults.length).toBe(0); + }); + it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when buttonBodyCopy has 6 total
  • ...
  • , but one is empty ", async () => { + // Arrange + const moddedProps = mockProps; + moddedProps["buttonBodyCopy"] = + "
    • buttonBodyCopy test copy
    • 2
    • 3
    • 4
    • 5
    "; + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: moddedProps, + }, + }); + + // Act + const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy); + + // Assert + expect(results.length).toBe(5); + }); +}); + +const mockProps = { + groupName: "group-name", + buttonLabel: "buttonLabel test copy", + buttonLabelAuxillaryCopy: "buttonLabelAuxillaryCopy test copy", + buttonLabelSubCopy: "buttonLabelSubCopy test copy", + buttonBodyCopy: + "
    • buttonBodyCopy test copy
    • 2
    • 3
    • 4
    • 5
    ", + buttonFooterCopy: "buttonFooterCopy test copy", + buttonAuxillaryCopy: "buttonAuxillaryCopy test copy", +}; + +function setupMocks({ mountOptionsMockData = {} }) { + const wrapper = mount(multiLocationRadio, { + ...mountOptionsMockData, + mixins: [inputButtonWrapperMixin], + }); + + return { wrapper }; +} diff --git a/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.vue b/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.vue new file mode 100644 index 000000000..64c8e3ce2 --- /dev/null +++ b/src/layouts/schedule/multi-location-modal/multi-location-radio/multi-location-radio.vue @@ -0,0 +1,309 @@ + + + + + From b3fb548813eddfdebc96e1569f033ddc71fd3c35 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 4 Mar 2026 08:01:13 -0500 Subject: [PATCH 4/7] CASH-2378: new component for multi location modal --- .../multi-location-modal.vue | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 src/layouts/schedule/multi-location-modal/multi-location-modal.vue diff --git a/src/layouts/schedule/multi-location-modal/multi-location-modal.vue b/src/layouts/schedule/multi-location-modal/multi-location-modal.vue new file mode 100644 index 000000000..78dbe6ba5 --- /dev/null +++ b/src/layouts/schedule/multi-location-modal/multi-location-modal.vue @@ -0,0 +1,317 @@ + + + + From e5aa47673b3f55df137291819e4d9b3bd8fcc7c7 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 4 Mar 2026 08:05:52 -0500 Subject: [PATCH 5/7] CASH-2378: updates to mobile first modal (renaming, change in helper method) --- src/layouts/schedule/schedule.vue | 54 +++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 07330b5d6..e1dd86e06 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -5,7 +5,7 @@ + @confirm-appointment="updateMobileFirstTimeSlotandNavigateForward" /> 0; } }, + isShowMobileFirstAppt() { + return experimentMixin.methods.hasSettingEqualTo( + experimentSettings.SHOW_MOBILE_FIRST_APPT, + "true" + ); + }, }, methods: { splitCopyOnCMSPlaceHolder, @@ -1934,10 +1936,10 @@ export default { const order = this.$store.getters.order; const isRepair = order.damage.isRepair; - let preSelectedMobileAppointment = null; + let promotedMobileAppointment = null; const todaysDate = getTodayDate(); - let firstMobileAMAppt = await this.getFirstAvailableMobileApptByTOD("AM"); - let firstMobilePMAppt = await this.getFirstAvailableMobileApptByTOD("PM"); + let firstMobileAMAppt = await this.getFirstAvailableApptByTOD("AM", "mobile"); + let firstMobilePMAppt = await this.getFirstAvailableApptByTOD("PM", "mobile"); if (!firstMobileAMAppt && !firstMobilePMAppt) { return; } @@ -2032,7 +2034,7 @@ export default { if (this.isShowMobileFirstAppt) { if (pmMobileDays == 0) { // Selects the first available time slot - preSelectedMobileAppointment = + promotedMobileAppointment = numberOfDaysToFirstMobileAMDate <= numberOfDaysToFirstMobilePMDate ? firstMobileAMAppt : firstMobilePMAppt; @@ -2041,20 +2043,20 @@ export default { numberOfDaysToFirstMobilePMDate <= pmMobileDays ) { // Selects the first available PM time slot - preSelectedMobileAppointment = firstMobilePMAppt; + promotedMobileAppointment = firstMobilePMAppt; } else if ( firstMobilePMDate && numberOfDaysToFirstMobilePMDate >= noPMMobileDays ) { // Selects the first available AM time slot. If none, selects the first available PM time slot - preSelectedMobileAppointment = firstMobileAMAppt + promotedMobileAppointment = firstMobileAMAppt ? firstMobileAMAppt : firstMobilePMAppt; } - if (preSelectedMobileAppointment) { + if (promotedMobileAppointment) { this.$refs.mobileFirstModal.setSelectedAppointment( - preSelectedMobileAppointment + promotedMobileAppointment ); this.$refs.mobileFirstModal.openModal(); } @@ -2062,11 +2064,19 @@ export default { } } }, - async getFirstAvailableMobileApptByTOD(timeOfDay) { - if (!this.selectableDatesMobile?.days?.length) { + async getFirstAvailableApptByTOD(timeOfDay, appointmentType) { + let selectableDays; + if (appointmentType?.toLowerCase() == "mobile") { + selectableDays = this.selectableDatesMobile?.days; + } else if (appointmentType?.toLowerCase() == "inshop") { + selectableDays = this.selectableDatesInshop?.days; + } else { + return; + } + if (!selectableDays?.length) { return null; } else { - for (const dateObj of this.selectableDatesMobile.days) { + for (const dateObj of selectableDays) { let matchingTimeSlot = { estimatedServiceMinutes: { minimum: this.estimatedServiceMinutesMinimum, @@ -2081,6 +2091,16 @@ export default { (matchingTimeSlot.timeSlot = slot) && (matchingTimeSlot.date = dateObj.date) ); + if ( + appointmentType?.toLowerCase() == "inshop" && + this.selectedShopAnswer?.address1 && + this.selectedShopAnswer?.address2 + ) { + matchingTimeSlot.addressCopy = + this.selectedShopAnswer?.address1 + + ", " + + this.selectedShopAnswer?.address2; + } if (isMatchingApptDay && matchingTimeSlot) { return matchingTimeSlot; } @@ -2088,7 +2108,7 @@ export default { return null; } }, - updateTimeSlotandNavigateForward(timeSlotObj) { + updateMobileFirstTimeSlotandNavigateForward(timeSlotObj) { this.selectedMobileFirstAppointment = true; this.appointmentType = AppointmentTypeStrings.MOBILE; this.selectedDate = timeSlotObj.date; From 364b5d29bf961af42e4371632941064a2591ba97 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 4 Mar 2026 08:06:27 -0500 Subject: [PATCH 6/7] CASH-2378: implement new multi location modal --- src/layouts/schedule/schedule.vue | 198 +++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 5 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index e1dd86e06..dbdaaa90f 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -6,6 +6,11 @@ modalWidgetName="MobileFirstModalWidget" ref="mobileFirstModal" @confirm-appointment="updateMobileFirstTimeSlotandNavigateForward" /> +
    + :class="[ + isMobileFirstModalOpen || isMultiLocationModalOpen ? 'hidden-background' : '', + ]">
    { - vm.showMobileFirstModal(); + // vm.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST + vm.showMultiLocationModal(); vm.hideLoadingModal(); vm.calendarLoadingStatus = "none"; if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable(); @@ -930,6 +940,11 @@ export default { ? this.$refs.mobileFirstModal?.getIsModalOpen() : false; }, + isMultiLocationModalOpen() { + return this.selectableDatesMobile.days && this.selectableDatesInshop.days + ? this.$refs.multiLocationModal?.getIsModalOpen() + : false; + }, displayWaitList() { if (!this.appointmentType) return false; const firstMobileDateString = @@ -990,6 +1005,12 @@ export default { "true" ); }, + isShowMultiLocationPopup() { + return experimentMixin.methods.hasSettingEqualTo( + experimentSettings.SHOW_MULTI_LOCATION_APPT, + "true" + ); + }, }, methods: { splitCopyOnCMSPlaceHolder, @@ -1275,7 +1296,8 @@ export default { } } this.initializeDatePicker().then(() => { - this.showMobileFirstModal(); + // this.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST + this.showMultiLocationModal(); this.hideLoadingModal(); this.calendarLoadingStatus = "none"; this.setSelectedDateToFirstAvailable(); @@ -1309,7 +1331,7 @@ export default { }, async initializeDatePicker() { - this.isShowMobileFirstAppt && this.showLoadingModal(); + this.isShowMultiLocationPopup && this.showLoadingModal(); const includeMobileTimeSlots = this.isServiceableMobile; const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff; @@ -1821,7 +1843,8 @@ export default { this.zipCodeCtu = newZipCode.zipCodeCtu; this.selectedDate = null; this.initializeDatePicker().then(() => { - this.showMobileFirstModal(); + // this.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST + this.showMultiLocationModal(); this.hideLoadingModal(); this.calendarLoadingStatus = "none"; this.setSelectedDateToFirstAvailable(); @@ -2064,6 +2087,157 @@ export default { } } }, + async showMultiLocationModal() { + if (!this.selectedRouteCodeData?.routeCode) { + let maxDayRangeToShowPmTimeslot = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_PM_DAYS_MULTI_LOCATION + ); + let maxDayRangeToShowNoPmTimeslot = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_NO_PM_DAYS_MULTI_LOCATION + ); + let maxDayRangeToShowAnyTimeslot = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION + ); + let showMultiLocationAppointment = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_MULTI_LOCATION_APPT + ); + + // temp vars to use for testing; TO BE REMOVED + maxDayRangeToShowPmTimeslot = 2; + maxDayRangeToShowNoPmTimeslot = 2; + maxDayRangeToShowAnyTimeslot = 5; + + let promotedMobileAppointment = null; + let promotedInshopAppointment = null; + const todaysDate = getTodayDate(); + + // Helper function to calculate days until appointment (handles null/undefined) + const calculateDaysUntilDate = (appointmentObj) => { + const dateString = appointmentObj?.date; + return dateString + ? (new Date(dateString) - todaysDate) / (1000 * 60 * 60 * 24) + : null; + }; + + // Fetch all appointments + const [firstMobileAMAppt, firstMobilePMAppt, firstInshopAMAppt, firstInshopPMAppt] = + await Promise.all([ + this.getFirstAvailableApptByTOD("AM", "mobile"), + this.getFirstAvailableApptByTOD("PM", "mobile"), + this.getFirstAvailableApptByTOD("AM", "inshop"), + this.getFirstAvailableApptByTOD("PM", "inshop"), + ]); + + // Extract dates + const firstMobileAMDate = firstMobileAMAppt?.date; + const firstMobilePMDate = firstMobilePMAppt?.date; + const firstInshopAMDate = firstInshopAMAppt?.date; + const firstInshopPMDate = firstInshopPMAppt?.date; + + // Calculate days until appointments + const numberOfDaysToFirstMobileAMDate = calculateDaysUntilDate(firstMobileAMAppt); + const numberOfDaysToFirstMobilePMDate = calculateDaysUntilDate(firstMobilePMAppt); + const numberOfDaysToFirstInshopAMDate = calculateDaysUntilDate(firstInshopAMAppt); + const numberOfDaysToFirstInshopPMDate = calculateDaysUntilDate(firstInshopPMAppt); + + const shouldExposeMultiLocationModal = () => { + const isMobileAppointmentInDateRange = + (firstMobileAMDate && + numberOfDaysToFirstMobileAMDate <= maxDayRangeToShowAnyTimeslot) || + (firstMobilePMDate && + numberOfDaysToFirstMobilePMDate <= maxDayRangeToShowAnyTimeslot); + const isInshopAppointmentInDateRange = + (firstInshopAMDate && + numberOfDaysToFirstInshopAMDate <= maxDayRangeToShowAnyTimeslot) || + (firstInshopPMDate && + numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowAnyTimeslot); + + if (showMultiLocationAppointment) { + return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange; + } + return false; + }; + + if (shouldExposeMultiLocationModal()) { + const multiLocationPopupExperiment = + store.getters.applicationUser.experiments.find( + (e) => e.universeName === experimentUniverses.MULTI_LOCATION_POPUP + ); + const hasExposedMultiLocationPopup = multiLocationPopupExperiment?.isExposed; + + if (!hasExposedMultiLocationPopup && multiLocationPopupExperiment) { + baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE, + { + userId: getUserIdValue(), + deviceId: getDeviceIdValue(), + sessionKey: getSessionKeyValue(), + pageName: "schedule", + experiment: multiLocationPopupExperiment, + }, + "schedule", + false + ); + } + if (this.isShowMultiLocationPopup) { + if (maxDayRangeToShowPmTimeslot == 0) { + // Selects the first available mobile time slot + promotedMobileAppointment = + numberOfDaysToFirstMobileAMDate <= numberOfDaysToFirstMobilePMDate + ? firstMobileAMAppt + : firstMobilePMAppt; + // Selects the first available inshop time slot + promotedInshopAppointment = + numberOfDaysToFirstInshopAMDate <= numberOfDaysToFirstInshopPMDate + ? firstInshopAMAppt + : firstInshopPMAppt; + } else { + if ( + firstMobilePMDate && + numberOfDaysToFirstMobilePMDate <= maxDayRangeToShowPmTimeslot + ) { + // Selects the first available PM time slot + promotedMobileAppointment = firstMobilePMAppt; + } else if ( + firstMobilePMDate && + numberOfDaysToFirstMobilePMDate >= maxDayRangeToShowNoPmTimeslot + ) { + // Selects the first available AM time slot. If none, selects the first available PM time slot + promotedMobileAppointment = firstMobileAMAppt + ? firstMobileAMAppt + : firstMobilePMAppt; + } + + if ( + firstInshopPMDate && + numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowPmTimeslot + ) { + // Selects the first available PM time slot + promotedInshopAppointment = firstInshopPMAppt; + } else if ( + firstInshopPMDate && + numberOfDaysToFirstInshopPMDate >= maxDayRangeToShowNoPmTimeslot + ) { + // Selects the first available AM time slot. If none, selects the first available PM time slot + promotedInshopAppointment = firstInshopAMAppt + ? firstInshopAMAppt + : firstInshopPMAppt; + } + } + + if (promotedMobileAppointment && promotedInshopAppointment) { + this.$refs.multiLocationModal.setPromotedMobileAppointment( + promotedMobileAppointment + ); + this.$refs.multiLocationModal.setPromotedInshopAppointment( + promotedInshopAppointment + ); + this.$refs.multiLocationModal.openModal(); + } + } + } + } + }, async getFirstAvailableApptByTOD(timeOfDay, appointmentType) { let selectableDays; if (appointmentType?.toLowerCase() == "mobile") { @@ -2084,6 +2258,7 @@ export default { }, timeSlot: null, date: null, + addressCopy: null, }; const isMatchingApptDay = dateObj.timeSlots.some( (slot) => @@ -2116,6 +2291,18 @@ export default { this.updateTimeSlot(timeSlotObj); this.forwardButtonAction(); }, + updateMultiLocationTimeSlotandNavigateForward({ selectedTimeSlot, appointmentType }) { + if (appointmentType?.toLowerCase() == "mobile") { + this.appointmentType = AppointmentTypeStrings.MOBILE; + } else if (appointmentType?.toLowerCase() == "inshop") { + this.appointmentType = AppointmentTypeStrings.IN_SHOP; + } + this.selectedMultiLocationAppointment = true; + this.selectedDate = selectedTimeSlot.date; + this.updateSelectedProvider(); + this.updateTimeSlot(selectedTimeSlot); + this.forwardButtonAction(); + }, updateRecalAcknowledgedAndNavigateForward(isAcknowledged) { this.isRecalAcknowledgedForScheduling = isAcknowledged; this.forwardButtonAction(); @@ -2139,6 +2326,7 @@ export default { textBlock, timeSlotQuestion, mobileFirstModal, + multiLocationModal, recalAckModal, alert, serviceZipModalQuestion, From e5b00fde3ed6239f4b232caf9ed3a3d7474f011c Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 4 Mar 2026 08:41:38 -0500 Subject: [PATCH 7/7] CASH-2378: implement copilot PR review suggestions (better guards, removing superfluous emit) --- .../schedule/multi-location-modal/multi-location-modal.vue | 3 +-- src/layouts/schedule/schedule.vue | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/layouts/schedule/multi-location-modal/multi-location-modal.vue b/src/layouts/schedule/multi-location-modal/multi-location-modal.vue index 78dbe6ba5..3ca0693a8 100644 --- a/src/layouts/schedule/multi-location-modal/multi-location-modal.vue +++ b/src/layouts/schedule/multi-location-modal/multi-location-modal.vue @@ -90,14 +90,13 @@ export default { }, onModalClosed() { this.pushEvent(); - this.$emit("close-mobile-first-modal"); this.confirmedAppointment = false; }, closeModal() { this.modal.closeModal(); }, confirmAppointment() { - if (!this.selectedAppointment) return; + if (!this.selectedAppointment.timeSlot.id) return; const selectedTimeSlot = { timeSlot: this.selectedAppointment.timeSlot, diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index dbdaaa90f..f4a9c09ac 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -2152,7 +2152,7 @@ export default { (firstInshopPMDate && numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowAnyTimeslot); - if (showMultiLocationAppointment) { + if (showMultiLocationAppointment?.toLowerCase() === "true") { return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange; } return false;