From 6ec8709aa42fff3d545961bfed477849b37566bb Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Tue, 14 Jan 2025 11:47:14 -0500 Subject: [PATCH 01/31] CASH-61: add logging to GoToFunnelStartOn404 --- src/mixins/analytics-mixin.js | 13 +++++++ src/router/index.js | 68 +++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index e970e94a8..30f8328f0 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -650,6 +650,19 @@ export default { }); }, + pushPageErrorToDataLayer(error) { + + pushToDataLayerIfDefined({ + event: "page-error", + error: { + type: error.type || "", + cause: error.cause || "", + currentPage: error.currentPage || "", + nextPage: error.nextPage || "", + }, + }); + }, + prependActionToMethod(object, method, actionToPrepend) { const baseMethodName = method.name.startsWith("bound ") ? method.name.substring(6) diff --git a/src/router/index.js b/src/router/index.js index da3c1914c..028a6d85f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -65,9 +65,17 @@ const routes = [ // If the saved session has timed out, clear the session, execute 404 logic. if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { log(" --save session timeout go to start"); + + const errorPayload = { + cause: "expired session", + currentPage: from.query.fmgPage, + nextPage: to.query.fmgPage, + }; + await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE); deleteFunnelCookie(); - GoToFunnelStartOn404(next); + GoToFunnelStartOn404(next, errorPayload); + return; } // Intercept all navigation if a submitted order exists in storage @@ -206,7 +214,14 @@ const routes = [ return; } - GoToFunnelStartOn404(next); + const errorPayload = { + cause: "invalid page prerequisites for page", + currentPage: from.query.fmgPage, + nextPage: to.query.fmgPage, + }; + + GoToFunnelStartOn404(next, errorPayload); + return; } log(" --has route:", to.query.fmgPage); @@ -214,9 +229,15 @@ const routes = [ } if (!isExistingFmgPageName(to.query.fmgPage)) { - console.log("Not an existing fmg page name:" + to.query.fmgPage); - GoToFunnelStartOn404(next); + const errorPayload = { + cause: "invalid page name", + currentPage: from.query.fmgPage, + nextPage: to.query.fmgPage, + }; + + GoToFunnelStartOn404(next, errorPayload); return; + } // Get route info for the given url. Names will have a 1:1 relationship with names in the Cms. @@ -237,9 +258,18 @@ const routes = [ .components.default(); if (!arePagePrerequisitesValid(nextComponent)) { + // prettier-ignore console.log("Page Prereqs not valid for next component: " + nextComponent.default.name); - GoToFunnelStartOn404(next); + + const errorPayload = { + cause: "invalid page prerequisites for page", + currentPage: from.query.fmgPage, + nextPage: to.query.fmgPage, + }; + + GoToFunnelStartOn404(next, errorPayload); + return; } log("------------- router index.js beforeEnter end -----------------"); @@ -261,7 +291,16 @@ const routes = [ console.log(new Date() + " Exception in beforeEnter:" + JSON.stringify(error)); // If we don't have a route, go to our 404 page. - GoToFunnelStartOn404(next); + const errorPayload = { + cause: "uncaught error in beforeEnter", + currentPage: from.query.fmgPage, + nextPage: to.query.fmgPage, + fullError: error, + errorStack: error.stack, + }; + + GoToFunnelStartOn404(next, errorPayload); + return; } }, }, @@ -399,6 +438,7 @@ router.navigateToExternalUrl = (url, optionalQuery = {}) => { router.navigateError = () => { DisplayPageError(); + /// TODO - Add error logging for dataLayer here? }; //Use this navigation when you need to call next() explicitly. beforeRouteEnter is a good example. @@ -588,7 +628,19 @@ function GetRouteInfoFromPageName(pageName) { } // Go to our start page on a 404. -function GoToFunnelStartOn404(next) { +function GoToFunnelStartOn404(next, errorPayload = null) { + if (errorPayload !== null) { + errorPayload.type = "GoToFunnelStartOn404"; + analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload); + + console.log( + "%cGoToFunnelStartOn404()... errorPayload", + "color: white; background-color: blue; padding: 5px;", + errorPayload + ); + + } + // Put item on the bus eventBus.addEventToBus( globalEvents.Categories.GLOBAL_ALERT, @@ -608,6 +660,8 @@ function GoToFunnelStartOn404(next) { } async function DisplayPageError() { + console.log("%c running DisplayPageError()... ", "font-size: 20px; color: purple;"); + // Put item on the bus eventBus.addEventToBus( globalEvents.Categories.GLOBAL_ALERT, From fc834f9d1211658c6389f995a86442e3edad3083 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 15 Jan 2025 10:31:06 -0500 Subject: [PATCH 02/31] Additional error logging --- src/global-methods.js | 8 +++++++- src/mixins/analytics-mixin.js | 7 +------ src/router/index.js | 20 ++++++++++++++++---- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/global-methods.js b/src/global-methods.js index 22512fe41..96c5e21fc 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -110,7 +110,13 @@ export default { if (error.response.status && error.response.status != "404") { // Do not route to error logic when no wipers found or no promo found (404s) - router.navigateError(); + const errorPayload = { + cause: `Response error ${error.response.status}`, + currentPage: pageNameToLog, + endpoint: endpoint, + }; + + router.navigateError(errorPayload); // do not log 404 errors from services because we return NotFound // when a service doesn't return an object diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 30f8328f0..d534a7200 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -654,12 +654,7 @@ export default { pushToDataLayerIfDefined({ event: "page-error", - error: { - type: error.type || "", - cause: error.cause || "", - currentPage: error.currentPage || "", - nextPage: error.nextPage || "", - }, + error: error, }); }, diff --git a/src/router/index.js b/src/router/index.js index 028a6d85f..d92f213bc 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -436,9 +436,8 @@ router.navigateToExternalUrl = (url, optionalQuery = {}) => { navigateToUrl(url, optionalQuery); }; -router.navigateError = () => { - DisplayPageError(); - /// TODO - Add error logging for dataLayer here? +router.navigateError = (errorPayload = null) => { + DisplayPageError(errorPayload); }; //Use this navigation when you need to call next() explicitly. beforeRouteEnter is a good example. @@ -659,9 +658,22 @@ function GoToFunnelStartOn404(next, errorPayload = null) { }); } -async function DisplayPageError() { +async function DisplayPageError(errorPayload = null) { console.log("%c running DisplayPageError()... ", "font-size: 20px; color: purple;"); + if(errorPayload !== null) { + errorPayload.type = "DisplayPageError"; + analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload); + } else { + errorPayload = { + type: "DisplayPageError", + cause: "Unknown page error", + currentPage: getQuerystringParameter(queryStrings.FMG_PAGE), + nextPage: null, + }; + analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload); + } + // Put item on the bus eventBus.addEventToBus( globalEvents.Categories.GLOBAL_ALERT, From 7af0c3f0a1da276f10ae5f44f189c65df8e27e66 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 15 Jan 2025 10:50:14 -0500 Subject: [PATCH 03/31] Defensive coding in error handling --- src/router/index.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/router/index.js b/src/router/index.js index d92f213bc..0da560c84 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -68,8 +68,8 @@ const routes = [ const errorPayload = { cause: "expired session", - currentPage: from.query.fmgPage, - nextPage: to.query.fmgPage, + currentPage: from?.query?.fmgPage, + nextPage: to?.query?.fmgPage, }; await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE); @@ -216,8 +216,8 @@ const routes = [ const errorPayload = { cause: "invalid page prerequisites for page", - currentPage: from.query.fmgPage, - nextPage: to.query.fmgPage, + currentPage: from?.query?.fmgPage, + nextPage: to?.query?.fmgPage, }; GoToFunnelStartOn404(next, errorPayload); @@ -231,8 +231,8 @@ const routes = [ if (!isExistingFmgPageName(to.query.fmgPage)) { const errorPayload = { cause: "invalid page name", - currentPage: from.query.fmgPage, - nextPage: to.query.fmgPage, + currentPage: from?.query?.fmgPage, + nextPage: to?.query?.fmgPage, }; GoToFunnelStartOn404(next, errorPayload); @@ -264,8 +264,8 @@ const routes = [ const errorPayload = { cause: "invalid page prerequisites for page", - currentPage: from.query.fmgPage, - nextPage: to.query.fmgPage, + currentPage: from?.query?.fmgPage, + nextPage: to?.query?.fmgPage, }; GoToFunnelStartOn404(next, errorPayload); @@ -293,10 +293,10 @@ const routes = [ // If we don't have a route, go to our 404 page. const errorPayload = { cause: "uncaught error in beforeEnter", - currentPage: from.query.fmgPage, - nextPage: to.query.fmgPage, + currentPage: from?.query?.fmgPage, + nextPage: to?.query?.fmgPage, fullError: error, - errorStack: error.stack, + errorStack: error?.stack, }; GoToFunnelStartOn404(next, errorPayload); @@ -665,11 +665,17 @@ async function DisplayPageError(errorPayload = null) { errorPayload.type = "DisplayPageError"; analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload); } else { + // very cautiously to avoid additional errors: + var currentPage = ""; + try { + currentPage = getQuerystringParameter(queryStrings.FMG_PAGE); + } catch (e) { + // pass + } errorPayload = { type: "DisplayPageError", cause: "Unknown page error", - currentPage: getQuerystringParameter(queryStrings.FMG_PAGE), - nextPage: null, + currentPage: currentPage, }; analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload); } From 341d1b3ed36827ca2b30b75046564c0c23a10451 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 15 Jan 2025 11:54:08 -0500 Subject: [PATCH 04/31] Revert "Merge pull request #2198 from Safelite/feature/CASH-77" This reverts commit 4d462fdb0af8e4a7f4cfe7e10bafc1dae94b5115, reversing changes made to 94504194ea846c3b6313840daad7b2461b943cf6. --- src/digital-components/modal/modal.vue | 49 +--- .../save-progress-modal-question.vue | 73 +++--- .../save-progress-popup-question.spec.js | 51 ---- .../save-progress-popup-question.vue | 227 ------------------ .../capability-questions.vue | 4 +- .../molding-questions/molding-questions.vue | 4 +- src/layouts/part-questions/part-questions.vue | 4 +- src/layouts/quote/quote.spec.js | 4 +- src/layouts/quote/quote.vue | 49 +--- 9 files changed, 67 insertions(+), 398 deletions(-) delete mode 100644 src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js delete mode 100644 src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue index 2f9534750..e412678ff 100644 --- a/src/digital-components/modal/modal.vue +++ b/src/digital-components/modal/modal.vue @@ -2,9 +2,7 @@ -
-
- -
-
@@ -139,7 +134,6 @@ import { packageNames } from "@/constants/package-names"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { containsRecalParts } from "@/helpers/recal-helper"; import { externalParameterStatus } from "@/constants/external-parameters"; -import { saveQuote } from "@/helpers/heritage-integration/order-helper.js"; defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); @@ -461,9 +455,6 @@ export default { }, }, methods: { - async handleClick() { - await saveQuote({ pageNameToLog: "quote" }); - }, openModalAction(modalName) { this.$refs[modalName].openModal(); }, diff --git a/src/store/index.js b/src/store/index.js index 18c2834a4..4f9bf61e3 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1931,49 +1931,6 @@ export const actions = { }, // Session API Actions - saveQuote(context, { pageNameToLog, payload }) { - const vehicle = context.getters.vehicle; - const damage = context.getters.damage; - const order = context.state.order; - const applicationUser = context.getters.applicationUser; - const lineItems = context.state.order.lineItems; - const excludeRecalInquote = context.getters.shouldHideRecalibration; - - return globalMethods.callHttpClient({ - method: endpoints.SaveQuote.method, - endpoint: endpoints.SaveQuote.url, - payload: { - referralCorrelationId: order.referralCorrelationId, - referralNumber: order.referralNumber?.toString(), - referralSequenceNumber: order.referralSequenceNumber, - emailAddress: "cn@gm.com", //order.customer.emailAddress, - lastPage: pageNameToLog, - isRepair: damage.isRepair, - firstName: order.customer.firstName, - lastName: order.customer.lastName, - savedSessionId: applicationUser.savedSessionId, - serviceState: order.serviceLocation.state, - serviceZipCode: order.serviceLocation.zipCode, - parentAccountNumber: order.payment.parentAccountNumber, - excludeRecalInquote: excludeRecalInquote, - year: vehicle.year, - make: vehicle.make, - model: vehicle.model, - isInsurance: order.payment.isInsurance ?? false, - isVerified: order.payment.insuranceCoverage.isVerified ?? false, - lineItems: { - glassParts: lineItems.glassParts, - supportingItems: lineItems.supportingItems, - vaps: lineItems.vaps, - serverData: lineItems.serverData, - promos: lineItems.promos, - }, - }, - logApiCall: true, - pageNameToLog: pageNameToLog, - }); - }, - saveSession(context, { pageNameToLog, payload }) { const vehicle = context.getters.vehicle; const damage = context.getters.damage; @@ -2051,7 +2008,8 @@ export const actions = { order.payment.insuranceCoverage.coverageStatus?.toString(), coverageVerificationType: order.payment.insuranceCoverage.coverageVerificationType, - coverageSubStatus: order.payment.insuranceCoverage.coverageSubStatus, + coverageSubStatus: + order.payment.insuranceCoverage.coverageSubStatus, }, isInsurance: order.payment.isInsurance, parentAccountNumber: order.payment.parentAccountNumber, From 9791542dc0883a7aac557a5922dc897e312b91d7 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 15 Jan 2025 13:08:41 -0500 Subject: [PATCH 15/31] Revert "Revert "Merge pull request #2199 from Safelite/feature/CASH-79-BE"" This reverts commit a90b330d466396c85eb682dc1c5c3fe6f38b15b3. --- src/helpers/logger.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/helpers/logger.js b/src/helpers/logger.js index 53c2cb808..8af062c3b 100644 --- a/src/helpers/logger.js +++ b/src/helpers/logger.js @@ -32,7 +32,12 @@ export class Logger { } formatLogEntry(message, details) { - return `Application: ${applicationConfig.APPLICATION_NAME}\n${message}\n${ + const refSeqNumber = store.getters.order.referralSequenceNumber ?? ""; + const year = store.getters.vehicle.year ?? ""; + const make = store.getters.vehicle.make ?? ""; + const model = store.getters.vehicle.model ?? ""; + const vehicle = `${year} ${make} ${model}`; + return `${new Date()} ReferralSequenceNumber: ${refSeqNumber}\nVehicle: ${vehicle}\nApplication: ${applicationConfig.APPLICATION_NAME}\n${message}\n${ details ? JSON.stringify(details, undefined, 2) : "" }`; } From 9e24c0d45b1e57ee2f708bb57c718499464dd20f Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Thu, 16 Jan 2025 06:50:04 -0500 Subject: [PATCH 16/31] Revert "Merge pull request #2204 from Safelite/release/revert-syp-from-2025.01.16" This reverts commit 38e81949b3077f33e65c0d122c74cdde9c7d0e05, reversing changes made to 4d462fdb0af8e4a7f4cfe7e10bafc1dae94b5115. --- src/constants/endpoints.js | 4 + src/constants/store-actions.js | 1 + src/digital-components/modal/modal.vue | 83 ++++++- .../questions-page-layout.vue | 12 +- .../save-progress-modal-question.spec.js | 51 ++++ .../save-progress-modal-question.vue | 186 ++++++++++++++ .../save-progress-question.spec.js | 40 +++ .../save-progress-question.vue | 62 +++++ .../save-progress-popup-question.spec.js | 51 ++++ .../save-progress-popup-question.vue | 227 ++++++++++++++++++ .../heritage-integration/order-helper.js | 19 ++ .../capability-questions.vue | 18 +- .../molding-questions/molding-questions.vue | 18 +- src/layouts/part-questions/part-questions.vue | 18 +- src/layouts/quote/quote.spec.js | 36 +++ src/layouts/quote/quote.vue | 55 ++++- src/layouts/vehicle-parts/vehicle-parts.vue | 15 +- src/store/index.js | 46 +++- src/styles/common-styles.scss | 2 +- src/ux-components/button-main/button-main.vue | 4 +- 20 files changed, 924 insertions(+), 24 deletions(-) create mode 100644 src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js create mode 100644 src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue create mode 100644 src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.spec.js create mode 100644 src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.vue create mode 100644 src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js create mode 100644 src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 16e0803e6..c972d8118 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -124,6 +124,10 @@ const endpoints = { url: "/order/api/v1/order/load-session", method: "POST", }, + SaveQuote: { + url: "/order/api/v1/order/initiate-saved-progress-email", + method: "POST", + }, GetSignature: { url: "/order/api/v1/order/sign", method: "POST", diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 41e5896bd..45a18bace 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -37,6 +37,7 @@ const storeActions = { GET_MOBILE_PREMIUM_FEE: "getMobilePremiumFee", SAVE_SESSION: "saveSession", LOAD_SESSION: "loadSession", + SAVE_QUOTE: "saveQuote", UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse", VALIDATE_ZIP: "validateZip", PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "priceOrderItemsAndSaveServerData", diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue index 37ebee18e..2f9534750 100644 --- a/src/digital-components/modal/modal.vue +++ b/src/digital-components/modal/modal.vue @@ -2,7 +2,9 @@ @@ -53,6 +55,8 @@ export default { props: { headerText: String, footerButtonText: String, + suppressPageScroll: Boolean, + staticBackdrop: Boolean, onModalOpenedCallback: { type: Function, }, @@ -91,7 +95,7 @@ export default { e.preventDefault(); if (this.isEnterClicked) return; this.isEnterClicked = true; - document.getElementById("modalbtn")?.focus(); + document.getElementById(this.modalId + "-modalbtn")?.focus(); this.$refs.modalButtonMain.clicked(); document.getElementById(this.modalId)?.focus(); setTimeout(() => { @@ -107,8 +111,12 @@ export default { onModalClosed() { this.resetButtonStyle(); this.onModalClosedCallback?.(); + if (this.suppressPageScroll) + document.querySelector("body").classList.remove("prevent-modal-scroll"); }, openModal() { + if (this.suppressPageScroll) + document.querySelector("body").classList.add("prevent-modal-scroll"); const modal = Modal.getOrCreateInstance(document.getElementById(this.modalId)); modal.show(); this.$emit("isModalOpened", true); @@ -126,6 +134,9 @@ export default { } return !this.meta.dirty || !this.meta.valid; }, + backdropSetting() { + return this.staticBackdrop ? "static" : "true"; + }, }, components: { modalButtonMain, @@ -231,6 +242,54 @@ export default { } } } +.save-progress-modal-question { + p.modal-body { + padding: 0; + } + .modal-body { + display: flex; + flex-direction: column; + + .textbox-question { + padding: 0; + + margin: 0 0 1.5rem 0; + } + } +} + +.save-progress-modal-question, +.save-progress-popup-question { + p.modal-body { + padding: 0; + } + .modal-body { + display: flex; + flex-direction: column; + + .textbox-question { + padding: 0; + + label { + text-align: left; + font-weight: 900; + } + } + } + .modal-body-inner { + text-align: center; + margin-bottom: 1.5rem; + } + .modal-disclaimer { + font-size: 0.75rem; + order: 2; + } + .modal-footer { + margin: 0 0 1.5rem 0; + padding-top: 0; + padding-bottom: 0; + } +} body { .modal-backdrop { height: 100%; diff --git a/src/fmg-components/layouts/questions-page-layout/questions-page-layout.vue b/src/fmg-components/layouts/questions-page-layout/questions-page-layout.vue index 982738394..2c4d71803 100644 --- a/src/fmg-components/layouts/questions-page-layout/questions-page-layout.vue +++ b/src/fmg-components/layouts/questions-page-layout/questions-page-layout.vue @@ -25,7 +25,7 @@
-
+
+ + diff --git a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js new file mode 100644 index 000000000..15fb1481d --- /dev/null +++ b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js @@ -0,0 +1,51 @@ +import { mount, shallowMount } from "@vue/test-utils"; +import saveProgressModalQuestion from "./save-progress-modal-question"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +jest.mock("@/digital-components/modal/modal", () => ({ + methods: { + openModal: jest.fn(), + resetButtonStyle: jest.fn(), + }, +})); + +describe("save-progress-modal-question ", () => { + describe("when openModal is run ", () => { + test("the modal should open ", () => { + // Arrange + const { wrapper } = setupMocks({ + props: { + modelValue: "", + modalWidgetName: "testModal", + }, + }); + + // Act + wrapper.vm.openModal(); + + // Assert + expect(wrapper.vm.modal).not.toBeNull(); + }); + }); +}); + +function setupMocks({ options, props }) { + const mountOptions = getMountOptions({ + ...options, + }); + + const mockBaseMixin = { + methods: { + getCmsContent: jest.fn(), + dispatchStoreAction: jest.fn(), + }, + }; + + if (props) mountOptions.propsData = props; + + mountOptions.global.mixins = [mockBaseMixin]; + + const wrapper = mount(saveProgressModalQuestion, mountOptions); + + return { wrapper }; +} diff --git a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue new file mode 100644 index 000000000..c8465fef7 --- /dev/null +++ b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.spec.js b/src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.spec.js new file mode 100644 index 000000000..7cc3d100c --- /dev/null +++ b/src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.spec.js @@ -0,0 +1,40 @@ +import { shallowMount } from "@vue/test-utils"; +import saveProgressQuestion from "./save-progress-question"; + +describe("save-progress-question.vue", () => { + it("Should get the modelValue", async () => { + // Arrange + const text = "test"; + const wrapper = shallowMount(saveProgressQuestion, { + props: { + modelValue: text, + }, + attachTo: document.body, + }); + + // Act + const modelValueText = wrapper.vm.value; + wrapper.vm.value = "test also"; + + // Assert + expect(modelValueText).toEqual("test"); + }); + + it("Should emit to set value", async () => { + // Arrange + const text = "test"; + const wrapper = shallowMount(saveProgressQuestion, { + props: { + modelValue: text, + }, + attachTo: document.body, + }); + + // Act + const modelValueText = wrapper.vm.value; + wrapper.vm.value = "test also"; + + // Assert + expect(wrapper.emitted("update:modelValue")).toEqual([["test also"]]); + }); +}); diff --git a/src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.vue b/src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.vue new file mode 100644 index 000000000..221aa3415 --- /dev/null +++ b/src/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question.vue @@ -0,0 +1,62 @@ + + + diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js new file mode 100644 index 000000000..e2ca84f1d --- /dev/null +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js @@ -0,0 +1,51 @@ +import { mount, shallowMount } from "@vue/test-utils"; +import saveProgressPopupQuestion from "./save-progress-popup-question"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +jest.mock("@/digital-components/modal/modal", () => ({ + methods: { + closeModal: jest.fn(), + resetButtonStyle: jest.fn(), + }, +})); + +describe("save-progress-popup-question ", () => { + describe("when closeModal is run ", () => { + test("the modal should close ", () => { + // Arrange + const { wrapper } = setupMocks({ + props: { + modelValue: "", + modalWidgetName: "testModal", + }, + }); + + // Act + wrapper.vm.closeModal(); + + // Assert + expect(wrapper.vm.modal).not.toBeNull(); + }); + }); +}); + +function setupMocks({ options, props }) { + const mountOptions = getMountOptions({ + ...options, + }); + + const mockBaseMixin = { + methods: { + getCmsContent: jest.fn(), + dispatchStoreAction: jest.fn(), + }, + }; + + if (props) mountOptions.propsData = props; + + mountOptions.global.mixins = [mockBaseMixin]; + + const wrapper = mount(saveProgressPopupQuestion, mountOptions); + + return { wrapper }; +} diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue new file mode 100644 index 000000000..740385dac --- /dev/null +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js index 555c41325..76729e49f 100644 --- a/src/helpers/heritage-integration/order-helper.js +++ b/src/helpers/heritage-integration/order-helper.js @@ -98,6 +98,25 @@ export async function submitWorkOrder({ }); } +export async function saveQuote({ pageNameToLog }) { + await baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.SAVE_QUOTE, + {}, + pageNameToLog, + false + ); + + // save session back to sv2 so the email gets saved just in case they refresh or step away and come back later + await saveSession({ + pageNameToLog: pageNameToLog, + shouldAwaitSaveSessionQueue: true, + submitAfterSave: false, + createUnscheduledStatusWorkOrderForPIA: false, + }); + + return; +} + // PRIVATE FUNCTIONS // /* diff --git a/src/layouts/capability-questions/capability-questions.vue b/src/layouts/capability-questions/capability-questions.vue index 4d637c978..9d51ec2f7 100644 --- a/src/layouts/capability-questions/capability-questions.vue +++ b/src/layouts/capability-questions/capability-questions.vue @@ -12,13 +12,20 @@ @forwardButtonAction="forwardButtonAction" @back-click="navigateBack" :key="currentGlassIndex" - :index="currentGlassIndex" /> + :index="currentGlassIndex"> + + + diff --git a/src/layouts/molding-questions/molding-questions.vue b/src/layouts/molding-questions/molding-questions.vue index 00df8c04b..2f2cc346c 100644 --- a/src/layouts/molding-questions/molding-questions.vue +++ b/src/layouts/molding-questions/molding-questions.vue @@ -12,13 +12,20 @@ @forwardButtonAction="forwardButtonAction" @back-click="navigateBack" :key="currentGlassIndex" - :index="currentGlassIndex" /> + :index="currentGlassIndex"> + + + diff --git a/src/layouts/part-questions/part-questions.vue b/src/layouts/part-questions/part-questions.vue index bc1ec308e..a5441acd6 100644 --- a/src/layouts/part-questions/part-questions.vue +++ b/src/layouts/part-questions/part-questions.vue @@ -12,13 +12,20 @@ @forwardButtonAction="forwardButtonAction" @back-click="navigateBack" :key="currentGlassIndex" - :index="currentGlassIndex" /> + :index="currentGlassIndex"> + + + diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index 778ce0ce8..deca0d6d1 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -121,6 +121,9 @@ store.getters = { isInsurance: false, parentAccountNumber: applicationConfig.CASH_PARENT_ACCOUNT_NUMBER, }, + customer: { + emailAddress: "test@test.com", + }, }, }; @@ -136,6 +139,9 @@ afterEach(() => { insuranceCoverage: {}, isInsurance: false, }, + customer: { + emailAddress: "test@test.com", + }, }; }); @@ -263,6 +269,9 @@ describe("quote.vue", () => { glassParts: ["item", "item2"], }, payment: {}, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { zipCode: "12345", zipCodeCtu: "value", @@ -309,6 +318,9 @@ describe("quote.vue", () => { glassParts: ["item", "item2"], }, payment: {}, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { zipCode: "12345", zipCodeCtu: "value", @@ -355,6 +367,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, }, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { state: null, }, @@ -396,6 +411,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, }, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { state: null, }, @@ -438,6 +456,9 @@ describe("quote.vue", () => { payment: { isInsurance: true, }, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { state: null, }, @@ -480,6 +501,9 @@ describe("quote.vue", () => { payment: { isInsurance: false, }, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { state: null, }, @@ -523,6 +547,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, // Ensure that previous selection isn't overriding selection }, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { state: null, }, @@ -568,6 +595,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, // Ensure that previous selection isn't overriding selection }, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { state: null, }, @@ -612,6 +642,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, }, + customer: { + emailAddress: "test@test.com", + }, serviceLocation: { state: "AZ", }, @@ -717,6 +750,9 @@ describe("quote.vue", () => { isInsurance: true, inactivePromos: [], }, + customer: { + emailAddress: "test@test.com", + }, }, externalParameterState: { isExternalParameter: 1 }, externalParameterQuote: { diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 6f3809304..366a9d2cc 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -1,8 +1,10 @@ @@ -100,6 +120,8 @@ import contentGroupModal from "@/fmg-components/content-group-modal/content-grou import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner"; import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue"; +import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; +import saveProgressPopupQuestion from "@/fmg-components/save-progress-popup-question/save-progress-popup-question"; // Supporting files import baseMixin from "@/mixins/base-mixin.js"; @@ -134,6 +156,7 @@ import { packageNames } from "@/constants/package-names"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { containsRecalParts } from "@/helpers/recal-helper"; import { externalParameterStatus } from "@/constants/external-parameters"; +import { saveQuote } from "@/helpers/heritage-integration/order-helper.js"; defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); @@ -183,6 +206,10 @@ export default { ]; const resultMap = await settleAllPromises(promiseResultMap); + const emailFromStore = store.getters.order.customer.emailAddress; + const isEmailInStoreOnPageLoad = emailFromStore?.length > 0; + const showSaveProgressPopup = isEmailInStoreOnPageLoad ? false : true; + const lineItems = deepClone(store.getters.order.lineItems); lineItems.vaps = lineItems.vaps ?? []; const nullSafeGlassParts = lineItems.glassParts ?? []; @@ -263,6 +290,8 @@ export default { // Call the "next" function to complete the transition to this page. next(async (vm) => { vm.setCmsContent(resultMap.cmsContent); + vm.isEmailInStoreOnPageLoad = isEmailInStoreOnPageLoad; + vm.showSaveProgressPopup = showSaveProgressPopup; vm.addableVaps = addableVaps; vm.lineItems = lineItems; vm.availableLineItems = pricingResults; @@ -409,6 +438,8 @@ export default { servicePackage: null, holdParentAccountNumber: null, holdBillToAccountNumber: null, + isEmailInStoreOnPageLoad: null, + showSaveProgressPopup: null, }; }, computed: { @@ -495,6 +526,14 @@ export default { backButtonAction() { vehicleQuestionsMixin.methods.navigateBack(this); }, + saveProgress() { + this.isEmailInStoreOnPageLoad = true; + this.showSaveProgressPopup = false; + saveQuote({ pageNameToLog: "quote" }); + }, + closeSaveProgressPopup() { + this.showSaveProgressPopup = false; + }, forwardButtonAction() { this.dispatchStoreAction( this.storeActions.SAVE_PAYMENT_TYPE, @@ -680,10 +719,22 @@ export default { afterpayModalBanner, promoModalQuestion, recalDisclaimer, + saveProgressModalQuestion, + saveProgressPopupQuestion, }, }; + diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index d534a7200..5a2da603d 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -26,6 +26,7 @@ import store from "@/store"; import baseMixin from "@/mixins/base-mixin"; import { applicationConfig } from "../constants/application-config"; +import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; export default { methods: { @@ -651,7 +652,6 @@ export default { }, pushPageErrorToDataLayer(error) { - pushToDataLayerIfDefined({ event: "page-error", error: error, @@ -710,6 +710,17 @@ export default { }, async validateSession() { + // The noSession function checks the cookies related to analytics logging(sid). it is not the funnel info cookie. + // The sid cookie for analytics will expire every 30 minutes and get recreated in initSession. If this happens + // and we also have the funnel cookie present, that indicates they have an existing session that is now expired + // so route them to the return user page. + const funnelCookieLastTouched = getFunnelCookie()?.LastTouched; + if (this.noSession() && funnelCookieLastTouched !== null && funnelCookieLastTouched !== undefined) { + await this.initSession(); + window.location.href = applicationConfig.RETURN_USER_PAGE; + return; + } + if (this.noSession()) { await this.initSession(); } diff --git a/src/router/router-constants/fmgPage-values.js b/src/router/router-constants/fmgPage-values.js index 6b4ec77ea..5fade6506 100644 --- a/src/router/router-constants/fmgPage-values.js +++ b/src/router/router-constants/fmgPage-values.js @@ -22,8 +22,10 @@ const fmgPageValues = { PAYMENT: "payment", PAYMENT_PIA_RETURN: "payment-pia-return", CONFIRMATION: "confirmation", + RETURN_USER: "return-user", }; const funnelStartPageName = fmgPageValues.VEHICLE; +const returnUserPageName = fmgPageValues.RETURN_USER; -export { fmgPageValues, funnelStartPageName }; +export { fmgPageValues, funnelStartPageName, returnUserPageName }; diff --git a/vue.config.js b/vue.config.js index 28d20e406..d8cb42355 100644 --- a/vue.config.js +++ b/vue.config.js @@ -7,6 +7,7 @@ process.env.VUE_APP_MY_ACCOUNT = "https://myaccountdev.safelite.com/"; //process.env.VUE_APP_SAFELITE_HOP = "http://localhost:60966/fmgCheckoutShared.aspx"; process.env.VUE_APP_SAFELITE_HOP = "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx"; process.env.VUE_APP_SOLARWINDS_MONITORING = ""; +process.env.VUE_APP_SESSION_TIMEOUT_MINUTES = 30; // GA & GTM process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = From 47586b581619273210dd859bec66f2b5bb5aef85 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Fri, 17 Jan 2025 16:50:25 -0500 Subject: [PATCH 26/31] CASH-124: fix bug with unmounted hook --- .../save-progress-modal-question.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue index 510f7bc3e..e1fc3e180 100644 --- a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue +++ b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue @@ -53,6 +53,10 @@ export default { modalWidgetName: String, pageName: String, }, + unmounted() { + // remove any modal effects before leaving page (e.g. user hits browser back button) + this.modal.closeModal(); + }, computed: { buttonText() { return this.getCmsContent(this.modalWidgetName, "SubheaderText"); From 29ed44c68b022d5cabf682f0c7ff5bfb087e0331 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Fri, 17 Jan 2025 16:52:39 -0500 Subject: [PATCH 27/31] CASH-122: fix bug with edge case of styling of skip button --- .../modal-button-main/modal-button-main.vue | 2 +- .../save-progress-popup-question.vue | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/digital-components/modal/ux-components/modal-button-main/modal-button-main.vue b/src/digital-components/modal/ux-components/modal-button-main/modal-button-main.vue index 687ae6858..41f9d2d8a 100644 --- a/src/digital-components/modal/ux-components/modal-button-main/modal-button-main.vue +++ b/src/digital-components/modal/ux-components/modal-button-main/modal-button-main.vue @@ -128,7 +128,7 @@ export default { @include blue-gradient; } &:focus, // Mouse, touch, stylus focus - &:focus-visible { + &:focus-visible { // Keyboard focus for accessibility outline: none; box-shadow: diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue index e23266a51..4ab1213c4 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue @@ -143,11 +143,12 @@ export default { } } - .skip-button { + .btn.btn-secondary.skip-button { order: 4; position: relative; color: $black; border: none; + border-radius: .25rem; font-weight: 900; font-size: 0.875rem; text-align: center; @@ -164,12 +165,17 @@ export default { &:hover, &:active, - &:focus, - &:focus-visible { + &:focus { color: $blue-700; background: none; box-shadow: none; } + &:focus-visible { + outline: none; + box-shadow: + 0 0 0 3px $white, + 0 0 0 5.5px $blue-300; + } &.delay { // fixes flicker while transitioning between states From 5360783093aacef1b0b25a474ea4f8e895d717c0 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Sat, 18 Jan 2025 07:53:42 -0500 Subject: [PATCH 28/31] CASH-109 CASH-109 will follow up later with tests for return-user --- jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jest.config.js b/jest.config.js index ce4aa3ad6..052e1c47b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -27,6 +27,7 @@ module.exports = { "!src/layouts/insurance/*.vue", // Temp test exclusion while in development "!src/layouts/insurance-company/*.vue", // Temp test exclusion while in development "!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development + "!src/layouts/return-user/*.vue", // Temp test exclusion while in development // END ], // ! means exclude from coverage. testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], From 6a9585b661403ccfafd8be58d675af0dd2941363 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Sat, 18 Jan 2025 08:37:20 -0500 Subject: [PATCH 29/31] CASH-109 CASH-109 new variable --- azure-pipelines.yml | 4 ++++ vue.release.config.js | 1 + 2 files changed, 5 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3ac871d20..23d5e6806 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -100,6 +100,7 @@ stages: __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) __VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__) + __VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__) indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) @@ -160,6 +161,7 @@ stages: __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) __VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__) + __VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__) indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) @@ -220,6 +222,7 @@ stages: __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) __VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__) + __VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__) indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) @@ -280,6 +283,7 @@ stages: __VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__) __VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__) __VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__) + __VUE_APP_SESSION_TIMEOUT_MINUTES__: $(__VUE_APP_SESSION_TIMEOUT_MINUTES__) indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) diff --git a/vue.release.config.js b/vue.release.config.js index 2c215c8ad..350a78ce3 100644 --- a/vue.release.config.js +++ b/vue.release.config.js @@ -5,6 +5,7 @@ process.env.VUE_APP_CURRENT_ENVIRONMENT = "__VUE_APP_CURRENT_ENVIRONMENT__"; process.env.VUE_APP_MY_ACCOUNT = "__VUE_APP_MY_ACCOUNT__"; process.env.VUE_APP_SAFELITE_HOP = "__VUE_APP_SAFELITE_HOP__"; process.env.VUE_APP_SOLARWINDS_MONITORING_SCRIPT = "__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__"; +process.env.VUE_APP_SESSION_TIMEOUT_MINUTES = 30; // GA & GTM process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = "__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__"; From 32a73e25f43572a0e1a772c72c7c1d4da4cee3cb Mon Sep 17 00:00:00 2001 From: CarlNation Date: Sun, 19 Jan 2025 12:22:51 -0500 Subject: [PATCH 30/31] CASH-109 add logic to direct to return user page when navigating from external site excluding heritage CASH-109 add logic to direct to return user page when navigating from external site excluding heritage --- src/constants/query-strings.js | 1 + src/layouts/return-user/return-user.vue | 11 ++- src/mixins/analytics-mixin.js | 6 +- src/router/index.js | 72 ++++++++++++++++++-- src/router/router-constants/routing-table.js | 10 +++ 5 files changed, 90 insertions(+), 10 deletions(-) diff --git a/src/constants/query-strings.js b/src/constants/query-strings.js index 3cdfd655f..210ac28f0 100644 --- a/src/constants/query-strings.js +++ b/src/constants/query-strings.js @@ -45,6 +45,7 @@ const queryStrings = { ORGANIC: "organic", ORGANIC_SOCIAL: "organic_social", EXPERIMENTS: "experiments", + FROM_HERITAGE: "fromheritage", }; export { queryStrings }; diff --git a/src/layouts/return-user/return-user.vue b/src/layouts/return-user/return-user.vue index 189d6592f..3e72c91a9 100644 --- a/src/layouts/return-user/return-user.vue +++ b/src/layouts/return-user/return-user.vue @@ -82,8 +82,10 @@ export default { }, async forwardButtonAction() { - // clicking continue and use no page name to trigger the implicit navigation in router - window.location.href = "/fmg/"; + this.$router.navigateWithoutSaving( + this.navigationScenarios.CLICKED_FORWARD, + this.$route + ); }, async startOver() { @@ -95,7 +97,10 @@ export default { ); deleteFunnelCookie(); await this.dispatchStoreAction(storeActions.RESET_STATE); - window.location.href = "/fmg/"; + this.$router.navigateWithoutSaving( + this.navigationScenarios.CLICKED_FORWARD, + this.$route + ); }, }, components: { diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 5a2da603d..136c0ef14 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -715,7 +715,11 @@ export default { // and we also have the funnel cookie present, that indicates they have an existing session that is now expired // so route them to the return user page. const funnelCookieLastTouched = getFunnelCookie()?.LastTouched; - if (this.noSession() && funnelCookieLastTouched !== null && funnelCookieLastTouched !== undefined) { + if ( + this.noSession() && + funnelCookieLastTouched !== null && + funnelCookieLastTouched !== undefined + ) { await this.initSession(); window.location.href = applicationConfig.RETURN_USER_PAGE; return; diff --git a/src/router/index.js b/src/router/index.js index f6950b624..f04c0613d 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -26,6 +26,7 @@ import { loadSessionIfPresent, saveSession } from "@/helpers/heritage-integratio import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel, + getImplicitNavigation, } from "@/helpers/heritage-integration/navigation-helper"; import baseMixin from "@/mixins/base-mixin"; @@ -53,6 +54,7 @@ const routes = [ log("------------- router index.js beforeEnter start -----------------"); log(" --to: ", to); log(" --cookie: ", getFunnelCookie()); + log(` --from.redirectedFrom:>${JSON.stringify(from.redirectedFrom)}<`, ""); await analyticsMixin.methods.validateSession(); @@ -78,6 +80,9 @@ const routes = [ return; } + const fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE) === "true"; + const fromReturnUser = from?.name === fmgPageValues.RETURN_USER; + // Intercept all navigation if a submitted order exists in storage if (window.sessionStorage.getItem("submittedOrder") !== null) { if (to.query.fmgPage !== funnelStartPageName) { @@ -86,7 +91,21 @@ const routes = [ } } // On entering the funnel "fresh", read cookie information, decide what to do next. - else if (from.redirectedFrom === undefined) { + else if (from.redirectedFrom === undefined || fromReturnUser) { + // if entering the funnel from non-funnel check and see if there is already a funnel cookie. + // if so, send them to return-user page + const funnelCookieLastTouched = getFunnelCookie()?.LastTouched; + if ( + to?.query?.fmgPage !== fmgPageValues.RETURN_USER && + !fromReturnUser && + !fromHeritage && + funnelCookieLastTouched !== null && + funnelCookieLastTouched !== undefined + ) { + log(" --navigate to return user"); + NavigateToReturnUser(next, to); + } + // 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 log(" --clear promise"); @@ -145,6 +164,11 @@ const routes = [ store.commit(storeMutations.RESET_GLASS_PARTS_STATE); } + // if coming from the return user page, clear the destination page so implicit navigation runs + if (fromReturnUser && to.query) { + to.query[queryStrings.FMG_PAGE] = ""; + } + const pageToRedirectTo = await getPageToRouteExistingOrderTo(to); log(" --pageToRedirectTo ", JSON.stringify(pageToRedirectTo)); @@ -237,11 +261,11 @@ const routes = [ GoToFunnelStartOn404(next, errorPayload); 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); + log("--routeData:", routeData); // Add our dynamic route. router.addRoute({ @@ -258,7 +282,6 @@ const routes = [ .components.default(); if (!arePagePrerequisitesValid(nextComponent)) { - // prettier-ignore console.log("Page Prereqs not valid for next component: " + nextComponent.default.name); @@ -319,6 +342,7 @@ const router = createRouter({ //---------------------------------------------------------- Router Functions ---------------------------------------------------------- router.beforeEach(async (to, from, next) => { + log("------------- router index.js beforeEach start -----------------"); // 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; @@ -370,6 +394,7 @@ router.beforeEach(async (to, from, next) => { } else { next(); } + log("------------- router index.js beforeEach end -----------------"); }); router.afterEach(async (to, from) => { @@ -626,6 +651,42 @@ function GetRouteInfoFromPageName(pageName) { return routeData; } +async function NavigateToReturnUser(next, to) { + const returnRoute = GetRouteInfoFromPageName(fmgPageValues.RETURN_USER); + log("--returnRoute:", returnRoute); + + 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 === fmgPageValues.RETURN_USER)[0].components; + + log("--return-user component:", component); + + // If the component hasn't been loaded fully, load it before we check prerequisites. + if (component.default.methods === undefined) { + component = await component.default(); + } + + log(" --has route for return-user:", fmgPageValues.RETURN_USER); + return next({ name: fmgPageValues.RETURN_USER, query: to.query, params: to.params }); + } else { + log(" --add route for return-user:", fmgPageValues.RETURN_USER); + router.addRoute({ + path: returnRoute[0].path, // Always the same path, because we control it with query strings. + name: returnRoute[0].name, + component: returnRoute[0].component, + }); + + next({ + name: returnRoute[0].name, + query: Object.assign(to.query, { fmgPage: returnRoute[0].name }), + params: to.params, + }); + return; + } +} + // Go to our start page on a 404. function GoToFunnelStartOn404(next, errorPayload = null) { if (errorPayload !== null) { @@ -637,7 +698,6 @@ function GoToFunnelStartOn404(next, errorPayload = null) { "color: white; background-color: blue; padding: 5px;", errorPayload ); - } // Put item on the bus @@ -661,7 +721,7 @@ function GoToFunnelStartOn404(next, errorPayload = null) { async function DisplayPageError(errorPayload = null) { console.log("%c running DisplayPageError()... ", "font-size: 20px; color: purple;"); - if(errorPayload !== null) { + if (errorPayload !== null) { errorPayload.type = "DisplayPageError"; analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload); } else { @@ -670,7 +730,7 @@ async function DisplayPageError(errorPayload = null) { try { currentPage = getQuerystringParameter(queryStrings.FMG_PAGE); } catch (e) { - // pass + // pass } errorPayload = { type: "DisplayPageError", diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 3e3dbd4bf..8891e7cff 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -530,6 +530,16 @@ const routingTable = function (store) { { fmgPageValue: fmgPageValues.CONFIRMATION, }, + // The destination page for return user does not matter. the router will invoke implicit navigation + { + fmgPageValue: fmgPageValues.RETURN_USER, + maps: [ + { + scenario: navigationScenarios.CLICKED_FORWARD, + destinationFmgPageValue: fmgPageValues.VEHICLE, + }, + ], + }, ]; }; From bfc0eda484bd37332f1b5f81ccc3999390886fc7 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Mon, 20 Jan 2025 06:27:26 -0500 Subject: [PATCH 31/31] CASH-109 style CASH-109 style --- src/layouts/return-user/return-user.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/layouts/return-user/return-user.vue b/src/layouts/return-user/return-user.vue index 3e72c91a9..5facfde0a 100644 --- a/src/layouts/return-user/return-user.vue +++ b/src/layouts/return-user/return-user.vue @@ -118,6 +118,7 @@ export default { font-weight: 600; color: $black; cursor: pointer; + text-decoration: underline; } }