diff --git a/src/digital-components/textbox-question/textbox-question.vue b/src/digital-components/textbox-question/textbox-question.vue index 2443d67a..e74b1708 100644 --- a/src/digital-components/textbox-question/textbox-question.vue +++ b/src/digital-components/textbox-question/textbox-question.vue @@ -47,7 +47,7 @@ :data-bs-toggle="includeSelectIcon ? 'modal' : ''" :data-bs-target="'#' + cmsWidgetName" @change="handleChange" - @blur="handleChange" + @blur="blurPlus" @focus="$emit('focus', $event.target.value)" @paste="trimOnPaste" @drop="trimOnPaste" /> @@ -92,6 +92,10 @@ export default { buttonMain }, props: { + alwaysEmitFieldErrorOnEvent: { + type: Boolean, + default: false + }, type: { type: String, default: 'text' @@ -136,7 +140,7 @@ export default { default: () => {} } }, - emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'], + emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event', 'field-has-error'], setup(props) { const propsClone = { ...props }; const { modelValue } = propsClone; @@ -218,6 +222,12 @@ export default { this.$emit('textboxQuestionEvent.inputIdAssigned', this.inputId); }, methods: { + blurPlus() { + this.handleChange(this.value); + if (this.alwaysEmitFieldErrorOnEvent) { + this.$emit('field-has-error', !this.meta.valid); + } + }, trimOnPaste(evt) { evt.stopPropagation(); evt.preventDefault(); @@ -254,6 +264,8 @@ export default { if (result.valid) { this.handleChange(this.value); this.$emit('click-event'); + } else if (this.alwaysEmitFieldErrorOnEvent) { + this.$emit('field-has-error', true); } } } @@ -291,6 +303,18 @@ input::-webkit-date-and-time-value { } .textbox-question { + &.has-error { + .input-wrapper.has-search-icon { + input.form-control[type='text'] { + border-color: #db0020; + } + button[type='submit'] { + border-color: #db0020; + background-color: #fbe5e9; + background-image: url($svg-error-search-icon); + } + } + } label { color: $black; font-weight: 600; diff --git a/src/global-methods.js b/src/global-methods.js index 280f318d..7f7ad72b 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -94,10 +94,11 @@ export default { } if (error.response.status !== 404) { - global.$logger.logError(`${method}: ${endpoint}: ${error.message}`, error.response); + const errorData = { method, url, payload, error }; + global.$logger.logError(`${method}: ${endpoint}`, errorData); if (bailoutOnError && global.bailoutOnAxiosError !== undefined) { - global.bailoutOnAxiosError({ url, error }); + global.bailoutOnAxiosError(errorData); } } return reject(error.response); diff --git a/src/global-methods.spec.js b/src/global-methods.spec.js index fbb084ff..e5e82e45 100644 --- a/src/global-methods.spec.js +++ b/src/global-methods.spec.js @@ -21,7 +21,8 @@ global.$logger = { function setupMocksForHttpClient({ endpoint = null, isError = false, - additionalData = null + additionalData = null, + bailoutOnError = false }) { // Clear node module axios.mockClear(); @@ -57,7 +58,8 @@ function setupMocksForHttpClient({ return { endpoint, - logApiCall: true + logApiCall: true, + bailoutOnError }; } @@ -92,3 +94,21 @@ it('Global Methods - Call Http Client - Should Reject Promise', () => { expect(err.status).toEqual(500); }); }); + +it('Global Methods - Call Http Client - Rejected Promised - bailoutOnError: true - Calls global.bailoutOnAxiosError', () => { + // Arrange + const endpoint = 'https://mock.safelite.com'; + const httpArgs = setupMocksForHttpClient({ + endpoint, + isError: true, + bailoutOnError: true + }); + analyticsMixIn.methods.pushEventToGA = jest.fn(); + global.bailoutOnAxiosError = jest.fn(); + + // Act + globalMethods.callHttpClient(httpArgs).catch((err) => { + // Assert + expect(global.bailoutOnAxiosError).toHaveBeenCalledTimes(1); + }); +}); \ No newline at end of file diff --git a/src/helpers/order-helper.js b/src/helpers/order-helper.js index f1ab7c72..cbc5c890 100644 --- a/src/helpers/order-helper.js +++ b/src/helpers/order-helper.js @@ -4,8 +4,8 @@ import submitType from '@/constants/submit-type'; /* Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing */ -async function saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA }) { - const savedSessionInfo = await store.saveSession({ submitAfterSave, createWorkOrderNumberForPIA }); +async function saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError }) { + const savedSessionInfo = await store.saveSession({ submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError }); if (savedSessionInfo) { store.setSaveSessionInfo(savedSessionInfo.data); } @@ -16,11 +16,11 @@ async function saveSessionHelper(store, { submitAfterSave, createWorkOrderNumber This will also set Referral information in the store after saving, and then update the cookie. To force synchronous behavior pass in 'true' for shouldAwaitSaveSessionQueue */ -export async function saveSession({ shouldAwaitSaveSessionQueue = false, submitAfterSave = false, createWorkOrderNumberForPIA = false }) { +export async function saveSession({ shouldAwaitSaveSessionQueue = false, submitAfterSave = false, createWorkOrderNumberForPIA = false, bailoutOnError = false }) { const store = useMainStore(); const saveSessionPromise = store.applicationUser.saveSessionPromise - ? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA })) - : saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA }); + ? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError })) + : saveSessionHelper(store, { submitAfterSave, createWorkOrderNumberForPIA, bailoutOnError }); store.setSaveSessionPromise(saveSessionPromise); @@ -38,7 +38,8 @@ export async function submitWorkOrder({ submitType }) { store.resetSubmittedOrder(); await saveSession({ shouldAwaitSaveSessionQueue: true, - submitAfterSave: true + submitAfterSave: true, + bailoutOnError: true }); await store.createSubmittedOrder(submitType); } diff --git a/src/layouts/capability-questions/capability-questions.vue b/src/layouts/capability-questions/capability-questions.vue index c322a54b..e6dfd7bf 100644 --- a/src/layouts/capability-questions/capability-questions.vue +++ b/src/layouts/capability-questions/capability-questions.vue @@ -157,8 +157,7 @@ export default { this.navigateForward(this.partsOrQuestionsData, null); }, requestCallbackBailout() { - this.mainStore.setBailout(bailoutMessage.RequestCallback()); - this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route); + this.$router.navigateBailout(bailoutMessage.RequestCallback()); } } }; diff --git a/src/layouts/coverage-statement/__snapshots__/coverage-statement.spec.js.snap b/src/layouts/coverage-statement/__snapshots__/coverage-statement.spec.js.snap index 26f60304..40af428e 100644 --- a/src/layouts/coverage-statement/__snapshots__/coverage-statement.spec.js.snap +++ b/src/layouts/coverage-statement/__snapshots__/coverage-statement.spec.js.snap @@ -5,6 +5,7 @@ Object { "CANCEL_CLAIM_REF_NAME": "CancelClaimModal", "RECAL_MODAL_REF_NAME": "RecalModal", "baseServiceLineItems": Array [], + "isPageLoading": true, "widget": Object { "disclaimerText": "DisclaimerWidget", "explanatoryText": "ExplanatoryTextWidget", diff --git a/src/layouts/coverage-statement/afterpay-modal-banner/afterpay-modal-banner.vue b/src/layouts/coverage-statement/afterpay-modal-banner/afterpay-modal-banner.vue new file mode 100644 index 00000000..232cf329 --- /dev/null +++ b/src/layouts/coverage-statement/afterpay-modal-banner/afterpay-modal-banner.vue @@ -0,0 +1,116 @@ + + + + + \ No newline at end of file diff --git a/src/layouts/coverage-statement/coverage-statement.spec.js b/src/layouts/coverage-statement/coverage-statement.spec.js index aa9c3058..a8b77aa5 100644 --- a/src/layouts/coverage-statement/coverage-statement.spec.js +++ b/src/layouts/coverage-statement/coverage-statement.spec.js @@ -63,7 +63,8 @@ const loadingModalStub = { function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}) { const mountOptions = getMountOptions({ router: { - navigate: jest.fn() + navigate: jest.fn(), + navigateBailout: jest.fn() } }); @@ -134,7 +135,10 @@ describe('coverageStatement.vue', () => { }); test('Should render site subheader', () => { // Arrange - const { wrapper } = getMountedComponent({}); + const initialDataForTest = { + isPageLoading: false + }; + const { wrapper } = getMountedComponent({}, initialDataForTest); // Act const subheader = wrapper.find({ ref: 'siteSubHeader' }); @@ -145,7 +149,10 @@ describe('coverageStatement.vue', () => { test('Should render explanatory text', () => { // Arrange - const { wrapper } = getMountedComponent({}); + const initialDataForTest = { + isPageLoading: false + }; + const { wrapper } = getMountedComponent({}, initialDataForTest); // Act const explanatoryText = wrapper.find({ ref: 'explanatoryText' }); @@ -155,7 +162,10 @@ describe('coverageStatement.vue', () => { }); test('Should render secondary text', () => { // Arrange - const { wrapper } = getMountedComponent({}); + const initialDataForTest = { + isPageLoading: false + }; + const { wrapper } = getMountedComponent({}, initialDataForTest); // Act const secondaryText = wrapper.find({ ref: 'secondaryText' }); @@ -430,6 +440,109 @@ describe('coverageStatement.vue', () => { expect(result).toBe(expected); }); }); + describe('displayAfterpayBanner', () => { + test('returns true if ITAC', () => { + // Arrange + const mainInitialState = { + order: { + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.ITAC + } + } + }; + const { wrapper } = getMountedComponent(mainInitialState); + + // Act + const result = wrapper.vm.displayAfterpayBanner; + + // Assert + expect(result).toBeTruthy(); + }); + test('returns true if No Comp', () => { + // Arrange + const mainInitialState = { + order: { + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.NO_COMP + } + } + }; + const { wrapper } = getMountedComponent(mainInitialState); + + // Act + const result = wrapper.vm.displayAfterpayBanner; + + // Assert + expect(result).toBeTruthy(); + }); + test('returns true if deductible is > $0', () => { + // Arrange + const mainInitialState = { + order: { + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.Deductible + }, + damage: { + isRepair: false + }, + currentDeductible: { + replace: 500 + } + } + }; + const { wrapper } = getMountedComponent(mainInitialState); + + // Act + const result = wrapper.vm.displayAfterpayBanner; + + // Assert + expect(result).toBeTruthy(); + }); + test('returns false if deductible is <= $0', () => { + // Arrange + const mainInitialState = { + order: { + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.Deductible + }, + damage: { + isRepair: false + }, + currentDeductible: { + replace: 0 + } + } + }; + const { wrapper } = getMountedComponent(mainInitialState); + + // Act + const result = wrapper.vm.displayAfterpayBanner; + + // Assert + expect(result).toBeFalsy(); + }); + test('returns false if Unverified', () => { + // Arrange + const mainInitialState = { + order: { + insuranceCoverage: { + coverageStatus: coverageStatuses.UNVERIFIED, + } + } + }; + const { wrapper } = getMountedComponent(mainInitialState); + + // Act + const result = wrapper.vm.displayAfterpayBanner; + + // Assert + expect(result).toBeFalsy(); + }); + }); }); describe('methods', () => { describe('arePagePrerequisitesValid', () => { @@ -578,7 +691,7 @@ describe('coverageStatement.vue', () => { undefined ); }); - test('If Verified ITAC, selected Cancel, navigate forward w/ CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP', () => { + test('If Verified ITAC, selected Cancel, navigateBailout', () => { // Arrange const deductible = servicePrice + 1; const mainInitialState = { @@ -604,11 +717,7 @@ describe('coverageStatement.vue', () => { wrapper.vm.cancelClaim(); // Assert - expect(wrapper.vm.$router.navigate) - .toHaveBeenCalledWith( - navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP, - undefined - ); + expect(wrapper.vm.$router.navigateBailout).toHaveBeenCalled(); }); test('If No Comp and selected Safelite, navigate forward with CLICKED_FORWARD_WITH_SAFELITE scenario', () => { // Arrange @@ -641,7 +750,7 @@ describe('coverageStatement.vue', () => { undefined ); }); - test('If No comp and selected other shop, navigate forward with CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP ', () => { + test('If No comp and selected other shop, navigateBailout ', () => { // Arrange const mainInitialState = { order: { @@ -666,11 +775,7 @@ describe('coverageStatement.vue', () => { wrapper.vm.cancelClaim(); // Assert - expect(wrapper.vm.$router.navigate) - .toHaveBeenCalledWith( - navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP, - undefined - ); + expect(wrapper.vm.$router.navigateBailout).toHaveBeenCalled(); }); }); describe('openCancelClaimModal', () => { diff --git a/src/layouts/coverage-statement/coverage-statement.vue b/src/layouts/coverage-statement/coverage-statement.vue index c1c7abbd..832d9219 100644 --- a/src/layouts/coverage-statement/coverage-statement.vue +++ b/src/layouts/coverage-statement/coverage-statement.vue @@ -10,7 +10,8 @@ cmsWidgetName="SiteHeaderWidget" />
-
+
{{ formatAmountInDollars(totalServicePrice) }}
+ 0); } }, watch: { @@ -347,6 +360,9 @@ export default { } }, mounted() { + if (this.isPageLoading) { + showIssLoadingModal(true); + } setupModalLinks(this); }, methods: { @@ -370,6 +386,7 @@ export default { } showIssLoadingModal(false); + this.isPageLoading = false; }, async getPricedParts() { const { glassParts, supportingItems } = this.mainStore.order.lineItems; @@ -380,18 +397,7 @@ export default { // We only call the ITAC pricing endpoint if we are not repair or we are NoComp if (!this.isRepair || this.mainStore.isNoComp) { - const pricingResults = await useMainStore().getITACPriceOrderItems(availableLineItems) - .catch((err) => { - useMainStore().setBailout(bailoutMessage.pricingResponseError( - availableLineItems.map((li) => li.partNumber), - { - code: err.code, - message: err.message, - data: err.data - } - )); - this.navigateWithScenario(navigationScenarios.PRICING_LOOKUP_ERROR); - }); + const pricingResults = await useMainStore().getITACPriceOrderItems(availableLineItems); this.setBaseServiceLineItems(pricingResults); } }, @@ -402,8 +408,7 @@ export default { this.mainStore.updateIsSafeliteProvider(true); this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE); } else { - this.mainStore.setBailout(bailoutMessage.coverageStatementInvalidState()); - this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_INVALID_STATE); + this.$router.navigateBailout(bailoutMessage.coverageStatementInvalidState()); } }, navigateWithScenario(scenario) { @@ -457,8 +462,7 @@ export default { }, cancelClaim() { this.mainStore.updateIsSafeliteProvider(false); - this.mainStore.setBailout(bailoutMessage.RequestCallback()); - this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP); + this.$router.navigateBailout(bailoutMessage.RequestCallback()); } } }; diff --git a/src/layouts/duplicate-check/duplicate-check.spec.js b/src/layouts/duplicate-check/duplicate-check.spec.js index 10be35e2..95346c26 100644 --- a/src/layouts/duplicate-check/duplicate-check.spec.js +++ b/src/layouts/duplicate-check/duplicate-check.spec.js @@ -357,11 +357,15 @@ describe('duplicateCheck.vue', () => { useMainStore().loadSession = jest.fn().mockImplementation(() => Promise.reject(error)); // Act - await wrapper.vm.forwardButtonAction(); // Assert + expect.assertions(2); + try { + await wrapper.vm.forwardButtonAction(); + } catch (e) { + expect(e).toMatch(error); + } expect(wrapper.vm.mainStore.loadSession).toHaveBeenCalledTimes(1); - expect(wrapper.vm.$router.navigate).toHaveBeenCalledTimes(1); }); test('coverageType deductible and policy vehicles returned => CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES', async () => { // Arrange diff --git a/src/layouts/duplicate-check/duplicate-check.vue b/src/layouts/duplicate-check/duplicate-check.vue index 1cac5a1e..3c4ead5f 100644 --- a/src/layouts/duplicate-check/duplicate-check.vue +++ b/src/layouts/duplicate-check/duplicate-check.vue @@ -140,18 +140,14 @@ export default { return; } - try { - const selectedReferral = - this.mainStore.applicationUser.duplicateOrders.find((o) => o.correlationId === this.selectedAnswer); + const selectedReferral = + this.mainStore.applicationUser.duplicateOrders.find((o) => o.correlationId === this.selectedAnswer); - if (selectedReferral) { - await this.mainStore.loadSession(selectedReferral); - } - } catch (err) { - console.error(`Error on loading session from duplicate check ${err}`); - } finally { - this.navigateForward(); + if (selectedReferral) { + await this.mainStore.loadSession(selectedReferral); } + + this.navigateForward(); }, navigateForward() { this.mainStore.updateDuplicateCheckVisited(true); diff --git a/src/layouts/entry-page/entry-page.vue b/src/layouts/entry-page/entry-page.vue index e9880117..47031709 100644 --- a/src/layouts/entry-page/entry-page.vue +++ b/src/layouts/entry-page/entry-page.vue @@ -34,44 +34,51 @@ export default { computed: { }, async mounted() { - const queryStringParams = this.parseQueryParms(); - - const { isAuthorized, clientData, decryptedParams } = await this.validateClientTagOnEntry(queryStringParams); - - this.unauthorized = !isAuthorized; - if (!isAuthorized) { - // Remove the loading animation if client tag validation fails so users can see the Unauthorized Access message. - showIssLoadingModal(false); - return; - } - - this.populateISSConfigValues(clientData); - try { - // Check cookie - const issCookie = getISSCookie(); - if (issCookie !== null && issCookie.VehicleMake && issCookie.VehicleModel) { - const clientParentAccountNumber = clientData.parentAccountNumber; - const cookieParentAccountNumber = issCookie.ReferralParentAccountNumber; + const queryStringParams = this.parseQueryParms(); - if (clientParentAccountNumber === cookieParentAccountNumber) { - const savedSessionTimeStamp = new Date(issCookie.SavedSessionTimeoutDate); - const isSavedSessionTimedOut = new Date(new Date().toUTCString()) > savedSessionTimeStamp; + const { isAuthorized, clientData, decryptedParams } = await this.validateClientTagOnEntry(queryStringParams); - if (!isSavedSessionTimedOut) { - this.mainStore.issConfig.enableContinueFromCookie = true; + this.unauthorized = !isAuthorized; + if (!isAuthorized) { + // Remove the loading animation if client tag validation fails so users can see the Unauthorized Access message. + showIssLoadingModal(false); + return; + } + + this.populateISSConfigValues(clientData); + + try { + // Check cookie + const issCookie = getISSCookie(); + if (issCookie !== null && issCookie.VehicleMake && issCookie.VehicleModel) { + const clientParentAccountNumber = clientData.parentAccountNumber; + const cookieParentAccountNumber = issCookie.ReferralParentAccountNumber; + + if (clientParentAccountNumber === cookieParentAccountNumber) { + const savedSessionTimeStamp = new Date(issCookie.SavedSessionTimeoutDate); + const isSavedSessionTimedOut = new Date(new Date().toUTCString()) > savedSessionTimeStamp; + + if (!isSavedSessionTimedOut) { + this.mainStore.issConfig.enableContinueFromCookie = true; + } } + } else { + updateOrCreateISSCookie(true); } - } else { + } catch { updateOrCreateISSCookie(true); } - } catch { - updateOrCreateISSCookie(true); - } - if (clientData.parameters?.length > 0) { - const finalParams = this.combineClientParameters(clientData.parameters, { ...queryStringParams, ...decryptedParams }); - this.populateStoreItemsFromParams(finalParams); + if (clientData.parameters?.length > 0) { + const finalParams = this.combineClientParameters(clientData.parameters, { ...queryStringParams, ...decryptedParams }); + this.populateStoreItemsFromParams(finalParams); + } + } catch (e) { + global.$logger.logError('[Entry Page] Client Setup Error:', e); + this.unauthorized = true; + showIssLoadingModal(false); + return; } this.mainStore.applicationUser.coverageAttempts = 0; @@ -151,37 +158,33 @@ export default { this.mainStore.issConfig.isCoverageEnabled = data.coverageEnabled; this.mainStore.issConfig.siteType = data.siteType; - try { - if (data.clientFlags) { - const clientFlags = JSON.parse(data.clientFlags); + if (data.clientFlags) { + const clientFlags = JSON.parse(data.clientFlags); - if (clientFlags.TPAEnabled) { - this.mainStore.issConfig.enableTPAFlow = true; - } - - if (clientFlags.ClientFullName != null) { - this.mainStore.issConfig.clientFullName = clientFlags.ClientFullName; - } - - if (clientFlags.ClientDisplayName != null) { - this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName; - this.mainStore.issConfig.clientPossessiveName = toPossessive(clientFlags.ClientDisplayName); - } - - if (clientFlags.ClientPossessiveName != null) { - this.mainStore.issConfig.clientPossessiveName = clientFlags.ClientPossessiveName; - } - - if (clientFlags.ClaimRegistrationRequired) { - this.mainStore.issConfig.isClaimRegistrationRequired = true; - } - - if (clientFlags.EnableNoCompQuote) { - this.mainStore.issConfig.enableNoCompQuote = true; - } + if (clientFlags.TPAEnabled) { + this.mainStore.issConfig.enableTPAFlow = true; + } + + if (clientFlags.ClientFullName != null) { + this.mainStore.issConfig.clientFullName = clientFlags.ClientFullName; + } + + if (clientFlags.ClientDisplayName != null) { + this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName; + this.mainStore.issConfig.clientPossessiveName = toPossessive(clientFlags.ClientDisplayName); + } + + if (clientFlags.ClientPossessiveName != null) { + this.mainStore.issConfig.clientPossessiveName = clientFlags.ClientPossessiveName; + } + + if (clientFlags.ClaimRegistrationRequired) { + this.mainStore.issConfig.isClaimRegistrationRequired = true; + } + + if (clientFlags.EnableNoCompQuote) { + this.mainStore.issConfig.enableNoCompQuote = true; } - } catch (e) { - console.error(`Error parsing client flags: ${e}`); } }, combineClientParameters(configParams, queryStringParams) { diff --git a/src/layouts/molding-questions/molding-questions.vue b/src/layouts/molding-questions/molding-questions.vue index cb0f4f04..44d6af81 100644 --- a/src/layouts/molding-questions/molding-questions.vue +++ b/src/layouts/molding-questions/molding-questions.vue @@ -148,8 +148,7 @@ export default { this.navigateForward(partsOrQuestions, null); }, requestCallbackBailout() { - this.mainStore.setBailout(bailoutMessage.RequestCallback()); - this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route); + this.$router.navigateBailout(bailoutMessage.RequestCallback()); } } }; diff --git a/src/layouts/part-questions/part-questions.vue b/src/layouts/part-questions/part-questions.vue index 1230016a..9bed6345 100644 --- a/src/layouts/part-questions/part-questions.vue +++ b/src/layouts/part-questions/part-questions.vue @@ -148,8 +148,7 @@ export default { this.navigateForward(glassPartsForStore, null); }, requestCallbackBailout() { - this.mainStore.setBailout(bailoutMessage.RequestCallback()); - this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route); + this.$router.navigateBailout(bailoutMessage.RequestCallback()); } }, }; diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index c93c429e..fee96d3a 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -410,25 +410,16 @@ export default { } } if (this.paymentMethod === paymentMethods.PAY_AT_TIME_OF_SERVICE) { - try { - await submitWorkOrder({ submitType: submitType.SAFELITE }); - this.$router.navigate( - this.navigationScenarios.CLICKED_FORWARD, - this.$route - ); - } catch (error) { - useMainStore().setBailout(bailoutMessage.saveSessionError(error.data)); - this.$router.navigate( - this.navigationScenarios.SAVE_SESSION_FAILED, - this.$route, - { issPage: issPageValues.PAYMENT_METHOD } - ); - console.error(`error: response from submit work order:${error.message}`); - } + await submitWorkOrder({ submitType: submitType.SAFELITE }); + this.$router.navigate( + this.navigationScenarios.CLICKED_FORWARD, + this.$route + ); } else { await saveSession({ createWorkOrderNumberForPIA: true, - shouldAwaitSaveSessionQueue: true + shouldAwaitSaveSessionQueue: true, + bailoutOnError: true }); this.$router.navigate( diff --git a/src/layouts/payment-return/payment-return.vue b/src/layouts/payment-return/payment-return.vue index 09860d17..d4ba19a4 100644 --- a/src/layouts/payment-return/payment-return.vue +++ b/src/layouts/payment-return/payment-return.vue @@ -124,13 +124,7 @@ export default { }, async saveAndSubmitWorkOrder() { // Final work order submit after returning from pay in advance. - try { - await submitWorkOrder({ submitType: submitType.SAFELITE }); - } catch (error) { - console.error(`error: response from submit work order:${error.message}`); - this.navigateOnPayInAdvanceError(); - return; - } + await submitWorkOrder({ submitType: submitType.SAFELITE }); this.$router.navigate( this.navigationScenarios.PAY_IN_ADVANCE_SUCCESS, diff --git a/src/layouts/policy-vehicles/policy-vehicles.spec.js b/src/layouts/policy-vehicles/policy-vehicles.spec.js index a4670b55..62c6b526 100644 --- a/src/layouts/policy-vehicles/policy-vehicles.spec.js +++ b/src/layouts/policy-vehicles/policy-vehicles.spec.js @@ -252,12 +252,12 @@ describe('policy-vehicles.vue', () => { ); test( - 'Error in lookupVehicleByVin call => bailout true and navigate forward with CLICKED_FORWARD_WITH_BAILOUT scenario.', + 'Error in lookupVehicleByVin call => error thrown in forwardButtonAction.', async () => { // Arrange const { wrapper } = setupMocks({}); const lookupReturnValue = { error: true, status: 500, data: 'error' }; - wrapper.vm.lookupVehicleByVin = jest.fn().mockReturnValue(lookupReturnValue); + wrapper.vm.lookupVehicleByVin = jest.fn().mockReturnValue(Promise.reject(lookupReturnValue)); const vin = getRandomString(17, 17); await wrapper.setData({ @@ -270,21 +270,14 @@ describe('policy-vehicles.vue', () => { }); // Act - wrapper.vm.mainStore.applicationUser.pageData[issPageValues.BAILOUT_PAGE] = { - 'bailout-page': { - bailoutCode: bailoutCode.VehicleVinLookupError - } - }; - await wrapper.vm.forwardButtonAction(); // Assert - expect(wrapper.vm.mainStore.setBailout).toHaveBeenCalledWith(bailoutMessage.vehicleVinLookupError(vin, lookupReturnValue.data)); - expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( - navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT, - undefined, - {}, - {} - ); + expect.assertions(1); + try { + await wrapper.vm.forwardButtonAction(); + } catch (e) { + expect(e).toBe(lookupReturnValue); + } } ); @@ -292,7 +285,7 @@ describe('policy-vehicles.vue', () => { async () => { // Arrange const { wrapper } = setupMocks({}); - wrapper.vm.lookupVehicleByVin = jest.fn().mockReturnValue({ error: true, status: 404 }); + wrapper.vm.lookupVehicleByVin = jest.fn().mockRejectedValue({ isAxiosError: true, status: 404 }); const vin = getRandomString(17, 17); await wrapper.setData({ diff --git a/src/layouts/policy-vehicles/policy-vehicles.vue b/src/layouts/policy-vehicles/policy-vehicles.vue index c459cefd..9cedd58d 100644 --- a/src/layouts/policy-vehicles/policy-vehicles.vue +++ b/src/layouts/policy-vehicles/policy-vehicles.vue @@ -59,7 +59,6 @@ import vehicleSelectionOptions from '@/constants/vehicle-selection-options.js'; import endorsementOptions from '@/constants/endorsement-options.js'; import globalRules from '@/constants/global-rules.js'; import { useMainStore } from '@/store/index.js'; -import bailoutMessage from '@/constants/bailoutMessage'; import { deductibleForSelectedVehicle, endorsementsForSelectedVehicle, @@ -144,10 +143,6 @@ export default { }, repairWaivedForSelectedVehicle() { return repairWaivedForSelectedVehicle(this.selectedPolicyVehicle); - }, - selectedVehicle() { - const vehicle = this.mainStore.lookupVehicleByVin(this.selectedVehicleVin); - return vehicle; } }, watch: { @@ -156,15 +151,12 @@ export default { if (value === vehicleSelectionOptions.VEHICLE_NOT_LISTED) { // clear previously selected vehicle and image this.mainStore.resetVehicleState(); - } else { - // get vehicle details from selected VIN - const vehicle = await this.lookupVehicleByVin(value); + return; + } - // handle error in case vehicle info doesn't come back for selected VIN - if (vehicle?.error === true) { - this.mainStore.resetVehicleState(); - return; - } + // get vehicle details from selected VIN + try { + const vehicle = await this.lookupVehicleByVin(value); if (!vehicle?.data.canSafeliteService) { this.displayNoServiceAlert = true; return; @@ -177,6 +169,8 @@ export default { vin: value }); } + } catch (e) { + this.mainStore.resetVehicleState(); } } }, @@ -196,33 +190,16 @@ export default { ); }, async forwardButtonAction() { - if (this.selectedVehicleVin !== vehicleSelectionOptions.VEHICLE_NOT_LISTED) { + if (this.selectedVehicleVin === vehicleSelectionOptions.VEHICLE_NOT_LISTED) { + useMainStore().updateCoverageType(coverageType.NONE); + useMainStore().updateCoverageStatus(coverageStatuses.NO_COVERAGE); + this.navigateForward(); + return; + } + + const vehicle = this.policyVehicles.find((pv) => pv.vin === this.selectedVehicleVin); + try { const vehicleLookupResponse = await this.lookupVehicleByVin(this.selectedVehicleVin); - const vehicle = this.policyVehicles.find((pv) => pv.vin === this.selectedVehicleVin); - - if (vehicleLookupResponse.error) { - if (vehicleLookupResponse.status === 404) { - this.mainStore.resetVehicleState(); - useMainStore().updateVehicle({ - policyVehicleId: vehicle.id, - carId: '0', - category: '', - year: vehicle.vehicleYear || '', - make: vehicle.vehicleMake || '', - model: vehicle.vehicleModel || '', - style: vehicle.vehicleStyle || '', - vin: vehicle.vin - }); - this.policyVinFound = false; - return this.navigateForward(); - } - - this.mainStore.setBailout(bailoutMessage.vehicleVinLookupError( - vehicle.vin, - vehicleLookupResponse.data - )); - return this.navigateForward(); - } useMainStore().updateVehicle({ ...vehicleLookupResponse.data, @@ -235,21 +212,30 @@ export default { repairWaived: this.repairWaivedForSelectedVehicle, endorsements: this.endorsementsForSelectedVehicle }); - } else { - useMainStore().updateCoverageType(coverageType.NONE); - useMainStore().updateCoverageStatus(coverageStatuses.NO_COVERAGE); + this.navigateForward(); + } catch (e) { + if (e.isAxiosError && e.status === 404) { + this.mainStore.resetVehicleState(); + useMainStore().updateVehicle({ + policyVehicleId: vehicle.id, + carId: '0', + category: '', + year: vehicle.vehicleYear || '', + make: vehicle.vehicleMake || '', + model: vehicle.vehicleModel || '', + style: vehicle.vehicleStyle || '', + vin: vehicle.vin + }); + this.policyVinFound = false; + this.navigateForward(); + return; + } + + throw e; } - return this.navigateForward(); }, navigateForward() { - if (this.mainStore.isBailout) { - this.$router.navigate( - this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT, - this.$route, - {}, - {} - ); - } else if (this.selectedVehicleVin === vehicleSelectionOptions.VEHICLE_NOT_LISTED) { + if (this.selectedVehicleVin === vehicleSelectionOptions.VEHICLE_NOT_LISTED) { this.$router.navigate( this.navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE, this.$route, @@ -281,15 +267,7 @@ export default { } }, async lookupVehicleByVin(vin) { - try { - return await useMainStore().lookupVehicleByVin(vin); - } catch (responseError) { - return { - error: true, - status: responseError.status, - data: responseError.data - }; - } + return useMainStore().lookupVehicleByVin(vin); }, async addAnotherVehicle() { this.selectedVehicleVin = vehicleSelectionOptions.VEHICLE_NOT_LISTED; diff --git a/src/layouts/provider-preference/provider-preference.spec.js b/src/layouts/provider-preference/provider-preference.spec.js index 3fdcb495..0d93ed3a 100644 --- a/src/layouts/provider-preference/provider-preference.spec.js +++ b/src/layouts/provider-preference/provider-preference.spec.js @@ -20,7 +20,8 @@ jest.mock('@/helpers/cms-content-helper', () => ({ function setupMocks(mockApiResponses) { const mountOptions = getMountOptions({ router: { - navigate: jest.fn() + navigate: jest.fn(), + navigateBailout: jest.fn() }, route: 'provider-preference' }); @@ -43,7 +44,7 @@ function setupMocks(mockApiResponses) { } describe('provider-preference.vue', () => { - test('Should navigate to tpa disabled route page when TPAOption selected and TPA Flow disabled', () => { + test('Should navigateBailout when TPAOption selected and TPA Flow disabled', () => { // Arrange const { wrapper } = setupMocks(); @@ -52,7 +53,7 @@ describe('provider-preference.vue', () => { wrapper.vm.findAnotherShopClicked(); // Test - expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED, 'provider-preference'); + expect(wrapper.vm.$router.navigateBailout).toBeCalled(); }); test('Should navigate to safelite flow when navigateWithTPARecalAnswer is called with SafeliteOption', () => { diff --git a/src/layouts/provider-preference/provider-preference.vue b/src/layouts/provider-preference/provider-preference.vue index 4400174e..27e38ff8 100644 --- a/src/layouts/provider-preference/provider-preference.vue +++ b/src/layouts/provider-preference/provider-preference.vue @@ -186,9 +186,7 @@ export default { this.scheduleWithTPA(); } } else { - this.mainStore.setBailout(bailoutMessage.TPANotEnabled()); - const scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED; - this.navigateForward(scenario); + this.$router.navigateBailout(bailoutMessage.TPANotEnabled()); } }, openStateSteeringModal() { diff --git a/src/layouts/schedule-page/schedule-page.vue b/src/layouts/schedule-page/schedule-page.vue index 3a74877e..28b73548 100644 --- a/src/layouts/schedule-page/schedule-page.vue +++ b/src/layouts/schedule-page/schedule-page.vue @@ -171,20 +171,20 @@ const getAvailableDates = async ( await Promise.all(storeActionConfigs.map(async (storeAction) => { let timeSlotsResponse = null; if (storeAction.storeAction === GET_SHOP_TIME_SLOTS) { - timeSlotsResponse = await useMainStore().getShopTimeSlots( - storeAction.payload.startDate, - storeAction.payload.endDate, - storeAction.payload.shopAppointmentType, - storeAction.payload.providerNumber - ); + if (storeAction.payload.providerNumber) { + timeSlotsResponse = await useMainStore().getShopTimeSlots( + storeAction.payload.startDate, + storeAction.payload.endDate, + storeAction.payload.shopAppointmentType, + storeAction.payload.providerNumber + ); + } } else if (storeAction.payload?.zipCodeOverride) { timeSlotsResponse = await useMainStore().getMobileTimeSlots( storeAction.payload.startDate, storeAction.payload.endDate, storeAction.payload.zipCodeOverride - ).catch(() => { - console.warn('Error fetching mobile time slots...'); - }); + ); } if (!timeSlotsResponse || !timeSlotsResponse.data) { @@ -204,6 +204,9 @@ const getAvailableDates = async ( // sort days chronologically timeSlotsResponsesData.days.sort(compareDayStrings); return timeSlotsResponsesData; + }) + .catch(() => { + return timeSlotsResponsesData; }); }; diff --git a/src/layouts/schedule-page/service-location/service-zip-question/service-zip-question.vue b/src/layouts/schedule-page/service-location/service-zip-question/service-zip-question.vue index e9671f9f..4f401049 100644 --- a/src/layouts/schedule-page/service-location/service-zip-question/service-zip-question.vue +++ b/src/layouts/schedule-page/service-location/service-zip-question/service-zip-question.vue @@ -2,6 +2,7 @@ + @clickEvent="updateModelValue" + @fieldHasError="handleFieldError" />