Merge pull request #3134 from Safelite/rlsmerge/2026.04.09-to-develop

Rlsmerge/2026.04.09 to develop
This commit is contained in:
Chris 2026-04-07 08:13:36 -04:00 committed by GitHub
commit db38bc343f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 207 additions and 18932 deletions

View file

@ -18,6 +18,7 @@ const GaCategories = {
INSHOP_CONFIRMATION_CLICKED: "inshop confirmation clicked", INSHOP_CONFIRMATION_CLICKED: "inshop confirmation clicked",
MOBILE_CONFIRMATION_CLICKED: "mobile confirmation clicked", MOBILE_CONFIRMATION_CLICKED: "mobile confirmation clicked",
RECAL_DISCLAIMER: "Recalibration disclaimer", RECAL_DISCLAIMER: "Recalibration disclaimer",
VIN_ELIGIBLE: "VIN eligible",
}; };
const GaActions = { const GaActions = {
@ -32,6 +33,8 @@ const GaActions = {
MORE_LOCATIONS_CLICKED: "more_locations_clicked", MORE_LOCATIONS_CLICKED: "more_locations_clicked",
YES: "yes", YES: "yes",
NO: "no", NO: "no",
TRUE: "true",
FALSE: "false",
ACKNOWLEDGED: "Acknowledged", ACKNOWLEDGED: "Acknowledged",
}; };

File diff suppressed because it is too large Load diff

View file

@ -20,6 +20,8 @@ const storeMutations = {
UPDATE_VEHICLE_VIN: "updateVehicleVin", UPDATE_VEHICLE_VIN: "updateVehicleVin",
UPDATE_VEHICLE: "updateVehicle", UPDATE_VEHICLE: "updateVehicle",
UPDATE_VEHICLE_CAN_SAFELITE_SERVICE: "updateVehicleCanSafeliteService", UPDATE_VEHICLE_CAN_SAFELITE_SERVICE: "updateVehicleCanSafeliteService",
UPDATE_SKIP_VIN_LOOKUP: "updateSkipVINLookup",
UPDATE_SKIP_PART_QUESTIONS: "updateSkipPartQuestions",
UPDATE_IS_REPAIR: "updateIsRepair", UPDATE_IS_REPAIR: "updateIsRepair",
UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips", UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips",

View file

@ -85,6 +85,7 @@
ref="alertMSRNotCoveredByInsurance" ref="alertMSRNotCoveredByInsurance"
class="mt-5 mb-5" class="mt-5 mb-5"
v-if="isMSRFeeNotCoveredByInsurance" v-if="isMSRFeeNotCoveredByInsurance"
:leftAlignCopy="true"
:manualHeadline="MSRFeeAlertHeadlineText" :manualHeadline="MSRFeeAlertHeadlineText"
:manualCopy="MSRFeeAlertBodyText" :manualCopy="MSRFeeAlertBodyText"
:showWarningIcon="true" :showWarningIcon="true"

View file

@ -17,6 +17,7 @@ import store from "@/store";
import { validate } from "vee-validate"; import { validate } from "vee-validate";
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js"; import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper"; import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper";
import { externalParameterStatus } from "@/constants/external-parameters";
// Mock our module for promises. // Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({ jest.mock("@/helpers/layout-helper.js", () => ({
@ -817,7 +818,11 @@ describe("vehicle-damage.vue", () => {
describe("vehicle-damage.vue", () => { describe("vehicle-damage.vue", () => {
test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => { test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
// Set up the store with isExternalParameter as true // Set up the store with isExternalParameter as true
store.getters.externalParameterState = { isExternalParameter: true }; store.getters.externalParameterState = {
isExternalParameter: true,
isExternalParameter: externalParameterStatus.ACTIVE,
vehicleDamage: { damageType: "windshieldReplace" },
};
store.getters.externalParameterDamage = { store.getters.externalParameterDamage = {
damageType: "windshieldReplace", damageType: "windshieldReplace",
isRepair: false, isRepair: false,

View file

@ -100,6 +100,7 @@ import store from "@/store";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
import { nextTick } from "vue"; import { nextTick } from "vue";
import { getBoolFromString } from "@/helpers/boolean-helper"; import { getBoolFromString } from "@/helpers/boolean-helper";
import { externalParameterStatus } from "@/constants/external-parameters";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED)); defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED));
@ -143,7 +144,12 @@ export default {
vm.$refs.backGlassOptions.initializeComponent( vm.$refs.backGlassOptions.initializeComponent(
resultMap.damageOptions.backGlassOptions.availableReplacementOptions resultMap.damageOptions.backGlassOptions.availableReplacementOptions
); );
if (store.getters.externalParameterState?.isExternalParameter) { if (
store.getters.externalParameterState?.isExternalParameter &&
store.getters.externalParameterState?.isExternalParameter ===
externalParameterStatus.ACTIVE &&
store.getters.externalParameterState.vehicleDamage?.damageType
) {
await nextTick(); await nextTick();
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm); const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
debugLog(`**** vehicle-damage is form valid: ${isValid} ****`); debugLog(`**** vehicle-damage is form valid: ${isValid} ****`);
@ -355,7 +361,12 @@ export default {
) { ) {
windShieldOptions.selectedWindshieldDamageType = windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPLACE; damageLocationsSelected.REPLACE;
if (!store.getters.externalParameterState.isExternalParameter) { if (
!store.getters.externalParameterState.isExternalParameter ||
store.getters.externalParameterState?.isExternalParameter !==
externalParameterStatus.ACTIVE ||
!store.getters.externalParameterState.vehicleDamage?.damageType
) {
windShieldOptions.selectedWindshieldReplaceOptions.push( windShieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.SINGLE damageLocationsSelected.SINGLE
); );
@ -488,6 +499,14 @@ export default {
policy.policyNumber !== ""; policy.policyNumber !== "";
const skipVin = await skipVinLookup(); const skipVin = await skipVinLookup();
this.pushEventToGA(
this.GaCategories.VIN_ELIGIBLE,
skipVin ? this.GaActions.FALSE : this.GaActions.TRUE,
this.$store.getters.vehicle.carId,
true
);
if (vehicleChangedDuringPolicyLookupInHeritage) { if (vehicleChangedDuringPolicyLookupInHeritage) {
if (skipVin) { if (skipVin) {
this.$router.navigateWithSaving( this.$router.navigateWithSaving(
@ -666,12 +685,21 @@ export default {
return this.$store.getters.requiresVerifiedRedirecting; return this.$store.getters.requiresVerifiedRedirecting;
}, },
isContinueEnabled() { isContinueEnabled() {
return ( var isEnabled =
this.isAnyDamageSelected && this.isAnyDamageSelected &&
this.isWindshieldValid && this.isWindshieldValid &&
this.isRearGlassValid && this.isRearGlassValid &&
this.isSideGlassValid this.isSideGlassValid;
); if (!isEnabled) {
debugLog(
`************************ isContinueEnabled: ${isEnabled} *****************************`
);
debugLog(`**** this.isAnyDamageSelected: ${this.isAnyDamageSelected} ****`);
debugLog(`**** this.isWindshieldValid: ${this.isWindshieldValid} ****`);
debugLog(`**** this.isRearGlassValid: ${this.isRearGlassValid} ****`);
debugLog(`**** this.isSideGlassValid: ${this.isSideGlassValid} ****`);
}
return isEnabled;
}, },
isAnyDamageSelected() { isAnyDamageSelected() {
return this.selectedDamageLocations != null && this.selectedDamageLocations.length != 0; return this.selectedDamageLocations != null && this.selectedDamageLocations.length != 0;

View file

@ -141,6 +141,8 @@ export default {
styleOptions: [], styleOptions: [],
displayNoServiceAlert: false, displayNoServiceAlert: false,
canSafeliteService: this.canSafeliteServiceFromStore(), canSafeliteService: this.canSafeliteServiceFromStore(),
skipVINLookup: this.getSkipVINLookupfromStore(),
skipPartQuestions: this.getSkipPartQuestionsfromStore(),
}; };
}, },
@ -486,6 +488,8 @@ export default {
this.vehicleSubType = result?.data.vehicleSubType; this.vehicleSubType = result?.data.vehicleSubType;
this.vehicleSpecialClass = result?.data.vehicleSpecialClass; this.vehicleSpecialClass = result?.data.vehicleSpecialClass;
this.isBigTruck = result?.data.isBigTruck; this.isBigTruck = result?.data.isBigTruck;
this.skipVINLookup = result?.data.skipVINLookup;
this.skipPartQuestions = result?.data.skipPartQuestions;
}, },
resetAlert() { resetAlert() {
this.displayNoServiceAlert = false; this.displayNoServiceAlert = false;
@ -542,6 +546,8 @@ export default {
imageUrl: this.imageUrl, imageUrl: this.imageUrl,
imageVifNumber: this.imageVifNumber, imageVifNumber: this.imageVifNumber,
imageVifColor: this.imageVifColor, imageVifColor: this.imageVifColor,
skipVINLookup: this.skipVINLookup,
skipPartQuestions: this.skipPartQuestions,
}, },
false false
); );
@ -627,6 +633,12 @@ export default {
getIsBigTruckfromStore() { getIsBigTruckfromStore() {
return store.getters.vehicle.isBigTruck; return store.getters.vehicle.isBigTruck;
}, },
getSkipVINLookupfromStore() {
return store.getters.vehicle.skipVINLookup;
},
getSkipPartQuestionsfromStore() {
return store.getters.vehicle.skipPartQuestions;
},
}, },
components: { components: {

View file

@ -5,6 +5,8 @@ import { storeActions } from "@/constants/store-actions";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store"; import store from "@/store";
import { getIsWindshieldOnly } from "@/helpers/damage-helper";
import { partTypeStrings } from "@/constants/part-type-strings";
export default { export default {
methods: { methods: {
@ -367,8 +369,13 @@ export default {
const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions); const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions);
const hasCapabilityQuestions = this.hasCapabilityQuestions(partsOrQuestions); const hasCapabilityQuestions = this.hasCapabilityQuestions(partsOrQuestions);
const isWindshieldOnly =
getIsWindshieldOnly().toUpperCase() === partTypeStrings.WINDSHIELD;
const skipPartQuestions = store.getters.vehicle?.skipPartQuestions && isWindshieldOnly;
if ( if (
hasPartQuestions && hasPartQuestions &&
!skipPartQuestions &&
this.currentPageComesBeforePage(currentPage, routeData.PART_QUESTIONS.name) this.currentPageComesBeforePage(currentPage, routeData.PART_QUESTIONS.name)
) { ) {
self.$router.navigateWithPageData( self.$router.navigateWithPageData(

View file

@ -9,7 +9,6 @@ import { storeActions } from "@/constants/store-actions";
import { applicationConfig } from "@/constants/application-config"; import { applicationConfig } from "@/constants/application-config";
import { experimentTriggers } from "@/constants/experiments"; import { experimentTriggers } from "@/constants/experiments";
import { damageLocationsSelected } from "@/constants/damage-locations-selected"; import { damageLocationsSelected } from "@/constants/damage-locations-selected";
import { singleWindshieldCarIds } from "@/constants/single-windshield-carids";
import { routeData } from "@/router/constants/routes"; import { routeData } from "@/router/constants/routes";
import { import {
deleteFunnelCookie, deleteFunnelCookie,
@ -83,6 +82,8 @@ const getDefaultState = () => {
registration: { registration: {
licensePlate: null, licensePlate: null,
}, },
skipVINLookup: false,
skipPartQuestions: false,
}, },
serviceLocation: { serviceLocation: {
address: null, address: null,
@ -252,6 +253,15 @@ export const mutations = {
updateVehicleCanSafeliteService(state, canSafeliteService) { updateVehicleCanSafeliteService(state, canSafeliteService) {
state.order.vehicle.canSafeliteService = canSafeliteService; state.order.vehicle.canSafeliteService = canSafeliteService;
}, },
updateSkipVINLookup(state, skipVINLookup) {
state.order.vehicle.skipVINLookup = skipVINLookup;
},
updateSkipPartQuestions(state, skipPartQuestions) {
state.order.vehicle.skipPartQuestions = skipPartQuestions;
},
updateSkipCapabilitiesQuestions(state, skipCapabilitiesQuestions) {
state.order.vehicle.skipCapabilitiesQuestions = skipCapabilitiesQuestions;
},
updateVehicleImageUrl(state, imageUrl) { updateVehicleImageUrl(state, imageUrl) {
state.order.vehicle.imageUrl = imageUrl; state.order.vehicle.imageUrl = imageUrl;
}, },
@ -1597,6 +1607,7 @@ export const actions = {
operatorId: "WEB", operatorId: "WEB",
userName: "SafeliteConceptFunnel", userName: "SafeliteConceptFunnel",
referrer: referrer, referrer: referrer,
clientData: window.location.search,
}; };
return globalMethods return globalMethods
@ -2691,6 +2702,8 @@ export const actions = {
imageUrl, imageUrl,
imageVifNumber, imageVifNumber,
imageVifColor, imageVifColor,
skipVINLookup,
skipPartQuestions,
} }
) { ) {
if ( if (
@ -2716,6 +2729,8 @@ export const actions = {
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor);
context.commit(storeMutations.UPDATE_SKIP_VIN_LOOKUP, skipVINLookup);
context.commit(storeMutations.UPDATE_SKIP_PART_QUESTIONS, skipPartQuestions);
} }
}, },
@ -3510,25 +3525,7 @@ export const actions = {
}, },
isVinOptionalVehicle(context) { isVinOptionalVehicle(context) {
//Optional for carIds with only a single windshield if (context.state.order.vehicle.skipVINLookup) return true;
if (singleWindshieldCarIds.find((item) => item === context.state.order.vehicle.carId)) {
return true;
}
//Optional for specific YMMSs
switch (context.state.order.vehicle.make.toLowerCase()) {
case "porsche":
return true;
default:
}
if (
(context.state.order.vehicle.make.toLowerCase() === "bmw" &&
context.state.order.vehicle.year < 2010) ||
(context.state.order.vehicle.make.toLowerCase() === "volkswagen" &&
context.state.order.vehicle.year < 2010) ||
(context.state.order.vehicle.make.toLowerCase() === "audi" &&
context.state.order.vehicle.year < 2010)
)
return true;
return false; return false;
}, },

View file

@ -4261,6 +4261,8 @@ describe("isVinOptionalVehicle", () => {
}, },
}; };
mutations.updateSkipVINLookup(context.state, expectedVinSkip);
var vinOptionalResult = actions.isVinOptionalVehicle(context); var vinOptionalResult = actions.isVinOptionalVehicle(context);
expect(vinOptionalResult).toEqual(expectedVinSkip); expect(vinOptionalResult).toEqual(expectedVinSkip);
} }
@ -4291,6 +4293,8 @@ describe("isVinOptionalVehicle", () => {
}, },
}; };
mutations.updateSkipVINLookup(context.state, expectedVinSkip);
var vinOptionalResult = actions.isVinOptionalVehicle(context); var vinOptionalResult = actions.isVinOptionalVehicle(context);
expect(vinOptionalResult).toEqual(expectedVinSkip); expect(vinOptionalResult).toEqual(expectedVinSkip);
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="alert fade show text-center mb-0 py-2 px-4 border-0" class="alert fade show mb-0 py-2 px-4 border-0"
role="alert" role="alert"
v-on="{ 'close.bs.alert': onAlertClose }" v-on="{ 'close.bs.alert': onAlertClose }"
:class="[ :class="[
@ -8,8 +8,9 @@
this.alertClass, this.alertClass,
this.cssClassNameForCmsWidget, this.cssClassNameForCmsWidget,
this.hasBorder ? 'bordered' : '', this.hasBorder ? 'bordered' : '',
this.leftAlignCopy ? 'text-start' : 'text-center',
]"> ]">
<p class="mx-6 my-0 fw-bold alert-heading"> <p class="my-0 fw-bold alert-heading" :class="leftAlignCopy ? 'mx-4' : 'mx-6'">
<img v-if="showInfoIcon" :src="infoIcon" alt="Info Icon" class="info-icon" /> <img v-if="showInfoIcon" :src="infoIcon" alt="Info Icon" class="info-icon" />
<img <img
v-if="showWarningIcon" v-if="showWarningIcon"
@ -74,6 +75,7 @@ export default {
hasBorder: Boolean, hasBorder: Boolean,
isDismissible: Boolean, isDismissible: Boolean,
alertClass: String, alertClass: String,
leftAlignCopy: Boolean,
/* /*
alertClass class names: alertClass class names:
alert-success (green) alert-success (green)
@ -198,35 +200,117 @@ export default {
} }
&.alert-info { &.alert-info {
background-color: $blue-100; background-color: $blue-100;
.alert-heading {
color: $blue-700; &.bordered {
border-color: $blue-400;
}
&.text-start {
.alert-heading {
display: flex;
align-items: center;
color: $black;
font-weight: 700;
.warning-icon {
margin-right: 0.5rem;
}
}
hr {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
border-color: $blue-400;
}
}
&.text-center {
.alert-heading {
color: $blue-700;
}
} }
svg { svg {
fill: $blue-700; fill: $blue-700;
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
} }
hr {
border-color: $red-800;
}
} }
&.alert-danger { &.alert-danger {
background-color: $red-100; background-color: $red-100;
.alert-heading {
color: $red-600; &.bordered {
border-color: $red-400;
}
&.text-start {
.alert-heading {
display: flex;
align-items: center;
color: $black;
font-weight: 700;
.warning-icon {
margin-right: 0.5rem;
}
}
hr {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
border-color: $red-400;
}
}
&.text-center {
.alert-heading {
color: $red-600;
}
} }
svg { svg {
fill: $red-600; fill: $red-600;
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
} }
hr {
border-color: $red-800;
}
} }
&.alert-warning { &.alert-warning {
background-color: $yellow-100; background-color: $yellow-100;
.alert-heading {
color: $yellow-600;
&.bordered {
border-color: $yellow-400;
}
&.text-start {
.alert-heading {
display: flex;
align-items: center;
color: $black;
font-weight: 700;
.warning-icon {
margin-right: 0.5rem;
}
}
hr {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
border-color: $yellow-400;
}
}
&.text-center {
.alert-heading {
color: $yellow-600;
}
}
.alert-heading {
.warning-icon { .warning-icon {
height: 1rem; height: 1rem;
} }
} }
svg { svg {
fill: $yellow-600; fill: $yellow-600;
width: 1rem; width: 1rem;
@ -238,14 +322,42 @@ export default {
} }
&.alert-success { &.alert-success {
background-color: $green-100; background-color: $green-100;
.alert-heading {
color: $green-700; &.bordered {
border-color: $green-400;
}
&.text-start {
.alert-heading {
display: flex;
align-items: center;
color: $black;
font-weight: 700;
.warning-icon {
margin-right: 0.5rem;
}
}
hr {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
border-color: $green-400;
}
}
&.text-center {
.alert-heading {
color: $green-700;
}
} }
svg { svg {
fill: $green-700; fill: $green-700;
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
} }
hr {
border-color: $green-800;
}
} }
&.bordered { &.bordered {
border: 1px solid; border: 1px solid;