diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ba2e359dc..e58f51862 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -78,4 +78,5 @@ stages: appDeployVariables: __VUE_APP_CONSUMER_API_GATEWAY__: $(__VUE_APP_CONSUMER_API_GATEWAY__) __VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__) + __VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__) cfDistributionId: $(cfDistributionId) \ No newline at end of file diff --git a/src/.eslintrc.js b/src/.eslintrc.js new file mode 100644 index 000000000..309a0e4fd --- /dev/null +++ b/src/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + env: { + jest: true + }, +//... +} \ No newline at end of file diff --git a/src/common-components/funnel-footer/funnel-footer.vue b/src/common-components/funnel-footer/funnel-footer.vue index 44afa5364..cb83ecae1 100644 --- a/src/common-components/funnel-footer/funnel-footer.vue +++ b/src/common-components/funnel-footer/funnel-footer.vue @@ -14,7 +14,7 @@ -
+
diff --git a/src/common-components/button-back/button-back.spec.js b/src/common-components/funnel-sub-header/button-back/button-back.spec.js similarity index 100% rename from src/common-components/button-back/button-back.spec.js rename to src/common-components/funnel-sub-header/button-back/button-back.spec.js diff --git a/src/common-components/button-back/button-back.vue b/src/common-components/funnel-sub-header/button-back/button-back.vue similarity index 100% rename from src/common-components/button-back/button-back.vue rename to src/common-components/funnel-sub-header/button-back/button-back.vue diff --git a/src/common-components/funnel-sub-header/funnel-sub-header.vue b/src/common-components/funnel-sub-header/funnel-sub-header.vue index bc290d473..db10885f7 100644 --- a/src/common-components/funnel-sub-header/funnel-sub-header.vue +++ b/src/common-components/funnel-sub-header/funnel-sub-header.vue @@ -23,7 +23,7 @@ diff --git a/src/layouts/button-question-examples/button-question-examples.vue b/src/layouts/button-question-examples/button-question-examples.vue deleted file mode 100644 index 6df20ddf1..000000000 --- a/src/layouts/button-question-examples/button-question-examples.vue +++ /dev/null @@ -1,138 +0,0 @@ - - - \ No newline at end of file diff --git a/src/layouts/component-test/component-test.vue b/src/layouts/component-test/component-test.vue index e14899d56..526b1e971 100644 --- a/src/layouts/component-test/component-test.vue +++ b/src/layouts/component-test/component-test.vue @@ -1144,7 +1144,7 @@ - - diff --git a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js index a6ee43924..be3ef9485 100644 --- a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js +++ b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js @@ -3,7 +3,7 @@ import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-que import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { nextTick } from "vue"; import store from "@/store"; -jest.mock("@/store", () => { return {}; }, {virtual: true}); +jest.mock("@/store",()=>{return{};},{virtual:true}); describe("replace-options-question.vue", () => { test("Selected damage option is emitted upon selection.", async () => { diff --git a/src/layouts/vehicle-damage/side-door-options/side-door-options.spec.js b/src/layouts/vehicle-damage/side-door-options/side-door-options.spec.js index 9c40a0683..29bbb68b5 100644 --- a/src/layouts/vehicle-damage/side-door-options/side-door-options.spec.js +++ b/src/layouts/vehicle-damage/side-door-options/side-door-options.spec.js @@ -3,7 +3,7 @@ import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-doo import { getMountOptions } from "@/helpers/unit-test-helper.js"; import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question"; import store from "@/store"; -jest.mock("@/store", () => { return {}; }, {virtual: true}); +jest.mock("@/store",()=>{return{};},{virtual:true}); describe("replace-options-question.vue", () => { test("Selected side door option is emitted upon selection.", async () => { @@ -95,7 +95,7 @@ describe("replace-options-question.vue", () => { }) { //Mock store - store.dispatch = jest.fn(() => dataFromStoreApi); + store.dispatch = jest.fn(() => {}); store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} }; const mountOptions = getMountOptions({ store: { diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index 8520e568c..fd3f55a74 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -5,7 +5,7 @@ ref="theForm" v-slot="{ meta }" > -
+
@@ -69,13 +69,15 @@ import alert from "@/ux-components/alert/alert"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; import { storeActions } from "@/constants/store-actions"; -import store from "@/store"; -import baseMixin from "@/mixins/base-mixin"; import { Form, defineRule } from "vee-validate"; import { required } from "@/helpers/validation-rules"; import { errorMessages } from "@/constants/error-messages"; import { damageLocationsCms } from "@/constants/damage-locations-cms.js"; import { damageLocationsSelected } from "@/constants/damage-locations-selected.js"; +import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; + +import store from "@/store"; +import baseMixin from "@/mixins/base-mixin"; // DEFINE VALIDATION RULES defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED)); @@ -148,7 +150,10 @@ export default { }, methods: { arePagePrerequisitesValid() { - return store.getters.vehicle.carId !== null; + if(store.getters.vehicle.carId){ + return true; + } + return false; }, resetDependentState() { store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); @@ -280,6 +285,12 @@ export default { }, navigateForward(partsData){ + // CSR-98 TEMP + if (store.getters.vehicle.year == 2010) { + navigateToHeritageFunnel(); + return; + } + //found problem questions if (partsData.data.partsOrQuestions.some(pq => pq.partQuestions != null && pq.partQuestions.length > 0)){ this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_DAMAGE_WITH_PART_QUESTIONS, this.$route, {}, {}, partsData.data); @@ -382,15 +393,15 @@ export default { hasSplitSingleConflict() { if (!this.selectedWindshieldOptions.selectedWindshieldReplaceOptions) return false; - return this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedSingleWindshield => + return this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedSingleWindshield => { return selectedSingleWindshield.toUpperCase() === damageLocationsSelected.SINGLE.toUpperCase(); - }) && - (this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedDriverWindshield => + }) && + (this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedDriverWindshield => { return selectedDriverWindshield.toUpperCase() === damageLocationsSelected.DRIVER.toUpperCase(); }) || - this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedPassengerWindshield => + this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedPassengerWindshield => { return selectedPassengerWindshield.toUpperCase() === damageLocationsSelected.PASSENGER.toUpperCase(); }) diff --git a/src/layouts/vehicle-make/vehicle-make.vue b/src/layouts/vehicle-make/vehicle-make.vue index 262bee6fd..4c00d7454 100644 --- a/src/layouts/vehicle-make/vehicle-make.vue +++ b/src/layouts/vehicle-make/vehicle-make.vue @@ -84,7 +84,10 @@ export default { ); }, arePagePrerequisitesValid() { - return store.getters.vehicle.year !== null; + if (store.getters.vehicle.year){ + return true; + } + return false; }, resetDependentState() { // Set diff --git a/src/layouts/vehicle-model/vehicle-model.vue b/src/layouts/vehicle-model/vehicle-model.vue index 97d017e34..749a926fe 100644 --- a/src/layouts/vehicle-model/vehicle-model.vue +++ b/src/layouts/vehicle-model/vehicle-model.vue @@ -85,7 +85,10 @@ export default { ); }, arePagePrerequisitesValid() { - return store.getters.vehicle.make !== null; + if(store.getters.vehicle.make){ + return true; + } + return false; }, resetDependentState() { // Set diff --git a/src/layouts/vehicle-parts/vehicle-parts.spec.js b/src/layouts/vehicle-parts/vehicle-parts.spec.js index 74196c524..6613fe6ad 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.spec.js +++ b/src/layouts/vehicle-parts/vehicle-parts.spec.js @@ -193,7 +193,7 @@ describe("vehicle-parts.vue", () => { test("PageData / isRepair populated in Vuex. arePagePrerequisitesValid should be true ", async () => { //Arrange - store.getters.pageData.mockReturnValueOnce(basePartResponse); + store.getters.pageData.mockReturnValue(basePartResponse); store.getters.lineItems = { glassParts: {} } const { wrapper } = setupMocks({ diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue index abc8ee936..c02edfd27 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.vue +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -1,26 +1,42 @@ diff --git a/src/layouts/vehicle-style/vehicle-style.vue b/src/layouts/vehicle-style/vehicle-style.vue index c5aec920c..137dda687 100644 --- a/src/layouts/vehicle-style/vehicle-style.vue +++ b/src/layouts/vehicle-style/vehicle-style.vue @@ -96,7 +96,10 @@ export default { ); }, arePagePrerequisitesValid() { - return store.getters.vehicle.model !== null; + if(store.getters.vehicle.model){ + return true; + } + return false; }, resetDependentState() { // Invokes diff --git a/src/common-components/vin-information/vin-information.spec.js b/src/layouts/vin-lookup/vin-information/vin-information.spec.js similarity index 82% rename from src/common-components/vin-information/vin-information.spec.js rename to src/layouts/vin-lookup/vin-information/vin-information.spec.js index 4938d54fe..b80daf9ef 100644 --- a/src/common-components/vin-information/vin-information.spec.js +++ b/src/layouts/vin-lookup/vin-information/vin-information.spec.js @@ -1,5 +1,5 @@ import { shallowMount } from "@vue/test-utils"; -import vinInformation from "@/common-components/vin-information/vin-information"; +import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information"; describe("vinInformation.vue", () => { it("Should return input class active if isActive is true", async () => { diff --git a/src/common-components/vin-information/vin-information.vue b/src/layouts/vin-lookup/vin-information/vin-information.vue similarity index 100% rename from src/common-components/vin-information/vin-information.vue rename to src/layouts/vin-lookup/vin-information/vin-information.vue diff --git a/src/layouts/vin-lookup/vin-lookup.spec.js b/src/layouts/vin-lookup/vin-lookup.spec.js index 1db5c18a6..3d0843e10 100644 --- a/src/layouts/vin-lookup/vin-lookup.spec.js +++ b/src/layouts/vin-lookup/vin-lookup.spec.js @@ -1,167 +1 @@ -// Components -import vinLookup from "@/layouts/vin-lookup/vin-lookup.vue"; -import vehicleDamage from "@/layouts/vehicle-damage/vehicle-damage.vue"; -import funnelHeader from "@/common-components/funnel-header/funnel-header"; -import funnelFooter from "@/common-components/funnel-footer/funnel-footer"; -import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner"; -import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; -import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options"; -import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question"; -import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options"; -import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question"; - -// Supporting Files -import { settleAllPromises } from "@/helpers/layout-helper.js"; -import baseMixin from "@/mixins/base-mixin"; -import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; -import { mount, flushPromises } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import { nextTick } from "vue"; -import { storeActions } from "@/constants/store-actions"; -import { storeMutations } from "@/constants/store-mutations"; -import store from "@/store"; -import { validate } from "vee-validate"; -import { damageLocationsSelected } from "@/constants/damage-locations-selected.js"; - -// Mock our module for promises. -jest.mock("@/helpers/layout-helper.js", () => ({ - settleAllPromises: jest.fn(), -})); - -// Mock fetchCmsContentForPage -jest.mock("@/helpers/cms-content-helper", () => ({ - fetchCmsContentForPage: jest.fn(), -})); - -// Mock Store -jest.mock("@/store", () => ({ - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - vehicle: { - carId: "C00000000", - image: "test.jpg", - }, - eventBusItem: jest.fn(), - damage: { - glassToReplace: [] - }, - }, -})); - -describe("vin-lookup.vue", () => { - - test("Call resetDependentState", async() => { - const {wrapper} = setupMocks({}); - wrapper.vm.resetDependentState(); - expect(store.dispatch).toBeCalledWith(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); - }); - -}); - -// TEMP -function setupMocks({ - pageHeaderWidgetHeaderText = {}, - mountOptionsMockData = { - router: { - navigate: jest.fn(), - }, - store: { - getters: { - vehicle: {}, - }, - }, - }, -}) { - //Mock api responses - baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn(); - const apiResponses = { - cmsContent: { - FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, - VehicleBannerWidget: { - GenericVehicleImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", - }, - FunnelHeaderWidget: { - LogoImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", - }, - }, - damageOptions: { - driverSideOptions: { - availableReplacementOptions: ["Front", "Back", "Side"], - }, - passengerSideOptions: { - availableReplacementOptions: ["Front", "Back", "Side"], - }, - windshieldOptions: { - availableReplacementOptions: ["Single", "Driver", "Passenger"], - }, - backGlassOptions: { - availableReplacementOptions: ["Front", "Back", "Side"], - }, - }, - }; - - const apiPromise = Promise.resolve(apiResponses); - - settleAllPromises.mockImplementation(() => apiPromise); - fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); - - //Mock damage initialize methods - funnelHeader.methods = { - initializeComponent: jest.fn(), - }; - - vehicleBanner.methods = { - initializeComponent: jest.fn(), - }; - - funnelSubHeader.methods = { - initializeComponent: jest.fn(), - }; - - damageLocationQuestion.methods = { - initializeComponent: jest.fn(), - }; - - sideDoorOptions.methods = { - initializeComponent: jest.fn(), - }; - - windshieldOptions.methods = { - initializeComponent: jest.fn(), - }; - - replaceOptionsQuestion.methods = { - initializeComponent: jest.fn(), - updateSelectedValues: jest.fn(), - }; - - funnelFooter.methods = { - initializeComponent: jest.fn(), - } - - const mountOptions = getMountOptions(mountOptionsMockData); - mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods - - const wrapper = mount(vinLookup, mountOptions); - - const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" }); - funnelHeaderWrapper.vm.initializeComponent = - funnelHeader.methods.initializeComponent; - - const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" }); - vehicleBannerWrapper.vm.initializeComponent = - vehicleBanner.methods.initializeComponent; - - const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader" }); - funnelSubHeaderWrapper.vm.initializeComponent = - funnelSubHeader.methods.initializeComponent; - - const funnelFooterWrapper = wrapper.findComponent({ name: "funnelFooter" }); - funnelFooterWrapper.vm.initializeComponent = - funnelFooter.methods.initializeComponent; - - return { wrapper, apiPromise }; -} +test.todo("some test to be written in the future"); diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 8dbb6cdc9..fc9058abf 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -1,23 +1,17 @@ @@ -27,83 +21,17 @@ import funnelHeader from "@/common-components/funnel-header/funnel-header"; import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner"; import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; import funnelFooter from "@/common-components/funnel-footer/funnel-footer"; - -// Supporting files -import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; -import { settleAllPromises } from "@/helpers/layout-helper"; -import { storeActions } from "@/constants/store-actions"; -import store from "@/store"; -import { storeMutations } from "@/constants/store-mutations"; -import baseMixin from "@/mixins/base-mixin"; -import { Form, defineRule } from "vee-validate"; -import { required } from "@/helpers/validation-rules"; -import { errorMessages } from "@/constants/error-messages"; - -// DEFINE VALIDATION RULES -defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED)); +import textboxQuestion from "@/common-components/textbox-question/textbox-question"; +import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information"; export default { name: "vin-lookup", - async beforeRouteEnter(to, from, next) { - // Call APIs - const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); - const damageOptionsPromise = - baseMixin.methods.dispatchNonBlockingStoreAction( - storeActions.GET_DAMAGE_OPTIONS, - { carId: store.getters.vehicle.carId } - ); - - // Settle promises and get results - const promiseResultMap = [ - { - resultKey: "cmsContent", - promise: cmsContentPromise, - }, - { - resultKey: "damageOptions", - promise: damageOptionsPromise, - }, - ]; - - const resultMap = await settleAllPromises(promiseResultMap); - - // Call the "next" function to complete the transition to this page. - next((vm) => { - vm.$refs.funnelHeader.initializeComponent( - resultMap.cmsContent.FunnelHeaderWidget - ); - vm.$refs.vehicleBanner.initializeComponent( - resultMap.cmsContent.VehicleBannerWidget - ); - vm.$refs.funnelSubHeader.initializeComponent( - resultMap.cmsContent.FunnelSubHeaderWidget - ); - vm.$refs.funnelFooter.initializeComponent( - resultMap.cmsContent.FunnelFooterWidget - ); - }); - }, - methods: { - arePagePrerequisitesValid() { - return true; - }, - resetDependentState() { - store.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); - }, - backButtonAction() { - // route to move backwards - this.$router.navigate( - this.navigationScenarios.CLICKED_BACK, - this.$route - ); - }, - }, components: { funnelHeader, vehicleBanner, funnelSubHeader, + vinInformation, funnelFooter, - Form, }, }; diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index f54909d06..e7f188589 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -2,7 +2,6 @@ import store from "@/store"; import { storeActions } from "@/constants/store-actions.js"; import { storeMutations } from "@/constants/store-mutations.js"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; -import { widgetNames } from "@/constants/widget-names.js"; import { vehicleCategories } from "@/constants/vehicle-categories.js"; export default { @@ -44,9 +43,6 @@ export default { navigationScenarios() { return navigationScenarios; }, - widgetNames() { - return widgetNames; - }, vehicleCategories() { return vehicleCategories; }, diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js index d32163469..48af9a7c0 100644 --- a/src/mixins/base-mixin.spec.js +++ b/src/mixins/base-mixin.spec.js @@ -1,6 +1,5 @@ import baseMixin from "@/mixins/base-mixin"; import { storeActions } from "@/constants/store-actions.js"; -import { widgetNames } from "@/constants/widget-names.js"; import { storeMutations } from "@/constants/store-mutations.js"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; import { vehicleCategories } from "@/constants/vehicle-categories.js"; @@ -67,18 +66,6 @@ describe("baseMixin.js", () => { // Assert expect(navigationScenariosForTest).toEqual(navigationScenarios); }); - - test("computed: widgetNames should be equal to import object", () => { - // Arrange - const mixIn = getMixInInstance({}); - - // Act - let widgetNamesForTest = mixIn.computed.widgetNames(); - - // Assert - expect(widgetNamesForTest).toEqual(widgetNames); - }); - test("computed: vehicleCategories should be equal to import object", () => { // Arrange const mixIn = getMixInInstance({}); @@ -131,7 +118,6 @@ function getMixInInstance({ isDispatchSuccess = true }) { const baseMixIn = baseMixin; baseMixIn.methods.$route = route; baseMixIn.methods.storeActions = storeActions; - baseMixIn.methods.widgetNames = widgetNames; baseMixIn.methods.vehicleCategories = vehicleCategories; store.dispatch = storeDispatch; diff --git a/src/router/index.js b/src/router/index.js index be434e9d8..fc24e70c4 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,19 +1,23 @@ // Supporting files import { createWebHistory, createRouter } from "vue-router"; import { storeActions } from "@/constants/store-actions"; -import baseMixin from "@/mixins/base-mixin"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { routingTable } from "@/router/router-constants/routing-table.js"; import { globalEvents, globalEventTypes } from "@/constants/events"; + +// Heritage integration +import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper"; +import { updateOrCreateFunnelCookie,getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; +import { loadOrderIfPresent, saveOrder } from "@/helpers/heritage-integration/order-helper"; +import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel} from "@/helpers/heritage-integration/navigation-helper"; + +import baseMixin from "@/mixins/base-mixin"; import eventBus from "@/helpers/event-bus/event-bus"; import store from "@/store"; // Components import ComponentTest from "@/layouts/component-test/component-test.vue"; -import AddressPOC from "@/layouts/address-poc/address-poc.vue"; import FormTest from "@/layouts/form-test/form-test.vue"; -import NestedRadio from "@/layouts/nested-radio-poc/nested-radio.vue"; -import buttonQuestionExamples from "@/layouts/button-question-examples/button-question-examples.vue" const routes = [ { @@ -26,26 +30,6 @@ const routes = [ name: "FormTest", component: FormTest, }, - { - path: "/address-poc", // This is a temporary route for testing. - name: "AddressPOC", - component: AddressPOC, - }, - { - path: "/form-test", // This is a temporary route for testing. - name: "FormTest", - component: FormTest, - }, - { - path: "/nested-radio", // This is a temporary route for testing. - name: "NestedRadio", - component: NestedRadio, - }, - { - path: "/button-question-examples", // This is a temporary route for testing. - name: "buttonQuestionExamples", - component: buttonQuestionExamples, - }, { path: "/", name: "root", @@ -55,9 +39,33 @@ const routes = [ await GoToFunnelStartOn404(next); } else { try { + + // If the saved session has timed out, clear the session, execute 404 logic. + if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { + await GoToFunnelStartOn404(next); + } + + // Process funnel cookie. + updateOrCreateFunnelCookie(); + + // On entering the funnel "fresh", read cookie information, decide what to do next. + if (from.redirectedFrom === undefined) { + const loadOrderResponse = await loadOrderIfPresent(); + const pageToRedirectTo = await getPageToRouteExistingOrderTo(to, loadOrderResponse); + + // If getPageToRouteExistingOrderTo determines that the return user needs to + // go back to heritage funnel, send them there and stop our current navigation. + if (pageToRedirectTo === 'heritage') { + await navigateToHeritageFunnel(); + return next(false); + } + + // Assign our fmgPage so it will load normally like the other pages. + to.query.fmgPage = pageToRedirectTo; + } + // If we already have our route, go to it. if (router.hasRoute(to.query.fmgPage)) { - // Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function. let component = router.getRoutes().filter((x) => x.name === to.query.fmgPage)[0].components; @@ -123,10 +131,14 @@ router.navigateAfterSave = (scenario, currentRoute, optionalQuery = {}, optional navigate(scenario, currentRoute, true, optionalQuery, optionalParams, optionalPageData); } +router.navigateToExternalUrl = (url, optionalQuery = {}) => { + navigateToUrl(url, optionalQuery); +} + // PRIVATE FUNCTIONS // Navigate to the next route, depending on the scenario. -function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) { +async function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) { if (!scenario) { console.error("No scenario provided. Please review the routing table."); return; @@ -134,8 +146,9 @@ function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery = {}, // Match our maps up and navigate if we have a destination. const matchingScenarioMap = getNavigationMap(scenario, currentRoute); + const destinationFmgPageValue = matchingScenarioMap.destinationFmgPageValue; - if (matchingScenarioMap.destinationFmgPageValue !== undefined) { + if (destinationFmgPageValue !== undefined) { // We're always pushing the same path, just changing query strings. Make sure our optional query strings get combined with our fmgPage one. // If we need to do invalidation @@ -146,17 +159,22 @@ function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery = {}, } // Append page data to the store for the NEXT page, if any. It will be an empty object if none is provided. - baseMixin.methods.savePageDataToStore(matchingScenarioMap.destinationFmgPageValue, optionalPageData); + baseMixin.methods.savePageDataToStore(destinationFmgPageValue, optionalPageData); + + // if cookie and referralNumber/Date exists + if (getFunnelCookie()?.ReferralNumber && getFunnelCookie()?.ReferralDate) { + await saveOrder(); + } router.push({ name: "root", query: Object.assign(optionalQuery, { - fmgPage: matchingScenarioMap.destinationFmgPageValue, + fmgPage: destinationFmgPageValue, }), - params: optionalParams, + params: optionalParams }); } else if (matchingScenarioMap.destinationUrl !== undefined) { - navigateToUrl(matchingScenarioMap.destinationUrl); + navigateToUrl(matchingScenarioMap.destinationUrl, optionalQuery); } } @@ -177,9 +195,15 @@ function getNavigationMap(scenario, currentRoute) { //---------------------------------------------------------- Private Functions ---------------------------------------------------------- // Navigate to an external url. -function navigateToUrl(url) { +function navigateToUrl(url, optionalQuery = {}) { // possibly show some loading screen in the future here. - window.location.assign(url); + var externalUrl = new URL(url); + + for (const queryKey in optionalQuery) { + externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]); + } + + window.location.assign(externalUrl); } // Get route information by page name. diff --git a/src/router/router-constants/externalUrl-values.js b/src/router/router-constants/externalUrl-values.js new file mode 100644 index 000000000..a5b059480 --- /dev/null +++ b/src/router/router-constants/externalUrl-values.js @@ -0,0 +1,5 @@ +const externalUrls = { + HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL, +}; + +export { externalUrls }; \ No newline at end of file diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js index 9fd1c6311..55efdcaec 100644 --- a/src/router/router-constants/navigation-scenarios.js +++ b/src/router/router-constants/navigation-scenarios.js @@ -4,6 +4,7 @@ const navigationScenarios = { SELECTED_MAKE: "SELECTED_MAKE", SELECTED_STYLE: "SELECTED_STYLE", CLICKED_BACK: "CLICKED_BACK", + CLICKED_FORWARD: "CLICKED_FORWARD", SELECTED_PARTS: "SELECTED_PARTS", SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART", SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS", diff --git a/src/router/router-constants/router-parameter-keys.js b/src/router/router-constants/router-parameter-keys.js deleted file mode 100644 index 0c75eb738..000000000 --- a/src/router/router-constants/router-parameter-keys.js +++ /dev/null @@ -1,3 +0,0 @@ -const routerParameterKeys = { }; - - export { routerParameterKeys }; \ No newline at end of file diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 584796a61..760dac76a 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -1,5 +1,6 @@ import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; +import { applicationConfig } from "@/constants/application-config"; const routingTable = [ { @@ -59,7 +60,7 @@ const routingTable = [ }, { scenario: navigationScenarios.SELECTED_DAMAGE_WITH_SINGLE_PART, - destinationFmgPageValue: fmgPageValues.REVEAL, + destinationFmgPageValue: fmgPageValues.REVEAL }, { scenario: navigationScenarios.SELECTED_DAMAGE_WITH_MULTIPLE_PARTS, @@ -80,6 +81,10 @@ const routingTable = [ }, { scenario: navigationScenarios.SELECTED_PARTS, + destinationFmgPageValue: fmgPageValues.QUOTE, + }, + { + scenario: navigationScenarios.CLICKED_FORWARD, destinationFmgPageValue: fmgPageValues.REVEAL, }, ], diff --git a/src/store/index.js b/src/store/index.js index 53e977114..5f08cf0ba 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,55 +1,64 @@ import { createStore } from "vuex"; import { endpoints } from "@/constants/endpoints.js"; import { storeMutations } from "@/constants/store-mutations"; +import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper"; import createPersistedState from "vuex-persistedstate"; import globalMethods from "@/global-methods"; - - // Export State -export const state = { - order: { - vehicle: { - year: null, - make: null, - model: null, - style: null, - carId: null, - category: null, - imageUrl: null, - imageVifNumber: null, - imageColor: null, - registration: { - licensePlate: null, - address: null, - city: null, - state: null, - zipCode: null, - firstName: null, - lastName: null, +const getDefaultState = () => { + return { + order: { + vehicle: { + year: null, + make: null, + model: null, + style: null, + carId: null, + category: null, + vin: null, + imageUrl: null, + imageVifNumber: null, + imageColor: null, + registration: { + licensePlate: null, + address: null, + city: null, + state: null, + zipCode: null, + firstName: null, + lastName: null, + }, }, + serviceLocation: { + zip: null, + }, + customer: { + emailAddress: null, + }, + damage: { + isRepair: null, + numberOfChips: null, + glassToReplace: null, + }, + lineItems: { + glassParts: null, + otherParts: null + }, + referralNumber: null, + referralDate: null, + referralCorrelationId: null, + parentAccountNumber: null, }, - serviceLocation: { - zip: null, + applicationUser: { + eventBus: [], + pageData: {}, + savedSessionTimeout: getDateForSavedSessionTimeout() }, - customer: { - emailAddress: null, - }, - damage: { - isRepair: null, - numberOfChips: null, - glassToReplace: null, - }, - lineItems:{ - glassParts: {}, - otherParts: {} - } - }, - applicationUser: { - eventBus: [], - pageData: {} - }, -} + } +}; + +export const state = getDefaultState(); // Export Mutations export const mutations = { @@ -81,49 +90,35 @@ export const mutations = { updateVehicleImageColor(state, imageColor) { state.order.vehicle.imageColor = imageColor; }, - updateIsRepair(state, isRepair){ + updateIsRepair(state, isRepair) { state.order.damage.isRepair = isRepair; }, - updateNumberOfChips(state, numberOfChips){ + updateNumberOfChips(state, numberOfChips) { state.order.damage.numberOfChips = numberOfChips; }, - updateGlassToReplace(state, glassToReplace){ + updateGlassToReplace(state, glassToReplace) { state.order.damage.glassToReplace = glassToReplace; }, - updateParts(state, partsData){ + updateParts(state, partsData) { state.order.lineItems.glassParts = partsData; }, - updatePageData(state, pageData){ + updatePageData(state, pageData) { state.applicationUser.pageData[pageData.page] = pageData.data; }, - updateRegistrationAddress(state, registrationAddress){ - state.order.vehicle.registration.address = registrationAddress; + updateReferralCorrelationId(state, referralCorrelationId) { + state.order.referralCorrelationId = referralCorrelationId; }, - updateRegistrationCity(state, registrationCity){ - state.order.vehicle.registration.city = registrationCity; + updateReferralNumber(state, referralNumber) { + state.order.referralNumber = referralNumber; }, - updateRegistrationState(state, registrationState){ - state.order.vehicle.registration.state = registrationState; + updateReferralDate(state, referralDate) { + state.order.referralDate = referralDate; }, - updateRegistrationZipCode(state, registrationZipCode){ - state.order.vehicle.registration.zipCode = registrationZipCode; - }, - updateRegistrationFirstName(state, registrationFirstName){ - state.order.vehicle.registration.firstName = registrationFirstName; - }, - updateRegistrationLastName(state, registrationLastName){ - state.order.vehicle.registration.lastName = registrationLastName; - }, - updateRegistrationLicensePlate(state, registrationLicensePlate){ - state.order.vehicle.registration.licensePlate = registrationLicensePlate; - }, - updateServiceLocationZip(state, serviceLocationZip){ - state.order.vehicle.serviceLocation.zip = serviceLocationZip; - }, - updateCustomerEmailAddress(state, customerEmailAddress){ - state.order.vehicle.customer.emailAddress = customerEmailAddress; + updateParentAcctNumber(state, parentAcctNumber) { + state.order.parentAccountNumber = parentAcctNumber; }, + // EVENT BUS MUTATIONS addEventToBus(state, event) { state.applicationUser.eventBus.push(event); @@ -168,6 +163,32 @@ export const mutations = { resetPartsState(state) { state.order.lineItems.glassParts = null; state.order.lineItems.otherParts = null; + }, + resetState(state) { + Object.assign(state, getDefaultState()); + }, + + // Misc Mutations + setLoadOrderInformation(state, orderInformation) { + state.order.referralNumber = orderInformation.referralNumber; + state.order.referralDate = orderInformation.referralDate; + state.order.referralCorrelationId = orderInformation.referralCorrelationId; + state.order.vehicle = { + year: orderInformation.vehicle?.year, + make: orderInformation.vehicle?.make, + model: orderInformation.vehicle?.model, + style: orderInformation.vehicle?.style, + carId: orderInformation.vehicle?.carId, + category: orderInformation.vehicle?.category, + imageUrl: orderInformation.vehicle?.imageUrl, + imageVifNumber: orderInformation.vehicle?.imageVifNumber, + imageColor: orderInformation.vehicle?.imageVifColor + }; + state.order.damage.glassToReplace = orderInformation.glassToReplace; + state.order.damage.isRepair = orderInformation.isRepair; + state.order.damage.numberOfChips = orderInformation.numberOfChips; + state.order.lineItems.glassParts = orderInformation.parts; + state.order.parentAccountNumber = orderInformation.parentAccountNumber; } } @@ -187,7 +208,9 @@ export const getters = { lineItems: (state) => state.order.lineItems, pageData: (state) => (page) => { return state.applicationUser.pageData[page]; - } + }, + applicationUser: (state) => state.applicationUser, + order: (state) => state.order, } // Export Actions @@ -278,6 +301,9 @@ export const actions = { resetPartsAndDependencies(context) { context.commit(storeMutations.RESET_PARTS_STATE); }, + resetState(context) { + context.commit(storeMutations.RESET_STATE); + }, // Content API Actions getRouteInfo(context, { pageName }) { @@ -310,6 +336,13 @@ export const actions = { }); }, + // Misc Actions + setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId }) { + context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber); + context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate); + context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId); + }, + // Parts API Actions getPartsOrQuestions(context, { carId, glassArray, zipCode, vin = '' }) { return globalMethods.callHttpClient({ @@ -322,6 +355,45 @@ export const actions = { vin: vin }, }); + }, + + // Order API Actions + saveOrder(context) { + const vehicle = context.getters.vehicle; + const damage = context.getters.damage; + + return globalMethods.callHttpClient({ + method: endpoints.SaveOrder.method, + endpoint: endpoints.SaveOrder.url, + payload: { + vehicle: { + carId: vehicle.carId, + year: vehicle.year, + make: vehicle.make, + model: vehicle.model, + style: vehicle.style, + }, + numberOfChips: damage.numberOfChips, + glassToReplace: damage.glassToReplace, + referralNumber: context.state.order.referralNumber, + referralDate: context.state.order.referralDate + }, + }); + }, + + loadOrder(context, { referralNumber, referralDate, referralCorrelationId }) { + return globalMethods.callHttpClient({ + method: endpoints.LoadOrder.method, + endpoint: endpoints.LoadOrder.url, + payload: { + referralNumber: referralNumber, + referralDate: referralDate, + referralCorrelationId: referralCorrelationId + }, + }).then((response) => { + context.commit(storeMutations.SET_LOAD_FUNNEL_SESSION_INFO, response.data); + return response; + }); } } @@ -337,3 +409,5 @@ export default createStore({ getters, actions, }); + +// Private Functions diff --git a/src/store/store.spec.js b/src/store/store.spec.js index a3f8a5cb5..c8fe2476b 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -200,6 +200,38 @@ describe("Mutations", () => { expect(storeState.applicationUser.pageData['vehicle-year']).toEqual({}); }); + it("setLoadOrderInformation, should set order information in state", () => { + // Arrange + const storeState = state; + + // Act + mutations.setLoadOrderInformation(storeState, { + referralNumber: 123, + referralDate: new Date().toUTCString(), + referralCorrelationId: "xxx-xxx-xxx", + vehicle: { + year: "2019", + make: "Acura", + model: "ILX", + style: "4 DOOR SEDAN", + carId: "C0000001", + category: "CAR" + }, + glassToReplace: ["Windshield"], + isRepair: false, + numberOfChips: 0, + parts: [], + parentAccountNumber: "123456789", + }); + + // Assert + expect(storeState.order.referralNumber).toEqual(123); + expect(storeState.order.referralCorrelationId).toEqual("xxx-xxx-xxx"); + expect(storeState.order.vehicle.year).toEqual("2019"); + expect(storeState.order.vehicle.make).toEqual("Acura"); + expect(storeState.order.vehicle.model).toEqual("ILX"); + }); + }); describe("Actions", () => { @@ -466,6 +498,66 @@ describe("Actions", () => { expect(response.data).toEqual({ imageUrl: "https://test.com" }); }); + it("saveOrder action, returns order information", async () => { + // Arrange + const context = state; + + context.getters = { + vehicle: {}, + damage: {}, + }; + context.state = { + order: {} + }; + + globalMethods.callHttpClient.mockImplementation(() => { + return Promise.resolve({ data: { referralNumber: 123 } }); + }); + + // Act + const response = await actions.saveOrder(context); + + // Assert + expect(response.data).toEqual({ referralNumber: 123 }); + }); + + + it("loadOrder action, returns order information, calls mutation", async () => { + // Arrange + const context = state; + + globalMethods.callHttpClient.mockImplementation(() => { + return Promise.resolve({ data: { referralNumber: 123 } }); + }); + + const commit = jest.fn(); + + context.commit = commit; + + // Act + const response = await actions.loadOrder(context, {referralNumber: "123", referralDate: new Date().toUTCString(), referralCorrelationId: "xxx-xxx-xxx"}); + + // Assert + expect(response.data).toEqual({ referralNumber: 123 }); + expect(commit).toBeCalledWith(storeMutations.SET_LOAD_FUNNEL_SESSION_INFO, {"referralNumber": 123}); + }); + + it("setReferralInformation, should call commit three times", () => { + // Arrange + const context = state; + const commit = jest.fn(); + + context.commit = commit; + + // Act + actions.setReferralInformation(context, {referralNumber: "123", referralDate: new Date().toUTCString(), referralCorrelationId: "xxx-xxx-xxx"}); + + // Assert + expect(commit).toBeCalledWith(storeMutations.UPDATE_REFERRAL_NUMBER, "123"); + expect(commit).toBeCalledWith(storeMutations.UPDATE_REFERRAL_DATE, new Date().toUTCString()); + expect(commit).toBeCalledWith(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, "xxx-xxx-xxx"); + }); + }); describe("Getters", () => { diff --git a/src/styles/common-error-styles.scss b/src/styles/common-error-styles.scss index d4dc8244a..fe1637e43 100644 --- a/src/styles/common-error-styles.scss +++ b/src/styles/common-error-styles.scss @@ -3,14 +3,19 @@ &.list-card { border: 1px solid $red; color: $red; - label { - box-shadow: 0 0 1px $red; - border-radius: .5rem; - } input[type=checkbox]:focus + label, input[type=radio]:focus + label { box-shadow: 0 0 0 2.5px $red; } + input[type=checkbox]:focus + label:hover, + input[type=radio]:focus + label { + box-shadow: 0 0 0 1px $red; + } + &:hover { + box-shadow: 0px 0px 0px 4px $red-200; + border-radius: 10px !important; + border: 1px solid $red; + } } input[type=checkbox]:checked + label, @@ -21,10 +26,13 @@ color: $red; label { border: 1px solid $red; + &:hover { + box-shadow: 0px 0px 0px 4px $red-200; + } } input[type=checkbox]:focus + label, input[type=radio]:focus + label { - box-shadow: 0 0 0 2.5px $red; + box-shadow: 0 0 1px $red !important; } } &.ui-radio, @@ -76,9 +84,9 @@ &.btn.btn-primary:hover, &.btn.btn-primary:focus, &.btn.btn-primary:focus-visible { - color: $gray; - background: $gray-200; - box-shadow: none; + color: $gray !important; + background: $gray-200 !important; + box-shadow: none !important; } } diff --git a/src/ux-components/alert/alert.vue b/src/ux-components/alert/alert.vue index 56e97cb4d..5b27f385a 100644 --- a/src/ux-components/alert/alert.vue +++ b/src/ux-components/alert/alert.vue @@ -1,6 +1,6 @@