diff --git a/src/constants/application-config.js b/src/constants/application-config.js index 8c033d4cd..684c29467 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -1,6 +1,7 @@ const applicationConfig = { CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY, - SESSION_TIMEOUT_CONFIG: 30 + SESSION_TIMEOUT_CONFIG: 30, + SAVED_SESSION_TIMEOUT: 45 }; export { applicationConfig }; 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/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/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-helper.js b/src/helpers/heritage-integration-helper.js index 3ee84c437..290cc8fdb 100644 --- a/src/helpers/heritage-integration-helper.js +++ b/src/helpers/heritage-integration-helper.js @@ -1,13 +1,18 @@ import { storeActions } from "@/constants/store-actions.js"; import { cookieNames } from "@/constants/cookie-names"; +import { queryStrings } from "@/constants/query-strings"; import { externalUrls } from "@/router/router-constants/externalUrl-values"; import { applicationConfig } from "@/constants/application-config"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; - import store from "@/store"; import router from "@/router"; import baseMixin from "../mixins/base-mixin"; +/* + 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); @@ -23,6 +28,12 @@ export async function saveOrder() { updateOrCreateConceptCookie(); } +/* + 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(); @@ -47,7 +58,27 @@ export async function loadOrderIfPresent() { return (await loadOrder(conceptCookie.ReferralNumber, conceptCookie.ReferralDate, conceptCookie.ReferralCorrelationId)).data; } -export async function getPageToRouteExistingOrderTo() { +/* + 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; @@ -61,19 +92,30 @@ export async function getPageToRouteExistingOrderTo() { return "vehicle-model"; } else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) { return "vehicle-style"; - }else { - return "vehicle-damage"; + } else if (!store.getters.damage.isRepair || !store.getters.vehicle.carId) { + return 'vehicle-damage' + } else { + if (store.getters.vehicle.vin) { + return "vin-lookup"; + } else { + return "estimate" + } } + } +/* + 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.state.order.referralNumber, - ReferralDate: store.state.order.referralDate, - ReferralCorrelationId: store.state.order.referralCorrelationId, + ReferralNumber: store.getters.order.referralNumber, + ReferralDate: store.getters.order.referralDate, + ReferralCorrelationId: store.getters.order.referralCorrelationId, }); // Create the cookie @@ -82,21 +124,25 @@ export function updateOrCreateConceptCookie() { // Set up cookie with all the props. setConceptCookieProperties({ LastTouched: new Date().toUTCString(), + SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout, DidHeritageFunnelUpdateLast: false, ShouldResetState: false, - ReferralNumber: store.state.order.referralNumber, - ReferralDate: store.state.order.referralDate, - ReferralCorrelationId: store.state.order.referralCorrelationId, + ReferralNumber: store.getters.order.referralNumber, + ReferralDate: store.getters.order.referralDate, + ReferralCorrelationId: store.getters.order.referralCorrelationId, }); } -export function isConceptSessionStillActive() { +/* + 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.SESSION_TIMEOUT_CONFIG; const isMoreThanHalfHourAgo = ((new Date() - new Date(lastTouchedValue)) / 60000) > timeoutAmount; - console.log("has session expired -->", isMoreThanHalfHourAgo); if (isMoreThanHalfHourAgo) { return false; @@ -106,6 +152,31 @@ export function isConceptSessionStillActive() { } } +/* + 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("Saved Session Timed Out? --->", isSavedSessionTimedOut); + + if (isSavedSessionTimedOut) { + return false; + } + + return true; + } +} + +/* + 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. @@ -114,7 +185,7 @@ export async function navigateToHeritageFunnel() { router.navigateToExternalUrl( externalUrls.HERITAGE_FUNNEL, { - corid: store.state.order.referralCorrelationId, + corid: store.getters.order.referralCorrelationId, src: "concept-funnel", cns: "all", experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=ServAreaNoRemove_V7_TEST=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true" @@ -122,6 +193,10 @@ export async function navigateToHeritageFunnel() { ); } +/* + 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("; ") @@ -135,28 +210,40 @@ export function getConceptCookie() { } } +/* +Function to get the date for the saved session timeout. +*/ - - -// --------- PRIVATE FUNCTIONS --------- -async function loadOrder(referralNumber, referralDate, correlationId) { - console.log("loading order...", referralNumber, referralDate, correlationId); - try { - - const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER, - { - referralNumber: referralNumber.toString(), - referralDate: referralDate, - correlationId: correlationId - }, false); - - return response; - - } catch (e) { - console.log("error loading order:", e); - } +export function getDateForSavedSessionTimeout() { + const currentDate = new Date(new Date().toUTCString()) + currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT) + return currentDate.toUTCString(); } +//-------------------------------------\\\ +// --------- PRIVATE FUNCTIONS --------- \\\ +//----------------------------------------\\\ + +/* + Calls API to load order given the referral number, referralDate, and referralCorrelationId + and returns the response. +*/ +async function loadOrder(referralNumber, referralDate, correlationId) { + console.log("loading order...", referralNumber, referralDate, correlationId); + const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER, + { + referralNumber: referralNumber.toString(), + referralDate: referralDate, + correlationId: correlationId + }, false); + + return response; +} + +/* + 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(); @@ -173,6 +260,9 @@ function setConceptCookieProperties(properties) { } } +/* + Removes concept cookie from browser. +*/ function deleteConceptCookie() { // If this cookie is ever created from the concept funnel, will need to add another // line with the path=/fmg/ diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index af2eb1c2a..2e978fe0a 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -1,9 +1,7 @@ import store from "@/store"; -import router from "@/router"; 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 { @@ -33,9 +31,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 da684af1b..5c1f529e3 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 store from "@/store"; @@ -66,17 +65,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); - }); }); function getMixInInstance({ isDispatchSuccess = true }) { @@ -100,7 +88,6 @@ function getMixInInstance({ isDispatchSuccess = true }) { const baseMixIn = baseMixin; baseMixIn.methods.$route = route; baseMixIn.methods.storeActions = storeActions; - baseMixIn.methods.widgetNames = widgetNames; store.dispatch = storeDispatch; store.commit = jest.fn(); diff --git a/src/router/index.js b/src/router/index.js index 4e85f7b21..e0701d095 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -4,7 +4,7 @@ import { storeActions } from "@/constants/store-actions"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { routingTable } from "@/router/router-constants/routing-table.js"; import { globalEvents, globalEventTypes } from "@/constants/events"; -import * as integrationHelper from "@/helpers/heritage-integration-helper"; +import * as heritageIntegrationHelper from "@/helpers/heritage-integration-helper"; import baseMixin from "@/mixins/base-mixin"; import eventBus from "@/helpers/event-bus/event-bus"; @@ -35,20 +35,25 @@ const routes = [ } else { try { - // Check our 'Session' is still good. If not, reset state and go back to the start. - if (!integrationHelper.isConceptSessionStillActive()) { - baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE); + // If the saved session has timed out, clear the session, execute 404 logic. + if (!heritageIntegrationHelper.isSavedSessionStillActive()) { await GoToFunnelStartOn404(next); } - // Create funnel cookie, or update it if it already exists. - integrationHelper.updateOrCreateConceptCookie(); + // Process concept funnel cookie. + heritageIntegrationHelper.updateOrCreateConceptCookie(); // On entering the concept funnel "fresh", read cookie information, decide what to do next. if (from.redirectedFrom === undefined) { - await integrationHelper.loadOrderIfPresent(); + const loadOrderResponse = await heritageIntegrationHelper.loadOrderIfPresent(); + const pageToRedirectTo = await heritageIntegrationHelper.getPageToRouteExistingOrderTo(to, loadOrderResponse); - const pageToRedirectTo = await integrationHelper.getPageToRouteExistingOrderTo(); + // 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 heritageIntegrationHelper.navigateToHeritageFunnel(); + return next(false); + } // Assign our fmgPage so it will load normally like the other pages. to.query.fmgPage = pageToRedirectTo; @@ -154,8 +159,8 @@ async function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery baseMixin.methods.savePageDataToStore(destinationFmgPageValue, optionalPageData); // if cookie and referralNumber/Date exists - if (integrationHelper.getConceptCookie()?.ReferralNumber && integrationHelper.getConceptCookie()?.ReferralDate) { - await integrationHelper.saveOrder(); + if (heritageIntegrationHelper.getConceptCookie()?.ReferralNumber && heritageIntegrationHelper.getConceptCookie()?.ReferralDate) { + await heritageIntegrationHelper.saveOrder(); } router.push({ @@ -187,7 +192,7 @@ function getNavigationMap(scenario, currentRoute) { //---------------------------------------------------------- Private Functions ---------------------------------------------------------- // Navigate to an external url. -function navigateToUrl(url, optionalQuery) { +function navigateToUrl(url, optionalQuery = {}) { // possibly show some loading screen in the future here. var externalUrl = new URL(url); 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 2407d74c4..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/store/index.js b/src/store/index.js index 494be3dfb..9c5bfba5d 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,6 +1,7 @@ import { createStore } from "vuex"; import { endpoints } from "@/constants/endpoints.js"; import { storeMutations } from "@/constants/store-mutations"; +import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration-helper"; import createPersistedState from "vuex-persistedstate"; import globalMethods from "@/global-methods"; @@ -17,16 +18,32 @@ const getDefaultState = () => { 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:{ + lineItems: { glassParts: null, otherParts: null }, @@ -37,7 +54,8 @@ const getDefaultState = () => { }, applicationUser: { eventBus: [], - pageData: {} + pageData: {}, + savedSessionTimeout: getDateForSavedSessionTimeout() }, } }; @@ -74,19 +92,19 @@ 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; }, updateReferralCorrelationId(state, referralCorrelationId) { @@ -149,7 +167,7 @@ export const mutations = { // Misc Mutations setLoadOrderInformation(state, orderInformation) { state.order.referralNumber = orderInformation.referralNumber; - state.order.referralDate = orderInformation.referralDate; + state.order.referralDate = orderInformation.referralDate; state.order.referralCorrelationId = orderInformation.correlationId; state.order.vehicle = { year: orderInformation.vehicle?.year, @@ -186,7 +204,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 @@ -357,7 +377,7 @@ export const actions = { }); }, - loadOrder(context, {referralNumber, referralDate, correlationId}) { + loadOrder(context, { referralNumber, referralDate, correlationId }) { return globalMethods.callHttpClient({ method: endpoints.LoadOrder.method, endpoint: endpoints.LoadOrder.url, @@ -366,7 +386,7 @@ export const actions = { referralDate: referralDate, correlationId: correlationId }, - }).then( (response) => { + }).then((response) => { context.commit(storeMutations.SET_LOAD_CONCEPT_SESSION_INFO, response.data); return response; }); @@ -384,4 +404,6 @@ export default createStore({ mutations, getters, actions, -}); \ No newline at end of file +}); + +// Private Functions