Merge branch 'develop' into feature/CASH-1252
This commit is contained in:
commit
b82bda7d01
45 changed files with 1276 additions and 19130 deletions
|
|
@ -15,6 +15,7 @@ module.exports = {
|
||||||
"!src/main.js",
|
"!src/main.js",
|
||||||
"!src/constants/*.js",
|
"!src/constants/*.js",
|
||||||
"!src/router/**/*.js",
|
"!src/router/**/*.js",
|
||||||
|
"src/router/methods/error.js", // Re-include after the exclusion above (Jest applies later positive globs as overrides).
|
||||||
"!src/helpers/unit-test-helper.js",
|
"!src/helpers/unit-test-helper.js",
|
||||||
"!src/helpers/logger.js",
|
"!src/helpers/logger.js",
|
||||||
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
|
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
|
||||||
|
|
|
||||||
10
src/App.vue
10
src/App.vue
|
|
@ -64,6 +64,16 @@ window.onerror = (msg, url, line, col, error) => {
|
||||||
var suppressErrorAlert = true;
|
var suppressErrorAlert = true;
|
||||||
return suppressErrorAlert;
|
return suppressErrorAlert;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Log Promise rejections that are never handled (.catch / await try/catch), e.g. fire-and-forget async.
|
||||||
|
window.addEventListener("unhandledrejection", (event) => {
|
||||||
|
const reason = event.reason;
|
||||||
|
const message = reason instanceof Error ? reason.message : String(reason);
|
||||||
|
global.$logger.logError(`unhandledrejection: ${message}`, {
|
||||||
|
stack: reason instanceof Error ? reason.stack : undefined,
|
||||||
|
reason: reason instanceof Error ? undefined : reason,
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ const endpoints = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
GetSupportingItems: {
|
GetSupportingItems: {
|
||||||
url: "/parts/api/v1/parts/supporting-items",
|
url: "/parts/api/v2/parts/supporting-items",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
GetRecalPart: {
|
GetRecalPart: {
|
||||||
|
|
@ -180,10 +180,6 @@ const endpoints = {
|
||||||
url: "/analytics/api/v1/analytics/log-part-questions",
|
url: "/analytics/api/v1/analytics/log-part-questions",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
LogDigitalConsumer: {
|
|
||||||
url: "/analytics/api/v1/analytics/digitalconsumer-log",
|
|
||||||
method: "POST",
|
|
||||||
},
|
|
||||||
LogFmgSessionData: {
|
LogFmgSessionData: {
|
||||||
url: "/analytics/api/v1/analytics/digitalconsumer-session-logging",
|
url: "/analytics/api/v1/analytics/digitalconsumer-session-logging",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -196,6 +192,10 @@ const endpoints = {
|
||||||
url: "/experiments/api/v1/experiments/run",
|
url: "/experiments/api/v1/experiments/run",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
|
ForceExperiments: {
|
||||||
|
url: "/experiments/api/v1/experiments/forceVariations",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
GetInsuranceCompanyList: {
|
GetInsuranceCompanyList: {
|
||||||
url: "/account/api/v1/account/insurance-companies",
|
url: "/account/api/v1/account/insurance-companies",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ const experimentSettings = {
|
||||||
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold",
|
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold",
|
||||||
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold",
|
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold",
|
||||||
DISPLAY_MSR: "DisplayMSR",
|
DISPLAY_MSR: "DisplayMSR",
|
||||||
|
ENABLE_MSR_SPLIT_PAY: "EnableMSRSplitPay",
|
||||||
DISPLAY_WAITLIST: "DisplayWaitlist2.0",
|
DISPLAY_WAITLIST: "DisplayWaitlist2.0",
|
||||||
WAITLIST_THRESHOLD_DAYS: "Waitlist_Threshold_Days",
|
WAITLIST_THRESHOLD_DAYS: "Waitlist_Threshold_Days",
|
||||||
PRICING_BY_DAY: "DisplayPricingByDay",
|
PRICING_BY_DAY: "DisplayPricingByDay",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
export const sessionStorageKeyConstants = {
|
export const sessionStorageKeyConstants = {
|
||||||
SUBMITTED_STATE: "submittedState",
|
SUBMITTED_STATE: "submittedState",
|
||||||
|
/** Counts SuppressConceptFunnel → heritage redirects to avoid an infinite loop in one tab session. */
|
||||||
|
HERITAGE_SUPPRESS_REDIRECT_COUNT: "heritageSuppressRedirectCount",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -47,6 +47,7 @@ const storeActions = {
|
||||||
TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "taxOrderItemsAndSaveServerData",
|
TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "taxOrderItemsAndSaveServerData",
|
||||||
GET_EXPERIMENTS_BY_USER: "GetExperimentsByUser",
|
GET_EXPERIMENTS_BY_USER: "GetExperimentsByUser",
|
||||||
RUN_EXPERIMENTS_FOR_TRIGGER: "runExperimentsForTrigger",
|
RUN_EXPERIMENTS_FOR_TRIGGER: "runExperimentsForTrigger",
|
||||||
|
FORCE_EXPERIMENTS: "forceExperiments",
|
||||||
CLEAR_VIN: "clearVin",
|
CLEAR_VIN: "clearVin",
|
||||||
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",
|
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",
|
||||||
GET_SIGNATURE: "getSignature",
|
GET_SIGNATURE: "getSignature",
|
||||||
|
|
@ -63,7 +64,6 @@ const storeActions = {
|
||||||
LOG_CUSTOM_EVENT: "logCustomEvent",
|
LOG_CUSTOM_EVENT: "logCustomEvent",
|
||||||
INITIALIZE_SESSION: "initializeSession",
|
INITIALIZE_SESSION: "initializeSession",
|
||||||
LOG_PART_QUESTIONS: "logPartQuestions",
|
LOG_PART_QUESTIONS: "logPartQuestions",
|
||||||
LOG_DIGITALCONSUMER: "logDigitalConsumer",
|
|
||||||
LOG_FMG_SESSION_DATA: "logFmgSessionData",
|
LOG_FMG_SESSION_DATA: "logFmgSessionData",
|
||||||
|
|
||||||
// DEPENDENCY MUTATIONS
|
// DEPENDENCY MUTATIONS
|
||||||
|
|
@ -114,6 +114,7 @@ const storeActions = {
|
||||||
SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling",
|
SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling",
|
||||||
SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected",
|
SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected",
|
||||||
SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable",
|
SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable",
|
||||||
|
SAVE_IS_MSR_FEE_COVERED_BY_INSURANCE: "saveIsMSRFeeCoveredByInsurance",
|
||||||
|
|
||||||
CREATE_SUBMITTED_STATE: "createSubmittedState",
|
CREATE_SUBMITTED_STATE: "createSubmittedState",
|
||||||
RESET_SUBMITTED_STATE: "resetSubmittedState",
|
RESET_SUBMITTED_STATE: "resetSubmittedState",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
@ -72,6 +74,7 @@ const storeMutations = {
|
||||||
UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "updateIsRecalAcknowledgedForScheduling",
|
UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "updateIsRecalAcknowledgedForScheduling",
|
||||||
UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected",
|
UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected",
|
||||||
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
|
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
|
||||||
|
UPDATE_IS_MSR_FEE_COVERED_BY_INSURANCE: "updateIsMSRFeeCoveredByInsurance",
|
||||||
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",
|
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",
|
||||||
|
|
||||||
// EVENT BUS MUTATIONS
|
// EVENT BUS MUTATIONS
|
||||||
|
|
@ -118,7 +121,6 @@ const storeMutations = {
|
||||||
UPDATE_EXTERNAL_PARAMETER_IS_INSURANCE: "updateExternalParameterIsInsurance",
|
UPDATE_EXTERNAL_PARAMETER_IS_INSURANCE: "updateExternalParameterIsInsurance",
|
||||||
UPDATE_EXTERNAL_PARAMETER_VIN_SELECTION: "updateExternalParameterVinSelection",
|
UPDATE_EXTERNAL_PARAMETER_VIN_SELECTION: "updateExternalParameterVinSelection",
|
||||||
UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE: "updateExternalParameterServicePackage",
|
UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE: "updateExternalParameterServicePackage",
|
||||||
UPDATE_EXTERNAL_PARAMETER_SOURCE: "updateExternalParameterSource",
|
|
||||||
UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER: "updateExternalParameterPhoneNumber",
|
UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER: "updateExternalParameterPhoneNumber",
|
||||||
UPDATE_EXTERNAL_PARAMETER_QS_STASH: "updateExternalParameterQsStash",
|
UPDATE_EXTERNAL_PARAMETER_QS_STASH: "updateExternalParameterQsStash",
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
i % 2 == 0 ? 'even' : 'odd',
|
i % 2 == 0 ? 'even' : 'odd',
|
||||||
cartItem.isRemovable ? 'removable-cart-item' : '',
|
cartItem.isRemovable ? 'removable-cart-item' : '',
|
||||||
]">
|
]">
|
||||||
<span v-if="cartItem != recycleFeeCartItem">{{ cartItem.name }}</span>
|
<span v-if="!this.shouldShowInfoIcon(cartItem)">{{ cartItem.name }}</span>
|
||||||
<textLink
|
<textLink
|
||||||
v-if="allowItemRemoval && cartItem.isRemovable"
|
v-if="allowItemRemoval && cartItem.isRemovable"
|
||||||
ref="removeLink"
|
ref="removeLink"
|
||||||
|
|
@ -87,11 +87,15 @@
|
||||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
|
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
|
||||||
<span v-else-if="cartItem == recycleFeeCartItem">
|
<span v-else-if="cartItem == recycleFeeCartItem">
|
||||||
{{ recycleFeeCartItem.name }}
|
{{ recycleFeeCartItem.name }}
|
||||||
<a
|
<toolTip
|
||||||
href="javascript:void(0)"
|
:cmsWidgetName="recyclingModalCmsWidgetName"
|
||||||
@click="openModal(recyclingModalCmsWidgetName)">
|
:customBodyText="recyclingToolTipBodyText" />
|
||||||
<img :src="infoIcon" alt="Info Icon" class="info-icon" />
|
</span>
|
||||||
</a>
|
<span v-if="cartItem == msrFeeCartItem">
|
||||||
|
{{ msrFeeCartItem.name }}
|
||||||
|
<toolTip
|
||||||
|
:cmsWidgetName="msrModalCmsWidgetName"
|
||||||
|
:customBodyText="msrToolTipBodyText" />
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
|
|
@ -138,6 +142,10 @@
|
||||||
:pageName="pageName"
|
:pageName="pageName"
|
||||||
modalWidgetName="PromoModalWidget"
|
modalWidgetName="PromoModalWidget"
|
||||||
@promoAdded="saveVaps" />
|
@promoAdded="saveVaps" />
|
||||||
|
<removeMsrFeeModal
|
||||||
|
modalWidgetName="RemoveMsrFeeModalWidget"
|
||||||
|
ref="removeMsrFeeModal"
|
||||||
|
@switchToInshop="switchToInshop" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -150,9 +158,6 @@
|
||||||
applied
|
applied
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<contentGroupModal ref="RecycleModal" cmsWidgetName="RecycleModal">
|
|
||||||
<textBlock cmsWidgetName="RecycleTextBlock" />
|
|
||||||
</contentGroupModal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -160,8 +165,9 @@
|
||||||
// Components
|
// Components
|
||||||
import textLink from "@/ux-components/text-link/text-link";
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
import textBlock from "@/digital-components/text-block/text-block";
|
import textBlock from "@/digital-components/text-block/text-block";
|
||||||
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
|
||||||
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
||||||
|
import removeMsrFeeModal from "./remove-msr-fee-modal/remove-msr-fee-modal.vue";
|
||||||
|
import toolTip from "@/ux-components/tool-tip/tool-tip.vue";
|
||||||
|
|
||||||
// Mixins
|
// Mixins
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
@ -183,6 +189,7 @@ import {
|
||||||
getSalesTax,
|
getSalesTax,
|
||||||
getAmountDueWithDonation,
|
getAmountDueWithDonation,
|
||||||
} from "@/helpers/pricing-helper.js";
|
} from "@/helpers/pricing-helper.js";
|
||||||
|
import { formatToUSDollar } from "@/helpers/cms-content-helper";
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { partTypeStrings } from "@/constants/part-type-strings";
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
|
|
@ -191,6 +198,7 @@ import { cartItemTypes } from "@/constants/cart-item-types";
|
||||||
import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance";
|
import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance";
|
||||||
import { experimentSettings } from "@/constants/experiments";
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
import { quotePageDiscountTable } from "@/constants/quote-page-discounts";
|
import { quotePageDiscountTable } from "@/constants/quote-page-discounts";
|
||||||
|
import { partNumberStrings } from "@/constants/part-number-strings";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "cart",
|
name: "cart",
|
||||||
|
|
@ -203,6 +211,7 @@ export default {
|
||||||
pageName: String,
|
pageName: String,
|
||||||
allowItemRemoval: Boolean,
|
allowItemRemoval: Boolean,
|
||||||
recyclingModalCmsWidgetName: String,
|
recyclingModalCmsWidgetName: String,
|
||||||
|
msrModalCmsWidgetName: String,
|
||||||
showAsPaid: Boolean,
|
showAsPaid: Boolean,
|
||||||
isInsurance: Boolean,
|
isInsurance: Boolean,
|
||||||
insuranceDeductible: Number,
|
insuranceDeductible: Number,
|
||||||
|
|
@ -214,6 +223,7 @@ export default {
|
||||||
isExpandedOnLoad: Boolean,
|
isExpandedOnLoad: Boolean,
|
||||||
isCollapsible: { type: Boolean, default: true },
|
isCollapsible: { type: Boolean, default: true },
|
||||||
isMSRFeeApplicable: Boolean,
|
isMSRFeeApplicable: Boolean,
|
||||||
|
IsMSRFeeCoveredByInsurance: Boolean,
|
||||||
donationCartItem: Object,
|
donationCartItem: Object,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -311,6 +321,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeItem(cartItemType, category) {
|
async removeItem(cartItemType, category) {
|
||||||
|
if (cartItemType == cartItemTypes.MOBILE_FEE && this.isMSRFeeApplicable) {
|
||||||
|
this.$refs.removeMsrFeeModal.openModal();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this.lineItems[category] = this.lineItems[category].filter(
|
this.lineItems[category] = this.lineItems[category].filter(
|
||||||
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
|
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
|
||||||
);
|
);
|
||||||
|
|
@ -375,6 +389,12 @@ export default {
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
shouldShowInfoIcon(cartItem) {
|
||||||
|
return cartItem == this.recycleFeeCartItem || cartItem == this.msrFeeCartItem;
|
||||||
|
},
|
||||||
|
switchToInshop() {
|
||||||
|
this.$emit("switchToInshop");
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isCartReadyToLoad() {
|
isCartReadyToLoad() {
|
||||||
|
|
@ -490,6 +510,10 @@ export default {
|
||||||
cartItems.push(this.mobileFeeCartItem);
|
cartItems.push(this.mobileFeeCartItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.msrFeeCartItem) {
|
||||||
|
cartItems.push(this.msrFeeCartItem);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.quotePageDiscountCartItem) {
|
if (this.quotePageDiscountCartItem) {
|
||||||
cartItems.push(this.quotePageDiscountCartItem);
|
cartItems.push(this.quotePageDiscountCartItem);
|
||||||
}
|
}
|
||||||
|
|
@ -892,7 +916,9 @@ export default {
|
||||||
let cartItem = null;
|
let cartItem = null;
|
||||||
let showMobileFeeCartItem = false;
|
let showMobileFeeCartItem = false;
|
||||||
const mobileFeeLineItem = this.supportingItems.find(
|
const mobileFeeLineItem = this.supportingItems.find(
|
||||||
(lineItem) => lineItem.partType == partTypeStrings.MOBILE_FEE
|
(lineItem) =>
|
||||||
|
lineItem.partType == partTypeStrings.MOBILE_FEE &&
|
||||||
|
lineItem.partNumber == partTypeStrings.MOBILE_FEE
|
||||||
);
|
);
|
||||||
|
|
||||||
var mobileTotal =
|
var mobileTotal =
|
||||||
|
|
@ -929,6 +955,72 @@ export default {
|
||||||
}
|
}
|
||||||
return cartItem;
|
return cartItem;
|
||||||
},
|
},
|
||||||
|
msrFeeCartItemName() {
|
||||||
|
return this.getCmsContent("MSRFeeTextWidget", "Text");
|
||||||
|
},
|
||||||
|
getMsrFeeLineItem() {
|
||||||
|
return this.supportingItems.find(
|
||||||
|
(lineItem) =>
|
||||||
|
lineItem.partType == partTypeStrings.MOBILE_FEE &&
|
||||||
|
lineItem.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE
|
||||||
|
);
|
||||||
|
},
|
||||||
|
msrFeeCartItem() {
|
||||||
|
let cartItem = null;
|
||||||
|
const msrFeeLineItem = this.getMsrFeeLineItem;
|
||||||
|
const shouldCreateMsrFeeCartItem =
|
||||||
|
this.isMSRFeeApplicable &&
|
||||||
|
(!this.isInsurance || !this.IsMSRFeeCoveredByInsurance) &&
|
||||||
|
msrFeeLineItem &&
|
||||||
|
this.msrFeeAmount > 0;
|
||||||
|
|
||||||
|
if (shouldCreateMsrFeeCartItem) {
|
||||||
|
cartItem = {
|
||||||
|
name: this.msrFeeCartItemName,
|
||||||
|
category: cartItemCategories.SUPPORTING_ITEMS,
|
||||||
|
cartItemType: cartItemTypes.MOBILE_FEE,
|
||||||
|
isDisplayed: true,
|
||||||
|
isRemovable: true,
|
||||||
|
subTotal: 0,
|
||||||
|
salesTax: 0,
|
||||||
|
lineItems: [],
|
||||||
|
isCoveredByInsurance: this.IsMSRFeeCoveredByInsurance,
|
||||||
|
};
|
||||||
|
msrFeeLineItem.cartItemType = cartItem.cartItemType;
|
||||||
|
cartItem.lineItems.push(msrFeeLineItem);
|
||||||
|
cartItem.subTotal += this.msrFeeAmount;
|
||||||
|
cartItem.salesTax += msrFeeLineItem.salesTax ?? 0;
|
||||||
|
}
|
||||||
|
return cartItem;
|
||||||
|
},
|
||||||
|
msrFeeAmount() {
|
||||||
|
const msrFeeLineItem = this.getMsrFeeLineItem;
|
||||||
|
if (!msrFeeLineItem) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
msrFeeLineItem.laborAmount + msrFeeLineItem.sellingPrice + msrFeeLineItem.kitPrice
|
||||||
|
);
|
||||||
|
},
|
||||||
|
recyclingToolTipBodyText() {
|
||||||
|
return this.getCmsContent("RecycleTextBlock", "Text");
|
||||||
|
},
|
||||||
|
msrToolTipBodyText() {
|
||||||
|
let cmsContentText = this.isInsurance
|
||||||
|
? this.getCmsContent("MSRModal", "BodyText")
|
||||||
|
: this.getCmsContent("MSRModal", "BodyText2");
|
||||||
|
if (this.isInsurance && cmsContentText) {
|
||||||
|
cmsContentText = cmsContentText.replaceAll(
|
||||||
|
"{custom:mobileFee}",
|
||||||
|
formatToUSDollar(this.msrFeeAmount)
|
||||||
|
);
|
||||||
|
cmsContentText = cmsContentText.replaceAll(
|
||||||
|
"{custom:submittedOrder.policy.insuranceCompanyName}",
|
||||||
|
this.insuranceCompanyName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cmsContentText;
|
||||||
|
},
|
||||||
quotePageDiscountCartItemName() {
|
quotePageDiscountCartItemName() {
|
||||||
return this.getCmsContent("ServicePackageDiscountTextWidget", "Text");
|
return this.getCmsContent("ServicePackageDiscountTextWidget", "Text");
|
||||||
},
|
},
|
||||||
|
|
@ -1187,8 +1279,9 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
textBlock,
|
textBlock,
|
||||||
textLink,
|
textLink,
|
||||||
contentGroupModal,
|
|
||||||
promoModalQuestion,
|
promoModalQuestion,
|
||||||
|
removeMsrFeeModal,
|
||||||
|
toolTip,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1289,6 +1382,10 @@ export default {
|
||||||
order: 3;
|
order: 3;
|
||||||
margin-right: 100%;
|
margin-right: 100%;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
|
&#msr-fee-cartItem {
|
||||||
|
margin: 0 0 0 0.2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.package-type,
|
.package-type,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
<template>
|
||||||
|
<div id="msr-fee-modal-container">
|
||||||
|
<modal
|
||||||
|
:ref="modalName"
|
||||||
|
:headerText="modalHeaderText"
|
||||||
|
suppressPageScroll
|
||||||
|
:onModalClosedCallback="onModalClosed"
|
||||||
|
:footerButtonText="modalFooterText"
|
||||||
|
:isFooterButtonPrimary="true"
|
||||||
|
@footer-button-event="switchToInshop"
|
||||||
|
@isModalOpened="setIsModalOpen">
|
||||||
|
<p class="modal-body-inner" v-html="modalBodyText"></p>
|
||||||
|
<template v-slot:modal-header-slot>
|
||||||
|
<span>{{ modalSubHeaderText }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:modal-footer-slot>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-link"
|
||||||
|
id="cancel-button"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="closeModal">
|
||||||
|
{{ buttonText }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Supporting files
|
||||||
|
import modal from "@/digital-components/modal/modal";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "remove-msr-fee-modal",
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: String,
|
||||||
|
modalWidgetName: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isModalOpen: false,
|
||||||
|
switchedToInshop: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openModal() {
|
||||||
|
this.modal.openModal();
|
||||||
|
},
|
||||||
|
onModalClosed() {
|
||||||
|
this.$emit("close-remove-msr-fee-modal");
|
||||||
|
this.switchedToInshop = false;
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.modal.closeModal();
|
||||||
|
},
|
||||||
|
getIsModalOpen() {
|
||||||
|
return this.isModalOpen;
|
||||||
|
},
|
||||||
|
setIsModalOpen(isOpen) {
|
||||||
|
this.isModalOpen = isOpen;
|
||||||
|
},
|
||||||
|
switchToInshop() {
|
||||||
|
this.switchedToInshop = true;
|
||||||
|
this.$emit("switchToInshop");
|
||||||
|
this.modal.closeModal();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modalName() {
|
||||||
|
return this.modalWidgetName;
|
||||||
|
},
|
||||||
|
modal() {
|
||||||
|
return this.$refs[this.modalName];
|
||||||
|
},
|
||||||
|
modalSubHeaderText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "SubheaderText");
|
||||||
|
},
|
||||||
|
modalHeaderText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
modalBodyText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "BodyText");
|
||||||
|
},
|
||||||
|
modalFooterText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
||||||
|
},
|
||||||
|
buttonText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "FooterText2");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { modal },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
#msr-fee-modal-container {
|
||||||
|
.modal-component {
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
.modal-dialog {
|
||||||
|
left: 0;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 22.063rem;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-dialog {
|
||||||
|
.modal-content {
|
||||||
|
.modal-header {
|
||||||
|
flex-direction: column;
|
||||||
|
& > span {
|
||||||
|
color: $red;
|
||||||
|
font-size: $font-size-14;
|
||||||
|
font-weight: $font-weight-600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: $font-size-20;
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-body {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
|
.modal-body-inner {
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
font-size: $font-size-14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
flex-flow: wrap-reverse;
|
||||||
|
|
||||||
|
#cancel-button {
|
||||||
|
width: 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
font-weight: $font-weight-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -26,6 +26,7 @@ export default {
|
||||||
name: "content-group-modal",
|
name: "content-group-modal",
|
||||||
props: {
|
props: {
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
|
customBodyText: String,
|
||||||
footerButtonActionName: {
|
footerButtonActionName: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
|
|
@ -43,6 +44,9 @@ export default {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||||
},
|
},
|
||||||
ModalBodyText() {
|
ModalBodyText() {
|
||||||
|
if (this.customBodyText) {
|
||||||
|
return this.customBodyText;
|
||||||
|
}
|
||||||
return this.getCmsContent(this.cmsWidgetName, "BodyText");
|
return this.getCmsContent(this.cmsWidgetName, "BodyText");
|
||||||
},
|
},
|
||||||
ModalSubBodyText() {
|
ModalSubBodyText() {
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,6 @@ export default {
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
if (
|
if (
|
||||||
endpoint.toLowerCase().includes(endpoints.LogDigitalConsumer.url) ||
|
|
||||||
endpoint.toLowerCase().includes(endpoints.LogFmgSessionData.url) ||
|
endpoint.toLowerCase().includes(endpoints.LogFmgSessionData.url) ||
|
||||||
endpoint.toLowerCase().includes(endpoints.LogPageView.url) ||
|
endpoint.toLowerCase().includes(endpoints.LogPageView.url) ||
|
||||||
endpoint.toLowerCase().includes(endpoints.LogCustomEvent.url) ||
|
endpoint.toLowerCase().includes(endpoints.LogCustomEvent.url) ||
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLo
|
||||||
var heritageParms = {
|
var heritageParms = {
|
||||||
corid: store.getters.order.referralCorrelationId,
|
corid: store.getters.order.referralCorrelationId,
|
||||||
src: "concept-funnel",
|
src: "concept-funnel",
|
||||||
conceptsqid: store.getters.applicationUser.savedSessionId,
|
conceptsqid: store.getters.applicationUser.savedSessionId ?? "",
|
||||||
isInsurance: store.getters.payment.isInsurance,
|
isInsurance: store.getters.payment.isInsurance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import store from "@/store";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import { deepClone } from "@/helpers/object-helper";
|
import { deepClone } from "@/helpers/object-helper";
|
||||||
|
import { partNumberStrings } from "@/constants/part-number-strings";
|
||||||
|
|
||||||
export function getDisplayAmountDue(lineItemsObject, includeTax = true) {
|
export function getDisplayAmountDue(lineItemsObject, includeTax = true) {
|
||||||
return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", {
|
return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", {
|
||||||
|
|
@ -32,6 +33,10 @@ export function getAmountDue(lineItemsObject, includeTax = true) {
|
||||||
|
|
||||||
if (store.getters.coverageIsVerified && !order.policy.isNoComp && !order.policy.isItac) {
|
if (store.getters.coverageIsVerified && !order.policy.isNoComp && !order.policy.isItac) {
|
||||||
amountDue = order.policy.currentDeductible;
|
amountDue = order.policy.currentDeductible;
|
||||||
|
|
||||||
|
if (order.isMSRFeeApplicable && !order.isMSRFeeCoveredByInsurance) {
|
||||||
|
amountDue += getMSRFeePartPrice(lineItemsObject?.supportingItems, includeTax);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineItemsObject?.vaps) {
|
if (lineItemsObject?.vaps) {
|
||||||
|
|
@ -100,6 +105,20 @@ export async function getPricingByDayPartWithPrice(pageNameToLog) {
|
||||||
return pricingResults[0];
|
return pricingResults[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMSRFeePartPrice(supportingItems, includeTax) {
|
||||||
|
let msrFeePrice = 0;
|
||||||
|
const msrFeeLineItem = supportingItems?.find(
|
||||||
|
(lineItem) => lineItem.partNumber === partNumberStrings.MOBILE_STATIC_RECAL_FEE
|
||||||
|
);
|
||||||
|
if (msrFeeLineItem) {
|
||||||
|
msrFeePrice = baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(
|
||||||
|
[msrFeeLineItem],
|
||||||
|
includeTax
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return msrFeePrice;
|
||||||
|
}
|
||||||
|
|
||||||
export function addPricesToLineItems(lineItems, pricingLineItems) {
|
export function addPricesToLineItems(lineItems, pricingLineItems) {
|
||||||
lineItems.forEach((lineItem) => {
|
lineItems.forEach((lineItem) => {
|
||||||
const lineItemIndex = pricingLineItems.findIndex(
|
const lineItemIndex = pricingLineItems.findIndex(
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@
|
||||||
:showAsPaid="isPia"
|
:showAsPaid="isPia"
|
||||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||||
recyclingModalCmsWidgetName="RecycleModal"
|
recyclingModalCmsWidgetName="RecycleModal"
|
||||||
|
msrModalCmsWidgetName="MSRModal"
|
||||||
:isInsurance="isInsurance"
|
:isInsurance="isInsurance"
|
||||||
:insuranceDeductible="currentDeductible"
|
:insuranceDeductible="currentDeductible"
|
||||||
:insuranceCompanyName="insuranceCompanyName"
|
:insuranceCompanyName="insuranceCompanyName"
|
||||||
|
|
@ -70,7 +71,8 @@
|
||||||
:isItac="isItac"
|
:isItac="isItac"
|
||||||
:isNoComp="isNoComp"
|
:isNoComp="isNoComp"
|
||||||
:isExpandedOnLoad="false"
|
:isExpandedOnLoad="false"
|
||||||
:isMSRFeeApplicable="isMSRFeeApplicable" />
|
:isMSRFeeApplicable="isMSRFeeApplicable"
|
||||||
|
:IsMSRFeeCoveredByInsurance="isMSRFeeCoveredByInsurance" />
|
||||||
|
|
||||||
<hr class="mb-5" />
|
<hr class="mb-5" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -419,6 +421,9 @@ export default {
|
||||||
isMSRFeeApplicable() {
|
isMSRFeeApplicable() {
|
||||||
return this.submittedOrder?.isMSRFeeApplicable;
|
return this.submittedOrder?.isMSRFeeApplicable;
|
||||||
},
|
},
|
||||||
|
isMSRFeeCoveredByInsurance() {
|
||||||
|
return this.submittedOrder?.isMSRFeeCoveredByInsurance;
|
||||||
|
},
|
||||||
isInsurance() {
|
isInsurance() {
|
||||||
return this.submittedOrder?.payment?.isInsurance;
|
return this.submittedOrder?.payment?.isInsurance;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { nextTick } from "vue";
|
import { nextTick } from "vue";
|
||||||
import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash";
|
import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash";
|
||||||
|
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
||||||
|
|
||||||
// Define Validation Rules
|
// Define Validation Rules
|
||||||
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
||||||
|
|
@ -169,7 +170,7 @@ export default {
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
baseMixin.methods.ResetExternalParamsAndHideModal();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
showFmgLoadingModal(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
baseMixin.methods.ResetExternalParamsAndHideModal();
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,118 @@ describe("payment-adyen.vue", () => {
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("splitStreetAddress", () => {
|
||||||
|
const setInShopStreet = (streetAddress) => {
|
||||||
|
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP;
|
||||||
|
store.getters.order.serviceLocation.provider.address.streetAddress = streetAddress;
|
||||||
|
};
|
||||||
|
|
||||||
|
test("splits typical address into first token as number and remainder as street", () => {
|
||||||
|
setInShopStreet("123 West Elm St");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "123",
|
||||||
|
street: "West Elm St",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("two tokens: number and single-word street", () => {
|
||||||
|
setInShopStreet("456 Oak");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "456",
|
||||||
|
street: "Oak",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("single token with no spaces: full string in number, empty street", () => {
|
||||||
|
setInShopStreet("123OakStreet");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "123OakStreet",
|
||||||
|
street: "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("collapses multiple spaces between words", () => {
|
||||||
|
setInShopStreet("123 West Elm St");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "123",
|
||||||
|
street: "West Elm St",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("trims leading and trailing whitespace on full address", () => {
|
||||||
|
setInShopStreet(" 99 Maple Ave ");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "99",
|
||||||
|
street: "Maple Ave",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("empty string yields empty number and street", () => {
|
||||||
|
setInShopStreet("");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "",
|
||||||
|
street: "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("whitespace-only address yields empty number and street", () => {
|
||||||
|
setInShopStreet(" \t ");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "",
|
||||||
|
street: "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("null address yields empty number and street", () => {
|
||||||
|
setInShopStreet(null);
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "",
|
||||||
|
street: "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses mobile service address when appointment is mobile", () => {
|
||||||
|
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||||
|
store.getters.order.serviceLocation.address = "700 Broadway Blvd";
|
||||||
|
store.getters.order.serviceLocation.city = "Columbus";
|
||||||
|
store.getters.order.serviceLocation.state = "OH";
|
||||||
|
store.getters.order.serviceLocation.zipCode = "43235";
|
||||||
|
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "700",
|
||||||
|
street: "Broadway Blvd",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("splits on tabs and other whitespace runs", () => {
|
||||||
|
setInShopStreet("10\tPine\tLane");
|
||||||
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
|
expect(wrapper.vm.splitStreetAddress).toEqual({
|
||||||
|
number: "10",
|
||||||
|
street: "Pine Lane",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupMocks({ customMountOptions } = {}) {
|
function setupMocks({ customMountOptions } = {}) {
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,12 @@
|
||||||
v-model="lineItems"
|
v-model="lineItems"
|
||||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||||
recyclingModalCmsWidgetName="RecycleModal"
|
recyclingModalCmsWidgetName="RecycleModal"
|
||||||
|
msrModalCmsWidgetName="MSRModal"
|
||||||
:isInsurance="isInsurance"
|
:isInsurance="isInsurance"
|
||||||
:insuranceDeductible="currentDeductible"
|
:insuranceDeductible="currentDeductible"
|
||||||
:insuranceCompanyName="insuranceCompanyName"
|
:insuranceCompanyName="insuranceCompanyName"
|
||||||
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
||||||
|
:isMSRFeeApplicable="isMSRFeeApplicable"
|
||||||
:isItac="isItac"
|
:isItac="isItac"
|
||||||
:isNoComp="isNoComp"
|
:isNoComp="isNoComp"
|
||||||
:isCollapsible="false" />
|
:isCollapsible="false" />
|
||||||
|
|
@ -514,15 +516,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
splitStreetAddress() {
|
splitStreetAddress() {
|
||||||
const address = this.locationInfo.address;
|
const address = (this.locationInfo?.address ?? "").trim();
|
||||||
const tokens = address.split(" ") ?? [""];
|
const tokens = address.length ? address.split(/\s+/) : [""];
|
||||||
const number = tokens[0];
|
const number = tokens[0] ?? "";
|
||||||
|
const street = tokens.length > 1 ? tokens.slice(1).join(" ") : "";
|
||||||
const street = tokens.slice(1).reduce((prev, next) => `${prev} ${next}`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
number: number ?? "",
|
number,
|
||||||
street: street ?? "",
|
street,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -568,6 +569,9 @@ export default {
|
||||||
insuranceCompanyName() {
|
insuranceCompanyName() {
|
||||||
return this.$store.getters.policy.insuranceCompanyName;
|
return this.$store.getters.policy.insuranceCompanyName;
|
||||||
},
|
},
|
||||||
|
isMSRFeeApplicable() {
|
||||||
|
return this.$store.getters.order.isMSRFeeApplicable;
|
||||||
|
},
|
||||||
lineItems() {
|
lineItems() {
|
||||||
return deepClone(this.$store.getters.lineItems);
|
return deepClone(this.$store.getters.lineItems);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
pageName="payment-method"
|
pageName="payment-method"
|
||||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||||
recyclingModalCmsWidgetName="RecycleModal"
|
recyclingModalCmsWidgetName="RecycleModal"
|
||||||
|
msrModalCmsWidgetName="MSRModal"
|
||||||
:isInsurance="isInsurance"
|
:isInsurance="isInsurance"
|
||||||
:insuranceDeductible="currentDeductible"
|
:insuranceDeductible="currentDeductible"
|
||||||
:insuranceCompanyName="insuranceCompanyName"
|
:insuranceCompanyName="insuranceCompanyName"
|
||||||
|
|
@ -35,7 +36,9 @@
|
||||||
:isNoComp="isNoComp"
|
:isNoComp="isNoComp"
|
||||||
:isExpandedOnLoad="false"
|
:isExpandedOnLoad="false"
|
||||||
:isMSRFeeApplicable="isMSRFeeApplicable"
|
:isMSRFeeApplicable="isMSRFeeApplicable"
|
||||||
@itemRemoved="evaluatePromosAndTaxItemsOnOrder" />
|
:IsMSRFeeCoveredByInsurance="isMSRFeeCoveredByInsurance"
|
||||||
|
@itemRemoved="evaluatePromosAndTaxItemsOnOrder"
|
||||||
|
@switchToInshop="navigateToSchedulePage" />
|
||||||
|
|
||||||
<afterpayBreakout
|
<afterpayBreakout
|
||||||
v-if="isAfterpayBreakoutDisplay"
|
v-if="isAfterpayBreakoutDisplay"
|
||||||
|
|
@ -214,6 +217,7 @@ export default {
|
||||||
name: "paymentMethod",
|
name: "paymentMethod",
|
||||||
props: {
|
props: {
|
||||||
recyclingModalCmsWidgetName: String,
|
recyclingModalCmsWidgetName: String,
|
||||||
|
msrModalCmsWidgetName: String,
|
||||||
},
|
},
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
// Call APIs
|
// Call APIs
|
||||||
|
|
@ -607,6 +611,13 @@ export default {
|
||||||
openModal(modalName) {
|
openModal(modalName) {
|
||||||
this.$refs[modalName].openModal();
|
this.$refs[modalName].openModal();
|
||||||
},
|
},
|
||||||
|
async navigateToSchedulePage() {
|
||||||
|
this.$refs.loadingModal.showModal();
|
||||||
|
this.$router.navigateWithSaving(
|
||||||
|
this.navigationScenarios.CLICKED_SWITCH_TO_INSHOP,
|
||||||
|
this.pageName
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
AppointmentType() {
|
AppointmentType() {
|
||||||
|
|
@ -682,6 +693,9 @@ export default {
|
||||||
isMSRFeeApplicable() {
|
isMSRFeeApplicable() {
|
||||||
return this.$store.getters.order.isMSRFeeApplicable;
|
return this.$store.getters.order.isMSRFeeApplicable;
|
||||||
},
|
},
|
||||||
|
isMSRFeeCoveredByInsurance() {
|
||||||
|
return this.$store.getters.order.isMSRFeeCoveredByInsurance;
|
||||||
|
},
|
||||||
isInsurance() {
|
isInsurance() {
|
||||||
return this.hasSubmittedOrder()
|
return this.hasSubmittedOrder()
|
||||||
? this.getSubmittedOrder()?.payment.isInsurance
|
? this.getSubmittedOrder()?.payment.isInsurance
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,12 @@
|
||||||
v-model="lineItems"
|
v-model="lineItems"
|
||||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||||
recyclingModalCmsWidgetName="RecycleModal"
|
recyclingModalCmsWidgetName="RecycleModal"
|
||||||
|
msrModalCmsWidgetName="MSRModal"
|
||||||
:isInsurance="isInsurance"
|
:isInsurance="isInsurance"
|
||||||
:insuranceDeductible="currentDeductible"
|
:insuranceDeductible="currentDeductible"
|
||||||
:insuranceCompanyName="insuranceCompanyName"
|
:insuranceCompanyName="insuranceCompanyName"
|
||||||
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
||||||
|
:isMSRFeeApplicable="isMSRFeeApplicable"
|
||||||
:isItac="isItac"
|
:isItac="isItac"
|
||||||
:isNoComp="isNoComp"
|
:isNoComp="isNoComp"
|
||||||
:isCollapsible="false" />
|
:isCollapsible="false" />
|
||||||
|
|
@ -410,6 +412,9 @@ export default {
|
||||||
insuranceCompanyName() {
|
insuranceCompanyName() {
|
||||||
return this.$store.getters.policy.insuranceCompanyName;
|
return this.$store.getters.policy.insuranceCompanyName;
|
||||||
},
|
},
|
||||||
|
isMSRFeeApplicable() {
|
||||||
|
return this.$store.getters.order.isMSRFeeApplicable;
|
||||||
|
},
|
||||||
isPaypal() {
|
isPaypal() {
|
||||||
if (this.paymentType == "pp") {
|
if (this.paymentType == "pp") {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -590,12 +590,6 @@ export default {
|
||||||
}
|
}
|
||||||
// there are no active or inactive external parameters; use internal threshold
|
// there are no active or inactive external parameters; use internal threshold
|
||||||
if (internalThreshold) thresholdToUse = internalThreshold;
|
if (internalThreshold) thresholdToUse = internalThreshold;
|
||||||
|
|
||||||
vm.isInsuranceSelected = getIsInsuranceSelectedValue(
|
|
||||||
vm.availableLineItems,
|
|
||||||
thresholdToUse
|
|
||||||
);
|
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
|
||||||
} else {
|
} else {
|
||||||
// user came from an external source, however, the externalParms may have been reset on service-zip, property-questions, etc...
|
// user came from an external source, however, the externalParms may have been reset on service-zip, property-questions, etc...
|
||||||
if (getBoolFromString(store.getters.externalParameterQuote?.isInsurance)) {
|
if (getBoolFromString(store.getters.externalParameterQuote?.isInsurance)) {
|
||||||
|
|
@ -613,8 +607,6 @@ export default {
|
||||||
) {
|
) {
|
||||||
await vm.skip(insuranceSelection, servicePackage);
|
await vm.skip(insuranceSelection, servicePackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
|
||||||
} else {
|
} else {
|
||||||
if (externalThreshold) thresholdToUse = externalThreshold;
|
if (externalThreshold) thresholdToUse = externalThreshold;
|
||||||
|
|
||||||
|
|
@ -622,15 +614,17 @@ export default {
|
||||||
{
|
{
|
||||||
debugLog("quote.vue tab select NOT EXTERNAL");
|
debugLog("quote.vue tab select NOT EXTERNAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.isInsuranceSelected = getIsInsuranceSelectedValue(
|
|
||||||
vm.availableLineItems,
|
|
||||||
thresholdToUse
|
|
||||||
);
|
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vm.isInsuranceSelected == null) {
|
||||||
|
vm.isInsuranceSelected = getIsInsuranceSelectedValue(
|
||||||
|
vm.availableLineItems,
|
||||||
|
thresholdToUse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
baseMixin.methods.ResetExternalParamsAndHideModal();
|
||||||
|
|
||||||
if (vm.skipToInsurance && !vm.showSaveProgressPopup) {
|
if (vm.skipToInsurance && !vm.showSaveProgressPopup) {
|
||||||
// skip ahead only if no email popup.
|
// skip ahead only if no email popup.
|
||||||
await vm.skip(true, externalParamPackageLabels.GLASS_ONLY);
|
await vm.skip(true, externalParamPackageLabels.GLASS_ONLY);
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -705,7 +706,8 @@ export default {
|
||||||
isMobileStaticRecalibrationApplicable() {
|
isMobileStaticRecalibrationApplicable() {
|
||||||
return (
|
return (
|
||||||
this.displayMSR &&
|
this.displayMSR &&
|
||||||
this.mobileFeePart?.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE
|
this.mobileFeePart?.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE &&
|
||||||
|
(this.enableMSRSplitPay || this.isCashItacNoComp || this.mobileFeePart?.isInsurable)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
displayMSR() {
|
displayMSR() {
|
||||||
|
|
@ -715,6 +717,13 @@ export default {
|
||||||
?.toLowerCase() === "true"
|
?.toLowerCase() === "true"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
enableMSRSplitPay() {
|
||||||
|
return (
|
||||||
|
experimentMixin.methods
|
||||||
|
.getSettingValue(experimentSettings.ENABLE_MSR_SPLIT_PAY)
|
||||||
|
?.toLowerCase() === "true"
|
||||||
|
);
|
||||||
|
},
|
||||||
isMSRFeeNotCoveredByInsurance() {
|
isMSRFeeNotCoveredByInsurance() {
|
||||||
return (
|
return (
|
||||||
this.isMobileSelected &&
|
this.isMobileSelected &&
|
||||||
|
|
@ -1173,6 +1182,13 @@ export default {
|
||||||
isMSRFeeApplicable
|
isMSRFeeApplicable
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
updateAndSaveIsMSRFeeCoveredByInsurance() {
|
||||||
|
const isMSRFeeCoveredByInsurance = this.mobileFeePart?.isInsurable;
|
||||||
|
this.dispatchStoreAction(
|
||||||
|
this.storeActions.SAVE_IS_MSR_FEE_COVERED_BY_INSURANCE,
|
||||||
|
isMSRFeeCoveredByInsurance
|
||||||
|
);
|
||||||
|
},
|
||||||
updateAndSaveSupportingItems() {
|
updateAndSaveSupportingItems() {
|
||||||
let supportingItems = store.getters.lineItems.supportingItems;
|
let supportingItems = store.getters.lineItems.supportingItems;
|
||||||
let shouldSaveSupportingItems = false;
|
let shouldSaveSupportingItems = false;
|
||||||
|
|
@ -1528,6 +1544,7 @@ export default {
|
||||||
|
|
||||||
this.updateAndSaveSupportingItems();
|
this.updateAndSaveSupportingItems();
|
||||||
this.updateAndSaveIsMSRFeeApplicable();
|
this.updateAndSaveIsMSRFeeApplicable();
|
||||||
|
this.updateAndSaveIsMSRFeeCoveredByInsurance();
|
||||||
|
|
||||||
this.updateSupportingItems();
|
this.updateSupportingItems();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { routeData } from "@/router/constants/routes";
|
import { routeData } from "@/router/constants/routes";
|
||||||
|
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
||||||
|
|
||||||
// Define Validation Rules
|
// Define Validation Rules
|
||||||
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
||||||
|
|
@ -137,6 +138,13 @@ export default {
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
vm.serviceZipCode = store.getters.externalParameterServiceZip.zipCode;
|
||||||
|
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
|
||||||
|
if (isValid) {
|
||||||
|
showFmgLoadingModal(true);
|
||||||
|
vm.forwardButtonAction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.getters.externalParameterCustomer?.phoneNumber) {
|
if (store.getters.externalParameterCustomer?.phoneNumber) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -93,12 +93,14 @@ import { errorMessages } from "@/constants/error-messages";
|
||||||
import { damageLocationsCms } from "@/constants/damage-locations-cms.js";
|
import { damageLocationsCms } from "@/constants/damage-locations-cms.js";
|
||||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
|
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
|
||||||
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
|
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
||||||
import { debugLog } from "@/helpers/debug-log-helper";
|
import { debugLog } from "@/helpers/debug-log-helper";
|
||||||
|
|
||||||
import store from "@/store";
|
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));
|
||||||
|
|
||||||
|
|
@ -142,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} ****`);
|
||||||
|
|
@ -199,7 +206,7 @@ export default {
|
||||||
return vm.forwardButtonAction();
|
return vm.forwardButtonAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
showFmgLoadingModal(false);
|
||||||
|
|
||||||
//clear any validation errors for external parameter flow
|
//clear any validation errors for external parameter flow
|
||||||
const form = vm.$refs.theForm;
|
const form = vm.$refs.theForm;
|
||||||
|
|
@ -209,7 +216,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
showFmgLoadingModal(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -354,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
|
||||||
);
|
);
|
||||||
|
|
@ -487,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(
|
||||||
|
|
@ -665,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;
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,11 @@ describe("vehicle.vue", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("vehicle.vue", () => {
|
describe("vehicle.vue", () => {
|
||||||
test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
|
test("should call forwardButtonAction if isExternalParameter is true, has a year, make and model, and form is valid", async () => {
|
||||||
store.getters.externalParameterState.isExternalParameter = true;
|
store.getters.externalParameterState.isExternalParameter = true;
|
||||||
|
store.getters.externalParameterVehicle.year = "1886";
|
||||||
|
store.getters.externalParameterVehicle.make = "Benz";
|
||||||
|
store.getters.externalParameterVehicle.model = "Patent-Motorwagen";
|
||||||
// Create a shallow mount of MyComponent
|
// Create a shallow mount of MyComponent
|
||||||
const { wrapper } = setupMocks();
|
const { wrapper } = setupMocks();
|
||||||
// Set displayNoServiceAlert to false
|
// Set displayNoServiceAlert to false
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
import { applicationConfig } from "../../constants/application-config";
|
import { applicationConfig } from "../../constants/application-config";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||||
|
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
||||||
import { debugLog } from "@/helpers/debug-log-helper";
|
import { debugLog } from "@/helpers/debug-log-helper";
|
||||||
|
|
||||||
//define validation rules
|
//define validation rules
|
||||||
|
|
@ -140,6 +141,8 @@ export default {
|
||||||
styleOptions: [],
|
styleOptions: [],
|
||||||
displayNoServiceAlert: false,
|
displayNoServiceAlert: false,
|
||||||
canSafeliteService: this.canSafeliteServiceFromStore(),
|
canSafeliteService: this.canSafeliteServiceFromStore(),
|
||||||
|
skipVINLookup: this.getSkipVINLookupfromStore(),
|
||||||
|
skipPartQuestions: this.getSkipPartQuestionsfromStore(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -252,17 +255,17 @@ export default {
|
||||||
const matchingMake = resultMap.makeQuestionInitialData?.filter(
|
const matchingMake = resultMap.makeQuestionInitialData?.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.toLowerCase() ===
|
item.toLowerCase() ===
|
||||||
store.getters.externalParameterVehicle.make.toLowerCase()
|
store.getters.externalParameterVehicle.make?.toLowerCase()
|
||||||
);
|
);
|
||||||
const matchingModel = resultMap.modelQuestionInitialData?.filter(
|
const matchingModel = resultMap.modelQuestionInitialData?.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.toLowerCase() ===
|
item.toLowerCase() ===
|
||||||
store.getters.externalParameterVehicle.model.toLowerCase()
|
store.getters.externalParameterVehicle.model?.toLowerCase()
|
||||||
);
|
);
|
||||||
const matchingStyle = resultMap.styleQuestionInitialData?.filter(
|
const matchingStyle = resultMap.styleQuestionInitialData?.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.toLowerCase() ===
|
item.toLowerCase() ===
|
||||||
store.getters.externalParameterVehicle.style.toLowerCase()
|
store.getters.externalParameterVehicle.style?.toLowerCase()
|
||||||
);
|
);
|
||||||
baseMixin.methods.dispatchStoreAction(
|
baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.UPDATE_EXTERNAL_PARAMETER_MMS,
|
storeActions.UPDATE_EXTERNAL_PARAMETER_MMS,
|
||||||
|
|
@ -291,7 +294,12 @@ export default {
|
||||||
resultMap.modelQuestionInitialData,
|
resultMap.modelQuestionInitialData,
|
||||||
resultMap.styleQuestionInitialData
|
resultMap.styleQuestionInitialData
|
||||||
);
|
);
|
||||||
if (store.getters.externalParameterState?.isExternalParameter) {
|
if (
|
||||||
|
store.getters.externalParameterState?.isExternalParameter &&
|
||||||
|
store.getters.externalParameterVehicle.year &&
|
||||||
|
store.getters.externalParameterVehicle.make &&
|
||||||
|
store.getters.externalParameterVehicle.model
|
||||||
|
) {
|
||||||
await vm.getVehicleDetails();
|
await vm.getVehicleDetails();
|
||||||
if (!vm.displayNoServiceAlert) {
|
if (!vm.displayNoServiceAlert) {
|
||||||
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
|
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
|
||||||
|
|
@ -304,7 +312,7 @@ export default {
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
baseMixin.methods.ResetExternalParamsAndHideModal();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
showFmgLoadingModal(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -480,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;
|
||||||
|
|
@ -536,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
|
||||||
);
|
);
|
||||||
|
|
@ -621,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: {
|
||||||
|
|
|
||||||
|
|
@ -132,51 +132,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async logDigitalConsumer() {
|
|
||||||
const currentPageName = getPageNameFromRouter();
|
|
||||||
const universes = store.getters.applicationUser.experiments;
|
|
||||||
|
|
||||||
const variationNames = universes
|
|
||||||
.filter((item) => item.universeName === experimentUniverses.CONCEPT_FUNNEL)
|
|
||||||
.map((item) => item.variationName)
|
|
||||||
.filter(Boolean); // removes undefined/null
|
|
||||||
|
|
||||||
const conceptVariation = variationNames.length > 0 ? variationNames[0] : "";
|
|
||||||
|
|
||||||
const isConceptExposed = universes.find(
|
|
||||||
(item) => item.universeName === experimentUniverses.CONCEPT_FUNNEL
|
|
||||||
)?.isExposed;
|
|
||||||
|
|
||||||
const submittedOrder = baseMixin.methods.getSubmittedOrder();
|
|
||||||
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
|
|
||||||
const hasSubmittedOrderAtConfirmationPage =
|
|
||||||
hasSubmittedOrder && currentPageName?.toLowerCase() == routeData.CONFIRMATION.name;
|
|
||||||
|
|
||||||
var payload = {
|
|
||||||
actionName: `Browser page:${currentPageName}`,
|
|
||||||
referralSequenceNumber: hasSubmittedOrderAtConfirmationPage
|
|
||||||
? submittedOrder.referralSequenceNumber
|
|
||||||
: store.getters.order.referralSequenceNumber,
|
|
||||||
referralNumber: hasSubmittedOrderAtConfirmationPage
|
|
||||||
? submittedOrder.referralNumber
|
|
||||||
: store.getters.order.referralNumber,
|
|
||||||
workOrderId: hasSubmittedOrderAtConfirmationPage
|
|
||||||
? submittedOrder.workOrderId
|
|
||||||
: store.getters.order.workOrderId,
|
|
||||||
workOrderNumber: hasSubmittedOrderAtConfirmationPage
|
|
||||||
? submittedOrder.workOrderNumber
|
|
||||||
: store.getters.order.workOrderNumber,
|
|
||||||
conceptVariation: conceptVariation,
|
|
||||||
isConceptExposed: isConceptExposed,
|
|
||||||
};
|
|
||||||
|
|
||||||
await baseMixin.methods.dispatchStoreAction(
|
|
||||||
storeActions.LOG_DIGITALCONSUMER,
|
|
||||||
payload,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
async pushEventForChatsToGA(category, action, label, pushToLogApp = false) {
|
async pushEventForChatsToGA(category, action, label, pushToLogApp = false) {
|
||||||
const currentPageName = getPageNameFromRouter();
|
const currentPageName = getPageNameFromRouter();
|
||||||
const value = `2.0_${currentPageName}`;
|
const value = `2.0_${currentPageName}`;
|
||||||
|
|
@ -206,6 +161,11 @@ export default {
|
||||||
// S3 bucket, safelite-dev-digitalconsumer-session-data-us-east-2/1.
|
// S3 bucket, safelite-dev-digitalconsumer-session-data-us-east-2/1.
|
||||||
// This bucket data is then picked up by snowflake for analytics use.
|
// This bucket data is then picked up by snowflake for analytics use.
|
||||||
async pushFmgSessionData() {
|
async pushFmgSessionData() {
|
||||||
|
var currentPageName = getPageNameFromRouter(true);
|
||||||
|
if (!currentPageName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
|
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
|
||||||
const submittedOrder = baseMixin.methods.getSubmittedOrder();
|
const submittedOrder = baseMixin.methods.getSubmittedOrder();
|
||||||
const order = hasSubmittedOrder ? submittedOrder : store.getters.order;
|
const order = hasSubmittedOrder ? submittedOrder : store.getters.order;
|
||||||
|
|
@ -247,22 +207,6 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`;
|
var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`;
|
||||||
var currentPageName = getPageNameFromRouter();
|
|
||||||
|
|
||||||
// add query strings to the page name for debugging. on the vehicle page, if from an external link, pull it from the stash
|
|
||||||
if (currentPageName === "vehicle") {
|
|
||||||
if (!window.location.search) {
|
|
||||||
if (store.getters.externalParameterState?.qsStash) {
|
|
||||||
currentPageName += `${store.getters.externalParameterState.qsStash}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentPageName += `${window.location.search}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (window.location.search) {
|
|
||||||
currentPageName += `${window.location.search}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sessionData = {};
|
var sessionData = {};
|
||||||
sessionData.currentPage = currentPageName;
|
sessionData.currentPage = currentPageName;
|
||||||
|
|
@ -911,6 +855,9 @@ export default {
|
||||||
setSessionIdIfUnset(response.data.sessionId);
|
setSessionIdIfUnset(response.data.sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logging on session init so we capture complete query string data
|
||||||
|
await this.pushFmgSessionData();
|
||||||
},
|
},
|
||||||
|
|
||||||
noSession() {
|
noSession() {
|
||||||
|
|
@ -977,7 +924,7 @@ function pushToDataLayerIfDefined(data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPageNameFromRouter() {
|
function getPageNameFromRouter(useDefaultUrl = false) {
|
||||||
if (
|
if (
|
||||||
router &&
|
router &&
|
||||||
router.currentRoute &&
|
router.currentRoute &&
|
||||||
|
|
@ -987,7 +934,9 @@ function getPageNameFromRouter() {
|
||||||
return router.currentRoute.value.name;
|
return router.currentRoute.value.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return window.location.href.replace(/\/$/, "").split("/").pop();
|
return useDefaultUrl
|
||||||
|
? window.location.search
|
||||||
|
: window.location.href.replace(/\/$/, "").split("/").pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getValueToLog(value, valueToLogType) {
|
function getValueToLog(value, valueToLogType) {
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ const navigationScenarios = {
|
||||||
CLICKED_PAY_NOW: "CLICKED_PAY_NOW",
|
CLICKED_PAY_NOW: "CLICKED_PAY_NOW",
|
||||||
CLICKED_PAY_NOW_ADYEN: "CLICKED_PAY_NOW_ADYEN",
|
CLICKED_PAY_NOW_ADYEN: "CLICKED_PAY_NOW_ADYEN",
|
||||||
CLICKED_PAY_LATER: "CLICKED_PAY_LATER",
|
CLICKED_PAY_LATER: "CLICKED_PAY_LATER",
|
||||||
|
CLICKED_SWITCH_TO_INSHOP: "CLICKED_SWITCH_TO_INSHOP",
|
||||||
PIA_ERROR: "PIA_ERROR",
|
PIA_ERROR: "PIA_ERROR",
|
||||||
PIA_CC_ERROR: "PIA_CC_ERROR",
|
PIA_CC_ERROR: "PIA_CC_ERROR",
|
||||||
PIA_APPLE_ERROR: "PIA_APPLE_ERROR",
|
PIA_APPLE_ERROR: "PIA_APPLE_ERROR",
|
||||||
|
|
|
||||||
|
|
@ -553,6 +553,10 @@ const routingTable = function () {
|
||||||
scenario: navigationScenarios.CLICKED_INSURANCE,
|
scenario: navigationScenarios.CLICKED_INSURANCE,
|
||||||
destinationPageData: routeData.INSURANCE_COMPANY,
|
destinationPageData: routeData.INSURANCE_COMPANY,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_SWITCH_TO_INSHOP,
|
||||||
|
destinationPageData: routeData.SCHEDULE,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,6 @@ import { storeMutations } from "@/constants/store-mutations";
|
||||||
export async function afterEach(to, from) {
|
export async function afterEach(to, from) {
|
||||||
store.commit(storeMutations.UPDATE_LAST_PAGE_VISITED, to.name);
|
store.commit(storeMutations.UPDATE_LAST_PAGE_VISITED, to.name);
|
||||||
|
|
||||||
// digital consumer logging
|
|
||||||
analyticsMixin.methods.logDigitalConsumer();
|
|
||||||
|
|
||||||
// digital consumer fmg session logging to snowflake
|
// digital consumer fmg session logging to snowflake
|
||||||
analyticsMixin.methods.pushFmgSessionData();
|
analyticsMixin.methods.pushFmgSessionData();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { isSavedSessionStillActive } from "@/helpers/heritage-integration/sessio
|
||||||
import analyticsMixin from "@/mixins/analytics-mixin";
|
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
import { routeData, FUNNEL_START_PAGE } from "@/router/constants/routes";
|
import { routeData, FUNNEL_START_PAGE } from "@/router/constants/routes";
|
||||||
import { runExperiments } from "@/router/methods/helpers/run-experiments";
|
import { runExperiments } from "@/router/methods/helpers/run-experiments";
|
||||||
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
|
||||||
import { handleSoftError, handleHardError } from "@/router/methods/error";
|
import { handleSoftError, handleHardError } from "@/router/methods/error";
|
||||||
import { checkPagePrerequisites } from "@/router/methods/page-prerequisites";
|
import { checkPagePrerequisites } from "@/router/methods/page-prerequisites";
|
||||||
|
|
@ -16,6 +17,8 @@ import { handleHeritageReturn } from "@/router/methods/helpers/handle-heritage-r
|
||||||
import { isVirtualRoute } from "@/router/methods/helpers/is-virtual-route";
|
import { isVirtualRoute } from "@/router/methods/helpers/is-virtual-route";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
|
||||||
|
const MAX_HERITAGE_SUPPRESS_REDIRECTS_PER_SESSION = 5;
|
||||||
|
|
||||||
export async function beforeEach(to, from) {
|
export async function beforeEach(to, from) {
|
||||||
try {
|
try {
|
||||||
await analyticsMixin.methods.validateSession();
|
await analyticsMixin.methods.validateSession();
|
||||||
|
|
@ -27,10 +30,37 @@ export async function beforeEach(to, from) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getFunnelCookie()?.SuppressConceptFunnel) {
|
if (getFunnelCookie()?.SuppressConceptFunnel) {
|
||||||
// Redirect to heritage.
|
const redirectKey = sessionStorageKeyConstants.HERITAGE_SUPPRESS_REDIRECT_COUNT;
|
||||||
return {
|
const redirectCount = Number(window.sessionStorage.getItem(redirectKey) ?? 0);
|
||||||
name: routeData.HERITAGE.name,
|
|
||||||
};
|
if (redirectCount < MAX_HERITAGE_SUPPRESS_REDIRECTS_PER_SESSION) {
|
||||||
|
window.sessionStorage.setItem(redirectKey, String(redirectCount + 1));
|
||||||
|
navigateToHeritageFunnel({
|
||||||
|
shouldSaveSession: false,
|
||||||
|
pageNameToLog: to.name,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// If the redirect count is greater than the max allowed, we need to handle the error.
|
||||||
|
|
||||||
|
// Reset the redirect count to 0.
|
||||||
|
window.sessionStorage.setItem(redirectKey, "0");
|
||||||
|
|
||||||
|
debugLog(
|
||||||
|
"SuppressConceptFunnel: skipped heritage redirect after max attempts this session",
|
||||||
|
{ redirectCount, to: to.name }
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorPayload = {
|
||||||
|
cause: "Handling heritage redirect after max attempts this session.",
|
||||||
|
currentPage: from?.name,
|
||||||
|
nextPage: to?.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Eject user from Vue app in this scenario and clear localstorage.
|
||||||
|
await handleSoftError(errorPayload, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure session has not expired
|
// Ensure session has not expired
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export async function handleSoftError(errorPayload, forceRestart = false) {
|
||||||
deleteFunnelCookie();
|
deleteFunnelCookie();
|
||||||
}
|
}
|
||||||
|
|
||||||
analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload);
|
logErrorToDataLayerAndLogger("handleSoftError", errorPayload, forceRestart);
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
name: routeData.ERROR.name,
|
name: routeData.ERROR.name,
|
||||||
|
|
@ -27,7 +27,7 @@ export async function handleHardError(errorPayload) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isInStaticErrorExperiment) {
|
if (isInStaticErrorExperiment) {
|
||||||
analyticsMixin?.methods?.pushPageErrorToDataLayer(errorPayload);
|
logErrorToDataLayerAndLogger("handleHardError", errorPayload, null);
|
||||||
window.top.location = "/fmg/static/error";
|
window.top.location = "/fmg/static/error";
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -38,3 +38,10 @@ export async function handleHardError(errorPayload) {
|
||||||
await handleSoftError(errorPayload, true);
|
await handleSoftError(errorPayload, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function logErrorToDataLayerAndLogger(context, errorPayload, forceRestart = null) {
|
||||||
|
const forceRestartSegment = forceRestart === true ? " (force restart)" : "";
|
||||||
|
const message = `${context}${forceRestartSegment}: ${errorPayload?.cause ?? "unknown cause"}`;
|
||||||
|
analyticsMixin?.methods?.pushPageErrorToDataLayer(errorPayload);
|
||||||
|
global.$logger.logError(message, errorPayload);
|
||||||
|
}
|
||||||
|
|
|
||||||
168
src/router/methods/error.spec.js
Normal file
168
src/router/methods/error.spec.js
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { handleSoftError, handleHardError } from "./error";
|
||||||
|
import router from "@/router";
|
||||||
|
import store from "@/store";
|
||||||
|
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
|
import experimentMixin from "../../mixins/experiment-mixin";
|
||||||
|
import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import { routeData } from "@/router/constants/routes";
|
||||||
|
|
||||||
|
jest.mock("@/router", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
push: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/store", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
dispatch: jest.fn(() => Promise.resolve()),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/helpers/heritage-integration/cookie-helper", () => ({
|
||||||
|
deleteFunnelCookie: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/mixins/analytics-mixin", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
methods: {
|
||||||
|
pushPageErrorToDataLayer: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../mixins/experiment-mixin", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
methods: {
|
||||||
|
hasSettingEqualTo: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("router/methods/error", () => {
|
||||||
|
const errorPayload = { cause: "test cause", currentPage: "from", nextPage: "to" };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
store.dispatch.mockResolvedValue(undefined);
|
||||||
|
global.$logger = {
|
||||||
|
logInformation: jest.fn(),
|
||||||
|
logWarning: jest.fn(),
|
||||||
|
logError: jest.fn(),
|
||||||
|
logCritical: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handleSoftError", () => {
|
||||||
|
it("logs and navigates to the error route without reset when forceRestart is false", async () => {
|
||||||
|
await handleSoftError(errorPayload, false);
|
||||||
|
|
||||||
|
expect(store.dispatch).not.toHaveBeenCalled();
|
||||||
|
expect(deleteFunnelCookie).not.toHaveBeenCalled();
|
||||||
|
expect(analyticsMixin.methods.pushPageErrorToDataLayer).toHaveBeenCalledWith(
|
||||||
|
errorPayload
|
||||||
|
);
|
||||||
|
expect(global.$logger.logError).toHaveBeenCalledWith(
|
||||||
|
"handleSoftError: test cause",
|
||||||
|
errorPayload
|
||||||
|
);
|
||||||
|
expect(router.push).toHaveBeenCalledWith({ name: routeData.ERROR.name });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dispatches reset, deletes funnel cookie, logs, and navigates when forceRestart is true", async () => {
|
||||||
|
await handleSoftError(errorPayload, true);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(storeActions.RESET_STATE);
|
||||||
|
expect(deleteFunnelCookie).toHaveBeenCalled();
|
||||||
|
expect(analyticsMixin.methods.pushPageErrorToDataLayer).toHaveBeenCalledWith(
|
||||||
|
errorPayload
|
||||||
|
);
|
||||||
|
expect(global.$logger.logError).toHaveBeenCalledWith(
|
||||||
|
"handleSoftError (force restart): test cause",
|
||||||
|
errorPayload
|
||||||
|
);
|
||||||
|
expect(router.push).toHaveBeenCalledWith({ name: routeData.ERROR.name });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses unknown cause in the log message when payload has no cause", async () => {
|
||||||
|
await handleSoftError({}, false);
|
||||||
|
|
||||||
|
expect(global.$logger.logError).toHaveBeenCalledWith(
|
||||||
|
"handleSoftError: unknown cause",
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handleHardError", () => {
|
||||||
|
let topLocationValue;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
topLocationValue = undefined;
|
||||||
|
Object.defineProperty(window, "top", {
|
||||||
|
configurable: true,
|
||||||
|
value: {},
|
||||||
|
});
|
||||||
|
Object.defineProperty(window.top, "location", {
|
||||||
|
configurable: true,
|
||||||
|
set(value) {
|
||||||
|
topLocationValue = value;
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return topLocationValue;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs and sets static error location when UseStaticErrorPage experiment is on", async () => {
|
||||||
|
experimentMixin.methods.hasSettingEqualTo.mockReturnValue(true);
|
||||||
|
|
||||||
|
await handleHardError(errorPayload);
|
||||||
|
|
||||||
|
expect(experimentMixin.methods.hasSettingEqualTo).toHaveBeenCalledWith(
|
||||||
|
"UseStaticErrorPage",
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
expect(analyticsMixin.methods.pushPageErrorToDataLayer).toHaveBeenCalledWith(
|
||||||
|
errorPayload
|
||||||
|
);
|
||||||
|
expect(global.$logger.logError).toHaveBeenCalledWith(
|
||||||
|
"handleHardError: test cause",
|
||||||
|
errorPayload
|
||||||
|
);
|
||||||
|
expect(topLocationValue).toBe("/fmg/static/error");
|
||||||
|
expect(router.push).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("delegates to handleSoftError with force restart when static error page is off", async () => {
|
||||||
|
experimentMixin.methods.hasSettingEqualTo.mockReturnValue(false);
|
||||||
|
|
||||||
|
await handleHardError(errorPayload);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(storeActions.RESET_STATE);
|
||||||
|
expect(deleteFunnelCookie).toHaveBeenCalled();
|
||||||
|
expect(global.$logger.logError).toHaveBeenCalledWith(
|
||||||
|
"handleSoftError (force restart): test cause",
|
||||||
|
errorPayload
|
||||||
|
);
|
||||||
|
expect(router.push).toHaveBeenCalledWith({ name: routeData.ERROR.name });
|
||||||
|
expect(topLocationValue).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("delegates to handleSoftError with force restart when experiment check throws", async () => {
|
||||||
|
experimentMixin.methods.hasSettingEqualTo.mockImplementation(() => {
|
||||||
|
throw new Error("experiment failure");
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleHardError(errorPayload);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(storeActions.RESET_STATE);
|
||||||
|
expect(deleteFunnelCookie).toHaveBeenCalled();
|
||||||
|
expect(router.push).toHaveBeenCalledWith({ name: routeData.ERROR.name });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -9,6 +9,7 @@ import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { externalParameterStatus } from "@/constants/external-parameters";
|
import { externalParameterStatus } from "@/constants/external-parameters";
|
||||||
import { debugLog } from "@/helpers/debug-log-helper";
|
import { debugLog } from "@/helpers/debug-log-helper";
|
||||||
|
import { runExperiments, forceExperiments } from "@/router/methods/helpers/run-experiments";
|
||||||
|
|
||||||
export async function initializeFromQueryStrings() {
|
export async function initializeFromQueryStrings() {
|
||||||
// Get advertiser info
|
// Get advertiser info
|
||||||
|
|
@ -16,6 +17,17 @@ export async function initializeFromQueryStrings() {
|
||||||
|
|
||||||
// Read all external paramter queries and write to store.
|
// Read all external paramter queries and write to store.
|
||||||
updateExternalParameterState();
|
updateExternalParameterState();
|
||||||
|
|
||||||
|
forceExperimentsFromQuerystrings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forceExperimentsFromQuerystrings() {
|
||||||
|
const experimentsFromQueryString = getQuerystringParameter(queryStrings.EXPERIMENTS);
|
||||||
|
if (experimentsFromQueryString) {
|
||||||
|
var qs = decodeURIComponent(window.location.search);
|
||||||
|
let forcedExperiments = qs.substring(qs.indexOf(queryStrings.EXPERIMENTS));
|
||||||
|
await forceExperiments(forcedExperiments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupAdvertisersAndAffiliates() {
|
function setupAdvertisersAndAffiliates() {
|
||||||
|
|
@ -39,7 +51,6 @@ function updateExternalParameterState() {
|
||||||
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
|
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
|
||||||
const externalParameterServicePackage = getQuerystringParameter(queryStrings.SERVICE_PACKAGE);
|
const externalParameterServicePackage = getQuerystringParameter(queryStrings.SERVICE_PACKAGE);
|
||||||
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
|
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
|
||||||
const externalParameterSource = getQuerystringParameter(queryStrings.EXPERIMENTS);
|
|
||||||
const externalParameterPhoneNumber = getQuerystringParameter(queryStrings.PHONE_NUMBER);
|
const externalParameterPhoneNumber = getQuerystringParameter(queryStrings.PHONE_NUMBER);
|
||||||
|
|
||||||
if (!externalParameterZipCode) {
|
if (!externalParameterZipCode) {
|
||||||
|
|
@ -58,7 +69,6 @@ function updateExternalParameterState() {
|
||||||
debugLog("externalParameterVinSelection:", externalParameterVinSelection);
|
debugLog("externalParameterVinSelection:", externalParameterVinSelection);
|
||||||
debugLog("externalParameterServicePackage:", externalParameterServicePackage);
|
debugLog("externalParameterServicePackage:", externalParameterServicePackage);
|
||||||
debugLog("externalParameterNumberOfChips:", externalParameterNumberOfChips);
|
debugLog("externalParameterNumberOfChips:", externalParameterNumberOfChips);
|
||||||
debugLog("externalParameterSource:", externalParameterSource);
|
|
||||||
debugLog("externalPhoneNumber:", externalParameterPhoneNumber);
|
debugLog("externalPhoneNumber:", externalParameterPhoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,6 +105,7 @@ function updateExternalParameterState() {
|
||||||
}
|
}
|
||||||
if (externalParameterZipCode) {
|
if (externalParameterZipCode) {
|
||||||
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_ZIP_CODE, externalParameterZipCode);
|
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_ZIP_CODE, externalParameterZipCode);
|
||||||
|
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, externalParameterStatus.ACTIVE);
|
||||||
}
|
}
|
||||||
if (externalParameterEmail) {
|
if (externalParameterEmail) {
|
||||||
store.commit(
|
store.commit(
|
||||||
|
|
@ -115,9 +126,6 @@ function updateExternalParameterState() {
|
||||||
externalParameterVinSelection
|
externalParameterVinSelection
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (externalParameterSource) {
|
|
||||||
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_SOURCE, externalParameterSource);
|
|
||||||
}
|
|
||||||
if (externalParameterPhoneNumber) {
|
if (externalParameterPhoneNumber) {
|
||||||
store.commit(
|
store.commit(
|
||||||
storeMutations.UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER,
|
storeMutations.UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER,
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,15 @@ export async function runExperiments(nextPageName) {
|
||||||
nextPageName
|
nextPageName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function forceExperiments(forceExperimentQueryStringValues) {
|
||||||
|
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.FORCE_EXPERIMENTS,
|
||||||
|
{
|
||||||
|
deviceId: getDeviceIdValue(),
|
||||||
|
queryStringExperiments: forceExperimentQueryStringValues,
|
||||||
|
},
|
||||||
|
"/",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ export async function errorBeforeEnter(to, from) {
|
||||||
nextPage: to?.name,
|
nextPage: to?.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
analyticsMixin.methods.logDigitalConsumer();
|
|
||||||
await handleHardError(errorPayload);
|
await handleHardError(errorPayload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +32,6 @@ export async function errorBeforeEnter(to, from) {
|
||||||
nextPage: to?.name,
|
nextPage: to?.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
analyticsMixin.methods.logDigitalConsumer();
|
|
||||||
await handleHardError(errorPayload);
|
await handleHardError(errorPayload);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
|
||||||
|
// Unsure if this is needed. I don't think its used anywhere
|
||||||
export async function heritageBeforeEnter(to, from) {
|
export async function heritageBeforeEnter(to, from) {
|
||||||
await navigateToHeritageFunnel({ shouldSaveSession: false, pageNameToLog: to.name });
|
await navigateToHeritageFunnel({ shouldSaveSession: false, pageNameToLog: to.name });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -192,6 +193,7 @@ const getDefaultState = () => {
|
||||||
isRecalAckOptIn: false,
|
isRecalAckOptIn: false,
|
||||||
isRecalAcknowledgedForScheduling: "",
|
isRecalAcknowledgedForScheduling: "",
|
||||||
isMSRFeeApplicable: false,
|
isMSRFeeApplicable: false,
|
||||||
|
isMSRFeeCoveredByInsurance: false,
|
||||||
},
|
},
|
||||||
applicationUser: {
|
applicationUser: {
|
||||||
eventBus: [],
|
eventBus: [],
|
||||||
|
|
@ -251,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;
|
||||||
},
|
},
|
||||||
|
|
@ -359,6 +370,9 @@ export const mutations = {
|
||||||
updateIsMSRFeeApplicable(state, isMSRFeeApplicable) {
|
updateIsMSRFeeApplicable(state, isMSRFeeApplicable) {
|
||||||
state.order.isMSRFeeApplicable = isMSRFeeApplicable;
|
state.order.isMSRFeeApplicable = isMSRFeeApplicable;
|
||||||
},
|
},
|
||||||
|
updateIsMSRFeeCoveredByInsurance(state, isMSRFeeCoveredByInsurance) {
|
||||||
|
state.order.isMSRFeeCoveredByInsurance = isMSRFeeCoveredByInsurance;
|
||||||
|
},
|
||||||
updateCashPriceSubTotal(state, cashPriceSubTotal) {
|
updateCashPriceSubTotal(state, cashPriceSubTotal) {
|
||||||
state.order.cashPriceSubTotal =
|
state.order.cashPriceSubTotal =
|
||||||
cashPriceSubTotal === "" ? null : cashPriceSubTotal.toString();
|
cashPriceSubTotal === "" ? null : cashPriceSubTotal.toString();
|
||||||
|
|
@ -1593,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
|
||||||
|
|
@ -1630,38 +1645,6 @@ export const actions = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
logDigitalConsumer(
|
|
||||||
context,
|
|
||||||
{
|
|
||||||
actionName,
|
|
||||||
referralSequenceNumber,
|
|
||||||
referralNumber,
|
|
||||||
workOrderId,
|
|
||||||
workOrderNumber,
|
|
||||||
conceptVariation,
|
|
||||||
isConceptExposed,
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
var payload = {
|
|
||||||
sessionId: getSessionIdValue(),
|
|
||||||
deviceId: getDeviceIdValue(),
|
|
||||||
actionName: actionName ?? "",
|
|
||||||
referralSequenceNumber: referralSequenceNumber ?? "",
|
|
||||||
referralNumber: referralNumber ?? "",
|
|
||||||
applicationName: baseMixin.methods.isMobileDevice() ? "2.0 Mobile" : "2.0",
|
|
||||||
workOrderId: workOrderId ?? "",
|
|
||||||
workOrderNumber: workOrderNumber ?? "",
|
|
||||||
conceptVariation: conceptVariation,
|
|
||||||
isConceptExposed: isConceptExposed,
|
|
||||||
};
|
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
|
||||||
method: endpoints.LogDigitalConsumer.method,
|
|
||||||
endpoint: endpoints.LogDigitalConsumer.url,
|
|
||||||
payload: payload,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
logFmgSessionData(
|
logFmgSessionData(
|
||||||
context,
|
context,
|
||||||
{
|
{
|
||||||
|
|
@ -1770,6 +1753,7 @@ export const actions = {
|
||||||
method: endpoints.LogFmgSessionData.method,
|
method: endpoints.LogFmgSessionData.method,
|
||||||
endpoint: endpoints.LogFmgSessionData.url,
|
endpoint: endpoints.LogFmgSessionData.url,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
|
logApiCall: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1816,6 +1800,25 @@ export const actions = {
|
||||||
context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments);
|
context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async forceExperiments(
|
||||||
|
context,
|
||||||
|
{ payload: { deviceId, queryStringExperiments }, pageNameToLog }
|
||||||
|
) {
|
||||||
|
var payload = {
|
||||||
|
applicationName: applicationConfig.APPLICATION_NAME,
|
||||||
|
deviceId: deviceId,
|
||||||
|
queryStringExperiments: queryStringExperiments,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await globalMethods.callHttpClient({
|
||||||
|
method: endpoints.ForceExperiments.method,
|
||||||
|
endpoint: endpoints.ForceExperiments.url,
|
||||||
|
payload: payload,
|
||||||
|
logApiCall: true,
|
||||||
|
pageNameToLog: pageNameToLog,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getEvoxImage(context, { relativeUrl }) {
|
getEvoxImage(context, { relativeUrl }) {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetPageData.method,
|
method: endpoints.GetPageData.method,
|
||||||
|
|
@ -2058,52 +2061,27 @@ export const actions = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||||
const escapeRecalibrationType = (rt) => rt.split("&").join("%26");
|
const flattenedLineItemsWithChildParts = getFlattenedArrayOfLineItemsWithChildParts(
|
||||||
|
context.getters.order.lineItems.glassParts
|
||||||
const partialLineItemsObjects = context.getters.order.lineItems.glassParts?.map(
|
|
||||||
(lineItem) => ({
|
|
||||||
partNumber: lineItem.partNumber,
|
|
||||||
recalibrationType:
|
|
||||||
lineItem.recalibrationType && isRecalPartOrHasChildRecalPart(lineItem)
|
|
||||||
? escapeRecalibrationType(lineItem.recalibrationType)
|
|
||||||
: undefined,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var lineItems = null;
|
const lineItemsList = flattenedLineItemsWithChildParts
|
||||||
if (partialLineItemsObjects) {
|
.map((item) => item.partNumber)
|
||||||
lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
|
.join(",");
|
||||||
partialLineItemsObjects,
|
|
||||||
"lineItems"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const vehicle = context.getters.vehicle;
|
const vehicle = context.getters.vehicle;
|
||||||
const carId = vehicle.carId;
|
const carId = vehicle.carId;
|
||||||
const damage = context.getters.damage;
|
const damage = context.getters.damage;
|
||||||
const glassArray = convertGlassPieceNamingForApi(damage.glassToReplace);
|
|
||||||
|
|
||||||
const glassPieces = buildQueryStringParameterFromArrayOfComplexObjects(
|
|
||||||
glassArray,
|
|
||||||
"glassPieces"
|
|
||||||
);
|
|
||||||
|
|
||||||
const parentAccountNumber =
|
const parentAccountNumber =
|
||||||
context.getters.order.payment.parentAccountNumber ??
|
context.getters.order.payment.parentAccountNumber ??
|
||||||
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
|
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
|
||||||
const referralSequenceNumber = context.getters.order.referralSequenceNumber;
|
|
||||||
|
|
||||||
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&parentAccountNumber=${parentAccountNumber}&referralSequenceNumber=${referralSequenceNumber}&applicationName=${applicationConfig.ANALYTICS_APPLICATION_NAME}`;
|
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&parentAccountNumber=${parentAccountNumber}&isRepair=${damage.isRepair}`;
|
||||||
if (lineItems) {
|
if (lineItemsList) {
|
||||||
endPoint += `&${lineItems}`;
|
endPoint += `&lineItems=${lineItemsList}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glassPieces) {
|
|
||||||
endPoint += `&${glassPieces}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
endPoint += `&isHeavyTruckVehicle=${context.getters.order.vehicle.isBigTruck}`;
|
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetServiceabilityDetails.method,
|
method: endpoints.GetServiceabilityDetails.method,
|
||||||
endpoint: endPoint,
|
endpoint: endPoint,
|
||||||
|
|
@ -2146,6 +2124,7 @@ export const actions = {
|
||||||
const carId = context.getters.vehicle.carId;
|
const carId = context.getters.vehicle.carId;
|
||||||
const isRepair = context.getters.damage.isRepair;
|
const isRepair = context.getters.damage.isRepair;
|
||||||
const numberOfChips = context.getters.damage.numberOfChips;
|
const numberOfChips = context.getters.damage.numberOfChips;
|
||||||
|
const apptType = context.getters.order?.serviceLocation?.appointmentType;
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetSupportingItems.method,
|
method: endpoints.GetSupportingItems.method,
|
||||||
|
|
@ -2156,6 +2135,9 @@ export const actions = {
|
||||||
parentAccountNumber: applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
parentAccountNumber: applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
||||||
parts: glassPartsArray,
|
parts: glassPartsArray,
|
||||||
numberOfRepairChips: isRepair ? numberOfChips : 0,
|
numberOfRepairChips: isRepair ? numberOfChips : 0,
|
||||||
|
providerNumber: context.getters.order.serviceLocation.zipCodeCtu,
|
||||||
|
facilityType:
|
||||||
|
apptType && apptType.trim() !== "" ? apptType : AppointmentTypeStrings.IN_SHOP,
|
||||||
},
|
},
|
||||||
logApiCall: true,
|
logApiCall: true,
|
||||||
pageNameToLog: pageNameToLog,
|
pageNameToLog: pageNameToLog,
|
||||||
|
|
@ -2611,6 +2593,7 @@ export const actions = {
|
||||||
lockToken: order.lockToken,
|
lockToken: order.lockToken,
|
||||||
isRecalAckOptIn: order.isRecalAckOptIn,
|
isRecalAckOptIn: order.isRecalAckOptIn,
|
||||||
isMSRFeeApplicable: order.isMSRFeeApplicable,
|
isMSRFeeApplicable: order.isMSRFeeApplicable,
|
||||||
|
isMSRFeeCoveredByInsurance: order.isMSRFeeCoveredByInsurance,
|
||||||
cashPriceSubTotal: order.cashPriceSubTotal,
|
cashPriceSubTotal: order.cashPriceSubTotal,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -2723,6 +2706,8 @@ export const actions = {
|
||||||
imageUrl,
|
imageUrl,
|
||||||
imageVifNumber,
|
imageVifNumber,
|
||||||
imageVifColor,
|
imageVifColor,
|
||||||
|
skipVINLookup,
|
||||||
|
skipPartQuestions,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
|
|
@ -2748,6 +2733,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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -3043,6 +3030,13 @@ export const actions = {
|
||||||
context.commit(storeMutations.UPDATE_IS_MSR_FEE_APPLICABLE, isMSRFeeApplicable);
|
context.commit(storeMutations.UPDATE_IS_MSR_FEE_APPLICABLE, isMSRFeeApplicable);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveIsMSRFeeCoveredByInsurance(context, isMSRFeeCoveredByInsurance) {
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_IS_MSR_FEE_COVERED_BY_INSURANCE,
|
||||||
|
isMSRFeeCoveredByInsurance
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
saveParentAccountNumber(context, parentAccountNumber) {
|
saveParentAccountNumber(context, parentAccountNumber) {
|
||||||
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber);
|
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber);
|
||||||
},
|
},
|
||||||
|
|
@ -3535,25 +3529,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;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
121
src/ux-components/tool-tip/tool-tip.spec.js
Normal file
121
src/ux-components/tool-tip/tool-tip.spec.js
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { shallowMount, mount } from "@vue/test-utils";
|
||||||
|
import toolTip from "@/ux-components/tool-tip/tool-tip.vue";
|
||||||
|
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn(() => "Mock CMS Content"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function setupMocks(mountOptionsMockData = {}) {
|
||||||
|
const defaultMountOptions = {
|
||||||
|
propsData: {
|
||||||
|
cmsWidgetName: "testWidget",
|
||||||
|
},
|
||||||
|
mixins: [mockMixin],
|
||||||
|
};
|
||||||
|
return { ...defaultMountOptions, ...mountOptionsMockData };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("toolTip.vue", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show tooltip initially", () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = shallowMount(toolTip, setupMocks());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.isVisible).toBe(false);
|
||||||
|
expect(wrapper.find(".tooltip").exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show tooltip when icon is clicked", async () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = shallowMount(toolTip, setupMocks());
|
||||||
|
const icon = wrapper.find(".tooltip-icon");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await icon.trigger("click");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.isVisible).toBe(true);
|
||||||
|
expect(wrapper.find(".tooltip").exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hide tooltip when clicked outside", async () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = shallowMount(toolTip, setupMocks());
|
||||||
|
const icon = wrapper.find(".tooltip-icon");
|
||||||
|
|
||||||
|
// Show tooltip first
|
||||||
|
await icon.trigger("click");
|
||||||
|
expect(wrapper.vm.isVisible).toBe(true);
|
||||||
|
|
||||||
|
// Act - simulate click outside
|
||||||
|
const clickEvent = new Event("click");
|
||||||
|
Object.defineProperty(clickEvent, "target", { value: document.body });
|
||||||
|
document.dispatchEvent(clickEvent);
|
||||||
|
|
||||||
|
// Wait for next tick to allow event handling
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.isVisible).toBe(false);
|
||||||
|
expect(wrapper.find(".tooltip").exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should toggle tooltip visibility when icon is clicked multiple times", async () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = shallowMount(toolTip, setupMocks());
|
||||||
|
const icon = wrapper.find(".tooltip-icon");
|
||||||
|
|
||||||
|
// Act & Assert - First click shows
|
||||||
|
await icon.trigger("click");
|
||||||
|
expect(wrapper.vm.isVisible).toBe(true);
|
||||||
|
|
||||||
|
// Second click hides
|
||||||
|
await icon.trigger("click");
|
||||||
|
expect(wrapper.vm.isVisible).toBe(false);
|
||||||
|
|
||||||
|
// Third click shows again
|
||||||
|
await icon.trigger("click");
|
||||||
|
expect(wrapper.vm.isVisible).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not hide tooltip when clicking inside the tooltip container", async () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = shallowMount(toolTip, setupMocks());
|
||||||
|
const icon = wrapper.find(".tooltip-icon");
|
||||||
|
|
||||||
|
// Show tooltip first
|
||||||
|
await icon.trigger("click");
|
||||||
|
expect(wrapper.vm.isVisible).toBe(true);
|
||||||
|
|
||||||
|
// Act - simulate click inside container
|
||||||
|
const container = wrapper.find(".tooltip-container").element;
|
||||||
|
const clickEvent = new Event("click");
|
||||||
|
Object.defineProperty(clickEvent, "target", { value: container });
|
||||||
|
document.dispatchEvent(clickEvent);
|
||||||
|
|
||||||
|
// Wait for next tick
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert - tooltip should still be visible
|
||||||
|
expect(wrapper.vm.isVisible).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should calculate lineItemContainerLength based on parent element width", () => {
|
||||||
|
// Arrange
|
||||||
|
const wrapper = mount(toolTip, setupMocks());
|
||||||
|
const parentDiv = document.createElement("div");
|
||||||
|
Object.defineProperty(parentDiv, "offsetWidth", { value: 300, writable: true });
|
||||||
|
|
||||||
|
const container = wrapper.vm.$refs.container;
|
||||||
|
Object.defineProperty(container, "parentElement", { value: parentDiv, writable: true });
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
expect(wrapper.vm.lineItemContainerLength).toBe(-300);
|
||||||
|
});
|
||||||
|
});
|
||||||
140
src/ux-components/tool-tip/tool-tip.vue
Normal file
140
src/ux-components/tool-tip/tool-tip.vue
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<template>
|
||||||
|
<span class="tooltip-container" ref="container">
|
||||||
|
<span @click="toggleTooltip" class="tooltip-icon">
|
||||||
|
<slot name="icon">
|
||||||
|
<img src="@/assets/img/icons/info-circle-blue.svg" />
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="isVisible"
|
||||||
|
class="tooltip"
|
||||||
|
:class="{ 'tooltip-visible': isVisible }"
|
||||||
|
:style="{ left: lineItemContainerLength + 'px' }">
|
||||||
|
<slot>
|
||||||
|
<h3 v-html="toolTipHeaderText"></h3>
|
||||||
|
<p v-html="toolTipBodyText"></p>
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "toolTip",
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: {
|
||||||
|
type: String,
|
||||||
|
default(rawProps) {
|
||||||
|
if (!rawProps.cmsWidgetName) {
|
||||||
|
console.log("Error: Missing a CMS Widget Name (required field)");
|
||||||
|
}
|
||||||
|
return "widgetUndefined";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customHeaderText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
customBodyText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isVisible: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
document.addEventListener("click", this.handleClickOutside);
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
document.removeEventListener("click", this.handleClickOutside);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
toolTipHeaderText() {
|
||||||
|
return (
|
||||||
|
this.customHeaderText || this.getCmsContent(this.cmsWidgetName, "HeaderText") || ""
|
||||||
|
);
|
||||||
|
},
|
||||||
|
toolTipBodyText() {
|
||||||
|
return this.customBodyText || this.getCmsContent(this.cmsWidgetName, "BodyText") || "";
|
||||||
|
},
|
||||||
|
lineItemContainerLength() {
|
||||||
|
// Get the width of the parent container - used to position the tooltip to the left
|
||||||
|
if (this.$refs.container && this.$refs.container.parentElement) {
|
||||||
|
return 0 - this.$refs.container.parentElement.offsetWidth;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showTooltip() {
|
||||||
|
this.isVisible = true;
|
||||||
|
},
|
||||||
|
hideTooltip() {
|
||||||
|
this.isVisible = false;
|
||||||
|
},
|
||||||
|
toggleTooltip() {
|
||||||
|
this.isVisible = !this.isVisible;
|
||||||
|
},
|
||||||
|
handleClickOutside(event) {
|
||||||
|
if (this.$refs.container && !this.$refs.container.contains(event.target)) {
|
||||||
|
this.hideTooltip();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tooltip-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
|
||||||
|
.tooltip-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition:
|
||||||
|
opacity 0.2s,
|
||||||
|
visibility 0.2s;
|
||||||
|
z-index: 500;
|
||||||
|
width: 16.5rem;
|
||||||
|
background-color: $white;
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: $font-weight-600;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-visible {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue