@@ -86,6 +89,7 @@
@@ -260,6 +272,8 @@ export default {
}
.alert {
border: 1px solid $red;
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
}
.btn-success {
background: $green-100;
@@ -277,5 +291,12 @@ export default {
left: -0.25rem;
}
}
+ .custom-secondary {
+ &:hover {
+ color: $blue;
+ background: transparent;
+ border: 1px solid $blue;
+ }
+ }
}
diff --git a/src/experiment-components/service-package-radio-for-afterpay.vue b/src/experiment-components/service-package-radio-for-afterpay.vue
new file mode 100644
index 000000000..3356ee225
--- /dev/null
+++ b/src/experiment-components/service-package-radio-for-afterpay.vue
@@ -0,0 +1,474 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ in 4 interest-free payments
+
+ or
+
+
+ in single payment
+
+
+
+
+
+
+
+
+
+
+
+
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/cart/cart.vue b/src/fmg-components/cart/cart.vue
index f569c11d4..2a715bc93 100644
--- a/src/fmg-components/cart/cart.vue
+++ b/src/fmg-components/cart/cart.vue
@@ -401,9 +401,15 @@ export default {
if (!this.lineItems || this.lineItems.length < 1) {
return [];
}
+ let supportingItemsToAdd = [];
+ if (this.lineItems.supportingItems) {
+ supportingItemsToAdd = this.lineItems.supportingItems.filter(
+ (item) => item.partType !== partTypeStrings.DONATION
+ );
+ }
return [
...(this.lineItems.glassParts ?? []),
- ...(this.lineItems.supportingItems ?? []),
+ ...supportingItemsToAdd,
...(this.availableVaps ?? []),
];
},
@@ -466,6 +472,10 @@ export default {
});
}
+ if (this.donationCartItem) {
+ cartItems.push(this.donationCartItem);
+ }
+
return cartItems;
},
},
@@ -1064,6 +1074,42 @@ export default {
return promoCartItems;
},
+ showDonationCartItem() {
+ const donationLineItem = this.lineItems.supportingItems.find(
+ (item) => item.partType === partTypeStrings.DONATION
+ );
+ return !!donationLineItem;
+ },
+ donationCartItemName() {
+ return this.getCmsContent("DonationCartTextWidget", "Text");
+ },
+ donationCartItem() {
+ if (!this.lineItems || !this.lineItems.supportingItems) {
+ return null;
+ }
+
+ const donationLineItem = this.lineItems.supportingItems.find(
+ (item) => item.partType === partTypeStrings.DONATION
+ );
+
+ if (donationLineItem) {
+ return {
+ name: this.donationCartItemName,
+ category: cartItemCategories.SUPPORTING_ITEMS,
+ cartItemType: cartItemTypes.DONATION,
+ isDisplayed: true,
+ isRemovable: false,
+ subTotal: donationLineItem.sellingPrice,
+ salesTax: 0,
+ lineItems: [],
+ isCoveredByInsurance: cartItemTypesCoveredByInsurance.includes(
+ cartItemTypes.DONATION
+ ),
+ };
+ }
+
+ return null;
+ },
removeLinkText() {
return this.getCmsContent("RemoveCartItemTextWidget", "Text");
},
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 226941652..4862b5205 100644
--- a/src/fmg-components/funnel-header/funnel-header.vue
+++ b/src/fmg-components/funnel-header/funnel-header.vue
@@ -4,7 +4,9 @@
v-if="imageSrc">
+
+
@@ -95,6 +97,9 @@ export default {
diff --git a/src/layouts/quote/service-package-question/service-package-question.vue b/src/layouts/quote/service-package-question/service-package-question.vue
index ab4772107..64c075408 100644
--- a/src/layouts/quote/service-package-question/service-package-question.vue
+++ b/src/layouts/quote/service-package-question/service-package-question.vue
@@ -4,7 +4,7 @@
:answers="servicePackageAnswers"
:groupName="groupName"
buttonTypeString="servicePackageRadio"
- :buttonTypeObject="hasPricingByDay() ? servicePackageRadioExperiment : servicePackageRadio"
+ :buttonTypeObject="getButtonTypeObject()"
v-model="selectedPackageName"
:validationRules="validationRules"
:isRequired="isRequired"
@@ -18,6 +18,7 @@ import baseMixin from "@/mixins/base-mixin.js";
import { processIfStatements } from "@/helpers/cms-content-helper";
import servicePackageRadio from "./service-package-radio/service-package-radio";
import servicePackageRadioExperiment from "@/experiment-components/service-package-radio";
+import servicePackageRadioAfterpayExperiment from "@/experiment-components/service-package-radio-for-afterpay";
import { partTypeStrings } from "@/constants/part-type-strings";
import { packageNames } from "@/constants/package-names";
import {
@@ -61,6 +62,7 @@ export default {
return {
servicePackageRadio: servicePackageRadio,
servicePackageRadioExperiment: servicePackageRadioExperiment,
+ servicePackageRadioAfterpayExperiment: servicePackageRadioAfterpayExperiment,
selectedPackageName: null,
};
},
@@ -102,6 +104,11 @@ export default {
this.$emit("servicePackageDiscountSelected", supportingItems);
},
+ servicePackage(newValue) {
+ if (newValue !== null) {
+ this.selectDefaultPackage();
+ }
+ },
},
computed: {
nullSafeAvailableLineItems() {
@@ -151,6 +158,7 @@ export default {
Text: this.isInsuranceSelected
? false
: "Special: Save $" + this.getServicePackageDiscountPrice(),
+ isInsuranceSelected: this.isInsuranceSelected,
},
}));
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
@@ -232,6 +240,24 @@ export default {
"true"
);
},
+ isAfterpayBreakoutDisplay() {
+ return (
+ (!this.shouldHideRecalibration || !this.isRecalibrationOnOrder) &&
+ experimentMixin.methods.hasSettingEqualTo(
+ experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY,
+ "true"
+ )
+ );
+ },
+ getButtonTypeObject() {
+ if (this.isAfterpayBreakoutDisplay()) {
+ return this.servicePackageRadioAfterpayExperiment;
+ }
+ if (this.hasPricingByDay()) {
+ return this.servicePackageRadioExperiment;
+ }
+ return this.servicePackageRadio;
+ },
getHeaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, "HeaderText");
},
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 48876d5e8..2fd06d312 100644
--- a/src/layouts/schedule/schedule.vue
+++ b/src/layouts/schedule/schedule.vue
@@ -250,9 +250,15 @@ export default {
isRecalibrationOnOrder && shouldHideRecalibration
? getItemsWithoutRecalParts(lineItems.glassParts)
: (lineItems.glassParts ?? []);
+ const supportingItemsFromStore = lineItems?.supportingItems;
const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers(
lineItems.supportingItems,
- { partNumbersToRemove: [partNumberStrings.RECYCLE_FEE] }
+ {
+ partNumbersToRemove: [
+ partNumberStrings.RECYCLE_FEE,
+ partNumberStrings.PRICING_BY_DAY_UPCHARGE,
+ ],
+ }
);
const lineItemsToBePriced = {
glassParts: glassParts,
@@ -263,14 +269,16 @@ 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;
- if (supportingItemsWithoutFees) {
- const pricingByDayUpchargeFeeIndex = supportingItemsWithoutFees.findIndex(
+ if (supportingItemsFromStore) {
+ const pricingByDayUpchargeFeeIndex = supportingItemsFromStore?.findIndex(
(item) => item.partType == PRICING_BY_DAY_PART_TYPE
);
- if (pricingByDayUpchargeFeeIndex > -1) {
+ if (pricingByDayUpchargeFeeIndex > -1 && !isInsurance) {
includePricingByDayUpcharge = true;
}
}
@@ -300,7 +308,9 @@ export default {
});
// Get pricingByDayUpcharge needed for Pricing By Day
- const pricingByDayUpchargePartPromise = getPricingByDayPartWithPrice();
+ const pricingByDayUpchargePartPromise = showPricingByDay
+ ? getPricingByDayPartWithPrice()
+ : null;
const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_MOBILE_PREMIUM_FEE,
@@ -348,10 +358,12 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap);
- const pricingByDayUpcharge = await baseMixin.methods.getTotalLineItemPrice(
- resultMap.pricingByDayUpchargePart,
- false
- );
+ const pricingByDayUpcharge = showPricingByDay
+ ? await baseMixin.methods.getTotalLineItemPrice(
+ resultMap.pricingByDayUpchargePart,
+ false
+ )
+ : null;
const datePickerInitialData = resultMap.datePickerInitialData;
datePickerInitialData.pricingByDayBasePrice = pricingByDayBasePrice;
@@ -624,8 +636,8 @@ export default {
(item) => item.partType == PRICING_BY_DAY_PART_TYPE
);
- if (this.includePricingByDayUpcharge) {
- if (pricingByDayUpchargeFeeIndex > -1) {
+ if (this.includePricingByDayUpcharge && this.showPricingByDay) {
+ if (pricingByDayUpchargeFeeIndex && pricingByDayUpchargeFeeIndex > -1) {
supportingItems[pricingByDayUpchargeFeeIndex].laborAmount =
this.pricingByDayUpchargeLineItem.laborAmount;
supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice =
@@ -637,6 +649,7 @@ export default {
}
} else {
if (pricingByDayUpchargeFeeIndex >= 0) {
+ // remove pricing by day upcharge if it already was in store
supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
}
}
@@ -744,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 99b200037..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
@@ -1,5 +1,6 @@
import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
+import { partNumberStrings } from "@/constants/part-number-strings";
export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
if (!serviceZipCode) {
@@ -42,6 +43,50 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
return pricingResults[0];
}
+export async function getPricedRecycleFeePart(serviceZipCode, pageNameToLog) {
+ if (!serviceZipCode) {
+ return null;
+ }
+
+ const zipCodeData = await getZipCodeData(serviceZipCode);
+
+ // Get supporting items
+ const supportingItems = await baseMixin.methods.dispatchStoreActionWithLogging(
+ storeActions.GET_SUPPORTING_ITEMS,
+ null,
+ pageNameToLog,
+ false
+ );
+
+ if (
+ supportingItems.data === null ||
+ supportingItems.data === undefined ||
+ supportingItems.data === ""
+ ) {
+ return null;
+ }
+
+ const recycleFeePart = supportingItems.data.find(
+ (item) => item.partNumber === partNumberStrings.RECYCLE_FEE
+ );
+
+ if (recycleFeePart) {
+ // Get the Recycle Fee Part Price
+ const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
+ storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
+ {
+ availableLineItems: [recycleFeePart],
+ serviceZipCode: serviceZipCode,
+ serviceZipCodeCtu: zipCodeData.zipCodeCtu,
+ },
+ pageNameToLog,
+ false
+ );
+
+ return pricingResults[0];
+ }
+}
+
export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) {
// Get the Mobile Fee Part
return await baseMixin.methods.dispatchStoreActionWithLogging(
@@ -104,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/mobile-location-modal-questions/mobile-location-modal-questions.spec.js b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js
index f38262005..3e2873fa1 100644
--- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js
+++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js
@@ -106,6 +106,23 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
return Promise.resolve(mobileFeePart);
};
+const mockGetPricedRecycleFeePart = (mockServiceZipCode) => {
+ let recycleFeePart = {};
+
+ if (mockServiceZipCode === "43235") {
+ recycleFeePart = {
+ partNumber: "RECYCLE FEE",
+ description: "RECYCLE FEE",
+ partType: "FEE",
+ laborAmount: 39.99,
+ sellingPrice: 0,
+ kitPrice: 0,
+ };
+ }
+
+ return Promise.resolve(recycleFeePart);
+};
+
const mockGetServiceabilityDetails = (mockServiceZipCode) => {
const serviceabilityDetails = {
isGlassServiceableInshop: true,
@@ -123,6 +140,9 @@ jest.mock(
getPricedMobileFeePart: jest.fn((mockServiceZipCode) => {
return mockGetPricedMobileFeePart(mockServiceZipCode);
}),
+ getPricedRecycleFeePart: jest.fn((mockServiceZipCode) => {
+ return mockGetPricedRecycleFeePart(mockServiceZipCode);
+ }),
getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode);
}),
diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
index 4672cca7f..0d7a82248 100644
--- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
+++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
@@ -82,6 +82,7 @@ import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location
import { deepClone } from "@/helpers/object-helper";
import {
getPricedMobileFeePart,
+ getPricedRecycleFeePart,
getServiceabilityDetails,
getBillToAccountNumber,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
@@ -267,6 +268,12 @@ export default {
"service-location"
);
+ // retrieve recycle fee part
+ const recycleFeePart = await getPricedRecycleFeePart(
+ serviceZipCode,
+ "service-location"
+ );
+
// retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails(
serviceZipCode,
@@ -280,6 +287,7 @@ export default {
// update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart);
+ this.$emit("updated-recycle-fee-part", recycleFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu);
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 ddb765da3..7b3163c87 100644
--- a/src/layouts/service-location/service-location.vue
+++ b/src/layouts/service-location/service-location.vue
@@ -15,8 +15,11 @@
editScreenReaderTextCmsWidgetName="ScreenReaderZipEditWidget"
v-model="serviceZipCodeQuestion"
ref="serviceZipCodeQuestion"
+ :carId="carId"
+ :isVehicleHeavyTruck="isVehicleHeavyTruck"
:mobileFeePart="mobileFeePart"
@updated-mobile-fee-part="setMobileFeePart"
+ @updated-recycle-fee-part="setRecycleFeePart"
@updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase"
@updated-bill-to-account-number="setBillToAccountNumber"
@@ -73,6 +76,7 @@
v-show="isAppointmentTypeDisplayed"
:isServiceableMobile="isServiceableMobile"
:isServiceableInshop="isServiceableInshop"
+ :isServiceableDropoff="isServiceableDropoff"
:isDisplayed="isAppointmentTypeDisplayed"
:mobileFeeApplies="mobileFeeApplies"
ref="appointmentTypeQuestion"
@@ -90,6 +94,7 @@
:mobileFeePart="mobileFeePart"
:mobileFeeApplies="mobileFeeApplies"
@updated-mobile-fee-part="setMobileFeePart"
+ @updated-recycle-fee-part="setRecycleFeePart"
@updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase"
@updated-mobile-ctu="setCtuForMobile"
@@ -184,17 +189,22 @@ 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(),
selectedProvider: this.getSelectedProvider(),
mobileFeePart: null,
+ recycleFeePart: null,
zipContainsMilitaryBase: false,
zipCodeCtu: null,
billToAccountNumber: null,
@@ -347,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" ||
@@ -480,6 +497,9 @@ export default {
setMobileFeePart(mobileFeePart) {
this.mobileFeePart = mobileFeePart;
},
+ setRecycleFeePart(recycleFeePart) {
+ this.recycleFeePart = recycleFeePart;
+ },
//creating async function as the computed property can not directly handle asynchronous operations or promises.
async setMobileLocationQuestions(newValue) {
var newZipCode = newValue.addressQuestions.zipCode;
@@ -508,6 +528,9 @@ export default {
});
}
},
+ getCarIdfromStore() {
+ return store.getters.vehicle.carId;
+ },
getServiceAddressFromStore() {
return store.getters.order.serviceLocation.address;
},
@@ -523,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;
},
@@ -543,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;
@@ -592,6 +621,7 @@ export default {
},
updateAndSaveSupportingItems() {
let supportingItems = store.getters.lineItems.supportingItems;
+ let shouldSaveSupportingItems = false;
supportingItems =
!supportingItems && this.isMobileStaticRecalibrationApplicable
@@ -603,6 +633,21 @@ export default {
return;
}
+ // update recyle fee price
+ if (this.recycleFeePart) {
+ const recycleFeeIndex = supportingItems.findIndex(
+ (item) => item.partNumber == partNumberStrings.RECYCLE_FEE
+ );
+ if (recycleFeeIndex >= 0) {
+ supportingItems[recycleFeeIndex].laborAmount = this.recycleFeePart.laborAmount;
+ supportingItems[recycleFeeIndex].sellingPrice =
+ this.recycleFeePart.sellingPrice;
+ supportingItems[recycleFeeIndex].kitPrice = this.recycleFeePart.kitPrice;
+
+ shouldSaveSupportingItems = true;
+ }
+ }
+
// if we have a mobile fee, then save/update supporting items
if (this.selectedAppointmentType == "Mobile") {
const mobileFeeIndex = supportingItems.findIndex(
@@ -617,11 +662,7 @@ export default {
if (this.mobileFeePart !== null) supportingItems.push(this.mobileFeePart);
}
- this.dispatchStoreAction(
- this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
- supportingItems,
- false
- );
+ shouldSaveSupportingItems = true;
} else {
// if it's not a mobile, then make sure we remove any that may have been added
const removeMobileFeeIndex = supportingItems?.findIndex(
@@ -630,13 +671,18 @@ export default {
if (removeMobileFeeIndex >= 0) {
supportingItems.splice(removeMobileFeeIndex, 1);
- this.dispatchStoreAction(
- this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
- supportingItems,
- false
- );
+ shouldSaveSupportingItems = true;
}
}
+
+ // Use shouldSaveSupportingItems flag to determine if we need to save supporting items. Prevents unnecessary/multiple saves
+ if (shouldSaveSupportingItems) {
+ this.dispatchStoreAction(
+ this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
+ supportingItems,
+ false
+ );
+ }
},
async forwardButtonAction() {
this.navigatingForward = true;
diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js
index ed8a811ed..b1231991b 100644
--- a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js
+++ b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js
@@ -32,6 +32,23 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
return Promise.resolve(mobileFeePart);
};
+const mockGetPricedRecycleFeePart = (mockServiceZipCode) => {
+ let recycleFeePart = {};
+
+ if (mockServiceZipCode === "43235") {
+ recycleFeePart = {
+ partNumber: "RECYCLE FEE",
+ description: "RECYCLE FEE",
+ partType: "FEE",
+ laborAmount: 39.99,
+ sellingPrice: 0,
+ kitPrice: 0,
+ };
+ }
+
+ return Promise.resolve(recycleFeePart);
+};
+
const mockGetServiceabilityDetails = (mockServiceZipCode) => {
const serviceabilityDetails = {
isGlassServiceableInshop: true,
@@ -49,6 +66,9 @@ jest.mock(
getPricedMobileFeePart: jest.fn((mockServiceZipCode) => {
return mockGetPricedMobileFeePart(mockServiceZipCode);
}),
+ getPricedRecycleFeePart: jest.fn((mockServiceZipCode) => {
+ return mockGetPricedRecycleFeePart(mockServiceZipCode);
+ }),
getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode);
}),
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 24d063e5e..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" />
+
@@ -45,8 +52,10 @@ import alert from "@/ux-components/alert/alert";
import {
getPricedMobileFeePart,
+ getPricedRecycleFeePart,
getServiceabilityDetails,
getBillToAccountNumber,
+ getClosestApplicableShops,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
export default {
@@ -62,6 +71,7 @@ export default {
internalModel: this.copyModel(this.modelValue),
serviceZipCodeTextInputId: "",
displayInvalidZipAlert: false,
+ displayNoServiceAlert: false,
};
},
props: {
@@ -72,6 +82,11 @@ export default {
zipCode: "",
}),
},
+ carId: String,
+ isVehicleHeavyTruck: {
+ type: Boolean,
+ default: false,
+ },
mobileFeePart: {
type: Object,
default: () => ({}),
@@ -106,6 +121,7 @@ export default {
methods: {
resetAlerts() {
this.displayInvalidZipAlert = false;
+ this.displayNoServiceAlert = false;
},
resetsOnZipInput() {
this.resetAlerts();
@@ -154,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;
@@ -164,6 +196,12 @@ export default {
"service-location"
);
+ // retrieve recycle fee part
+ const recycleFeePart = await getPricedRecycleFeePart(
+ serviceZipCode,
+ "service-location"
+ );
+
// retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails(
serviceZipCode,
@@ -177,6 +215,7 @@ export default {
// update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart);
+ this.$emit("updated-recycle-fee-part", recycleFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-bill-to-account-number", billToAccountNumber);
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..7d53d105d 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,16 @@ export default {
return this.$refs.navbar.removeLoader();
}
this.displayNonServiceableZipAlert = false;
+ 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;
@@ -309,6 +343,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..788425861 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,17 @@ 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) {
@@ -394,6 +431,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 +522,7 @@ export default {
this.displayInvalidZipAlert = false;
this.displayVinNotFoundAlert = false;
this.displayVinScanFailedAlert = false;
+ this.displayNoServiceAlert = false;
},
},
mounted() {
@@ -552,6 +599,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 6fd6075ab..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: {
@@ -230,6 +247,12 @@ export default {
return "widget-name-" + this.cmsWidgetName;
},
},
+
+ mounted() {
+ if (!store.getters.externalParameterState?.isExternalParameter) {
+ showFmgLoadingModal(false);
+ }
+ },
};
function encodeUriData(payload) {
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 2230a652d..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 &&
@@ -433,9 +450,6 @@ router.afterEach(async (to, from) => {
await saveSession({ pageNameToLog: to.query.fmgPage });
}
}
- if (!store.getters.externalParameterState?.isExternalParameter) {
- showFmgLoadingModal(false);
- }
// Push page view to GA
analyticsMixin.methods.pushPageViewToGA();
diff --git a/src/store/index.js b/src/store/index.js
index da849c127..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;
@@ -720,7 +744,8 @@ export const mutations = {
// if we're loading a session and we do not have a provider number yet, then set isInsurance to null so that
// a service package is not selected by default on the quote page.
if (
- !sessionInformation.order.payment.isInsurance &&
+ (sessionInformation.order.payment.isInsurance === null ||
+ sessionInformation.order.payment.isInsurance === undefined) &&
!sessionInformation.order.serviceLocation.provider?.providerNumber
) {
state.order.payment.isInsurance = null;
@@ -821,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;
}
@@ -849,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) => {
@@ -973,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
@@ -1240,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);
@@ -1827,6 +1863,8 @@ export const actions = {
endPoint += `&${glassPieces}`;
}
+ endPoint += `&isHeavyTruckVehicle=${context.getters.order.vehicle.isBigTruck}`;
+
return globalMethods.callHttpClient({
method: endpoints.GetServiceabilityDetails.method,
endpoint: endPoint,
@@ -1853,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,
@@ -1891,6 +1931,49 @@ export const actions = {
pageNameToLog: pageNameToLog,
});
},
+ async submitDonationPartToSV2(
+ context,
+ { payload: { donationAmount, submittedOrder }, pageNameToLog }
+ ) {
+ 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,
+ },
+ };
+
+ const response = await globalMethods.callHttpClient({
+ method: endpoints.AddDonationPart.method,
+ endpoint: endpoints.AddDonationPart.url,
+ payload: payload,
+ logApiCall: true,
+ pageNameToLog: pageNameToLog,
+ });
+ return response.data;
+ },
getPartFromCapabilityQuestionAnswer(context, { payload, pageNameToLog }) {
const glassLocation = payload;
@@ -2084,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: {
@@ -2137,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,
@@ -2347,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 ||
@@ -2363,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);
@@ -3110,8 +3217,8 @@ export const actions = {
context.commit(storeMutations.UPDATE_CUSTOMER_WAITLIST_REQUESTED, waitListRequested);
},
- savePageData(context, emailOrSms) {
- context.commit(storeMutations.UPDATE_PAGE_DATA, emailOrSms);
+ savePageData(context, pageData) {
+ context.commit(storeMutations.UPDATE_PAGE_DATA, pageData);
},
saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) {
@@ -3165,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
@@ -3196,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",
- description: null,
- partType: "DONATION",
- isInsurable: false,
- isChildPart: false,
- laborAmount: 0,
- sellingPrice: donationAmount,
- kitPrice: 0,
- salesTax: null,
- 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;