From 6ec8709aa42fff3d545961bfed477849b37566bb Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Tue, 14 Jan 2025 11:47:14 -0500 Subject: [PATCH 01/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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, }, }; +