diff --git a/src/assets/img/icons/Google.svg b/src/assets/img/icons/Google.svg new file mode 100644 index 000000000..606351ec9 --- /dev/null +++ b/src/assets/img/icons/Google.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/img/icons/Outlook.com.svg b/src/assets/img/icons/Outlook.com.svg new file mode 100644 index 000000000..467b585ac --- /dev/null +++ b/src/assets/img/icons/Outlook.com.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/img/icons/Outlook.svg b/src/assets/img/icons/Outlook.svg new file mode 100644 index 000000000..f974fb13a --- /dev/null +++ b/src/assets/img/icons/Outlook.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/img/icons/Yahoo.svg b/src/assets/img/icons/Yahoo.svg new file mode 100644 index 000000000..0206eccbb --- /dev/null +++ b/src/assets/img/icons/Yahoo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/img/icons/iCal.svg b/src/assets/img/icons/iCal.svg new file mode 100644 index 000000000..399bcdea0 --- /dev/null +++ b/src/assets/img/icons/iCal.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/constants/calendar-options.js b/src/constants/calendar-options.js new file mode 100644 index 000000000..c87a85b18 --- /dev/null +++ b/src/constants/calendar-options.js @@ -0,0 +1,8 @@ +const calendarOptions = { + ICAL: "iCal", + GOOGLE: "Google", + OUTLOOK: "Outlook", + OUTLOOKCOM: "Outlook.com", + YAHOO: "Yahoo", +}; +export { calendarOptions }; diff --git a/src/constants/calendar-status.js b/src/constants/calendar-status.js new file mode 100644 index 000000000..38cf23467 --- /dev/null +++ b/src/constants/calendar-status.js @@ -0,0 +1,7 @@ +const calendarStatus = { + FREE: "Free", + BUSY: "Busy", + TENTATIVE: "Tentative", + OUT_OF_THE_OFFICE: "OutOfTheOffice", +}; +export { calendarStatus }; diff --git a/src/fmg-components/add-vaps-modal-buttons/add-vaps-modal-buttons.vue b/src/fmg-components/add-vaps-modal-buttons/add-vaps-modal-buttons.vue index 0a6faa72f..d894f2ee8 100644 --- a/src/fmg-components/add-vaps-modal-buttons/add-vaps-modal-buttons.vue +++ b/src/fmg-components/add-vaps-modal-buttons/add-vaps-modal-buttons.vue @@ -21,9 +21,8 @@ export default { name: "add-vaps-modal-buttons", props: { cartItems: Object, - rainDefenseItem: Object, - wiperItems: Object, vapsTilesCmsName: String, + availableLineItems: Object, }, data() { return { @@ -55,13 +54,19 @@ export default { }); }, rainDefensePrice() { - const lineItem = this.rainDefenseItem; + // const lineItem = this.rainDefenseItem; + const lineItem = this.availableLineItems.find((item) => { + return item.partType === "RAIN DEFENSE"; + }); + const price = lineItem.laborAmount + lineItem.sellingPrice + lineItem.kitPrice; return parseFloat(price).toFixed(2); }, wipersPrice() { let price = 0; - const lineItems = this.wiperItems; + const lineItems = this.availableLineItems.filter((item) => { + return item.partType.includes(" WIPER"); + }); lineItems?.forEach((item) => { price += item.laborAmount + item.sellingPrice + item.kitPrice; }); diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 67558c323..7107e64e8 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -54,36 +54,28 @@ + diff --git a/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue new file mode 100644 index 000000000..7f4fe2801 --- /dev/null +++ b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-list-button/calendar-modal-list-button.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js new file mode 100644 index 000000000..d28c2442f --- /dev/null +++ b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js @@ -0,0 +1,53 @@ +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import calendarModalQuestion from "@/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue"; + +describe("calendar-modal-question.vue", () => { + test("When selected calendar option is changed, a correctly selectedCalendarOption should be emitted", async () => { + //Arrange + const url = "iCal url"; + const name = "iCal"; + const icon = "iCal.svg"; + const expectedEmit = [ + [ + { + url: url, + name: name, + icon: icon, + }, + ], + ]; + + const { wrapper } = setupMocks({ + customMountOptions: { + propsData: { + calendarOptionsData: [ + { + url: url, + name: name, + icon: icon, + }, + ], + }, + }, + }); + + wrapper.vm.$refs.calendarOptions.closeModal = jest.fn(); + wrapper.vm.selectedCalendarOption = name; + + //Act + wrapper.vm.setSelectedCalendarOption(); + + //Assert + expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit); + }); +}); + +function setupMocks({ customMountOptions }) { + const mountOptions = getMountOptions({ + ...customMountOptions, + }); + + const wrapper = shallowMount(calendarModalQuestion, mountOptions); + return { wrapper }; +} diff --git a/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue new file mode 100644 index 000000000..dacda07f2 --- /dev/null +++ b/src/layouts/add-to-calendar/calendar-modal-question/calendar-modal-question.vue @@ -0,0 +1,179 @@ + + + diff --git a/src/layouts/confirmation/confirmation.spec.js b/src/layouts/confirmation/confirmation.spec.js index c60b19c26..4bef5f8a3 100644 --- a/src/layouts/confirmation/confirmation.spec.js +++ b/src/layouts/confirmation/confirmation.spec.js @@ -1,5 +1,7 @@ // Components import confirmation from "@/layouts/confirmation/confirmation.vue"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; +import { applicationConfig } from "@/constants/application-config.js"; // Supporting Files import { shallowMount } from "@vue/test-utils"; @@ -12,14 +14,14 @@ import router from "@/router"; jest.mock("@/helpers/layout-helper.js", () => ({ settleAllPromises: jest.fn(), })); - +var wordingText = "wording Text {custom:ADDRESS}"; beforeEach(() => { jest.restoreAllMocks(); jest.clearAllMocks(); store.getters = { order: { schedule: { - date: "2019-01-01", + date: "2023-01-01", startTime: "09:00", endTime: "10:00", routeCode: "000", @@ -33,9 +35,9 @@ beforeEach(() => { supportingItems: [], }, serviceLocation: { - address: "", - address2: null, - city: "", + address: "test", + address2: "123", + city: "test", state: "AZ", appointmentType: "Inshop", zipCode: "12345", @@ -112,8 +114,188 @@ describe("confirmation.vue", () => { expect(window.location.assign).toHaveBeenCalled(); }); }); +describe("computed properties...", () => { + test("ScheduleDateFormatted should return date in expected format.", () => { + //Arrange + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ScheduleDateFormatted; + + // Assert + expect(testValue).toEqual("Sunday, January 1"); + }); + test("ScheduleTimeFormatted should return mobile time in expected format.", () => { + //Arrange + store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE; + store.getters.order.schedule.startTime = "09:00"; + store.getters.order.schedule.endTime = "11:00"; + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ScheduleTimeFormatted; + + // Assert + expect(testValue).toEqual("Between 9 AM - 11 AM"); + }); + test("ScheduleTimeFormatted should return DROP_OFF time in expected format.", () => { + //Arrange + store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF; + store.getters.order.schedule.startTime = "09:00"; + store.getters.order.schedule.endTime = "11:00"; + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ScheduleTimeFormatted; + + // Assert + expect(testValue).toEqual("Drop off before 9:30 AM"); + }); + test("ScheduleTimeFormatted should return Inshop time in expected format.", () => { + //Arrange + store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP; + store.getters.order.schedule.startTime = "09:00"; + store.getters.order.schedule.endTime = "11:00"; + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ScheduleTimeFormatted; + + // Assert + expect(testValue).toEqual("at 9:00 AM"); + }); + test("AppointmentWordingText should return mobile text in expected format.", () => { + //Arrange + store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE; + + const { wrapper } = setupMocks({}); + // Act + const testValue = wrapper.vm.AppointmentWordingText; + + // Assert + expect(testValue).toEqual("wording Text 123, test,
test, AZ 12345"); + }); + test("AppointmentWordingText should return DROP_OFF text in expected format.", () => { + //Arrange + store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF; + + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.AppointmentWordingText; + + // Assert + expect(testValue).toEqual("wording Text test1,
test, AZ 12345"); + }); + test("AppointmentWordingText should return IN_SHOP text in expected format.", () => { + //Arrange + store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP; + + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.AppointmentWordingText; + + // Assert + expect(testValue).toEqual("wording Text test1,
test, AZ 12345"); + }); + test("ConfirmationEmailText should return text in expected format.", () => { + //Arrange + wordingText = "{custom:MY_ACCOUNT_URL}"; + const { wrapper } = setupMocks({}); + // Act + const testValue = wrapper.vm.ConfirmationEmailText; + + // Assert + expect(testValue).toEqual(applicationConfig.MY_ACCOUNT); + }); + test("ScheduleDate should return date in expected format.", () => { + //Arrange + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ScheduleDate; + + // Assert + expect(testValue).toEqual("2023-01-01"); + }); + test("ScheduleStartTime should return time in expected format.", () => { + //Arrange + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ScheduleStartTime; + + // Assert + expect(testValue).toEqual("09:00"); + }); + test("ScheduleEndTime should return time in expected format.", () => { + //Arrange + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ScheduleEndTime; + + // Assert + expect(testValue).toEqual("10:00"); + }); + test("InShopWordingText should return text in expected format.", () => { + //Arrange + wordingText = "InShopWordingText format"; + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.InShopWordingText; + + // Assert + expect(testValue).toEqual(wordingText); + }); + test("MobileWordingText should return text in expected format.", () => { + //Arrange + wordingText = "MobileWordingText format"; + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.MobileWordingText; + + // Assert + expect(testValue).toEqual(wordingText); + }); + test("DropOffWordingText should return text in expected format.", () => { + //Arrange + wordingText = "DropOffWordingText format"; + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.DropOffWordingText; + + // Assert + expect(testValue).toEqual(wordingText); + }); + test("ServiceLocationFullAddress should return text in expected format.", () => { + //Arrange + + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ServiceLocationFullAddress; + + // Assert + expect(testValue).toEqual("123, test,
test, AZ 12345"); + }); + test("ProviderFullAddress should return text in expected format.", () => { + //Arrange + + const { wrapper } = setupMocks({}); + + // Act + const testValue = wrapper.vm.ProviderFullAddress; + + // Assert + expect(testValue).toEqual("test1,
test, AZ 12345"); + }); +}); -const wordingText = "wording Text"; function setupMocks({ customMountOptions }) { const mountOptions = getMountOptions({ ...customMountOptions, diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index 3691cf496..cb7ca43c1 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -10,6 +10,10 @@ +
+
{{ this.ScheduleDateFormatted }}

{{ this.ScheduleTimeFormatted }}

+ -
-
- -
-
@@ -58,6 +49,7 @@ import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner"; import navbar from "@/fmg-components/nav-bar/nav-bar"; +import addToCalendar from "@/layouts/add-to-calendar/add-to-calendar"; //Supporting files import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { AppointmentTypeStrings } from "@/constants/schedule-constants"; @@ -243,6 +235,7 @@ export default { Form, navbar, vehicleBanner, + addToCalendar, }, }; diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index eac00fe15..ecd310c5c 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -17,8 +17,8 @@ @@ -34,10 +34,50 @@ + :availableLineItems="availableLineItems" + vapsTilesCmsName="VapsProductTiles" + v-on="{ 'buttonEvent.openModal': openModal }" /> +
+ + + + +
+
+ +
+
+ +
+ + @@ -64,6 +104,7 @@ import paymentMethodQuestion from "@/layouts/payment-method/payment-method-quest import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import cart from "@/fmg-components/cart/cart"; import addVapsModalButtons from "@/fmg-components/add-vaps-modal-buttons/add-vaps-modal-buttons"; +import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal"; import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"; import alert from "@/ux-components/alert/alert"; @@ -88,11 +129,14 @@ export default { null, "payment-method" ); + const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging( storeActions.GET_RAIN_DEFENSE, null, "payment-method" ); + console.log("rainDefensePromise: ", rainDefensePromise); + const supportingItemsPromise = baseMixin.methods.dispatchStoreActionWithLogging( storeActions.GET_SUPPORTING_ITEMS, null, @@ -119,16 +163,17 @@ export default { ]; const resultMap = await settleAllPromises(promiseResultMap); - const clonedGlassParts = store.getters.order.lineItems.glassParts - ? JSON.parse(JSON.stringify(store.getters.order.lineItems.glassParts)) - : []; + + const glassParts = (await store.getters.order.lineItems.glassParts) ?? []; + const availableLineItems = [ resultMap.rainDefense, ...resultMap.supportingItems, ...resultMap.wipers, - ...clonedGlassParts, + ...glassParts, ]; - const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging( + + const pricedAvailableLineItems = await baseMixin.methods.dispatchStoreActionWithLogging( storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, { availableLineItems: availableLineItems, @@ -136,30 +181,25 @@ export default { "payment-method", false ); - const storeLineItems = store.getters.lineItems; - const cartItems = [ - ...clonedGlassParts, - ...storeLineItems.supportingItems, - ...storeLineItems.vaps, - ]; + const lineItems = store.getters.lineItems; + + const cartItems = [...glassParts, ...lineItems.supportingItems, ...lineItems.vaps]; // TOBEREMOVED BY AJC IN CSR-1384 // Call the "next" function to complete the transition to this page. next((vm) => { + vm.cartItems = cartItems; // TOBEREMOVED BY AJC IN CSR-1384 + vm.setCmsContent(resultMap.cmsContent); - vm.availableLineItems = pricingResults; - vm.availableWipers = resultMap.wipers; - vm.availableRainDefense = resultMap.rainDefense; - vm.cartItems = cartItems; + vm.availableLineItems = pricedAvailableLineItems; + vm.lineItems = lineItems; }); }, data() { return { - cartItems: [], - availableWipers: null, - availableRainDefense: null, - availableLineItems: null, - supportingItems: null, - pricedGlassParts: null, + cartItems: [], // TOBEREMOVED BY AJC IN CSR-1384 + + lineItems: [], + availableLineItems: [], displayPaypalAlert: this.getPaypalAlert(), paymentMethodInternalModel: null, }; @@ -217,6 +257,9 @@ export default { packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs ); }, + openModal(modalName) { + this.$refs[modalName].openModal(); + }, getPaypalAlert() { return store.getters.order.payment.piaErrorCode ? true : false; }, @@ -231,7 +274,7 @@ export default { const matchedIndices = []; this.cartItems.forEach((cartItem, i) => { if (cartItem.partType === item.partType) { - console.log("adding this to matchedIndices: ", i); + // console.log("adding this to matchedIndices: ", i); matchedIndices.push(i); } }); @@ -284,12 +327,9 @@ export default { }, computed: { damageInfo() { + // console.log("this.$store.getters.damage ", this.$store.getters.damage) return this.$store.getters.damage; }, - storeLineItems() { - // console.log("storeLineItems, aka lineItems from store: ", this.$store.getters.lineItems) - return this.$store.getters.lineItems; - }, customCtaCopy() { switch (this.paymentMethod) { case paymentMethods.CREDIT_CARD: @@ -332,6 +372,7 @@ export default { cart, alert, addVapsModalButtons, + contentGroupModal, paymentMethodQuestion, }, }; diff --git a/src/layouts/schedule/schedule.spec.js1 b/src/layouts/schedule/schedule.spec.js1 deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/store/index.js b/src/store/index.js index 2c2eb8492..bcafa6509 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -14,6 +14,7 @@ import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper import { deepEqual } from "@/helpers/object-helper"; import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants"; import { getQuerystringParameter } from "@/helpers/querystring-helper"; +import { deepClone } from "@/helpers/object-helper"; import { queryStrings } from "@/constants/query-strings"; import { partTypeStrings } from "@/constants/part-type-strings"; import { @@ -2067,9 +2068,9 @@ export const actions = { context.commit(storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA, response.data.serverData); - availableLineItems = addPricesToLineItems(availableLineItems, response.data.lineItems); + const pricedLineItems = addPricesToLineItems(availableLineItems, response.data.lineItems); - return availableLineItems; + return pricedLineItems; }, // Misc order actions @@ -2277,7 +2278,8 @@ function convertGlassPieceNamingFromApi(glassArray) { } function addPricesToLineItems(lineItems, pricingLineItems) { - lineItems.forEach((lineItem) => { + const pricedLineItems = deepClone(lineItems); + pricedLineItems.forEach((lineItem) => { let lineItemIndex = pricingLineItems.findIndex( (pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber ); @@ -2289,7 +2291,7 @@ function addPricesToLineItems(lineItems, pricingLineItems) { lineItem.sellingPrice = pricedLineItem.sellingPrice; lineItem.kitPrice = pricedLineItem.kitPrice; }); - return lineItems; + return pricedLineItems; } function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {