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 @@
-
+
@@ -99,8 +107,17 @@
// Supporting files
import loader from "@/ux-components/loader/loader";
import store from "@/store";
-import { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR } from "./mixins/constants";
-import { selectableDaysOptions, requiredParameter, forceTwoDigitString } from "./mixins/helpers";
+import {
+ TIMINGFUNC_MAP,
+ BUFFER_OFFSET,
+ MONTHS_OF_YEAR,
+ DAYS_OF_WEEK,
+} from "@/digital-components/date-picker/mixins/constants";
+import {
+ selectableDaysOptions,
+ requiredParameter,
+ forceTwoDigitString,
+} from "@/digital-components/date-picker/mixins/helpers";
import {
convertDateToDateString,
convertDateStringToDate,
@@ -148,6 +165,10 @@ export default {
type: String,
default: "",
},
+ showPricingByDay: Boolean,
+ pricingByDayBasePrice: Number,
+ pricingByDayUpcharge: Number,
+ isPricingByDayExperiment: Boolean,
},
setup(props) {
const uuid = uuidv4();
@@ -199,6 +220,12 @@ export default {
if (this.selectableDatesSetting === "custom") return "future";
return "past";
},
+ isPricingByDayClass() {
+ return this.isPricingByDayExperiment ? "pricing-by-day" : "";
+ },
+ showPricingByDayClass() {
+ return this.showPricingByDay ? "show-pricing-by-day" : "";
+ },
selectedDate: {
get() {
return this.modelValue;
@@ -212,12 +239,12 @@ export default {
initializeComponent(initialData) {
this.setCalendarData(initialData);
},
- fireDateSelectedEvent(event) {
+ fireDateSelectedEvent(event, date) {
// Ignore if arrow key selected radioButton
if (event.screenX === 0 && event.screenY === 0) {
return;
}
- this.$emit("date-clicked");
+ this.$emit("date-clicked", date);
},
getWeekStartDate(dateString) {
const date = convertDateStringToDate(dateString);
@@ -440,6 +467,8 @@ export default {
initialViewEndDate: config.initialViewEndDate,
hideSecondMonth: hideSecondMonth,
preSelectedDate: config.preSelectedDate,
+ pricingByDayBasePrice: config.pricingByDayBasePrice,
+ pricingByDayUpcharge: config.pricingByDayUpcharge,
};
if (direction === "future") {
// first 0, then 1
@@ -573,6 +602,17 @@ export default {
("0" + monthNum).slice(-2) +
"-" +
("0" + i).slice(-2);
+ const dayIndex = convertDateStringToDate(dateString).getDay();
+ const dayObject = DAYS_OF_WEEK[dayIndex];
+ const isSelectable =
+ this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
+ ? true
+ : false;
+ const isPricingByDayUpchargeDay = dayObject.isPricingByDayUpchargeDay;
+ const displayPrice = isPricingByDayUpchargeDay
+ ? options.pricingByDayBasePrice + options.pricingByDayUpcharge
+ : options.pricingByDayBasePrice;
+ const priceString = "$" + displayPrice;
if (offset === 0 && i === this.todayDateNum) {
dayClasses += " current-day";
@@ -583,8 +623,8 @@ export default {
if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") {
dayClasses += " unavailable-day";
}
- if (convertDateStringToDate(dateString).getDay() === 0) {
- dayClasses += " sunday";
+ if (dayIndex === 0) {
+ dayClasses += " " + dayObject.cssClass;
}
if (
this.hideSomeDaysForInitialView &&
@@ -598,10 +638,9 @@ export default {
dateNum: i,
dayClasses: dayClasses,
inputValue: dateString,
- isSelectable:
- this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
- ? true
- : false,
+ priceString: priceString,
+ isSelectable: isSelectable,
+ isPricingByDayUpchargeDay: isPricingByDayUpchargeDay,
};
dates.push(dateObject);
}
@@ -897,7 +936,6 @@ export default {
}
}
- &:hover,
&:checked {
@include media-breakpoint-up(sm) {
box-shadow: 0 0 0 4px transparent;
@@ -924,9 +962,7 @@ export default {
&.selectable-day {
label {
- color: $blue;
background-color: $blue-100;
- border: 1px solid $blue;
min-width: 2.5rem;
width: 2.5rem;
border-radius: 50%;
@@ -934,12 +970,11 @@ export default {
}
&.unavailable-day {
label {
- color: $gray-500;
- background-color: $gray-100;
border: none;
pointer-events: none;
}
}
+ //NEEDED?
&.unavailable-day:not(&.sunday) {
label {
&::before {
@@ -949,7 +984,6 @@ export default {
left: -1rem;
width: 1rem;
height: 2.5rem;
- background: $gray-100;
}
}
}
@@ -1056,7 +1090,107 @@ export default {
}
}
}
+ // pricing by day override styles
+ &.pricing-by-day {
+ .calendar-grid-container .grid-item {
+ margin: 0 0 0.15rem 0;
+ }
+ .month-year {
+ grid-area: 1 / 1 / 2 / 8;
+ }
+ .legend {
+ display: none;
+ }
+ .radio-wrapper {
+ align-items: flex-start;
+ width: 100%;
+ height: 2.5rem;
+ color: $black;
+
+ input[type="radio"] {
+ &:focus-visible + label {
+ box-shadow: none;
+ }
+
+ &:focus + label,
+ &:checked:focus + label {
+ box-shadow: none;
+ background-color: $white;
+ color: $black;
+ }
+ &:checked + label {
+ background: $blue-100;
+ border: 2px solid $blue;
+ border-radius: 0.25rem;
+ color: $black;
+ span {
+ font-family: AvertaSemibold;
+ font-weight: 400;
+ }
+ }
+ }
+ label {
+ flex-direction: column;
+ height: 2.5rem;
+ width: 100%;
+ font-size: 0.75rem;
+ line-height: 1.2;
+ justify-content: center;
+ padding: 0.25rem;
+ }
+ &.selectable-day {
+ label {
+ color: $black;
+ background-color: $blue-100;
+ min-width: 2.5rem;
+ width: 2.5rem;
+ border-radius: 0.25rem;
+ }
+ }
+ &.current-day {
+ label:after {
+ margin-top: 0.2rem;
+ position: relative;
+ top: 0;
+ }
+ }
+ }
+ &.show-pricing-by-day {
+ .radio-wrapper {
+ &.selectable-day label span {
+ text-decoration: none;
+ color: $black;
+ font-weight: 400;
+ font-family: "AvertaSemibold";
+
+ &.price {
+ font-family: "AvertaRegular";
+ font-weight: 400;
+ }
+ }
+ &.upch-day label span.price {
+ color: $gray-600;
+ font-weight: 400;
+ font-family: $font-family-sans-serif;
+ }
+ input[type="radio"] {
+ &:focus + label,
+ &:checked:focus + label {
+ color: $gray-600;
+ }
+ &:checked + label {
+ color: $blue;
+ span {
+ font-weight: 400;
+ font-family: "AvertaSemibold";
+ }
+ }
+ }
+ }
+ }
+ }
}
+
.btn-link {
display: block;
position: relative;
diff --git a/src/digital-components/salesforce-webchat/salesforce-helper-dev.js b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js
new file mode 100644
index 000000000..fb29bc9eb
--- /dev/null
+++ b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js
@@ -0,0 +1,66 @@
+/*
+The code below is generated from salesforce but modified in the following ways:
+ * Only the javascript inside the second
+
+
diff --git a/src/experiment-components/date-picker-for-pricing-by-day.vue b/src/experiment-components/date-picker-for-pricing-by-day.vue
deleted file mode 100644
index cad976cf7..000000000
--- a/src/experiment-components/date-picker-for-pricing-by-day.vue
+++ /dev/null
@@ -1,1221 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/experiment-components/service-package-radio-for-afterpay.vue b/src/experiment-components/service-package-radio-for-afterpay.vue
index b936e21bf..f38621462 100644
--- a/src/experiment-components/service-package-radio-for-afterpay.vue
+++ b/src/experiment-components/service-package-radio-for-afterpay.vue
@@ -5,6 +5,7 @@
:class="[
this.buttonLabelSubCopy ? 'has-subheader' : '',
this.additionalButtonData.isInsuranceSelected ? 'is-insurance' : '',
+ this.hasPackageDiscount() ? 'has-package-discount' : '',
]"
for="testradio">
@@ -54,12 +55,7 @@
v-html="this.buttonFooterCopy">
-
+
@@ -144,6 +140,11 @@ export default {
truncatedSinglePayment() {
return "$" + Math.trunc(this.buttonAuxillaryCopy.replace("$", ""));
},
+ hasPackageDiscount() {
+ return (
+ this.additionalButtonData.servicePackageDiscount && this.additionalButtonData.Text
+ );
+ },
},
};
@@ -212,12 +213,6 @@ export default {
}
&.has-subheader {
- min-height: 150px;
-
- &.is-insurance {
- min-height: 72px;
- }
-
& > .package-specs {
& > div:first-of-type {
display: flex;
@@ -225,6 +220,10 @@ export default {
}
}
+ &.has-package-discount {
+ min-height: 150px;
+ }
+
&:before {
content: "";
position: relative;
diff --git a/src/fmg-components/funnel-footer/funnel-footer.vue b/src/fmg-components/funnel-footer/funnel-footer.vue
index 0cff25b0b..ca8be5bf0 100644
--- a/src/fmg-components/funnel-footer/funnel-footer.vue
+++ b/src/fmg-components/funnel-footer/funnel-footer.vue
@@ -11,7 +11,6 @@
+ © 2025 Safelite Group
@@ -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 @@
+
+
@@ -26,6 +28,8 @@ import alert from "@/ux-components/alert/alert";
import eventBus from "@/helpers/event-bus/event-bus";
import { globalEvents } from "@/constants/events";
import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal";
+import salesforceWebchat from "../../digital-components/salesforce-webchat/salesforce-webchat.vue";
+import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar";
// Constants
const ALERT_DURATION = 3000; // millisecond time to display alert before dismissal
@@ -39,6 +43,10 @@ export default {
},
props: {
cmsWidgetName: String,
+ hideSalesforceWebchatLaunchButton: {
+ type: Boolean,
+ default: false,
+ },
},
computed: {
imageSrc() {
@@ -62,6 +70,8 @@ export default {
components: {
alert,
menuModal,
+ salesforceWebchat,
+ progressBar,
},
mounted() {
// Check if alert event is on the bus
diff --git a/src/fmg-components/funnel-header/progress-bar/progres-bar.spec.js b/src/fmg-components/funnel-header/progress-bar/progres-bar.spec.js
new file mode 100644
index 000000000..92e6e15a5
--- /dev/null
+++ b/src/fmg-components/funnel-header/progress-bar/progres-bar.spec.js
@@ -0,0 +1,84 @@
+import { shallowMount } from "@vue/test-utils";
+import progressBar from "./progress-bar";
+import store from "@/store";
+
+describe("progressBar", () => {
+ test("progress should be 0", () => {
+ // Arrange
+
+ // Act
+ const wrapper = shallowMount(progressBar, {
+ mixins: [mockMixin],
+ });
+
+ // Assert
+ expect(wrapper.vm.progress).toBe(0);
+ wrapper.unmount();
+ });
+});
+
+describe("progressBar", () => {
+ test("progress should be 4%", () => {
+ // Arrange
+ store.getters = {
+ applicationUser: {
+ lastPageVisited: "vehicle",
+ },
+ };
+
+ // Act
+ const wrapper = shallowMount(progressBar, {
+ mixins: [mockMixin],
+ });
+
+ // Assert
+ expect(wrapper.vm.progress).toBe(4);
+ wrapper.unmount();
+ });
+});
+
+describe("progressBar", () => {
+ test("progress should be 48%", () => {
+ // Arrange
+ store.getters = {
+ applicationUser: {
+ lastPageVisited: "quote",
+ },
+ };
+
+ // Act
+ const wrapper = shallowMount(progressBar, {
+ mixins: [mockMixin],
+ });
+
+ // Assert
+ expect(wrapper.vm.progress).toBe(48);
+ wrapper.unmount();
+ });
+});
+
+describe("progressBar", () => {
+ test("progress should be 100%", () => {
+ // Arrange
+ store.getters = {
+ applicationUser: {
+ lastPageVisited: "confirmation",
+ },
+ };
+
+ // Act
+ const wrapper = shallowMount(progressBar, {
+ mixins: [mockMixin],
+ });
+
+ // Assert
+ expect(wrapper.vm.progress).toBe(100);
+ wrapper.unmount();
+ });
+});
+
+const mockMixin = {
+ methods: {
+ getProgress: jest.fn(),
+ },
+};
diff --git a/src/fmg-components/funnel-header/progress-bar/progress-bar.vue b/src/fmg-components/funnel-header/progress-bar/progress-bar.vue
new file mode 100644
index 000000000..c8d880dab
--- /dev/null
+++ b/src/fmg-components/funnel-header/progress-bar/progress-bar.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
diff --git a/src/layouts/address-lookup/address-lookup.spec.js b/src/layouts/address-lookup/address-lookup.spec.js
index 19a46abb8..677c14069 100644
--- a/src/layouts/address-lookup/address-lookup.spec.js
+++ b/src/layouts/address-lookup/address-lookup.spec.js
@@ -182,39 +182,6 @@ describe("address-lookup.vue", () => {
// Assert
expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true);
});
- 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("navigation", () => {
@@ -774,18 +741,6 @@ function setupMocks({
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
- const closestShops = {
- data: {
- providers: [
- { id: 1, name: "Shop 1" },
- { id: 2, name: "Shop 2" },
- ],
- },
- };
-
- wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
- return new Promise((resolve) => resolve(closestShops));
- });
return { wrapper };
}
diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue
index c33ef68e5..ba2f8f6dd 100644
--- a/src/layouts/address-lookup/address-lookup.vue
+++ b/src/layouts/address-lookup/address-lookup.vue
@@ -81,20 +81,13 @@
-
+ :isForwardActionDisabled="!meta.valid" />
@@ -172,7 +165,7 @@ export default {
},
serviceZipCode: this.getServiceZipFromStore(),
carId: this.getCarIdfromStore(),
- isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
+ isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
displayNonServiceableZipAlert: false,
displayVinNotFoundAlert: false,
displayMatchedDifferentVehicleAlert: false,
@@ -184,7 +177,6 @@ export default {
displayInvalidZipAlert: false,
showServiceZipField: this.getServiceZipFromStore(),
isZipServiceable: false,
- displayNoServiceAlert: false,
};
},
methods: {
@@ -232,8 +224,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(
@@ -301,17 +293,6 @@ export default {
return this.$refs.navbar.removeLoader();
}
this.displayInvalidZipAlert = false;
- if (this.serviceZipCode != null && 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 carsFound = resultMap.vinLookupResponse.vinVehicles;
// Handle cases for different amounts of VINS found for the address.
@@ -463,7 +444,6 @@ export default {
this.displayNonServiceableZipAlert = false;
this.displayMatchedDifferentVehicleAlert = false;
this.displayVinLookupByHomeAddressNotAllowedAlert = false;
- this.displayNoServiceAlert = false;
},
},
mounted() {
@@ -515,7 +495,6 @@ export default {
handler(newValue) {
// If they modify the service zip code, then hide the error message.
this.displayNonServiceableZipAlert = false;
- this.displayNoServiceAlert = false;
},
},
showServiceZipField: {
diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue
index 3b3ead4c1..40736b2c2 100644
--- a/src/layouts/confirmation/confirmation.vue
+++ b/src/layouts/confirmation/confirmation.vue
@@ -5,7 +5,10 @@
diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.spec.js b/src/layouts/license-plate-lookup/license-plate-lookup.spec.js
index 2ffe61193..14f0408d1 100644
--- a/src/layouts/license-plate-lookup/license-plate-lookup.spec.js
+++ b/src/layouts/license-plate-lookup/license-plate-lookup.spec.js
@@ -611,42 +611,6 @@ describe("license-plate-lookup.vue", () => {
expect(arePagePrerequisitesValid).toBe(false);
});
});
-
- describe("alerts", () => {
- 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);
- });
- });
});
function setupMocks({
@@ -752,18 +716,5 @@ function setupMocks({
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
- const closestShops = {
- data: {
- providers: [
- { id: 1, name: "Shop 1" },
- { id: 2, name: "Shop 2" },
- ],
- },
- };
-
- wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
- return new Promise((resolve) => resolve(closestShops));
- });
-
return { wrapper, apiPromise };
}
diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue
index a359d3a9d..8736df956 100644
--- a/src/layouts/license-plate-lookup/license-plate-lookup.vue
+++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue
@@ -76,7 +76,6 @@
:manualCopy="AlertMatchedDifferentVehicleBody"
alertClass="alert-warning"
v-bind:isDismissible="false" />
-
+ @ForwardClicked="forwardButtonAction || displayNoServiceAlert" />
@@ -167,7 +166,7 @@ export default {
emailOrSms: this.getEmailOrSmsFromStore(),
serviceZipCode: this.getServiceZipFromStore(),
carId: this.getCarIdfromStore(),
- isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
+ isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
displayNonServiceableZipAlert: false,
displayVinNotFoundAlert: false,
displayMatchedDifferentVehicleAlert: false,
@@ -178,7 +177,6 @@ export default {
displayInvalidZipAlert: false,
showServiceZipField: this.getServiceZipFromStore(),
isZipServiceable: false,
- displayNoServiceAlert: false,
};
},
methods: {
@@ -214,8 +212,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(
@@ -281,17 +279,6 @@ export default {
this.displayInvalidZipAlert = true;
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();
- }
- }
-
// Check if the CarId has changed.
this.isCarIdDifferent =
vinLookup.data.vehicle.carId !== this.$store.getters.vehicle.carId;
@@ -406,7 +393,6 @@ export default {
this.displayMatchedDifferentVehicleAlert = false;
this.displayVinLookupByHomeAddressNotAllowedAlert = false;
this.displayInvalidZipAlert = false;
- this.displayNoServiceAlert = false;
},
},
mounted() {
@@ -451,7 +437,6 @@ export default {
serviceZipCode() {
// If they modify the service zip code, then hide the error message.
this.displayNonServiceableZipAlert = false;
- this.displayNoServiceAlert = false;
this.$refs.navbar.updateButtonText(
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
);
diff --git a/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue b/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue
new file mode 100644
index 000000000..06a717289
--- /dev/null
+++ b/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
{{ this.afterpayPrice }}
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ this.afterpayPrice }}
+
+
+
+
+
+
+
+
{{ this.afterpayPrice }}
+
+
+
+
+
+
+
+
{{ this.afterpayPrice }}
+
+
+
+
+
+
+
+
{{ this.afterpayPrice }}
+
+
+
+
+
+
+
![]()
+
Payment Schedule
+
+
+
![]()
+
Amount Due
+
+
+
+
+
+
+
+
+
diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js
index 013916af0..3e31f88c5 100644
--- a/src/layouts/payment-method/payment-method.spec.js
+++ b/src/layouts/payment-method/payment-method.spec.js
@@ -117,6 +117,12 @@ function setupMocks() {
return "false";
}),
+ hasSettingEqualTo: jest.fn((settingName) => {
+ if (settingName === experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY) {
+ return true;
+ }
+ return false;
+ }),
getCmsContent: jest.fn().mockImplementation(() => {
return "test text";
}),
diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue
index 33e070a93..d5c7d93c3 100644
--- a/src/layouts/payment-method/payment-method.vue
+++ b/src/layouts/payment-method/payment-method.vue
@@ -39,6 +39,10 @@
:isExpandedOnLoad="false"
:isMSRFeeApplicable="isMSRFeeApplicable" />
+
@@ -118,6 +122,7 @@ import alert from "@/ux-components/alert/alert";
import textBlock from "@/digital-components/text-block/text-block";
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
+import afterpayBreakout from "@/layouts/payment-method/afterpay-breakout/afterpay-breakout";
// Supporting Items
import baseMixin from "@/mixins/base-mixin.js";
@@ -131,7 +136,6 @@ import {
} from "@/helpers/cms-content-helper";
import { paymentMethods } from "@/constants/payment-method-constants";
import { experimentSettings } from "@/constants/experiments";
-import experimentMixin from "@/mixins/experiment-mixin.js";
import {
revalidatePromosAndValidateQueryStringPromo,
buildToastMessagesFromRevalidateOrValidatePromoResponse,
@@ -805,6 +809,18 @@ export default {
lineItemsCloneForWatcher() {
return Object.assign({}, this.lineItems);
},
+ isRecalPriceRemove() {
+ return (
+ this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)?.toLowerCase() ===
+ "true"
+ );
+ },
+ isAfterpayBreakoutDisplay() {
+ return (
+ (!this.isRecalPriceRemove || !this.isRecalibrationOnOrder) &&
+ this.hasSettingEqualTo(experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY, "true")
+ );
+ },
},
watch: {
customCtaCopy(newValue) {
@@ -856,6 +872,7 @@ export default {
textBlock,
checkboxQuestion,
contentGroupModal,
+ afterpayBreakout,
},
};
diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue
index 6ef3d4749..4a20b4dbc 100644
--- a/src/layouts/quote/quote.vue
+++ b/src/layouts/quote/quote.vue
@@ -891,7 +891,7 @@ export default {
&.funnel-header-wrapper,
&.save-progress-popup {
- display: block;
+ display: flex;
}
}
diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue
index cd3e09806..c3081d0fe 100644
--- a/src/layouts/schedule/schedule.vue
+++ b/src/layouts/schedule/schedule.vue
@@ -18,7 +18,7 @@
marginTopSizeOverride="1" />
-
{
]);
});
- 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: {