419 lines
15 KiB
JavaScript
419 lines
15 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 { 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";
|
|
|
|
import review from "@/layouts/review/review";
|
|
import paymentMethod from "@/layouts/payment-method/payment-method";
|
|
const routes = [
|
|
{
|
|
path: "/payment-method", // This is a temporary route for testing.
|
|
name: "payment-method",
|
|
component: paymentMethod,
|
|
},
|
|
{
|
|
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,
|
|
loadSessionResponse
|
|
);
|
|
|
|
// 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;
|
|
}
|
|
|
|
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 queryString = window.location.search;
|
|
const urlParams = new URLSearchParams(queryString);
|
|
const lowerCaseParams = new URLSearchParams();
|
|
for (const [name, value] of urlParams) {
|
|
lowerCaseParams.append(name.toLowerCase(), value);
|
|
}
|
|
const hasZip = lowerCaseParams.has(queryStrings.ZIP_CODE);
|
|
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE);
|
|
|
|
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;
|
|
}
|
|
|
|
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.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
|
|
);
|
|
}
|
|
|
|
export default router;
|