569 lines
22 KiB
JavaScript
569 lines
22 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";
|
|
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
|
import { fmgPageValues, funnelStartPageName } from "@/router/router-constants/fmgPage-values";
|
|
|
|
// Heritage integration
|
|
import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper";
|
|
import {
|
|
updateOrCreateFunnelCookie,
|
|
getFunnelCookie,
|
|
updateSessionIdCookie,
|
|
deleteFunnelCookie,
|
|
} 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";
|
|
import { shouldStripPromoQueryString } from "@/helpers/promotions-helper";
|
|
import bailout from "@/layouts/bailout/bailout";
|
|
|
|
const routes = [
|
|
{
|
|
path: "/bailout",
|
|
name: "bailout",
|
|
component: bailout,
|
|
},
|
|
{
|
|
path: "/",
|
|
name: "root",
|
|
async beforeEnter(to, from, next) {
|
|
// If we have no query string, or we don't have the FmgPage query string.
|
|
try {
|
|
await analyticsMixin.methods.validateSession();
|
|
|
|
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);
|
|
deleteFunnelCookie();
|
|
GoToFunnelStartOn404(next);
|
|
}
|
|
|
|
// Intercept all navigation if a submitted order exists in storage
|
|
if (store.getters.hasSubmittedOrder) {
|
|
if (to.query.fmgPage !== funnelStartPageName) {
|
|
to.query.fmgPage = fmgPageValues.CONFIRMATION;
|
|
}
|
|
}
|
|
// On entering the funnel "fresh", read cookie information, decide what to do next.
|
|
else 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);
|
|
|
|
if (!to.query.fmgPage?.startsWith("payment")) {
|
|
//payment pages used to return from safelitehop so exclude here
|
|
// Remove the parameter after quote release
|
|
|
|
// for testing end to end locally, see if there is a referralNumber on the querystring
|
|
// that could have changed in heritage and then stick it in the store and then update the cookie
|
|
// so load-session runs properly. this can be removed when heritage goes away
|
|
const referralNumber = getQuerystringParameter(
|
|
queryStrings.REFERRAL_NUMBER
|
|
);
|
|
const parentAccount = getQuerystringParameter(queryStrings.PARENT_ACCOUNT);
|
|
const correlationId = getQuerystringParameter(queryStrings.CORRELATION_ID);
|
|
if (referralNumber) {
|
|
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
|
store.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccount);
|
|
store.commit(
|
|
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
|
|
correlationId
|
|
);
|
|
updateOrCreateFunnelCookie();
|
|
}
|
|
|
|
const loadSessionResponse = await loadSessionIfPresent(
|
|
to.query.isInsurance != null
|
|
? to.query.isInsurance == "true"
|
|
? true
|
|
: false
|
|
: null,
|
|
to.query.fmgPage
|
|
);
|
|
|
|
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to);
|
|
|
|
// This logic may determine that the user should be sent to heritage -- if so, do that here.
|
|
if (pageToRedirectTo === fmgPageValues.HERITAGE) {
|
|
await navigateToHeritageFunnel({ shouldSaveSession: false });
|
|
return next(false);
|
|
}
|
|
|
|
// Assign our fmgPage so it will load normally like the other pages.
|
|
to.query.fmgPage = pageToRedirectTo;
|
|
}
|
|
}
|
|
|
|
// Reroute around pages that should not be available.
|
|
|
|
// Exception 1: Integrated Users should not see vehicle or quote pages.
|
|
if (store.getters.requiresVerifiedRedirecting) {
|
|
if (to.query.fmgPage === fmgPageValues.VEHICLE) {
|
|
to.query.fmgPage = fmgPageValues.VEHICLE_DAMAGE;
|
|
} else if (
|
|
to.query.fmgPage === fmgPageValues.QUOTE ||
|
|
to.query.fmgPage === fmgPageValues.INSURANCE_COMPANY
|
|
) {
|
|
// Push to heritage if going to quote & integrated.
|
|
await navigateToHeritageFunnel({ shouldSaveSession: false });
|
|
return next(false);
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
console.log("Page prereq error: " + component.default.name);
|
|
GoToFunnelStartOn404(next);
|
|
}
|
|
|
|
return next({ name: to.query.fmgPage, query: to.query, params: to.params });
|
|
}
|
|
|
|
if (!isExistingFmgPageName(to.query.fmgPage)) {
|
|
console.log("Not an existing fmg page name:" + to.query.fmgPage);
|
|
GoToFunnelStartOn404(next);
|
|
}
|
|
|
|
// Get route info for the given url. Names will have a 1:1 relationship with names in the Cms.
|
|
const routeData = 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)) {
|
|
console.log(
|
|
"Page Prereqs not valid for next component: " + nextComponent.default.name
|
|
);
|
|
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) {
|
|
global.$logger.logError(`Routing ${error.stack}`);
|
|
|
|
if (to.query?.fmgPage === funnelStartPageName) {
|
|
deleteFunnelCookie();
|
|
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
|
|
}
|
|
|
|
// If we don't have a route, go to our 404 page.
|
|
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;
|
|
|
|
const fromQueryPage = from.query?.fmgPage;
|
|
if (fromQueryPage === undefined) {
|
|
showFmgLoadingModal(true);
|
|
}
|
|
|
|
const toQueryPage = to.query?.fmgPage;
|
|
const notToPIAReturn = toQueryPage != fmgPageValues.PAYMENT_PIA_RETURN;
|
|
const isInIframe = window !== window.top;
|
|
|
|
// fromPaymentToConfirmation workaround for navigating from an iframe but
|
|
// isInIframe evaluates to false for some reason when navigating from payment to confirmation
|
|
const fromPaymentToConfirmation =
|
|
fromQueryPage === fmgPageValues.PAYMENT && toQueryPage === fmgPageValues.CONFIRMATION;
|
|
|
|
if ((isInIframe && notToPIAReturn) || fromPaymentToConfirmation) {
|
|
const newUrl = `${window.top.location.origin}${to.href}`;
|
|
window.top.location.href = newUrl;
|
|
// Refresh page if navigating to self to prevent locking.
|
|
// For now only carved out for vehicle; all modals are opened through anchor tags at the moment,
|
|
// which also self navigate, but relied on the page remaining the same on self-navigation.
|
|
} else if (toQueryPage === fromQueryPage && toQueryPage === getRedirectedStartPage()) {
|
|
router.go(0);
|
|
} else {
|
|
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({ pageNameToLog: to.query.fmgPage });
|
|
}
|
|
}
|
|
|
|
showFmgLoadingModal(false);
|
|
|
|
// Push page view to GA
|
|
analyticsMixin.methods.pushPageViewToGA();
|
|
|
|
// Push experiments to Data Layer
|
|
analyticsMixin.methods.pushExperimentsToDataLayer();
|
|
|
|
// Push current order status to Data Layer
|
|
analyticsMixin.methods.pushOrderToDataLayer();
|
|
});
|
|
|
|
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);
|
|
};
|
|
|
|
router.navigateError = () => {
|
|
DisplayPageError();
|
|
};
|
|
|
|
//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);
|
|
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
|
|
|
|
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 && !shouldStripPromoQueryString(currentRoute.query.fmgPage)) {
|
|
queryStringsObject[queryStrings.PROMO] = promo;
|
|
}
|
|
|
|
if (pageError) {
|
|
queryStringsObject[queryStrings.PAGE_ERROR] = pageError;
|
|
}
|
|
|
|
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 no longer reach out to the Cms. There is a 1:1 relationship between page names and route names and layouts.
|
|
function GetRouteInfoFromPageName(pageName) {
|
|
// check if pagename is a valid route/component
|
|
if (!isExistingFmgPageName(pageName)) {
|
|
return [];
|
|
}
|
|
|
|
const routeData = [
|
|
{
|
|
path: "/",
|
|
name: pageName,
|
|
component: lazyLoadComponent(pageName),
|
|
},
|
|
];
|
|
|
|
return routeData;
|
|
}
|
|
|
|
// Go to our start page on a 404.
|
|
function GoToFunnelStartOn404(next) {
|
|
// 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: funnelStartPageName },
|
|
});
|
|
}
|
|
|
|
async function DisplayPageError() {
|
|
// Put item on the bus
|
|
eventBus.addEventToBus(
|
|
globalEvents.Categories.GLOBAL_ALERT,
|
|
globalEvents.SubCategories.UNKNOWN_ERROR,
|
|
{
|
|
isDismissible: true,
|
|
messageCopy: "",
|
|
messageHeadline: "We're sorry, something went wrong.",
|
|
type: globalEventTypes.Danger,
|
|
}
|
|
);
|
|
|
|
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
|
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
|
|
|
|
// we use the pageError querystring as a counter to how many times a user has experienced an error and been routed here.
|
|
// once they receive more than 1 pageerrors, we'll reset their state to hopefully correct any issues they may be having.
|
|
var navPage = fmgPageValues.VEHICLE;
|
|
if (store.getters.requiresVerifiedRedirecting) {
|
|
navPage = fmgPageValues.VEHICLE_DAMAGE;
|
|
}
|
|
|
|
if (pageError) {
|
|
var errorCount = Number(pageError);
|
|
if (errorCount > 1) {
|
|
deleteFunnelCookie();
|
|
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
|
|
|
|
router.push({
|
|
path: "/",
|
|
query: { fmgPage: navPage },
|
|
});
|
|
} else {
|
|
errorCount++;
|
|
router.push({
|
|
path: "/",
|
|
query: { fmgPage: navPage, pageError: errorCount },
|
|
});
|
|
}
|
|
} else {
|
|
router.push({
|
|
path: "/",
|
|
query: { fmgPage: navPage, pageError: "1" },
|
|
});
|
|
}
|
|
}
|
|
|
|
function isExistingFmgPageName(pageName) {
|
|
const names = Object.values(fmgPageValues);
|
|
|
|
return names.some((name) => name === pageName);
|
|
}
|
|
|
|
// 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.dispatchStoreActionWithLogging(
|
|
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
|
{
|
|
userId: getDeviceIdValue(),
|
|
triggerEvent: experimentTriggers.SITE_ENTRY,
|
|
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
|
},
|
|
nextPage
|
|
);
|
|
}
|
|
|
|
await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
|
{
|
|
userId: getDeviceIdValue(),
|
|
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
|
triggerValue: nextPage,
|
|
},
|
|
nextPage
|
|
);
|
|
}
|
|
|
|
function getRedirectedStartPage() {
|
|
if (store.getters.requiresVerifiedRedirect) {
|
|
return fmgPageValues.VEHICLE_DAMAGE;
|
|
} else {
|
|
return funnelStartPageName;
|
|
}
|
|
}
|
|
|
|
export default router;
|