+
+${{ wipersModal.totalRearPrice }}
@@ -62,12 +55,17 @@
import buttonQuestion from "@/digital-components/button-question/button-question";
import addVapsButton from "./add-vaps-button/add-vaps-button";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
-import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
// SUPPORTING
import { partTypeStrings } from "@/constants/part-type-strings";
+import { defineRule } from "vee-validate";
+import { required } from "@/helpers/validation-rules";
+import { errorMessages } from "@/constants/error-messages";
-const wipersToDisplayStrings = {
+// DEFINE VALIDATION RULES
+defineRule("checkbox-required", required(errorMessages.OPTION_REQUIRED));
+
+const wipersOfferedStrings = {
FRONT: "FRONT",
REAR: "REAR",
BOTH: "BOTH",
@@ -83,15 +81,18 @@ export default {
data() {
return {
addVapsButton: addVapsButton,
- selectedFrontWipers: false,
- selectedRearWipers: false,
- wipersToDisplay: null,
+ wipersOffered: null,
+ isModalOpened: false,
+ selectedWipers: this.getSelectedWipers(),
};
},
methods: {
openModal(modalName) {
this.$refs[modalName].openModal();
},
+ closeModal(modalName) {
+ this.$refs[modalName].closeModal();
+ },
addRainDefenseToCart(modalName, part) {
const alreadyInCart = this.currentCartItems.vaps.some((item) => {
return item.partType === part.partType;
@@ -99,9 +100,11 @@ export default {
if (!alreadyInCart) {
this.currentCartItems.vaps.push(part);
}
- this.$refs[modalName].closeModal();
+ this.closeModal(modalName);
},
addWipersToCart(modalName, part) {
+ const itemsForCart = [];
+
const frontWipersAlreadyInCart = this.currentCartItems.vaps.some((item) => {
return item.partType === part.frontWiperLineItems[0].partType;
});
@@ -109,36 +112,43 @@ export default {
return item.partType === part.rearWiperLineItems[0]?.partType;
});
- if (this.wipersToDisplay === wipersToDisplayStrings.FRONT) {
- this.selectedFrontWipers = true; // set as true if front wipers is only option
+ if (this.wipersOffered === wipersOfferedStrings.BOTH) {
+ this.selectedWipers.forEach((wiper) => {
+ itemsForCart.push(wiper);
+ });
+ } else {
+ if (
+ !frontWipersAlreadyInCart &&
+ this.wipersOffered === wipersOfferedStrings.FRONT
+ ) {
+ itemsForCart.push(wipersOfferedStrings.FRONT);
+ }
+ if (!rearWipersAlreadyInCart && this.wipersOffered === wipersOfferedStrings.REAR) {
+ itemsForCart.push(wipersOfferedStrings.REAR);
+ }
}
- if (this.wipersToDisplay === wipersToDisplayStrings.REAR) {
- this.selectedRearWipers = true; // set as true if rear wipers is only option
- }
- if (!frontWipersAlreadyInCart && this.selectedFrontWipers) {
- if (part.frontWiperLineItems) {
- // for wipers, a "combined" part (frontWiperLineItems)
+
+ itemsForCart.forEach((type) => {
+ if (type === wipersOfferedStrings.FRONT) {
part.frontWiperLineItems.forEach((part) => {
this.currentCartItems.vaps.push(part);
});
}
- }
- if (!rearWipersAlreadyInCart && this.selectedRearWipers) {
- if (part.rearWiperLineItems) {
+ if (type === wipersOfferedStrings.REAR) {
part.rearWiperLineItems.forEach((part) => {
this.currentCartItems.vaps.push(part);
});
}
- }
- this.$refs[modalName].closeModal();
+ });
- if (this.wipersToDisplay === wipersToDisplayStrings.BOTH) {
- this.selectedFrontWipers = false; // set as false if both options are given
- this.selectedRearWipers = false; // set as false if both options are given
- }
+ this.closeModal(modalName);
+ this.selectedWipers;
},
- updateWipersToDisplay(value) {
- this.wipersToDisplay = value;
+ updateWipersOffered(value) {
+ this.wipersOffered = value;
+ },
+ getSelectedWipers() {
+ return null;
},
},
computed: {
@@ -229,31 +239,72 @@ export default {
];
prices.sort((a, b) => a - b);
modalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1];
- this.updateWipersToDisplay(wipersToDisplayStrings.BOTH);
+ this.updateWipersOffered(wipersOfferedStrings.BOTH);
buttons.push(modalData);
} else {
// NO FRONT WIPERS IN CART; SHOW ONLY FRONT WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalFrontPrice;
- this.updateWipersToDisplay(wipersToDisplayStrings.FRONT);
+ this.updateWipersOffered(wipersOfferedStrings.FRONT);
buttons.push(modalData);
}
} else if (!rearWipersInCart && this.wipersModal?.rearWiperLineItems.length > 0) {
// NO REAR WIPERS IN CART; SHOW ONLY REAR WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalRearPrice;
- this.updateWipersToDisplay(wipersToDisplayStrings.REAR);
+ this.updateWipersOffered(wipersOfferedStrings.REAR);
buttons.push(modalData);
}
return buttons;
},
- wipersToDisplayStrings() {
- return wipersToDisplayStrings;
+ wipersOfferedStrings() {
+ return wipersOfferedStrings;
+ },
+ wipersOptions() {
+ const wipersOptions = [];
+ const wipersFrontCopy = this.getCmsContent(
+ "AddFrontBladesQuestionWidget",
+ "QuestionText"
+ );
+ const wipersRearCopy = this.getCmsContent(
+ "AddRearBladesQuestionWidget",
+ "QuestionText"
+ );
+ const frontWipersLineItems = this.wipersModal.frontWiperLineItems;
+ const rearWipersLineItems = this.wipersModal.rearWiperLineItems;
+
+ if (frontWipersLineItems) {
+ const wipersOption = {
+ buttonLabel: wipersFrontCopy,
+ groupName: "wipers-options",
+ value: wipersOfferedStrings.FRONT,
+ buttonLabelSubCopy: "buttonLabelSubCopy",
+ buttonBodyCopy: "buttonBodyCopy",
+ buttonAuxillaryCopy: "buttonAuxillaryCopy",
+ buttonFooterCopy: "buttonFooterCopy",
+ additionalButtonData: "+$" + this.wipersModal.totalFrontPrice,
+ };
+ wipersOptions.push(wipersOption);
+ }
+ if (rearWipersLineItems) {
+ const wipersOption = {
+ buttonLabel: wipersRearCopy,
+ groupName: "wipers-options",
+ value: wipersOfferedStrings.REAR,
+ buttonLabelSubCopy: "buttonLabelSubCopy",
+ buttonBodyCopy: "buttonBodyCopy",
+ buttonAuxillaryCopy: "buttonAuxillaryCopy",
+ buttonFooterCopy: "buttonFooterCopy",
+ additionalButtonData: "+$" + this.wipersModal.totalRearPrice,
+ };
+ wipersOptions.push(wipersOption);
+ }
+
+ return wipersOptions;
},
},
components: {
buttonQuestion,
contentGroupModal,
- checkboxQuestion,
},
};
@@ -286,26 +337,31 @@ export default {
text-align: center;
}
.wiper-options {
- & > div {
- display: flex;
- justify-content: space-between;
- margin-bottom: 1rem;
+ .ui-checkbox {
+ display: block;
- &:last-child {
- margin-bottom: 0;
+ & > div.col {
+ margin-bottom: 1rem;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
}
+ .checkbox {
+ display: flex;
+ justify-content: space-between;
- .form-check {
- margin-bottom: 0;
+ .additional-button-data {
+ font-size: 0.875rem;
+ line-height: 1.75;
+ padding-left: 0.5rem;
+ color: $green;
+ }
}
}
p {
margin-bottom: 0;
- }
- p.price {
- font-size: 0.875rem;
- padding-left: 0.5rem;
- color: $green;
+ line-height: 1.75;
}
}
}
diff --git a/src/layouts/service-location/shop-question/shop-question.spec.js b/src/layouts/service-location/shop-question/shop-question.spec.js
index 82e48d470..b93728110 100644
--- a/src/layouts/service-location/shop-question/shop-question.spec.js
+++ b/src/layouts/service-location/shop-question/shop-question.spec.js
@@ -186,18 +186,21 @@ describe("shop-question.vue", () => {
buttonLabel: "Westerville",
buttonLabelSubCopy: "5 mi",
value: "003335",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
{
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
buttonLabel: "Worthington",
buttonLabelSubCopy: "10.5 mi",
value: "001820",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
{
buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
buttonLabel: "Columbus",
buttonLabelSubCopy: "11.5 mi",
value: "003343",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
]);
});
@@ -414,6 +417,10 @@ describe("shop-question.vue", () => {
},
});
+ displayedAnswers.forEach(
+ (answer) => (answer.additionalButtonData = wrapper.vm.additionalButtonData)
+ );
+
// Act
await wrapper.setProps({
shopProviderData: shopQuestionInitialData,
@@ -445,42 +452,49 @@ describe("shop-question.vue", () => {
// Assert
expect(wrapper.vm.answers.length).toBe(6);
+ console.log(wrapper.vm.answers);
expect(wrapper.vm.answers).toEqual([
{
buttonBodyCopy: "4403 Executive Pkwy, Westerville, OH 43081",
buttonLabel: "Westerville",
buttonLabelSubCopy: "5 mi",
value: "003335",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
{
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
buttonLabel: "Worthington",
buttonLabelSubCopy: "10.5 mi",
value: "001820",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
{
buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
buttonLabel: "Columbus",
buttonLabelSubCopy: "11.5 mi",
value: "003343",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
{
buttonBodyCopy: "1670 Harmon Ave, Columbus, OH 43223",
buttonLabel: "Columbus",
buttonLabelSubCopy: "16 mi",
value: "006747",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
{
buttonBodyCopy: "3938 Powell Rd, Powell, OH 43065",
buttonLabel: "Powell",
buttonLabelSubCopy: "16.5 mi",
value: "003341",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
{
buttonBodyCopy: "4580 W Broad St, Columbus, OH 43228",
buttonLabel: "Columbus",
buttonLabelSubCopy: "19.5 mi",
value: "003342",
+ additionalButtonData: wrapper.vm.additionalButtonData,
},
]);
});
diff --git a/src/layouts/service-location/shop-question/shop-question.vue b/src/layouts/service-location/shop-question/shop-question.vue
index 57702f841..1b4d7dea8 100644
--- a/src/layouts/service-location/shop-question/shop-question.vue
+++ b/src/layouts/service-location/shop-question/shop-question.vue
@@ -19,8 +19,7 @@
textPosition="text-start"
v-model="selectedProviderNumber"
isRequired
- validationRules="option-required"
- :additionalButtonData="additionalButtonData" />
+ validationRules="option-required" />
{
// use current page url query string name when to.name is "root" (due to unresolved navigation in beforeEach)
router.lastNavigationPage = to.name == "root" ? analyticsMixin.methods.getPageName() : to.name;
+ const fromQueryPage = from.query?.fmgPage;
+ if (fromQueryPage === undefined) {
+ showFmgLoadingModal(true);
+ }
+
next();
});
@@ -171,6 +177,8 @@ router.afterEach(async (to, from) => {
}
}
+ showFmgLoadingModal(false);
+
// Push page view to GA
analyticsMixin.methods.pushPageViewToGA();
diff --git a/src/store/index.js b/src/store/index.js
index 0d90f5e92..ba09c54bc 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -2196,15 +2196,25 @@ export const actions = {
"lineItems"
);
- let queryString =
- `ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
- `&BillToAccountNumber=${billToAccountNumber}` +
- `&ProviderNumber=${providerNumber}` +
- `&AppointmentType=${appointmentType}` +
- `&ServiceLocation.City=${serviceLocationCity}` +
- `&ServiceLocation.State=${serviceLocationState}` +
- `&ServiceLocation.ZipCode=${serviceLocationZipCode}` +
- `&${pricedLineItemsFormattedForRequest}`;
+ let queryString = "";
+ if (appointmentType == "Mobile") {
+ queryString =
+ `ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
+ `&BillToAccountNumber=${billToAccountNumber}` +
+ `&ProviderNumber=${providerNumber}` +
+ `&AppointmentType=${appointmentType}` +
+ `&ServiceLocation.City=${serviceLocationCity}` +
+ `&ServiceLocation.State=${serviceLocationState}` +
+ `&ServiceLocation.ZipCode=${serviceLocationZipCode}` +
+ `&${pricedLineItemsFormattedForRequest}`;
+ } else {
+ queryString =
+ `ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
+ `&BillToAccountNumber=${billToAccountNumber}` +
+ `&ProviderNumber=${providerNumber}` +
+ `&AppointmentType=${appointmentType}` +
+ `&${pricedLineItemsFormattedForRequest}`;
+ }
const lineItemServerData = context.getters.order.lineItems.serverData;
if (lineItemServerData) {
@@ -2257,13 +2267,18 @@ export const actions = {
},
};
- const validateResponse = await globalMethods.callHttpClient({
- method: endpoints.ValidatePromo.method,
- endpoint: endpoints.ValidatePromo.url,
- payload: requestObject,
- logApiCall: true,
- pageNameToLog: pageNameToLog,
- });
+ const validateResponse = await globalMethods
+ .callHttpClient({
+ method: endpoints.ValidatePromo.method,
+ endpoint: endpoints.ValidatePromo.url,
+ payload: requestObject,
+ logApiCall: true,
+ pageNameToLog: pageNameToLog,
+ })
+ .catch((error) => {
+ // Validate promo will return 4xx errors when an invalid promo
+ return error;
+ });
if (validateResponse?.data?.serverData) {
context.commit(
diff --git a/src/ux-components/checkbox/checkbox.spec.js b/src/ux-components/checkbox/checkbox.spec.js
new file mode 100644
index 000000000..7aa72b718
--- /dev/null
+++ b/src/ux-components/checkbox/checkbox.spec.js
@@ -0,0 +1,66 @@
+import { mount } from "@vue/test-utils";
+import checkbox from "./checkbox";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
+
+describe("checkbox.vue", () => {
+ it("Should have correct group name", async () => {
+ // Arrange
+ let { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ propsData: {
+ groupName: "checkbox-button-test",
+ value: "test value",
+ },
+ },
+ });
+
+ // Act
+ const input = wrapper.find("input");
+
+ // Assert
+ expect(input.attributes().name).toEqual("checkbox-button-test");
+ });
+
+ it("Should have correct label text", async () => {
+ // Act
+ let { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ propsData: {
+ buttonLabel: "label text",
+ value: "test value",
+ },
+ },
+ });
+
+ // Arrange
+ const paragraph = wrapper.find("p");
+
+ // Assert
+ expect(paragraph.text()).toEqual("label text");
+ });
+
+ it("Should have correct screenreader-only text", async () => {
+ // Act
+ let { wrapper } = setupMocks({});
+
+ // Arrange
+ await wrapper.setProps({
+ screenReaderOnlyText: "screenreader text",
+ value: "test value",
+ });
+ const paragraph = wrapper.find(".sr-only");
+
+ // Assert
+ expect(paragraph.text()).toEqual("screenreader text");
+ });
+});
+
+function setupMocks({ mountOptionsMockData = {} }) {
+ const wrapper = mount(checkbox, {
+ ...mountOptionsMockData,
+ mixins: [inputButtonWrapperMixin],
+ });
+
+ return { wrapper };
+}
diff --git a/src/ux-components/checkbox/checkbox.vue b/src/ux-components/checkbox/checkbox.vue
new file mode 100644
index 000000000..9c02b7b99
--- /dev/null
+++ b/src/ux-components/checkbox/checkbox.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+