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/constants/experiments.js b/src/constants/experiments.js index f89aad963..b702b11bb 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -6,6 +6,8 @@ const experimentSettings = { GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index", SUPPRESS_VIN_CAPTURE: "SuppressVinCapture", DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators", + PIA_EXPERIENCE: "PIA Experience", + SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA", }; const experimentTriggers = { diff --git a/src/constants/payment-method-constants.js b/src/constants/payment-method-constants.js new file mode 100644 index 000000000..807e44c0e --- /dev/null +++ b/src/constants/payment-method-constants.js @@ -0,0 +1,13 @@ +export const paymentMethods = { + NONE: null, + LATER: "Later", + CREDIT_CARD: "CreditCard", + PAYPAL: "Paypal", + AFTERPAY: "Afterpay", +}; + +export const paymentTimes = { + NONE: null, + ADVANCE: "Advance", + LATER: "Later", +}; diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 052e19cc1..b5ef43d3c 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -90,6 +90,7 @@ const storeActions = { SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes", SAVE_WORK_ORDER_FLAG: "saveWorkOrderFlag", SAVE_PIA_ERROR_CODE: "savePiaErrorCode", + SAVE_PIA_WORK_ORDER: "savePiaWorkOrder", }; export { storeActions }; diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index e02b3bb0e..af3a0a5ad 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -1,6 +1,7 @@ const storeMutations = { UPDATE_WORK_ORDER_FLAG: "updateWorkOrderFlag", UPDATE_PIA_ERROR_CODE: "updatePiaErrorCode", + UPDATE_PIA_WORK_ORDER: "updatePiaWorkOrder", // VEHICLE MUTATIONS UPDATE_YEAR: "updateYear", diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index 441ed6e0b..cec014712 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -223,6 +223,10 @@ export default { classes = "package-wrapper"; } + if (this.buttonTypeString == "listCard" && this.buttonsInfo.length > 2) { + classes += " two-list-card-width"; + } + return classes; }, buttonsInfo() { @@ -304,18 +308,9 @@ export default { 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..eb77afcb1 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 88ab05111..cb7ca43c1 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -1,65 +1,61 @@ diff --git a/src/layouts/customer-details/customer-details.vue b/src/layouts/customer-details/customer-details.vue index 6e539b133..9568b2a13 100644 --- a/src/layouts/customer-details/customer-details.vue +++ b/src/layouts/customer-details/customer-details.vue @@ -7,7 +7,7 @@
-
+
-
+
diff --git a/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.spec.js b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.spec.js new file mode 100644 index 000000000..8cf6c55bb --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.spec.js @@ -0,0 +1,104 @@ +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +import paymentMethodListButton from "@/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button"; + +const testConstants = { + images: { + A: "imageA", + B: "imageB", + NONE: null, + }, + names: { + A: "nameA", + B: "nameB", + }, + content: { + withInline: "Button text with {custom:inlineImage} inline.", + noInline: "Button text with no inline", + }, +}; + +let cmsContent; + +describe("Payment Method Question", () => { + beforeEach(() => { + cmsContent = {}; + }); + + describe("Side image", () => { + it("Renders side image if image is present and not inline", () => { + // Arrange + const props = generateDefaultProps(); + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const showSideImage = wrapper.vm.shouldDisplaySideImage; + + expect(showSideImage).toBe(true); + }); + + it("Does not render side image if no image is present", () => { + // Arrange + let props = generateDefaultProps(); + + props.buttonImage = testConstants.images.NONE; + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const showSideImage = wrapper.vm.shouldDisplaySideImage; + + expect(showSideImage).toBe(false); + }); + + it("Does not render side image if inline", () => { + // Arrange + let props = generateDefaultProps(); + + props.buttonLabel = testConstants.content.withInline; + props.altText = testConstants.content.withInline; + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const showSideImage = wrapper.vm.shouldDisplaySideImage; + + expect(showSideImage).toBe(false); + }); + }); +}); + +function generateDefaultProps() { + return { + buttonLabel: testConstants.noInline, + altText: testConstants.noInline, + groupName: "payment-method", + value: testConstants.names.A, + buttonImage: testConstants.images.A, + }; +} + +function setupMocks(customMountOptions) { + const mountOptions = getMountOptions(customMountOptions); + + const mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return cmsContent?.[widgetName]?.[cmsFieldName] ?? ""; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(paymentMethodListButton, mountOptions); + wrapper.vm.setCmsContent = jest.fn(); + return { wrapper }; +} diff --git a/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.vue b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.vue new file mode 100644 index 000000000..2763f1af9 --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/layouts/payment-method/payment-method-question/payment-method-question.spec.js b/src/layouts/payment-method/payment-method-question/payment-method-question.spec.js new file mode 100644 index 000000000..d3a486d57 --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-question.spec.js @@ -0,0 +1,130 @@ +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +import paymentMethodQuestion from "@/layouts/payment-method/payment-method-question/payment-method-question"; + +const testConstants = { + cms: { + question: { + text: "Choose a payment option", + }, + answers: { + optionA: { + name: "NameA", + text: "TextA", + subText: "SubTextA", + imageId: "idA", + image: "imageA", + subWidget: "subWidgetA", + }, + optionB: { + name: "NameB", + text: "TextB", + subText: "SubTextB", + imageId: "idB", + image: "imageB", + subWidget: "subWidgetB", + }, + }, + }, +}; + +let cmsContent; + +describe("Payment Method Question", () => { + beforeEach(() => { + cmsContent = { + PaymentMethodWidget: { + QuestionText: testConstants.cms.question, + Answers: [ + { + Name: testConstants.cms.answers.optionA.name, + Text: testConstants.cms.answers.optionA.text, + SubText: testConstants.cms.answers.optionA.subText, + ImageId: testConstants.cms.answers.optionA.imageId, + AnswerImageUrl: testConstants.cms.answers.optionA.image, + SubWidgetName: testConstants.cms.answers.optionA.subWidget, + }, + { + Name: testConstants.cms.answers.optionB.name, + Text: testConstants.cms.answers.optionB.text, + SubText: testConstants.cms.answers.optionB.subText, + ImageId: testConstants.cms.answers.optionB.imageId, + AnswerImageUrl: testConstants.cms.answers.optionB.image, + SubWidgetName: testConstants.cms.answers.optionB.subWidget, + }, + ], + }, + }; + }); + + it("Should map cms content correctly", () => { + // Arrange + const props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const mappedContent = wrapper.vm.paymentMethodAnswerData; + + // Assert + expect(mappedContent).toEqual([ + { + buttonLabel: testConstants.cms.answers.optionA.text, + altText: testConstants.cms.answers.optionA.text, + groupName: "payment-method", + value: testConstants.cms.answers.optionA.name, + buttonImage: testConstants.cms.answers.optionA.image, + }, + { + buttonLabel: testConstants.cms.answers.optionB.text, + altText: testConstants.cms.answers.optionB.text, + groupName: "payment-method", + value: testConstants.cms.answers.optionB.name, + buttonImage: testConstants.cms.answers.optionB.image, + }, + ]); + }); + + it("Handles null cms content gracefully", () => { + // Arrange + cmsContent = {}; + const props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const mappedContent = wrapper.vm.paymentMethodAnswerData; + + // Assert + expect(mappedContent).toEqual([]); + }); +}); + +function generateDefaultProps() { + return { + modelValue: "modelValue", + }; +} + +function setupMocks(customMountOptions) { + const mountOptions = getMountOptions(customMountOptions); + + const mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return cmsContent?.[widgetName]?.[cmsFieldName] ?? ""; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(paymentMethodQuestion, mountOptions); + wrapper.vm.setCmsContent = jest.fn(); + return { wrapper }; +} diff --git a/src/layouts/payment-method/payment-method-question/payment-method-question.vue b/src/layouts/payment-method/payment-method-question/payment-method-question.vue new file mode 100644 index 000000000..473e36114 --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-question.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js index 130a2def4..803f10829 100644 --- a/src/layouts/payment-method/payment-method.spec.js +++ b/src/layouts/payment-method/payment-method.spec.js @@ -5,6 +5,7 @@ import customerDetails from "@/layouts/payment-method/payment-method.vue"; import { shallowMount } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; import store from "@/store"; +import { experimentSettings } from "@/constants/experiments"; // Mock our module for promises. jest.mock("@/helpers/layout-helper.js", () => ({ @@ -15,6 +16,8 @@ jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({ submitWorkOrder: jest.fn(), })); +let piaDisabledFlag = false; + describe("payment-method.vue", () => { describe("navigation", () => { test("if the back button is clicked, navigate back", async () => { @@ -52,19 +55,37 @@ function setupMocks() { }, }; - const wrapper = shallowMount( - customerDetails, - getMountOptions({ - router: { - navigate: jest.fn(), - navigateWithSaving: jest.fn(), - navigateWithoutSaving: jest.fn(), - }, - store: { - getters: store.getters, - }, - }) - ); + const mountOptions = getMountOptions({ + router: { + navigate: jest.fn(), + navigateWithSaving: jest.fn(), + navigateWithoutSaving: jest.fn(), + }, + store: { + getters: store.getters, + }, + }); + + const mockMixin = { + methods: { + getSettingValue: jest.fn((settingName) => { + if (!piaDisabledFlag) { + if (settingName === experimentSettings.PIA_EXPERIENCE) { + return "PIA Optional"; + } + if (settingName === experimentSettings.SUBMIT_ORDER_ENABLE_PIA) { + return "true"; + } + } + + return "false"; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(customerDetails, mountOptions); return { wrapper }; } diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index cc688ca27..ecd310c5c 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -8,7 +8,7 @@
-
+
@@ -17,8 +17,8 @@ @@ -34,9 +34,9 @@ + :availableLineItems="availableLineItems" + vapsTilesCmsName="VapsProductTiles" + v-on="{ 'buttonEvent.openModal': openModal }" />
+ + + + { + 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, }; }, methods: { arePagePrerequisitesValid() { - return true; + // Line Items + const packageReqs = !!store.getters.order.lineItems.supportingItems; + + // Service Location + const serviceLocation = store.getters.order.serviceLocation; + const mobileReqs = !!( + serviceLocation.address && + serviceLocation.city && + serviceLocation.state && + serviceLocation.zipCode + ); + + const providerLocation = serviceLocation.provider.address; + const dropOffInshopReqs = !!( + providerLocation.streetAddress && + providerLocation.city && + providerLocation.state && + providerLocation.zipCode + ); + + const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE; + + const serviceLocationReqs = + (isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs); + + // Insurance + const isInsuranceSet = store.getters.order.payment.isInsurance !== null; + + // Schedule + const schedule = store.getters.order.schedule; + const scheduleReqs = !!( + schedule.date && + schedule.startTime && + (!isMobile || schedule.endTime) && + schedule.jobMaxMinutes && + schedule.jobMinMinutes + ); + + // Customer + const customer = store.getters.order.customer; + const customerReqs = !!( + customer.firstName && + customer.lastName && + customer.phoneNumber && + customer.emailAddress + ); + + return ( + packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs + ); + }, + openModal(modalName) { + this.$refs[modalName].openModal(); }, getPaypalAlert() { return store.getters.order.payment.piaErrorCode ? true : false; @@ -209,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); } }); @@ -231,32 +296,29 @@ export default { ); }, async ccdPayment() { - //leaving site, clear any save session promises that we will not be able to resolve when we return - this.$refs.loadingModal.showModal(); - this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false); - await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); - await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, "cc", false); - this.$router.navigateWithoutSaving( - this.navigationScenarios.CLICKED_PAY_NOW, - this.$route - ); + this.piaSetup("cc"); }, async paypal() { - //leaving site, clear any save session promises that we will not be able to resolve when we return - this.$refs.loadingModal.showModal(); - this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false); - await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); - await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, "pp", false); - this.$router.navigateWithoutSaving( - this.navigationScenarios.CLICKED_PAY_NOW, - this.$route - ); + this.piaSetup("pp"); }, async afterpay() { + this.piaSetup("ap"); + }, + async piaSetup(payNowType) { this.$refs.loadingModal.showModal(); - this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false); + + //leaving site, clear any save session promises that we will not be able to resolve when we return await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); - await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, "ap", false); + await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, payNowType, false); + + // make sure pia error codes are reset, and get a pia work order in delete status + this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false); + this.dispatchStoreAction(storeActions.SAVE_PIA_WORK_ORDER, true); + + if (!store.getters.order.workOrderNumber) { + await submitWorkOrder({ pageNameToLog: "payment-method" }); + } + this.$router.navigateWithoutSaving( this.navigationScenarios.CLICKED_PAY_NOW, this.$route @@ -265,11 +327,40 @@ 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: + return "Continue to checkout"; + case paymentMethods.PAYPAL: + return "Continue to Paypal"; + case paymentMethods.AFTERPAY: + return "Continue to Afterpay"; + default: + return null; + } + }, + isPiaDisabled() { + const piaExperience = + this.getSettingValue(experimentSettings.PIA_EXPERIENCE) === "PIA Optional"; + const enablePIA = + this.getSettingValue(experimentSettings.SUBMIT_ORDER_ENABLE_PIA) === "true"; + + return !(piaExperience && enablePIA); + }, + paymentMethod() { + if (this.isPiaDisabled) { + return paymentMethods.LATER; + } + + return this.paymentMethodInternalModel; + }, + }, + watch: { + customCtaCopy(newValue) { + this.$refs.navbar.updateButtonText(newValue); }, }, components: { @@ -281,6 +372,8 @@ export default { cart, alert, addVapsModalButtons, + contentGroupModal, + paymentMethodQuestion, }, }; diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue index 86051265c..8d547cd34 100644 --- a/src/layouts/payment-pia-return/payment-pia-return.vue +++ b/src/layouts/payment-pia-return/payment-pia-return.vue @@ -1,5 +1,7 @@ diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue index 6e94bfaed..914a51df2 100644 --- a/src/layouts/payment/payment.vue +++ b/src/layouts/payment/payment.vue @@ -98,7 +98,7 @@ - + @@ -152,7 +152,7 @@ value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment.aspx?error=ccTimeout" /> - + @@ -186,6 +186,7 @@ import { externalUrls } from "@/router/router-constants/externalUrl-values"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; import { applicationConfig } from "@/constants/application-config"; +import baseMixin from "@/mixins/base-mixin.js"; // Validation import { Form } from "vee-validate"; @@ -213,12 +214,11 @@ export default { phoneNumber: store.getters.order.customer.phoneNumber, ctu: store.getters.order.serviceLocation.zipCodeCtu, referralCorrelationId: store.getters.order.referralCorrelationId, - // TODO: replace these - workOrderNumber: Math.floor(Math.random() * 90000) + 10000, - invoiceNumber: Math.floor(Math.random() * 90000) + 10000, - totalAmount: "321.00", // TODO: get from cart? - displayAmount: "$321.00", // TODO: get from cart? - lineItems: "Labor|189.99|1||Repair supplies|7.99|1", + workOrderNumber: this.getWOrkOrderNumber(), + invoiceNumber: this.getInvoiceNumber(), + totalAmount: this.getAmountDue(), + displayAmount: this.getDisplayAmountDue(), + piaLineItems: this.getPiaLineItems(), sgHeaderline1: this.getHeaderLine1(), sgHeaderline2: this.getHeaderLine2(), paypalPayment: this.isPaypal(), @@ -249,6 +249,25 @@ export default { // TODO return true; }, + getWOrkOrderNumber() { + if (store.getters.order.workOrderNumber) { + var items = store.getters.order.workOrderNumber.split("-"); + return items[1]; + } + + // no work order, set pia error and return + this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, "NO WORK ORDER", false); + this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); + }, + getInvoiceNumber() { + if (store.getters.order.workOrderNumber) { + return store.getters.order.workOrderNumber.replace("-", ""); + } + + // no work order, set pia error and return + this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, "NO WORK ORDER", false); + this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); + }, getAddress1() { return store.getters.order.serviceLocation.address ? store.getters.order.serviceLocation.address @@ -289,6 +308,27 @@ export default { } return ""; }, + getPiaLineItems() { + const itemsClone = { ...store.getters.order.lineItems }; + delete itemsClone.serverData; + + var itemsForPia = ""; + for (var propertyName in itemsClone) { + for (var item in itemsClone[propertyName]) { + var amount = + +itemsClone[propertyName][item].laborAmount + + +itemsClone[propertyName][item].sellingPrice; + itemsForPia += itemsClone[propertyName][item].partType + "|" + amount + "|1|"; + } + } + return itemsForPia; + }, + getAmountDue() { + return baseMixin.methods.getAmountDue(store.getters.order.lineItems); + }, + getDisplayAmountDue() { + return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems); + }, isPaypal() { if (this.getPaymentType() == "pp") { return true; diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index bd78f06d6..30a37ff9e 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -8,7 +8,7 @@
-
+
diff --git a/src/layouts/review/review.vue b/src/layouts/review/review.vue index 20daf921f..a5f6eca7c 100644 --- a/src/layouts/review/review.vue +++ b/src/layouts/review/review.vue @@ -8,7 +8,7 @@
-
+
@@ -177,8 +177,7 @@ export default { // damageReqs handles checking for damage, even though it is also required for this section. const packageReqs = !!( (damage.isRepair || lineItems.glassParts) && - lineItems.supportingItems && - lineItems.vaps + lineItems.supportingItems ); // Service Location @@ -208,7 +207,7 @@ export default { const scheduleReqs = !!( schedule.date && schedule.startTime && - schedule.endTime && + (!isMobile || schedule.endTime) && schedule.jobMaxMinutes && schedule.jobMinMinutes ); 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/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 4adf9eb50..fccef82e1 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -8,7 +8,7 @@
-
+