// Supporting files import { createWebHistory, createRouter } from "vue-router"; import { storeActions } from "@/constants/store-actions"; 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 { setCookieProperties, getDeviceIdValue, getSessionIdValue, getSessionKeyValue } from "@/helpers/heritage-integration/cookie-helper"; import { cookieNames } from "@/constants/cookie-names"; // Heritage integration import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper"; import { updateOrCreateFunnelCookie, getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; import { loadOrderIfPresent, saveOrder } 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"; // Components import ComponentTest from "@/layouts/component-test/component-test.vue"; import FormTest from "@/layouts/form-test/form-test.vue"; import Modal from "@/common-components/loading-modal/loading-modal.vue"; const routes = [ { path: "/component-test", // This is a temporary route for testing. name: "ComponentTest", component: ComponentTest, }, { path: "/form-test", // This is a temporary route for testing. name: "FormTest", component: FormTest, }, { path: "/loading-modal", // This is a temporary route for testing. name: "Modal", component: Modal, }, { 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 (noSession()) { await initSession(); } // If the saved session has timed out, clear the session, execute 404 logic. if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { await GoToFunnelStartOn404(next); } // On entering the funnel "fresh", read cookie information, decide what to do next. if (from.redirectedFrom === undefined) { const loadOrderResponse = await loadOrderIfPresent(); const pageToRedirectTo = await getPageToRouteExistingOrderTo(to, loadOrderResponse); // 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; } // 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, }); //---------------------------------------------------------- Router Functions ---------------------------------------------------------- router.afterEach(async (to, from) => { // Push page view to GA analyticsMixin.methods.pushPageViewToGA(to.query[queryStrings.FMG_PAGE]); baseMixin.methods.dispatchStoreAction(storeActions.GET_EXPERIMENTS_BY_USER, { userId: getDeviceIdValue() }) .then( (response) => { analyticsMixin.methods.pushExperimentsToDataLayer(response.data); }); }); router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) => { navigate(scenario, currentRoute, false, optionalQuery, optionalParams, optionalPageData); } router.navigateAfterSave = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) => { navigate(scenario, currentRoute, true, optionalQuery, optionalParams, optionalPageData); } router.navigateToExternalUrl = (url, optionalQuery = {}) => { navigateToUrl(url, optionalQuery); } // PRIVATE FUNCTIONS // Navigate to the next route, depending on the scenario. async function navigate(scenario, currentRoute, invalidateOnSave, 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. // If we need to do invalidation const currentComponent = currentRoute.matched[0].components; if (invalidateOnSave) { resetDependentState(currentComponent); } // Append page data to the store for the NEXT page, if any. It will be an empty object if none is provided. baseMixin.methods.savePageDataToStore(destinationFmgPageValue, optionalPageData); // if cookie and referralNumber/Date exists if (getFunnelCookie()?.ReferralNumber && getFunnelCookie()?.ReferralDate) { await saveOrder(); } router.push({ name: "root", query: Object.assign(optionalQuery, { fmgPage: destinationFmgPageValue, }), 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 .filter( (item) => item.fmgPageValue === fmgPageValue && item.maps.filter((map) => map.scenario === scenario).length > 0 ) .map((m) => m.maps.filter((map) => map.scenario === scenario)); return matchedQueryValue[0][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; } async function initSession() { const sid = getSessionIdValue(); const skey = getSessionKeyValue(); var payload = { userId: getDeviceIdValue(), sessionId: sid, userAgent: navigator.userAgent, referrer: document.referrer, }; const response = await baseMixin.methods.dispatchStoreAction(storeActions.INITIALIZE_SESSION, payload, false); if (response.data) { if (response.data.sessionKey && skey === 0) { setCookieProperties({ [cookieNames.SESSION_KEY]: response.data.sessionKey}); } if (response.data.sessionId && sid === '00000000-0000-0000-0000-000000000000') { setCookieProperties({ [cookieNames.SESSION_ID]: response.data.sessionId}); } } return true; } function noSession() { if (getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000') { return true; } else { return false; } } // 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(); } // Reset dependant state on route change. function resetDependentState(component) { return component.default.methods.resetDependentState(); } export default router;