router/index.js assumes a load-session successful response should always go to heritage. This may have been true in the initial version of NextGen but now we can load and direct it to the correct page. The incorrect routing to heritage was also failing here for returning users that already had a NextGen cookie. The error caused us to route to the beginning. Fixed that error by removing the default true in the navigateToHeritageFunnel and changing all calls to pass a true or false.
394 lines
14 KiB
JavaScript
394 lines
14 KiB
JavaScript
// Supporting files
|
|
import { createWebHistory, createRouter } from "vue-router";
|
|
import { storeActions } from "@/constants/store-actions";
|
|
import { storeMutations } from "../constants/store-mutations";
|
|
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 { queryStrings } from "@/constants/query-strings";
|
|
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
|
import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper";
|
|
|
|
// Heritage integration
|
|
import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper";
|
|
import {
|
|
updateOrCreateFunnelCookie,
|
|
getFunnelCookie,
|
|
updateSessionIdCookie,
|
|
} from "@/helpers/heritage-integration/cookie-helper";
|
|
import { loadSessionIfPresent, saveSession } 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";
|
|
import analyticsMixin from "@/mixins/analytics-mixin";
|
|
import { experimentTriggers } from "../constants/experiments";
|
|
import { applicationConfig } from "../constants/application-config";
|
|
|
|
const routes = [
|
|
{
|
|
path: "/",
|
|
name: "root",
|
|
async beforeEnter(to, from, next) {
|
|
// If we have no query string, or we don't have the FmgPage query string.
|
|
try {
|
|
if (analyticsMixin.methods.noSession()) {
|
|
await analyticsMixin.methods.initSession();
|
|
} else {
|
|
updateSessionIdCookie();
|
|
}
|
|
|
|
if (getFunnelCookie()?.SuppressConceptFunnel) {
|
|
await navigateToHeritageFunnel({ shouldSaveSession: false });
|
|
return next(false);
|
|
}
|
|
|
|
// If the saved session has timed out, clear the session, execute 404 logic.
|
|
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) {
|
|
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
|
|
await GoToFunnelStartOn404(next);
|
|
}
|
|
|
|
// On entering the funnel "fresh", read cookie information, decide what to do next.
|
|
if (from.redirectedFrom === undefined) {
|
|
// clear the saveSessionPromise - if it exists in the vuex store but a new instance was created
|
|
// the saveSessionPromise will no longer point to a valid promise
|
|
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
|
|
|
// Remove the parameter after quote release
|
|
const loadSessionResponse = await loadSessionIfPresent(
|
|
to.query.isInsurance != null
|
|
? to.query.isInsurance == "true"
|
|
? true
|
|
: false
|
|
: null
|
|
);
|
|
|
|
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to);
|
|
|
|
// Assign our fmgPage so it will load normally like the other pages.
|
|
to.query.fmgPage = pageToRedirectTo;
|
|
}
|
|
|
|
await runExperiments(to.query.fmgPage);
|
|
|
|
// Process funnel cookie.
|
|
updateOrCreateFunnelCookie();
|
|
|
|
// 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;
|
|
|
|
// If the component hasn't been loaded fully, load it before we check prerequisites.
|
|
if (component.default.methods === undefined) {
|
|
component = await component.default();
|
|
}
|
|
|
|
if (!arePagePrerequisitesValid(component)) {
|
|
await GoToFunnelStartOn404(next);
|
|
}
|
|
|
|
return next({ name: to.query.fmgPage, query: to.query, params: to.params });
|
|
}
|
|
|
|
// Get route info for the given url. Names will have a 1:1 relationship with names in the Cms.
|
|
const routeData = await GetRouteInfoFromPageName(to.query.fmgPage);
|
|
|
|
// Add our dynamic route.
|
|
router.addRoute({
|
|
path: routeData[0].path, // Always the same path, because we control it with query strings.
|
|
name: routeData[0].name,
|
|
component: routeData[0].component,
|
|
});
|
|
|
|
// Call the next components arePagePrerequisitesValid method before load.
|
|
// If it returns false, use the 404 logic.
|
|
const nextComponent = await router
|
|
.getRoutes()
|
|
.filter((x) => x.name === routeData[0].name)[0]
|
|
.components.default();
|
|
|
|
if (!arePagePrerequisitesValid(nextComponent)) {
|
|
await GoToFunnelStartOn404(next);
|
|
}
|
|
|
|
// Assign current query string parameters, as well as our fmgPage one.
|
|
next({
|
|
name: routeData[0].name,
|
|
query: Object.assign(to.query, { fmgPage: routeData[0].name }),
|
|
params: to.params,
|
|
});
|
|
} catch (error) {
|
|
console.log(error);
|
|
|
|
// If we don't have a route, go to our 404 page.
|
|
await GoToFunnelStartOn404(next);
|
|
}
|
|
},
|
|
},
|
|
];
|
|
|
|
const router = createRouter({
|
|
history: createWebHistory("/fmg/"),
|
|
routes,
|
|
//Cause "page" to begin at the top when route chanages.
|
|
scrollBehavior(to, from, savedPosition) {
|
|
// always scroll to top
|
|
return { top: 0 };
|
|
},
|
|
});
|
|
|
|
//---------------------------------------------------------- Router Functions ----------------------------------------------------------
|
|
|
|
router.beforeEach(async (to, from, next) => {
|
|
// set lastNavigationPage here to capture state before API calls for analytics.
|
|
// use current page url query string name when to.name is "root" (due to unresolved navigation in beforeEach)
|
|
router.lastNavigationPage = to.name == "root" ? analyticsMixin.methods.getPageName() : to.name;
|
|
|
|
next();
|
|
});
|
|
|
|
router.afterEach(async (to, from) => {
|
|
// Update lastPageVisited in the store
|
|
store.commit(storeMutations.UPDATE_LAST_PAGE_VISITED, to.name);
|
|
|
|
// If saving on navigation is requested, check for saved SessionId or EmailAddress to determine if saving is appropriate
|
|
if (eval(to.params.isSavingNavigation)) {
|
|
if (
|
|
store.getters.applicationUser.savedSessionId ||
|
|
store.getters.order.customer?.emailAddress
|
|
) {
|
|
await saveSession({});
|
|
}
|
|
}
|
|
|
|
// Push page view to GA
|
|
analyticsMixin.methods.pushPageViewToGA();
|
|
|
|
// Push experiments to Data Layer
|
|
analyticsMixin.methods.pushExperimentsToDataLayer();
|
|
});
|
|
|
|
router.navigateWithoutSaving = (
|
|
scenario,
|
|
currentRoute,
|
|
optionalQuery = {},
|
|
optionalParams = {},
|
|
optionalPageData = {}
|
|
) => {
|
|
navigate(scenario, currentRoute, false, optionalQuery, optionalParams, optionalPageData);
|
|
};
|
|
|
|
router.navigateWithSaving = (
|
|
scenario,
|
|
currentRoute,
|
|
optionalQuery = {},
|
|
optionalParams = {},
|
|
optionalPageData = {}
|
|
) => {
|
|
navigate(scenario, currentRoute, true, optionalQuery, optionalParams, optionalPageData);
|
|
};
|
|
|
|
router.navigateToExternalUrl = (url, optionalQuery = {}) => {
|
|
navigateToUrl(url, optionalQuery);
|
|
};
|
|
|
|
//Use this navigation when you need to call next() explicitly. beforeRouteEnter is a good example.
|
|
router.overrideNavigation = (
|
|
scenario,
|
|
currentRoute,
|
|
next,
|
|
isSavingNavigation,
|
|
optionalQuery = {},
|
|
optionalParams = {},
|
|
optionalPageData
|
|
) => {
|
|
navigate(
|
|
scenario,
|
|
currentRoute,
|
|
isSavingNavigation,
|
|
optionalQuery,
|
|
optionalParams,
|
|
optionalPageData
|
|
);
|
|
next();
|
|
};
|
|
|
|
router.getNextPage = () => nextPageName;
|
|
|
|
// PRIVATE VARIABLES
|
|
|
|
var nextPageName;
|
|
|
|
// PRIVATE FUNCTIONS
|
|
|
|
// Navigate to the next route, depending on the scenario.
|
|
async function navigate(
|
|
scenario,
|
|
currentRoute,
|
|
isSavingNavigation,
|
|
optionalQuery = {},
|
|
optionalParams = {},
|
|
optionalPageData = {}
|
|
) {
|
|
if (!scenario) {
|
|
console.error("No scenario provided. Please review the routing table.");
|
|
return;
|
|
}
|
|
|
|
// Match our maps up and navigate if we have a destination.
|
|
const matchingScenarioMap = getNavigationMap(scenario, currentRoute);
|
|
const destinationFmgPageValue = matchingScenarioMap.destinationFmgPageValue;
|
|
|
|
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.
|
|
|
|
nextPageName = destinationFmgPageValue;
|
|
|
|
// Update page data to the store for next page if provided. Otherwise, keep existing page data or set to empty object
|
|
const existingPageDataForPage = store.getters.pageData(destinationFmgPageValue);
|
|
baseMixin.methods.savePageDataToStore(
|
|
destinationFmgPageValue,
|
|
Object.keys(optionalPageData).length > 0
|
|
? optionalPageData
|
|
: existingPageDataForPage ?? {}
|
|
);
|
|
|
|
optionalParams.isSavingNavigation = isSavingNavigation;
|
|
|
|
// add querystring params to the route. if they are already in the store then no need to add them
|
|
var queryStringsObject = {
|
|
fmgPage: destinationFmgPageValue,
|
|
};
|
|
|
|
const hasZip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
|
const zip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
|
const promo = getQuerystringParameter(queryStrings.PROMO);
|
|
|
|
if (
|
|
hasZip &&
|
|
(store.getters.order.serviceLocation.zipCode === undefined ||
|
|
store.getters.order.serviceLocation.zipCode == null) &&
|
|
(store.getters.order.vehicle.registration.zipCode === undefined ||
|
|
store.getters.order.vehicle.registration.zipCode == null)
|
|
) {
|
|
queryStringsObject[queryStrings.ZIP_CODE] = zip;
|
|
}
|
|
|
|
if (promo) {
|
|
queryStringsObject[queryStrings.PROMO] = promo;
|
|
}
|
|
|
|
router.push({
|
|
name: "root",
|
|
query: Object.assign(optionalQuery, queryStringsObject),
|
|
params: optionalParams,
|
|
});
|
|
} else if (matchingScenarioMap.destinationUrl !== undefined) {
|
|
navigateToUrl(matchingScenarioMap.destinationUrl, optionalQuery);
|
|
}
|
|
}
|
|
|
|
// Get navigation map depending on the scenario and the current 'page' you're on.
|
|
function getNavigationMap(scenario, currentRoute) {
|
|
const fmgPageValue = currentRoute.query.fmgPage;
|
|
const matchedQueryValue = routingTable(store)
|
|
.filter(
|
|
(item) =>
|
|
item.fmgPageValue === fmgPageValue &&
|
|
item.maps.filter((map) => map.scenario === scenario).length > 0
|
|
)
|
|
.map((m) => m.maps.filter((map) => map.scenario === scenario))[0]
|
|
.filter((x) => x.filter === true || x.filter === undefined);
|
|
|
|
return matchedQueryValue[0];
|
|
}
|
|
|
|
//---------------------------------------------------------- Private Functions ----------------------------------------------------------
|
|
|
|
// Navigate to an external url.
|
|
function navigateToUrl(url, optionalQuery = {}) {
|
|
// possibly show some loading screen in the future here.
|
|
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.
|
|
// This will reach out to the Cms and there is a 1:1 relationship between page names and route names.
|
|
async function GetRouteInfoFromPageName(pageName) {
|
|
const response = await store.dispatch(storeActions.GET_ROUTE_INFO_ACTION, {
|
|
pageName: pageName,
|
|
});
|
|
const jsonFromResponse = JSON.parse(response.data.Result);
|
|
let routeData = [];
|
|
|
|
// Add our route data and return our array.
|
|
Object.keys(jsonFromResponse).forEach((key) => {
|
|
routeData.push({
|
|
path: "/",
|
|
name: `${key}`,
|
|
component: lazyLoadComponent(jsonFromResponse[key].LayoutName),
|
|
});
|
|
});
|
|
|
|
return routeData;
|
|
}
|
|
|
|
// Go to our start page on a 404.
|
|
async function GoToFunnelStartOn404(next) {
|
|
const apiResponse = await store.dispatch(storeActions.GET_HOMEPAGE_NAME);
|
|
const homepageName = apiResponse.data.Result;
|
|
|
|
// Put item on the bus
|
|
eventBus.addEventToBus(
|
|
globalEvents.Categories.GLOBAL_ALERT,
|
|
globalEvents.SubCategories.PAGE_NOT_FOUND,
|
|
{
|
|
isDismissible: true,
|
|
messageCopy: "You can get a quote by starting on this page.",
|
|
messageHeadline: "We're sorry, something went wrong.",
|
|
type: globalEventTypes.Danger,
|
|
}
|
|
);
|
|
|
|
next({
|
|
path: "/",
|
|
query: { fmgPage: homepageName },
|
|
});
|
|
}
|
|
|
|
// Checks arePagePrerequisitesValid on the component passed in.
|
|
function arePagePrerequisitesValid(component) {
|
|
return component.default.methods.arePagePrerequisitesValid();
|
|
}
|
|
|
|
// Run SiteEntry and PageEntry triggers for experiments
|
|
async function runExperiments(nextPage) {
|
|
if (!store.getters.applicationUser.triggeredSiteEntry) {
|
|
await baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, {
|
|
userId: getDeviceIdValue(),
|
|
triggerEvent: experimentTriggers.SITE_ENTRY,
|
|
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
|
});
|
|
}
|
|
|
|
await baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, {
|
|
userId: getDeviceIdValue(),
|
|
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
|
triggerValue: nextPage,
|
|
});
|
|
}
|
|
|
|
export default router;
|