diff --git a/src/constants/analytics.js b/src/constants/analytics.js index dc023b15..be55304d 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -14,6 +14,7 @@ const GaEvents = Object.freeze({ const GaCategories = Object.freeze({ API_RESPONSE: 'Api_Response', + BAILOUT: 'Bailout', CONFIRMATION_CLICKED: "confirmation clicked", CONFIRMATION_CLICKED_INSHOP: "inshop confirmation clicked", CONFIRMATION_CLICKED_MOBILE: "mobile confirmation clicked", @@ -57,4 +58,10 @@ const analyticsServicePackageMap = new Map([ [packageNames.TIER_THREE, 'essentialsplus'] ]); -export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes, analyticsPaymentTypeMap, analyticsServicePackageMap }; +const analyticsVapsModalMap = new Map([ + ['FrontWiperModal', 'wiper'], + ['RearWiperModal', 'wiper'], + ['RainDefenseModal', 'raindefense'], +]); + +export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes, analyticsPaymentTypeMap, analyticsServicePackageMap, analyticsVapsModalMap }; diff --git a/src/iss-components/cart-dropdown/cart-dropdown.vue b/src/iss-components/cart-dropdown/cart-dropdown.vue index a24586c7..3bbfacb4 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.vue +++ b/src/iss-components/cart-dropdown/cart-dropdown.vue @@ -42,7 +42,7 @@ linkType="text" text="Remove" href="javascript:void(0)" - @clickEvent="removeVap(item.partType)"> + @clickEvent="removeVap(item.partType, item?.name ?? '')"> @@ -367,8 +367,15 @@ export default { ?.filter((vapsLineItem) => vapsLineItem.partType === partType) ?? []; return this.getCartItem(label, lineItems, cartItemType.VAP, partType); }, - removeVap(partType) { + removeVap(partType, partName) { const newVaps = useMainStore().lineItems.vaps?.filter((vap) => vap?.partType !== partType) ?? []; + if (partType === partTypeStrings.FRONT_WIPER) { + this.pushEventToGA('Removed From Cart', 'Product - Front Wiper', 'Safelite advanced', true); + } else if (partType === partTypeStrings.REAR_WIPER) { + this.pushEventToGA('Removed From Cart', 'Product - Rear Wiper', 'Safelite advanced', true); + } else { + this.pushEventToGA('Removed From Cart', `Product - Other - ${partName}`, 'none', true); + } useMainStore().updateVaps(newVaps); useMainStore().updateServicePackage(null); }, diff --git a/src/layouts/bailout-page/bailout-page.vue b/src/layouts/bailout-page/bailout-page.vue index 8617ce77..dbd250d4 100644 --- a/src/layouts/bailout-page/bailout-page.vue +++ b/src/layouts/bailout-page/bailout-page.vue @@ -88,8 +88,10 @@ import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue'; import textBlock from '@/digital-components/text-block/text-block.vue'; // Supporting files +import analyticsMixIn from '@/mixins/analytics-mixin.js'; import BaseFormMixin from '@/mixins/base-form-mixin.js'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; +import { GaCategories } from '@/constants/analytics'; import globalRules from '@/constants/global-rules'; import settleAllPromises from '@/helpers/layout-helper'; import { useMainStore } from '@/store'; @@ -111,8 +113,17 @@ export default { Form, textBlock }, - mixins: [BaseFormMixin], + mixins: [BaseFormMixin, analyticsMixIn], async beforeRouteEnter(to, from, next) { + const fromPage = from?.query?.issPage || 'external'; + // Only log bailout event if user is coming from a different page. + // This ensures that we cannot get stuck in a loop, if the bailout-page ever bails out. + if (fromPage !== issPageValues.BAILOUT_PAGE) { + const bailoutCode = useMainStore().pageData(issPageValues.BAILOUT_PAGE).bailoutCode; + const bailoutString = Object.keys(BailoutCode).find(key => BailoutCode[key] === bailoutCode); + analyticsMixIn.methods.pushEventToGA(GaCategories.BAILOUT, bailoutString, fromPage, true, null, null); + } + // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); // Settle promises and get results diff --git a/src/layouts/payment-method/payment-method-question/payment-method-question.vue b/src/layouts/payment-method/payment-method-question/payment-method-question.vue index 33d82adf..87980750 100644 --- a/src/layouts/payment-method/payment-method-question/payment-method-question.vue +++ b/src/layouts/payment-method/payment-method-question/payment-method-question.vue @@ -20,6 +20,7 @@ import buttonQuestion from '@/digital-components/button-question/button-question import widgetFields from '@/constants/cms-widget-fields.js'; import paymentMethodListButton from './payment-method-list-button/payment-method-list-button.vue'; import { markRaw } from 'vue'; +import { analyticsPaymentTypeMap } from '@/constants/analytics'; export default { name: 'payment-method-question', @@ -43,6 +44,8 @@ export default { return this.modelValue; }, set(selectedMethod) { + const paymentMethod = analyticsPaymentTypeMap.get(this.piaType) ?? 'UNKNOWN'; + this.pushEventToGA('submit_order', 'payment_type_selected', paymentMethod, true); this.$emit('update:modelValue', selectedMethod); } }, diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 0c3d0c7d..210f6fd4 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -156,6 +156,9 @@ import { getCartTotal } from '@/helpers/cart-helper'; import { formatAmountInDollars } from '@/helpers/text-helper'; import widgetFields from '@/constants/cms-widget-fields'; import MaskaFormattedMasks from '@/constants/maska-masks'; +import { containsRecalParts } from '@/helpers/recal-helper'; +import coverageType from '@/constants/coverage-type'; +import partTypeStrings from '@/constants/part-type-strings'; export default { name: 'payment-method', @@ -197,6 +200,9 @@ export default { vm.updateFooterButtonText(vm.customCallToActionButtonCopy); }); }, + mounted() { + this.handleAnalyticsEventsOnMounted(); + }, data() { return { paymentMethod: null, @@ -411,6 +417,7 @@ export default { this.$refs.siteFooter.updateButtonText(newValue); }, async forwardButtonAction() { + this.handleAnalyticsEventsOnForwardButtonAction(); this.mainStore.savePaymentMethodChoice(this.paymentMethod); this.savePageDataToStore(issPageValues.PAYMENT_METHOD, { smsOptIn: this.smsOptIn }); if (!this.hideSMSOptIn) { @@ -460,6 +467,7 @@ export default { handleEditClicked(section) { switch (section) { case 'location': + this.pushEventToGA('order_summary', 'change', 'location', true); const scenario = this.mainStore.isMobileAppointment ? this.navigationScenarios.EDIT_SERVICE_LOCATION_MOBILE : this.navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP; @@ -469,6 +477,7 @@ export default { ); break; case 'schedule': + this.pushEventToGA('order_summary', 'change', 'time', true); this.$router.navigateWithSpinner( this.navigationScenarios.EDIT_SCHEDULE, this.$route @@ -499,6 +508,58 @@ export default { }, closeContactDetailsWithoutSaving() { this.smsOptIn = 'No'; + }, + handleAnalyticsEventsOnMounted() { + const mobileOrInshop = this.mainStore.isMobileAppointment ? 'mobile' : 'in_shop'; + const verifiedOrNotVerified = this.mainStore.isVerified ? 'verified' : 'not_verified'; + const repairOrReplace = this.mainStore.damage.isRepair ? 'repair' : 'replace'; + this.pushEventToGA('order_summary', 'safelite', `${mobileOrInshop}_${repairOrReplace}_${verifiedOrNotVerified}`, true); + + if (containsRecalParts(this.mainStore.lineItems)) { + let coverageTypeForLabel = ''; + if (this.mainStore.isITAC) { + coverageTypeForLabel = 'ITAC'; + } else if (this.mainStore.isNoComp) { + coverageTypeForLabel = 'no_comp'; + } else if (this.mainStore.isVerified) { + coverageTypeForLabel = 'verified'; + } else { + coverageTypeForLabel = 'unverified'; + } + const recalibrationType = this.mainStore.lineItems.glassParts?.find((part) => part.requiresRecalibration)?.recalibrationType; + this.pushEventToGA(`recalibration_added_${coverageTypeForLabel}`, this.mainStore.vehicle.carId, `recal_type_${recalibrationType}`.replace(/ /g, '_').toLowerCase(), true); + } + + const hasFrontWiper = this.mainStore.lineItems?.vaps?.some((part) => part.partType === partTypeStrings.FRONT_WIPER); + const hasRearWiper = this.mainStore.lineItems?.vaps?.some((part) => part.partType === partTypeStrings.REAR_WIPER); + const wiperEventLabel = `front_${hasFrontWiper ? 'advanced' : 'none'}_rear_${hasRearWiper ? 'advanced' : 'none'}`; + this.pushEventToGA('wipers', 'attached_to_order', wiperEventLabel, true); + }, + handleAnalyticsEventsOnForwardButtonAction() { + this.pushEventToGA('order_summary', 'edit', 'contact_details', true); + let action; + let label; + switch (this.mainStore.insuranceCoverage?.coverageType) { + case coverageType.Deductible: + action = 'Covered'; + label = this.mainStore.currentDeductible; + break; + case coverageType.ITAC: + action = 'ITAC'; + label = this.mainStore.currentDeductible + "_" + getCartTotal(this.mainStore.order); + break; + case coverageType.NO_COMP: + action = 'NOCOMP'; + label = 'Unverified'; + break; + case coverageType.NONE: + default: + action = 'Unverified'; + label = 'Unverified'; + break; + d + } + this.pushEventToGA('Submit Appointment', action, label, true, null, 1); } } }; diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index 5951d611..ccfa4629 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -115,7 +115,8 @@ export default { }, textLinkEmit(copy) { this.$parent.$emit('link-event', { - args: getRouterLinkRouteFromCopy(copy) + route: getRouterLinkRouteFromCopy(copy), + packageName: this.value }); } } diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue index 1e378490..1060572f 100644 --- a/src/layouts/service-packages/service-packages.vue +++ b/src/layouts/service-packages/service-packages.vue @@ -81,6 +81,7 @@ import servicePackageQuestion from '@/layouts/service-packages/service-package-q import issPageValues from '@/router/router-constants/issPage-values'; import bailoutMessage from '@/constants/bailoutMessage'; import buttonMain from '@/ux-components/button-main/button-main.vue'; +import { analyticsServicePackageMap, analyticsVapsModalMap } from '@/constants/analytics'; const store = useMainStore(); @@ -205,8 +206,11 @@ export default { } }, methods: { - openModalAction(modalName) { - this.$refs[modalName.args].openModal(); + openModalAction(eventData) { + this.$refs[eventData.route].openModal(); + const packageName = analyticsServicePackageMap.get(eventData.packageName) ?? 'UNKNOWN_PACKAGE'; + const modalName = analyticsVapsModalMap.get(eventData.route) ?? 'UNKNOWN_MODAL'; + this.pushEventToGA('service_package', 'modal_displayed', `${packageName}_${modalName}_modal_displayed`, true); }, arePagePrerequisitesValid() { return ( @@ -238,6 +242,9 @@ export default { store.updateVaps(this.selectedVaps); store.updateServicePackage(this.selectedPackageTier); + const packageName = analyticsServicePackageMap.get(this.selectedPackageTier) ?? 'UNKNOWN_PACKAGE'; + this.pushEventToGA('service_package', 'attached_a_package', packageName, true); + const scenario = store.isMobileAppointment ? this.navigationScenarios.CLICKED_FORWARD_MOBILE : this.navigationScenarios.CLICKED_FORWARD_INSHOP; diff --git a/src/router/router-constants/router-titles.js b/src/router/router-constants/router-titles.js index e6b8d0a6..09697588 100644 --- a/src/router/router-constants/router-titles.js +++ b/src/router/router-constants/router-titles.js @@ -9,7 +9,7 @@ const routerTitles = Object.freeze({ [issPageValues.ADDRESS_LOOKUP]: `Address Lookup ${titleSuffix}`, [issPageValues.ADDRESS_VEHICLES]: `Address Vehicles ${titleSuffix}`, - [issPageValues.BAILOUT_PAGE]: `Bailout Page ${titleSuffix}`, + [issPageValues.BAILOUT_PAGE]: `Need Help ${titleSuffix}`, [issPageValues.CAPABILITY_QUESTIONS]: `Capability Questions ${titleSuffix}`, [issPageValues.CONTACT_CONFIRMATION]: `Contact Confirmation ${titleSuffix}`, [issPageValues.CONTACT_DETAILS]: `Contact Details ${titleSuffix}`,