From 935e21c9934b646bdd34c51305d7161678bba14f Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Wed, 8 Apr 2026 14:55:48 -0400 Subject: [PATCH 01/22] schedule page analytics --- src/layouts/schedule-page/schedule-page.vue | 30 +++++++++++++++++ .../service-location/service-location.vue | 1 - src/mixins/analytics-mixin.js | 32 +++++++++++++++---- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/layouts/schedule-page/schedule-page.vue b/src/layouts/schedule-page/schedule-page.vue index 904329f3..61d38ad4 100644 --- a/src/layouts/schedule-page/schedule-page.vue +++ b/src/layouts/schedule-page/schedule-page.vue @@ -514,6 +514,8 @@ export default { jobMinMinutes: emittedValue.estimatedServiceMinutesMin.toString(), }; + this.selectedAppointmentType = AppointmentTypeStrings.MOBILE; + this.pushServiceTypeEvent(); this.mainStore.saveSchedule(timeSlotToUse); showIssLoadingModal(true); this.$router.navigate( @@ -813,6 +815,7 @@ export default { if (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE || this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) { + this.pushMobileFirstDateEvent(initialData); this.mobileDatesData = initialData; if (this.selectedProvider?.providerNumber) { @@ -833,9 +836,11 @@ export default { daysFromStart: endDateObject.daysFromStart || null })); + this.pushInshopFirstDateEvent(inShopData); this.inShopDatesData = inShopData; } } else { + this.pushInshopFirstDateEvent(initialData); this.inShopDatesData = initialData; this.mobileDatesData = []; } @@ -940,6 +945,30 @@ export default { }); } }, + pushInshopFirstDateEvent(inshopData) { + let dateDiffString = ''; + if (inshopData?.initialShopTimeSlotsResponse?.days?.length > 0) { + const todayDateString = convertDateToDateString(new Date()); + dateDiffString = calcDaysBetweenDates(todayDateString, inshopData.initialShopTimeSlotsResponse.days[0].date).toString(); + } + this.pushEventToGA('appointment', 'availability_inshop', `days_out_${dateDiffString}`, true, null, null); + }, + pushMobileFirstDateEvent(mobileData) { + let dateDiffString = ''; + if (mobileData?.initialShopTimeSlotsResponse?.days?.length > 0) { + const todayDateString = convertDateToDateString(new Date()); + dateDiffString = calcDaysBetweenDates(todayDateString, mobileData.initialShopTimeSlotsResponse.days[0].date).toString(); + } + this.pushEventToGA('appointment', 'availability_mobile', `days_out_${dateDiffString}`, true, null, null); + }, + pushServiceTypeEvent() { + if (this.selectedAppointmentType) { + const gaScheduleType = { + ['service_type']: this.selectedAppointmentType.toLowerCase() + }; + this.pushGenericObjectToGA(gaScheduleType, 'service_type', true); + } + }, async refreshDatePicker() { this.resetLocalFlags(); this.isDatePickerRefreshing = true; @@ -977,6 +1006,7 @@ export default { } await this.$refs.serviceLocation.forwardButtonAction(appointmentTypeToUse); + this.pushServiceTypeEvent(); this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot); this.$router.navigate( this.navigationScenarios.CLICKED_FORWARD, diff --git a/src/layouts/schedule-page/service-location/service-location.vue b/src/layouts/schedule-page/service-location/service-location.vue index f9787d16..9b23059e 100644 --- a/src/layouts/schedule-page/service-location/service-location.vue +++ b/src/layouts/schedule-page/service-location/service-location.vue @@ -571,7 +571,6 @@ export default { await mobileFeePromise.then((result) => { this.mobileFeePart = result ?? null; }); - } } } diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index cfe48753..f7bcac5a 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -27,6 +27,10 @@ import coverageType from '@/constants/coverage-type'; import issPageValues from '@/router/router-constants/issPage-values'; import { useMainStore } from '@/store'; +function keyExistsInDataLayer(keyName) { + return window.dataLayer.some(obj => Object.prototype.hasOwnProperty.call(obj, keyName)); +} + function pushToDataLayerIfDefined(data) { if (window.dataLayer !== undefined) { window.dataLayer.push(data); @@ -91,7 +95,6 @@ export default { store.logCustomEvent(payload); }, - pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null, value = undefined) { const currentPageName = this.getPageNameByQueryString(); const labelToLog = getValueToLog(label, valueToLogType); @@ -111,6 +114,13 @@ export default { this.logCustomEvent(category, action, labelToLog, value); } }, + pushGenericObjectToGA(object, keyName = null, skipExistsCheck = false) { + if (keyName && keyExistsInDataLayer(keyName) && !skipExistsCheck) { + console.log(`Key ${keyName} already exists in dataLayer. Skipping push.`); + } else { + pushToDataLayerIfDefined(object); + } + }, pushPageViewToGA() { const currentPageName = this.getPageNameByQueryString(); @@ -126,9 +136,17 @@ export default { }, pushValueToGA() { - pushToDataLayerIfDefined({ - siteType: useMainStore().issConfig.siteType - }); + const gaSiteType = { + ['siteType']: useMainStore().issConfig.siteType + }; + this.pushGenericObjectToGA(gaSiteType, 'siteType'); + + const gaServiceType = { + ['service_type']: useMainStore().order?.serviceLocation?.appointmentType?.toLowerCase() + }; + if (gaServiceType && gaServiceType['service_type']) { + this.pushGenericObjectToGA(gaServiceType, 'service_type'); + } }, pushExperimentsToDataLayer() { @@ -229,13 +247,13 @@ export default { const applicationUser = store.applicationUser; // NOTE: ISS does not support early bird times. - const isEarlyBird = false; /*order?.lineItems?.supportingItems?.find( + const isEarlyBird = false; /* order?.lineItems?.supportingItems?.find( (lineItem) => lineItem.partType == partTypeStrings.EARLY_BIRD ); */ // NOTE: No promo codes on ISS at the moment. - const promoCodes = ""; /*Array.isArray(order?.lineItems?.promos) + const promoCodes = ""; /* Array.isArray(order?.lineItems?.promos) ? order?.lineItems?.promos .map((item) => item.promoCode) .filter((code) => code) @@ -280,7 +298,7 @@ export default { } } */ - var parentAccountNumber = (order?.parentAccountNumber ?? ( issConfig.parentAccountNumber ?? 0)).toString(); + var parentAccountNumber = (order?.parentAccountNumber ?? (issConfig.parentAccountNumber ?? 0)).toString(); var sessionData = {}; sessionData.currentPage = currentPageName; From ed179010a73977babc197f6abbcdf555917e9ced Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Wed, 8 Apr 2026 15:01:18 -0400 Subject: [PATCH 02/22] test update --- src/layouts/schedule-page/schedule-page.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/layouts/schedule-page/schedule-page.spec.js b/src/layouts/schedule-page/schedule-page.spec.js index fefde032..411f7e4f 100644 --- a/src/layouts/schedule-page/schedule-page.spec.js +++ b/src/layouts/schedule-page/schedule-page.spec.js @@ -38,7 +38,8 @@ const mockMixin = { days: [] } }; - }) + }), + pushGenericObjectToGA: jest.fn() } }; From 221c8ef2ce12491e81d80ef1a1b9c78011c73ab6 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Thu, 9 Apr 2026 11:21:45 -0400 Subject: [PATCH 03/22] INSR-2124 | Remove conditional env logic There is no longer a mismatch between environments that must be rectified on the front-end --- src/constants/endpoints.js | 33 ++++++++++++-------------------- src/store/index.js | 39 -------------------------------------- 2 files changed, 12 insertions(+), 60 deletions(-) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index d18d073e..0f75c323 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -1,5 +1,3 @@ -import applicationConfig from './application-config'; - const ACCOUNT_BASE_URL = '/account/api/v1/account'; const ANALYTICS_BASE_URL = '/analytics/api/v1/analytics'; const CLIENT_AUTH_BASE_URL = '/clientauth/api/v1/clientauth'; @@ -9,18 +7,11 @@ const EXPERIMENTS_BASE_URL = '/experiments/api/v1/experiments'; const LOCATION_BASE_URL = '/location/api/v1/location'; const ORDER_BASE_URL = '/order/api/v1/order'; const PARTS_BASE_URL = '/parts/api/v2/parts'; -const PARTS_V1_BASE_URL = '/parts/api/v1/parts'; const PRICE_BASE_URL = '/price/api/v1/price'; const SCHEDULE_BASE_URL = '/schedule/api/v1/schedule'; const VEHICLE_BASE_URL = '/vehicle/api/v1/vehicle'; const PAYMENT_BASE_URL = '/payment/api/v1/payment'; -const isLocalOrDev = (() => { - const currentEnv = (applicationConfig?.CURRENT_ENVIRONMENT || '').toLowerCase(); - return currentEnv === 'localhost' || currentEnv === 'dev'; -})(); -const PARTS_EFFECTIVE_BASE_URL = isLocalOrDev ? PARTS_BASE_URL : PARTS_V1_BASE_URL; - const endpoints = Object.freeze({ GetRouteInfo: { url: (applicationAbbreviation) => `${CONTENT_BASE_URL}/${applicationAbbreviation}/RouteInfo`, @@ -51,7 +42,7 @@ const endpoints = Object.freeze({ method: 'POST' }, GetMobilePremiumFee: { - url: `${PARTS_EFFECTIVE_BASE_URL}/mobile-premium-fee`, + url: `${PARTS_BASE_URL}/mobile-premium-fee`, method: 'GET' }, GetVehicleYears: { @@ -71,15 +62,15 @@ const endpoints = Object.freeze({ method: 'GET' }, GetDamageOptions: { - url: `${PARTS_EFFECTIVE_BASE_URL}/damage-options`, + url: `${PARTS_BASE_URL}/damage-options`, method: 'GET' }, GetPartsOrQuestions: { - url: `${PARTS_EFFECTIVE_BASE_URL}/parts-or-questions`, + url: `${PARTS_BASE_URL}/parts-or-questions`, method: 'POST' }, GetParts: { - url: `${PARTS_EFFECTIVE_BASE_URL}/parts`, + url: `${PARTS_BASE_URL}/parts`, method: 'POST' }, GetITACPriceOrderItems: { @@ -108,19 +99,19 @@ const endpoints = Object.freeze({ method: 'GET' }, GetCapabilityQuestions: { - url: `${PARTS_EFFECTIVE_BASE_URL}/capability-questions`, + url: `${PARTS_BASE_URL}/capability-questions`, method: 'GET' }, GetPartFromCapabilityAnswer: { - url: `${PARTS_EFFECTIVE_BASE_URL}/part-from-capability-answer`, + url: `${PARTS_BASE_URL}/part-from-capability-answer`, method: 'POST' }, GetWipers: { - url: `${PARTS_EFFECTIVE_BASE_URL}/wipers`, + url: `${PARTS_BASE_URL}/wipers`, method: 'GET' }, GetRainDefense: { - url: `${PARTS_EFFECTIVE_BASE_URL}/rain-repel`, + url: `${PARTS_BASE_URL}/rain-repel`, method: 'GET' }, GetRecalParts: { @@ -132,19 +123,19 @@ const endpoints = Object.freeze({ zipCode, applicationName, referralSequenceNumber - ) => `${PARTS_EFFECTIVE_BASE_URL}/recal-parts/${carId}/${partNumber}/${recalibrationType}/${parentAccountNumber}/${zipCode}/${applicationName}/${referralSequenceNumber}`, + ) => `${PARTS_BASE_URL}/recal-parts/${carId}/${partNumber}/${recalibrationType}/${parentAccountNumber}/${zipCode}/${applicationName}/${referralSequenceNumber}`, method: 'GET' }, GetGlassFees: { - url: `${PARTS_EFFECTIVE_BASE_URL}/glass-fees`, + url: `${PARTS_BASE_URL}/glass-fees`, method: 'GET' }, GetSupportingItems: { - url: `${PARTS_EFFECTIVE_BASE_URL}/supporting-items`, + url: `${PARTS_BASE_URL}/supporting-items`, method: 'POST' }, GetMobileFeePart: { - url: `${PARTS_EFFECTIVE_BASE_URL}/mobile-fee`, + url: `${PARTS_BASE_URL}/mobile-fee`, method: 'GET' }, GetServiceabilityDetails: { diff --git a/src/store/index.js b/src/store/index.js index 2aa8248a..930a0ecc 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1348,45 +1348,6 @@ export const useMainStore = defineStore({ }); }, getServiceabilityDetails(serviceZipCode) { - console.log('BEEP BOOP'); - console.log('--ENVIRONMENT DETECTOR ROBOT ENGAGED--'); - const currentEnvironment = applicationConfig.CURRENT_ENVIRONMENT; - console.log('I HAVE DETERMINED THAT THE CURRENT ENVIRONMENT IS:', currentEnvironment); - console.log('---'); - console.log('---'); - if (currentEnvironment === 'Localhost' || currentEnvironment === 'Dev') { - console.log('NEW SERVICEABILITY DETAILS METHOD ACTIVATED'); - return this.getServiceabilityDetailsNewMethod(serviceZipCode); - } else { - console.log('OLD SERVICEABILITY DETAILS METHOD ACTIVATED'); - return this.getServiceabilityDetailsOldMethod(serviceZipCode); - } - }, - getServiceabilityDetailsOldMethod(serviceZipCode) { - const { vehicle, damage, parentAccountNumber, referralSequenceNumber, lineItems } = this.order; - const { carId } = vehicle; - const glassArray = convertGlassPieceNamingForApi(damage.glassToReplace); - const lineItemParts = [...(lineItems.glassParts || []), ...(lineItems.supportingItems || [])].map((part) => ({ - partNumber: part.partNumber, - recalibrationType: part.recalibrationType - })); - - const params = buildURLSearchParams({ - applicationName: applicationConfig.APPLICATION_NAME, - parentAccountNumber, - referralSequenceNumber, - zip: serviceZipCode, - carId, - glassPieces: glassArray, - lineItems: lineItemParts - }); - - return globalMethods.callHttpClient({ - method: endpoints.GetServiceabilityDetails.method, - endpoint: `${endpoints.GetServiceabilityDetails.url}?${params.toString()}` - }); - }, - getServiceabilityDetailsNewMethod(serviceZipCode) { const { damage, lineItems, parentAccountNumber, vehicle } = this.order; const { carId } = vehicle; const flattenedGlassParts = getLineItemsFlattened(lineItems.glassParts); From 4b1039f06a384640379e031d2b6bc1d95af5edcc Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Thu, 9 Apr 2026 13:51:46 -0400 Subject: [PATCH 04/22] remove dupecheck --- src/layouts/schedule-page/schedule-page.vue | 2 +- src/mixins/analytics-mixin.js | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/layouts/schedule-page/schedule-page.vue b/src/layouts/schedule-page/schedule-page.vue index 61d38ad4..b40ad1d1 100644 --- a/src/layouts/schedule-page/schedule-page.vue +++ b/src/layouts/schedule-page/schedule-page.vue @@ -966,7 +966,7 @@ export default { const gaScheduleType = { ['service_type']: this.selectedAppointmentType.toLowerCase() }; - this.pushGenericObjectToGA(gaScheduleType, 'service_type', true); + this.pushGenericObjectToGA(gaScheduleType); } }, async refreshDatePicker() { diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index f7bcac5a..b619b6b4 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -27,10 +27,6 @@ import coverageType from '@/constants/coverage-type'; import issPageValues from '@/router/router-constants/issPage-values'; import { useMainStore } from '@/store'; -function keyExistsInDataLayer(keyName) { - return window.dataLayer.some(obj => Object.prototype.hasOwnProperty.call(obj, keyName)); -} - function pushToDataLayerIfDefined(data) { if (window.dataLayer !== undefined) { window.dataLayer.push(data); @@ -114,12 +110,8 @@ export default { this.logCustomEvent(category, action, labelToLog, value); } }, - pushGenericObjectToGA(object, keyName = null, skipExistsCheck = false) { - if (keyName && keyExistsInDataLayer(keyName) && !skipExistsCheck) { - console.log(`Key ${keyName} already exists in dataLayer. Skipping push.`); - } else { - pushToDataLayerIfDefined(object); - } + pushGenericObjectToGA(object) { + pushToDataLayerIfDefined(object); }, pushPageViewToGA() { @@ -139,13 +131,13 @@ export default { const gaSiteType = { ['siteType']: useMainStore().issConfig.siteType }; - this.pushGenericObjectToGA(gaSiteType, 'siteType'); + this.pushGenericObjectToGA(gaSiteType); const gaServiceType = { ['service_type']: useMainStore().order?.serviceLocation?.appointmentType?.toLowerCase() }; if (gaServiceType && gaServiceType['service_type']) { - this.pushGenericObjectToGA(gaServiceType, 'service_type'); + this.pushGenericObjectToGA(gaServiceType); } }, From db69745484b7ed91d16edf756b5052a428735910 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Thu, 9 Apr 2026 16:10:37 -0400 Subject: [PATCH 05/22] INSR-9064: Remove repair supplies fee, account for repair in TPA search --- src/constants/part-number-strings.js | 3 ++- src/store/index.js | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/constants/part-number-strings.js b/src/constants/part-number-strings.js index 89f4a97f..f6627aab 100644 --- a/src/constants/part-number-strings.js +++ b/src/constants/part-number-strings.js @@ -2,7 +2,8 @@ const partNumberStrings = Object.freeze({ RECYCLE_FEE: 'RECYCLE FEE', LABOR2: 'LABOR2', RECAL_MOBILE: 'RECAL MOBILE', - RECAL_MOBILEDUAL: 'RECAL MOBILEDUAL' + RECAL_MOBILEDUAL: 'RECAL MOBILEDUAL', + REPAIR_SUPPLIES: 'REPAIR-SUPPLIES' }); export default partNumberStrings; diff --git a/src/store/index.js b/src/store/index.js index 2aa8248a..bb4ae0ed 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1034,7 +1034,7 @@ export const useMainStore = defineStore({ const damageType = this.damage.isRepair ? 'Repair' : 'Install'; const parts = getLineItemsFlattened(this.order.lineItems.glassParts); - const partNumbers = parts.map((part) => part.partNumber).join(','); + const partNumbers = this.damage.isRepair ? 'WSREPAIR' : parts.map((part) => part.partNumber).join(','); let url = `${endpoints.GetTpaAndSafeliteProviders.url}/${zipCode}?accountNumber=${parentAccountNumber}&carId=${carId}&damageType=${damageType}&partNumbers=${partNumbers}`; if (provderNameSearchString) { @@ -1985,8 +1985,13 @@ export const useMainStore = defineStore({ // Process Recycle Fee this.updateRecycleFee(partsData.find((rf) => rf.partNumber === partNumberStrings.RECYCLE_FEE)); - // Remove RecycleFee from supporting items since it's already been added to feeItems - this.order.lineItems.supportingItems = partsData.filter((i) => i.partNumber !== partNumberStrings.RECYCLE_FEE); + // Remove Recycle Fee from supporting items since it's already been added to feeItems + let supportingItems = partsData.filter((i) => i.partNumber !== partNumberStrings.RECYCLE_FEE); + + // Remove Repair Supplies Fee, which is hidden for ISS except in scenarios not yet implemented + supportingItems = supportingItems.filter((i) => i.partNumber !== partNumberStrings.REPAIR_SUPPLIES); + + this.order.lineItems.supportingItems = supportingItems; }, updateVaps(partsData) { From fb297261f9f29afaa950e79e793ea68f14ff5c4b Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Thu, 9 Apr 2026 16:30:40 -0400 Subject: [PATCH 06/22] INSR-9225: Update vin-lookup events to match what actually fires in Heritage --- src/layouts/vin-lookup/vin-lookup.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index af598359..9057e864 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -179,7 +179,7 @@ export default { if (this.needToLookupVehicle) { try { const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin); - this.pushEventToGA("VIN", "TEXT", "SUCCESS", true, null, '1'); + this.pushEventToGA("policy_vehicle", "VIN_lookup", "SUCCESS", true, null, '1'); if (!vehicleLookupResponse.data.canSafeliteService) { this.mainStore.setBailout(bailoutMessage.HeavyTruckVehicle(vehicleLookupResponse.data.carId)); @@ -196,6 +196,7 @@ export default { } ); } catch (e) { + this.pushEventToGA("policy_vehicle", "VIN_lookup", "FAIL", true, null, '0'); if (e.status === 404) { this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_FOUND; this.resetVehicleFromLookup(); @@ -203,7 +204,6 @@ export default { // because the form itself actually passes its client-side validation. // SSR-189 Scenario #4. this.$refs.siteFooter.enableForwardAction(); - this.pushEventToGA("VIN", "TEXT", "NO VEHICLE FOUND", true, null, '0'); showIssLoadingModal(false); return; } From ca6ec847d037eab9dbc98d7f46eeb19733a23d77 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Thu, 9 Apr 2026 16:46:27 -0400 Subject: [PATCH 07/22] Fix tests, correct part number string for repair supplies --- src/constants/part-number-strings.js | 3 ++- src/store/index.js | 4 ++-- src/store/store.spec.js | 25 ++++++++++++++++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/constants/part-number-strings.js b/src/constants/part-number-strings.js index f6627aab..55905408 100644 --- a/src/constants/part-number-strings.js +++ b/src/constants/part-number-strings.js @@ -3,7 +3,8 @@ const partNumberStrings = Object.freeze({ LABOR2: 'LABOR2', RECAL_MOBILE: 'RECAL MOBILE', RECAL_MOBILEDUAL: 'RECAL MOBILEDUAL', - REPAIR_SUPPLIES: 'REPAIR-SUPPLIES' + REPAIR: 'WSREPAIR', + SUPPLIES_REPAIR: 'SUPPLIES-REPAIR' }); export default partNumberStrings; diff --git a/src/store/index.js b/src/store/index.js index bb4ae0ed..db5894c6 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1034,7 +1034,7 @@ export const useMainStore = defineStore({ const damageType = this.damage.isRepair ? 'Repair' : 'Install'; const parts = getLineItemsFlattened(this.order.lineItems.glassParts); - const partNumbers = this.damage.isRepair ? 'WSREPAIR' : parts.map((part) => part.partNumber).join(','); + const partNumbers = this.damage.isRepair ? partNumberStrings.REPAIR : parts.map((part) => part.partNumber).join(','); let url = `${endpoints.GetTpaAndSafeliteProviders.url}/${zipCode}?accountNumber=${parentAccountNumber}&carId=${carId}&damageType=${damageType}&partNumbers=${partNumbers}`; if (provderNameSearchString) { @@ -1989,7 +1989,7 @@ export const useMainStore = defineStore({ let supportingItems = partsData.filter((i) => i.partNumber !== partNumberStrings.RECYCLE_FEE); // Remove Repair Supplies Fee, which is hidden for ISS except in scenarios not yet implemented - supportingItems = supportingItems.filter((i) => i.partNumber !== partNumberStrings.REPAIR_SUPPLIES); + supportingItems = supportingItems.filter((i) => i.partNumber !== partNumberStrings.SUPPLIES_REPAIR); this.order.lineItems.supportingItems = supportingItems; }, diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 38a52c16..d6a29e43 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -15,6 +15,7 @@ import { AppointmentTypeStrings } from '@/constants/schedule-constants'; import coverageType from '@/constants/coverage-type'; import { getEnumName } from '@/helpers/unit-test-helper'; import { strictEqual } from 'assert'; +import partNumberStrings from '@/constants/part-number-strings'; describe('Store', () => { let store; @@ -1978,7 +1979,7 @@ describe('Store', () => { const carId = getRandomString(10, 14); const parentAccountNumber = getRandomString(6, 6); - const partNumber = getRandomString(6, 6); + const partNumber = isRepair ? partNumberStrings.REPAIR : getRandomString(6, 6); store.order.parentAccountNumber = parentAccountNumber; store.order.damage.isRepair = isRepair; store.order.vehicle.carId = carId; @@ -2512,6 +2513,28 @@ describe('Store', () => { ]) ); }); + it('updateSupportingItems removes Repair Supplies Fee from supporting items in store', () => { + // Arrange + const partsData = [ + { + partNumber: partNumberStrings.SUPPLIES_REPAIR, + partType: 'REPAIR FEE' + } + ]; + + // Act + store.updateSupportingItems(partsData); + + // Assert + expect(store.order.lineItems.supportingItems).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + partNumber: partNumberStrings.SUPPLIES_REPAIR, + partType: 'REPAIR FEE' + }) + ]) + ); + }); }); describe('updateGlassFees method', () => { it('updateGlassFees does not add Labor2 fee to feeItems', () => { From 0a631a6dc46dc3f32613a686fcc8bb7e87fc6fbc Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Fri, 10 Apr 2026 11:41:33 -0400 Subject: [PATCH 08/22] Initial updates. --- src/mixins/analytics-mixin.js | 251 ++++++++++++++++++++++++++++++++++ src/router/index.js | 3 + 2 files changed, 254 insertions(+) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index b619b6b4..ec213fbc 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -24,6 +24,7 @@ import { getCartTotal, getSubtotal } from '@/helpers/cart-helper'; import { getRecalPartNumbers } from "@/helpers/recal-helper"; import coverageStatuses from '@/constants/coverage-statuses'; import coverageType from '@/constants/coverage-type'; +import { AppointmentTypeStrings } from '@/constants/schedule-constants'; import issPageValues from '@/router/router-constants/issPage-values'; import { useMainStore } from '@/store'; @@ -140,6 +141,256 @@ export default { this.pushGenericObjectToGA(gaServiceType); } }, + + pushOrderToDataLayer() { + // helper check for if an object is defined (but maybe falsey) + const isDefined = (x) => x !== null && x !== undefined; + const store = useMainStore(); + + // Get correct order object + const hasSubmittedOrder = store.hasSubmittedOrder(); + const submittedOrder = store.getSubmittedOrder(); + const order = hasSubmittedOrder ? submittedOrder : store.order; + + // Begin assembling payload for data layer + const payload = {}; + + // Service Zip + if ( + order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE && + isDefined(order.serviceLocation.zipCode) + ) { + payload.serviceZipCode = order.serviceLocation.zipCode; + } else if ( + isDefined(order.serviceLocation.appointmentType) && + order.serviceLocation.appointmentType !== AppointmentTypeStrings.MOBILE && + isDefined(order.serviceLocation.provider.address.zipCode) + ) { + payload.serviceZipCode = order.serviceLocation.provider.address.zipCode; + } else { + payload.serviceZipCode = ""; + } + + // Damage Type + if (isDefined(order.damage.isRepair)) { + payload.damageType = order.damage.isRepair ? "repair" : "replace"; + } else { + payload.damageType = ""; + } + + // Account Type + if (isDefined(order.payment.isInsurance)) { + payload.accountType = order.payment.isInsurance ? "insurance" : "cash"; + } else { + payload.accountType = ""; + } + + // Promo Codes + const promos = order.lineItems.promos ?? []; + if (promos.length === 0) { + payload.promoCodes = ""; + } else { + const promoCodes = promos.map((promo) => promo.promoCode); + const promoString = promoCodes.reduce((prev, next) => `${prev},${next}`); + payload.promoCodes = promoString; + } + + // Vehicle info + if (isDefined(order.vehicle.year)) { + // Ensure cast to string. + payload.vehicleYear = `${order.vehicle.year}`; + } else { + payload.vehicleYear = ""; + } + + if (isDefined(order.vehicle.make)) { + payload.vehicleMake = order.vehicle.make; + } else { + payload.vehicleMake = ""; + } + + if (isDefined(order.vehicle.model)) { + payload.vehicleModel = order.vehicle.model; + } else { + payload.vehicleModel = ""; + } + + if (isDefined(order.vehicle.style)) { + payload.vehicleStyle = order.vehicle.style; + } else { + payload.vehicleStyle = ""; + } + + // Glass pieces + const glass = order.damage.glassToReplace ?? []; + if (glass.length === 0) { + payload.glassToReplace = ""; + } else { + const glassNames = glass.map((g) => `${g.glassLocation}/${g.glassName}`); + const glassString = glassNames.reduce((prev, next) => `${prev},${next}`); + + payload.glassToReplace = glassString; + } + + //EON + if (order.eon) { + payload.eon = order.eon; + } else { + payload.eon = ""; + } + + // Work Order Id + if (order.workOrderId) { + const parsedId = parseInt(order.workOrderId); + if (!isNaN(parsedId)) { + payload.workOrderId = parsedId; + } else { + payload.workOrderId = ""; + } + } else { + payload.workOrderId = ""; + } + + // Provider Ctu + if (isDefined(order.serviceLocation.zipCodeCtu)) { + payload.providerCtu = order.serviceLocation.zipCodeCtu; + } else { + payload.providerCtu = ""; + } + + // Work Order Number + if (order.workOrderNumber) { + payload.orderNumber = order.workOrderNumber; + } else { + payload.orderNumber = ""; + } + + // Pricing + // Only fire for completed orders? + + const lineItems = order.lineItems ?? {}; + const combinedLineItems = [ + ...(lineItems.glassParts ?? []), + ...(lineItems.supportingItems ?? []), + ...(lineItems.vaps ?? []), + ...(lineItems.promos ?? []), + ]; + + const isPricingAvailable = + combinedLineItems.length > 0 && + combinedLineItems.every( + (lineItem) => + isDefined(lineItem.kitPrice) && + isDefined(lineItem.laborAmount) && + isDefined(lineItem.sellingPrice) + ); + const isTaxAvailable = + isPricingAvailable && + combinedLineItems.every((lineItem) => isDefined(lineItem.salesTax)); + + //unverified (in scenarios we don’t display the price) + if ( + order.payment.isInsurance && + isDefined(order.payment.insuranceCoverage.isVerified) && + !order.payment.insuranceCoverage.isVerified + ) { + payload.priceSubTotal = ""; + } + //deductible (in scenarios we don’t display the price) + // TODO: Need to redo this for ISS 2.0 + else if ( + order.payment.isInsurance && + isDefined(order.payment.insuranceCoverage.isVerified) && + order.payment.insuranceCoverage.isVerified && + isDefined(order.policy.currentDeductible) && + order.policy.currentDeductible >= 0 && + !order.policy.isItac && + !order.policy.isNoComp + ) { + payload.priceSubTotal = ""; + } else if (isPricingAvailable) { + const subtotal = getSubtotal(order).toFixed(2); + + payload.priceSubTotal = parseFloat(subtotal); + } else { + payload.priceSubTotal = ""; + } + + // Cash Quote or Cash Price Sub Total + payload.cashPriceSubTotal = order?.cashPriceSubTotal ?? ""; + + //unverified (in scenarios we don’t display the price) + if ( + order.payment.isInsurance && + isDefined(order.payment.insuranceCoverage.isVerified) && + !order.payment.insuranceCoverage.isVerified + ) { + payload.priceTotal = ""; + } + //deductible (in scenarios we don’t display the price) + else if ( + order.payment.isInsurance && + isDefined(order.payment.insuranceCoverage.isVerified) && + order.payment.insuranceCoverage.isVerified && + isDefined(order.policy.currentDeductible) && + order.policy.currentDeductible >= 0 && + !order.policy.isItac && + !order.policy.isNoComp + ) { + payload.priceTotal = ""; + } else if (isTaxAvailable) { + const total = getCartTotal(order).toFixed(2); + + payload.priceTotal = parseFloat(total); + } else { + payload.priceTotal = ""; + } + + // Recalibration + // TODO: Find equivalent ISS methods. + if (hasSubmittedOrder) { + payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedState; + } else { + payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder; + } + + // Appointment Type + if (isDefined(order.serviceLocation.appointmentType)) { + payload.appointmentType = order.serviceLocation.appointmentType; + } else { + payload.appointmentType = ""; + } + //Insurance + // TODO: Find equivalent ISS methods. + if (order.payment.isInsurance) { + payload.isInsuranceVerified = order.payment.insuranceCoverage.isVerified ?? ""; + payload.insuranceCompanyName = order.policy.insuranceCompanyName ?? ""; + if (!order.payment.insuranceCoverage.isVerified) { + payload.isInsuranceItac = ""; + payload.isInsuranceNoComp = ""; + } else { + payload.isInsuranceItac = order.policy.isItac ?? ""; + payload.isInsuranceNoComp = order.policy.isNoComp ?? ""; + } + if ( + order.policy.isItac || + order.policy.isNoComp || + !order.payment.insuranceCoverage.isVerified + ) { + payload.insuranceDeductible = ""; + } else { + payload.insuranceDeductible = order.policy.currentDeductible ?? ""; + } + } else { + payload.isInsuranceVerified = ""; + payload.insuranceDeductible = ""; + payload.isInsuranceItac = ""; + payload.isInsuranceNoComp = ""; + payload.insuranceCompanyName = ""; + } + + pushToDataLayerIfDefined(payload); + }, pushExperimentsToDataLayer() { const { experiments } = useMainStore().applicationUser; diff --git a/src/router/index.js b/src/router/index.js index eeca615e..4fd5306e 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -192,6 +192,9 @@ router.afterEach(async (to, from) => { // Push values to GA analyticsMixin.methods.pushValueToGA(); + + // Push current order status to Data Layer + analyticsMixin.methods.pushOrderToDataLayer(); } }); From 4ddd961d18cd5454dad2a4f23ee56d14a77336e9 Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Fri, 10 Apr 2026 12:12:09 -0400 Subject: [PATCH 09/22] Updates on some of the logic. --- src/mixins/analytics-mixin.js | 87 +++++++++++++---------------------- 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index ec213fbc..25e06531 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -178,12 +178,8 @@ export default { payload.damageType = ""; } - // Account Type - if (isDefined(order.payment.isInsurance)) { - payload.accountType = order.payment.isInsurance ? "insurance" : "cash"; - } else { - payload.accountType = ""; - } + // Account Type - always insurance for ISS + payload.accountType = "insurance"; // Promo Codes const promos = order.lineItems.promos ?? []; @@ -289,23 +285,16 @@ export default { combinedLineItems.every((lineItem) => isDefined(lineItem.salesTax)); //unverified (in scenarios we don’t display the price) - if ( - order.payment.isInsurance && - isDefined(order.payment.insuranceCoverage.isVerified) && - !order.payment.insuranceCoverage.isVerified - ) { + if (!store.isVerified) { payload.priceSubTotal = ""; } //deductible (in scenarios we don’t display the price) // TODO: Need to redo this for ISS 2.0 else if ( - order.payment.isInsurance && - isDefined(order.payment.insuranceCoverage.isVerified) && - order.payment.insuranceCoverage.isVerified && - isDefined(order.policy.currentDeductible) && - order.policy.currentDeductible >= 0 && - !order.policy.isItac && - !order.policy.isNoComp + store.isVerified && + store.currentDeductible >= 0 && + !store.isItac && + !store.isNoComp ) { payload.priceSubTotal = ""; } else if (isPricingAvailable) { @@ -317,25 +306,18 @@ export default { } // Cash Quote or Cash Price Sub Total - payload.cashPriceSubTotal = order?.cashPriceSubTotal ?? ""; + payload.cashPriceSubTotal = getSubtotal(order).toString(); //unverified (in scenarios we don’t display the price) - if ( - order.payment.isInsurance && - isDefined(order.payment.insuranceCoverage.isVerified) && - !order.payment.insuranceCoverage.isVerified - ) { + if (!store.isVerified) { payload.priceTotal = ""; } //deductible (in scenarios we don’t display the price) else if ( - order.payment.isInsurance && - isDefined(order.payment.insuranceCoverage.isVerified) && - order.payment.insuranceCoverage.isVerified && - isDefined(order.policy.currentDeductible) && - order.policy.currentDeductible >= 0 && - !order.policy.isItac && - !order.policy.isNoComp + store.isVerified && + store.currentDeductible >= 0 && + !store.isItac && + !store.isNoComp ) { payload.priceTotal = ""; } else if (isTaxAvailable) { @@ -351,7 +333,7 @@ export default { if (hasSubmittedOrder) { payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedState; } else { - payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder; + payload.isRecalibrationOnOrder = store.hasRecalibrationPart && containsRecalParts(order.lineItems); } // Appointment Type @@ -360,33 +342,26 @@ export default { } else { payload.appointmentType = ""; } + //Insurance - // TODO: Find equivalent ISS methods. - if (order.payment.isInsurance) { - payload.isInsuranceVerified = order.payment.insuranceCoverage.isVerified ?? ""; - payload.insuranceCompanyName = order.policy.insuranceCompanyName ?? ""; - if (!order.payment.insuranceCoverage.isVerified) { - payload.isInsuranceItac = ""; - payload.isInsuranceNoComp = ""; - } else { - payload.isInsuranceItac = order.policy.isItac ?? ""; - payload.isInsuranceNoComp = order.policy.isNoComp ?? ""; - } - if ( - order.policy.isItac || - order.policy.isNoComp || - !order.payment.insuranceCoverage.isVerified - ) { - payload.insuranceDeductible = ""; - } else { - payload.insuranceDeductible = order.policy.currentDeductible ?? ""; - } - } else { - payload.isInsuranceVerified = ""; - payload.insuranceDeductible = ""; + // TODO: Find equivalent ISS methods. Using store atm need to run off order object. + payload.isInsuranceVerified = store.isVerified; + payload.insuranceCompanyName = store.issConfig.clientName ?? ""; + if (!store.isVerified) { payload.isInsuranceItac = ""; payload.isInsuranceNoComp = ""; - payload.insuranceCompanyName = ""; + } else { + payload.isInsuranceItac = store.isItac ?? ""; + payload.isInsuranceNoComp = store.isNoComp ?? ""; + } + if ( + store.isItac || + store.isNoComp || + !store.isVerified + ) { + payload.insuranceDeductible = ""; + } else { + payload.insuranceDeductible = store.currentDeductible ?? ""; } pushToDataLayerIfDefined(payload); From 7125e1fa97ecc12486d03b4f256a10026ef06e07 Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Fri, 10 Apr 2026 14:47:14 -0400 Subject: [PATCH 10/22] Added more fields. --- src/mixins/analytics-mixin.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 25e06531..08c1a6e0 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -129,11 +129,6 @@ export default { }, pushValueToGA() { - const gaSiteType = { - ['siteType']: useMainStore().issConfig.siteType - }; - this.pushGenericObjectToGA(gaSiteType); - const gaServiceType = { ['service_type']: useMainStore().order?.serviceLocation?.appointmentType?.toLowerCase() }; @@ -155,6 +150,10 @@ export default { // Begin assembling payload for data layer const payload = {}; + payload.appName = "ISS"; + payload.siteType = store.issConfig.siteType; + payload.pageName = this.getPageNameByQueryString(); + // Service Zip if ( order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE && From 2754f7be9c875b43cc135c4d61be5b5be540133a Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Fri, 10 Apr 2026 15:20:14 -0400 Subject: [PATCH 11/22] Some additional fields. --- src/mixins/analytics-mixin.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 08c1a6e0..b1d7d8cf 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -146,6 +146,8 @@ export default { const hasSubmittedOrder = store.hasSubmittedOrder(); const submittedOrder = store.getSubmittedOrder(); const order = hasSubmittedOrder ? submittedOrder : store.order; + const deviceId = getDeviceIdValue(); + const sid = getSessionIdValue(); // Begin assembling payload for data layer const payload = {}; @@ -153,7 +155,11 @@ export default { payload.appName = "ISS"; payload.siteType = store.issConfig.siteType; payload.pageName = this.getPageNameByQueryString(); - + payload.deviceId = deviceId; + payload.sessionId = sid; + payload.clientName = store.issConfig.clientName; + payload.lossCause = order.policy?.damageCause ?? ""; + // Service Zip if ( order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE && From d27b07d517ca9b9bb7b30c285800abbb1db4e08d Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 13 Apr 2026 09:21:40 -0400 Subject: [PATCH 12/22] INSR-8882: Events added to payment page and order confirmation page --- src/constants/analytics.js | 19 +++- .../order-confirmation/order-confirmation.vue | 96 +++++++++++++++++-- .../payment-page-adyen/payment-page-adyen.vue | 13 +++ .../payment-return-adyen.vue | 16 ++++ 4 files changed, 134 insertions(+), 10 deletions(-) diff --git a/src/constants/analytics.js b/src/constants/analytics.js index 8cf147e8..7649a96b 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -1,3 +1,6 @@ +import packageNames from "./package-names"; +import { paymentMethods } from "./payment-method-constants"; + const analyticsPageEvents = Object.freeze({ ENTRY: 'ENTRY', EVENT: 'EVENT' @@ -40,4 +43,18 @@ const ValueToLogTypes = Object.freeze({ LAST_5: 'last_5' }); -export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes }; +const analyticsPaymentTypeMap = new Map([ + [paymentMethods.AFTERPAY, 'after_pay'], + [paymentMethods.CREDIT_CARD, 'credit_card'], + [paymentMethods.PAY_AT_TIME_OF_SERVICE, 'pay_at_service'], + [paymentMethods.PAYPAL, 'pay_pal'], + [paymentMethods.APPLEPAY, 'apple_pay'], +]) + +const analyticsServicePackageMap = new Map([ + [packageNames.TIER_ONE, 'basic'], + [packageNames.TIER_TWO, 'essentials'], + [packageNames.TIER_THREE, 'essentialsplus'] +]) + +export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes, analyticsPaymentTypeMap, analyticsServicePackageMap }; diff --git a/src/layouts/order-confirmation/order-confirmation.vue b/src/layouts/order-confirmation/order-confirmation.vue index 21ce4fc7..dd8a3198 100644 --- a/src/layouts/order-confirmation/order-confirmation.vue +++ b/src/layouts/order-confirmation/order-confirmation.vue @@ -55,7 +55,7 @@ :isRepair="isRepair" />
{{ wipersTitle }} {{ description }}
{{ rainRepelTitle }} {{ rainRepelBody }} @@ -135,6 +135,10 @@ import widgetFields from '@/constants/cms-widget-fields.js'; import { getGlassList } from '@/helpers/damage-helper'; import partTypeStrings from '@/constants/part-type-strings'; import coverageType from '@/constants/coverage-type'; +import { analyticsPaymentTypeMap, analyticsServicePackageMap } from '@/constants/analytics'; +import { containsRecalParts } from '@/helpers/recal-helper'; +import issPageValues from '@/router/router-constants/issPage-values'; +import { getCartTotal } from '@/helpers/cart-helper'; export default { name: 'order-confirmation', @@ -199,6 +203,7 @@ export default { hasRecalibrationPart, referralNumber: referralNumber?.toString(), isNoComp: this.submittedOrder.insuranceCoverage.coverageType === coverageType.NO_COMP, + isITAC: this.submittedOrder.insuranceCoverage.coverageType === coverageType.ITAC, widgets: { siteHeader: 'SiteHeaderWidget', emailConfirmation: 'EmailConfirmationWordingWidget', @@ -418,10 +423,16 @@ export default { // Temporarily set return true to see cart in localhost or dev environment return false; }, - displayWipers() { + hasWipers() { const hasWiperPart = this.submittedOrder.lineItems.vaps.some((part) => part.partType.toLowerCase().includes('wiper')); return hasWiperPart; }, + hasFrontWiper() { + return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.FRONT_WIPER); + }, + hasRearWiper() { + return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.REAR_WIPER); + }, wipersTitle() { return this.getCmsContent( this.widgets.wipersText, @@ -431,13 +442,10 @@ export default { wipersBody() { const wiperTypesOnOrder = []; - const hasFrontWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.FRONT_WIPER); - const hasRearWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.REAR_WIPER); - - if (hasFrontWiper) { + if (this.hasFrontWiper) { wiperTypesOnOrder.push(partTypeStrings.FRONT_WIPER); } - if (hasRearWiper) { + if (this.hasRearWiper) { wiperTypesOnOrder.push(partTypeStrings.REAR_WIPER); } @@ -447,7 +455,7 @@ export default { return wiperDescriptions; }, - displayRainRepel() { + hasRainRepel() { return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.RAIN_DEFENSE); }, rainRepelTitle() { @@ -508,6 +516,10 @@ export default { if (this.carrierUrl) { this.$refs.siteFooter.updateButtonText(`Go back to ${this.carrierName}`); } + + const isFirstLoad = this.mainStore.pageData(issPageValues.ORDER_CONFIRMATION)?.isFirstLoad ?? true; + this.savePageDataToStore(issPageValues.ORDER_CONFIRMATION, { isFirstLoad: false }); + this.handleAnalyticsEvents(isFirstLoad); }, methods: { arePagePrerequisitesValid() { @@ -611,6 +623,72 @@ export default { const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType); return vapsTypeDescription?.Text ?? ''; + }, + handleAnalyticsEvents(isFirstLoad) { + const mobileOrInshop = this.submittedOrder.isMobileAppointment ? 'mobile' : 'in_shop'; + const verifiedOrNotVerified = this.submittedOrder.isVerified ? 'verified' : 'not_verified'; + const repairOrReplace = this.submittedOrder.isRepair ? 'repair' : 'replace'; + this.pushEventToGA('confirmation', 'safelite', `${mobileOrInshop}_${repairOrReplace}_${verifiedOrNotVerified}`, true); + + if (this.payment.isPayInAdvance) { + const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.payment.paymentMethod); + this.pushEventToGA('payment_page', 'pia_successful', paymentMethodForAnalytics, true); + } + + if (this.submittedOrder.servicePackage) { + const servicePackageForAnalytics = analyticsServicePackageMap.get(this.submittedOrder.servicePackage); + this.pushEventToGA('service_package', 'package_purchased', servicePackageForAnalytics, true); + } + + if (containsRecalParts(this.submittedOrder.lineItems)) { + let coverageType = ''; + if (this.isITAC) { + coverageType = 'ITAC'; + } else if (this.isNoComp) { + coverageType = 'no_comp'; + } else if (this.submittedOrder.isVerified) { + coverageType = 'verified'; + } else { + coverageType = 'unverified'; + } + const recalibrationType = this.submittedOrder.lineItems.glassParts.find((part) => part.requiresRecalibration).recalibrationType; + this.pushEventToGA(`recalibration_scheduled_${coverageType}`, this.vehicle.carId, `recal_type_${recalibrationType}`.replace(/ /g, '_').toLowerCase(), true); + } + + const ymms = `${this.vehicle.year}_${this.vehicle.make}_${this.vehicle.model}_${this.vehicle.style}`; + if (isFirstLoad && getCartTotal(this.submittedOrder) > 0) { + this.pushEventToGA('total_price', getCartTotal(this.submittedOrder).toString(), ymms, true); + } + + this.pushEventToGA('rain_defense', this.hasRainRepel ? 'purchased' : 'no_purchase', ymms, true); + + // TODO: Check if we're coming from SFA and add relevant events + // eslint-disable-next-line + if (false) { + this.pushEventToGA('visitor_info_confirmation', 'referring_site', 'SFA', null); + } + else { + this.pushEventToGA('visitor_info_confirmation', 'referring_site', 'ClientSite', true); + } + + this.pushEventToGA('visitor_info_confirmation', 'client_name', this.mainStore.accountNameForEvents, true); + + const wiperAction = this.hasWipers ? 'purchased' : 'no_purchase'; + + let wiperLabel = this.wipersBody.join('_').toLowerCase().replace('
', '').replace(/beam/g, '').replace(/blades/g, '').trim().replace(/ /g, '_'); + if (wiperLabel.indexOf('front') < 0 && wiperLabel.indexOf('rear') < 0) { + wiperLabel = 'none_none'; + } else { + if (wiperLabel.indexOf('front') < 0) { + wiperLabel = 'front_none_' + wiperLabel; + } + if (wiperLabel.indexOf('rear') < 0) { + wiperLabel = wiperLabel + '_rear_none'; + } + } + wiperLabel = wiperLabel.replace(/__/g, '_'); + + this.pushEventToGA('wipers', wiperAction, wiperLabel, true); } } }; diff --git a/src/layouts/payment-page-adyen/payment-page-adyen.vue b/src/layouts/payment-page-adyen/payment-page-adyen.vue index 91462f51..cdf6529f 100644 --- a/src/layouts/payment-page-adyen/payment-page-adyen.vue +++ b/src/layouts/payment-page-adyen/payment-page-adyen.vue @@ -75,6 +75,7 @@ import { mapAdyenToIssPaymentMethod, mapIssToAdyenPaymentMethod } from '@/helper import { createAdyenCheckout } from "@/helpers/adyen-helper"; import { Dropin } from "@adyen/adyen-web/auto"; import applicationConfig from '@/constants/application-config'; +import { analyticsPaymentTypeMap } from '@/constants/analytics'; export default { name: 'payment-page-adyen', @@ -118,6 +119,7 @@ export default { }, mounted() { showIssLoadingModal(true); + this.pushEventToGA('payment_page', 'Mode', 'Adyen', true); this.initializeAdyen().finally(() => { showIssLoadingModal(false); }); @@ -353,6 +355,8 @@ export default { }, async paymentFailedPayLater() { this.showIssLoadingModal(true); + const paymentMethod = analyticsPaymentTypeMap.get(this.piaType); + this.pushEventToGA('transaction_declined_displayed', 'save_your_appointment_clicked', `${paymentMethod}_declined`, true); this.mainStore.savePaymentMethodChoice(paymentMethods.PAY_AT_TIME_OF_SERVICE); await submitWorkOrder({ submitType: submitType.SAFELITE }); this.$router.navigate( @@ -523,6 +527,8 @@ export default { const paymentMethodFromSession = adyenResponse?.paymentMethod; const paymentMethod = mapAdyenToIssPaymentMethod(paymentMethodFromSession); + const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(paymentMethod); + this.pushEventToGA('PIA', 'Pay Today', paymentMethodForAnalytics, true, null, 0); this.mainStore.savePaymentMethodChoice(paymentMethod); @@ -544,6 +550,10 @@ export default { await global.$logger.logError(stringToLog); + const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('payment_page', 'pia_failed', paymentMethodForAnalytics, true, null, result?.resultCode); + this.hasPaymentFailureError = true; } @@ -556,6 +566,9 @@ export default { const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`; await global.$logger.logError(stringToLog); + const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('payment_page', 'pia_error', paymentMethodForAnalytics, true, null, error?.name); this.hasPaymentFailureError = true; } diff --git a/src/layouts/payment-return-adyen/payment-return-adyen.vue b/src/layouts/payment-return-adyen/payment-return-adyen.vue index d306857d..33c35194 100644 --- a/src/layouts/payment-return-adyen/payment-return-adyen.vue +++ b/src/layouts/payment-return-adyen/payment-return-adyen.vue @@ -18,6 +18,7 @@ import navigationScenarios from '@/router/router-constants/navigation-scenarios' import submitType from '@/constants/submit-type'; import showIssLoadingModal from '@/helpers/loading-modal-helper'; import { paymentMethods } from '@/constants/payment-method-constants'; +import { analyticsPaymentTypeMap } from '@/constants/analytics'; export default { name: 'payment-return-adyen', @@ -58,6 +59,8 @@ export default { const sessionInfo = await getSessionInfo(sessionId, result.sessionResult); const paymentMethod = mapAdyenToIssPaymentMethod(sessionInfo?.paymentMethod); + const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(paymentMethod); + this.pushEventToGA('PIA', 'Pay Today', paymentMethodForAnalytics, true, null, 0); const amountDue = getCartTotal(store.order); const ccToken = generateCcToken(sessionInfo); if (paymentMethod === paymentMethods.AFTERPAY) { @@ -95,6 +98,11 @@ export default { } ); }, + computed: { + piaType() { + return this.mainStore.payment.paymentMethod; + } + }, methods: { finalizeAdyenPayment(sessionId, redirectResult) { return new Promise((resolve, reject) => { @@ -111,6 +119,10 @@ export default { const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${sessionId}`; global.$logger.logError(stringToLog); + + const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('payment_page', 'pia_failed', paymentMethodForAnalytics, true, null, result?.resultCode); } reject({ @@ -123,6 +135,10 @@ export default { const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`; global.$logger.logError(stringToLog); + + const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('payment_page', 'pia_error', paymentMethodForAnalytics, true, null, error?.name); } reject({ From 50404eb4e6239381046f3367f27d14243a3700e1 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 13 Apr 2026 09:43:06 -0400 Subject: [PATCH 13/22] INSR-8882: Fix failing tests --- .../order-confirmation/order-confirmation.spec.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/layouts/order-confirmation/order-confirmation.spec.js b/src/layouts/order-confirmation/order-confirmation.spec.js index be64fdc8..af243e29 100644 --- a/src/layouts/order-confirmation/order-confirmation.spec.js +++ b/src/layouts/order-confirmation/order-confirmation.spec.js @@ -27,7 +27,8 @@ jest.mock('@/helpers/cms-content-helper', () => ({ const mockMixin = { methods: { getCmsContent: jest.fn(), - setCmsContent: jest.fn() + setCmsContent: jest.fn(), + savePageDataToStore: jest.fn() } }; @@ -778,7 +779,8 @@ describe('OrderConfirmation.vue', () => { } return ''; }), - setCmsContent: jest.fn() + setCmsContent: jest.fn(), + savePageDataToStore: jest.fn() } }; }); @@ -827,7 +829,8 @@ describe('OrderConfirmation.vue', () => { } return ''; }), - setCmsContent: jest.fn() + setCmsContent: jest.fn(), + savePageDataToStore: jest.fn() } }; }); From 5eb4ef0418bae67e24bb1d8f3c3b80917d97d9a1 Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Mon, 13 Apr 2026 10:25:48 -0400 Subject: [PATCH 14/22] Moved some common methods around. --- src/helpers/object-helper.js | 14 ++++++++ src/helpers/recal-helper.js | 27 +++++++++++++- src/mixins/analytics-mixin.js | 68 ++++++----------------------------- src/store/index.js | 40 +++------------------ 4 files changed, 56 insertions(+), 93 deletions(-) diff --git a/src/helpers/object-helper.js b/src/helpers/object-helper.js index 37ee46be..e142c236 100644 --- a/src/helpers/object-helper.js +++ b/src/helpers/object-helper.js @@ -86,3 +86,17 @@ export function getPropertyCaseInsensitive(obj, property) { while (prop = props.pop()) if (prop.toLowerCase() === property.toLowerCase()) return prop; return null; } + +export function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { + return (array ?? []).map((x) => x[propertyName]).filter((x) => x); +} + +export function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) { + if (!arrayOfObjects) return null; + + return arrayOfObjects.sort((a, b) => { + if (a[propertyName] < b[propertyName]) return -1; + if (a[propertyName] > b[propertyName]) return 1; + return 0; + }); +} diff --git a/src/helpers/recal-helper.js b/src/helpers/recal-helper.js index f364bf68..029aee25 100644 --- a/src/helpers/recal-helper.js +++ b/src/helpers/recal-helper.js @@ -1,5 +1,5 @@ import partTypeStrings from '@/constants/part-type-strings'; -import { deepClone } from '@/helpers/object-helper'; +import { deepClone, getNonFalseValuesOfPropertyInArrayOfObjects } from '@/helpers/object-helper'; const recalPartTypes = [partTypeStrings.RECALIBRATION, partTypeStrings.ADAS_RECALIBRATION]; @@ -73,6 +73,31 @@ export function containsRecalParts(lineItems) { } } +export function isRecalOrder(lineItems) { + return (containsRecalParts(lineItems) && getHasRecalibrationPart(lineItems)); +} + +export function getHasRecalibrationPart(lineItems) { + const hasRequiresRecalibration = getNonFalseValuesOfPropertyInArrayOfObjects(lineItems.glassParts, 'requiresRecalibration')?.length > 0; + const hasRecalibrationType = getNonFalseValuesOfPropertyInArrayOfObjects(lineItems.glassParts, 'recalibrationType')?.length > 0; + + if (hasRequiresRecalibration) { + if (hasRecalibrationType) { + // Has both 'requiresRecalibration' and 'recalibrationType' and 'recalibrationType' + return ( + getNonFalseValuesOfPropertyInArrayOfObjects( + lineItems.glassParts, + 'recalibrationType' + )[0].toLowerCase() !== 'unknown' + ); + } + // Has 'requiresRecalibration' but no 'recalibrationType' at all + return true; + } + // Does not have 'requiresRecalibration' + return false; +} + export function anyPartWithRequiresRecalFlag(lineItems) { if (!lineItems) { return false; diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index b1d7d8cf..4827a559 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -21,7 +21,7 @@ import { ValueToLogTypes } from '@/constants/analytics'; import { getCartTotal, getSubtotal } from '@/helpers/cart-helper'; -import { getRecalPartNumbers } from "@/helpers/recal-helper"; +import { getRecalPartNumbers, isRecalOrder } from "@/helpers/recal-helper"; import coverageStatuses from '@/constants/coverage-statuses'; import coverageType from '@/constants/coverage-type'; import { AppointmentTypeStrings } from '@/constants/schedule-constants'; @@ -266,35 +266,12 @@ export default { payload.orderNumber = ""; } - // Pricing - // Only fire for completed orders? - - const lineItems = order.lineItems ?? {}; - const combinedLineItems = [ - ...(lineItems.glassParts ?? []), - ...(lineItems.supportingItems ?? []), - ...(lineItems.vaps ?? []), - ...(lineItems.promos ?? []), - ]; - - const isPricingAvailable = - combinedLineItems.length > 0 && - combinedLineItems.every( - (lineItem) => - isDefined(lineItem.kitPrice) && - isDefined(lineItem.laborAmount) && - isDefined(lineItem.sellingPrice) - ); - const isTaxAvailable = - isPricingAvailable && - combinedLineItems.every((lineItem) => isDefined(lineItem.salesTax)); - - //unverified (in scenarios we don’t display the price) + // Unverified (no price or deductible displayed) if (!store.isVerified) { payload.priceSubTotal = ""; + payload.priceTotal = ""; } - //deductible (in scenarios we don’t display the price) - // TODO: Need to redo this for ISS 2.0 + // Deductible case (no price is displayed, only deductible) else if ( store.isVerified && store.currentDeductible >= 0 && @@ -302,44 +279,23 @@ export default { !store.isNoComp ) { payload.priceSubTotal = ""; - } else if (isPricingAvailable) { + payload.priceTotal = ""; + // ITAC or NoComp (where cash price is shown) + } else if (store.isVerified && (store.isItac || store.isNoComp)) { const subtotal = getSubtotal(order).toFixed(2); - payload.priceSubTotal = parseFloat(subtotal); + const total = getCartTotal(order).toFixed(2); + payload.priceTotal = parseFloat(total); } else { payload.priceSubTotal = ""; + payload.priceTotal = ""; } // Cash Quote or Cash Price Sub Total payload.cashPriceSubTotal = getSubtotal(order).toString(); - //unverified (in scenarios we don’t display the price) - if (!store.isVerified) { - payload.priceTotal = ""; - } - //deductible (in scenarios we don’t display the price) - else if ( - store.isVerified && - store.currentDeductible >= 0 && - !store.isItac && - !store.isNoComp - ) { - payload.priceTotal = ""; - } else if (isTaxAvailable) { - const total = getCartTotal(order).toFixed(2); - - payload.priceTotal = parseFloat(total); - } else { - payload.priceTotal = ""; - } - // Recalibration - // TODO: Find equivalent ISS methods. - if (hasSubmittedOrder) { - payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedState; - } else { - payload.isRecalibrationOnOrder = store.hasRecalibrationPart && containsRecalParts(order.lineItems); - } + payload.isRecalibrationOnOrder = isRecalOrder(order.lineItems); // Appointment Type if (isDefined(order.serviceLocation.appointmentType)) { @@ -348,8 +304,6 @@ export default { payload.appointmentType = ""; } - //Insurance - // TODO: Find equivalent ISS methods. Using store atm need to run off order object. payload.isInsuranceVerified = store.isVerified; payload.insuranceCompanyName = store.issConfig.clientName ?? ""; if (!store.isVerified) { diff --git a/src/store/index.js b/src/store/index.js index db5894c6..a4689af7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -22,11 +22,12 @@ import { repairWaivedForSelectedVehicle } from '@/helpers/policy-vehicle-helper'; import { buildURLSearchParams, getPartNumbersListForQueryString } from '@/helpers/querystring-helper'; -import { getRecalPartNumbers, getTopLevelGlassPartsWithRecal } from '@/helpers/recal-helper'; +import { getRecalPartNumbers, getTopLevelGlassPartsWithRecal, getHasRecalibrationPart } from '@/helpers/recal-helper'; import { getDateForSavedSessionTimeout } from '@/helpers/session-helper'; import { isMobileDevice } from '@/helpers/useragent-helper'; import issPageValues from '@/router/router-constants/issPage-values'; import CoverageStatuses from '@/constants/coverage-statuses'; +import { getNonFalseValuesOfPropertyInArrayOfObjects, sortArrayOfObjectsByPropertyValue } from '@/helpers/object-helper'; const storeId = 'main'; @@ -276,7 +277,7 @@ export const useMainStore = defineStore({ state: () => state, getters: { billToAccountNumber: (storeState) => storeState.issConfig.billToAccountNumber, - hasRecalibrationPart: (storeState) => getHasRecalibrationPart(storeState), + hasRecalibrationPart: (storeState) => getHasRecalibrationPartOnOrder(storeState), vehicle: (storeState) => storeState.order.vehicle, damage: (storeState) => storeState.order.damage, lineItems: (state) => state.order.lineItems, @@ -3179,39 +3180,8 @@ export const useMainStore = defineStore({ // Private Functions -function getHasRecalibrationPart(state) { - const hasRequiresRecalibration = getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, 'requiresRecalibration')?.length > 0; - const hasRecalibrationType = getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, 'recalibrationType')?.length > 0; - - if (hasRequiresRecalibration) { - if (hasRecalibrationType) { - // Has both 'requiresRecalibration' and 'recalibrationType' and 'recalibrationType' - return ( - getNonFalseValuesOfPropertyInArrayOfObjects( - state.order.lineItems.glassParts, - 'recalibrationType' - )[0].toLowerCase() !== 'unknown' - ); - } - // Has 'requiresRecalibration' but no 'recalibrationType' at all - return true; - } - // Does not have 'requiresRecalibration' - return false; -} - -function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { - return (array ?? []).map((x) => x[propertyName]).filter((x) => x); -} - -function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) { - if (!arrayOfObjects) return null; - - return arrayOfObjects.sort((a, b) => { - if (a[propertyName] < b[propertyName]) return -1; - if (a[propertyName] > b[propertyName]) return 1; - return 0; - }); +function getHasRecalibrationPartOnOrder(state) { + return getHasRecalibrationPart(state.order.lineItems); } function convertGlassPieceNamingForApi(glassArray) { From d4f7213a251c21b01929e8d7976b06e37a2187c0 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 13 Apr 2026 11:17:42 -0400 Subject: [PATCH 15/22] INSR-8882: add unit tests, address possible errors, formatting fixes --- src/constants/analytics.js | 8 +- .../order-confirmation.spec.js | 249 +++++++++++++++++- .../order-confirmation/order-confirmation.vue | 4 +- .../payment-page-adyen/payment-page-adyen.vue | 4 +- .../payment-return-adyen.vue | 4 +- 5 files changed, 258 insertions(+), 11 deletions(-) diff --git a/src/constants/analytics.js b/src/constants/analytics.js index 7649a96b..1cb1a160 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -1,5 +1,5 @@ -import packageNames from "./package-names"; -import { paymentMethods } from "./payment-method-constants"; +import packageNames from './package-names'; +import { paymentMethods } from './payment-method-constants'; const analyticsPageEvents = Object.freeze({ ENTRY: 'ENTRY', @@ -49,12 +49,12 @@ const analyticsPaymentTypeMap = new Map([ [paymentMethods.PAY_AT_TIME_OF_SERVICE, 'pay_at_service'], [paymentMethods.PAYPAL, 'pay_pal'], [paymentMethods.APPLEPAY, 'apple_pay'], -]) +]); const analyticsServicePackageMap = new Map([ [packageNames.TIER_ONE, 'basic'], [packageNames.TIER_TWO, 'essentials'], [packageNames.TIER_THREE, 'essentialsplus'] -]) +]); export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes, analyticsPaymentTypeMap, analyticsServicePackageMap }; diff --git a/src/layouts/order-confirmation/order-confirmation.spec.js b/src/layouts/order-confirmation/order-confirmation.spec.js index af243e29..ed1f1922 100644 --- a/src/layouts/order-confirmation/order-confirmation.spec.js +++ b/src/layouts/order-confirmation/order-confirmation.spec.js @@ -14,6 +14,8 @@ import coverageType from '@/constants/coverage-type'; import { deepClone } from '@/helpers/object-helper'; import { AppointmentTypeStrings } from '@/constants/schedule-constants'; import { paymentMethods } from '@/constants/payment-method-constants'; +import partTypeStrings from '@/constants/part-type-strings'; +import packageNames from '@/constants/package-names'; jest.mock('@/helpers/layout-helper.js', () => jest.fn()); @@ -28,7 +30,8 @@ const mockMixin = { methods: { getCmsContent: jest.fn(), setCmsContent: jest.fn(), - savePageDataToStore: jest.fn() + savePageDataToStore: jest.fn(), + pushEventToGA: jest.fn() } }; @@ -958,6 +961,250 @@ describe('OrderConfirmation.vue', () => { }); }); describe('Methods', () => { + describe('handleAnalyticsEvents', () => { + let mixin; + beforeEach(() => { + mixin = { + methods: { + getCmsContent: jest.fn(), + setCmsContent: jest.fn(), + savePageDataToStore: jest.fn(), + pushEventToGA: jest.fn() + } + }; + }); + test('should call pushEventToGA with confirmation event for mobile repair', () => { + // Arrange + const order = deepClone(sessionStorage); + order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE; + order.damage.isRepair = true; + order.isVerified = false; + order.isMobileAppointment = true; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('confirmation', 'safelite', expect.stringContaining('mobile'), true); + }); + test('should call pushEventToGA with confirmation event for in-shop replace', () => { + // Arrange + const order = deepClone(sessionStorage); + order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP; + order.damage.isRepair = false; + order.isVerified = true; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('confirmation', 'safelite', expect.stringContaining('replace'), true); + }); + test('should call pushEventToGA for payment PIA successful when payment.isPayInAdvance is true', () => { + // Arrange + const order = deepClone(sessionStorage); + order.payment.isPayInAdvance = true; + order.payment.paymentMethod = paymentMethods.CREDIT_CARD; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('payment_page', 'pia_successful', expect.any(String), true); + }); + test('should not call pushEventToGA for PIA when payment.isPayInAdvance is false', () => { + // Arrange + const order = deepClone(sessionStorage); + order.payment.isPayInAdvance = false; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + const paymentPageCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'payment_page'); + expect(paymentPageCalls.length).toBe(0); + }); + test('should call pushEventToGA for service package when submittedOrder.servicePackage exists', () => { + // Arrange + const order = deepClone(sessionStorage); + order.servicePackage = packageNames.TIER_ONE; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('service_package', 'package_purchased', expect.any(String), true); + }); + test('should not call pushEventToGA for service package when submittedOrder.servicePackage is null', () => { + // Arrange + const order = deepClone(sessionStorage); + order.servicePackage = null; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + const servicePackageCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'service_package'); + expect(servicePackageCalls.length).toBe(0); + }); + test('should call pushEventToGA for total price when isFirstLoad is true and cart total greater than 0', () => { + // Arrange + const order = deepClone(sessionStorage); + const expectedTotal = 100; + order.lineItems.glassParts = [{ partType: 'WINDSHIELD', sellingPrice: expectedTotal }]; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // Act + wrapper.vm.handleAnalyticsEvents(true); + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('total_price', expectedTotal.toString(), expect.any(String), true); + }); + test('should not call pushEventToGA for total price when isFirstLoad is false', () => { + // Arrange + const order = deepClone(sessionStorage); + const expectedTotal = 100; + order.lineItems.glassParts = [{ partType: 'WINDSHIELD', sellingPrice: expectedTotal }]; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + useMainStore().pageData = jest.fn().mockReturnValue({ isFirstLoad: false }); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + const totalPriceCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'total_price'); + expect(totalPriceCalls.length).toBe(0); + }); + test('should not call pushEventToGA for total price when cart total is 0', () => { + // Arrange + const order = deepClone(sessionStorage); + const expectedTotal = 0; + order.lineItems.glassParts = [{ partType: 'WINDSHIELD', sellingPrice: 0 }]; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + const totalPriceCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'total_price'); + expect(totalPriceCalls.length).toBe(0); + }); + test('should call pushEventToGA for rain defense purchased when hasRainRepel is true', () => { + // Arrange + const order = deepClone(sessionStorage); + order.lineItems.vaps = [{ partType: partTypeStrings.RAIN_DEFENSE }]; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('rain_defense', 'purchased', expect.any(String), true); + }); + test('should call pushEventToGA for rain defense no_purchase when hasRainRepel is false', () => { + // Arrange + const order = deepClone(sessionStorage); + order.lineItems.vaps = []; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('rain_defense', 'no_purchase', expect.any(String), true); + }); + test('should call pushEventToGA for visitor_info_confirmation with ClientSite referring_site', () => { + // Arrange + const order = deepClone(sessionStorage); + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('visitor_info_confirmation', 'referring_site', 'ClientSite', true); + }); + test('should call pushEventToGA for visitor_info_confirmation with client_name', () => { + // Arrange + const order = deepClone(sessionStorage); + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('visitor_info_confirmation', 'client_name', expect.any(String), true); + }); + test('should call pushEventToGA for wipers purchased when hasWipers is true', () => { + // Arrange + const order = deepClone(sessionStorage); + order.lineItems.vaps = [{ partType: partTypeStrings.FRONT_WIPER }]; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const mixin = { + methods: { + getCmsContent: jest.fn().mockReturnValue([{ Text: 'Front Beam' }]), + setCmsContent: jest.fn(), + savePageDataToStore: jest.fn(), + pushEventToGA: jest.fn() + } + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('wipers', 'purchased', expect.any(String), true); + }); + test('should call pushEventToGA for wipers no_purchase when hasWipers is false', () => { + // Arrange + const order = deepClone(sessionStorage); + order.lineItems.vaps = []; + const mockStoreActions = () => { + useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); + }; + const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin); + + // handleAnalyticsEvents is called on mount, no need to act + + // Assert + expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('wipers', 'no_purchase', 'none_none', true); + }); + });; describe('formatDate', () => { test.each([ ['Wednesday, April 22, 2020', '2020-04-22'], diff --git a/src/layouts/order-confirmation/order-confirmation.vue b/src/layouts/order-confirmation/order-confirmation.vue index dd8a3198..063346f2 100644 --- a/src/layouts/order-confirmation/order-confirmation.vue +++ b/src/layouts/order-confirmation/order-confirmation.vue @@ -627,7 +627,7 @@ export default { handleAnalyticsEvents(isFirstLoad) { const mobileOrInshop = this.submittedOrder.isMobileAppointment ? 'mobile' : 'in_shop'; const verifiedOrNotVerified = this.submittedOrder.isVerified ? 'verified' : 'not_verified'; - const repairOrReplace = this.submittedOrder.isRepair ? 'repair' : 'replace'; + const repairOrReplace = this.submittedOrder.isRepair ? 'repair' : 'replace'; this.pushEventToGA('confirmation', 'safelite', `${mobileOrInshop}_${repairOrReplace}_${verifiedOrNotVerified}`, true); if (this.payment.isPayInAdvance) { @@ -651,7 +651,7 @@ export default { } else { coverageType = 'unverified'; } - const recalibrationType = this.submittedOrder.lineItems.glassParts.find((part) => part.requiresRecalibration).recalibrationType; + const recalibrationType = this.submittedOrder.lineItems.glassParts?.find((part) => part.requiresRecalibration)?.recalibrationType; this.pushEventToGA(`recalibration_scheduled_${coverageType}`, this.vehicle.carId, `recal_type_${recalibrationType}`.replace(/ /g, '_').toLowerCase(), true); } diff --git a/src/layouts/payment-page-adyen/payment-page-adyen.vue b/src/layouts/payment-page-adyen/payment-page-adyen.vue index cdf6529f..8608bc29 100644 --- a/src/layouts/payment-page-adyen/payment-page-adyen.vue +++ b/src/layouts/payment-page-adyen/payment-page-adyen.vue @@ -551,7 +551,7 @@ export default { await global.$logger.logError(stringToLog); const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); - this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0); this.pushEventToGA('payment_page', 'pia_failed', paymentMethodForAnalytics, true, null, result?.resultCode); this.hasPaymentFailureError = true; @@ -567,7 +567,7 @@ export default { await global.$logger.logError(stringToLog); const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); - this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0); this.pushEventToGA('payment_page', 'pia_error', paymentMethodForAnalytics, true, null, error?.name); this.hasPaymentFailureError = true; diff --git a/src/layouts/payment-return-adyen/payment-return-adyen.vue b/src/layouts/payment-return-adyen/payment-return-adyen.vue index 33c35194..85e1888b 100644 --- a/src/layouts/payment-return-adyen/payment-return-adyen.vue +++ b/src/layouts/payment-return-adyen/payment-return-adyen.vue @@ -121,7 +121,7 @@ export default { global.$logger.logError(stringToLog); const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); - this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0); this.pushEventToGA('payment_page', 'pia_failed', paymentMethodForAnalytics, true, null, result?.resultCode); } @@ -137,7 +137,7 @@ export default { global.$logger.logError(stringToLog); const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType); - this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, true, true, null, 0); + this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0); this.pushEventToGA('payment_page', 'pia_error', paymentMethodForAnalytics, true, null, error?.name); } From 568ecd30047e0dd2d48782073d3516dc299c2448 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 13 Apr 2026 11:23:04 -0400 Subject: [PATCH 16/22] INSR-9928: Add forward_progress GA event on vehicle damage page --- src/layouts/vehicle-damage/vehicle-damage.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index 9833cdcd..42ae3603 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -487,6 +487,7 @@ export default { logEvents() { const vehicleString = this.mainStore.order.vehicle.make + '_' + this.mainStore.order.vehicle.model + '_' + this.mainStore.order.vehicle.style; this.pushEventToGA("CAR SUBMISSION", this.mainStore.order.vehicle.year?.toString(), vehicleString, true, null, 0); + this.pushEventToGA('forward_progress', 'continue_clicked', 'vehicle_damage_2', true); if (this.isWindshieldRepair) { this.pushEventToGA("damage", "selected", "repair", true, null, null); From 7146bc1656131264dcfd167ec696f54eb41496e6 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 13 Apr 2026 11:58:57 -0400 Subject: [PATCH 17/22] INSR-8882: Fix test that failed when run in larger suite --- src/layouts/order-confirmation/order-confirmation.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/layouts/order-confirmation/order-confirmation.spec.js b/src/layouts/order-confirmation/order-confirmation.spec.js index ed1f1922..df85a7ec 100644 --- a/src/layouts/order-confirmation/order-confirmation.spec.js +++ b/src/layouts/order-confirmation/order-confirmation.spec.js @@ -156,7 +156,8 @@ const sessionStorage = { currentDeductible: { replace: 100, repair: 0 - } + }, + isUnverified: false }; function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}, mixin = mockMixin) { @@ -1103,6 +1104,7 @@ describe('OrderConfirmation.vue', () => { const order = deepClone(sessionStorage); const expectedTotal = 0; order.lineItems.glassParts = [{ partType: 'WINDSHIELD', sellingPrice: 0 }]; + order.insuranceCoverage.coverageType = coverageType.ITAC; const mockStoreActions = () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; From c17c57dbedf305f45436e7ce71af43fd0000465c Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Mon, 13 Apr 2026 12:13:22 -0400 Subject: [PATCH 18/22] ITAC method call fix. --- src/mixins/analytics-mixin.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 4827a559..e35d1319 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -275,13 +275,13 @@ export default { else if ( store.isVerified && store.currentDeductible >= 0 && - !store.isItac && + !store.isITAC && !store.isNoComp ) { payload.priceSubTotal = ""; payload.priceTotal = ""; // ITAC or NoComp (where cash price is shown) - } else if (store.isVerified && (store.isItac || store.isNoComp)) { + } else if (store.isVerified && (store.isITAC || store.isNoComp)) { const subtotal = getSubtotal(order).toFixed(2); payload.priceSubTotal = parseFloat(subtotal); const total = getCartTotal(order).toFixed(2); @@ -310,11 +310,11 @@ export default { payload.isInsuranceItac = ""; payload.isInsuranceNoComp = ""; } else { - payload.isInsuranceItac = store.isItac ?? ""; + payload.isInsuranceItac = store.isITAC ?? ""; payload.isInsuranceNoComp = store.isNoComp ?? ""; } if ( - store.isItac || + store.isITAC || store.isNoComp || !store.isVerified ) { @@ -530,7 +530,7 @@ export default { sessionData.coverageSubStatus = coverageType.mapToApi(order?.insuranceCoverage.coverageType); sessionData.isNoComp = store.isNoComp; - sessionData.isItac = store.isITAC; + sessionData.isItac = store.isITAC; sessionData.subTotalPrice = getSubtotal(order).toString(); sessionData.totalPrice = getCartTotal(order).toString(); sessionData.cashPriceSubTotal = getSubtotal(order).toString(); From 3c3e16667134ad179b70d94330229ebefb76bc0a Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Mon, 13 Apr 2026 12:17:05 -0400 Subject: [PATCH 19/22] Enforcement check. --- src/mixins/analytics-mixin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index e35d1319..81d63e41 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -274,7 +274,7 @@ export default { // Deductible case (no price is displayed, only deductible) else if ( store.isVerified && - store.currentDeductible >= 0 && + (typeof store.currentDeductible === 'number' && store.currentDeductible >= 0) && !store.isITAC && !store.isNoComp ) { From 7e37f4b7fe429cdd4f9664ea7c6584471b8c577d Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 13 Apr 2026 12:31:10 -0400 Subject: [PATCH 20/22] INSR-8882: absolute pathing for imports in analytics constants file --- src/constants/analytics.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants/analytics.js b/src/constants/analytics.js index 1cb1a160..dc023b15 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -1,5 +1,5 @@ -import packageNames from './package-names'; -import { paymentMethods } from './payment-method-constants'; +import packageNames from '@/constants/package-names'; +import { paymentMethods } from '@/constants/payment-method-constants'; const analyticsPageEvents = Object.freeze({ ENTRY: 'ENTRY', From db4584bc00d5427358295f36897f0862eb9eb1a8 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 13 Apr 2026 12:33:13 -0400 Subject: [PATCH 21/22] INSR-8882: Move method call after const declaration --- src/layouts/order-confirmation/order-confirmation.spec.js | 2 +- src/layouts/payment-return-adyen/payment-return-adyen.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/order-confirmation/order-confirmation.spec.js b/src/layouts/order-confirmation/order-confirmation.spec.js index df85a7ec..9438b038 100644 --- a/src/layouts/order-confirmation/order-confirmation.spec.js +++ b/src/layouts/order-confirmation/order-confirmation.spec.js @@ -1206,7 +1206,7 @@ describe('OrderConfirmation.vue', () => { // Assert expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('wipers', 'no_purchase', 'none_none', true); }); - });; + }); describe('formatDate', () => { test.each([ ['Wednesday, April 22, 2020', '2020-04-22'], diff --git a/src/layouts/payment-return-adyen/payment-return-adyen.vue b/src/layouts/payment-return-adyen/payment-return-adyen.vue index 85e1888b..ce1ae7e5 100644 --- a/src/layouts/payment-return-adyen/payment-return-adyen.vue +++ b/src/layouts/payment-return-adyen/payment-return-adyen.vue @@ -60,7 +60,6 @@ export default { const paymentMethod = mapAdyenToIssPaymentMethod(sessionInfo?.paymentMethod); const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(paymentMethod); - this.pushEventToGA('PIA', 'Pay Today', paymentMethodForAnalytics, true, null, 0); const amountDue = getCartTotal(store.order); const ccToken = generateCcToken(sessionInfo); if (paymentMethod === paymentMethods.AFTERPAY) { @@ -70,6 +69,7 @@ export default { ccToken.expMonth = "03"; ccToken.expYear = "2030"; } + this.pushEventToGA('PIA', 'Pay Today', paymentMethodForAnalytics, true, null, 0); store.savePaymentMethodChoice(paymentMethod); store.updateCreditCardToken(ccToken); From 1bd17570839ca4ea1b2fdd7f644e936ccf03d74c Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Tue, 14 Apr 2026 08:12:56 -0400 Subject: [PATCH 22/22] Change versioning Some parts v2 will not be ready for our 4.23 release --- src/constants/endpoints.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 0f75c323..59515bd3 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -6,7 +6,8 @@ const COVERAGE_BASE_URL = '/coverage/api/v1/coverage'; const EXPERIMENTS_BASE_URL = '/experiments/api/v1/experiments'; const LOCATION_BASE_URL = '/location/api/v1/location'; const ORDER_BASE_URL = '/order/api/v1/order'; -const PARTS_BASE_URL = '/parts/api/v2/parts'; +const PARTS_V1_BASE_URL = '/parts/api/v1/parts'; +const PARTS_V2_BASE_URL = '/parts/api/v2/parts'; const PRICE_BASE_URL = '/price/api/v1/price'; const SCHEDULE_BASE_URL = '/schedule/api/v1/schedule'; const VEHICLE_BASE_URL = '/vehicle/api/v1/vehicle'; @@ -42,7 +43,7 @@ const endpoints = Object.freeze({ method: 'POST' }, GetMobilePremiumFee: { - url: `${PARTS_BASE_URL}/mobile-premium-fee`, + url: `${PARTS_V1_BASE_URL}/mobile-premium-fee`, method: 'GET' }, GetVehicleYears: { @@ -62,15 +63,15 @@ const endpoints = Object.freeze({ method: 'GET' }, GetDamageOptions: { - url: `${PARTS_BASE_URL}/damage-options`, + url: `${PARTS_V1_BASE_URL}/damage-options`, method: 'GET' }, GetPartsOrQuestions: { - url: `${PARTS_BASE_URL}/parts-or-questions`, + url: `${PARTS_V1_BASE_URL}/parts-or-questions`, method: 'POST' }, GetParts: { - url: `${PARTS_BASE_URL}/parts`, + url: `${PARTS_V1_BASE_URL}/parts`, method: 'POST' }, GetITACPriceOrderItems: { @@ -99,19 +100,19 @@ const endpoints = Object.freeze({ method: 'GET' }, GetCapabilityQuestions: { - url: `${PARTS_BASE_URL}/capability-questions`, + url: `${PARTS_V1_BASE_URL}/capability-questions`, method: 'GET' }, GetPartFromCapabilityAnswer: { - url: `${PARTS_BASE_URL}/part-from-capability-answer`, + url: `${PARTS_V1_BASE_URL}/part-from-capability-answer`, method: 'POST' }, GetWipers: { - url: `${PARTS_BASE_URL}/wipers`, + url: `${PARTS_V1_BASE_URL}/wipers`, method: 'GET' }, GetRainDefense: { - url: `${PARTS_BASE_URL}/rain-repel`, + url: `${PARTS_V1_BASE_URL}/rain-repel`, method: 'GET' }, GetRecalParts: { @@ -123,19 +124,19 @@ const endpoints = Object.freeze({ zipCode, applicationName, referralSequenceNumber - ) => `${PARTS_BASE_URL}/recal-parts/${carId}/${partNumber}/${recalibrationType}/${parentAccountNumber}/${zipCode}/${applicationName}/${referralSequenceNumber}`, + ) => `${PARTS_V1_BASE_URL}/recal-parts/${carId}/${partNumber}/${recalibrationType}/${parentAccountNumber}/${zipCode}/${applicationName}/${referralSequenceNumber}`, method: 'GET' }, GetGlassFees: { - url: `${PARTS_BASE_URL}/glass-fees`, + url: `${PARTS_V1_BASE_URL}/glass-fees`, method: 'GET' }, GetSupportingItems: { - url: `${PARTS_BASE_URL}/supporting-items`, + url: `${PARTS_V1_BASE_URL}/supporting-items`, method: 'POST' }, GetMobileFeePart: { - url: `${PARTS_BASE_URL}/mobile-fee`, + url: `${PARTS_V2_BASE_URL}/mobile-fee`, method: 'GET' }, GetServiceabilityDetails: {