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/vehicle-banner/vehicle-banner.spec.js b/src/common-components/vehicle-banner/vehicle-banner.spec.js index a6dc13154..210653df2 100644 --- a/src/common-components/vehicle-banner/vehicle-banner.spec.js +++ b/src/common-components/vehicle-banner/vehicle-banner.spec.js @@ -93,7 +93,7 @@ function setupMocks({ categoryValue = "CAR" }) { //Mock store - store.dispatch = jest.fn(() => dataFromStoreApi); + store.dispatch = jest.fn(() => {}); store.getters = { vehicle: { category: categoryValue, imageUrl: imageUrlValue } }; const mountOptions = getMountOptions({ store: { diff --git a/src/constants/application-config.js b/src/constants/application-config.js index 9c90aadbc..d427b1d72 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -1,6 +1,9 @@ const applicationConfig = { CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY, GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY, + ANALYTICS_SESSION_TIMEOUT: 30, + SAVED_SESSION_TIMEOUT: 45 }; + export { applicationConfig }; diff --git a/src/constants/cookie-names.js b/src/constants/cookie-names.js new file mode 100644 index 000000000..6b1e3c909 --- /dev/null +++ b/src/constants/cookie-names.js @@ -0,0 +1,6 @@ +const cookieNames = { + CONCEPT_SESSION_INFO: "ConceptSessionInfo", +}; + +export { cookieNames }; + \ No newline at end of file diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 24e5120bd..03c03cf6c 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -47,6 +47,14 @@ const endpoints = { url: "/parts/api/v1/parts/parts-or-questions", method: "POST", }, + SaveOrder: { + url: "/order/api/v1/order/save", + method: "POST", + }, + LoadOrder: { + url: "/order/api/v1/order/load", + method: "POST", + } }; export { endpoints }; diff --git a/src/constants/query-strings.js b/src/constants/query-strings.js new file mode 100644 index 000000000..6655f9378 --- /dev/null +++ b/src/constants/query-strings.js @@ -0,0 +1,7 @@ +const queryStrings = { + FMG_PAGE: 'fmgPage', + START_TYPE: 'start_type' + }; + + export { queryStrings }; + \ No newline at end of file diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index e83b44096..c0e6893a4 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -12,12 +12,17 @@ const storeActions = { LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms", LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin", GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions", + SAVE_ORDER: "saveOrder", + LOAD_ORDER: "loadOrder", + SET_REFERRAL_INFORMATION: "setReferralInformation", + SET_LOAD_ORDER_DATA: "setLoadOrderData", // DEPENDENCY MUTATIONS RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies", RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies", RESET_REGISTRATION_STATE_AND_DEPENDENCIES: "resetRegistrationAndDependencies", RESET_PARTS_STATE_AND_DEPENDENCIES: "resetPartsAndDependencies", + RESET_STATE: "resetState", }; export { storeActions }; diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index c07e88bf2..00ac66d58 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -15,6 +15,12 @@ const storeMutations = { UPDATE_GLASS_TO_REPLACE: "updateGlassToReplace", UPDATE_PARTS: "updateParts", + // ORDER MUTATIONS + UPDATE_REFERRAL_NUMBER: "updateReferralNumber", + UPDATE_REFERRAL_DATE: "updateReferralDate", + UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId", + UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber", + // EVENT BUS MUTATIONS ADD_EVENT_TO_BUS: "addEventToBus", REMOVE_EVENT_FROM_BUS: "removeEventFromBus", @@ -24,9 +30,11 @@ const storeMutations = { RESET_DAMAGE_STATE: "resetDamageState", RESET_REGISTRATION_STATE: "resetRegistrationState", RESET_PARTS_STATE: "resetPartsState", + RESET_STATE: "resetState", // OTHER MUTATIONS UPDATE_PAGE_DATA: "updatePageData", + SET_LOAD_CONCEPT_SESSION_INFO: "setLoadOrderInformation" }; diff --git a/src/constants/widget-names.js b/src/constants/widget-names.js deleted file mode 100644 index d9782f90b..000000000 --- a/src/constants/widget-names.js +++ /dev/null @@ -1,8 +0,0 @@ -const widgetNames = { - FUNNEL_SUB_HEADER_WIDGET: "FunnelSubHeaderWidget", - RADIO_QUESTION_WIDGET: "RadioQuestionWidget", - VEHICLE_BANNER_WIDGET: "VehicleBannerWidget", - FUNNEL_HEADER_WIDGET: "FunnelHeaderWidget", -}; - -export { widgetNames }; diff --git a/src/global-methods.js b/src/global-methods.js index 788d4c2e4..f0675c02d 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -6,6 +6,7 @@ export default { callHttpClient({ method, endpoint, payload }) { return new Promise((resolve, reject) => { const apiGatewayUrl = applicationConfig.CONSUMER_APIGATEWAY_URL; + const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: "FixMyGlass", }); diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index a20446ef8..3bd5319b5 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -1,5 +1,4 @@ import { storeActions } from "@/constants/store-actions.js"; -import { widgetNames } from "@/constants/widget-names.js"; import store from "@/store"; export function fetchCmsContentForPage(fmgPage) { diff --git a/src/helpers/heritage-integration/cookie-helper.js b/src/helpers/heritage-integration/cookie-helper.js new file mode 100644 index 000000000..9de5d5760 --- /dev/null +++ b/src/helpers/heritage-integration/cookie-helper.js @@ -0,0 +1,78 @@ +import { cookieNames } from "@/constants/cookie-names"; +import store from "@/store"; + + +/* + Will update the cookie if present, or create a new one if not. +*/ +export function updateOrCreateConceptCookie() { + console.log("Updating cookie...", { + LastTouched: new Date().toUTCString(), + SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout, + DidHeritageFunnelUpdateLast: false, + ShouldResetState: false, + ReferralNumber: store.getters.order.referralNumber, + ReferralDate: store.getters.order.referralDate, + ReferralCorrelationId: store.getters.order.referralCorrelationId, + }); + + // Create the cookie + document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}={}; path=/`; + + // Set up cookie with all the props. + setConceptCookieProperties({ + LastTouched: new Date().toUTCString(), + SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout, + DidHeritageFunnelUpdateLast: false, + ShouldResetState: false, + ReferralNumber: store.getters.order.referralNumber, + ReferralDate: store.getters.order.referralDate, + ReferralCorrelationId: store.getters.order.referralCorrelationId, + }); + +} + +/* + Gets the current instance of the concept funnel cookie. + Returns null if cookie isn't valid JSON. +*/ +export function getConceptCookie() { + const cookieJson = document.cookie + ?.split("; ") + ?.find(row => row.startsWith(`${cookieNames.CONCEPT_SESSION_INFO}=`)) + ?.split("=")[1]; + + try { + return JSON.parse(cookieJson); + } catch (error) { + return null; + } +} + +/* + Removes concept cookie from browser. +*/ +export function deleteConceptCookie() { + document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}=; Max-Age=0; path=/; domain=${location.hostname}`; +} + +/* + Used to set properties on the concept funnel cookie. + Takes an object with properties to set. Will overwrite existing properties. +*/ +function setConceptCookieProperties(properties) { + if (typeof properties == "object") { + let cookie = getConceptCookie(); + + if (cookie !== null) { + Object.keys(properties).forEach(key => { + cookie[key] = properties[key]; + }); + + const cookieValueJson = JSON.stringify(cookie); + + document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}=${cookieValueJson}; path=/`; + } + } +} + diff --git a/src/helpers/heritage-integration/cookie-helper.spec.js b/src/helpers/heritage-integration/cookie-helper.spec.js new file mode 100644 index 000000000..a20af9300 --- /dev/null +++ b/src/helpers/heritage-integration/cookie-helper.spec.js @@ -0,0 +1,104 @@ +import {getConceptCookie} from "@/helpers/heritage-integration/cookie-helper.js"; +import { removeAllTestCookies, setupCookies } from "@/helpers/unit-test-helper"; + +describe("cookies", () => { + + afterEach(() => { + removeAllTestCookies(); + }) + + describe("getConceptCookie method", () => { + test("gets correct value when cookie is present", () => { + // Arrange + const testReferralNumber = 1566818; + const testReferralDate = "2022-03-15T10:56:24.597"; + const testReferralCorrelationId = "404d2b04-f86e-45c3-b373-127b6217b060"; + const testShouldResetState = false; + const testDidHeritageFunnelUpdateLast = true; + + const testCookieValue = { + ReferralNumber: testReferralNumber, + ReferralDate: testReferralDate, + ReferralCorrelationId: testReferralCorrelationId, + ShouldResetState: testShouldResetState, + DidHeritageFunnelUpdateLast: testDidHeritageFunnelUpdateLast + } + + setupCookies({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + var result = getConceptCookie(); + + // Assert + expect(result).toEqual(testCookieValue); + expect(typeof result).toEqual("object"); + expect(result.ReferralNumber).toEqual(testReferralNumber); + expect(result.ReferralDate).toEqual(testReferralDate); + expect(result.ReferralCorrelationId).toEqual(testReferralCorrelationId); + expect(result.ShouldResetState).toEqual(testShouldResetState); + expect(result.DidHeritageFunnelUpdateLast).toEqual(testDidHeritageFunnelUpdateLast); + }); + + test("returns empty object when value is empty object", () => { + // Arrange + const testCookieValue = {}; + setupCookies({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + var result = getConceptCookie(); + + // Assert + expect(result).toEqual(testCookieValue); + expect(typeof result).toEqual("object"); + expect(Object.keys(result)).toHaveLength(0); + }); + + test("returns null when value is empty string", () => { + // Arrange + const testCookieValue = ""; + setupCookies({ conceptCookieValue: testCookieValue }); + + // Act + var result = getConceptCookie(); + + // Assert + expect(result).toEqual(null); + }); + + test("returns null when concept cookie doesn't exist", () => { + // Arrange + setupCookies({ includeHeritageCookie: false }); + + // Act + var result = getConceptCookie(); + + // Assert + expect(result).toEqual(null); + }); + + test("gets correct cookie value", () => { + // Arrange + const testCookieValue = { test: "testValue" }; + + setupCookies({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + const actualCookieValue = getConceptCookie(); + + // Assert + expect(actualCookieValue).toEqual(testCookieValue); + }); + + test("getCookieValue: Gets null cookie value", () => { + // Arrange + setupCookies({ includeHeritageCookie: false }); + + // Act + const actualCookieValue = getConceptCookie(); + + // Assert + expect(actualCookieValue).toBeNull(); + }); + }) + }) + \ No newline at end of file diff --git a/src/helpers/heritage-integration/navigation-helper.js b/src/helpers/heritage-integration/navigation-helper.js new file mode 100644 index 000000000..20a4315bf --- /dev/null +++ b/src/helpers/heritage-integration/navigation-helper.js @@ -0,0 +1,72 @@ +import { queryStrings } from "@/constants/query-strings"; +import { externalUrls } from "@/router/router-constants/externalUrl-values"; +import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; +import {saveOrder} from "@/helpers/heritage-integration/order-helper.js"; +import store from "@/store"; +import router from "@/router"; + +/* + If the user has visited the concept funnel before this method will determine the bets place to + drop them so they don't start at the beginning again. This method will return 'heritage' if + the user has an existing order and they come back in from the Safelite.com CTA. +*/ +export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) { + + // If the user is coming in via the Safelite.Com CTA + if (toRoute.query[queryStrings.START_TYPE] === 'fmg') { + console.log("Start type is FMG... trying to figure out where to send them..."); + + // If they have an existing order, return 'heritage' for the page name. + if (existingHeritageOrder) { + console.log("Existing heritage order found, returning 'heritage' for page redirect..."); + return 'heritage'; + } + } + + + // If this is a non-CTA navigation, determine where to send the user based on page prerequisites. + // This also works if a user has a 'fmg' start_type query string but no current order. + // That shouldn't happen, but it's possible. + const vehicleMakeComponent = (await lazyLoadComponent('vehicle-make')()).default; + const vehicleModelComponent = (await lazyLoadComponent('vehicle-model')()).default; + const vehicleStyleComponent = (await lazyLoadComponent('vehicle-style')()).default; + const vehicleDamageComponent = (await lazyLoadComponent('vehicle-damage')()).default; + + + if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) { + return "vehicle-year"; + } else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) { + return "vehicle-make"; + } else if (!vehicleStyleComponent.methods.arePagePrerequisitesValid()) { + return "vehicle-model"; + } else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) { + return "vehicle-style"; + } else if (store.getters.damage.isRepair == null || !store.getters.vehicle.carId) { + return 'vehicle-damage' + } else { + if (store.getters.vehicle.vin) { + return "vin-lookup"; + } else { + return "estimate" + } + } + +} + +/* + Used to navigate to the heritage funnel with the correct query string and url. +*/ + +export async function navigateToHeritageFunnel() { + + // Create the order (or save existing order) when navigating to Heritage Funnel. + await saveOrder(); + + router.navigateToExternalUrl( + externalUrls.HERITAGE_FUNNEL, + { + corid: store.getters.order.referralCorrelationId, + src: "concept-funnel", + } + ); +} \ No newline at end of file diff --git a/src/helpers/heritage-integration/navigation-helper.spec.js b/src/helpers/heritage-integration/navigation-helper.spec.js new file mode 100644 index 000000000..fd06920c2 --- /dev/null +++ b/src/helpers/heritage-integration/navigation-helper.spec.js @@ -0,0 +1,370 @@ +import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; +import * as orderHelper from "@/helpers/heritage-integration/order-helper"; +import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; +import { storeActions } from "@/constants/store-actions"; +import { setupMocksForJsFiles, getMockOrderInfo } from "@/helpers/unit-test-helper.js"; +import { externalUrls } from "@/router/router-constants/externalUrl-values"; +import { queryStrings } from "@/constants/query-strings"; + +import store from "@/store"; +import router from "@/router"; + +// Mock Lazy Load +jest.mock("@/router/dynamic-routing/component-loader.js", () => ({ + lazyLoadComponent: jest.fn(), +})); + +describe("getPageToRouteExistingOrderTo", () => { + + test("getPageToRouteExistingOrderTo, should return vehicle-year", async () => { + // Arrange + const toRoute = { + query: {} + }; + + // Mock out the lazy load calls for all components. + lazyLoadComponent + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false) + } + } + } + }); + + // Act + const result = await getPageToRouteExistingOrderTo(toRoute, false); + + //Assert + expect(result).toBe('vehicle-year'); + }); + + test("getPageToRouteExistingOrderTo, should return vehicle-model", async () => { + // Arrange + const toRoute = { + query: {} + }; + + // Mock out the lazy load calls for all components. + lazyLoadComponent + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false) + } + } + } + }); + + // Act + const result = await getPageToRouteExistingOrderTo(toRoute, false); + + //Assert + expect(result).toBe('vehicle-model'); + }); + + test("getPageToRouteExistingOrderTo, should return vehicle-damage", async () => { + // Arrange + const toRoute = { + query: {} + }; + + // Mock out the lazy load calls for all components. + lazyLoadComponent + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }); + + store.getters.damage.isRepair = undefined; + store.getters.vehicle.carId = 'C00000'; + + // Act + const result = await getPageToRouteExistingOrderTo(toRoute, false); + + //Assert + expect(result).toBe('vehicle-damage'); + }); + + test("getPageToRouteExistingOrderTo, should return vin-lookup", async () => { + // Arrange + const toRoute = { + query: {} + }; + + // Mock out the lazy load calls for all components. + lazyLoadComponent + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }); + + store.getters.damage.isRepair = true; + store.getters.vehicle.carId = 'C00000'; + store.getters.vehicle.vin = "1FADP3F26DL212886" + + // Act + const result = await getPageToRouteExistingOrderTo(toRoute, false); + + //Assert + expect(result).toBe('vin-lookup'); + }); + + test("getPageToRouteExistingOrderTo, should return estimate", async () => { + // Arrange + const toRoute = { + query: {} + }; + + // Mock out the lazy load calls for all components. + lazyLoadComponent + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }) + .mockReturnValueOnce(() => { + return { + default: { + methods: { + arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true) + } + } + } + }); + + store.getters.damage.isRepair = true; + store.getters.vehicle.carId = 'C00000'; + store.getters.vehicle.vin = null; + // Act + const result = await getPageToRouteExistingOrderTo(toRoute, false); + + //Assert + expect(result).toBe('estimate'); + }); + + test("getPageToRouteExistingOrderTo, existing order, should return heritage", async () => { + // Arrange + const toRoute = { + query: { + [queryStrings.START_TYPE]: 'fmg' + } + } + + // Act + const result = await getPageToRouteExistingOrderTo(toRoute, true); + + // Assert + expect(result).toBe("heritage"); + }) +}); + +describe("navigateToHeritageFunnel", () => { + test("should save order", async () => { + // Arrange + const mockReferralNumber = 2; + const mockCorrelationId = "55"; + const mockReferralDate = "2022"; + + const mockOrderInfo = getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate); + + const mockData = { + actionList: [{ + actionName: storeActions.SAVE_ORDER, + data: mockOrderInfo, + }] + } + + setupMocksForJsFiles(mockData); + const saveOrderFunction = jest.spyOn(orderHelper, "saveOrder"); + router.navigateToExternalUrl = jest.fn(); + + // Act + await navigateToHeritageFunnel(); + + // Assert + expect(saveOrderFunction).toHaveBeenCalled(); + + // Should alway save before we navigate to heritage + const saveOrderFunctionCallOrder = saveOrderFunction.mock.invocationCallOrder[0]; + const routerNavigateFunctionCallOrder = router.navigateToExternalUrl.mock.invocationCallOrder[0]; + expect(saveOrderFunctionCallOrder).toBeLessThan(routerNavigateFunctionCallOrder); + saveOrderFunction.mockRestore(); + }); + + test("should go to heritage funnel", async () => { + // Arrange + const mockReferralNumber = 2; + const mockCorrelationId = "55"; + const mockReferralDate = "2022"; + + const mockOrderInfo = getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate); + + const mockData = { + actionList: [{ + actionName: storeActions.SAVE_ORDER, + data: mockOrderInfo, + }] + } + + setupMocksForJsFiles(mockData); + + store.getters.order.referralCorrelationId = mockCorrelationId + + router.navigateToExternalUrl = jest.fn(); + + // Act + await navigateToHeritageFunnel(); + + // Assert + expect(router.navigateToExternalUrl).toHaveBeenCalled(); + expect(router.navigateToExternalUrl).toHaveBeenCalledWith(externalUrls.HERITAGE_FUNNEL, + expect.objectContaining({ + corid: mockCorrelationId + }) + ); + }); +}); diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js new file mode 100644 index 000000000..cf8a45d16 --- /dev/null +++ b/src/helpers/heritage-integration/order-helper.js @@ -0,0 +1,74 @@ +import { storeActions } from "@/constants/store-actions.js"; +import { getConceptCookie, updateOrCreateConceptCookie, deleteConceptCookie } from "@/helpers/heritage-integration/cookie-helper.js"; +import baseMixin from "@/mixins/base-mixin"; + +/* + Will call API and hydrate state with data from API if present. If there is no order present + method will return null. If the cookie dictates the state should be reset + it will reset the state and go back to the start of the funnel. + +*/ +export async function loadOrderIfPresent() { + console.log("attempting to load referral...."); + const conceptCookie = getConceptCookie(); + + console.log(conceptCookie) + + // Do nothing if there is no cookie or no correlation id. + if (conceptCookie == null || conceptCookie.ReferralCorrelationId == null) { + console.log("No referral found"); + return null; + } + + // Reset state if cookie says to. + if (conceptCookie.ShouldResetState) { + console.log("Resetting state..."); + baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE); + deleteConceptCookie(); + return null; + } + + console.log("calling load order from loadOrderIfPresent()..."); + + // Load referral if there is a cookie, and it doesn't indicate it needs a state reset. + return (await loadOrder(conceptCookie.ReferralNumber, conceptCookie.ReferralDate, conceptCookie.ReferralCorrelationId)).data; +} + +/* + Will call API to save existing order, or create new one depending where it's called from. + This will also set Referral information in the store after saving, and then + update the cookie. +*/ +export async function saveOrder() { + console.log("saving order..."); + const savedOrderInfo = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SAVE_ORDER); + + // Save the referral information back from the store. + await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SET_REFERRAL_INFORMATION, { + referralNumber: savedOrderInfo.data.referralNumber, + referralCorrelationId: savedOrderInfo.data.referralCorrelationId, + referralDate: savedOrderInfo.data.referralDate, + }, false); + + // Update the cookie with the referral information when saved. + updateOrCreateConceptCookie(); +} + + +// PRIVATE FUNCTIONS // + +/* + Calls API to load order given the referral number, referralDate, and referralCorrelationId + and returns the response. +*/ +async function loadOrder(referralNumber, referralDate, referralCorrelationId) { + console.log("loading order...", referralNumber, referralDate, referralCorrelationId); + const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER, + { + referralNumber: referralNumber.toString(), + referralDate: referralDate, + referralCorrelationId: referralCorrelationId + }, false); + + return response; +} \ No newline at end of file diff --git a/src/helpers/heritage-integration/order-helper.spec.js b/src/helpers/heritage-integration/order-helper.spec.js new file mode 100644 index 000000000..fd0507bbb --- /dev/null +++ b/src/helpers/heritage-integration/order-helper.spec.js @@ -0,0 +1,170 @@ +import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper"; +import { loadOrderIfPresent, saveOrder } from "@/helpers/heritage-integration/order-helper"; +import { cookieNames } from "@/constants/cookie-names"; +import { setupMocksForJsFiles, removeAllTestCookies, getMockOrderInfo, setupCookies } from "@/helpers/unit-test-helper.js"; +import { storeActions } from "@/constants/store-actions"; +import router from "@/router"; + +describe("loadOrderIfPresent", () => { + + afterEach(() => { + removeAllTestCookies(); + }); + + test("ShouldResetState == true => concept cookie is deleted", () => { + // Arrange + const testShouldResetState = true; + + const testCookieValue = { + ShouldResetState: testShouldResetState + } + + document.cookie = `${cookieNames.ORDER_INFO}=${JSON.stringify(testCookieValue)}; path=/; domain=${location.hostname}`; + + // Act + loadOrderIfPresent(); + + // Assert + expect(document.cookie).toBe(""); + }); + + test("ShouldResetState == true => reset store", () => { + // Arrange + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie").mockReturnValueOnce({ ShouldResetState: true, ReferralCorrelationId: "xxx-xxx-xxx" }); + + const mockData = { + actionList: [{ + actionName: storeActions.RESET_STATE + }], + } + + var mocks = setupMocksForJsFiles(mockData); + + // Act + loadOrderIfPresent(); + + // Assert + expect(cookieHelper.getConceptCookie).toHaveBeenCalled(); + expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.RESET_STATE); + }); + + test("Concept cookie is null => store is unchanged", () => { + // Arrange + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie").mockReturnValueOnce(null); + + const mockData = { + actionList: [{ + actionName: storeActions.RESET_STATE + }], + } + + var mocks = setupMocksForJsFiles(mockData); + + // Act + loadOrderIfPresent(); + + // Assert + expect(cookieHelper.getConceptCookie).toHaveBeenCalled(); + expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.RESET_STATE); + }); + + test("Concept cookie valid, should call loadOrder", async () => { + // Arrange + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie") + .mockReturnValueOnce({ ShouldResetState: false, ReferralNumber: 123456, ReferralCorrelationId: "yyy-yyy-yyyy", ReferralDate: new Date()}); + + const mockData = { + actionList: [{ + actionName: storeActions.LOAD_ORDER, + data: { ReferralNumber: 123456, vehicle: { year: 2010 } } + }], + } + + var mocks = setupMocksForJsFiles(mockData); + + // Act + const result = await loadOrderIfPresent(); + + // Assert + expect(cookieHelper.getConceptCookie).toHaveBeenCalled(); + expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.LOAD_ORDER); + expect(result.ReferralNumber).toBe(123456); + expect(result.vehicle.year).toBe(2010); + }); +}); + +describe("saveOrder", () => { + + afterEach(() => { + removeAllTestCookies(); + }); + + test("saveOrder => should set state order values", async () => { + // Arrange + const mockReferralNumber = 2; + const mockCorrelationId = "55"; + const mockReferralDate = "2022"; + + const mockOrderInfo = getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate); + + const mockData = { + actionList: [ + { + actionName: storeActions.SAVE_ORDER, + data: mockOrderInfo, + }, + { + actionName: storeActions.SET_REFERRAL_INFORMATION + } + ] + } + + const mocks = setupMocksForJsFiles(mockData); + + // Act + await saveOrder(); + + // Assert + expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.SAVE_ORDER); + expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.SET_REFERRAL_INFORMATION, { + referralNumber: mockReferralNumber, + referralDate: mockReferralDate, + referralCorrelationId: mockCorrelationId + }, false); + }); + + test("saveOrder => should update DidHeritageFunnelUpdateLast cookie value to false", async () => { + // Arrange + const testReferralNumber = 1566818; + const testReferralDate = "2022-03-15T10:56:24.597"; + const testReferralCorrelationId = "404d2b04-f86e-45c3-b373-127b6217b060"; + + const mockOrderInfo = getMockOrderInfo(testReferralNumber, testReferralCorrelationId, testReferralDate); + + const mockData = { + actionList: [{ + actionName: storeActions.SAVE_ORDER, + data: mockOrderInfo, + }], + router: router + }; + + setupMocksForJsFiles(mockData); + + const testCookieValue = { + ReferralNumber: testReferralNumber, + ReferralDate: testReferralDate, + ReferralCorrelationId: testReferralCorrelationId, + ShouldResetState: false, + DidHeritageFunnelUpdateLast: true + } + + setupCookies({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + await saveOrder(); + + // Assert + expect(cookieHelper.getConceptCookie().DidHeritageFunnelUpdateLast).toEqual(false); + }); +}); diff --git a/src/helpers/heritage-integration/session-helper.js b/src/helpers/heritage-integration/session-helper.js new file mode 100644 index 000000000..49a0d8530 --- /dev/null +++ b/src/helpers/heritage-integration/session-helper.js @@ -0,0 +1,52 @@ +import { applicationConfig } from "@/constants/application-config"; +import { getConceptCookie} from "@/helpers/heritage-integration/cookie-helper.js"; + +/* + Method to determine if our analytics session has timed out or not. + Amount used for timeout is configurable in application-config.js +*/ +export function isAnalyticsSessionStillActive() { + if (getConceptCookie() !== null) { + const lastTouchedValue = getConceptCookie().LastTouched; + const timeoutAmount = applicationConfig.ANALYTICS_SESSION_TIMEOUT; + const isMoreThanHalfHourAgo = ((new Date() - new Date(lastTouchedValue)) / 60000) > timeoutAmount; + + if (isMoreThanHalfHourAgo) { + return false; + } + + return true; + } +} + +/* + Method to determine if the users 'saved' session is still active. + When user state is created, there is a date that is saved into state + this method checks against that date. + + Note: That time for saved session timeout is configurable in application-config.js +*/ +export function isSavedSessionStillActive() { + if (getConceptCookie() !== null) { + const savedSessionTimeStamp = new Date(getConceptCookie().SavedQuoteTimeoutDate); + const isSavedSessionTimedOut = (new Date(new Date().toUTCString()) > savedSessionTimeStamp); + console.log(savedSessionTimeStamp, isSavedSessionTimedOut); + console.log("Saved Session Timed Out? --->", isSavedSessionTimedOut); + + if (isSavedSessionTimedOut) { + return false; + } + + return true; + } +} + +/* +Function to get the date for the saved session timeout. +*/ + +export function getDateForSavedSessionTimeout() { + const currentDate = new Date(new Date().toUTCString()) + currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT) + return currentDate.toUTCString(); +} \ No newline at end of file diff --git a/src/helpers/heritage-integration/session-helper.spec.js b/src/helpers/heritage-integration/session-helper.spec.js new file mode 100644 index 000000000..1c974d508 --- /dev/null +++ b/src/helpers/heritage-integration/session-helper.spec.js @@ -0,0 +1,82 @@ +import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper"; +import { isAnalyticsSessionStillActive, isSavedSessionStillActive, getDateForSavedSessionTimeout} from "@/helpers/heritage-integration/session-helper"; +import { applicationConfig } from "@/constants/application-config"; + +describe("isAnalyticsSessionStillActive", () => { + test("isAnalyticsSessionStillActive, should return true", () => { + // Arrange + const mockDate = new Date(new Date().toUTCString()) + mockDate.setDate(mockDate.getDate() + 1) + + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie") + .mockReturnValue({ LastTouched: mockDate }); + + // Act + const result = isAnalyticsSessionStillActive(); + + // Assert + expect(result).toBe(true); + }); + + test("isAnalyticsSessionStillActive, should return false", () => { + // Arrange + const mockDate = new Date(new Date().toUTCString()) + mockDate.setDate(mockDate.getDate() - 1) + + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie") + .mockReturnValue({ LastTouched: mockDate }); + + // Act + const result = isAnalyticsSessionStillActive(); + + // Assert + expect(result).toBe(false); + }); +}); + +describe("isSavedSessionStillActive", () => { + test("isSavedSessionStillActive, should return true", () => { + // Arrange + const mockDate = new Date(new Date().toUTCString()) + mockDate.setDate(mockDate.getDate() + 1); + + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie") + .mockReturnValue({ SavedQuoteTimeoutDate: mockDate }); + + // Act + const result = isSavedSessionStillActive(); + + // Assert + expect(result).toBe(true); + }); + + test("isSavedSessionStillActive, should return false", () => { + // Arrange + const mockDate = new Date(new Date().toUTCString()) + mockDate.setDate(mockDate.getDate() - 1) + + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie") + .mockReturnValue({ SavedQuoteTimeoutDate: mockDate }); + + // Act + const result = isSavedSessionStillActive(); + + // Assert + expect(result).toBe(false); + + }); +}); + +describe("getDateForSavedSessionTimeout", () => { + test("getDateForSavedSessionTimeout, should equal application config setting", () =>{ + // Arrange + const currentDate = new Date(new Date().toUTCString()) + currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT) + + // Act + const result = getDateForSavedSessionTimeout(); + + // Assert + expect(result).toEqual(currentDate.toUTCString()); + }); +}) \ No newline at end of file diff --git a/src/helpers/unit-test-helper.js b/src/helpers/unit-test-helper.js index 1509a4b16..699c4f8e9 100644 --- a/src/helpers/unit-test-helper.js +++ b/src/helpers/unit-test-helper.js @@ -3,27 +3,17 @@ import { storeMutations } from "@/constants/store-mutations.js"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js"; import { vehicleCategories } from "@/constants/vehicle-categories.js"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; +import { cookieNames } from "@/constants/cookie-names"; import baseMixin from "@/mixins/base-mixin"; +// Common methods export function getMountOptions(mockData) { // Define our mocks to attached to the 'global' object for Vue/Jest. const mocks = {}; //this is mocking if you use the mixin directly(baseMixin.methods.dispatchNonBlockingStoreAction) vs this.dispatchNonBlockingStoreAction - if (mockData.actionList !== undefined) { - baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn(); - baseMixin.methods.dispatchNonBlockingStoreAction.mockImplementation((actionName) => { - let actionFilterResult = mockData.actionList.filter( - (x) => x.actionName == actionName - ); + setupBaseMixinDispatchNonBlockingStoreAction(mockData); - if (actionFilterResult.length > 0 && actionFilterResult.length === 1) { - return Promise.resolve({ - data: actionFilterResult[0].data, - }); - } - }); - } mocks.dispatchNonBlockingStoreAction = jest.fn(); mocks.dispatchNonBlockingStoreAction.mockImplementation((actionName) => { let actionFilterResult = mockData.actionList.filter( @@ -49,10 +39,65 @@ export function getMountOptions(mockData) { mocks.$router = mockData.router; mocks.$route = mockData.route; - const global = { mocks: mocks, }; return { global }; } + +export function setupMocksForJsFiles(mockData = {}) { + setupBaseMixinDispatchNonBlockingStoreAction(mockData); + + return { baseMixin }; +} + +// Heritage integration common methods +export const cookies = { + [cookieNames.CONCEPT_SESSION_INFO]: `{"ReferralNumber":"1566818","ReferralDate":"2022-03-15T10:56:24.597","ReferralCorrelationId":"404d2b04-f86e-45c3-b373-127b6217b060","ShouldResetState":false,"DidHeritageFunnelUpdateLast":true}`, + "UNIQUE_SESSION_ID": "33756020-b58e-4ec7-b8b8-3f1576719c40", + "anotherCookie": "{}", + "someOtherCookie": "{}" +}; + +export function removeAllTestCookies() { + Object.keys(cookies).forEach(key => { + document.cookie = `${key}=;Max-Age=0;`; + document.cookie = `${key}=;Max-Age=0;path=/`; + }); +} + +export function getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate) { + return { + referralNumber: mockReferralNumber, + referralCorrelationId: mockCorrelationId, + referralDate: mockReferralDate + } +} + +export function setupCookies({ conceptCookieValue = "", includeHeritageCookie = true }) { + Object.keys(cookies).forEach(key => { + const cookieValue = key == cookieNames.CONCEPT_SESSION_INFO ? conceptCookieValue : cookies[key]; + + if (includeHeritageCookie || key != cookieNames.CONCEPT_SESSION_INFO) + document.cookie = `${key}=${cookieValue}; path=/;`; + }); +} + +// Private methods +function setupBaseMixinDispatchNonBlockingStoreAction(mockData) { + if (mockData.actionList !== undefined) { + baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn(); + baseMixin.methods.dispatchNonBlockingStoreAction.mockImplementation((actionName) => { + let actionFilterResult = mockData.actionList.filter( + (x) => x.actionName == actionName + ); + + if (actionFilterResult.length > 0 && actionFilterResult.length === 1) { + return Promise.resolve({ + data: actionFilterResult[0].data, + }); + } + }); + } +} \ No newline at end of file diff --git a/src/layouts/address-poc/address-poc.vue b/src/layouts/address-poc/address-poc.vue deleted file mode 100644 index 8bb42a659..000000000 --- a/src/layouts/address-poc/address-poc.vue +++ /dev/null @@ -1,233 +0,0 @@ - - - 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/nested-radio-poc/nested-radio.vue b/src/layouts/nested-radio-poc/nested-radio.vue deleted file mode 100644 index d0a78d085..000000000 --- a/src/layouts/nested-radio-poc/nested-radio.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - - - 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..93a3c9680 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -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); 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/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..eef9d4b23 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 { updateOrCreateConceptCookie,getConceptCookie } 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,35 @@ const routes = [ await GoToFunnelStartOn404(next); } else { try { + + // If the saved session has timed out, clear the session, execute 404 logic. + if (!isSavedSessionStillActive()) { + await GoToFunnelStartOn404(next); + } + + // Process concept funnel cookie. + updateOrCreateConceptCookie(); + + // On entering the concept 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; + + console.log("Page to redirect to: ", 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 +133,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 +148,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 +161,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 (getConceptCookie()?.ReferralNumber && getConceptCookie()?.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 +197,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..26a0b31b6 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_CONCEPT_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..b97ede119 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_CONCEPT_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/vue.config.js b/vue.config.js index 25494f865..7bca415e3 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,5 +1,7 @@ process.env.VUE_APP_CONSUMER_API_GATEWAY = "https://consumerapidev.safelite.com"; +process.env.VUE_APP_HERITAGE_FUNNEL = + "http://localhost:38000/default.aspx"; process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo" @@ -22,4 +24,7 @@ module.exports = { }, }, }, + configureWebpack: { + devtool: 'source-map' + }, }; diff --git a/vue.release.config.js b/vue.release.config.js index 0c470ffe4..86563f59e 100644 --- a/vue.release.config.js +++ b/vue.release.config.js @@ -1,5 +1,6 @@ process.env.VUE_APP_CONSUMER_API_GATEWAY = "__VUE_APP_CONSUMER_API_GATEWAY__"; process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__"; +process.env.VUE_APP_HERITAGE_FUNNEL = "__VUE_APP_HERITAGE_FUNNEL__"; module.exports = { outputDir: "dist/fmg",