449 lines
16 KiB
JavaScript
449 lines
16 KiB
JavaScript
import { createWebHistory, createRouter } from 'vue-router';
|
|
import lazyLoadComponent from '@/router/dynamic-routing/component-loader';
|
|
import issPageValues from '@/router/router-constants/issPage-values';
|
|
import routerTitles from '@/router/router-constants/router-titles';
|
|
import routingTable from '@/router/router-constants/routing-table';
|
|
import { useMainStore } from '@/store';
|
|
import eventBus from '@/helpers/event-bus/event-bus';
|
|
import { globalEvents, globalEventTypes } from '@/constants/events';
|
|
import baseMixin from '@/mixins/base-mixin';
|
|
import { isSavedSessionStillActive } from '@/helpers/session-helper';
|
|
import { getDeviceIdValue, getISSCookie, updateOrCreateISSCookie, updateSessionIdCookie } from '@/helpers/cookie-helper';
|
|
import { experimentTriggers } from '@/constants/experiments';
|
|
import applicationConfig from '@/constants/application-config';
|
|
import showIssLoadingModal from '@/helpers/loading-modal-helper';
|
|
|
|
import analyticsMixin from '@/mixins/analytics-mixin';
|
|
import { saveSession } from '@/helpers/order-helper.js';
|
|
import routerParams from '@/router/router-constants/router-params';
|
|
import canBailoutNavigateBack from '@/helpers/bailout-helper';
|
|
import submitType from '@/constants/submit-type';
|
|
import navigationScenarios from './router-constants/navigation-scenarios';
|
|
|
|
const routes = [
|
|
{
|
|
path: '/',
|
|
name: 'root',
|
|
async beforeEnter(to, from, next) {
|
|
const issPageToUse = !to.query.issPage ? issPageValues.WELCOME_PAGE : to.query.issPage;
|
|
const fromQueryPage = from.query?.issPage;
|
|
|
|
if ((issPageToUse === issPageValues.ACCESS_DENIED
|
|
|| (issPageToUse !== issPageValues.ENTRY_PAGE && !useMainStore().issConfig.parentAccountNumber))
|
|
&& process.env.VUE_APP_CURRENT_ENVIRONMENT !== 'Localhost'
|
|
) {
|
|
return await GoToAccessIsDenied(next);
|
|
}
|
|
|
|
await analyticsMixin.methods.validateSession();
|
|
|
|
// Do not run these for the main entry page - as it is not part of the user flow.
|
|
if (issPageToUse !== issPageValues.ENTRY_PAGE) {
|
|
try {
|
|
await runExperiments(issPageToUse);
|
|
} catch (error) {
|
|
console.error('Failed to run experiments during route navigation.', error);
|
|
}
|
|
}
|
|
|
|
// Intercept all navigation if a submitted order exists in storage
|
|
if (useMainStore().hasSubmittedOrder()) {
|
|
if (to.query.issPage !== issPageValues.ENTRY_PAGE) {
|
|
return await GoToConfirmationPage(next, useMainStore().getSubmittedOrder());
|
|
}
|
|
}
|
|
|
|
// If the saved session has timed out, clear the session, execute 404 logic.
|
|
if (getISSCookie() !== null && !isSavedSessionStillActive()) {
|
|
// await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
|
|
await GoToStartOn404(next);
|
|
}
|
|
|
|
// Process ISS cookie.
|
|
// Skip if Entry Page or Refreshing Welcome page
|
|
if (issPageToUse !== issPageValues.ENTRY_PAGE
|
|
&& !(issPageToUse === issPageValues.WELCOME_PAGE && fromQueryPage === undefined)) {
|
|
updateOrCreateISSCookie();
|
|
}
|
|
|
|
if (router.hasRoute(issPageToUse)) {
|
|
// Since our route is already in scope, we can grab the component and call the arePagePrerequisitesValid function.
|
|
let component = router.getRoutes().filter((x) => x.name === issPageToUse)[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 GoToStartOn404(next);
|
|
}
|
|
|
|
return next({name: issPageToUse, query: to.query, params: to.params});
|
|
}
|
|
|
|
const routeData = await GetRouteInfoFromPageName(issPageToUse);
|
|
|
|
if (routeData[0].name.toLowerCase() === 'error') {
|
|
throw new Error('Page not found!');
|
|
}
|
|
|
|
// 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)) {
|
|
const tempMsgCopy = 'Pre Requisites failed, need to handle. Default is Welcome Page.';
|
|
const tempMsgHeadline = `${issPageToUse}: pre-req failed...`;
|
|
await GoToStartOn404(next, tempMsgCopy, tempMsgHeadline);
|
|
}
|
|
|
|
// Assign current query string parameters, as well as our issPage one.
|
|
next({
|
|
name: routeData[0].name,
|
|
query: Object.assign(to.query, {issPage: routeData[0].name}),
|
|
params: to.params
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
];
|
|
|
|
const router = createRouter({
|
|
history: createWebHistory('/'),
|
|
routes,
|
|
scrollBehavior() {
|
|
// always scroll to top
|
|
return { top: 0 };
|
|
}
|
|
});
|
|
|
|
router.beforeEach(async (to, from) => {
|
|
const fromQueryPage = from.query?.issPage;
|
|
if (fromQueryPage === undefined) {
|
|
showIssLoadingModal(true);
|
|
}
|
|
|
|
const toQueryPage = to.query?.issPage;
|
|
const notToPayInAdvanceReturn = toQueryPage !== issPageValues.PAYMENT_RETURN;
|
|
const isInIframe = window !== window.top || fromQueryPage === issPageValues.PAYMENT_PAGE;
|
|
|
|
// isFromPaymentPageToOrderConfirmation workaround for navigating from an iframe but
|
|
// isInIframe evaluates to false for some reason when navigating from payment to confirmation
|
|
const isFromPaymentPageToOrderConfirmation =
|
|
fromQueryPage === issPageValues.PAYMENT_PAGE && toQueryPage === issPageValues.ORDER_CONFIRMATION;
|
|
|
|
if ((isInIframe && notToPayInAdvanceReturn) || isFromPaymentPageToOrderConfirmation) {
|
|
// need to set window.top.location.href directly when navigating out of an iframe
|
|
// especially when navigating with browser buttons
|
|
const newUrl = `${window.top.location.origin}${to.href}`;
|
|
window.top.location.href = newUrl;
|
|
return false;
|
|
}
|
|
|
|
const store = useMainStore();
|
|
// Prevent navigating backwards if we enter a bailout that we are not allowed to go back on
|
|
if (store.isBailout && from.name === issPageValues.BAILOUT_PAGE && to.name !== 'root' && to.name !== issPageValues.CONTACT_CONFIRMATION
|
|
&& !canBailoutNavigateBack()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
router.afterEach(async (to, from) => {
|
|
const store = useMainStore();
|
|
// Update lastPageVisited in the store
|
|
store.updateLastPageVisited(to.name);
|
|
|
|
if (from.redirectedFrom === undefined) {
|
|
store.clearSaveSessionPromise();
|
|
}
|
|
|
|
const skipSaveSession = !!router.options.history.state[routerParams.SKIP_SAVE_SESSION];
|
|
if (from.name !== undefined && !skipSaveSession && !store.hasSubmittedOrder()) {
|
|
await saveSession({ bailoutOnError: from.name === issPageValues.ENTRY_PAGE});
|
|
}
|
|
|
|
if (to.query.issPage !== issPageValues.ENTRY_PAGE) {
|
|
document.title = routerTitles[to.query.issPage] || 'Safelite Solutions®';
|
|
|
|
// digital consumer logging
|
|
analyticsMixin.methods.logDigitalConsumer();
|
|
|
|
// digital consumer ISS session logging to snowflake
|
|
analyticsMixin.methods.pushIssSessionData();
|
|
|
|
// 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, optionalQuery, optionalParams, optionalPageData);
|
|
};
|
|
|
|
router.navigateToExternalUrl = (url, optionalQuery = {}) => {
|
|
navigateToUrl(url, optionalQuery);
|
|
};
|
|
|
|
// 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 useMainStore().getRouteInfo(pageName);
|
|
|
|
const jsonFromResponse = JSON.parse(response.data.Result);
|
|
const 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;
|
|
}
|
|
|
|
// 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.navigate = (
|
|
scenario,
|
|
currentRoute,
|
|
optionalQuery = {},
|
|
optionalParams = {},
|
|
optionalPageData = {}
|
|
) => {
|
|
navigate(scenario, currentRoute, optionalQuery, optionalParams, optionalPageData);
|
|
};
|
|
|
|
router.navigateWithSpinner = (
|
|
scenario,
|
|
currentRoute
|
|
) => {
|
|
navigate(scenario, currentRoute, undefined, undefined, undefined, true);
|
|
};
|
|
|
|
// Navigate to the next route, depending on the scenario.
|
|
function navigate(
|
|
scenario,
|
|
currentRoute,
|
|
optionalQuery = {},
|
|
optionalParams = {},
|
|
optionalPageData = {},
|
|
forceSpinner = false
|
|
) {
|
|
if (!scenario) {
|
|
window.console.error('No scenario provided. Please review the routing table.');
|
|
return;
|
|
}
|
|
|
|
// Match our maps up and navigate if we have a destination.
|
|
let matchingScenarioMap = getNavigationMap(scenario, currentRoute);
|
|
if (!matchingScenarioMap && scenario === navigationScenarios.BAILOUT) {
|
|
matchingScenarioMap = { destinationIssPageValue: issPageValues.BAILOUT_PAGE }
|
|
}
|
|
|
|
if (!matchingScenarioMap) {
|
|
window.console.error('No matching scenario found. Please review the routing table.');
|
|
return;
|
|
}
|
|
|
|
if (forceSpinner) {
|
|
showIssLoadingModal(true);
|
|
}
|
|
|
|
if (scenario === navigationScenarios.CLICKED_BACK_PREVIOUS) {
|
|
// Update page to the prevous page in the router.
|
|
// If we need to worry about typing this in from outside of the app,
|
|
// we'll need to pre check history.length or if there is a previous page stored in state.
|
|
router.go(-1);
|
|
} else if (matchingScenarioMap.destinationIssPageValue) {
|
|
// Update page data to the store for next page if provided. Otherwise, keep existing page data or set to empty object
|
|
const existingPageDataForPage = useMainStore().pageData(matchingScenarioMap.destinationIssPageValue);
|
|
baseMixin.methods.savePageDataToStore(
|
|
matchingScenarioMap.destinationIssPageValue,
|
|
Object.keys(optionalPageData).length > 0
|
|
? optionalPageData
|
|
: existingPageDataForPage ?? {}
|
|
);
|
|
|
|
// We're always pushing the same path, just changing query strings.
|
|
// Make sure our optional query strings get combined with our issPage one.
|
|
router.push({
|
|
name: 'root',
|
|
query: Object.assign(optionalQuery, {
|
|
issPage: matchingScenarioMap.destinationIssPageValue
|
|
}),
|
|
state: optionalParams
|
|
});
|
|
} else if (matchingScenarioMap.destinationUrl) {
|
|
navigateToUrl(matchingScenarioMap.destinationUrl, optionalQuery);
|
|
}
|
|
}
|
|
|
|
// Navigate to an external url.
|
|
function navigateToUrl(url, optionalQuery = {}) {
|
|
// possibly show some loading screen in the future here.
|
|
const externalUrl = new URL(url);
|
|
for (const queryKey in optionalQuery) {
|
|
externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]);
|
|
}
|
|
|
|
window.location.assign(externalUrl);
|
|
}
|
|
|
|
router.navigateBailout = (bailoutData = null) => {
|
|
if (bailoutData != null && !useMainStore().isBailout) {
|
|
useMainStore().setBailout(bailoutData)
|
|
}
|
|
router.navigate(
|
|
navigationScenarios.BAILOUT,
|
|
router.currentRoute.value,
|
|
{},
|
|
{ [routerParams.SKIP_SAVE_SESSION]: true }
|
|
);
|
|
}
|
|
|
|
// Get navigation map depending on the scenario and the current 'page' you're on.
|
|
function getNavigationMap(scenario, currentRoute) {
|
|
const issPageValue = currentRoute.query.issPage;
|
|
const matchedQueryValue = routingTable(useMainStore())
|
|
.filter((item) => item.issPageValue === issPageValue && item.maps.filter((map) => map.scenario === scenario).length > 0);
|
|
|
|
const maps = matchedQueryValue ? matchedQueryValue.map((m) => m.maps.filter((map) => map.scenario === scenario))[0] : undefined;
|
|
|
|
return maps ? maps.filter((x) => x.filter === true || x.filter === undefined)[0] : undefined;
|
|
}
|
|
|
|
async function GoToAccessIsDenied(next) {
|
|
const errorPageName = issPageValues.ACCESS_DENIED;
|
|
router.addRoute({
|
|
path: '/',
|
|
name: errorPageName,
|
|
component: lazyLoadComponent(errorPageName)
|
|
});
|
|
|
|
next({
|
|
name: errorPageName,
|
|
query: { issPage: errorPageName }
|
|
});
|
|
}
|
|
|
|
async function GoToConfirmationPage(next, order) {
|
|
const nextPageName = getConfirmationPageFromOrder(order);
|
|
router.addRoute({
|
|
path: '/',
|
|
name: nextPageName,
|
|
component: lazyLoadComponent(nextPageName)
|
|
});
|
|
|
|
next({
|
|
name: nextPageName,
|
|
query: { issPage: nextPageName }
|
|
});
|
|
}
|
|
|
|
function getConfirmationPageFromOrder(order) {
|
|
switch (order.submitType) {
|
|
case submitType.SAFELITE:
|
|
return issPageValues.ORDER_CONFIRMATION;
|
|
case submitType.TPA:
|
|
return issPageValues.TPA_CONFIRMATION;
|
|
case submitType.BAILOUT:
|
|
return issPageValues.CONTACT_CONFIRMATION;
|
|
default:
|
|
return issPageValues.ORDER_CONFIRMATION;
|
|
}
|
|
}
|
|
|
|
async function GoToStartOn404(next, msgCopy = null, msgHeadline = null) {
|
|
const errorPageName = issPageValues.WELCOME_PAGE;
|
|
router.addRoute({
|
|
path: '/',
|
|
name: errorPageName,
|
|
component: lazyLoadComponent(errorPageName)
|
|
});
|
|
|
|
// Put item on the bus
|
|
eventBus.addEventToBus(globalEvents.Categories.GLOBAL_ALERT, globalEvents.SubCategories.PAGE_NOT_FOUND, {
|
|
isDismissible: true,
|
|
messageCopy: msgCopy ?? 'You can get a quote by starting on this page.',
|
|
messageHeadline: msgHeadline ?? "We're sorry, something went wrong.",
|
|
type: globalEventTypes.Danger
|
|
});
|
|
|
|
next({
|
|
name: errorPageName,
|
|
query: { issPage: errorPageName }
|
|
});
|
|
}
|
|
|
|
// Checks arePagePrerequisitesValid on the component passed in.
|
|
function arePagePrerequisitesValid(component) {
|
|
return component.default.methods.arePagePrerequisitesValid === undefined || component.default.methods.arePagePrerequisitesValid();
|
|
}
|
|
|
|
// Run SiteEntry and PageEntry triggers for experiments
|
|
async function runExperiments(nextPage) {
|
|
const store = useMainStore();
|
|
|
|
if (!store.applicationUser.triggeredSiteEntry) {
|
|
await store.runExperimentsForTrigger({
|
|
deviceId: getDeviceIdValue(),
|
|
triggerEvent: experimentTriggers.SITE_ENTRY,
|
|
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE
|
|
});
|
|
}
|
|
|
|
await store.runExperimentsForTrigger({
|
|
deviceId: getDeviceIdValue(),
|
|
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
|
triggerValue: nextPage
|
|
});
|
|
}
|
|
|
|
export default router;
|