DigitalConsumer.FixMyGlass/src/router/index.js
hiteshkumar87 8322d44173 CSR-1632
Affiliate Cookies
2024-08-09 18:04:43 +05:30

663 lines
27 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,
getAffiliateCookies,
} 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";
import { nextTick } from "vue";
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);
const leadGenYear = getQuerystringParameter(queryStrings.VEHICLE_YEAR);
const leadGenMake = getQuerystringParameter(queryStrings.VEHICLE_MAKE);
const leadGenModel = getQuerystringParameter(queryStrings.VEHICLE_MODEL);
const leadGenStyle = getQuerystringParameter(queryStrings.VEHICLE_STYLE);
const leadGenDamage = getQuerystringParameter(queryStrings.VEHICLE_DAMAGE);
const leadGenZipCode = getQuerystringParameter(queryStrings.SERVICE_ZIP);
const leadGenEmail = getQuerystringParameter(queryStrings.EMAIL);
const leadGenIsInsurance = getQuerystringParameter(
queryStrings.IS_INSURANCE
);
const leadGenVinSelection = getQuerystringParameter(
queryStrings.VIN_SELECTION
);
const leadGenServicePackage = getQuerystringParameter(
queryStrings.SERVICE_PACKAGE
);
const leadGenNumberOfChips = getQuerystringParameter(
queryStrings.NUMBER_OF_CHIPS
);
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();
}
if (
leadGenYear &&
leadGenMake &&
leadGenModel &&
leadGenStyle &&
leadGenDamage &&
leadGenZipCode &&
leadGenEmail &&
leadGenIsInsurance &&
(leadGenDamage.toUpperCase() == "WINDSHIELDREPAIR" ||
leadGenVinSelection?.length > 0) &&
(leadGenIsInsurance?.toUpperCase() == "FALSE" ||
leadGenServicePackage?.length > 0) &&
(leadGenDamage.toUpperCase() != "WINDSHIELDREPAIR" ||
leadGenNumberOfChips?.length > 0)
) {
store.commit(storeMutations.UPDATE_IS_LEAD_GEN, true);
store.commit(storeMutations.UPDATE_LEAD_GEN_YEAR, leadGenYear);
store.commit(storeMutations.UPDATE_LEAD_GEN_MAKE, leadGenMake);
store.commit(storeMutations.UPDATE_LEAD_GEN_MODEL, leadGenModel);
store.commit(storeMutations.UPDATE_LEAD_GEN_STYLE, leadGenStyle);
store.commit(storeMutations.UPDATE_LEAD_GEN_DAMAGE_TYPE, leadGenDamage);
if (leadGenDamage.toUpperCase() == "WINDSHIELDREPLACE") {
store.commit(storeMutations.UPDATE_LEAD_GEN_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_LEAD_GEN_NUMBER_OF_CHIPS, null);
} else if (leadGenDamage.toUpperCase() == "WINDSHIELDREPAIR") {
store.commit(storeMutations.UPDATE_LEAD_GEN_IS_REPAIR, true);
store.commit(
storeMutations.UPDATE_LEAD_GEN_NUMBER_OF_CHIPS,
leadGenNumberOfChips
);
}
store.commit(storeMutations.UPDATE_LEAD_GEN_ZIP_CODE, leadGenZipCode);
store.commit(
storeMutations.UPDATE_LEAD_GEN_EMAIL_ADDRESS,
leadGenEmail
);
if (leadGenIsInsurance?.toUpperCase() == "TRUE") {
store.commit(storeMutations.UPDATE_LEAD_GEN_IS_INSURANCE, true);
store.commit(
storeMutations.UPDATE_LEAD_GEN_SERVICE_PACKAGE,
leadGenServicePackage?.toLowerCase()
);
}
if (leadGenDamage.toUpperCase() != "WINDSHIELDREPAIR") {
store.commit(
storeMutations.UPDATE_LEAD_GEN_VIN_SELECTION,
leadGenVinSelection
);
}
}
//Affiliate Cookies
const affiliateCookies = getAffiliateCookies();
if (affiliateCookies != null && affiliateCookies.length > 0) {
store.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies);
}
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);
return;
}
// 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;
if (store.getters.isLeadGen) {
// Check if alert event is on the bus
const alertEvent = eventBus.readEventFromBus(
globalEvents.Categories.GLOBAL_ALERT,
globalEvents.SubCategories.PAGE_NOT_FOUND
);
//Uknown alerts most likely were added by a failed api call in global-methods
const unknownAlertEvent = eventBus.readEventFromBus(
globalEvents.Categories.GLOBAL_ALERT,
globalEvents.SubCategories.UNKNOWN_ERROR
);
// If alert event is on the bus, then display the alert
if (alertEvent !== undefined || unknownAlertEvent !== undefined) {
store.commit(storeMutations.UPDATE_IS_LEAD_GEN, false);
}
}
// 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 });
}
}
if (!store.getters.isLeadGen) {
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,
}
);
store.commit(storeMutations.UPDATE_IS_LEAD_GEN, false);
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.requiresVerifiedRedirecting) {
return fmgPageValues.VEHICLE_DAMAGE;
} else {
return funnelStartPageName;
}
}
export default router;