diff --git a/jest.config.js b/jest.config.js index 905e907dc..e534b3e3f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,7 +35,7 @@ module.exports = { testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], coverageThreshold: { global: { - statements: 74, + statements: 72, }, }, // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit diff --git a/src/assets/img/icons/calendar-icon-black.svg b/src/assets/img/icons/calendar-icon-black.svg new file mode 100644 index 000000000..9c66862ac --- /dev/null +++ b/src/assets/img/icons/calendar-icon-black.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/img/icons/calendar-icon-grey.svg b/src/assets/img/icons/calendar-icon-grey.svg new file mode 100644 index 000000000..5a67d0eae --- /dev/null +++ b/src/assets/img/icons/calendar-icon-grey.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/img/icons/info-circle.svg b/src/assets/img/icons/info-circle.svg new file mode 100644 index 000000000..7f176afd2 --- /dev/null +++ b/src/assets/img/icons/info-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/icons/payment-icon-green.svg b/src/assets/img/icons/payment-icon-green.svg new file mode 100644 index 000000000..931226bee --- /dev/null +++ b/src/assets/img/icons/payment-icon-green.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/img/icons/payment-icon-grey.svg b/src/assets/img/icons/payment-icon-grey.svg new file mode 100644 index 000000000..bef98d2de --- /dev/null +++ b/src/assets/img/icons/payment-icon-grey.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/constants/progress-bar-mapper.js b/src/constants/progress-bar-mapper.js new file mode 100644 index 000000000..98ae07992 --- /dev/null +++ b/src/constants/progress-bar-mapper.js @@ -0,0 +1,26 @@ +const pagePercentageMapper = { + vehicle: 4, + "vehicle-damage": 16, + estimate: 28, + "service-zip": 32, + "vin-lookup": 32, + "license-plate-lookup": 32, + "address-lookup": 32, + "address-vehicles": 36, + "part-questions": 40, + "molding-questions": 40, + "vehicle-parts": 40, + "capability-questions": 40, + quote: 48, + "insurance-company": 52, + "service-location": 60, + schedule: 72, + "customer-details": 84, + "payment-method": 92, + payment: 96, + confirmation: 100, +}; + +export const getProgressBarPercentage = (page) => { + return pagePercentageMapper[page] || 0; +}; diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue index 71c09bbbf..0d0ef066d 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -1,5 +1,7 @@ @@ -60,8 +60,9 @@ export default { flex-direction: column; width: 100%; background: transparent; - align-items: end; + align-items: center; margin-top: auto; + padding: 0 0 2rem 0; .skyline { height: 96px; @@ -70,30 +71,34 @@ export default { height: 48px; } + p { + font-size: 0.875rem; + margin-top: 0.25rem; + @include media-breakpoint-up(md) { + font-size: 1rem; + margin-top: 0.5rem; + } + } + .footer-inner-wrapper { display: flex; flex-direction: column; border-top: 1px solid $gray; - padding: 2rem 0; + padding: 2rem 0 0 0; width: 100%; justify-content: center; align-items: center; - - p { - font-size: 0.875rem; - color: $black; - @include media-breakpoint-up(md) { - font-size: 1rem; - } - } + color: $gray-600; :deep(a) { text-decoration: none; &.new-window-link { margin: 0 0.75rem; + color: $gray-600; } &.navigation-link { margin: 0 0.75rem; + color: $gray-600; } } diff --git a/src/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue index c6cbb8727..bf4f17ec1 100644 --- a/src/fmg-components/funnel-header/funnel-header.vue +++ b/src/fmg-components/funnel-header/funnel-header.vue @@ -2,10 +2,12 @@
+ Safelite logo +
- { ]); }); - it("Should display only the In-Shop answer when only in-shop service is available", async () => { + it("Should display only the In-Shop and Drop-Off answers when only in-shop service is available", async () => { // Arrange/Act const { wrapper } = setupMocks({ mixins: [mockMixin], @@ -108,7 +108,6 @@ describe("appointment-type-question.vue", () => { cmsWidgetName: cmsWidgetName, isServiceableInshop: true, isServiceableMobile: false, - isServiceableDropoff: false, }, mountOptions: { attachTo: document.body, @@ -124,6 +123,13 @@ describe("appointment-type-question.vue", () => { SubWidgetName: "", Text: "In-shop", }, + { + AnswerImageUrl: "", + Name: "Dropoff", + SubText: "", + SubWidgetName: "", + Text: "Drop-off", + }, ]); }); diff --git a/src/layouts/service-location/appointment-type-question/appointment-type-question.vue b/src/layouts/service-location/appointment-type-question/appointment-type-question.vue index 4c1c0d031..64bf159ff 100644 --- a/src/layouts/service-location/appointment-type-question/appointment-type-question.vue +++ b/src/layouts/service-location/appointment-type-question/appointment-type-question.vue @@ -32,7 +32,6 @@ export default { cmsWidgetName: String, isServiceableMobile: Boolean, isServiceableInshop: Boolean, - isServiceableDropoff: Boolean, mobileFeeApplies: Boolean, }, computed: { @@ -46,7 +45,7 @@ export default { const shouldShowMobile = this.isServiceableMobile; const shouldShowInshop = this.isServiceableInshop; const shouldShowDropoff = - this.isServiceableDropoff && !this.$store.getters.damage.isRepair; + this.isServiceableInshop && !this.$store.getters.damage.isRepair; var answers = this.answersFromCms ? this.answersFromCms.filter((answer) => { @@ -78,11 +77,6 @@ export default { isMobileOnly() { return this.isServiceableMobile && !this.isServiceableInshop; }, - isInshopOnly() { - return ( - this.isServiceableInshop && !this.isServiceableMobile && !this.isServiceableDropoff - ); - }, }, watch: { answersToDisplay: { @@ -92,7 +86,7 @@ export default { newValue.length == 1 && newValue.findIndex((answer) => answer.Name == "Mobile") != -1 ) { - this.selectedValue = "Mobile"; + this.selectedValues = "Mobile"; } }, immediate: true, @@ -100,14 +94,7 @@ export default { isMobileOnly: { handler(newValue) { if (newValue) { - this.selectedValue = "Mobile"; - } - }, - }, - isInshopOnly: { - handler(newValue) { - if (newValue) { - this.selectedValue = "Inshop"; + this.selectedValues = "Mobile"; } }, }, diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 7b3163c87..a85a5bd17 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -76,7 +76,6 @@ v-show="isAppointmentTypeDisplayed" :isServiceableMobile="isServiceableMobile" :isServiceableInshop="isServiceableInshop" - :isServiceableDropoff="isServiceableDropoff" :isDisplayed="isAppointmentTypeDisplayed" :mobileFeeApplies="mobileFeeApplies" ref="appointmentTypeQuestion" @@ -197,8 +196,6 @@ export default { isVehicleProtected: this.getIsVehicleProtectedFromStore(), isGlassServiceableInshop: null, isRecalibrationServiceableInshop: null, - isGlassServiceableDropoff: null, - isRecalibrationServiceableDropoff: null, isGlassServiceableMobile: null, isRecalibrationServiceableMobile: null, selectedAppointmentType: this.getSelectedAppointmentType(), @@ -357,13 +354,6 @@ export default { return this.isGlassServiceableInshop; } }, - isServiceableDropoff() { - if (this.isRecalibrationServiceableDropoff !== null) { - return this.isGlassServiceableDropoff && this.isRecalibrationServiceableDropoff; - } else { - return this.isGlassServiceableDropoff; - } - }, isShopQuestionDisplayed() { return ( this.selectedAppointmentType === "Inshop" || @@ -569,9 +559,6 @@ export default { this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop; this.isRecalibrationServiceableInshop = serviceabilityDetails.isRecalibrationServiceableInshop; - this.isGlassServiceableDropoff = serviceabilityDetails.isGlassServiceableDropoff; - this.isRecalibrationServiceableDropoff = - serviceabilityDetails.isRecalibrationServiceableDropoff; this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile; this.isRecalibrationServiceableMobile = serviceabilityDetails.isRecalibrationServiceableMobile; diff --git a/src/layouts/service-zip/service-zip.spec.js b/src/layouts/service-zip/service-zip.spec.js index e7cd68e3d..f8fc1f2ae 100644 --- a/src/layouts/service-zip/service-zip.spec.js +++ b/src/layouts/service-zip/service-zip.spec.js @@ -415,33 +415,6 @@ describe("service-zip.vue", () => { expect(wrapper.vm.displayInvalidZipAlert).toBe(false); expect(wrapper.vm.displayNonServiceableZipAlert).toBe(false); }); - test("should show the AlertNoService when displayNoServiceAlert is true", async () => { - // Arrange - // no changes to store - applyMockStoreDataToGetters(); - - const wrapper = setupMocks({}); - wrapper.vm.serviceZipCode = "12345"; - - // Act - wrapper.vm.displayNoServiceAlert = true; - - // Check if the AlertNoService component is rendered - expect(wrapper.vm.displayNoServiceAlert).toBe(true); - }); - test("should hide the AlertNoService when displayNoServiceAlert is false", async () => { - // Arrange - // no changes to store - applyMockStoreDataToGetters(); - - const wrapper = setupMocks({}); - wrapper.vm.serviceZipCode = "12345"; - //ACT - wrapper.vm.displayNoServiceAlert = false; - - // Check if the AlertNoService component is rendered - expect(wrapper.vm.displayNoServiceAlert).toBe(false); - }); }); }); @@ -538,16 +511,5 @@ function setupMocks({ customMountOptions, customZipQuery, customZipDataResponse ]; const wrapper = shallowMount(serviceZip, mountOptions); - wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => { - return Promise.resolve({ - data: { - providers: [ - { id: 1, name: "Shop 1" }, - { id: 2, name: "Shop 2" }, - ], - }, - }); - }); - return wrapper; } diff --git a/src/layouts/service-zip/service-zip.vue b/src/layouts/service-zip/service-zip.vue index 7d53d105d..5bb9ed471 100644 --- a/src/layouts/service-zip/service-zip.vue +++ b/src/layouts/service-zip/service-zip.vue @@ -47,18 +47,10 @@ v-if="displayNonServiceableZipAlert" alertClass="alert-danger" /> - - @@ -119,10 +111,9 @@ export default { serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode, emailOrSms: this.getEmailOrSmsFromStore(), carId: this.getCarIdfromStore(), - isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(), + isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(), displayInvalidZipAlert: false, displayNonServiceableZipAlert: false, - displayNoServiceAlert: false, }; }, @@ -198,8 +189,8 @@ export default { getCarIdfromStore() { return store.getters.vehicle.carId; }, - getIsVehicleHeavyTruckFromStore() { - return store.getters.isHeavyTruckVehicle; + getIsVehicleHeavyTruckServiceableFromStore() { + return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService; }, async findClosestApplicableShops() { return await this.dispatchStoreActionWithLogging( @@ -208,6 +199,7 @@ export default { "service-zip" ); }, + async backButtonAction() { const skipVin = await skipVinLookup(); // route to move backwards @@ -286,6 +278,16 @@ export default { return this.$refs.navbar.removeLoader(); } } + if (this.isHeavyTruck) { + const closestShops = await this.findClosestApplicableShops( + this.serviceZipCode, + this.carId + ); + this.displayNoServiceAlert = !closestShops?.data?.providers?.length; + if (this.displayNoServiceAlert) { + return this.$refs.navbar.removeLoader(); + } + } const payment = this.$store.getters.payment; const policy = this.$store.getters.policy; @@ -344,6 +346,7 @@ export default { serviceZipCode() { this.displayNonServiceableZipAlert = false; this.displayNoServiceAlert = false; + this.displayNoServiceAlert = false; }, }, components: { diff --git a/src/layouts/vin-lookup/vin-lookup.spec.js b/src/layouts/vin-lookup/vin-lookup.spec.js index 458f9fc48..6eb1aa515 100644 --- a/src/layouts/vin-lookup/vin-lookup.spec.js +++ b/src/layouts/vin-lookup/vin-lookup.spec.js @@ -258,39 +258,6 @@ describe("vin-lookup.vue", () => { // Assert expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(1); }); - - test("should show the AlertNoService when displayNoServiceAlert is true", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: true, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - // Set displayNoServiceAlert to true - await wrapper.setData({ displayNoServiceAlert: true }); - - // Check if the AlertNoService component is rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(true); - }); - test("should hide the AlertNoService when displayNoServiceAlert is false", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: false, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - // Ensure displayNoServiceAlert is false - await wrapper.setData({ displayNoServiceAlert: false }); - - // Check if the AlertNoService component is not rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(false); - }); }); describe("getVinFromImage", () => { diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 788425861..1b57ee068 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -71,8 +71,7 @@ vinPopulatedOnPageLoad && isInsuranceVerified && !displayInvalidZipAlert && - !displayNonServiceableZipAlert && - !displayNoServiceAlert + !displayNonServiceableZipAlert " alertClass="alert-success" /> @@ -108,23 +107,14 @@ vinPopulatedOnPageLoad && !isInsuranceVerified && !displayInvalidZipAlert && - !displayNonServiceableZipAlert && - !displayNoServiceAlert + !displayNonServiceableZipAlert " alertClass="alert-success" /> - - @@ -208,7 +198,7 @@ export default { serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode, emailOrSms: this.getEmailOrSmsFromStore(), carId: this.getCarIdfromStore(), - isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(), + isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(), isCarIdDifferent: false, customAlertData: {}, previouslyEnteredCarId: "", @@ -219,7 +209,6 @@ export default { displayVinNotFoundAlert: false, displayMatchedDifferentVehicleAlert: false, displayVinScanFailedAlert: false, - displayNoServiceAlert: false, }; }, methods: { @@ -235,12 +224,6 @@ export default { getZipFromStore() { return this.$store.getters.order.serviceLocation.zipCode; }, - getCarIdfromStore() { - return store.getters.vehicle.carId; - }, - getIsVehicleHeavyTruckFromStore() { - return store.getters.isHeavyTruckVehicle; - }, attachCustomEvents() { this.prependActionToMethod(this, this.forwardButtonAction, () => { this.pushEventToGA( @@ -264,6 +247,9 @@ export default { ); } }, + getIsVehicleHeavyTruckServiceableFromStore() { + return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService; + }, async findClosestApplicableShops() { return await this.dispatchStoreActionWithLogging( storeActions.GET_CLOSEST_APPLICABLE_SHOPS, @@ -303,18 +289,6 @@ export default { return this.$refs.navbar.removeLoader(); } this.displayInvalidZipAlert = false; - if (this.isHeavyTruck) { - // If a VIN has already been found. Validate the Service Zip (in case of changes) - var closestShops = await this.findClosestApplicableShops( - this.serviceZipCode, - this.carId - ); - this.displayNoServiceAlert = !closestShops?.data?.providers?.length; - if (this.displayNoServiceAlert) { - return this.$refs.navbar.removeLoader(); - } - } - // If either lookup fails, remove the loader and stop processing the page. if (!resultMap.vehicleLookupResponse || !resultMap.zipCodeData.isServiceable) { // If the vehicle result is undefined, the vin entered was invalid. @@ -366,6 +340,18 @@ export default { }, false ); + if (this.isHeavyTruck) { + closestShops = await this.findClosestApplicableShops( + this.serviceZipCode, + this.carId + ); + this.displayNoServiceAlert = !closestShops?.data?.providers?.length; + if (this.displayNoServiceAlert) { + return this.$refs.navbar.removeLoader(); + } + } + + // if no value due to field being optional, blank both phone and email address // if no value due to field being optional, blank both phone and email address if (!this.emailOrSms) { @@ -430,16 +416,6 @@ export default { false ); } - - closestShops = await this.findClosestApplicableShops( - this.serviceZipCode, - this.carId - ); - this.displayNoServiceAlert = !closestShops?.data?.providers?.length; - if (this.displayNoServiceAlert) { - return this.$refs.navbar.removeLoader(); - } - // if no value due to field being optional, blank both phone and email address if (!this.emailOrSms) { await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false); @@ -523,6 +499,7 @@ export default { this.displayVinNotFoundAlert = false; this.displayVinScanFailedAlert = false; this.displayNoServiceAlert = false; + this.displayNoServiceAlert = false; }, }, mounted() { @@ -599,7 +576,6 @@ export default { }, serviceZipCode() { this.displayNonServiceableZipAlert = false; - this.displayNoServiceAlert = false; }, }, components: {