@@ -261,6 +261,8 @@ export default {
}
.alert {
border: 1px solid $red;
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
}
.btn-success {
background: $green-100;
diff --git a/src/experiment-components/service-package-radio.vue b/src/experiment-components/service-package-radio.vue
index f0eb991c6..440bcbb58 100644
--- a/src/experiment-components/service-package-radio.vue
+++ b/src/experiment-components/service-package-radio.vue
@@ -169,10 +169,10 @@ export default {
display: flex;
flex-direction: column;
&:first-child {
- margin: 1rem 0.5rem 0 0;
+ margin: 1rem 1rem 0 0;
}
&:last-child {
- margin: 1rem 0 0 0.5rem;
+ margin: 1rem 0 0 1rem;
}
}
diff --git a/src/fmg-components/funnel-footer/funnel-footer.spec.js b/src/fmg-components/funnel-footer/funnel-footer.spec.js
new file mode 100644
index 000000000..0e3dff0f8
--- /dev/null
+++ b/src/fmg-components/funnel-footer/funnel-footer.spec.js
@@ -0,0 +1 @@
+test.todo("Predominantly a UI component, so no tests are written");
diff --git a/src/fmg-components/funnel-footer/funnel-footer.vue b/src/fmg-components/funnel-footer/funnel-footer.vue
new file mode 100644
index 000000000..89db470f0
--- /dev/null
+++ b/src/fmg-components/funnel-footer/funnel-footer.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
diff --git a/src/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue
index 22d9e77fa..c6cbb8727 100644
--- a/src/fmg-components/funnel-header/funnel-header.vue
+++ b/src/fmg-components/funnel-header/funnel-header.vue
@@ -3,7 +3,9 @@
class="funnel-header d-flex justify-content-center align-items-center flex-column"
v-if="imageSrc">
+
+
@@ -88,6 +90,9 @@ export default {
diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js
index 3344e3d27..3a389aec5 100644
--- a/src/layouts/schedule/schedule.spec.js
+++ b/src/layouts/schedule/schedule.spec.js
@@ -7,7 +7,6 @@ import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
import router from "@/router";
import baseMixin from "../../mixins/base-mixin";
-import { experimentSettings } from "../../constants/experiments";
// Mock basemixin
jest.mock("@/mixins/base-mixin.js", () => ({
@@ -147,6 +146,9 @@ beforeEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
store.getters = {
+ applicationUser: {
+ experiments: [],
+ },
order: {
schedule: {
date: "2019-01-01",
diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue
index 63e4a3775..2fd06d312 100644
--- a/src/layouts/schedule/schedule.vue
+++ b/src/layouts/schedule/schedule.vue
@@ -269,6 +269,8 @@ export default {
const priceString = getAmountDue(lineItemsToBePriced, false); // pass the IncludeTax param as false
const priceStringIntegerRoundedDown = priceString?.split(".")[0]; // same method used as getDisplayPrice() in service-package-radio used on /quote
const pricingByDayBasePrice = parseInt(priceStringIntegerRoundedDown);
+ const payment = store.getters.order.payment;
+ const isInsurance = payment?.isInsurance;
// Check to see if includePricingByDayUpcharge should already be set (based on order lineItems)
let includePricingByDayUpcharge = false;
@@ -276,7 +278,7 @@ export default {
const pricingByDayUpchargeFeeIndex = supportingItemsFromStore?.findIndex(
(item) => item.partType == PRICING_BY_DAY_PART_TYPE
);
- if (pricingByDayUpchargeFeeIndex > -1) {
+ if (pricingByDayUpchargeFeeIndex > -1 && !isInsurance) {
includePricingByDayUpcharge = true;
}
}
@@ -630,26 +632,25 @@ export default {
const supportingItems = this.getSupportingItems();
// if we have a pricing by day upcharge, then save/update supporting items with it
- if (this.showPricingByDay) {
- const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex(
- (item) => item.partType == PRICING_BY_DAY_PART_TYPE
- );
+ const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex(
+ (item) => item.partType == PRICING_BY_DAY_PART_TYPE
+ );
- if (this.includePricingByDayUpcharge) {
- if (pricingByDayUpchargeFeeIndex > -1) {
- supportingItems[pricingByDayUpchargeFeeIndex].laborAmount =
- this.pricingByDayUpchargeLineItem.laborAmount;
- supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice =
- this.pricingByDayUpchargeLineItem.sellingPrice;
- supportingItems[pricingByDayUpchargeFeeIndex].kitPrice =
- this.pricingByDayUpchargeLineItem.kitPrice;
- } else {
- supportingItems.push(this.pricingByDayUpchargeLineItem);
- }
+ if (this.includePricingByDayUpcharge && this.showPricingByDay) {
+ if (pricingByDayUpchargeFeeIndex && pricingByDayUpchargeFeeIndex > -1) {
+ supportingItems[pricingByDayUpchargeFeeIndex].laborAmount =
+ this.pricingByDayUpchargeLineItem.laborAmount;
+ supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice =
+ this.pricingByDayUpchargeLineItem.sellingPrice;
+ supportingItems[pricingByDayUpchargeFeeIndex].kitPrice =
+ this.pricingByDayUpchargeLineItem.kitPrice;
} else {
- if (pricingByDayUpchargeFeeIndex >= 0) {
- supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
- }
+ supportingItems.push(this.pricingByDayUpchargeLineItem);
+ }
+ } else {
+ if (pricingByDayUpchargeFeeIndex >= 0) {
+ // remove pricing by day upcharge if it already was in store
+ supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
}
}
@@ -756,6 +757,9 @@ export default {
&:focus {
outline: 1px solid $blue;
}
+ @include media-breakpoint-up(md) {
+ font-size: 1rem;
+ }
}
}
.funnel-sub-header {
diff --git a/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js b/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js
index 63242df22..8ff7c9b1c 100644
--- a/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js
+++ b/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js
@@ -59,7 +59,7 @@ afterEach(() => {
});
describe("appointment-type-question.vue", () => {
- it("Should display all options if both in-shop and mobile are available", async () => {
+ it("Should display all options if in-shop, mobile, and dropoff are available", async () => {
// Arrange/Act
const { wrapper } = setupMocks({
mixins: [mockMixin],
@@ -67,6 +67,7 @@ describe("appointment-type-question.vue", () => {
cmsWidgetName: cmsWidgetName,
isServiceableInshop: true,
isServiceableMobile: true,
+ isServiceableDropoff: true,
},
mountOptions: {
attachTo: document.body,
@@ -99,7 +100,7 @@ describe("appointment-type-question.vue", () => {
]);
});
- it("Should display only the In-Shop and Drop-Off answers when only in-shop service is available", async () => {
+ it("Should display only the In-Shop answer when only in-shop service is available", async () => {
// Arrange/Act
const { wrapper } = setupMocks({
mixins: [mockMixin],
@@ -107,6 +108,34 @@ describe("appointment-type-question.vue", () => {
cmsWidgetName: cmsWidgetName,
isServiceableInshop: true,
isServiceableMobile: false,
+ isServiceableDropoff: false,
+ },
+ mountOptions: {
+ attachTo: document.body,
+ },
+ });
+
+ // Assert
+ expect(wrapper.vm.answersToDisplay).toEqual([
+ {
+ AnswerImageUrl: "",
+ Name: "Inshop",
+ SubText: "",
+ SubWidgetName: "",
+ Text: "In-shop",
+ },
+ ]);
+ });
+
+ it("Should display only the In-Shop and Drop-Off answers when mobile service is not available", async () => {
+ // Arrange/Act
+ const { wrapper } = setupMocks({
+ mixins: [mockMixin],
+ props: {
+ cmsWidgetName: cmsWidgetName,
+ isServiceableInshop: true,
+ isServiceableMobile: false,
+ isServiceableDropoff: true,
},
mountOptions: {
attachTo: document.body,
@@ -140,6 +169,7 @@ describe("appointment-type-question.vue", () => {
cmsWidgetName: cmsWidgetName,
isServiceableInshop: false,
isServiceableMobile: true,
+ isServiceableDropoff: false,
},
mountOptions: {
attachTo: document.body,
@@ -172,6 +202,7 @@ describe("appointment-type-question.vue", () => {
cmsWidgetName: cmsWidgetName,
isServiceableInshop: true,
isServiceableMobile: true,
+ isServiceableDropoff: true,
},
mountOptions: {
attachTo: document.body,
@@ -205,6 +236,7 @@ describe("appointment-type-question.vue", () => {
cmsWidgetName: cmsWidgetName,
isServiceableInshop: false,
isServiceableMobile: false,
+ isServiceableDropoff: false,
},
mountOptions: {
attachTo: document.body,
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 64bf159ff..4c1c0d031 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,6 +32,7 @@ export default {
cmsWidgetName: String,
isServiceableMobile: Boolean,
isServiceableInshop: Boolean,
+ isServiceableDropoff: Boolean,
mobileFeeApplies: Boolean,
},
computed: {
@@ -45,7 +46,7 @@ export default {
const shouldShowMobile = this.isServiceableMobile;
const shouldShowInshop = this.isServiceableInshop;
const shouldShowDropoff =
- this.isServiceableInshop && !this.$store.getters.damage.isRepair;
+ this.isServiceableDropoff && !this.$store.getters.damage.isRepair;
var answers = this.answersFromCms
? this.answersFromCms.filter((answer) => {
@@ -77,6 +78,11 @@ export default {
isMobileOnly() {
return this.isServiceableMobile && !this.isServiceableInshop;
},
+ isInshopOnly() {
+ return (
+ this.isServiceableInshop && !this.isServiceableMobile && !this.isServiceableDropoff
+ );
+ },
},
watch: {
answersToDisplay: {
@@ -86,7 +92,7 @@ export default {
newValue.length == 1 &&
newValue.findIndex((answer) => answer.Name == "Mobile") != -1
) {
- this.selectedValues = "Mobile";
+ this.selectedValue = "Mobile";
}
},
immediate: true,
@@ -94,7 +100,14 @@ export default {
isMobileOnly: {
handler(newValue) {
if (newValue) {
- this.selectedValues = "Mobile";
+ this.selectedValue = "Mobile";
+ }
+ },
+ },
+ isInshopOnly: {
+ handler(newValue) {
+ if (newValue) {
+ this.selectedValue = "Inshop";
}
},
},
diff --git a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js
index 7a9ac00d0..106ed892c 100644
--- a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js
+++ b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js
@@ -149,6 +149,23 @@ export async function getAvailabilityRating(
return shopStatus;
}
+export async function getClosestApplicableShops(serviceZipCode, carId, pageNameToLog) {
+ if (!serviceZipCode) {
+ return null;
+ }
+
+ const closestShops = await baseMixin.methods.dispatchStoreActionWithLogging(
+ storeActions.GET_CLOSEST_APPLICABLE_SHOPS,
+ {
+ zip: serviceZipCode,
+ carId: carId,
+ },
+ pageNameToLog
+ );
+
+ return closestShops.data;
+}
+
export async function getZipCodeData(serviceZipCode) {
return await baseMixin.methods.getZipCodeData(serviceZipCode, "service-location");
}
diff --git a/src/layouts/service-location/service-location.spec.js b/src/layouts/service-location/service-location.spec.js
index fb34f3921..7dccc1b1e 100644
--- a/src/layouts/service-location/service-location.spec.js
+++ b/src/layouts/service-location/service-location.spec.js
@@ -174,6 +174,7 @@ const mockMixin = {
beforeEach(() => {
store.getters = {
+ applicationUser: { experiments: [] },
lineItems: {
supportingItems: [
{
diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue
index 56c76bb2d..7b3163c87 100644
--- a/src/layouts/service-location/service-location.vue
+++ b/src/layouts/service-location/service-location.vue
@@ -15,6 +15,8 @@
editScreenReaderTextCmsWidgetName="ScreenReaderZipEditWidget"
v-model="serviceZipCodeQuestion"
ref="serviceZipCodeQuestion"
+ :carId="carId"
+ :isVehicleHeavyTruck="isVehicleHeavyTruck"
:mobileFeePart="mobileFeePart"
@updated-mobile-fee-part="setMobileFeePart"
@updated-recycle-fee-part="setRecycleFeePart"
@@ -74,6 +76,7 @@
v-show="isAppointmentTypeDisplayed"
:isServiceableMobile="isServiceableMobile"
:isServiceableInshop="isServiceableInshop"
+ :isServiceableDropoff="isServiceableDropoff"
:isDisplayed="isAppointmentTypeDisplayed"
:mobileFeeApplies="mobileFeeApplies"
ref="appointmentTypeQuestion"
@@ -186,12 +189,16 @@ export default {
return {
streetAddress: this.getServiceAddressFromStore(),
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
+ carId: this.getCarIdfromStore(),
city: this.getServiceCityFromStore(),
state: this.getServiceStateFromStore(),
zipCode: this.getServiceZipCodeFromStore(),
+ isVehicleHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
isVehicleProtected: this.getIsVehicleProtectedFromStore(),
isGlassServiceableInshop: null,
isRecalibrationServiceableInshop: null,
+ isGlassServiceableDropoff: null,
+ isRecalibrationServiceableDropoff: null,
isGlassServiceableMobile: null,
isRecalibrationServiceableMobile: null,
selectedAppointmentType: this.getSelectedAppointmentType(),
@@ -350,6 +357,13 @@ export default {
return this.isGlassServiceableInshop;
}
},
+ isServiceableDropoff() {
+ if (this.isRecalibrationServiceableDropoff !== null) {
+ return this.isGlassServiceableDropoff && this.isRecalibrationServiceableDropoff;
+ } else {
+ return this.isGlassServiceableDropoff;
+ }
+ },
isShopQuestionDisplayed() {
return (
this.selectedAppointmentType === "Inshop" ||
@@ -514,6 +528,9 @@ export default {
});
}
},
+ getCarIdfromStore() {
+ return store.getters.vehicle.carId;
+ },
getServiceAddressFromStore() {
return store.getters.order.serviceLocation.address;
},
@@ -529,6 +546,9 @@ export default {
getServiceZipCodeFromStore() {
return store.getters.order.serviceLocation.zipCode;
},
+ getIsVehicleHeavyTruckFromStore() {
+ return store.getters.order.vehicle?.isBigTruck ?? false;
+ },
getIsVehicleProtectedFromStore() {
return store.getters.order.serviceLocation.isVehicleProtected;
},
@@ -549,6 +569,9 @@ 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-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 a2ab4134e..5e7aa73e4 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
@@ -33,6 +33,13 @@
cmsWidgetName="AlertInvalidZipWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
+
@@ -48,6 +55,7 @@ import {
getPricedRecycleFeePart,
getServiceabilityDetails,
getBillToAccountNumber,
+ getClosestApplicableShops,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
export default {
@@ -63,6 +71,7 @@ export default {
internalModel: this.copyModel(this.modelValue),
serviceZipCodeTextInputId: "",
displayInvalidZipAlert: false,
+ displayNoServiceAlert: false,
};
},
props: {
@@ -73,6 +82,11 @@ export default {
zipCode: "",
}),
},
+ carId: String,
+ isVehicleHeavyTruck: {
+ type: Boolean,
+ default: false,
+ },
mobileFeePart: {
type: Object,
default: () => ({}),
@@ -107,6 +121,7 @@ export default {
methods: {
resetAlerts() {
this.displayInvalidZipAlert = false;
+ this.displayNoServiceAlert = false;
},
resetsOnZipInput() {
this.resetAlerts();
@@ -155,6 +170,22 @@ export default {
this.focusOnZipInput();
this.resetModalButtonStyle();
} else {
+ if (this.isVehicleHeavyTruck) {
+ // check the location endpoint to verify this zip can service a heavy truck
+ const closestShops = await getClosestApplicableShops(
+ this.internalModel.zipCode,
+ this.carId,
+ "service-location"
+ );
+
+ if (!closestShops || closestShops.providers?.length === 0) {
+ this.displayNoServiceAlert = true;
+ this.focusOnZipInput();
+ this.resetModalButtonStyle();
+ return null;
+ }
+ }
+
this.internalModel.state = zipCodeData.state;
this.internalModel.zipCodeCtu = zipCodeData.zipCodeCtu;
diff --git a/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue b/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue
index e46b02a96..20accf43b 100644
--- a/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue
+++ b/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue
@@ -6,7 +6,7 @@
v-model="selectedValue">
+ class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4 p-md-4">
{{ buttonLabel }}
diff --git a/src/layouts/service-location/shop-question/shop-question.vue b/src/layouts/service-location/shop-question/shop-question.vue
index a15d8bb0b..417a3b4c4 100644
--- a/src/layouts/service-location/shop-question/shop-question.vue
+++ b/src/layouts/service-location/shop-question/shop-question.vue
@@ -179,12 +179,12 @@ export default {
}
});
- this.pushEventToGA(
- this.GaCategories.SERVICE_LOCATION,
- gaAction,
- shops.join(","),
- true
- );
+ var joinedShops = shops.join(",");
+ if (!joinedShops) {
+ joinedShops = "no-shops";
+ }
+
+ this.pushEventToGA(this.GaCategories.SERVICE_LOCATION, gaAction, joinedShops, true);
}
if (this.answers.length === 0) {
@@ -271,6 +271,10 @@ export default {
margin-top: 1rem;
text-align: center;
+ a {
+ @include responsive-font-size-md(0.875rem, 1rem);
+ }
+
.button-question {
.question-text {
margin-top: 0.5rem;
diff --git a/src/layouts/service-zip/service-zip.spec.js b/src/layouts/service-zip/service-zip.spec.js
index 773157980..e7cd68e3d 100644
--- a/src/layouts/service-zip/service-zip.spec.js
+++ b/src/layouts/service-zip/service-zip.spec.js
@@ -47,6 +47,7 @@ function resetMockStoreData() {
registration: {
licensePlate: null,
},
+ isServiceable: true,
},
serviceLocation: {
address: null,
@@ -173,15 +174,21 @@ function resetMockStoreData() {
};
}
+const applicationUser = {
+ experiments: [],
+};
+
function applyMockStoreDataToGetters() {
store.getters = {
experimentSettings: mockExperimentSettings,
+ applicationUser: applicationUser,
order: mockStoreData,
damage: mockStoreData.damage,
payment: mockStoreData.payment,
policy: mockStoreData.policy,
externalParameterServiceZip: mockStoreData.externalParameterServiceZip,
emailOrSms: mockStoreData.customer.emailAddress,
+ vehicle: mockStoreData.vehicle,
};
store.state.order = mockStoreData;
store.state.applicationUser.experiments = mockExperimentSettings;
@@ -408,6 +415,33 @@ 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);
+ });
});
});
@@ -504,6 +538,16 @@ 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 9528d50bc..f8952a206 100644
--- a/src/layouts/service-zip/service-zip.vue
+++ b/src/layouts/service-zip/service-zip.vue
@@ -47,10 +47,18 @@
v-if="displayNonServiceableZipAlert"
alertClass="alert-danger" />
+
+
@@ -110,8 +118,11 @@ export default {
return {
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
emailOrSms: this.getEmailOrSmsFromStore(),
+ carId: this.getCarIdfromStore(),
+ isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
displayInvalidZipAlert: false,
displayNonServiceableZipAlert: false,
+ displayNoServiceAlert: false,
};
},
@@ -184,6 +195,19 @@ export default {
arePagePrerequisitesValid() {
return store.getters.damage.isRepair || store.getters.damage.glassToReplace?.length > 0;
},
+ getCarIdfromStore() {
+ return store.getters.vehicle.carId;
+ },
+ getIsVehicleHeavyTruckFromStore() {
+ return store.getters.isHeavyTruckVehicle;
+ },
+ async findClosestApplicableShops() {
+ return await this.dispatchStoreActionWithLogging(
+ storeActions.GET_CLOSEST_APPLICABLE_SHOPS,
+ { zip: this.serviceZipCode, carId: this.carId },
+ "service-zip"
+ );
+ },
async backButtonAction() {
const skipVin = await skipVinLookup();
// route to move backwards
@@ -252,6 +276,14 @@ export default {
return this.$refs.navbar.removeLoader();
}
this.displayNonServiceableZipAlert = false;
+ 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;
@@ -309,6 +341,7 @@ export default {
watch: {
serviceZipCode() {
this.displayNonServiceableZipAlert = false;
+ this.displayNoServiceAlert = false;
},
},
components: {
diff --git a/src/layouts/vehicle/vehicle.vue b/src/layouts/vehicle/vehicle.vue
index 12e7268fa..bad52fb02 100644
--- a/src/layouts/vehicle/vehicle.vue
+++ b/src/layouts/vehicle/vehicle.vue
@@ -48,6 +48,19 @@
validationRules="style-required"
placeHolderText="Select style"
customDropdownId="styleQuestionField" />
+
+
+
@@ -557,4 +636,9 @@ export default {
margin-top: 1.5rem;
margin-bottom: 0;
}
+.separator-line {
+ grid-area: 2/1/2/8;
+ border-top: 1px solid $gray-500;
+ margin: 0.5rem 0;
+}
diff --git a/src/layouts/vin-lookup/vin-lookup.spec.js b/src/layouts/vin-lookup/vin-lookup.spec.js
index 39aa0f008..458f9fc48 100644
--- a/src/layouts/vin-lookup/vin-lookup.spec.js
+++ b/src/layouts/vin-lookup/vin-lookup.spec.js
@@ -32,6 +32,7 @@ jest.mock("@/store", () => ({
damage: {
glassToReplace: "windshield",
},
+ isHeavyTruckVehicle: true,
},
}));
@@ -257,6 +258,39 @@ 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 f913993b2..aaef69fe8 100644
--- a/src/layouts/vin-lookup/vin-lookup.vue
+++ b/src/layouts/vin-lookup/vin-lookup.vue
@@ -71,7 +71,8 @@
vinPopulatedOnPageLoad &&
isInsuranceVerified &&
!displayInvalidZipAlert &&
- !displayNonServiceableZipAlert
+ !displayNonServiceableZipAlert &&
+ !displayNoServiceAlert
"
alertClass="alert-success" />
@@ -107,14 +108,23 @@
vinPopulatedOnPageLoad &&
!isInsuranceVerified &&
!displayInvalidZipAlert &&
- !displayNonServiceableZipAlert
+ !displayNonServiceableZipAlert &&
+ !displayNoServiceAlert
"
alertClass="alert-success" />
+
+
@@ -197,6 +207,8 @@ export default {
vin: this.getVinFromStore(),
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
emailOrSms: this.getEmailOrSmsFromStore(),
+ carId: this.getCarIdfromStore(),
+ isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
isCarIdDifferent: false,
customAlertData: {},
previouslyEnteredCarId: "",
@@ -207,6 +219,7 @@ export default {
displayVinNotFoundAlert: false,
displayMatchedDifferentVehicleAlert: false,
displayVinScanFailedAlert: false,
+ displayNoServiceAlert: false,
};
},
methods: {
@@ -222,6 +235,12 @@ 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(
@@ -245,6 +264,13 @@ export default {
);
}
},
+ async findClosestApplicableShops() {
+ return await this.dispatchStoreActionWithLogging(
+ storeActions.GET_CLOSEST_APPLICABLE_SHOPS,
+ { zip: this.serviceZipCode, carId: this.carId },
+ "vin-lookup"
+ );
+ },
async forwardButtonAction() {
this.resetAlerts();
@@ -277,6 +303,15 @@ export default {
return this.$refs.navbar.removeLoader();
}
this.displayInvalidZipAlert = false;
+ // 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) {
@@ -394,6 +429,15 @@ export default {
);
}
+ 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);
@@ -476,6 +520,7 @@ export default {
this.displayInvalidZipAlert = false;
this.displayVinNotFoundAlert = false;
this.displayVinScanFailedAlert = false;
+ this.displayNoServiceAlert = false;
},
},
mounted() {
@@ -552,6 +597,7 @@ export default {
},
serviceZipCode() {
this.displayNonServiceableZipAlert = false;
+ this.displayNoServiceAlert = false;
},
},
components: {
diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js
index e59ec5039..d6e608d4b 100644
--- a/src/mixins/analytics-mixin.js
+++ b/src/mixins/analytics-mixin.js
@@ -337,7 +337,7 @@ export default {
// Recalibration
if (hasSubmittedOrder) {
- payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedOrder;
+ payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedState;
} else {
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder;
}
diff --git a/src/mixins/analytics-mixin.spec.js b/src/mixins/analytics-mixin.spec.js
index a4b4672bb..711702c4a 100644
--- a/src/mixins/analytics-mixin.spec.js
+++ b/src/mixins/analytics-mixin.spec.js
@@ -425,7 +425,7 @@ describe("analyticsMixin.js", () => {
workOrderId: "222222222222",
};
store.getters.isRecalibrationOnOrder = true;
- store.getters.isRecalibrationOnSubmittedOrder = false;
+ store.getters.isRecalibrationOnSubmittedState = false;
});
test("Pushes to data layer if nominal", () => {
diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js
index 151d17dd9..b29eae8c1 100644
--- a/src/mixins/base-mixin.js
+++ b/src/mixins/base-mixin.js
@@ -1,6 +1,7 @@
import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js";
+import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { vehicleCategories } from "@/constants/vehicle-categories.js";
import { routerParams } from "@/router/router-constants/router-params";
@@ -198,10 +199,26 @@ export default {
}
},
getSubmittedOrder() {
- return JSON.parse(window.sessionStorage.getItem("submittedOrder"));
+ return JSON.parse(
+ window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
+ )?.order;
},
hasSubmittedOrder() {
- return window.sessionStorage.getItem("submittedOrder") !== null;
+ const submittedState = window.sessionStorage.getItem(
+ sessionStorageKeyConstants.SUBMITTED_STATE
+ );
+ return submittedState !== null && submittedState.order !== null;
+ },
+ getSubmittedApplicationUser() {
+ return JSON.parse(
+ window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
+ )?.applicationUser;
+ },
+ hasSubmittedApplicationUser() {
+ const submittedState = window.sessionStorage.getItem(
+ sessionStorageKeyConstants.SUBMITTED_STATE
+ );
+ return submittedState !== null && submittedState.applicationUser !== null;
},
},
computed: {
diff --git a/src/mixins/experiment-mixin.js b/src/mixins/experiment-mixin.js
index 11b573683..474945a54 100644
--- a/src/mixins/experiment-mixin.js
+++ b/src/mixins/experiment-mixin.js
@@ -1,17 +1,36 @@
import store from "@/store";
+import * as experimentHelper from "../helpers/experiment-helper";
+
+/*
+ This is a wrapper around experiment-helper.js methods that allows an override
+ to provide a custom list of experiments. At the time of implementation this
+ was necessary for checking submittedState for experiments
+*/
export default {
methods: {
- hasSettingEqualTo(settingName, settingValue) {
- return store.getters.experimentSettings[settingName] == settingValue;
+ hasSettingEqualTo(
+ settingName,
+ settingValue,
+ experimentList = store.getters.applicationUser.experiments
+ ) {
+ const experimentSettings =
+ experimentHelper.getExperimentSettingsFromExperimentList(experimentList);
+ return experimentHelper.hasSettingEqualTo(
+ settingName,
+ settingValue,
+ experimentSettings
+ );
},
- hasSetting(settingName) {
- return Object.hasOwn(store.getters.experimentSettings, settingName);
+ hasSetting(settingName, experimentList = store.getters.applicationUser.experiments) {
+ const experimentSettings =
+ experimentHelper.getExperimentSettingsFromExperimentList(experimentList);
+ return experimentHelper.hasSetting(settingName, experimentSettings);
},
- getSettingValue(settingName) {
- return this.hasSetting(settingName)
- ? store.getters.experimentSettings[settingName]
- : null;
+ getSettingValue(settingName, experimentList = store.getters.applicationUser.experiments) {
+ const experimentSettings =
+ experimentHelper.getExperimentSettingsFromExperimentList(experimentList);
+ return experimentHelper.getSettingValue(settingName, experimentSettings);
},
},
};
diff --git a/src/mixins/experiment-mixin.spec.js b/src/mixins/experiment-mixin.spec.js
index 1b2bff2f5..8dc3df2b6 100644
--- a/src/mixins/experiment-mixin.spec.js
+++ b/src/mixins/experiment-mixin.spec.js
@@ -135,6 +135,15 @@ function setupMocks({ experimentSettings }) {
store.getters = {
experimentSettings: experimentSettings ?? testExperimentSettings,
+ applicationUser: {
+ experiments: [
+ {
+ isActive: true,
+ isExposed: true,
+ settings: experimentSettings ?? testExperimentSettings,
+ },
+ ],
+ },
};
const mockComponent = {
diff --git a/src/router/index.js b/src/router/index.js
index 345790734..eddc14e16 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -2,6 +2,7 @@
import { createWebHistory, createRouter } from "vue-router";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "../constants/store-mutations";
+import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { routingTable } from "@/router/router-constants/routing-table.js";
import { globalEvents, globalEventTypes } from "@/constants/events";
@@ -58,11 +59,17 @@ const routes = [
log(` --from.redirectedFrom:>${JSON.stringify(from.redirectedFrom)}<`, "");
await analyticsMixin.methods.validateSession();
-
+ var fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE);
+ var offerQuote = getQuerystringParameter(queryStrings.OFFER_QUOTE);
+ log(" --offerQuote: ", offerQuote);
// after session is validated, remove the fromHeritage querystring if it exists so session expiration works
if (to.query) {
delete to.query[queryStrings.FROM_HERITAGE];
}
+ //remove offerquote querystring if it exists
+ if (offerQuote) {
+ delete to.query[queryStrings.OFFER_QUOTE];
+ }
if (getFunnelCookie()?.SuppressConceptFunnel) {
log(" --go to heritage suppressConceptFunnel: ");
@@ -93,10 +100,15 @@ const routes = [
log(" --to.query.fmgPage ", to.query?.fmgPage);
// Intercept all navigation if a submitted order exists in storage
- if (window.sessionStorage.getItem("submittedOrder") !== null) {
+ if (
+ window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !==
+ null
+ ) {
if (to.query.fmgPage !== funnelStartPageName) {
to.query.fmgPage = fmgPageValues.CONFIRMATION;
- log(" --has submittedOrder go to confirmation");
+ log(
+ ` --has ${sessionStorageKeyConstants.SUBMITTED_STATE} go to confirmation`
+ );
}
}
// On entering the funnel "fresh", read cookie information, decide what to do next.
@@ -173,6 +185,11 @@ const routes = [
updateExternalParameterState();
}
+ //Update CASH tab when return from heritage and have offerQuote param
+ if (offerQuote === "true" && fromHeritage === "true") {
+ store.commit(storeMutations.UPDATE_IS_INSURANCE, false);
+ }
+
// clear part related state because heritage selected a new vehicle
if (
to.query.fmgPage === fmgPageValues.VEHICLE &&
diff --git a/src/store/index.js b/src/store/index.js
index e67db8f1e..c36af8bb0 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,6 +1,7 @@
import { createStore } from "vuex";
import { endpoints } from "@/constants/endpoints.js";
import { storeMutations } from "@/constants/store-mutations";
+import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper";
import createPersistedState from "vuex-persistedstate";
import globalMethods from "@/global-methods";
@@ -20,6 +21,10 @@ import {
} from "@/constants/schedule-constants";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper";
+import {
+ getExperimentSettingsFromExperimentList,
+ hasSettingEqualTo,
+} from "../helpers/experiment-helper";
import { queryStrings } from "@/constants/query-strings";
import { partTypeStrings } from "@/constants/part-type-strings";
import {
@@ -61,9 +66,13 @@ const getDefaultState = () => {
make: null,
model: null,
style: null,
+ vehicleSubType: null,
+ vehicleSpecialClass: null,
+ isBigTruck: false,
carId: null,
category: null,
vin: null,
+ canSafeliteService: null,
imageUrl: null,
imageVifNumber: null,
imageColor: null,
@@ -211,12 +220,24 @@ export const mutations = {
updateStyle(state, style) {
state.order.vehicle.style = style;
},
+ updateVehicleSubType(state, vehicleSubType) {
+ state.order.vehicle.vehicleSubType = vehicleSubType;
+ },
+ updateVehicleSpecialClass(state, vehicleSpecialClass) {
+ state.order.vehicle.vehicleSpecialClass = vehicleSpecialClass;
+ },
+ updateIsBigTruck(state, isBigTruck) {
+ state.order.vehicle.isBigTruck = isBigTruck;
+ },
updateCarId(state, carId) {
state.order.vehicle.carId = carId;
},
updateVehicleCategory(state, category) {
state.order.vehicle.category = category;
},
+ updateVehicleCanSafeliteService(state, canSafeliteService) {
+ state.order.vehicle.canSafeliteService = canSafeliteService;
+ },
updateVehicleImageUrl(state, imageUrl) {
state.order.vehicle.imageUrl = imageUrl;
},
@@ -657,6 +678,9 @@ export const mutations = {
imageUrl: sessionInformation.order.vehicle?.imageUrl,
imageVifNumber: sessionInformation.order.vehicle?.imageVifNumber,
imageColor: sessionInformation.order.vehicle?.imageVifColor,
+ vehicleSubType: sessionInformation.order.vehicle?.vehicleSubType,
+ vehicleSpecialClass: sessionInformation.order.vehicle?.vehicleSpecialClass,
+ isBigTruck: sessionInformation.order.vehicle?.isBigTruck,
});
state.order.damage.glassToReplace = sessionInformation.order.damage.glassToReplace;
@@ -822,8 +846,10 @@ export const getters = {
},
coverageIsVerified: (state) => {
let order;
- if (window.sessionStorage.getItem("submittedOrder") !== null) {
- order = JSON.parse(window.sessionStorage.getItem("submittedOrder"));
+ if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
+ order = JSON.parse(
+ window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
+ ).order;
} else {
order = state.order;
}
@@ -850,31 +876,34 @@ export const getters = {
isRecalibrationOnOrder: (state) => {
return getHasRecalibrationPart(state);
},
- isRecalibrationOnSubmittedOrder: (state) => {
- if (window.sessionStorage.getItem("submittedOrder") !== null) {
+ isRecalibrationOnSubmittedState: (state) => {
+ if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
return getHasRecalibrationPart({
- order: JSON.parse(window.sessionStorage.getItem("submittedOrder")),
+ order: JSON.parse(
+ window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
+ ).order,
});
}
-
return false;
},
shouldHideRecalibration: (state) => {
- var order;
- if (window.sessionStorage.getItem("submittedOrder") !== null) {
- order = JSON.parse(window.sessionStorage.getItem("submittedOrder"));
- } else {
- order = state.order;
+ if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
+ state = JSON.parse(
+ window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
+ );
}
-
+ let order = state.order;
+ let applicationUser = state.applicationUser;
if (order.payment.isInsurance) {
return false;
}
return (
- experimentMixin.methods
- .getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)
- ?.toLowerCase() === "true" && getters.isRecalibrationOnOrder(state)
+ hasSettingEqualTo(
+ experimentSettings.RECAL_PRICE_REMOVE,
+ "true",
+ getExperimentSettingsFromExperimentList(applicationUser.experiments)
+ ) && getters.isRecalibrationOnOrder(state)
);
},
areRearWipersOnOrder: (state) => {
@@ -974,10 +1003,7 @@ export const getters = {
};
},
experimentSettings: (state) =>
- state.applicationUser.experiments
- .filter((x) => !!x.isActive)
- .map((x) => x.settings)
- .reduce((r, c) => Object.assign(r, c), {}) ?? {},
+ getExperimentSettingsFromExperimentList(state.applicationUser.experiments),
isVerifiedAndDeductibleZeroConfirmed: (state) => {
// used in CMS on /payment-method in Header Sub Text, for FunnelSubHeaderWidget on cart pages
@@ -1241,6 +1267,15 @@ export const actions = {
});
},
+ getClosestApplicableShops(context, { payload: { zip, carId }, pageNameToLog }) {
+ return globalMethods.callHttpClient({
+ methods: endpoints.ClosestApplicableShops.method,
+ endpoint: `${endpoints.ClosestApplicableShops.url}/${zip}/${carId}`,
+ logApiCall: true,
+ pageNameToLog: pageNameToLog,
+ });
+ },
+
// Dependency Actions
resetVehicleStateAndDependencies(context) {
context.commit(storeMutations.RESET_VEHICLE_STATE);
@@ -1828,6 +1863,8 @@ export const actions = {
endPoint += `&${glassPieces}`;
}
+ endPoint += `&isHeavyTruckVehicle=${context.getters.order.vehicle.isBigTruck}`;
+
return globalMethods.callHttpClient({
method: endpoints.GetServiceabilityDetails.method,
endpoint: endPoint,
@@ -1854,6 +1891,8 @@ export const actions = {
if (windshieldPartWithRecal) {
url += `/${windshieldPartWithRecal.partNumber}`;
}
+ //heavy truck vehicle
+ url += `/${context.getters.order.vehicle.isBigTruck}`;
return globalMethods.callHttpClient({
method: endpoints.GetProviders.method,
@@ -1892,96 +1931,48 @@ export const actions = {
pageNameToLog: pageNameToLog,
});
},
- submitDonationPartToSV2(
+ async submitDonationPartToSV2(
context,
{ payload: { donationAmount, submittedOrder }, pageNameToLog }
) {
- donationAmount = parseInt(donationAmount);
- /* Request contract
- {
- "donationAmount": 5,
- "savedSessionId": "96b1a16e-410a-4d08-8421-fe36e82b2275",
- "paymentInfo": {
- "parentAccountNumber": 167132,
- "billToAccountNumber": 87291,
- "providerNumber": "005819",
- "carId": "CR00069266",
- "make": "Acura",
- "model": "MDX",
- "year": 2020,
- "eon": "S1820397",
- "zipCode": "43015",
- "state": "OH",
- "referralNumber": "1821895",
- "referralDate": "2025-03-20T16:27:38.93",
- "referralSequenceNumber": "1821895",
- "serviceZipCode": "43015",
- "isReplacement": true,
- "deductible": 0,
- "coverageStatus": null,
- "coverageSubStatus": null,
- "serverData": "ZtCNOF1tCLQBAx2NRKHE/n/fqD8zUxRfYtDuTu7uUiPpeg7i6TmwXwggssX5KsXVQbeft+PN+vXYfBFJytLVYgbafNDR0JH3izVTIOh7KGTeB6YaPuCJmodF4SfgVVDHD9LEmEH9wNzM9j3pFjgfXehOFH11SH9dNwTkXIbsGzAeu64DRyJsDdpamM/BPOxlJj60S/R8vnUM8mjyM7B9a63NskFI6BCVIh5mxDE4cSMUgb4zFtwvDgTPxJSwXqKShtoY6ryB8n5UPNkG/Z5tjVuJ7/8RV7WrURM7PN7xLfp6pVEeipWyeFnVEK33G9JOuB6yH75jPO6vMCYRNi0p0pg6laasFDy3hmfuy2W/N0qtcJLVa5N0bUMFFuPQusyEeqsEZ/zx1Srlvsk7i0DF8pTyBcm3JTF5BOWm60wg3ntEOqfRuQqzFc5updILwLAIp39CwJG/fs5Mj3HCDcUKwYlK70fAARtJwFu7wI0vqW5bUSUvJkvm1JRPlyWJ8JNY4Oq1apYqhZS9bps5ofgiOYJbBhPEboEnVukoTkQiYjgbMtBwVGKGL2nNEd77osoEg1KXhEQS+0KD3PoUu/bZkGub6FNGE6iKeFPt9gDSs+S21s22rVSaZkgaoC6SkufPpN6aV78Jx69nMIDd4go4pj2IJX4kS1pD1NEOSWjN4bgVKAtV6/bxBvhNJwnqUE+mE8eAmULsezub4LMhPqA58y/ZlO44jt/qkAw9nuVDRPBve+TdJTotID5dIqJJd6/vkDoHTVaSrbkCBbdG6d9By8AEbKuVrQS/+VurUnYEd2TGahPbOyRdNlud8RB4+4af0ZRFmiKkr6ppa5IbHbTIZ2qtyeOSbIOkv8O7xK/GL6e6Iaaz0wDHaQZC4Ea8FczjpFOlQgixfYcYJBb/W2db+uRvZ3lftVyJ/d082CGbYplOa+aTxymVcQSjBRbXzs6GZ2ytDqRYcRe94F9x/XSVj7g+mih7VS+YH0eOEp0ViTuWP8B1dVnOV5yTkdMbr8n9Q82G4v5brtQdiMDMY3HS6mxPb1IiKiqFX6Jgju8sIMxkJFZQ3iIkAJuOqUYkG1ZW7s4uI6hT46bRqzYaQA18CFu56qaMblOBFuxlJIsYsnMePh2xAH89oay5Kc88xwT2T/4+dzYQ0CqhQ4MTBB7nXelav2rMCjaLQ/V/a5r1TnLr2n0GbvODQeNl97qzi5lDYKlnuF8nOfOqZNOsPSsrsk3UZJJYZwNxDZaweHIeeg5P2ZW5agswPx/U5DkU1MzE1VAQ+DQAenaE2SLo4dSPBHMmt2gLpgu0LaCuzTR2IL/EM+Tpdn/Pcv1E7lPJIycIIwO7QGJcVkwCx8CJ/WCo6xDMq+9zjSVbqAaL5MzySZCeFCMyZxvEk7qXig6rflB3K3Jay4qSFjBxBXhOEuT6zKXyhR41IXRtZRmtPkscypFEUXvR5nJV7AQUIbljGEcikOLmSiCEnl8dI2Ny9xCHLOro8UXqVUy8dHGmTRGTE/mS1JNZ3y4XaZSyHcABVWbZcR3KIXVxd6rtUA/pBvWpbq8nH3WWlJGqkoxUVEridtXqcBuzTKg3/i6vLs0/pHGwdM38XizKcIxykvJrur3vEQ=="
- }
- }
- */
+ const state = JSON.parse(
+ window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
+ );
+ const order = state.order;
+ const payload = {
+ donationAmount: parseInt(donationAmount),
+ savedSessionId: state.applicationUser.savedSessionId,
+ paymentInfo: {
+ parentAccountNumber: order.payment.parentAccountNumber,
+ billToAccountNumber: order.payment.billToAccountNumber,
+ providerNumber: order.serviceLocation.provider.providerNumber,
+ carId: order.vehicle.carId,
+ make: order.vehicle.make,
+ model: order.vehicle.model,
+ year: order.vehicle.year,
+ eon: order.eon,
+ zipCode: order.serviceLocation.zipCode,
+ state: order.serviceLocation.state,
+ referralNumber: order.referralNumber,
+ referralDate: order.referralDate,
+ referralSequenceNumber: order.referralSequenceNumber,
+ serviceZipCode: order.serviceLocation.zipCode,
+ isReplacement: !order.damage.isRepair,
+ deductible: order.policy.currentDeductible,
+ coverageStatus: order.payment.insuranceCoverage.coverageStatus,
+ coverageSubStatus: order.payment.insuranceCoverage.coverageSubStatus,
+ serverData: order.lineItems.serverData,
+ },
+ };
- /* Uncomment and use when endpoint integration is finalized
- return globalMethods.callHttpClient({
+ const response = await globalMethods.callHttpClient({
method: endpoints.AddDonationPart.method,
endpoint: endpoints.AddDonationPart.url,
- payload: {
- donationAmount: donationAmount,
- savedSessionId: "96b1a16e-410a-4d08-8421-fe36e82b2275",
- paymentInfo: {
- parentAccountNumber: submittedSessionData.parentAccountNumber,
- billToAccountNumber: submittedSessionData.billToAccountNumber,
- providerNumber: submittedSessionData.providerNumber,
- carId: "CR00069266",
- make: "Acura",
- model: "MDX",
- year: 2020,
- eon: "S1820397",
- zipCode: "43015",
- state: "OH",
- referralNumber: "1821895",
- referralDate: "2025-03-20T16:27:38.93",
- referralSequenceNumber: "1821895",
- serviceZipCode: "43015",
- isReplacement: true,
- deductible: 0,
- coverageStatus: null,
- coverageSubStatus: null,
- serverData: "ZtCNOF1tCLQBAx2NRKHE/n/fqD8zUxRfYtDuTu7uUiPpeg7i6TmwXwggssX5KsXVQbeft+PN+vXYfBFJytLVYgbafNDR0JH3izVTIOh7KGTeB6YaPuCJmodF4SfgVVDHD9LEmEH9wNzM9j3pFjgfXehOFH11SH9dNwTkXIbsGzAeu64DRyJsDdpamM/BPOxlJj60S/R8vnUM8mjyM7B9a63NskFI6BCVIh5mxDE4cSMUgb4zFtwvDgTPxJSwXqKShtoY6ryB8n5UPNkG/Z5tjVuJ7/8RV7WrURM7PN7xLfp6pVEeipWyeFnVEK33G9JOuB6yH75jPO6vMCYRNi0p0pg6laasFDy3hmfuy2W/N0qtcJLVa5N0bUMFFuPQusyEeqsEZ/zx1Srlvsk7i0DF8pTyBcm3JTF5BOWm60wg3ntEOqfRuQqzFc5updILwLAIp39CwJG/fs5Mj3HCDcUKwYlK70fAARtJwFu7wI0vqW5bUSUvJkvm1JRPlyWJ8JNY4Oq1apYqhZS9bps5ofgiOYJbBhPEboEnVukoTkQiYjgbMtBwVGKGL2nNEd77osoEg1KXhEQS+0KD3PoUu/bZkGub6FNGE6iKeFPt9gDSs+S21s22rVSaZkgaoC6SkufPpN6aV78Jx69nMIDd4go4pj2IJX4kS1pD1NEOSWjN4bgVKAtV6/bxBvhNJwnqUE+mE8eAmULsezub4LMhPqA58y/ZlO44jt/qkAw9nuVDRPBve+TdJTotID5dIqJJd6/vkDoHTVaSrbkCBbdG6d9By8AEbKuVrQS/+VurUnYEd2TGahPbOyRdNlud8RB4+4af0ZRFmiKkr6ppa5IbHbTIZ2qtyeOSbIOkv8O7xK/GL6e6Iaaz0wDHaQZC4Ea8FczjpFOlQgixfYcYJBb/W2db+uRvZ3lftVyJ/d082CGbYplOa+aTxymVcQSjBRbXzs6GZ2ytDqRYcRe94F9x/XSVj7g+mih7VS+YH0eOEp0ViTuWP8B1dVnOV5yTkdMbr8n9Q82G4v5brtQdiMDMY3HS6mxPb1IiKiqFX6Jgju8sIMxkJFZQ3iIkAJuOqUYkG1ZW7s4uI6hT46bRqzYaQA18CFu56qaMblOBFuxlJIsYsnMePh2xAH89oay5Kc88xwT2T/4+dzYQ0CqhQ4MTBB7nXelav2rMCjaLQ/V/a5r1TnLr2n0GbvODQeNl97qzi5lDYKlnuF8nOfOqZNOsPSsrsk3UZJJYZwNxDZaweHIeeg5P2ZW5agswPx/U5DkU1MzE1VAQ+DQAenaE2SLo4dSPBHMmt2gLpgu0LaCuzTR2IL/EM+Tpdn/Pcv1E7lPJIycIIwO7QGJcVkwCx8CJ/WCo6xDMq+9zjSVbqAaL5MzySZCeFCMyZxvEk7qXig6rflB3K3Jay4qSFjBxBXhOEuT6zKXyhR41IXRtZRmtPkscypFEUXvR5nJV7AQUIbljGEcikOLmSiCEnl8dI2Ny9xCHLOro8UXqVUy8dHGmTRGTE/mS1JNZ3y4XaZSyHcABVWbZcR3KIXVxd6rtUA/pBvWpbq8nH3WWlJGqkoxUVEridtXqcBuzTKg3/i6vLs0/pHGwdM38XizKcIxykvJrur3vEQ=="
- }
- },
+ payload: payload,
logApiCall: true,
pageNameToLog: pageNameToLog,
});
- */
-
- /* Response contract
- {
- "wasSuccessful": true,
- "wasLocked": true
- }
- */
- let result;
-
- switch (donationAmount) {
- case 1:
- result = { wasSuccessful: true, wasLocked: false };
- break;
- case 3:
- result = { wasSuccessful: false, wasLocked: true };
- break;
- case 5:
- result = { wasSuccessful: false, wasLocked: false };
- break;
- default:
- result = { wasSuccessful: null, wasLocked: null }; // Default case if amount doesn't match
- }
-
- return result;
+ return response.data;
},
getPartFromCapabilityQuestionAnswer(context, { payload, pageNameToLog }) {
@@ -2176,6 +2167,9 @@ export const actions = {
year: vehicle.year,
make: vehicle.make,
model: vehicle.model,
+ subType: vehicle.vehicleSubType,
+ specialClass: vehicle.vehicleSpecialClass,
+ isBigTruck: vehicle.isBigTruck,
isInsurance: order.payment.isInsurance ?? false,
isVerified: order.payment.insuranceCoverage.isVerified ?? false,
lineItems: {
@@ -2229,6 +2223,9 @@ export const actions = {
model: vehicle.model,
style: vehicle.style,
vin: vehicle.vin,
+ vehicleSubType: vehicle.vehicleSubType,
+ vehicleSpecialClass: vehicle.vehicleSpecialClass,
+ isBigTruck: vehicle.isBigTruck,
registration: {
firstName: vehicle.registration.firstName,
lastName: vehicle.registration.lastName,
@@ -2439,7 +2436,21 @@ export const actions = {
// Vehicle
saveVehicle(
context,
- { year, make, model, style, carId, category, imageUrl, imageVifNumber, imageVifColor }
+ {
+ year,
+ make,
+ model,
+ style,
+ vehicleSubType,
+ vehicleSpecialClass,
+ isBigTruck,
+ carId,
+ category,
+ canSafeliteService,
+ imageUrl,
+ imageVifNumber,
+ imageVifColor,
+ }
) {
if (
context.state.order.vehicle.year != year ||
@@ -2455,8 +2466,12 @@ export const actions = {
context.commit(storeMutations.UPDATE_MAKE, make);
context.commit(storeMutations.UPDATE_MODEL, model);
context.commit(storeMutations.UPDATE_STYLE, style);
+ context.commit(storeMutations.UPDATE_VEHICLE_SUBTYPE, vehicleSubType);
+ context.commit(storeMutations.UPDATE_VEHICLE_SPECIAL_CLASS, vehicleSpecialClass);
+ context.commit(storeMutations.UPDATE_IS_BIGTRUCK, isBigTruck);
context.commit(storeMutations.UPDATE_CAR_ID, carId);
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category);
+ context.commit(storeMutations.UPDATE_VEHICLE_CAN_SAFELITE_SERVICE, canSafeliteService);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor);
@@ -3257,19 +3272,25 @@ export const actions = {
return false;
},
- createSubmittedOrder(context) {
- if (window.sessionStorage.getItem("submittedOrder") !== null) {
+ createSubmittedState(context) {
+ if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
return;
}
// create a submitted order object from vuex.
- const submittedOrder = context.state.order;
+ const submittedState = {
+ order: context.state.order,
+ applicationUser: context.state.applicationUser,
+ };
const experiments = context.state.applicationUser.experiments;
const affiliateCookies = context.state.applicationUser.affiliateCookies;
// set to local storage
- window.sessionStorage.setItem("submittedOrder", JSON.stringify(submittedOrder));
+ window.sessionStorage.setItem(
+ sessionStorageKeyConstants.SUBMITTED_STATE,
+ JSON.stringify(submittedState)
+ );
window.sessionStorage.setItem("createNewSessionForHeritage", true);
// clear vuex
@@ -3288,45 +3309,38 @@ export const actions = {
context.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies);
},
- addDonationToSubmittedOrder(context, donationAmount) {
- console.log("running addDonationToSubmittedOrder()... donationAmount: ", donationAmount);
-
+ addDonationToSubmittedState(context, donationAmount) {
const submittedOrder = deepClone(baseMixin.methods.getSubmittedOrder());
+ const submittedApplicationUser = deepClone(baseMixin.methods.getSubmittedApplicationUser());
- if (donationAmount > 0) {
- console.log(
- "running addDonationToSubmittedOrder()... adding donation to supportingItems in session storage "
- );
- submittedOrder.lineItems.supportingItems.push({
- partNumber: `DONATION_${donationAmount}`,
- description: `DONATION $${donationAmount}`,
- partType: partTypeStrings.DONATION,
- isInsurable: false,
- isChildPart: false,
- laborAmount: 0,
- sellingPrice: donationAmount,
- kitPrice: 0,
- salesTax: 0,
- id: `donation-${donationAmount}`,
- });
- } else {
- // filter out all donations
- console.log(
- "running addDonationToSubmittedOrder()... removing all donation items in supportingItems in session storage "
- );
+ submittedOrder.lineItems.supportingItems.push({
+ partNumber: `DONATION_${donationAmount}`,
+ description: `DONATION $${donationAmount}`,
+ partType: partTypeStrings.DONATION,
+ isInsurable: false,
+ isChildPart: false,
+ laborAmount: 0,
+ sellingPrice: donationAmount,
+ kitPrice: 0,
+ salesTax: 0,
+ });
- const nonDonationItems = submittedOrder.lineItems.supportingItems.filter((item) => {
- return item.partType !== "DONATION";
- });
- submittedOrder.lineItems.supportingItems = nonDonationItems;
- }
+ addGuidToLineItemsIfNotAlreadyThere(submittedOrder.lineItems.supportingItems);
+
+ const submittedState = {
+ order: submittedOrder,
+ applicationUser: submittedApplicationUser,
+ };
// set to local storage
- window.sessionStorage.setItem("submittedOrder", JSON.stringify(submittedOrder));
+ window.sessionStorage.setItem(
+ sessionStorageKeyConstants.SUBMITTED_STATE,
+ JSON.stringify(submittedState)
+ );
},
- resetSubmittedOrder(context) {
+ resetSubmittedState(context) {
// clear from local storage
- window.sessionStorage.removeItem("submittedOrder");
+ window.sessionStorage.removeItem(sessionStorageKeyConstants.SUBMITTED_STATE);
},
resetExternalParameterState(context) {
if (context.getters.isExternalParameter) {
diff --git a/src/styles/common-styles.scss b/src/styles/common-styles.scss
index a378f881e..ac50e7d70 100644
--- a/src/styles/common-styles.scss
+++ b/src/styles/common-styles.scss
@@ -4,13 +4,25 @@ body {
font-size: 16px;
background-color: #fff;
color: #4d5151;
+ #app {
+ display: flex;
+ flex-direction: column;
+ min-height: 100dvh;
+ form {
+ > .container-fluid {
+ margin-bottom: -1rem;
+ @media screen and (min-width: 1090px) {
+ margin-bottom: -5rem;
+ }
+ }
+ }
+ }
.container-fluid {
padding: 0 1.5rem;
.prevent-squish {
overflow-x: unset;
}
&.page-container-grouped-styles {
- height: 100dvh;
display: flex;
flex-direction: column;
}
diff --git a/src/styles/common-typography-styles.scss b/src/styles/common-typography-styles.scss
index 6a9c4f893..a6ab8289f 100644
--- a/src/styles/common-typography-styles.scss
+++ b/src/styles/common-typography-styles.scss
@@ -8,6 +8,7 @@ body {
//Headings
//add helper fw-bold to any element to get bold style (500)
+
h1,
.h1 {
line-height: 1.325;
@@ -36,6 +37,7 @@ h5,
.h5 {
line-height: 1.325;
font-weight: 400;
+ @include responsive-font-size-md(1.25rem, 1.625rem);
}
h6,
diff --git a/src/styles/mixins/customMixins.scss b/src/styles/mixins/customMixins.scss
index d0670ad6c..62abcc82d 100644
--- a/src/styles/mixins/customMixins.scss
+++ b/src/styles/mixins/customMixins.scss
@@ -8,3 +8,15 @@
@mixin box-shadow-hover($color) {
box-shadow: 0 0 0 4px $color;
}
+
+// Typography size mixin for medium breakpoints
+// Use this to adjust font size for medium breakpoint
+@mixin responsive-font-size-md($font-size, $md-font-size) {
+ font-size: $font-size;
+
+ @include media-breakpoint-up(md) {
+ font-size: $md-font-size;
+ }
+}
+// Usage: @include responsive-font-size-md(mobile-font-size-in-rem, desktop-font-size-in-rem);
+// Example: @include responsive-font-size-md(0.875rem, 1rem);
diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss
index df984b224..5c4045fd7 100644
--- a/src/styles/ux-variables.scss
+++ b/src/styles/ux-variables.scss
@@ -141,7 +141,7 @@ $h1-font-size: $font-size-base * 3;
$h2-font-size: $font-size-base * 2.625;
$h3-font-size: $font-size-base * 2;
$h4-font-size: $font-size-base * 1.625;
-$h5-font-size: $font-size-base * 1.25;
+//$h5-font-size: $font-size-base * 1.25;// Font size set in customMixins.scss
$h6-font-size: $font-size-base * 0.875;
//Border Radius
diff --git a/src/ux-components/list-button/list-button.vue b/src/ux-components/list-button/list-button.vue
index 1ebc46cc7..d31f20e5d 100644
--- a/src/ux-components/list-button/list-button.vue
+++ b/src/ux-components/list-button/list-button.vue
@@ -5,7 +5,7 @@
v-model="selectedValue">
+ class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4 p-md-4">
{{ buttonLabel }}
diff --git a/src/ux-components/text-link/text-link.vue b/src/ux-components/text-link/text-link.vue
index 30cd2d7e5..9283c08e7 100644
--- a/src/ux-components/text-link/text-link.vue
+++ b/src/ux-components/text-link/text-link.vue
@@ -80,10 +80,14 @@ a {
}
&.navigation-link,
&.new-window-link {
+ font-size: 0.875rem;
color: $black;
line-height: 26px;
white-space: nowrap;
align-items: center;
+ @include media-breakpoint-up(md) {
+ font-size: 1rem;
+ }
}
&.new-window-link {
margin-bottom: 1.5rem;