From 9089da5d5a5421c1ed5cd26d565fdb079c901679 Mon Sep 17 00:00:00 2001 From: AdamCaouetteSafelite Date: Mon, 23 Jun 2025 12:33:40 -0400 Subject: [PATCH 01/75] CASH-926: adjust donation in cart without redoing pricing --- src/fmg-components/cart/cart.vue | 46 +++++++---------------- src/helpers/pricing-helper.js | 8 ++++ src/layouts/confirmation/confirmation.vue | 24 ++++++++++-- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 55e062e24..fb717d283 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -119,7 +119,7 @@
{{ donationCartItemName }} - {{ getLineItemAmount(donationCartItem.subTotal) }} + {{ getLineItemAmount(donationCartItem.sellingPrice) }}
{{ amountDueText }} @@ -182,6 +182,7 @@ import { getAmountDue, getSubTotal, getSalesTax, + getAmountDueWithDonation, } from "@/helpers/pricing-helper.js"; // Constants @@ -211,6 +212,7 @@ export default { isNoComp: Boolean, isExpandedOnLoad: Boolean, isMSRFeeApplicable: Boolean, + donationCartItem: Object, }, data() { return { @@ -1083,33 +1085,6 @@ export default { donationCartItemName() { return this.getCmsContent("DonationCartTextWidget", "Text"); }, - donationCartItem() { - if (!this.lineItems || !this.lineItems.supportingItems) { - return null; - } - - const donationLineItem = this.lineItems.supportingItems.find( - (item) => item.partType === partTypeStrings.DONATION - ); - - if (donationLineItem) { - return { - name: this.donationCartItemName, - category: cartItemCategories.SUPPORTING_ITEMS, - cartItemType: cartItemTypes.DONATION, - isDisplayed: true, - isRemovable: false, - subTotal: donationLineItem.sellingPrice, - salesTax: 0, - lineItems: [], - isCoveredByInsurance: cartItemTypesCoveredByInsurance.includes( - cartItemTypes.DONATION - ), - }; - } - - return null; - }, removeLinkText() { return this.getCmsContent("RemoveCartItemTextWidget", "Text"); }, @@ -1156,9 +1131,15 @@ export default { amountDue() { if (!this.lineItems || this.lineItems.length < 1) return; if (this.showAsPaid) return 0; - return this.isInsurance || !this.shouldHideRecalibration - ? getAmountDue(this.lineItems) // calculate with recal (if on order) - : getAmountDue(this.lineItemsWithoutRecal); // calculated without recal + if (this.isInsurance || !this.shouldHideRecalibration) { + return this.donationCartItem + ? getAmountDueWithDonation(this.lineItems, this.donationCartItem) + : getAmountDue(this.lineItems); + } else { + return this.donationCartItem + ? getAmountDueWithDonation(this.lineItemsWithoutRecal, this.donationCartItem) + : getAmountDue(this.lineItemsWithoutRecal); + } }, amountPaid() { if (!this.showAsPaid) { @@ -1311,8 +1292,7 @@ export default { .amount-due, .amount-paid, .donation-amount { - font-family: - UrbanistSemibold, AvertaSemibold; /* Okay to remove AvertaSemibold after 2025.06.19 merge/release */ + font-family: UrbanistSemibold; color: $black; } .sub-total { diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index fda5b3b9c..33311fd21 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -1,6 +1,7 @@ import store from "@/store"; import { storeActions } from "@/constants/store-actions"; import baseMixin from "@/mixins/base-mixin.js"; +import { deepClone } from "@/helpers/object-helper"; export function getDisplayAmountDue(lineItemsObject, includeTax = true) { return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", { @@ -62,6 +63,13 @@ export function getSalesTax(lineItemsObject) { ); } +export function getAmountDueWithDonation(lineItemsObject, donationLineItem) { + // this is amount due with Donation added + const lineItemsCloneWithDonation = deepClone(lineItemsObject); + lineItemsCloneWithDonation.supportingItems.push(donationLineItem); + return getAmountDue(lineItemsCloneWithDonation, false); +} + export async function getPricingByDayPartWithPrice(pageNameToLog) { // Get the Pricing By Day Part const basePriceByDayPart = await baseMixin.methods.dispatchStoreActionWithLogging( diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index e65eacc3c..e8c01fd05 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -55,7 +55,8 @@ :damage="damageInfo" :availableVaps="vaps" :allowItemRemoval="false" - v-model="lineItems" + v-model="lineItemsWithoutDonation" + :donationCartItem="donationLineItem" :showAsPaid="isPia" servicePackageOptionsCmsName="ServicePackageTitle" :isInsurance="isInsurance" @@ -123,6 +124,7 @@ import experimentMixin from "@/mixins/experiment-mixin.js"; import { containsRecalParts } from "@/helpers/recal-helper.js"; import donationBlock from "@/experiment-components/donation-block.vue"; import { partNumberStrings } from "@/constants/part-number-strings"; +import { partTypeStrings } from "@/constants/part-type-strings"; import analyticsMixin from "@/mixins/analytics-mixin"; export default { @@ -229,7 +231,7 @@ export default { }; const donationItems = lineItemsFromSubmittedOrder.supportingItems?.filter( - (item) => item.partNumber === partNumberStrings.DONATION + (item) => item.partType === partTypeStrings.DONATION ); const donationAmount = donationItems?.length > 0 ? donationItems[0].sellingPrice : 0; @@ -243,7 +245,7 @@ export default { vm.shouldDisplayFosterLove = hasFosterLoveExperiment; vm.donationAmount = donationAmount; vm.showDonationSuccess = lineItemsFromSubmittedOrder.supportingItems?.some( - (item) => item.partType === partNumberStrings.DONATION + (item) => item.partType === partTypeStrings.DONATION ); vm.pushAnalytics(); @@ -490,6 +492,22 @@ export default { donationValues() { return [1, 3, 5]; }, + lineItemsWithoutDonation() { + const lineItemsClone = deepClone(this.lineItems); + lineItemsClone.supportingItems = lineItemsClone.supportingItems?.filter( + (item) => item.partType !== partTypeStrings.DONATION + ); + return lineItemsClone; + }, + donationLineItem() { + if (!this.lineItems) return null; + const donationLineItems = this.lineItems.supportingItems?.filter( + (item) => item.partType === partTypeStrings.DONATION + ); + let output = + donationLineItems && donationLineItems.length > 0 ? donationLineItems[0] : null; + return output; + }, }, methods: { arePagePrerequisitesValid() { From 5a642ac47b08af3b717a05aea925def574a5ccd7 Mon Sep 17 00:00:00 2001 From: Bryan Mauger Date: Mon, 23 Jun 2025 14:56:13 -0400 Subject: [PATCH 02/75] CASH-1028 add EIN and footer copy to donation block. --- src/experiment-components/donation-block.vue | 31 +++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/experiment-components/donation-block.vue b/src/experiment-components/donation-block.vue index fde5509f8..0e29e7394 100644 --- a/src/experiment-components/donation-block.vue +++ b/src/experiment-components/donation-block.vue @@ -7,8 +7,9 @@
-
- +
+ + EIN: 26-3043727

@@ -80,6 +81,7 @@

+

@@ -121,6 +123,9 @@ export default { DonationFooterText() { return this.getCmsContent(this.cmsWidgetName, "FooterText"); }, + DonationFooterText2() { + return this.getCmsContent(this.cmsWidgetName, "FooterText2"); + }, LogoImage() { return this.getCmsContent("DonationWidget", "Image"); }, @@ -195,8 +200,26 @@ export default { } } - img { - width: 5rem; + .logo-block { + display: flex; + flex-direction: column; + background: white; + align-items: flex-start; + justify-content: center; + padding: 0 .5rem; + margin-right: 1rem; + height: max-content; + + img { + width: 88px; + } + + span { + font-size: .625rem; + margin: 0 auto; + padding: .5rem 0; + font-family: UrbanistSemibold; + } } .donation-slider { From 7ab5d17da78be1be019b60139bf3283feeebbe32 Mon Sep 17 00:00:00 2001 From: Bryan Mauger Date: Mon, 23 Jun 2025 14:57:42 -0400 Subject: [PATCH 03/75] Format code. --- src/experiment-components/donation-block.vue | 25 ++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/experiment-components/donation-block.vue b/src/experiment-components/donation-block.vue index 0e29e7394..d4e1d4e99 100644 --- a/src/experiment-components/donation-block.vue +++ b/src/experiment-components/donation-block.vue @@ -9,7 +9,8 @@
- EIN: 26-3043727 + EIN: 26-3043727 +

@@ -206,20 +207,20 @@ export default { background: white; align-items: flex-start; justify-content: center; - padding: 0 .5rem; + padding: 0 0.5rem; margin-right: 1rem; height: max-content; - img { - width: 88px; - } - - span { - font-size: .625rem; - margin: 0 auto; - padding: .5rem 0; - font-family: UrbanistSemibold; - } + img { + width: 88px; + } + + span { + font-size: 0.625rem; + margin: 0 auto; + padding: 0.5rem 0; + font-family: UrbanistSemibold; + } } .donation-slider { From 9921bd9b8ad749576073b3ff84958ea6e385f8e6 Mon Sep 17 00:00:00 2001 From: Bryan Mauger Date: Mon, 23 Jun 2025 15:00:43 -0400 Subject: [PATCH 04/75] Set margin. --- src/experiment-components/donation-block.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/experiment-components/donation-block.vue b/src/experiment-components/donation-block.vue index d4e1d4e99..34cd4178a 100644 --- a/src/experiment-components/donation-block.vue +++ b/src/experiment-components/donation-block.vue @@ -81,8 +81,8 @@
-

-

+

+

From f7bb4f3736103c1c0048124e640eb7573a8176b7 Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Mon, 23 Jun 2025 17:31:53 -0400 Subject: [PATCH 05/75] CASH-816 CASH-816 progress so far --- .../service-location/service-location.vue | 28 +- .../service-zip-modal-question.vue | 2 + .../service-zip-question.vue | 11 +- .../shop-question/shop-question-popup.vue | 615 ++++++++++++++++++ .../shop-question/shop-question.vue | 24 +- 5 files changed, 670 insertions(+), 10 deletions(-) create mode 100644 src/layouts/service-location/shop-question/shop-question-popup.vue diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 6893ad9b4..f9787a0e0 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -107,14 +107,27 @@ @setMobileLocation="setMobileLocationQuestions" @set-mobile-location-invalid="setMobileLocationInValid" /> - + cmsWidgetName="YourSafeliteShopWidget" /> --> + + + + @@ -137,6 +150,7 @@ import serviceZipModalQuestion from "@/layouts/service-location/service-zip-moda import mobileLocationModalQuestions from "@/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions"; import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question"; import shopQuestion from "@/layouts/service-location/shop-question/shop-question"; +import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup"; import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import navbar from "@/fmg-components/nav-bar/nav-bar"; @@ -763,6 +777,13 @@ export default { setMobileLocationInValid(isMobileLocationInValid) { this.isMobileAddressValid = !isMobileLocationInValid; }, + onShopSelected(providerNumber) { + // Find the provider object from your shopProviderData + const selectedProvider = this.shopProviderData.shopProviders.find( + (p) => p.providerNumber === providerNumber + ); + this.selectedProvider = selectedProvider; + }, }, watch: { zipCode: { @@ -814,7 +835,8 @@ export default { Form, loadingModal, contentGroupModal, - shopQuestion, + //shopQuestion, + shopQuestionPopup, }, }; diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue index 5e7aa73e4..0bbfbadf4 100644 --- a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue +++ b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue @@ -49,6 +49,7 @@ import textLink from "@/ux-components/text-link/text-link"; import serviceZipQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question"; import modal from "@/digital-components/modal/modal"; import alert from "@/ux-components/alert/alert"; +import shopQuestion from "@/layouts/service-location/shop-question/shop-question"; import { getPricedMobileFeePart, @@ -245,6 +246,7 @@ export default { serviceZipQuestion, modal, alert, + // shopQuestion }, }; diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue b/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue index 9841c5b16..a0a43dc14 100644 --- a/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue +++ b/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue @@ -8,8 +8,8 @@ cornerStyle="rounded" mask="#####" :displayQuestionText="false" - isRequired - validationRules="zip-required|zip-format" /> + :isRequired="isRequired" + :validationRules="computedValidationRules" /> + + diff --git a/src/layouts/service-location/shop-question/shop-question.vue b/src/layouts/service-location/shop-question/shop-question.vue index 5a9278d78..d1dc3a24c 100644 --- a/src/layouts/service-location/shop-question/shop-question.vue +++ b/src/layouts/service-location/shop-question/shop-question.vue @@ -20,7 +20,7 @@ v-model="selectedProviderNumber" isRequired validationRules="option-required" /> - + @click-event="getNextShopsFromList(1)" + :aria-label="showMoreShopsLinkText" /> -->
@@ -41,6 +41,8 @@ import alert from "@/ux-components/alert/alert"; import buttonQuestion from "@/digital-components/button-question/button-question"; import shopListButton from "./shop-list-button/shop-list-button"; import textLink from "@/ux-components/text-link/text-link"; +//import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup"; +//import serviceZipModalQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-modal-question"; // Supporting files import { defineRule } from "vee-validate"; @@ -125,7 +127,7 @@ export default { }, }, methods: { - async getNextShopsFromList(numberToGet = 3) { + async getNextShopsFromList(numberToGet = 1) { const logFirstShopsDisplayed = this.shopIndex == 0; const shopIterator = (array, n) => { @@ -234,6 +236,13 @@ export default { await nextTick(); } }, + onShopSelectedFromPopup(providerNumber) { + // Option 1: Update local state and emit up + this.$emit("shop-selected", providerNumber); + + // Option 2: If you want to update local state only, do: + // this.pendingSelectedProvider = providerNumber; + }, }, watch: { selectedAppointmentType: { @@ -258,8 +267,13 @@ export default { components: { alert, buttonQuestion, - textLink, + //textLink, + //shopQuestionPopup, + //serviceZipModalQuestion, }, + // mounted(){ + // console.log("shopProviderData" + this.shopProviderData); + // } }; From be19ba761083b6bd464a2e1193001fb161a5b03c Mon Sep 17 00:00:00 2001 From: hiteshkumar87 Date: Tue, 24 Jun 2025 19:11:19 +0530 Subject: [PATCH 06/75] CASH-813 New mobile-details page --- src/constants/progress-bar-mapper.js | 1 + src/constants/store-actions.js | 1 + src/constants/store-mutations.js | 1 + .../customer-details/customer-details.vue | 19 +- .../mobile-address-questions.spec.js | 198 +++++++++++++++ .../mobile-address-questions.vue | 223 ++++++++++++++++ .../mobile-details/mobile-details.spec.js | 139 ++++++++++ src/layouts/mobile-details/mobile-details.vue | 168 +++++++++++++ .../service-location/service-location.spec.js | 238 ------------------ .../service-location/service-location.vue | 151 +++++------ src/router/constants/navigation-scenarios.js | 6 + src/router/constants/routes.js | 4 + src/router/constants/routing-table.js | 21 ++ src/router/methods/routes.js | 1 + src/store/index.js | 9 + 15 files changed, 854 insertions(+), 326 deletions(-) create mode 100644 src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.spec.js create mode 100644 src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue create mode 100644 src/layouts/mobile-details/mobile-details.spec.js create mode 100644 src/layouts/mobile-details/mobile-details.vue diff --git a/src/constants/progress-bar-mapper.js b/src/constants/progress-bar-mapper.js index 98ae07992..dc1bc4a05 100644 --- a/src/constants/progress-bar-mapper.js +++ b/src/constants/progress-bar-mapper.js @@ -15,6 +15,7 @@ const pagePercentageMapper = { "insurance-company": 52, "service-location": 60, schedule: 72, + "mobile-details": 76, "customer-details": 84, "payment-method": 92, payment: 96, diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 9f0965480..d726819a1 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -78,6 +78,7 @@ const storeActions = { SAVE_VIN_LOOKUP: "saveVinLookup", SAVE_SERVICE_ZIP_CODE_INFO: "saveServiceZipCodeInfo", SAVE_SERVICE_LOCATION: "saveServiceLocation", + SAVE_MOBILE_DETAILS: "saveMobileDetails", SAVE_SCHEDULE: "saveSchedule", SAVE_EMAIL: "saveEmail", SAVE_PHONE_NUMBER: "savePhoneNumber", diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index f7e8d9f4b..2112c789c 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -38,6 +38,7 @@ const storeMutations = { UPDATE_SERVICE_ZIP: "updateServiceZip", UPDATE_SERVICE_LOCATION: "updateServiceLocation", + UPDATE_MOBILE_DETAILS: "updateMobileDetails", UPDATE_SERVICE_LOCATION_TECH_NOTES: "updateServiceLocationTechNotes", UPDATE_SCHEDULE: "updateSchedule", diff --git a/src/layouts/customer-details/customer-details.vue b/src/layouts/customer-details/customer-details.vue index e3549b813..f473aa50b 100644 --- a/src/layouts/customer-details/customer-details.vue +++ b/src/layouts/customer-details/customer-details.vue @@ -85,6 +85,7 @@ import { Form, defineRule } from "vee-validate"; import store from "@/store"; import { paymentMethods } from "@/constants/payment-method-constants"; import { flushSaveSessionQueue } from "@/helpers/heritage-integration/order-helper"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; // DEFINE VALIDATION RULES defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED)); @@ -157,11 +158,21 @@ export default { getTechNotesFromStore() { return store.getters.order.serviceLocation.techNotes; }, + getSelectedAppointmentType() { + return store.getters.order.serviceLocation.appointmentType; + }, backButtonAction() { - this.$router.navigateWithoutSaving( - this.navigationScenarios.CLICKED_BACK, - this.pageName - ); + if (this.getSelectedAppointmentType() == AppointmentTypeStrings.MOBILE) { + this.$router.navigateWithoutSaving( + this.navigationScenarios.CLICKED_BACK_WITH_MOBILE_SERVICE, + this.pageName + ); + } else { + this.$router.navigateWithoutSaving( + this.navigationScenarios.CLICKED_BACK, + this.pageName + ); + } }, async forwardButtonAction() { this.dispatchStoreAction( diff --git a/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.spec.js b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.spec.js new file mode 100644 index 000000000..06a3ca901 --- /dev/null +++ b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.spec.js @@ -0,0 +1,198 @@ +// Components +import mobileAddressQuestions from "@/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue"; + +// Supporting Files +import { mount, shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { storeMutations } from "@/constants/store-mutations"; +import store from "@/store"; + +describe("initial state", () => { + test("Should show all the address field", () => { + // Arrange + const { wrapper } = setupMocks({}); + + // Assert + const streetAddressField = wrapper.findComponent({ ref: "streetAddress" }); + const cityField = wrapper.findComponent({ ref: "city" }); + const stateField = wrapper.findComponent({ ref: "state" }); + const zipCodeField = wrapper.findComponent({ ref: "zipCode" }); + + expect(streetAddressField.exists()).toBe(true); + expect(streetAddressField.isVisible()).toBe(true); + expect(cityField.exists()).toBe(true); + expect(cityField.isVisible()).toBe(true); + expect(stateField.exists()).toBe(true); + expect(stateField.isVisible()).toBe(true); + expect(zipCodeField.exists()).toBe(true); + expect(zipCodeField.isVisible()).toBe(true); + }); + + test("Should render mobileAddressQuestions sub-components (textbox-questions and dropdown-questions)", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + // Act + const streetAddress = wrapper.findComponent({ ref: "streetAddress" }); + const city = wrapper.findComponent({ ref: "city" }); + const state = wrapper.findComponent({ ref: "state" }); + const zipCode = wrapper.findComponent({ ref: "zipCode" }); + + // Assert + expect(streetAddress.exists()).toBe(true); + expect(city.exists()).toBe(true); + expect(state.exists()).toBe(true); + expect(zipCode.exists()).toBe(true); + }); + + test("Should render apartmentNumberOrBusinessName textbox-question when captureApartmentNumberOrBusinessName = true", async () => { + // Arrange + const { wrapper } = setupMocks({ + props: { + modelValue: { + streetAddress: "", + apartmentNumberOrBusinessName: "", + city: "", + state: "", + zipCode: "", + }, + captureApartmentNumberOrBusinessName: true, + }, + }); + + // Act + const streetAddress = wrapper.findComponent({ ref: "streetAddress" }); + const apartmentNumberOrBusinessName = wrapper.findComponent({ + ref: "apartmentNumberOrBusinessName", + }); + const city = wrapper.findComponent({ ref: "city" }); + const state = wrapper.findComponent({ ref: "state" }); + const zipCode = wrapper.findComponent({ ref: "zipCode" }); + + // Assert + expect(streetAddress.exists()).toBe(true); + expect(apartmentNumberOrBusinessName.exists()).toBe(true); + expect(apartmentNumberOrBusinessName.isVisible()).toBe(true); + expect(city.exists()).toBe(true); + expect(state.exists()).toBe(true); + expect(zipCode.exists()).toBe(true); + }); + + test("Should *not* render apartmentNumberOrBusinessName textbox-question when captureApartmentNumberOrBusinessName = false", async () => { + // Arrange + const { wrapper } = setupMocks({ + props: { + modelValue: { + streetAddress: "", + apartmentNumberOrBusinessName: "", + city: "", + state: "", + zipCode: "", + }, + captureApartmentNumberOrBusinessName: false, + }, + }); + + // Act + const streetAddress = wrapper.findComponent({ ref: "streetAddress" }); + const apartmentNumberOrBusinessName = wrapper.findComponent({ + ref: "apartmentNumberOrBusinessName", + }); + const city = wrapper.findComponent({ ref: "city" }); + const state = wrapper.findComponent({ ref: "state" }); + const zipCode = wrapper.findComponent({ ref: "zipCode" }); + + // Assert + expect(streetAddress.exists()).toBe(true); + expect(apartmentNumberOrBusinessName.exists()).toBe(true); + expect(apartmentNumberOrBusinessName.isVisible()).toBe(false); + expect(city.exists()).toBe(true); + expect(state.exists()).toBe(true); + expect(zipCode.exists()).toBe(true); + }); +}); + +describe("happy paths", () => { + test("full street address is passed in => address fields are displayed", async () => { + // Arrange/Act + const { wrapper } = setupMocks({ + props: { + modelValue: { + streetAddress: "12345 Test Road", + city: "Tests", + state: "OH", + zipCode: "12312", + }, + }, + }); + + await wrapper.vm.$nextTick(); + + // Assert + const cityField = wrapper.findComponent({ ref: "city" }); + const stateField = wrapper.findComponent({ ref: "state" }); + const zipField = wrapper.findComponent({ ref: "zipCode" }); + expect(cityField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + expect(stateField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + expect(zipField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + }); +}); + +function setupMocks({ + mountOptions, + props, + isShallowMount = true, + querySelectorFunction, + geocoderResult = ["1234 Test Street"], + matchFound = false, +}) { + store.commit(storeMutations.RESET_STATE); + + const resultingMountOptions = getMountOptions({ + ...mountOptions, + router: { + navigate: jest.fn(), + navigate: jest.fn(), + }, + loadScript: jest.fn().mockResolvedValue(), + }); + + if (props) resultingMountOptions.propsData = props; + + if (isShallowMount) { + resultingMountOptions.global.stubs = { + ...(resultingMountOptions.global.stubs ?? {}), + textboxQuestion: { + template: ``, + methods: { + handleChange: jest.fn(), + }, + }, + dropdownQuestion: { + template: ``, + methods: { + handleChange: jest.fn(), + }, + }, + }; + } + + const wrapper = isShallowMount + ? shallowMount(mobileAddressQuestions, resultingMountOptions) + : mount(mobileAddressQuestions, resultingMountOptions); + + document.querySelector = jest.fn().mockImplementation((query) => { + let result = null; + if (query == ".pac-container") result = document.createElement("div"); + else if (querySelectorFunction) { + result = querySelectorFunction(query); + } + + return result ?? null; + }); + + return { wrapper }; +} diff --git a/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue new file mode 100644 index 000000000..40a50d47e --- /dev/null +++ b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/layouts/mobile-details/mobile-details.spec.js b/src/layouts/mobile-details/mobile-details.spec.js new file mode 100644 index 000000000..8ed9e27b7 --- /dev/null +++ b/src/layouts/mobile-details/mobile-details.spec.js @@ -0,0 +1,139 @@ +import mobileDetails from "@/layouts/mobile-details/mobile-details.vue"; +import { mount, shallowMount } from "@vue/test-utils"; +import { storeActions } from "@/constants/store-actions"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import store from "@/store"; + +const linkWidgetName = "linkWidgetName"; +const modalWidgetName = "modalWidgetName"; + +const mockLinkCmsContent = { + BodyText: "Sample link body text here.", +}; + +const mockModalCmsContent = { + FooterText: "Sample modal footer text here.", +}; + +const mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + if (widgetName === linkWidgetName) { + return mockLinkCmsContent[cmsFieldName]; + } + + if (widgetName === modalWidgetName) { + return mockModalCmsContent[cmsFieldName]; + } + + return null; + }), + dispatchStoreAction: jest.fn((actionName) => { + if (actionName === storeActions.SAVE_MOBILE_DETAILS) { + return true; + } + }), + }, +}; + +beforeEach(() => { + store.getters = { + order: { + serviceLocation: { + zipCode: "43235", + zipCodeCtu: "00000", + state: "OH", + appointmentType: "Mobile", + city: "Columbus", + address: "123 Main St", + address2: "Suite 100", + isVehicleProtected: true, + }, + schedule: { + date: "2023-10-10", + endTime: "14:00", + startTime: "13:00", + routeCode: "R1", + jobMaxMinutes: "60", + jobMinMinutes: "30", + }, + }, + payment: { + isInsurance: false, + }, + }; +}); + +describe("mobile-details.vue", () => { + test("if the continue button is clicked, navigate forward", async () => { + // Arrange + const { wrapper } = setupMocks(mobileDetails, { + mixins: [mockMixin], + attachTo: document.body, + }); + + wrapper.setData({ + addressQuestions: { + streetAddress: "Test Street", + apartmentNumberOrBusinessName: "Test Apartment", + city: "Test City", + state: "TS", + zipCode: "12345", + }, + isVehicleProtected: true, + }); + + // Act + await wrapper.vm.forwardButtonAction(); + + // Assert + expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled(); + }); + test("arePagePrerequisitesValid should be true ", async () => { + //Arrange + const { wrapper } = setupMocks(mobileDetails, { + mixins: [mockMixin], + attachTo: document.body, + }); + + //Act + let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(true); + }); + + test("if the back button is clicked, navigate back", async () => { + // Arrange + const { wrapper } = setupMocks(mobileDetails, { + mixins: [mockMixin], + attachTo: document.body, + }); + + // Act + await wrapper.vm.backButtonAction(); + + // Assert + expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); + }); +}); + +function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) { + const resultingMountOptions = getMountOptions({ + ...mountOptions, + mixins, + router: { + navigate: jest.fn(), + navigateWithSaving: jest.fn(), + navigateWithoutSaving: jest.fn(), + }, + }); + + if (props) resultingMountOptions.propsData = props; + resultingMountOptions["attachTo"] = document.body; + const wrapper = isShallowMount + ? shallowMount(mobileDetails, resultingMountOptions) + : mount(mobileDetails, resultingMountOptions); + + return { wrapper }; +} diff --git a/src/layouts/mobile-details/mobile-details.vue b/src/layouts/mobile-details/mobile-details.vue new file mode 100644 index 000000000..25268805c --- /dev/null +++ b/src/layouts/mobile-details/mobile-details.vue @@ -0,0 +1,168 @@ + + diff --git a/src/layouts/service-location/service-location.spec.js b/src/layouts/service-location/service-location.spec.js index 58187496e..551e15ae0 100644 --- a/src/layouts/service-location/service-location.spec.js +++ b/src/layouts/service-location/service-location.spec.js @@ -314,57 +314,6 @@ describe("service-location.vue", () => { expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeQuestion); }); - test("resets mobile location when service zip code is updated", () => { - // Arrange - const { wrapper } = setupMocks({}); - - const mobileLocationQuestionsComponent = wrapper.findComponent({ - ref: "mobileLocationQuestions", - }); - mobileLocationQuestionsComponent.resetComponent = jest.fn(); - - const serviceZipCodeComponent = wrapper.findComponent({ - ref: "serviceZipCodeQuestion", - }); - serviceZipCodeComponent.resetMobileFeePart = jest.fn(); - - const newServiceZipCodeQuestion = { - zipCode: "61606", - state: "IL", - zipCodeCtu: "01526", - }; - - const mobileLocationQuestions = { - addressQuestions: { - streetAddress: "5555 Sulgrave Dr", - apartmentNumberOrBusinessName: "Apt 1", - city: "New Albany", - state: "OH", - zipCode: "43054", - }, - isVehicleProtected: true, - }; - wrapper.vm.mobileLocationQuestions = mobileLocationQuestions; - - const newMobileLocationQuestions = { - addressQuestions: { - streetAddress: "", - apartmentNumberOrBusinessName: "", - city: "", - state: "IL", - zipCode: "61606", - }, - isVehicleProtected: null, - isMobileSelected: false, - }; - - // Act - serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion); - - // Assert - expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions); - }); - test("resets appointment type selection when service zip code is updated by service zip modal", () => { // Arrange const { wrapper } = setupMocks({}); @@ -424,190 +373,6 @@ describe("service-location.vue", () => { }); }); - describe("updating mobile location", () => { - test("updates the page model after providing the mobile location", async () => { - // Arrange - const { wrapper } = setupMocks({}); - - await wrapper.setData({ - shopProviderData: { - mobileProviderNumber: "001820", - }, - selectedAppointmentType: "Mobile", - providerData: { mobileProviderNumber: "01820" }, - isGlassServiceableMobile: true, - }); - - const mobileLocationQuestionsComponent = wrapper.findComponent({ - ref: "mobileLocationQuestions", - }); - mobileLocationQuestionsComponent.resetComponent = jest.fn(); - - const serviceZipCodeComponent = wrapper.findComponent({ - ref: "serviceZipCodeQuestion", - }); - serviceZipCodeComponent.resetMobileFeePart = jest.fn(); - wrapper.vm.forwardButtonAction = jest.fn(); - - const mobileLocationQuestions = { - addressQuestions: { - streetAddress: "", - apartmentNumberOrBusinessName: "", - city: "", - state: "", - zipCode: "", - }, - isVehicleProtected: null, - }; - wrapper.vm.mobileLocationQuestions = mobileLocationQuestions; - const newMobileLocationQuestions = { - addressQuestions: { - streetAddress: "5555 Sulgrave Dr", - apartmentNumberOrBusinessName: "Apt 1", - city: "New Albany", - state: "OH", - zipCode: "43054", - }, - isVehicleProtected: true, - isMobileSelected: true, - }; - - // Act - - // Trigger the event - await mobileLocationQuestionsComponent.vm.$emit( - "setMobileLocation", - newMobileLocationQuestions - ); - - await wrapper.vm.$nextTick(); // Wait for DOM updates - - // Assert - expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions); - }); - - test("resets service zip code when mobile location is updated", async () => { - // Arrange - const { wrapper } = setupMocks({}); - - await wrapper.setData({ - shopProviderData: { - mobileProviderNumber: "001820", - }, - selectedAppointmentType: "Mobile", - providerData: { mobileProviderNumber: "01820" }, - isGlassServiceableMobile: true, - }); - - const mobileLocationQuestionsComponent = wrapper.findComponent({ - ref: "mobileLocationQuestions", - }); - mobileLocationQuestionsComponent.resetComponent = jest.fn(); - - const serviceZipCodeComponent = wrapper.findComponent({ - ref: "serviceZipCodeQuestion", - }); - serviceZipCodeComponent.resetMobileFeePart = jest.fn(); - wrapper.vm.forwardButtonAction = jest.fn(); - const mobileLocationQuestions = { - addressQuestions: { - streetAddress: "", - apartmentNumberOrBusinessName: "", - city: "", - state: "", - zipCode: "", - }, - isVehicleProtected: null, - isMobileSelectedAndModalClosed: false, - mobileFeePart: null, - }; - wrapper.vm.mobileLocationQuestions = mobileLocationQuestions; - - const newMobileLocationQuestions = { - addressQuestions: { - streetAddress: "5555 Rustic Dr", - apartmentNumberOrBusinessName: "Apt 1", - city: "Westerville", - state: "OH", - zipCode: "43081", - }, - isVehicleProtected: true, - isMobileSelectedAndModalClosed: true, - mobileFeePart: null, - }; - - wrapper.vm.serviceZipCodeQuestion = { - zipCode: "61606", - state: "IL", - zipCodeCtu: "01526", - }; - - const newServiceZipCodeInfo = { - state: "OH", - zipCode: "43081", - zipCodeCtu: "01526", - }; - - wrapper.vm.closeModalAction = jest.fn(); - // Act - - // Trigger the event - await mobileLocationQuestionsComponent.vm.$emit( - "setMobileLocation", - newMobileLocationQuestions - ); - - await wrapper.vm.$nextTick(); // Wait for DOM updates - - // Assert - expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeInfo); - }); - - test("does not reset appointment type selection when service zip code is updated by mobile location modal when Mobile is selected", async () => { - // Arrange - const { wrapper } = setupMocks({}); - - await wrapper.setData({ - shopProviderData: { - mobileProviderNumber: "001820", - }, - selectedAppointmentType: "Mobile", - providerData: { mobileProviderNumber: "01820" }, - }); - - const mobileLocationQuestionsComponent = wrapper.findComponent({ - ref: "mobileLocationQuestions", - }); - mobileLocationQuestionsComponent.resetComponent = jest.fn(); - wrapper.vm.forwardButtonAction = jest.fn(); - wrapper.vm.isServiceableMobile = true; - - const serviceZipCodeComponent = wrapper.findComponent({ - ref: "serviceZipCodeQuestion", - }); - serviceZipCodeComponent.resetMobileFeePart = jest.fn(); - - const mobileLocationQuestions = { - addressQuestions: { - streetAddress: "555 Some Street", - apartmentNumberOrBusinessName: "", - city: "Westerville", - state: "OH", - zipCode: "43081", - }, - isVehicleProtected: "YesAnswer", - isMobileSelectedAndModalClosed: true, - }; - - wrapper.vm.closeModalAction = jest.fn(); - // Act - mobileLocationQuestionsComponent.vm.$emit("setMobileLocation", mobileLocationQuestions); - - // Assert - expect(wrapper.vm.selectedAppointmentType).toStrictEqual("Mobile"); - }); - }); - describe("serviceability logic", () => { describe("should be logical AND when recalibration is defined.", () => { test("T & T => T", async () => { @@ -1434,8 +1199,5 @@ function setupMocks({ mountOptionsMockData = {} }) { const mountOptions = getMountOptions(mountOptionsMockData); const wrapper = shallowMount(serviceLocation, mountOptions); wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; - wrapper.vm.$refs.mobileLocationQuestions.isMobileAddressComplete = jest.fn(); - wrapper.vm.$refs.mobileLocationQuestions.resetAlerts = jest.fn(); - wrapper.vm.$refs.mobileLocationQuestions.openModal = jest.fn(); return { wrapper, apiPromise }; } diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 6893ad9b4..ccb2c7866 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -89,24 +89,14 @@
- - +
+ +
{ - if (this.isServiceableMobile) { - this.shopProviderData = result.data; - this.selectedProvider = new Provider( - this.shopProviderData.mobileProviderNumber - ); - } - }); - } - if (this.isServiceableMobile) { - this.setMobileLocation(newValue); - this.setMobileLocationInValid(false); - } else { - await getZipCodeData(newZipCode).then((zipCodeData) => { - this.serviceZipCodeQuestion = { - state: zipCodeData.state, - zipCode: newZipCode, - zipCodeCtu: zipCodeData.zipCodeCtu, - }; - }); - } - }, getCarIdfromStore() { return store.getters.vehicle.carId; }, @@ -590,15 +556,6 @@ export default { this.isRecalibrationServiceableMobile = serviceabilityDetails.isRecalibrationServiceableMobile; }, - setMobileLocation(mobileLocation) { - this.streetAddress = mobileLocation.addressQuestions.streetAddress; - this.apartmentNumberOrBusinessName = - mobileLocation.addressQuestions.apartmentNumberOrBusinessName; - this.city = mobileLocation.addressQuestions.city; - this.state = mobileLocation.addressQuestions.state; - this.zipCode = mobileLocation.addressQuestions.zipCode; - this.isVehicleProtected = mobileLocation.isVehicleProtected; - }, async reloadShopData(zipCode) { await this.$refs.shopQuestion.reloadShopData(zipCode); }, @@ -755,13 +712,49 @@ export default { this.updateAndSaveSupportingItems(); this.updateAndSaveIsMSRFeeApplicable(); - this.$router.navigateWithSaving( - this.navigationScenarios.CLICKED_FORWARD, - this.pageName + + //mocked schedule and should be clear when implement schedule page. + this.dispatchStoreAction( + this.storeActions.SAVE_SCHEDULE, + this.getTimeSlotInfo(), + false ); + + if (this.selectedAppointmentType == AppointmentTypeStrings.MOBILE) { + //navigate to mobile details page + this.$router.navigateWithSaving( + this.navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE, + this.pageName + ); + } else { + this.$router.navigateWithSaving( + this.navigationScenarios.CLICKED_FORWARD, + this.pageName + ); + } }, - setMobileLocationInValid(isMobileLocationInValid) { - this.isMobileAddressValid = !isMobileLocationInValid; + getTimeSlotInfo() { + // Get the current date + const currentDate = new Date(); + + // Add 10 days to the current date + currentDate.setDate(currentDate.getDate() + 10); + + // Format the date as yyyy-mm-dd + const year = currentDate.getFullYear(); + const month = String(currentDate.getMonth() + 1).padStart(2, "0"); + const day = String(currentDate.getDate()).padStart(2, "0"); + + const formattedDate = `${year}-${month}-${day}`; + + return { + date: formattedDate, + endTime: "12:00", + jobMaxMinutes: "180", + jobMinMinutes: "120", + routeCode: "3357I-03357-M-I*20988*AM", + startTime: "08:00", + }; }, }, watch: { @@ -787,16 +780,6 @@ export default { this.selectedProvider = new Provider( this.shopProviderData.mobileProviderNumber ); - if (this.zipCode == this.getServiceZipCodeFromStore()) { - //restore mobile address from store in case user make changes to address but not commit it. - this.streetAddress = this.getServiceAddressFromStore(); - this.apartmentNumberOrBusinessName = this.getServiceAddress2FromStore(); - this.city = this.getServiceCityFromStore(); - this.isVehicleProtected = this.getIsVehicleProtectedFromStore(); - } else { - this.resetMobileLocation(); - } - this.$refs.mobileLocationQuestions.resetAlerts(); } else { this.selectedProvider = new Provider(); } @@ -807,7 +790,6 @@ export default { alert, serviceZipModalQuestion, appointmentTypeQuestion, - mobileLocationModalQuestions, funnelHeader, navbar, funnelSubHeader, @@ -815,6 +797,7 @@ export default { loadingModal, contentGroupModal, shopQuestion, + textBlock, }, }; diff --git a/src/router/constants/navigation-scenarios.js b/src/router/constants/navigation-scenarios.js index a6e117948..b660afca3 100644 --- a/src/router/constants/navigation-scenarios.js +++ b/src/router/constants/navigation-scenarios.js @@ -49,6 +49,12 @@ const navigationScenarios = { // Insurance-Company CLICKED_PAY_ON_MY_OWN: "CLICKED_PAY_ON_MY_OWN", + //Service-Location + CLICKED_FORWARD_WITH_MOBILE_SERVICE: "CLICKED_FORWARD_WITH_MOBILE_SERVICE", + + //Customer-details + CLICKED_BACK_WITH_MOBILE_SERVICE: "CLICKED_BACK_WITH_MOBILE_SERVICE", + // Schedule CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION", diff --git a/src/router/constants/routes.js b/src/router/constants/routes.js index d03954867..7e9a3a793 100644 --- a/src/router/constants/routes.js +++ b/src/router/constants/routes.js @@ -68,6 +68,10 @@ export const routeData = { name: "schedule", path: "/schedule", }, + MOBILE_DETAILS: { + name: "mobile-details", + path: "/mobile-details", + }, CUSTOMER_DETAILS: { name: "customer-details", path: "/customer-details", diff --git a/src/router/constants/routing-table.js b/src/router/constants/routing-table.js index 4b7286741..4a07bcfaf 100644 --- a/src/router/constants/routing-table.js +++ b/src/router/constants/routing-table.js @@ -460,6 +460,10 @@ const routingTable = function () { scenario: navigationScenarios.CLICKED_FORWARD, destinationPageData: routeData.SCHEDULE, }, + { + scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE, + destinationPageData: routeData.MOBILE_DETAILS, + }, ], }, { @@ -479,6 +483,19 @@ const routingTable = function () { }, ], }, + { + pageName: routeData.MOBILE_DETAILS.name, + maps: [ + { + scenario: navigationScenarios.CLICKED_BACK, + destinationPageData: routeData.SERVICE_LOCATION, + }, + { + scenario: navigationScenarios.CLICKED_FORWARD, + destinationPageData: routeData.CUSTOMER_DETAILS, + }, + ], + }, { pageName: routeData.CUSTOMER_DETAILS.name, maps: [ @@ -486,6 +503,10 @@ const routingTable = function () { scenario: navigationScenarios.CLICKED_BACK, destinationPageData: routeData.SCHEDULE, }, + { + scenario: navigationScenarios.CLICKED_BACK_WITH_MOBILE_SERVICE, + destinationPageData: routeData.MOBILE_DETAILS, + }, { scenario: navigationScenarios.CLICKED_FORWARD, destinationPageData: routeData.PAYMENT_METHOD, diff --git a/src/router/methods/routes.js b/src/router/methods/routes.js index c029ce2cb..01f4f8a58 100644 --- a/src/router/methods/routes.js +++ b/src/router/methods/routes.js @@ -37,6 +37,7 @@ export const routes = [ createRoute(routeData.PAYMENT_PIA_RETURN), createRoute(routeData.CONFIRMATION), createRoute(routeData.RETURN_USER), + createRoute(routeData.MOBILE_DETAILS), // Virtual pages (resolve to a non-virtual page.) createVirtualRoute(routeData.LANDING, landingBeforeEnter), diff --git a/src/store/index.js b/src/store/index.js index 073a50e23..89de1a494 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -421,6 +421,12 @@ export const mutations = { }, }; }, + updateMobileDetails(state, mobileDetails) { + state.order.serviceLocation.address = mobileDetails.address; + state.order.serviceLocation.address2 = mobileDetails.address2; + state.order.serviceLocation.city = mobileDetails.city; + state.order.serviceLocation.isVehicleProtected = mobileDetails.isVehicleProtected; + }, updateServiceLocationTechNotes(state, techNotes) { state.order.serviceLocation.techNotes = techNotes; }, @@ -3204,6 +3210,9 @@ export const actions = { context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo); }, + saveMobileDetails(context, mobileDetails) { + context.commit(storeMutations.UPDATE_MOBILE_DETAILS, mobileDetails); + }, saveServiceLocationTechNotes(context, techNotesInfo) { context.commit(storeMutations.UPDATE_SERVICE_LOCATION_TECH_NOTES, techNotesInfo); From c95d91b52e07d5129a225c4a9f8926d3840c3c6d Mon Sep 17 00:00:00 2001 From: hiteshkumar87 Date: Tue, 24 Jun 2025 20:45:22 +0530 Subject: [PATCH 07/75] CASH-813 constant file state options added --- src/constants/state-options.js | 54 +++++++++++++++++ .../mobile-address-questions.vue | 59 +------------------ 2 files changed, 57 insertions(+), 56 deletions(-) create mode 100644 src/constants/state-options.js diff --git a/src/constants/state-options.js b/src/constants/state-options.js new file mode 100644 index 000000000..19eb348d5 --- /dev/null +++ b/src/constants/state-options.js @@ -0,0 +1,54 @@ +const stateOptions = { + AL: "Alabama", + AK: "Alaska", + AZ: "Arizona", + AR: "Arkansas", + CA: "California", + CO: "Colorado", + CT: "Connecticut", + DE: "Delaware", + DC: "District Of Columbia", + FL: "Florida", + GA: "Georgia", + HI: "Hawaii", + ID: "Idaho", + IL: "Illinois", + IN: "Indiana", + IA: "Iowa", + KS: "Kansas", + KY: "Kentucky", + LA: "Louisiana", + ME: "Maine", + MD: "Maryland", + MA: "Massachusetts", + MI: "Michigan", + MN: "Minnesota", + MS: "Mississippi", + MO: "Missouri", + MT: "Montana", + NE: "Nebraska", + NV: "Nevada", + NH: "New Hampshire", + NJ: "New Jersey", + NM: "New Mexico", + NY: "New York", + NC: "North Carolina", + ND: "North Dakota", + OH: "Ohio", + OK: "Oklahoma", + OR: "Oregon", + PA: "Pennsylvania", + RI: "Rhode Island", + SC: "South Carolina", + SD: "South Dakota", + TN: "Tennessee", + TX: "Texas", + UT: "Utah", + VT: "Vermont", + VA: "Virginia", + WA: "Washington", + WV: "West Virginia", + WI: "Wisconsin", + WY: "Wyoming", +}; +export { stateOptions }; diff --git a/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue index 40a50d47e..2e5dd8324 100644 --- a/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue +++ b/src/layouts/mobile-details/mobile-address-questions/mobile-address-questions.vue @@ -79,6 +79,7 @@ import dropdownQuestion from "@/digital-components/dropdown-question/dropdown-qu import { defineRule } from "vee-validate"; import { required, regex } from "@/helpers/validation-rules"; import { errorMessages } from "@/constants/error-messages"; +import { stateOptions } from "@/constants/state-options"; // DEFINE VALIDATION RULES defineRule("street-address-required", required(errorMessages.STREET_ADDRESS_REQUIRED)); @@ -128,62 +129,8 @@ export default { }, }, computed: { - stateOptions: { - get: function () { - return { - AL: "Alabama", - AK: "Alaska", - AZ: "Arizona", - AR: "Arkansas", - CA: "California", - CO: "Colorado", - CT: "Connecticut", - DE: "Delaware", - DC: "District Of Columbia", - FL: "Florida", - GA: "Georgia", - HI: "Hawaii", - ID: "Idaho", - IL: "Illinois", - IN: "Indiana", - IA: "Iowa", - KS: "Kansas", - KY: "Kentucky", - LA: "Louisiana", - ME: "Maine", - MD: "Maryland", - MA: "Massachusetts", - MI: "Michigan", - MN: "Minnesota", - MS: "Mississippi", - MO: "Missouri", - MT: "Montana", - NE: "Nebraska", - NV: "Nevada", - NH: "New Hampshire", - NJ: "New Jersey", - NM: "New Mexico", - NY: "New York", - NC: "North Carolina", - ND: "North Dakota", - OH: "Ohio", - OK: "Oklahoma", - OR: "Oregon", - PA: "Pennsylvania", - RI: "Rhode Island", - SC: "South Carolina", - SD: "South Dakota", - TN: "Tennessee", - TX: "Texas", - UT: "Utah", - VT: "Vermont", - VA: "Virginia", - WA: "Washington", - WV: "West Virginia", - WI: "Wisconsin", - WY: "Wyoming", - }; - }, + stateOptions() { + return stateOptions; }, addressModel: { get: function () { From a0a922b302a4904d947f56a3757cbbf8003fa25a Mon Sep 17 00:00:00 2001 From: CarlNation Date: Tue, 24 Jun 2025 14:43:54 -0400 Subject: [PATCH 08/75] CASH-1029 Dynamo Logging CASH-1029 Dynamo Logging --- src/constants/endpoints.js | 4 +++ src/constants/experiments.js | 1 + src/constants/store-actions.js | 1 + src/fmg-components/cart/cart.vue | 3 +- src/global-methods.js | 58 +++++++++++++++++--------------- src/mixins/analytics-mixin.js | 57 ++++++++++++++++++++++++++++++- src/mixins/base-mixin.js | 12 +++++++ src/router/methods/after-each.js | 3 ++ src/store/index.js | 36 +++++++++++++++++++- src/styles/ux-variables.scss | 4 +-- 10 files changed, 146 insertions(+), 33 deletions(-) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index aa0643821..085651f20 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -176,6 +176,10 @@ const endpoints = { url: "/analytics/api/v1/analytics/log-part-questions", method: "POST", }, + LogDigitalConsumer: { + url: "/analytics/api/v1/analytics/digitalconsumer-log", + method: "POST", + }, GetExperimentsByUser: { url: "/analytics/api/v1/analytics/get-experiments", method: "GET", diff --git a/src/constants/experiments.js b/src/constants/experiments.js index c5c24536d..871c6fd24 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -26,6 +26,7 @@ const experimentSettings = { FOSTER_LOVE: "DisplayFosterLove", SKIP_TO_INSURANCE: "SkipToInsurance", DISPLAY_AFTERPAY_BREAKOUT_DISPLAY: "Display_AfterpayBreakoutDisplay", + DYNAMO_LOGGING: "DynamoLogging", }; const experimentTriggers = { diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 9f0965480..cb2c84940 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -63,6 +63,7 @@ const storeActions = { LOG_CUSTOM_EVENT: "logCustomEvent", INITIALIZE_SESSION: "initializeSession", LOG_PART_QUESTIONS: "logPartQuestions", + LOG_DIGITALCONSUMER: "logDigitalConsumer", // DEPENDENCY MUTATIONS RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleStateAndDependencies", diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 55e062e24..433dfb3e4 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -1311,8 +1311,7 @@ export default { .amount-due, .amount-paid, .donation-amount { - font-family: - UrbanistSemibold, AvertaSemibold; /* Okay to remove AvertaSemibold after 2025.06.19 merge/release */ + font-family: UrbanistSemibold, AvertaSemibold; /* Okay to remove AvertaSemibold after 2025.06.19 merge/release */ color: $black; } .sub-total { diff --git a/src/global-methods.js b/src/global-methods.js index e90843bdc..75fabb356 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -100,34 +100,38 @@ export default { return resolve(response); }, (error) => { - if (logApiCall) { - analyticsMixIn.methods.pushEventToGA( - GaCategories.API_RESPONSE, - GaActions.RESULT, - `${GaLabels.ERROR}_${endpoint}`, - true - ); + if (endpoint.toLowerCase().includes("/digitalconsumer-log")) { + return resolve({ data: null, error: "Ignore errors when logging" }); + } else { + if (logApiCall) { + analyticsMixIn.methods.pushEventToGA( + GaCategories.API_RESPONSE, + GaActions.RESULT, + `${GaLabels.ERROR}_${endpoint}`, + true + ); + } + + if (error.response.status && error.response.status != "404") { + // Do not route to error logic when no wipers found or no promo found (404s) + const errorPayload = { + cause: `Response error ${error.response.status}`, + currentPage: pageNameToLog, + endpoint: endpoint, + }; + + router.bailout(errorPayload); + + // do not log 404 errors from services because we return NotFound + // when a service doesn't return an object + global.$logger.logError( + `${method}: ${endpoint}: ${error.message}`, + error.response + ); + } + + return reject(error.response); } - - if (error.response.status && error.response.status != "404") { - // Do not route to error logic when no wipers found or no promo found (404s) - const errorPayload = { - cause: `Response error ${error.response.status}`, - currentPage: pageNameToLog, - endpoint: endpoint, - }; - - router.bailout(errorPayload); - - // do not log 404 errors from services because we return NotFound - // when a service doesn't return an object - global.$logger.logError( - `${method}: ${endpoint}: ${error.message}`, - error.response - ); - } - - return reject(error.response); } ); }); diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 298a3af8f..90513e764 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -12,7 +12,8 @@ import { setSessionKeyIfUnset, } from "@/helpers/heritage-integration/cookie-helper"; import { queryStrings } from "@/constants/query-strings"; -import { experimentSettings } from "@/constants/experiments"; +import { experimentSettings, experimentUniverses } from "@/constants/experiments"; +import experimentMixin from "@/mixins/experiment-mixin"; import { analyticsPageEvents, GaCategories, @@ -128,6 +129,60 @@ export default { } }, + async logDigitalConsumer() { + const currentPageName = getPageNameFromRouter(); + const universes = store.getters.applicationUser.experiments; + console.log(JSON.stringify(universes)); + + let hasDynamoLogging = experimentMixin.methods.hasSettingEqualTo( + experimentSettings.DYNAMO_LOGGING, + "true", + universes + ); + if (!hasDynamoLogging) { + return; + } + + const variationNames = universes + .filter((item) => item.universeName === experimentUniverses.CONCEPT_FUNNEL) + .map((item) => item.variationName) + .filter(Boolean); // removes undefined/null + + const conceptVariation = variationNames.length > 0 ? variationNames[0] : ""; + + const submittedOrder = baseMixin.methods.getSubmittedOrder(); + const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder(); + const hasSubmittedOrderAtConfirmationPage = + hasSubmittedOrder && currentPageName?.toLowerCase() == routeData.CONFIRMATION.name; + + const refSequenceNum = hasSubmittedOrderAtConfirmationPage + ? submittedOrder.referralSequenceNumber + : store.getters.order.referralSequenceNumber; + + var payload = { + actionName: `Browser page:${currentPageName}`, + referralSequenceNumber: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.referralSequenceNumber + : store.getters.order.referralSequenceNumber, + referralNumber: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.referralNumber + : store.getters.order.referralNumber, + workOrderId: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.workOrderId + : store.getters.order.workOrderId, + workOrderNumber: hasSubmittedOrderAtConfirmationPage + ? submittedOrder.workOrderNumber + : store.getters.order.workOrderNumber, + conceptVariation: conceptVariation, + }; + + await baseMixin.methods.dispatchStoreAction( + storeActions.LOG_DIGITALCONSUMER, + payload, + false + ); + }, + async pushEventForChatsToGA(category, action, label, pushToLogApp = false) { const currentPageName = getPageNameFromRouter(); const value = `2.0_${currentPageName}`; diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index ecce2632d..4046fa477 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -197,6 +197,18 @@ export default { return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)]; } }, + isMobileDevice() { + const userAgent = navigator.userAgent; + return ( + userAgent.includes("Android") || + userAgent.includes("Mobile") || + userAgent.includes("iPod") || + userAgent.includes("iPhone") || + userAgent.includes("IEMobile") || + userAgent.includes("BlackBerry") || + userAgent.includes("webOS") + ); + }, getSubmittedOrder() { return JSON.parse( window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) diff --git a/src/router/methods/after-each.js b/src/router/methods/after-each.js index ef3dc0d93..27cecffde 100644 --- a/src/router/methods/after-each.js +++ b/src/router/methods/after-each.js @@ -1,6 +1,9 @@ import analyticsMixin from "@/mixins/analytics-mixin"; export async function afterEach(to, from) { + // digital consumer logging + analyticsMixin.methods.logDigitalConsumer(); + // Push page view to GA analyticsMixin.methods.pushPageViewToGA(); diff --git a/src/store/index.js b/src/store/index.js index 073a50e23..2cfe39ead 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -11,7 +11,11 @@ import { experimentTriggers } from "@/constants/experiments"; import { damageLocationsSelected } from "@/constants/damage-locations-selected"; import { singleWindshieldCarIds } from "@/constants/single-windshield-carids"; import { routeData } from "@/router/constants/routes"; -import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper.js"; +import { + deleteFunnelCookie, + getSessionIdValue, + getDeviceIdValue, +} from "@/helpers/heritage-integration/cookie-helper.js"; import { deepEqual } from "@/helpers/object-helper"; import baseMixin from "@/mixins/base-mixin.js"; import { @@ -1551,6 +1555,36 @@ export const actions = { }); }, + logDigitalConsumer( + context, + { + actionName, + referralSequenceNumber, + referralNumber, + workOrderId, + workOrderNumber, + conceptVariation, + } + ) { + var payload = { + sessionId: getSessionIdValue(), + deviceId: getDeviceIdValue(), + actionName: actionName ?? "", + referralSequenceNumber: referralSequenceNumber ?? "", + referralNumber: referralNumber ?? "", + applicationName: baseMixin.methods.isMobileDevice() ? "2.0 Mobile" : "2.0", + workOrderId: workOrderId ?? "", + workOrderNumber: workOrderNumber ?? "", + conceptVariation: conceptVariation, + }; + + return globalMethods.callHttpClient({ + method: endpoints.LogDigitalConsumer.method, + endpoint: endpoints.LogDigitalConsumer.url, + payload: payload, + }); + }, + // Misc Actions setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) { context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber); diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss index 01b122b31..69ba0d7ac 100644 --- a/src/styles/ux-variables.scss +++ b/src/styles/ux-variables.scss @@ -121,8 +121,8 @@ $body-color: $gray-600; //Fonts $font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif; -$font-family-monospace: - UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +$font-family-monospace: UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", + monospace; // stylelint-enable value-keyword-case $font-family-base: $font-family-sans-serif; $font-family-code: $font-family-monospace; From 059c5bc25a700c8674f864fcca40bbbb27a8feaf Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Wed, 25 Jun 2025 14:57:02 -0400 Subject: [PATCH 09/75] CASH-952: Afterpay banner update --- src/constants/experiments.js | 1 + src/helpers/cms-content-helper.js | 4 + .../afterpay-modal-banner.vue | 110 ++++++++++++------ src/layouts/quote/quote.vue | 19 ++- src/styles/ux-variables.scss | 8 +- 5 files changed, 91 insertions(+), 51 deletions(-) diff --git a/src/constants/experiments.js b/src/constants/experiments.js index 871c6fd24..6a2796da9 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -26,6 +26,7 @@ const experimentSettings = { FOSTER_LOVE: "DisplayFosterLove", SKIP_TO_INSURANCE: "SkipToInsurance", DISPLAY_AFTERPAY_BREAKOUT_DISPLAY: "Display_AfterpayBreakoutDisplay", + AFTERPAY_EXTENDED_PAY_OPTION_THRESHOLD: "AfterPayExtendedPayOptionThreshold", DYNAMO_LOGGING: "DynamoLogging", }; diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index c1d28097a..727add296 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -339,3 +339,7 @@ export function splitCMSCopyOnParagraphTag(copy) { // filter removes empty strings that are a result of string.split with regex return copy.split(/(?:)|(?:<\/p>)/g).filter((paragraph) => paragraph !== ""); } + +export function splitCMSCopyOnBR(copy) { + return copy.split("
"); +} diff --git a/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue b/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue index 4c3644cac..32960509c 100644 --- a/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue +++ b/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue @@ -1,13 +1,14 @@ diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index f9787a0e0..0d7aa9e24 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -23,6 +23,7 @@ @updated-serviceability="setServiceabilityDetails" @updated-contains-military-base="setContainsMilitaryBase" @updated-bill-to-account-number="setBillToAccountNumber" + @updated-zipcode-ctu="setZipcodeCtu" linkWidgetName="ServiceZipLinkWidget" modalWidgetName="ServiceZipModalWidget" /> @@ -107,25 +108,36 @@ @setMobileLocation="setMobileLocationQuestions" @set-mobile-location-invalid="setMobileLocationInValid" /> - - - + @@ -151,6 +163,9 @@ import mobileLocationModalQuestions from "@/layouts/service-location/mobile-loca import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question"; import shopQuestion from "@/layouts/service-location/shop-question/shop-question"; import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup"; +import buttonQuestion from "@/digital-components/button-question/button-question.vue"; +import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button"; +import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import navbar from "@/fmg-components/nav-bar/nav-bar"; @@ -236,6 +251,7 @@ export default { shopProviderData: null, navigatingForward: false, isMobileAddressValid: true, + shopListButton: shopListButton, }; }, async beforeRouteEnter(to, from, next) { @@ -443,6 +459,54 @@ export default { isForwardActionDisabled() { return this.displayNoShopsAlert || !this.isMobileAddressValid; }, + additionalButtonData() { + const startDate = new Date(); + const endDate = new Date(); + endDate.setDate(startDate.getDate() + 6); + + const formattedStartDate = startDate.toISOString().split("T")[0]; + const formattedEndDate = endDate.toISOString().split("T")[0]; + + return { + availabilityRatingCallback: getAvailabilityRating, + startDate: formattedStartDate, + endDate: formattedEndDate, + shopAppointmentType: this.selectedAppointmentType, + }; + }, + selectedShopAnswer() { + const toTitleCase = (str) => { + if (!str) return ""; + return str.replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + }; + + if (this.selectedProvider && this.selectedProvider.address) { + const provider = this.shopProviderData?.shopProviders?.find( + (p) => p.providerNumber === this.selectedProvider?.providerNumber + ); + const streetAddress = toTitleCase(this.selectedProvider.address.streetAddress); + const city = toTitleCase(this.selectedProvider.address.city); + const state = this.selectedProvider.address.state; + const zipCode = this.selectedProvider.address.zipCode; + const distanceInMiles = provider ? Math.round(provider.distanceInMiles * 2) / 2 : 0; + + return [ + { + buttonLabel: `${city}`, + buttonLabelSubCopy: `${distanceInMiles} mi`, + buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`, + //additionalButtonData: this.additionalButtonData, + value: this.selectedProvider.providerNumber, + }, + ]; + } + return []; + }, + questionText() { + return this.getCmsContent("YourSafeliteShopWidget", "QuestionText"); + }, }, methods: { arePagePrerequisitesValid() { @@ -726,8 +790,6 @@ export default { this.selectedProvider.address.zipCode = null; this.selectedProvider.address.zipCodeCtu = null; } else { - // if we have a provider.zipCodeCtu that's different than the servicelocation.zipCodeCtu then they - // may have selected a shop in a different ctu. change the servicelocation zip/ctu if different if (this.zipCodeCtu != this.selectedProvider.address.zipCodeCtu) { ctu = this.selectedProvider.address.zipCodeCtu; } @@ -777,12 +839,59 @@ export default { setMobileLocationInValid(isMobileLocationInValid) { this.isMobileAddressValid = !isMobileLocationInValid; }, - onShopSelected(providerNumber) { - // Find the provider object from your shopProviderData - const selectedProvider = this.shopProviderData.shopProviders.find( - (p) => p.providerNumber === providerNumber + onShopSelected(providerObject) { + const current = this.shopProviderData?.shopProviders?.find( + (p) => p.providerNumber === this.selectedProvider?.providerNumber ); - this.selectedProvider = selectedProvider; + if (current) { + this.selectedProvider = current; + } + + let provider = this.shopProviderData?.shopProviders?.find( + (p) => p.providerNumber === providerObject?.providerNumber + ); + if (!provider && this.shopProviderData?.shopProviders?.length > 0) { + provider = this.shopProviderData.shopProviders[0]; + } + this.selectedProvider = provider; + if (provider && provider.address) { + this.zipCode = provider.address.zipCode; + this.state = provider.address.state; + } + if ( + !this.selectedProvider || + !this.shopProviderData?.shopProviders?.some( + (p) => p.providerNumber === this.selectedProvider?.providerNumber + ) + ) { + this.selectedProvider = this.shopProviderData?.shopProviders?.[0]; + } + if (providerObject && providerObject.address) { + this.zipCode = providerObject.address.zipCode; + this.state = providerObject.address.state; + } + }, + setZipcodeCtu(zipcodeCtu) { + this.zipCodeCtu = zipcodeCtu; + }, + onShopModelUpdated(newModel) { + if (newModel.zipCode) { + this.zipCode = newModel.zipCode; + } + if (newModel.zipCodeCtu) { + this.zipCodeCtu = newModel.zipCodeCtu; + } + if (newModel.state) { + this.state = newModel.state; + } + if (this.shopProviderData && newModel.selectedProviderNumber) { + const provider = this.shopProviderData.shopProviders.find( + (p) => p.providerNumber === newModel.selectedProviderNumber + ); + if (provider) { + this.selectedProvider = provider; + } + } }, }, watch: { @@ -796,7 +905,7 @@ export default { this.shopProviderData.mobileProviderNumber ); } else { - this.selectedProvider = new Provider(); + this.isMobileAddressValid = true; } }); } @@ -819,7 +928,7 @@ export default { } this.$refs.mobileLocationQuestions.resetAlerts(); } else { - this.selectedProvider = new Provider(); + this.selectedProvider = this.shopProviderData.shopProviders[0]; } }, }, @@ -835,8 +944,8 @@ export default { Form, loadingModal, contentGroupModal, - //shopQuestion, shopQuestionPopup, + buttonQuestion, }, }; @@ -861,4 +970,8 @@ export default { color: $black; } } +.shop-question-button .list-button-content { + background: #e4f1f7 !important; + box-shadow: 0 0 0 1px $primary !important; +} diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue b/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue index a0a43dc14..8fe355923 100644 --- a/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue +++ b/src/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue @@ -7,6 +7,8 @@ questionAlignment="center" cornerStyle="rounded" mask="#####" + :includeSearchIcon="includeSearchIcon" + @search-icon-click="$emit('search-icon-click')" :displayQuestionText="false" :isRequired="isRequired" :validationRules="computedValidationRules" /> @@ -35,6 +37,10 @@ export default { type: Boolean, default: true, }, + includeSearchIcon: { + type: Boolean, + default: false, + }, }, computed: { value: { diff --git a/src/layouts/service-location/shop-question/shop-question-popup.vue b/src/layouts/service-location/shop-question/shop-question-popup.vue index c3ea91faf..8da980863 100644 --- a/src/layouts/service-location/shop-question/shop-question-popup.vue +++ b/src/layouts/service-location/shop-question/shop-question-popup.vue @@ -8,29 +8,6 @@ cmsWidgetName="AlertDropoffInformationWidget" alertClass="alert-info" v-bind:isDismissible="false" /> - - @@ -149,6 +156,11 @@ export default { required: false, default: false, }, + insertOrLabelBeforeFinalOption: { + type: Boolean, + required: false, + default: false, + }, }, setup(props) { const propsClone = Object.assign({}, props); @@ -390,4 +402,16 @@ export default { } } } +.or-label { + width: 100%; + text-align: center; + border-bottom: 1px solid black; + line-height: 1px; + margin: 10px 0 20px; + + span { + background: #fff; + padding: 0 10px; + } +} diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue index 3d517fd85..642c90174 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -1373,7 +1373,7 @@ export default { } } } - :deep(.button-inner-wrapper) { + :deep(.timeslots .button-inner-wrapper) { display: flex; max-width: 100%; flex-wrap: wrap; diff --git a/src/layouts/schedule/time-slot-question/time-slot-question.vue b/src/layouts/schedule/time-slot-question/time-slot-question.vue index 58648746e..e3d2ef691 100644 --- a/src/layouts/schedule/time-slot-question/time-slot-question.vue +++ b/src/layouts/schedule/time-slot-question/time-slot-question.vue @@ -7,11 +7,30 @@ justifyText="center" typeStyle="small" class="duration-text-block" /> + +
+ +
+
{ + return this.isDropOffRouteCode(timeSlot.id); + }) + .map((timeSlot) => { + let buttonLabelValue; + if (timeSlot.id.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { + buttonLabelValue = this.overnightDropoffButtonText; + } else if (this.isSameDay) { + buttonLabelValue = this.sameDayDropoffButtonText; + } else { + buttonLabelValue = this.dropoffButtonText; + } + return { + value: timeSlot.id, + buttonLabel: buttonLabelValue, + }; + }); + if ( + this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate.timeSlots) + .length !== 0 + ) { + dropOffQuestionAnswers.push({ + value: PICK_A_TIME_BUTTON_VALUE, + buttonLabel: "Pick a time", + }); + } + return dropOffQuestionAnswers; + }, hasWaitListExperiment() { return experimentMixin.methods.hasSettingEqualTo( experimentSettings.DISPLAY_WAITLIST, @@ -360,6 +411,33 @@ export default { isDropOffAppointment() { return this.appointmentType === AppointmentTypeStrings.DROP_OFF; }, + shouldDisplayDropOffAlert() { + return this.isDropOffRouteCode(this.selectedAnswerForDropOff); + }, + dropOffTimeSlotAlertCMSWidgetName() { + if (this.selectedAnswerForDropOff.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { + return "OvernightDropOffTimeSlotAlert"; + } else if (this.isSameDay) { + return "SameDayDropOffTimeSlotAlert"; + } else { + return "DropOffTimeSlotAlert"; + } + }, + isDropOffAppointmentAvailable() { + console.log("its running"); + + return ( + this.answersForDropOffQuestion !== null && + this.answersForDropOffQuestion.length !== 0 && + this.answersForDropOffQuestion[0].value !== PICK_A_TIME_BUTTON_VALUE + ); + }, + shouldDisplayTimeSlotQuestion() { + return ( + this.selectedAnswerForDropOff == PICK_A_TIME_BUTTON_VALUE || + !this.isDropOffAppointmentAvailable + ); + }, }, methods: { async setSelectedTimeSlot() { @@ -382,31 +460,17 @@ export default { } }, getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) { - return timeSlotsForSelectedDate.map((timeSlot) => { - const readableTime = militaryToTwelveHourTime(timeSlot.startTime); - return { - value: timeSlot.id, - buttonLabel: readableTime, - }; - }); - }, - getAvailableTimeSlotsForDropOff(timeSlotsForSelectedDate) { - const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => { - let buttonLabelValue; - if (timeSlot.id.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { - buttonLabelValue = this.overnightDropoffButtonText; - } else if (this.isSameDay) { - buttonLabelValue = this.sameDayDropoffButtonText; - } else { - buttonLabelValue = this.dropoffButtonText; - } - return { - value: timeSlot.id, - buttonLabel: buttonLabelValue, - }; - }); - - return availableTimeSlots; + return timeSlotsForSelectedDate + .filter((timeSlot) => { + return !this.isDropOffRouteCode(timeSlot.id); + }) + .map((timeSlot) => { + const readableTime = militaryToTwelveHourTime(timeSlot.startTime); + return { + value: timeSlot.id, + buttonLabel: readableTime, + }; + }); }, getAvailableTimeSlotsForMobile(timeSlotsForSelectedDate) { const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => { @@ -463,9 +527,14 @@ export default { return store.getters.order.customer?.waitListRequested; }, autoSelectTimeSlotIfOnlyOneIsAvailable() { - const numberOfOptions = this.availableTimeSlots?.length; + console.log(this.timeSlotsForSelectedDate.timeSlots?.length); + const numberOfOptions = this.timeSlotsForSelectedDate.timeSlots?.length; if (numberOfOptions === 1) { - this.selectedRouteCode = this.availableTimeSlots[0].value; + if (this.availableTimeSlots.length > 0) { + this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value; + } else { + this.selectedAnswerForDropOff = this.answersForDropOffQuestion[0].value; + } } }, addPremiumFlagToInput(routeCode) { @@ -519,6 +588,12 @@ export default { successMessageElement.scrollIntoView({ behavior: "smooth" }); } }, + isDropOffRouteCode(routeCode) { + return ( + routeCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF) || + routeCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF) + ); + }, }, watch: { waitListRequested(newVal) { @@ -545,12 +620,24 @@ export default { this.setSelectedTimeSlot(); } }, + selectedAnswerForDropOff(newValue) { + if (newValue == PICK_A_TIME_BUTTON_VALUE) { + this.selectedRouteCode = null; + } else { + this.selectedAnswerForTimeSlots = null; + this.selectedRouteCode = newValue; + } + }, + selectedAnswerForTimeSlots(newValue) { + if (newValue) this.selectedRouteCode = newValue; + }, }, components: { // Removed modal textBlock, buttonQuestion, checkboxQuestion, + alert, }, }; @@ -623,5 +710,23 @@ export default { text-align: left; margin: 1.5rem 0 0; } + .drop-off-alert { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_13957_112512)'%3E%3Cpath d='M5.99865 0C4.81147 4.82643e-07 3.65095 0.352111 2.66392 1.01179C1.67688 1.67146 0.907678 2.60907 0.45361 3.70599C-0.000459241 4.80291 -0.11899 6.00986 0.113013 7.17415C0.345015 8.33845 0.917126 9.40778 1.75697 10.2469C2.59682 11.086 3.66666 11.6571 4.83117 11.8881C5.99567 12.119 7.20251 11.9994 8.29902 11.5443C9.39553 11.0893 10.3324 10.3192 10.9912 9.33159C11.65 8.34396 12.0011 7.18313 12 5.99594C11.9971 4.40566 11.3638 2.88142 10.2388 1.75742C9.11375 0.633431 7.58894 0.00143011 5.99865 0V0ZM5.99865 11.2478C4.96135 11.2473 3.94748 10.9392 3.08518 10.3627C2.22288 9.7861 1.55085 8.96685 1.15401 8.00846C0.75718 7.05006 0.653353 5.99554 0.855656 4.97815C1.05796 3.96077 1.55731 3.02618 2.29061 2.29251C3.0239 1.55884 3.95823 1.059 4.97551 0.856176C5.99279 0.653349 7.04737 0.756633 8.00597 1.15297C8.96457 1.54931 9.78416 2.22092 10.3612 3.08293C10.9382 3.94493 11.2467 4.95864 11.2478 5.99594C11.2478 7.38835 10.6949 8.72377 9.71053 9.70861C8.7262 10.6934 7.39106 11.2471 5.99865 11.2478V11.2478Z' fill='%2306577C'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.22736 8.84695C6.30613 8.76818 6.35038 8.66135 6.35038 8.54996V5.30996C6.35038 5.19857 6.30613 5.09174 6.22736 5.01298C6.1486 4.93421 6.04177 4.88996 5.93038 4.88996C5.81899 4.88996 5.71216 4.93421 5.63339 5.01298C5.55463 5.09174 5.51038 5.19857 5.51038 5.30996V8.54996C5.51038 8.66135 5.55463 8.76818 5.63339 8.84695C5.71216 8.92571 5.81899 8.96996 5.93038 8.96996C6.04177 8.96996 6.1486 8.92571 6.22736 8.84695ZM5.69704 3.97918C5.76611 4.02533 5.84731 4.04996 5.93038 4.04996C5.98558 4.05012 6.04026 4.03936 6.09129 4.01831C6.14232 3.99726 6.18868 3.96633 6.22771 3.9273C6.26675 3.88827 6.29768 3.8419 6.31873 3.79088C6.33978 3.73985 6.35053 3.68516 6.35038 3.62996C6.35038 3.54689 6.32574 3.46569 6.27959 3.39662C6.23344 3.32755 6.16785 3.27372 6.0911 3.24193C6.01436 3.21014 5.92991 3.20183 5.84844 3.21803C5.76697 3.23424 5.69213 3.27424 5.63339 3.33298C5.57465 3.39171 5.53465 3.46655 5.51845 3.54802C5.50224 3.6295 5.51056 3.71394 5.54235 3.79069C5.57414 3.86743 5.62797 3.93303 5.69704 3.97918Z' fill='%2306577C'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_13957_112512'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; + background-size: 0.75rem; + background-position: 0.5rem 0.75rem; + border-radius: 0.5rem; + display: flex; + flex-direction: row; + padding: 0.5rem 0.5rem 0.5rem 1.5rem; + gap: 0.25rem; + background-color: #e4f1f7; + + .alert-heading { + text-align: left; + font-size: 0.75rem; + line-height: 1.25rem; + } + } } diff --git a/src/layouts/service-location/shop-question/shop-question.vue b/src/layouts/service-location/shop-question/shop-question.vue index 5a9278d78..7191a1f07 100644 --- a/src/layouts/service-location/shop-question/shop-question.vue +++ b/src/layouts/service-location/shop-question/shop-question.vue @@ -277,23 +277,24 @@ export default { margin-top: 0.5rem; } } -} -.drop-off-alert { - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_13957_112512)'%3E%3Cpath d='M5.99865 0C4.81147 4.82643e-07 3.65095 0.352111 2.66392 1.01179C1.67688 1.67146 0.907678 2.60907 0.45361 3.70599C-0.000459241 4.80291 -0.11899 6.00986 0.113013 7.17415C0.345015 8.33845 0.917126 9.40778 1.75697 10.2469C2.59682 11.086 3.66666 11.6571 4.83117 11.8881C5.99567 12.119 7.20251 11.9994 8.29902 11.5443C9.39553 11.0893 10.3324 10.3192 10.9912 9.33159C11.65 8.34396 12.0011 7.18313 12 5.99594C11.9971 4.40566 11.3638 2.88142 10.2388 1.75742C9.11375 0.633431 7.58894 0.00143011 5.99865 0V0ZM5.99865 11.2478C4.96135 11.2473 3.94748 10.9392 3.08518 10.3627C2.22288 9.7861 1.55085 8.96685 1.15401 8.00846C0.75718 7.05006 0.653353 5.99554 0.855656 4.97815C1.05796 3.96077 1.55731 3.02618 2.29061 2.29251C3.0239 1.55884 3.95823 1.059 4.97551 0.856176C5.99279 0.653349 7.04737 0.756633 8.00597 1.15297C8.96457 1.54931 9.78416 2.22092 10.3612 3.08293C10.9382 3.94493 11.2467 4.95864 11.2478 5.99594C11.2478 7.38835 10.6949 8.72377 9.71053 9.70861C8.7262 10.6934 7.39106 11.2471 5.99865 11.2478V11.2478Z' fill='%2306577C'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.22736 8.84695C6.30613 8.76818 6.35038 8.66135 6.35038 8.54996V5.30996C6.35038 5.19857 6.30613 5.09174 6.22736 5.01298C6.1486 4.93421 6.04177 4.88996 5.93038 4.88996C5.81899 4.88996 5.71216 4.93421 5.63339 5.01298C5.55463 5.09174 5.51038 5.19857 5.51038 5.30996V8.54996C5.51038 8.66135 5.55463 8.76818 5.63339 8.84695C5.71216 8.92571 5.81899 8.96996 5.93038 8.96996C6.04177 8.96996 6.1486 8.92571 6.22736 8.84695ZM5.69704 3.97918C5.76611 4.02533 5.84731 4.04996 5.93038 4.04996C5.98558 4.05012 6.04026 4.03936 6.09129 4.01831C6.14232 3.99726 6.18868 3.96633 6.22771 3.9273C6.26675 3.88827 6.29768 3.8419 6.31873 3.79088C6.33978 3.73985 6.35053 3.68516 6.35038 3.62996C6.35038 3.54689 6.32574 3.46569 6.27959 3.39662C6.23344 3.32755 6.16785 3.27372 6.0911 3.24193C6.01436 3.21014 5.92991 3.20183 5.84844 3.21803C5.76697 3.23424 5.69213 3.27424 5.63339 3.33298C5.57465 3.39171 5.53465 3.46655 5.51845 3.54802C5.50224 3.6295 5.51056 3.71394 5.54235 3.79069C5.57414 3.86743 5.62797 3.93303 5.69704 3.97918Z' fill='%2306577C'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_13957_112512'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A"); - background-repeat: no-repeat; - background-size: 0.75rem; - background-position: 0.5rem 0.75rem; - border-radius: 0.5rem; - display: flex; - flex-direction: row; - padding: 0.5rem 0.5rem 0.5rem 1.5rem; - gap: 0.25rem; + .drop-off-alert { + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_13957_112512)'%3E%3Cpath d='M5.99865 0C4.81147 4.82643e-07 3.65095 0.352111 2.66392 1.01179C1.67688 1.67146 0.907678 2.60907 0.45361 3.70599C-0.000459241 4.80291 -0.11899 6.00986 0.113013 7.17415C0.345015 8.33845 0.917126 9.40778 1.75697 10.2469C2.59682 11.086 3.66666 11.6571 4.83117 11.8881C5.99567 12.119 7.20251 11.9994 8.29902 11.5443C9.39553 11.0893 10.3324 10.3192 10.9912 9.33159C11.65 8.34396 12.0011 7.18313 12 5.99594C11.9971 4.40566 11.3638 2.88142 10.2388 1.75742C9.11375 0.633431 7.58894 0.00143011 5.99865 0V0ZM5.99865 11.2478C4.96135 11.2473 3.94748 10.9392 3.08518 10.3627C2.22288 9.7861 1.55085 8.96685 1.15401 8.00846C0.75718 7.05006 0.653353 5.99554 0.855656 4.97815C1.05796 3.96077 1.55731 3.02618 2.29061 2.29251C3.0239 1.55884 3.95823 1.059 4.97551 0.856176C5.99279 0.653349 7.04737 0.756633 8.00597 1.15297C8.96457 1.54931 9.78416 2.22092 10.3612 3.08293C10.9382 3.94493 11.2467 4.95864 11.2478 5.99594C11.2478 7.38835 10.6949 8.72377 9.71053 9.70861C8.7262 10.6934 7.39106 11.2471 5.99865 11.2478V11.2478Z' fill='%2306577C'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.22736 8.84695C6.30613 8.76818 6.35038 8.66135 6.35038 8.54996V5.30996C6.35038 5.19857 6.30613 5.09174 6.22736 5.01298C6.1486 4.93421 6.04177 4.88996 5.93038 4.88996C5.81899 4.88996 5.71216 4.93421 5.63339 5.01298C5.55463 5.09174 5.51038 5.19857 5.51038 5.30996V8.54996C5.51038 8.66135 5.55463 8.76818 5.63339 8.84695C5.71216 8.92571 5.81899 8.96996 5.93038 8.96996C6.04177 8.96996 6.1486 8.92571 6.22736 8.84695ZM5.69704 3.97918C5.76611 4.02533 5.84731 4.04996 5.93038 4.04996C5.98558 4.05012 6.04026 4.03936 6.09129 4.01831C6.14232 3.99726 6.18868 3.96633 6.22771 3.9273C6.26675 3.88827 6.29768 3.8419 6.31873 3.79088C6.33978 3.73985 6.35053 3.68516 6.35038 3.62996C6.35038 3.54689 6.32574 3.46569 6.27959 3.39662C6.23344 3.32755 6.16785 3.27372 6.0911 3.24193C6.01436 3.21014 5.92991 3.20183 5.84844 3.21803C5.76697 3.23424 5.69213 3.27424 5.63339 3.33298C5.57465 3.39171 5.53465 3.46655 5.51845 3.54802C5.50224 3.6295 5.51056 3.71394 5.54235 3.79069C5.57414 3.86743 5.62797 3.93303 5.69704 3.97918Z' fill='%2306577C'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_13957_112512'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; + background-size: 0.75rem; + background-position: 0.5rem 0.75rem; + border-radius: 0.5rem; + display: flex; + flex-direction: row; + padding: 0.5rem 0.5rem 0.5rem 1.5rem; + gap: 0.25rem; + background-color: #e4f1f7; - .alert-heading { - text-align: left; - font-size: 0.75rem; - line-height: 1.25rem; + .alert-heading { + text-align: left; + font-size: 0.75rem; + line-height: 1.25rem; + } } } From b311f6d41c26eec32e13694901d6d8e668bf8d31 Mon Sep 17 00:00:00 2001 From: Bryan Mauger Date: Thu, 3 Jul 2025 10:33:25 -0400 Subject: [PATCH 32/75] CASH-959 update hop font-weights. --- public/css/hop-styling.css | 9 +++++---- public/scss/hop-styling.scss | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/public/css/hop-styling.css b/public/css/hop-styling.css index a79c2f049..41fc1edd5 100644 --- a/public/css/hop-styling.css +++ b/public/css/hop-styling.css @@ -12,7 +12,7 @@ body { padding: 8px; } body h3 { - font-weight: 600; + font-weight: 700; } body .validation-info { display: none; @@ -35,7 +35,7 @@ body .form-group { body .headerlinecontainer .headerline1 { font-size: 20px; line-height: 32px; - font-weight: 500; + font-weight: 400; margin: 24px 0; text-align: center; } @@ -96,7 +96,7 @@ body .creditCardSpecific div .headerlinecontainer { display: none; } body .creditCardSpecific div label { - font-weight: 600; + font-weight: 500; } body .creditCardSpecific #infoRow2 { margin-bottom: 0; @@ -140,6 +140,7 @@ body .buttonContainer button { border-radius: 8px; color: #fff; justify-content: center; + font-family: Urbanist, Arial, Helvetica, sans-serif; font-weight: 500; font-size: 16px; height: 48px; @@ -272,7 +273,7 @@ body .cc-error-container #alert-message { #fmg-checkout-shared #other-payments-container label span.paymentOptionText { margin: auto 0; padding-left: 0; - font-weight: 600; + font-weight: 400; } #fmg-checkout-shared #other-payments-container #afterpayParentLink label > span:first-of-type { width: 100%; diff --git a/public/scss/hop-styling.scss b/public/scss/hop-styling.scss index 3d30255cc..bb38cf6b9 100644 --- a/public/scss/hop-styling.scss +++ b/public/scss/hop-styling.scss @@ -12,7 +12,7 @@ body { padding: 8px; h3 { - font-weight: 600; + font-weight: 700; } .validation-info { @@ -42,7 +42,7 @@ body { .headerline1 { font-size: 20px; line-height: 32px; - font-weight: 500; + font-weight: 400; margin: 24px 0; text-align: center; } @@ -103,7 +103,7 @@ body { display: none; } label { - font-weight: 600; + font-weight: 500; } } #infoRow2 { @@ -151,6 +151,7 @@ body { border-radius: 8px; color: #fff; justify-content: center; + font-family: Urbanist, Arial, Helvetica, sans-serif; font-weight: 500; font-size: 16px; height: 48px; @@ -330,7 +331,7 @@ body { &.paymentOptionText { margin: auto 0; padding-left: 0; - font-weight: 600; + font-weight: 400; } } } From e3e6b27148c531941290b37b5704d07e10ad7cd5 Mon Sep 17 00:00:00 2001 From: Bryan Mauger Date: Thu, 3 Jul 2025 13:01:32 -0400 Subject: [PATCH 33/75] CASH-1127 update hop font. --- public/css/hop-styling.css | 2 ++ public/scss/hop-styling.scss | 2 ++ 2 files changed, 4 insertions(+) diff --git a/public/css/hop-styling.css b/public/css/hop-styling.css index 41fc1edd5..0318d52ff 100644 --- a/public/css/hop-styling.css +++ b/public/css/hop-styling.css @@ -51,6 +51,8 @@ body select { font-size: 16px; color: #000; background-color: #fff; + font-family: Urbanist, Arial, Helvetica, sans-serif; + font-weight: 400; } body input:focus, body input:focus-visible, body select:focus, diff --git a/public/scss/hop-styling.scss b/public/scss/hop-styling.scss index bb38cf6b9..aae9913e2 100644 --- a/public/scss/hop-styling.scss +++ b/public/scss/hop-styling.scss @@ -61,6 +61,8 @@ body { font-size: 16px; color: #000; background-color: #fff; + font-family: Urbanist, Arial, Helvetica, sans-serif; + font-weight: 400; &:focus, &:focus-visible { box-shadow: 0 0 0 2.5px #1574a1; From 76d67981f6a6e3dd430a1ddbb9288062f519bac8 Mon Sep 17 00:00:00 2001 From: AdamCaouetteSafelite Date: Thu, 3 Jul 2025 14:44:39 -0400 Subject: [PATCH 34/75] CASH-845 router change refactor --- src/router/constants/routes.js | 9 ++++----- src/router/methods/helpers/handle-heritage-return.js | 9 --------- src/router/methods/route-logic/service-location.js | 8 ++++++++ src/router/methods/routes.js | 4 ++-- 4 files changed, 14 insertions(+), 16 deletions(-) create mode 100644 src/router/methods/route-logic/service-location.js diff --git a/src/router/constants/routes.js b/src/router/constants/routes.js index 45fdfb243..6fec2455d 100644 --- a/src/router/constants/routes.js +++ b/src/router/constants/routes.js @@ -55,11 +55,10 @@ export const routeData = { name: "insurance-company", path: "/insurance-company", }, - // TODO: REMOVE THIS COMMENT AND BELOW, ONCE CASH-803 (SERVICE-LOCATION AND SCHEDULE PAGE COMBINATION) HAS BEEN VETTED - // SERVICE_LOCATION: { - // name: "service-location", - // path: "/service-location", - // }, + SERVICE_LOCATION: { // keep even though this page has been removed from the regular flow bc Heritage still points to it + name: "service-location", + path: "/service-location", + }, HERITAGE: { name: "heritage", path: "/virtual/heritage", diff --git a/src/router/methods/helpers/handle-heritage-return.js b/src/router/methods/helpers/handle-heritage-return.js index 6d8c60a58..74a1ce62a 100644 --- a/src/router/methods/helpers/handle-heritage-return.js +++ b/src/router/methods/helpers/handle-heritage-return.js @@ -1,7 +1,6 @@ import { queryStrings } from "@/constants/query-strings"; import { consumeReferralQuerystrings } from "@/router/methods/helpers/consume-referral-info"; import { loadSessionIfPresent } from "@/helpers/heritage-integration/order-helper"; -import router from "@/router"; import { routeData } from "@/router/constants/routes"; export async function handleHeritageReturn(to, from) { @@ -13,13 +12,5 @@ export async function handleHeritageReturn(to, from) { // load session with new cookie await loadSessionIfPresent(true, routeData.SCHEDULE.name); - - // Temporary redirect until URLs to old service-location page can be updated in Heritage - if (to.fullPath.includes("fmgPage=service-location")) { - // Redirect to Schedule page - router.push({ - name: routeData.SCHEDULE.name, - }); - } } } diff --git a/src/router/methods/route-logic/service-location.js b/src/router/methods/route-logic/service-location.js new file mode 100644 index 000000000..ecaebe149 --- /dev/null +++ b/src/router/methods/route-logic/service-location.js @@ -0,0 +1,8 @@ +import { routeData } from "@/router/constants/routes"; + +export async function serviceLocationBeforeEnter(to, from) { + return { + name: routeData.SCHEDULE.name, + replace: true, + }; +} diff --git a/src/router/methods/routes.js b/src/router/methods/routes.js index 83fbb3811..97af04068 100644 --- a/src/router/methods/routes.js +++ b/src/router/methods/routes.js @@ -12,6 +12,7 @@ import { errorBeforeEnter } from "@/router/methods/route-logic/error"; import { restartBeforeEnter } from "@/router/methods/route-logic/restart"; import { paymentMethodBeforeEnter } from "@/router/methods/route-logic/payment-method"; import { paymentBeforeEnter } from "@/router/methods/route-logic/payment"; +import { serviceLocationBeforeEnter } from "@/router/methods/route-logic/service-location"; export const routes = [ // Non-virtual pages. @@ -29,8 +30,7 @@ export const routes = [ createRoute(routeData.CAPABILITY_QUESTIONS), createRoute(routeData.QUOTE, quoteBeforeEnter), createRoute(routeData.INSURANCE_COMPANY, insuranceCompanyBeforeEnter), - // TODO: REMOVE THIS COMMENT AND BELOW, ONCE CASH-803 (SERVICE-LOCATION AND SCHEDULE PAGE COMBINATION) HAS BEEN VETTED - // createRoute(routeData.SERVICE_LOCATION), + createRoute(routeData.SERVICE_LOCATION, serviceLocationBeforeEnter), createRoute(routeData.SCHEDULE), createRoute(routeData.CUSTOMER_DETAILS), createRoute(routeData.PAYMENT_METHOD, paymentMethodBeforeEnter), From 13b8e7f7373d95b42c1e2bff8a03d5b4994070f6 Mon Sep 17 00:00:00 2001 From: AdamCaouetteSafelite Date: Thu, 3 Jul 2025 14:46:48 -0400 Subject: [PATCH 35/75] CASH-845 restore handle-heritage-return completely --- src/router/methods/helpers/handle-heritage-return.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/router/methods/helpers/handle-heritage-return.js b/src/router/methods/helpers/handle-heritage-return.js index 74a1ce62a..7914c8e0e 100644 --- a/src/router/methods/helpers/handle-heritage-return.js +++ b/src/router/methods/helpers/handle-heritage-return.js @@ -11,6 +11,6 @@ export async function handleHeritageReturn(to, from) { await consumeReferralQuerystrings(); // load session with new cookie - await loadSessionIfPresent(true, routeData.SCHEDULE.name); + await loadSessionIfPresent(true, routeData.SERVICE_LOCATION.name); } } From fc088ecd08a65907e61171bc3d7c5140b0a575ec Mon Sep 17 00:00:00 2001 From: AdamCaouetteSafelite Date: Thu, 3 Jul 2025 14:49:59 -0400 Subject: [PATCH 36/75] CASH-845: prettier change --- src/router/constants/routes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/router/constants/routes.js b/src/router/constants/routes.js index 6fec2455d..5632fe357 100644 --- a/src/router/constants/routes.js +++ b/src/router/constants/routes.js @@ -55,7 +55,8 @@ export const routeData = { name: "insurance-company", path: "/insurance-company", }, - SERVICE_LOCATION: { // keep even though this page has been removed from the regular flow bc Heritage still points to it + SERVICE_LOCATION: { + // keep even though this page has been removed from the regular flow bc Heritage still points to it name: "service-location", path: "/service-location", }, From 9f37c2cf42dd547eeef234af55c16c2ff9b96b3e Mon Sep 17 00:00:00 2001 From: AdamCaouetteSafelite Date: Thu, 3 Jul 2025 15:17:05 -0400 Subject: [PATCH 37/75] CASH-845 stage 2: update pagePrerequisites --- src/layouts/schedule/schedule.spec.js | 10 ++- src/layouts/schedule/schedule.vue | 90 ++++++++++++++++++++------- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js index 60d20b6e7..b3404f611 100644 --- a/src/layouts/schedule/schedule.spec.js +++ b/src/layouts/schedule/schedule.spec.js @@ -176,6 +176,9 @@ beforeEach(() => { isRepair: false, }, referralNumber: "1234567", + policy: { + policyNumber: "123", + }, }, payment: { isInsurance: true, @@ -195,11 +198,12 @@ afterEach(() => { describe("schedule.vue...", () => { describe("initial load", () => { - test("should pass arePagePrerequisitesValid with a mobile order and no providerNumber", () => { + test("should pass arePagePrerequisitesValid with a mobile CASH order and no providerNumber", () => { // Arrange const { wrapper } = setupMocks({}); store.getters.order.serviceLocation.appointmentType = "Mobile"; - store.getters.order.serviceLocation.provider = null; + store.getters.order.serviceLocation.provider.policyNumber = null; + store.getters.payment.isInsurance = false; // Act const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); @@ -208,7 +212,7 @@ describe("schedule.vue...", () => { expect(arePagePrerequisitesValid).toBe(true); }); - test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => { + test("should pass arePagePrerequisitesValid with an inshop order and providerNumber", () => { // Arrange const { wrapper } = setupMocks({}); diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index ee2096c36..5de235275 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -91,6 +91,7 @@ import { getItemsWithoutRecalParts } from "@/helpers/recal-helper"; import { partNumberStrings } from "@/constants/part-number-strings"; import { deepClone } from "@/helpers/object-helper"; import { debugLog } from "@/helpers/debug-log-helper"; +import { applicationConfig } from "@/constants/application-config"; // DEFINE VALIDATION RULES defineRule("date-required", required(errorMessages.DATE_REQUIRED)); @@ -367,12 +368,13 @@ export default { const resultMap = await settleAllPromises(promiseResultMap); - const pricingByDayUpcharge = showPricingByDay - ? await baseMixin.methods.getTotalLineItemPrice( - resultMap.pricingByDayUpchargePart, - false - ) - : null; + const pricingByDayUpcharge = + showPricingByDay && resultMap.pricingByDayUpchargePart + ? await baseMixin.methods.getTotalLineItemPrice( + resultMap.pricingByDayUpchargePart, + false + ) + : null; const datePickerInitialData = resultMap.datePickerInitialData; datePickerInitialData.pricingByDayBasePrice = pricingByDayBasePrice; @@ -480,32 +482,76 @@ export default { methods: { splitCopyOnCMSPlaceHolder, arePagePrerequisitesValid() { - const serviceLocation = store.getters.order.serviceLocation; // TODO - MAY NEED TO REMOVE THIS AS A PREREQ - - const serviceLocationPreReqs = serviceLocation.zipCode && serviceLocation.zipCodeCtu; // TODO - MAY NEED TO ADD IN SERVICE LOCATION PREREQS HERE TOO - const paymentInfo = store.getters.payment.isInsurance !== null; - const damageInfo = store.getters.order.damage.isRepair || (store.getters.order.lineItems?.glassParts != null && store.getters.order.lineItems.glassParts.length > 0); + const parentAccountNumberInfo = + store.getters.payment.parentAccountNumber !== + applicationConfig.CASH_PARENT_ACCOUNT_NUMBER; + const policyNumberInfo = store.getters.order.policy.policyNumber; + const zipCodeInfo = store.getters.order.serviceLocation.zipCode; + const supportingItemsInfo = store.getters.lineItems.supportingItems; - const preReqResult = serviceLocationPreReqs && paymentInfo && damageInfo; + // insurance drops the recycle fee on replace orders so it won't be in supportingItems + const insurancePreReqResult = + parentAccountNumberInfo && + policyNumberInfo && + zipCodeInfo && + paymentInfo && + damageInfo; + const cashPreReqResult = + supportingItemsInfo && zipCodeInfo && paymentInfo && damageInfo; - // prettier-ignore - { + const outputDebugLog = (preReqResult) => { + // prettier-ignore debugLog("--- schedule.vue pagePrereqs start ---", null, !preReqResult); - debugLog("store.getters.order.serviceLocation.zipCode:", serviceLocation.zipCode, !preReqResult); - debugLog("store.getters.order.serviceLocation.zipCodeCtu:", serviceLocation.zipCodeCtu, !preReqResult); - debugLog("store.getters.order.serviceLocation.appointmentType:", serviceLocation.appointmentType, !preReqResult); - debugLog("store.getters.order.serviceLocation.provider.providerNumber:", serviceLocation.provider?.providerNumber, !preReqResult); - debugLog("store.getters.payment.isInsurance:", store.getters.payment?.isInsurance, !preReqResult); - debugLog("store.getters.order.damage.isRepair:", store.getters.order.damage?.isRepair, !preReqResult); - debugLog("store.getters.order.lineItems.glassParts:", store.getters.order.lineItems?.glassParts, !preReqResult); + debugLog( + "store.getters.payment.isInsurance:", + store.getters.payment?.isInsurance, + !preReqResult + ); + debugLog( + "store.getters.order.damage.isRepair:", + store.getters.order.damage?.isRepair, + !preReqResult + ); + debugLog( + "store.getters.order.lineItems.glassParts:", + store.getters.order.lineItems?.glassParts, + !preReqResult + ); + debugLog( + "store.getters.payment.parentAccountNumber:", + store.getters.payment.parentAccountNumber, + !preReqResult + ); + debugLog( + "store.getters.order.policy.policyNumber:", + store.getters.order.policy.policyNumber, + !preReqResult + ); + debugLog( + "store.getters.order.serviceLocation.zipCode:", + store.getters.order.serviceLocation.zipCode, + !preReqResult + ); + debugLog( + "store.getters.lineItems.supportingItems:", + store.getters.lineItems.supportingItems, + !preReqResult + ); debugLog("--- schedule.vue pagePrereqs end ---", null, !preReqResult); + }; + + if (store.getters.payment.isInsurance) { + outputDebugLog(insurancePreReqResult); + return insurancePreReqResult; + } else { + outputDebugLog(cashPreReqResult); + return cashPreReqResult; } - return preReqResult; }, async getMoreScheduleData(startDate, endDate) { // called only when "View more dates" is clicked; not on initial page load From c1a5dcdeef1c4fe8cd3ba82ddb48e0afe4971672 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Mon, 7 Jul 2025 12:13:08 -0400 Subject: [PATCH 38/75] CASH-814 | Styling, cleanup, auto select, CMS integration --- .../button-question/button-question.vue | 2 +- .../date-picker/date-picker.spec.js | 2 +- .../date-picker/date-picker.vue | 2 + .../time-slot-question/time-slot-question.vue | 79 +++++++++---------- 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index dc1f50c15..2eaf204ad 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -407,7 +407,7 @@ export default { text-align: center; border-bottom: 1px solid black; line-height: 1px; - margin: 10px 0 20px; + margin: 20px 0 20px; span { background: #fff; diff --git a/src/digital-components/date-picker/date-picker.spec.js b/src/digital-components/date-picker/date-picker.spec.js index e1fbf302f..09bf21d76 100644 --- a/src/digital-components/date-picker/date-picker.spec.js +++ b/src/digital-components/date-picker/date-picker.spec.js @@ -481,7 +481,7 @@ describe("date-picker.vue", () => { }, }); const spy = jest.spyOn(wrapper.vm, "setCalendarData"); - + wrapper.vm.$refs.timeSlotModalQuestion.initializeComponent = jest.fn(); wrapper.vm.$nextTick(); // Act diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue index e008021db..8f6b35f60 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -140,6 +140,7 @@ dropoffCmsWidgetName="DropOffTimeSlotModal" sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal" overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal" + dropOffOrPickATimeQuestionCmsWidgetName="DropOffOrPickATimeQuestionWidget" :selectedDate="selectedDate" :appointmentType="appointmentType" :premiumAppointmentFee="premiumAppointmentFee" @@ -292,6 +293,7 @@ export default { methods: { initializeComponent(initialData) { this.setCalendarData(initialData); + this.$refs.timeSlotModalQuestion.initializeComponent(); }, fireDateSelectedEvent(event, date) { // Ignore if arrow key selected radioButton diff --git a/src/layouts/schedule/time-slot-question/time-slot-question.vue b/src/layouts/schedule/time-slot-question/time-slot-question.vue index 2d67ddc90..80d22cae5 100644 --- a/src/layouts/schedule/time-slot-question/time-slot-question.vue +++ b/src/layouts/schedule/time-slot-question/time-slot-question.vue @@ -10,7 +10,7 @@ { - let buttonLabelValue; - if (timeSlot.id.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) { - buttonLabelValue = this.dropoffButtonText; - } else if (timeSlot.id.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { - buttonLabelValue = this.overnightDropoffButtonText; - } else if (this.isSameDay) { - buttonLabelValue = this.sameDayDropoffButtonText; - } else { - buttonLabelValue = militaryToTwelveHourTime(timeSlot.startTime); - } - return { - value: timeSlot.id, - buttonLabel: buttonLabelValue, - }; - }); - - return availableTimeSlots; - }, getAvailableTimeSlotsForMobile(timeSlotsForSelectedDate) { const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => { const readableTime = `${militaryToTwelveHourTime(timeSlot.startTime)} - ${militaryToTwelveHourTime( @@ -529,27 +520,27 @@ export default { }, }; }, - getSelectedRouteCode() { - let selectedRouteCode; + setSelectedRouteCodeFromParent() { if (!this.modelValue?.timeSlot) { - selectedRouteCode = null; + return; } - + const routeCodeFromParent = this.modelValue.timeSlot.routeCode; if (this.modelValue?.isPremiumAppointment) { - selectedRouteCode = this.addPremiumFlagToInput( - this.modelValue?.timeSlot?.routeCode - ); + // Mobile Only + this.selectedAnswerForTimeSlots = this.addPremiumFlagToInput(routeCodeFromParent); } else { - selectedRouteCode = this.modelValue?.timeSlot?.routeCode; + if (this.isDropOffRouteCode(routeCodeFromParent)) { + this.selectedAnswerForDropOff = routeCodeFromParent; + } else { + this.selectedAnswerForDropOff = PICK_A_TIME_BUTTON_VALUE; + this.selectedAnswerForTimeSlots = routeCodeFromParent; + } } - - return selectedRouteCode; }, getWaitListRequestedFromStore() { return store.getters.order.customer?.waitListRequested; }, autoSelectTimeSlotIfOnlyOneIsAvailable() { - console.log(this.timeSlotsForSelectedDate.timeSlots?.length); const numberOfOptions = this.timeSlotsForSelectedDate.timeSlots?.length; if (numberOfOptions === 1) { if (this.availableTimeSlots.length > 0) { @@ -570,7 +561,6 @@ export default { if (routeCodeIncludesPremium) { routeCode = this.removePremiumFlagFromInput(routeCode); } - const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find( (slot) => slot.id == routeCode ); @@ -611,6 +601,9 @@ export default { } }, isDropOffRouteCode(routeCode) { + if (!routeCode) { + return false; + } return ( routeCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF) || routeCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF) From ba1217d36c27c760efa7368f2d549a1162e8dda6 Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Tue, 8 Jul 2025 11:25:11 -0400 Subject: [PATCH 39/75] CASH-816 CASH-816 clean up, defect fixes, looks to be running smoothly now --- src/digital-components/modal/modal.vue | 5 + .../service-location/service-location.vue | 23 +- .../shop-question/shop-question-popup.vue | 291 +++++++++++------- 3 files changed, 183 insertions(+), 136 deletions(-) diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue index 4cc75f701..55feca36f 100644 --- a/src/digital-components/modal/modal.vue +++ b/src/digital-components/modal/modal.vue @@ -36,6 +36,7 @@ :id="modalId + '-modalbtn'" ref="modalButtonMain" loaderColor="white" + :isDisabled="footerButtonDisabled" :buttonText="footerButtonText" @click-event="validateAndEmit" :class="isFooterButtonDisabled && 'form-test-invalid'" /> @@ -59,6 +60,7 @@ export default { footerButtonText: String, suppressPageScroll: Boolean, staticBackdrop: Boolean, + footerButtonDisabled: Boolean, onModalOpenedCallback: { type: Function, }, @@ -137,6 +139,9 @@ export default { }, computed: { isFooterButtonDisabled() { + if (this.footerButtonDisabled !== undefined) { + return this.footerButtonDisabled; + } if (!this.meta.touched) { return !this.meta.valid; } diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 776fe1199..329b99840 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -115,9 +115,9 @@ :selectedAppointmentType="selectedAppointmentType" :modelValue="{ zipCode: zipCode, - providerNumber: selectedProvider?.providerNumber, + state: state, }" - :preselectedProviderNumber="selectedProvider?.providerNumber" + :preSelectedProviderNumber="selectedProvider?.providerNumber" :shopProviderData="shopProviderData" :isServiceableInshop="isServiceableInshop" @updated-serviceability="setServiceabilityDetails" @@ -844,25 +844,10 @@ export default { let provider = this.shopProviderData?.shopProviders?.find( (p) => p.providerNumber === providerObject?.providerNumber ); - if (!provider && this.shopProviderData?.shopProviders?.length > 0) { - provider = this.shopProviderData.shopProviders[0]; - } this.selectedProvider = provider; - if (provider && provider.address) { - this.zipCode = provider.address.zipCode; - this.state = provider.address.state; - } - if ( - !this.selectedProvider || - !this.shopProviderData?.shopProviders?.some( - (p) => p.providerNumber === this.selectedProvider?.providerNumber - ) - ) { - this.selectedProvider = this.shopProviderData?.shopProviders?.[0]; - } if (providerObject && providerObject.address) { - this.zipCode = providerObject.address.zipCode; - this.state = providerObject.address.state; + this.zipCode = providerObject.searchedZip; + this.state = providerObject.searchedState; } }, setZipcodeCtu(zipcodeCtu) { diff --git a/src/layouts/service-location/shop-question/shop-question-popup.vue b/src/layouts/service-location/shop-question/shop-question-popup.vue index b345d370d..9f7507d77 100644 --- a/src/layouts/service-location/shop-question/shop-question-popup.vue +++ b/src/layouts/service-location/shop-question/shop-question-popup.vue @@ -24,22 +24,29 @@ :onModalOpenedCallback="onModalOpened" :onModalClosedCallback="onModalClosed" :footerButtonText="modalFooterText" + :footerButtonDisabled=" + displayInvalidZipAlert || displayNoShopsAlert || !selectedProviderNumber + " @footer-button-event="SetZipCodeAndShop"> -
-
- -
-
+ + - -