Merge branch 'develop' into CSR-1891-insurance-search-page
This commit is contained in:
commit
9e55eead45
57 changed files with 1990 additions and 452 deletions
|
|
@ -31,7 +31,7 @@ module.exports = {
|
||||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
statements: 78,
|
statements: 77,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||||
|
|
|
||||||
23
src/App.vue
23
src/App.vue
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }" ref="router-view">
|
||||||
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
|
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
|
||||||
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
||||||
<component :is="Component" @focusin="handleAnyComponentFocus" />
|
<component :is="Component" ref="component" @focusin="handleAnyComponentFocus" />
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
<loadingModal :showLoader="shouldShowLoader" :showTextCarousel="shouldShowTextCarousel" />
|
<loadingModal :showLoader="shouldShowLoader" :showTextCarousel="shouldShowTextCarousel" />
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper";
|
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper";
|
||||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||||
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "app",
|
name: "app",
|
||||||
setup() {
|
setup() {
|
||||||
|
|
@ -33,6 +34,24 @@ export default {
|
||||||
loadingModal,
|
loadingModal,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add a global Javascript exception handler for logging exceptions, this handler will log when there is an uncaught exception
|
||||||
|
window.onerror = (msg, url, line, col, error) => {
|
||||||
|
// Log the windows error
|
||||||
|
// Note that col & error are new to the HTML 5 spec and may not be
|
||||||
|
// supported in every browser. It worked for me in Chrome.
|
||||||
|
global.$logger.logError(msg, {
|
||||||
|
url: url,
|
||||||
|
line: line,
|
||||||
|
column: col ?? "",
|
||||||
|
error: error ?? "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// If you return true, then error alerts (like in older versions of
|
||||||
|
// Internet Explorer) will be suppressed.
|
||||||
|
var suppressErrorAlert = true;
|
||||||
|
return suppressErrorAlert;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const applicationConfig = {
|
||||||
PAGE_QUERYSTRING: "fmgPage",
|
PAGE_QUERYSTRING: "fmgPage",
|
||||||
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
|
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
|
||||||
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
||||||
|
CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER: "87291",
|
||||||
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT,
|
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT,
|
||||||
PIA_RESPONSE_URL:
|
PIA_RESPONSE_URL:
|
||||||
location.protocol +
|
location.protocol +
|
||||||
|
|
@ -27,6 +28,7 @@ const applicationConfig = {
|
||||||
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",
|
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",
|
||||||
OUTLOOK_CALENDAR:
|
OUTLOOK_CALENDAR:
|
||||||
"https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent",
|
"https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent",
|
||||||
|
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { applicationConfig };
|
export { applicationConfig };
|
||||||
|
|
|
||||||
11
src/constants/axios-response-interceptor-messages.js
Normal file
11
src/constants/axios-response-interceptor-messages.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
const axiosResponseInterceptorMessages = {
|
||||||
|
NETWORK_ERROR:
|
||||||
|
"A network error occurred. " +
|
||||||
|
"This could be a bad URL, a CORS issue, or a dropped internet connection. " +
|
||||||
|
"It is impossible for us to know.",
|
||||||
|
STATUS_CODE_ERROR: "Status Code Error",
|
||||||
|
NO_RESPONSE_ERROR: "The request was made but no response was received",
|
||||||
|
GENERIC_ERROR: "Error",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default axiosResponseInterceptorMessages;
|
||||||
18
src/constants/coverage-status.js
Normal file
18
src/constants/coverage-status.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
const coverageStatus = {
|
||||||
|
PENDING: "Pending",
|
||||||
|
NOCOMP: "NoComp",
|
||||||
|
VERIFIED: "Verified",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function coverageStatusValue(intCoverageStatus) {
|
||||||
|
switch (intCoverageStatus) {
|
||||||
|
case 0:
|
||||||
|
return coverageStatus.PENDING;
|
||||||
|
case 1:
|
||||||
|
return coverageStatus.NOCOMP;
|
||||||
|
case 2:
|
||||||
|
return coverageStatus.VERIFIED;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,9 @@ const experimentSettings = {
|
||||||
SUPPRESS_VIN_CAPTURE: "SuppressVinCapture",
|
SUPPRESS_VIN_CAPTURE: "SuppressVinCapture",
|
||||||
DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators",
|
DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators",
|
||||||
PIA_EXPERIENCE: "PIA Experience",
|
PIA_EXPERIENCE: "PIA Experience",
|
||||||
|
PIA_INSURANCE: "DisplayPIAInsurance",
|
||||||
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
|
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
|
||||||
|
IS_EMAIL_OPTIONAL: "isEmailOptional",
|
||||||
};
|
};
|
||||||
|
|
||||||
const experimentTriggers = {
|
const experimentTriggers = {
|
||||||
|
|
|
||||||
8
src/constants/logging-endpoint-methods.js
Normal file
8
src/constants/logging-endpoint-methods.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
const loggingEndpointMethods = {
|
||||||
|
LOG_INFORMATION: "log-information",
|
||||||
|
LOG_WARNING: "log-warning",
|
||||||
|
LOG_ERROR: "log-error",
|
||||||
|
LOG_CRITICAL: "log-critical",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default loggingEndpointMethods;
|
||||||
|
|
@ -21,6 +21,10 @@ const queryStrings = {
|
||||||
TRANS_REFERENCE_NUMBER: "auth_trans_ref_no",
|
TRANS_REFERENCE_NUMBER: "auth_trans_ref_no",
|
||||||
LAST_FOUR: "last_four",
|
LAST_FOUR: "last_four",
|
||||||
DISPLAY_PIA_ALERT: "displayPiaAlert",
|
DISPLAY_PIA_ALERT: "displayPiaAlert",
|
||||||
|
PAGE_ERROR: "pageerror",
|
||||||
|
REFERRAL_NUMBER: "rn",
|
||||||
|
PARENT_ACCOUNT: "pa",
|
||||||
|
CORRELATION_ID: "ci",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { queryStrings };
|
export { queryStrings };
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ const storeActions = {
|
||||||
SAVE_PAYMENT_TYPE: "savePaymentType",
|
SAVE_PAYMENT_TYPE: "savePaymentType",
|
||||||
SAVE_PAYMENT_METHOD_CHOICE: "savePaymentMethodChoice",
|
SAVE_PAYMENT_METHOD_CHOICE: "savePaymentMethodChoice",
|
||||||
SAVE_PARENT_ACCOUNT_NUMBER: "saveParentAccountNumber",
|
SAVE_PARENT_ACCOUNT_NUMBER: "saveParentAccountNumber",
|
||||||
|
SAVE_BILL_TO_ACCOUNT_NUMBER: "saveBillToAccountNumber",
|
||||||
SAVE_SUPPORTING_ITEMS: "saveSupportingItems",
|
SAVE_SUPPORTING_ITEMS: "saveSupportingItems",
|
||||||
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
||||||
"saveSupportingItemsSuppressingStateResetting",
|
"saveSupportingItemsSuppressingStateResetting",
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ const storeMutations = {
|
||||||
UPDATE_REFERRAL_DATE: "updateReferralDate",
|
UPDATE_REFERRAL_DATE: "updateReferralDate",
|
||||||
UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId",
|
UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId",
|
||||||
UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber",
|
UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber",
|
||||||
|
UPDATE_BILL_TO_ACCT_NUMBER: "updateBillToAcctNumber",
|
||||||
UPDATE_EON: "updateEON",
|
UPDATE_EON: "updateEON",
|
||||||
UPDATE_IS_INSURANCE: "updateIsInsurance",
|
UPDATE_IS_INSURANCE: "updateIsInsurance",
|
||||||
UPDATE_SAVED_SESSION_ID: "updateSavedSessionId",
|
UPDATE_SAVED_SESSION_ID: "updateSavedSessionId",
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,11 @@ export default {
|
||||||
hideInput: Boolean,
|
hideInput: Boolean,
|
||||||
centerErrorMessage: Boolean,
|
centerErrorMessage: Boolean,
|
||||||
keyDownHandler: Function,
|
keyDownHandler: Function,
|
||||||
|
addOptionalText: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
isRequired: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const uuid = uuidv4();
|
const uuid = uuidv4();
|
||||||
|
|
@ -202,7 +207,9 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
questionText() {
|
questionText() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
return this.addOptionalText
|
||||||
|
? this.getCmsContent(this.cmsWidgetName, "QuestionText") + " (optional)"
|
||||||
|
: this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
get: function () {
|
get: function () {
|
||||||
|
|
|
||||||
|
|
@ -13,78 +13,105 @@
|
||||||
<span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span>
|
<span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="price-table">
|
<div class="cart-panel">
|
||||||
<div class="service-type">
|
<div class="price-table">
|
||||||
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
|
<div class="service-type">
|
||||||
<span>
|
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
|
||||||
{{ getFormattedAmount("", packagePrice) }}
|
<span>
|
||||||
</span>
|
{{ getFormattedAmount("", packagePrice) }}
|
||||||
</div>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="packagePriceWithoutDiscount > packagePrice"
|
||||||
|
class="struck-out-price">
|
||||||
|
{{ getFormattedAmount("", packagePriceWithoutDiscount) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Packaged Cart Items -->
|
<!-- Packaged Cart Items -->
|
||||||
<div v-for="(cartItem, i) in packageCartItems" :key="i" class="packaged-cart-item">
|
<div
|
||||||
<span>{{ cartItem.name }}</span>
|
v-for="(cartItem, i) in packageCartItems"
|
||||||
<textLink
|
:key="i"
|
||||||
v-if="allowItemRemoval"
|
class="packaged-cart-item">
|
||||||
ref="removeLink"
|
<span
|
||||||
linkType="text"
|
><span v-if="cartItem.category === 'promos'"></span>{{ cartItem.name
|
||||||
:text="removeLinkText"
|
}}<span v-if="cartItem.category === 'promos'"></span
|
||||||
href="javascript:void(0)"
|
></span>
|
||||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)">
|
<textLink
|
||||||
<template v-slot:after-text>
|
v-if="allowItemRemoval"
|
||||||
<span class="sr-only">{{ cartItem.name }}</span>
|
ref="removeLink"
|
||||||
</template>
|
linkType="text"
|
||||||
</textLink>
|
:text="removeLinkText"
|
||||||
</div>
|
href="javascript:void(0)"
|
||||||
|
@click-event="removeItem(cartItem.cartItemType, cartItem.category)">
|
||||||
|
<template v-slot:after-text>
|
||||||
|
<span class="sr-only">{{ cartItem.name }}</span>
|
||||||
|
</template>
|
||||||
|
</textLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Nonpackaged Cart Items -->
|
<!-- Nonpackaged Cart Items -->
|
||||||
<div
|
<div
|
||||||
v-for="(cartItem, i) in nonpackageCartItems"
|
v-for="(cartItem, i) in nonpackageCartItems"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="non-packaged-cart-item"
|
class="non-packaged-cart-item"
|
||||||
:class="[
|
:class="[
|
||||||
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="cartItem != recycleFeeCartItem">{{ cartItem.name }}</span>
|
||||||
<textLink
|
<textLink
|
||||||
v-if="allowItemRemoval && cartItem.isRemovable"
|
v-if="allowItemRemoval && cartItem.isRemovable"
|
||||||
ref="removeLink"
|
ref="removeLink"
|
||||||
linkType="text"
|
linkType="text"
|
||||||
:text="removeLinkText"
|
:text="removeLinkText"
|
||||||
href="javascript:void(0)"
|
href="javascript:void(0)"
|
||||||
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
|
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
|
||||||
<textLink
|
<textLink
|
||||||
v-else-if="cartItem == recycleFeeCartItem && recyclingModalCmsWidgetName"
|
v-else-if="
|
||||||
linkType="text"
|
cartItem == recycleFeeCartItem && recyclingModalCmsWidgetName
|
||||||
:text="recycleFeeCartItem.name"
|
"
|
||||||
href="javascript:void(0)"
|
linkType="text"
|
||||||
@click-event="openModal(recyclingModalCmsWidgetName)" />
|
:text="recycleFeeCartItem.name"
|
||||||
<span v-else-if="cartItem == recycleFeeCartItem">
|
href="javascript:void(0)"
|
||||||
{{ recycleFeeCartItem.name }}
|
@click-event="openModal(recyclingModalCmsWidgetName)" />
|
||||||
</span>
|
<span v-else-if="cartItem == recycleFeeCartItem">
|
||||||
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
|
{{ recycleFeeCartItem.name }}
|
||||||
</div>
|
</span>
|
||||||
|
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Sub total, sales tax, total columns -->
|
<!-- Sub total, sales tax, total columns -->
|
||||||
<div class="sub-total">
|
<div class="sub-total">
|
||||||
<span>{{ subtotalText }}</span
|
<span>{{ subtotalText }}</span
|
||||||
><span>{{ getFormattedAmount("", subTotal) }}</span>
|
><span>{{ getFormattedAmount("", subTotal) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sales-tax">
|
<div class="sales-tax">
|
||||||
<span>{{ salesTaxText }}</span
|
<span>{{ salesTaxText }}</span
|
||||||
><span>{{ getFormattedAmount("", salesTax) }}</span>
|
><span>{{ getFormattedAmount("", salesTax) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showAsPaid" class="amount-paid">
|
<div v-if="showAsPaid" class="amount-paid">
|
||||||
<span>{{ amountPaidText }}</span
|
<span>{{ amountPaidText }}</span
|
||||||
><span>{{ getFormattedAmount("", amountPaid) }}</span>
|
><span>{{ getFormattedAmount("", amountPaid) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="amount-due">
|
<div class="amount-due">
|
||||||
<span>{{ amountDueText }}</span
|
<span>{{ amountDueText }}</span
|
||||||
><span>{{ getFormattedAmount("", amountDue) }}</span>
|
><span>{{ getFormattedAmount("", amountDue) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<promoModalQuestion
|
||||||
|
ref="promoModalQuestion"
|
||||||
|
class="small mt-4"
|
||||||
|
v-model="lineItems"
|
||||||
|
:availableVaps="availableVaps"
|
||||||
|
modalWidgetName="PromoModalWidget" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="my-2 lh-1 applied-promo"
|
||||||
|
v-for="(promoCode, i) in this.getPromoCodeList()"
|
||||||
|
:key="i">
|
||||||
|
<span class="caption">Promo code {{ promoCode }} applied </span>
|
||||||
|
</div>
|
||||||
<contentGroupModal ref="RecycleModal" cmsWidgetName="RecycleModal">
|
<contentGroupModal ref="RecycleModal" cmsWidgetName="RecycleModal">
|
||||||
<textBlock cmsWidgetName="RecycleTextBlock" />
|
<textBlock cmsWidgetName="RecycleTextBlock" />
|
||||||
</contentGroupModal>
|
</contentGroupModal>
|
||||||
|
|
@ -96,6 +123,7 @@
|
||||||
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 contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
||||||
|
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
||||||
|
|
||||||
// Mixins
|
// Mixins
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
@ -128,7 +156,6 @@ export default {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
}),
|
}),
|
||||||
lineItems: deepClone(this.modelValue),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -149,6 +176,17 @@ export default {
|
||||||
|
|
||||||
return subTotal;
|
return subTotal;
|
||||||
},
|
},
|
||||||
|
getPromoDiscounts() {
|
||||||
|
const promoItems = this.promoCartItems;
|
||||||
|
|
||||||
|
let subTotal = 0;
|
||||||
|
|
||||||
|
promoItems?.forEach((item) => {
|
||||||
|
subTotal += item.subTotal;
|
||||||
|
});
|
||||||
|
|
||||||
|
return subTotal;
|
||||||
|
},
|
||||||
getVapsCartItemsForSelectedPackage(packageName) {
|
getVapsCartItemsForSelectedPackage(packageName) {
|
||||||
const packageContentTypes = getPackageContents(
|
const packageContentTypes = getPackageContents(
|
||||||
this.glassToReplace,
|
this.glassToReplace,
|
||||||
|
|
@ -167,6 +205,12 @@ export default {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.promoCartItems) {
|
||||||
|
this.promoCartItems.forEach((promoCartItem) => {
|
||||||
|
vapsCartItemsForSelectedPackage.push(promoCartItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return vapsCartItemsForSelectedPackage;
|
return vapsCartItemsForSelectedPackage;
|
||||||
},
|
},
|
||||||
getCmsContentForVapsType(vapsType) {
|
getCmsContentForVapsType(vapsType) {
|
||||||
|
|
@ -189,11 +233,23 @@ export default {
|
||||||
this.lineItems[category] = this.lineItems[category].filter(
|
this.lineItems[category] = this.lineItems[category].filter(
|
||||||
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
|
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
|
||||||
);
|
);
|
||||||
|
},
|
||||||
this.$emit("update:modelValue", this.lineItems);
|
getPromoCodeList() {
|
||||||
|
if (this.$refs["promoModalQuestion"]) {
|
||||||
|
return this.$refs["promoModalQuestion"].getPromoCodeList();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
lineItems: {
|
||||||
|
get: function () {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.$emit("update:modelValue", newValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
screenReaderTotalAmountDueText() {
|
screenReaderTotalAmountDueText() {
|
||||||
return this.getCmsContent("ScreenReaderTotalAmountDueWidget", "Text");
|
return this.getCmsContent("ScreenReaderTotalAmountDueWidget", "Text");
|
||||||
},
|
},
|
||||||
|
|
@ -313,6 +369,12 @@ export default {
|
||||||
|
|
||||||
return packagePrice;
|
return packagePrice;
|
||||||
},
|
},
|
||||||
|
packagePriceWithoutDiscount() {
|
||||||
|
let fullPrice = this.packagePrice;
|
||||||
|
let discount = this.getPromoDiscounts();
|
||||||
|
fullPrice += Math.abs(discount);
|
||||||
|
return fullPrice;
|
||||||
|
},
|
||||||
isRepair() {
|
isRepair() {
|
||||||
if (!this.damage) {
|
if (!this.damage) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -540,7 +602,6 @@ export default {
|
||||||
|
|
||||||
return cartItem;
|
return cartItem;
|
||||||
},
|
},
|
||||||
|
|
||||||
suppliesRepairCartItemName() {
|
suppliesRepairCartItemName() {
|
||||||
return this.getCmsContent("SuppliesRepairTextWidget", "Text");
|
return this.getCmsContent("SuppliesRepairTextWidget", "Text");
|
||||||
},
|
},
|
||||||
|
|
@ -578,7 +639,6 @@ export default {
|
||||||
|
|
||||||
return cartItem;
|
return cartItem;
|
||||||
},
|
},
|
||||||
|
|
||||||
mobileFeeCartItemName() {
|
mobileFeeCartItemName() {
|
||||||
return this.getCmsContent("MobileServiceTextWidget", "Text");
|
return this.getCmsContent("MobileServiceTextWidget", "Text");
|
||||||
},
|
},
|
||||||
|
|
@ -734,7 +794,6 @@ export default {
|
||||||
|
|
||||||
return promoCartItems;
|
return promoCartItems;
|
||||||
},
|
},
|
||||||
|
|
||||||
removeLinkText() {
|
removeLinkText() {
|
||||||
return this.getCmsContent("RemoveCartItemTextWidget", "Text");
|
return this.getCmsContent("RemoveCartItemTextWidget", "Text");
|
||||||
},
|
},
|
||||||
|
|
@ -783,18 +842,11 @@ export default {
|
||||||
return this.subTotal + this.salesTax;
|
return this.subTotal + this.salesTax;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
modelValue: {
|
|
||||||
handler(newValue) {
|
|
||||||
this.lineItems = deepClone(newValue);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
textBlock,
|
textBlock,
|
||||||
textLink,
|
textLink,
|
||||||
contentGroupModal,
|
contentGroupModal,
|
||||||
|
promoModalQuestion,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -824,12 +876,14 @@ export default {
|
||||||
.amount-due {
|
.amount-due {
|
||||||
color: $green;
|
color: $green;
|
||||||
}
|
}
|
||||||
.price-table {
|
.cart-panel {
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
transition: all 350ms ease-in;
|
transition: all 350ms ease-in;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
.price-table {
|
||||||
div {
|
div {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -846,6 +900,10 @@ export default {
|
||||||
background-color: $gray-100;
|
background-color: $gray-100;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.struck-out-price {
|
||||||
|
text-decoration: line-through;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
.packaged-cart-item {
|
.packaged-cart-item {
|
||||||
background-color: $gray-100;
|
background-color: $gray-100;
|
||||||
padding-left: 2.5rem;
|
padding-left: 2.5rem;
|
||||||
|
|
@ -934,17 +992,26 @@ export default {
|
||||||
&.expanded:after {
|
&.expanded:after {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
&.expanded + .price-table {
|
&.expanded + .cart-panel {
|
||||||
max-height: 650px;
|
max-height: 650px;
|
||||||
transition: all 150ms ease-in;
|
transition: all 150ms ease-in;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-top: 1rem;
|
padding: 1rem 0 0.5rem;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.applied-promo {
|
||||||
|
span {
|
||||||
|
color: $green-700;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background-color: $green-100;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.payment-method-question {
|
.payment-method-question {
|
||||||
.question-text {
|
.question-text {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,400 @@
|
||||||
|
import { mount, shallowMount } from "@vue/test-utils";
|
||||||
|
import promoModalQuestion from "./promo-modal-question";
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
import {
|
||||||
|
promoErrorCodes,
|
||||||
|
getPromoCodesFromPromoObjectsWithoutDuplicates,
|
||||||
|
getPromoCodeWithoutBundleIdentifier,
|
||||||
|
} from "@/helpers/promotions-helper";
|
||||||
|
import { cartItemCategories } from "@/constants/cart-item-categories";
|
||||||
|
|
||||||
|
let mockReturnsForStoreActions = {};
|
||||||
|
|
||||||
|
jest.mock("@/mixins/base-mixin", () => ({
|
||||||
|
...jest.requireActual("@/mixins/base-mixin"),
|
||||||
|
methods: {
|
||||||
|
dispatchStoreActionWithLogging: jest.fn(
|
||||||
|
(action, { promoCode, addableVaps }, pageNameToLog, someBool) => {
|
||||||
|
return mockReturnsForStoreActions[action];
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// reset store action returns
|
||||||
|
mockReturnsForStoreActions = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock("@/digital-components/textbox-question/textbox-question", () => ({
|
||||||
|
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||||
|
return widgetName[cmsFieldName];
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/digital-components/modal/modal", () => ({
|
||||||
|
methods: {
|
||||||
|
closeModal: jest.fn(),
|
||||||
|
resetButtonStyle: jest.fn(),
|
||||||
|
resetForm: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const modalWidgetName = "modalWidgetName";
|
||||||
|
|
||||||
|
const mockModalCmsContent = {
|
||||||
|
FooterText: "Sample modal footer text here.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||||
|
if (widgetName === modalWidgetName) {
|
||||||
|
return mockModalCmsContent[cmsFieldName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("promo-modal-question.vue", () => {
|
||||||
|
it("Should reset all alerts on onModalClosed", async () => {
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.onModalClosed();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.displayInvalidPromoAlert).toBe(false);
|
||||||
|
expect(wrapper.vm.displayStackingPromoAlert).toBe(false);
|
||||||
|
expect(wrapper.vm.displayInShopPromoAlert).toBe(false);
|
||||||
|
expect(wrapper.vm.displaySimilarPromoAlert).toBe(false);
|
||||||
|
});
|
||||||
|
it("Should emit update:modelValue on Modal closed", async () => {
|
||||||
|
// Arrange
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
|
||||||
|
await wrapper.vm.onModalClosed();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted("update:modelValue")).toEqual([[lineItems]]);
|
||||||
|
});
|
||||||
|
it("focuses on the input element when focusOnPromoInput is called", async () => {
|
||||||
|
// Arrange
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
const focusMock = jest.fn();
|
||||||
|
const inputMock = { focus: focusMock };
|
||||||
|
|
||||||
|
// Mock document.getElementById to return the inputMock
|
||||||
|
jest.spyOn(document, "getElementById").mockReturnValue(inputMock);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
|
||||||
|
wrapper.vm.focusOnPromoInput();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(focusMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it("returns a validate response on applying promo", async () => {
|
||||||
|
// Arrange
|
||||||
|
const newPromo = "testPromo";
|
||||||
|
const pageNameToLog = "testPage";
|
||||||
|
const validateResponse = { orderPromos: [] };
|
||||||
|
const addableVaps = [];
|
||||||
|
mockReturnsForStoreActions[storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA] =
|
||||||
|
validateResponse;
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
// Act
|
||||||
|
|
||||||
|
wrapper.vm.getPromoCodeData();
|
||||||
|
|
||||||
|
const promoValidationResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
|
||||||
|
{
|
||||||
|
newPromo,
|
||||||
|
lineItems,
|
||||||
|
addableVaps,
|
||||||
|
},
|
||||||
|
pageNameToLog,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(promoValidationResponse).toEqual(validateResponse);
|
||||||
|
});
|
||||||
|
test("If promocode is valid return taxed lineItems", async () => {
|
||||||
|
//Arrange
|
||||||
|
const taxedlineItems = {};
|
||||||
|
|
||||||
|
mockReturnsForStoreActions[storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA] =
|
||||||
|
taxedlineItems;
|
||||||
|
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
let promoCode = "1wiper0";
|
||||||
|
const pricedLineItemsToTax = [];
|
||||||
|
|
||||||
|
pricedLineItemsToTax.push(promoCode);
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
wrapper.vm.addPromoCode();
|
||||||
|
|
||||||
|
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
|
{
|
||||||
|
billToAccountNumber: "87291",
|
||||||
|
providerNumber: 2,
|
||||||
|
appointmentType: "IN_SHOP",
|
||||||
|
serviceLocationCity: "city",
|
||||||
|
serviceLocationState: "state",
|
||||||
|
serviceLocationZipCode: "12345",
|
||||||
|
pricedLineItems: pricedLineItemsToTax,
|
||||||
|
},
|
||||||
|
|
||||||
|
"payment-method",
|
||||||
|
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
expect(taxedLineItems).toEqual(taxedlineItems);
|
||||||
|
});
|
||||||
|
test("Get promoCode list will return the applied promos", async () => {
|
||||||
|
// Arrange
|
||||||
|
const promos = [
|
||||||
|
{ promoCode: "duplicatePromo" },
|
||||||
|
{ promoCode: "duplicatePromo" },
|
||||||
|
{ promoCode: "bundlePromo/400" },
|
||||||
|
{ promoCode: "bundlePromo/401" },
|
||||||
|
];
|
||||||
|
const appliedPromos = ["duplicatePromo", "bundlePromo"];
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.getPromoCodeList();
|
||||||
|
const promoCodeToDisplay = getPromoCodesFromPromoObjectsWithoutDuplicates(promos);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(promoCodeToDisplay).toEqual(appliedPromos);
|
||||||
|
});
|
||||||
|
test("On clicking remove the promo gets removed from the lineItems", async () => {
|
||||||
|
//Arrange
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [{ promoCode: "duplicatePromo" }, { promoCode: "duplicatePromo" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
const promo = ["bundlePromo"];
|
||||||
|
//Act
|
||||||
|
wrapper.vm.removeItem(promo);
|
||||||
|
const existingPromo = (lineItems[cartItemCategories.PROMOS] = lineItems[
|
||||||
|
cartItemCategories.PROMOS
|
||||||
|
].filter(
|
||||||
|
(lineItemsToKeep) =>
|
||||||
|
getPromoCodeWithoutBundleIdentifier(lineItemsToKeep.promoCode) != promo
|
||||||
|
));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(existingPromo).toEqual(lineItems.promos);
|
||||||
|
});
|
||||||
|
test("Getting stacking alert on stack promo error code", async () => {
|
||||||
|
// Arrange
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
const additionalInfo = ["testAdditionalInfo"];
|
||||||
|
|
||||||
|
const stackingErrorCode = promoErrorCodes.PROMO_STACKING_NOT_ALLOWED;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.getErrorMessage(stackingErrorCode, additionalInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.displayStackingPromoAlert).toBe(true);
|
||||||
|
});
|
||||||
|
test("Getting invalid promo alert on invalid promoCode", async () => {
|
||||||
|
// Arrange
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
const additionalInfo = ["testAdditionalInfo"];
|
||||||
|
|
||||||
|
const invalidErrorCode = promoErrorCodes.INVALID_PROMO_ON_ORDER;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.getErrorMessage(invalidErrorCode, additionalInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.displayInvalidPromoAlert).toBe(true);
|
||||||
|
});
|
||||||
|
test("Getting invalid promo alert on invalid promoCode", async () => {
|
||||||
|
// Arrange
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
const additionalInfo = ["APPOINTMENT_TYPE"];
|
||||||
|
|
||||||
|
const invalidErrorCode = promoErrorCodes.INVALID_PROMO_ON_ORDER;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.getErrorMessage(invalidErrorCode, additionalInfo);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.displayInShopPromoAlert).toBe(true);
|
||||||
|
});
|
||||||
|
test("Get conflicting promoCodes on applying more than one promo", async () => {
|
||||||
|
// Arrange
|
||||||
|
const lineItems = {
|
||||||
|
glassParts: [],
|
||||||
|
supportingItems: [],
|
||||||
|
vaps: [],
|
||||||
|
promos: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(promoModalQuestion, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: lineItems,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
const additionalInfo = ["1wiper0", "Glass30"];
|
||||||
|
const conflictingCodes = ["1wiper0", "Glass30"];
|
||||||
|
|
||||||
|
//Act
|
||||||
|
wrapper.vm.getConflictingPromoCode(additionalInfo);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(conflictingCodes).toEqual(additionalInfo);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import textLink from "@/ux-components/text-link/text-link";
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
import promoQuestion from "@/layouts/payment-method/promo-modal-question/promo-question/promo-question";
|
import promoQuestion from "@/fmg-components/promo-modal-question/promo-question/promo-question";
|
||||||
import modal from "@/digital-components/modal/modal";
|
import modal from "@/digital-components/modal/modal";
|
||||||
import alert from "@/ux-components/alert/alert";
|
import alert from "@/ux-components/alert/alert";
|
||||||
import { storeActions } from "@/constants/store-actions.js";
|
import { storeActions } from "@/constants/store-actions.js";
|
||||||
|
|
@ -99,7 +99,13 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
modelValue: Object,
|
modelValue: Object,
|
||||||
modalWidgetName: String,
|
modalWidgetName: String,
|
||||||
availableVaps: Object,
|
addableVaps: Object,
|
||||||
|
pageName: String,
|
||||||
|
taxPromos: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
promoLinkText() {
|
promoLinkText() {
|
||||||
|
|
@ -180,14 +186,14 @@ export default {
|
||||||
resetModalButtonStyle() {
|
resetModalButtonStyle() {
|
||||||
this.modal.resetButtonStyle();
|
this.modal.resetButtonStyle();
|
||||||
},
|
},
|
||||||
async getPromoCodeData(promoCode, lineItems, availableVaps, pageNameToLog = null) {
|
async getPromoCodeData(promoCode, lineItems, addableVaps, pageNameToLog = null) {
|
||||||
const pageName = pageNameToLog ?? this.$options?.name;
|
const pageName = pageNameToLog ?? this.$options?.name;
|
||||||
const promoValidationResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
const promoValidationResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
|
storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
|
||||||
{
|
{
|
||||||
promoCode: promoCode,
|
promoCode: promoCode,
|
||||||
lineItemsToUse: lineItems,
|
lineItemsToUse: lineItems,
|
||||||
addableVaps: availableVaps,
|
addableVaps: addableVaps,
|
||||||
},
|
},
|
||||||
pageName,
|
pageName,
|
||||||
false
|
false
|
||||||
|
|
@ -252,45 +258,52 @@ export default {
|
||||||
const promoCodeData = await this.getPromoCodeData(
|
const promoCodeData = await this.getPromoCodeData(
|
||||||
this.promoCode,
|
this.promoCode,
|
||||||
this.lineItems,
|
this.lineItems,
|
||||||
this.availableVaps,
|
this.addableVaps,
|
||||||
"payment-method"
|
this.pageName
|
||||||
);
|
);
|
||||||
|
|
||||||
if (promoCodeData.isValid) {
|
if (promoCodeData.isValid) {
|
||||||
const pricedLineItemsToTax = [];
|
if (this.taxPromos) {
|
||||||
pricedLineItemsToTax.push(...promoCodeData.promoCode);
|
const pricedLineItemsToTax = [];
|
||||||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
pricedLineItemsToTax.push(...promoCodeData.promoCode);
|
||||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
const taxedLineItems =
|
||||||
{
|
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
billToAccountNumber: "87291",
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
providerNumber:
|
{
|
||||||
store.getters.order.serviceLocation.provider.providerNumber,
|
billToAccountNumber: "87291",
|
||||||
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
providerNumber:
|
||||||
serviceLocationCity: store.getters.order.serviceLocation.city,
|
store.getters.order.serviceLocation.provider.providerNumber,
|
||||||
serviceLocationState: store.getters.order.serviceLocation.state,
|
appointmentType:
|
||||||
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
store.getters.order.serviceLocation.appointmentType,
|
||||||
pricedLineItems: pricedLineItemsToTax,
|
serviceLocationCity: store.getters.order.serviceLocation.city,
|
||||||
},
|
serviceLocationState: store.getters.order.serviceLocation.state,
|
||||||
"payment-method",
|
serviceLocationZipCode:
|
||||||
false
|
store.getters.order.serviceLocation.zipCode,
|
||||||
);
|
pricedLineItems: pricedLineItemsToTax,
|
||||||
|
},
|
||||||
|
"payment-method",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
// Match all line items to the line items as they are in the store
|
// Match all line items to the line items as they are in the store
|
||||||
// and rebuild the original structure.
|
// and rebuild the original structure.
|
||||||
this.lineItems = mapTaxedLineItemsToStoreFormat(taxedLineItems, this.lineItems);
|
this.lineItems = mapTaxedLineItemsToStoreFormat(
|
||||||
const taxedVaps = mapTaxedLineItemsToStoreFormat(
|
taxedLineItems,
|
||||||
taxedLineItems,
|
this.lineItems
|
||||||
this.availableVaps
|
);
|
||||||
);
|
const taxedVaps = mapTaxedLineItemsToStoreFormat(
|
||||||
|
taxedLineItems,
|
||||||
|
this.addableVaps
|
||||||
|
);
|
||||||
|
|
||||||
const getVaps = getVapsThatNeedToBeAddedToSatisfyPromos(
|
const getVaps = getVapsThatNeedToBeAddedToSatisfyPromos(
|
||||||
promoCodeData.promoCode,
|
promoCodeData.promoCode,
|
||||||
taxedVaps,
|
taxedVaps,
|
||||||
this.lineItems
|
this.lineItems
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.lineItems.vaps?.push(...getVaps);
|
||||||
|
}
|
||||||
this.lineItems?.promos.push(...promoCodeData.promoCode);
|
this.lineItems?.promos.push(...promoCodeData.promoCode);
|
||||||
this.lineItems.vaps?.push(...getVaps);
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
} else {
|
} else {
|
||||||
this.getErrorMessage(promoCodeData.errorCode, promoCodeData.additionalInfo);
|
this.getErrorMessage(promoCodeData.errorCode, promoCodeData.additionalInfo);
|
||||||
|
|
@ -1,11 +1,50 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import analyticsMixIn from "@/mixins/analytics-mixin.js";
|
import analyticsMixIn from "@/mixins/analytics-mixin.js";
|
||||||
import store from "@/store";
|
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
import { applicationConfig } from "@/constants/application-config.js";
|
import { applicationConfig } from "@/constants/application-config.js";
|
||||||
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
|
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
|
||||||
import { headerKeys } from "@/constants/header-keys";
|
import { headerKeys } from "@/constants/header-keys";
|
||||||
|
import axiosResponseInterceptorMessages from "@/constants/axios-response-interceptor-messages.js";
|
||||||
|
|
||||||
|
// Add a response interceptor for global axios error handing.
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
let rejectionError = "";
|
||||||
|
if (typeof error.response === "undefined") {
|
||||||
|
// The request was not made, could be a bad url, bad connection, or a CORS error.
|
||||||
|
rejectionError = {
|
||||||
|
response: error,
|
||||||
|
message: axiosResponseInterceptorMessages.NETWORK_ERROR,
|
||||||
|
};
|
||||||
|
} else if (error.response) {
|
||||||
|
// The request was made and the server responded with a status code
|
||||||
|
// that falls out of the range of 2xx
|
||||||
|
rejectionError = {
|
||||||
|
response: error.response,
|
||||||
|
message: axiosResponseInterceptorMessages.STATUS_CODE_ERROR,
|
||||||
|
};
|
||||||
|
} else if (error.request) {
|
||||||
|
// The request was made but no response was received
|
||||||
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
|
// http.ClientRequest in node.js
|
||||||
|
rejectionError = {
|
||||||
|
response: error.request,
|
||||||
|
message: axiosResponseInterceptorMessages.NO_RESPONSE_ERROR,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Something happened in setting up the request that triggered an Error
|
||||||
|
rejectionError = {
|
||||||
|
response: error.message,
|
||||||
|
message: axiosResponseInterceptorMessages.GENERIC_ERROR,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(rejectionError);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
callHttpClient({
|
callHttpClient({
|
||||||
|
|
@ -50,8 +89,6 @@ export default {
|
||||||
return resolve(response);
|
return resolve(response);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
if (logApiCall) {
|
if (logApiCall) {
|
||||||
analyticsMixIn.methods.pushEventToGA(
|
analyticsMixIn.methods.pushEventToGA(
|
||||||
GaCategories.API_RESPONSE,
|
GaCategories.API_RESPONSE,
|
||||||
|
|
@ -61,7 +98,16 @@ export default {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//router.navigateError();
|
// Do not route to error logic when no wipers found or no promo found (404s)
|
||||||
|
if (error.response.status != "404") {
|
||||||
|
router.navigateError();
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$logger.logError(
|
||||||
|
`${method}: ${endpoint}: ${error.message}`,
|
||||||
|
error.response
|
||||||
|
);
|
||||||
|
|
||||||
return reject(error.response);
|
return reject(error.response);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
import globalMethods from "@/global-methods";
|
import globalMethods from "@/global-methods";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import analyticsMixIn from "@/mixins/analytics-mixin";
|
import analyticsMixIn from "@/mixins/analytics-mixin";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
//Mock external dependencies
|
//Mock external dependencies
|
||||||
jest.mock("axios");
|
jest.mock("axios");
|
||||||
jest.mock("@/mixins/analytics-mixin");
|
jest.mock("@/mixins/analytics-mixin");
|
||||||
|
|
||||||
|
global.$logger = {
|
||||||
|
logInformation: jest.fn(),
|
||||||
|
logWarning: jest.fn(),
|
||||||
|
logError: jest.fn(),
|
||||||
|
logCritical: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const endpoint = "https://mock.safelite.com";
|
const endpoint = "https://mock.safelite.com";
|
||||||
|
|
@ -29,6 +37,7 @@ it("Global Methods - Call Http Client - Should Reject Promise", () => {
|
||||||
isError: true,
|
isError: true,
|
||||||
});
|
});
|
||||||
analyticsMixIn.methods.pushEventToGA = jest.fn();
|
analyticsMixIn.methods.pushEventToGA = jest.fn();
|
||||||
|
router.navigateError = jest.fn();
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
globalMethods.callHttpClient(httpArgs).catch((err) => {
|
globalMethods.callHttpClient(httpArgs).catch((err) => {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function updateOrCreateFunnelCookie() {
|
||||||
ReferralNumber: store.getters.order.referralNumber,
|
ReferralNumber: store.getters.order.referralNumber,
|
||||||
ReferralDate: store.getters.order.referralDate,
|
ReferralDate: store.getters.order.referralDate,
|
||||||
ReferralCorrelationId: store.getters.order.referralCorrelationId,
|
ReferralCorrelationId: store.getters.order.referralCorrelationId,
|
||||||
ReferralParentAccountNumber: store.getters.order.accountNumber,
|
ReferralParentAccountNumber: store.getters.order.payment.parentAccountNumber,
|
||||||
HasDelayedClaimRegistration: wasClaimRegistrationDelayed,
|
HasDelayedClaimRegistration: wasClaimRegistrationDelayed,
|
||||||
SuppressConceptFunnel: shouldSuppressConceptFunnel,
|
SuppressConceptFunnel: shouldSuppressConceptFunnel,
|
||||||
SavedSessionId: store.getters.applicationUser.savedSessionId,
|
SavedSessionId: store.getters.applicationUser.savedSessionId,
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,7 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}) {
|
||||||
|
|
||||||
// If navigating to a specific page, and that page is not part of the vin pages.
|
// If navigating to a specific page, and that page is not part of the vin pages.
|
||||||
// Return that page, so that it can navigate like normal.
|
// Return that page, so that it can navigate like normal.
|
||||||
if (
|
if (toRoute.query[queryStrings.FMG_PAGE] !== undefined && !isVinRelatedPage(toRoute)) {
|
||||||
toRoute.query[queryStrings.FMG_PAGE] !== undefined &&
|
|
||||||
toRoute.query[queryStrings.FMG_PAGE] !== fmgPageValues.SERVICE_LOCATION &&
|
|
||||||
toRoute.query[queryStrings.FMG_PAGE] !== fmgPageValues.SCHEDULE &&
|
|
||||||
!isVinRelatedPage(toRoute)
|
|
||||||
) {
|
|
||||||
return overrideYmmsDirectionIfNeeded(toRoute);
|
return overrideYmmsDirectionIfNeeded(toRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,6 +73,13 @@ export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLo
|
||||||
heritageParms["promo"] = promo;
|
heritageParms["promo"] = promo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.VUE_APP_HERITAGE_FUNNEL.includes("fixmyglassdev") &&
|
||||||
|
process.env.VUE_APP_CURRENT_ENVIRONMENT === "Localhost"
|
||||||
|
) {
|
||||||
|
heritageParms["forceEndToEndInsurance"] = true;
|
||||||
|
}
|
||||||
|
|
||||||
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, heritageParms);
|
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, heritageParms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,11 +141,7 @@ async function getLatestPageForRedirection() {
|
||||||
} else if (!estimateComponent.methods.arePagePrerequisitesValid()) {
|
} else if (!estimateComponent.methods.arePagePrerequisitesValid()) {
|
||||||
return fmgPageValues.VEHICLE_DAMAGE;
|
return fmgPageValues.VEHICLE_DAMAGE;
|
||||||
} else {
|
} else {
|
||||||
if (scheduleComponent.methods.arePagePrerequisitesValid()) {
|
if (quoteComponent.methods.arePagePrerequisitesValid()) {
|
||||||
return fmgPageValues.SCHEDULE;
|
|
||||||
} else if (serviceLocationComponent.methods.arePagePrerequisitesValid()) {
|
|
||||||
return fmgPageValues.SERVICE_LOCATION;
|
|
||||||
} else if (quoteComponent.methods.arePagePrerequisitesValid()) {
|
|
||||||
return fmgPageValues.QUOTE;
|
return fmgPageValues.QUOTE;
|
||||||
} else if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) {
|
} else if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) {
|
||||||
return fmgPageValues.CAPABILITY_QUESTIONS;
|
return fmgPageValues.CAPABILITY_QUESTIONS;
|
||||||
|
|
|
||||||
91
src/helpers/logger.js
Normal file
91
src/helpers/logger.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { applicationConfig } from "@/constants/application-config.js";
|
||||||
|
import axios from "axios";
|
||||||
|
import store from "@/store";
|
||||||
|
import loggingEndpointMethods from "@/constants/logging-endpoint-methods";
|
||||||
|
|
||||||
|
import { headerKeys } from "@/constants/header-keys";
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
logInformation(message, details) {
|
||||||
|
this.writeLogEntry(
|
||||||
|
loggingEndpointMethods.LOG_INFORMATION,
|
||||||
|
this.formatLogEntry(message, details)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logWarning(message, details) {
|
||||||
|
this.writeLogEntry(
|
||||||
|
loggingEndpointMethods.LOG_WARNING,
|
||||||
|
this.formatLogEntry(message, details)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logError(message, details) {
|
||||||
|
this.writeLogEntry(loggingEndpointMethods.LOG_ERROR, this.formatLogEntry(message, details));
|
||||||
|
}
|
||||||
|
|
||||||
|
logCritical(message, details) {
|
||||||
|
this.writeLogEntry(
|
||||||
|
loggingEndpointMethods.LOG_CRITICAL,
|
||||||
|
this.formatLogEntry(message, details)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatLogEntry(message, details) {
|
||||||
|
return `Application: ${applicationConfig.APPLICATION_NAME}\n${message}\n${
|
||||||
|
details ? JSON.stringify(details, undefined, 2) : ""
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLogEntry(endpoint, logEntry) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// If running locally or in the Dev environment show the log entries in the console.
|
||||||
|
if (
|
||||||
|
applicationConfig.CURRENT_ENVIRONMENT === "Localhost" ||
|
||||||
|
applicationConfig.CURRENT_ENVIRONMENT === "Dev" ||
|
||||||
|
applicationConfig.CURRENT_ENVIRONMENT === "SysTest"
|
||||||
|
) {
|
||||||
|
switch (endpoint) {
|
||||||
|
case loggingEndpointMethods.LOG_INFORMATION:
|
||||||
|
console.info(logEntry);
|
||||||
|
break;
|
||||||
|
case loggingEndpointMethods.LOG_WARNING:
|
||||||
|
console.warn(logEntry);
|
||||||
|
break;
|
||||||
|
case loggingEndpointMethods.LOG_ERROR:
|
||||||
|
console.error(logEntry);
|
||||||
|
break;
|
||||||
|
case loggingEndpointMethods.LOG_CRITICAL:
|
||||||
|
console.error(logEntry);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(logEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url =
|
||||||
|
applicationConfig.CONSUMER_CF_DISTRO + applicationConfig.FRONTEND_LOGGER_PATH;
|
||||||
|
const headers = {
|
||||||
|
[headerKeys.EXPERIMENT]: JSON.stringify(store.getters.experimentSettings),
|
||||||
|
};
|
||||||
|
|
||||||
|
axios({
|
||||||
|
method: "POST",
|
||||||
|
url: `${url}/${endpoint}`,
|
||||||
|
data: { entry: logEntry },
|
||||||
|
crossDomain: true,
|
||||||
|
responseType: {},
|
||||||
|
headers: headers,
|
||||||
|
}).then(
|
||||||
|
(response) => {
|
||||||
|
return resolve(response);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Logger;
|
||||||
|
|
@ -331,10 +331,10 @@ export function getNewlyInactivatedPromos(oldInactivePromos, newInactivePromos)
|
||||||
|
|
||||||
export function createPromoSuccessAlert(promoCode) {
|
export function createPromoSuccessAlert(promoCode) {
|
||||||
return {
|
return {
|
||||||
messageHeadline: "Promo applied!",
|
messageHeadline: "Promo code applied!",
|
||||||
messageCopy: `Promo "${getPromoCodeWithoutBundleIdentifier(
|
messageCopy: `Promo code ${getPromoCodeWithoutBundleIdentifier(
|
||||||
promoCode.toUpperCase()
|
promoCode.toUpperCase()
|
||||||
)}" was successfully applied to your cart.`,
|
)} was successfully applied to your cart.`,
|
||||||
type: "alert-success",
|
type: "alert-success",
|
||||||
isDismissible: true,
|
isDismissible: true,
|
||||||
shouldAutoFade: true,
|
shouldAutoFade: true,
|
||||||
|
|
@ -343,19 +343,19 @@ export function createPromoSuccessAlert(promoCode) {
|
||||||
|
|
||||||
export function createPromoErrorAlert(promoCode, errorCode = null, additionalInfo) {
|
export function createPromoErrorAlert(promoCode, errorCode = null, additionalInfo) {
|
||||||
const alert = {
|
const alert = {
|
||||||
messageHeadline: "Promo error",
|
messageHeadline: "Promo code error",
|
||||||
type: "alert-danger",
|
type: "alert-danger",
|
||||||
shouldAutoFade: false,
|
shouldAutoFade: false,
|
||||||
isDismissible: true,
|
isDismissible: true,
|
||||||
};
|
};
|
||||||
if (stackingPromoErrorCodes.includes(errorCode)) {
|
if (stackingPromoErrorCodes.includes(errorCode)) {
|
||||||
alert.messageCopy = `Sorry, promo code "${getPromoCodeWithoutBundleIdentifier(
|
alert.messageCopy = `Sorry, promo code ${getPromoCodeWithoutBundleIdentifier(
|
||||||
promoCode.toUpperCase()
|
promoCode.toUpperCase()
|
||||||
)}" cannot be combined with "${additionalInfo[0].toUpperCase()}"`;
|
)} cannot be combined with ${additionalInfo[0].toUpperCase()}`;
|
||||||
} else {
|
} else {
|
||||||
alert.messageCopy = `Sorry, promo code "${getPromoCodeWithoutBundleIdentifier(
|
alert.messageCopy = `Sorry, promo code ${getPromoCodeWithoutBundleIdentifier(
|
||||||
promoCode.toUpperCase()
|
promoCode.toUpperCase()
|
||||||
)}" is not valid`;
|
)} is not valid`;
|
||||||
}
|
}
|
||||||
return alert;
|
return alert;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { storeMutations } from "@/constants/store-mutations";
|
||||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
|
||||||
jest.mock("@/helpers/damage-helper", () => ({
|
jest.mock("@/helpers/damage-helper", () => ({
|
||||||
isGlassAvailableForCarId: jest.fn().mockImplementation(() => true),
|
isGlassAvailableForCarId: jest.fn().mockImplementation(() => true),
|
||||||
|
|
@ -641,6 +642,17 @@ describe("address-lookup.vue", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getSettingValue: jest.fn((settingName) => {
|
||||||
|
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
return "false";
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function setupMocks({
|
function setupMocks({
|
||||||
isZipValid = true,
|
isZipValid = true,
|
||||||
isZipServiceable = true,
|
isZipServiceable = true,
|
||||||
|
|
@ -709,6 +721,7 @@ function setupMocks({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mixins: [mockMixin],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,11 @@
|
||||||
|
|
||||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
||||||
|
|
||||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
<customerQuestions
|
||||||
|
ref="customerQuestions"
|
||||||
|
v-model="customerQuestions"
|
||||||
|
:validationRules="EmailValidationRules"
|
||||||
|
:isEmailOptional="IsEmailOptional" />
|
||||||
|
|
||||||
<alert
|
<alert
|
||||||
ref="alertVinNotFound"
|
ref="alertVinNotFound"
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@
|
||||||
v-model="customerModel.emailAddress"
|
v-model="customerModel.emailAddress"
|
||||||
ref="emailAddress"
|
ref="emailAddress"
|
||||||
customInputId="emailAddress"
|
customInputId="emailAddress"
|
||||||
validationRules="email-address-required|email-address-format" />
|
:isRequired="!isEmailOptional"
|
||||||
|
:validationRules="validationRules"
|
||||||
|
:addOptionalText="isEmailOptional" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-0">
|
<div class="row mb-0">
|
||||||
|
|
@ -79,6 +81,7 @@ export default {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
|
isEmailOptional: Boolean,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
customerModel: {
|
customerModel: {
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,6 @@ export default {
|
||||||
if (
|
if (
|
||||||
store.getters.order.vehicle.carId &&
|
store.getters.order.vehicle.carId &&
|
||||||
store.getters.order.serviceLocation.zipCode &&
|
store.getters.order.serviceLocation.zipCode &&
|
||||||
store.getters.order.customer.emailAddress &&
|
|
||||||
store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES)
|
store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
48
src/layouts/bailout/bailout.spec.js
Normal file
48
src/layouts/bailout/bailout.spec.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Components
|
||||||
|
import bailout from "@/layouts/bailout/bailout.vue";
|
||||||
|
|
||||||
|
// Supporting Files
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
|
||||||
|
// Mock our module for promises.
|
||||||
|
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||||
|
settleAllPromises: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock fetchCmsContentForPage
|
||||||
|
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||||
|
fetchCmsContentForPage: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("bailout.vue", () => {
|
||||||
|
test("arePagePrerequisitesValid should be true ", async () => {
|
||||||
|
//Arrange
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
|
//Act
|
||||||
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(arePagePrerequisitesValid).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks() {
|
||||||
|
const mountOptions = getMountOptions({});
|
||||||
|
|
||||||
|
//Mock props
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mountOptions.mixins = [mockMixin];
|
||||||
|
const wrapper = shallowMount(bailout, mountOptions);
|
||||||
|
|
||||||
|
wrapper.vm.setCmsContent = jest.fn();
|
||||||
|
wrapper.vm.backButtonAction = jest.fn();
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
79
src/layouts/bailout/bailout.vue
Normal file
79
src/layouts/bailout/bailout.vue
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<Form>
|
||||||
|
<loadingModal ref="loadingModal" />
|
||||||
|
<div class="container-fluid page-container-grouped-styles">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 col-xl-4">
|
||||||
|
<vehicleBanner
|
||||||
|
cmsWidgetName="VehicleBannerWidget"
|
||||||
|
:displayGenericVehicleImage="false" />
|
||||||
|
|
||||||
|
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||||
|
|
||||||
|
<navbar
|
||||||
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
isForwardActionDisabled="true"
|
||||||
|
isSubmitHidden="true"
|
||||||
|
@back-clicked="backButtonAction" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||||
|
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||||
|
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||||
|
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||||
|
|
||||||
|
// Supporting files
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "bailout",
|
||||||
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
// Call APIs
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
|
||||||
|
// Settle promises and get results
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: "cmsContent",
|
||||||
|
promise: cmsContentPromise,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
||||||
|
// Call the "next" function to complete the transition to this page.
|
||||||
|
next((vm) => {
|
||||||
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
arePagePrerequisitesValid() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
backButtonAction() {
|
||||||
|
this.$router.go(-1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
funnelHeader,
|
||||||
|
navbar,
|
||||||
|
vehicleBanner,
|
||||||
|
funnelSubHeader,
|
||||||
|
loadingModal,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -135,7 +135,6 @@ export default {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
vm.lineItems = lineItemsFromSubmittedOrder;
|
vm.lineItems = lineItemsFromSubmittedOrder;
|
||||||
vm.vaps = availableVaps;
|
vm.vaps = availableVaps;
|
||||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import baseMixin from "../../mixins/base-mixin";
|
import baseMixin from "../../mixins/base-mixin";
|
||||||
import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js";
|
import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js";
|
||||||
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
|
||||||
// Mock our module for promises.
|
// Mock our module for promises.
|
||||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||||
|
|
@ -273,12 +274,24 @@ function setupMocks({
|
||||||
Answers: cmsAnswers,
|
Answers: cmsAnswers,
|
||||||
FunnelFooterWidget: FunnelFooterWidget,
|
FunnelFooterWidget: FunnelFooterWidget,
|
||||||
};
|
};
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getSettingValue: jest.fn((settingName) => {
|
||||||
|
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
return "false";
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
const apiPromise = Promise.resolve({ cmsContent });
|
const apiPromise = Promise.resolve({ cmsContent });
|
||||||
settleAllPromises.mockImplementation(() => apiPromise);
|
settleAllPromises.mockImplementation(() => apiPromise);
|
||||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||||
|
|
||||||
const mountOptions = getMountOptions({ ...mountOptionsMockData, mixins: [baseMixin] });
|
const mountOptions = getMountOptions({
|
||||||
|
...mountOptionsMockData,
|
||||||
|
mixins: [baseMixin, mockMixin],
|
||||||
|
});
|
||||||
mountOptions["attachTo"] = document.body;
|
mountOptions["attachTo"] = document.body;
|
||||||
|
|
||||||
const wrapper = shallowMount(estimate, mountOptions);
|
const wrapper = shallowMount(estimate, mountOptions);
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,10 @@
|
||||||
cmsWidgetName="EmailAddressQuestionWidget"
|
cmsWidgetName="EmailAddressQuestionWidget"
|
||||||
v-model="emailAddress"
|
v-model="emailAddress"
|
||||||
inputId="emailAddress"
|
inputId="emailAddress"
|
||||||
isRequired
|
:isRequired="!IsEmailOptional"
|
||||||
disableAutoFill
|
disableAutoFill
|
||||||
validationRules="email-address-required|email-address-format" />
|
:validationRules="EmailValidationRules"
|
||||||
|
:addOptionalText="IsEmailOptional" />
|
||||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||||
|
|
||||||
<alert
|
<alert
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import { Form, defineRule } from "vee-validate";
|
import { Form, defineRule } from "vee-validate";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import vinPagesMixin from "@/mixins/vin-pages-mixin";
|
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
|
||||||
// MAPPNG
|
// MAPPNG
|
||||||
|
|
@ -66,7 +66,6 @@ const termMapping = [
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "insurance-company",
|
name: "insurance-company",
|
||||||
mixins: [vinPagesMixin],
|
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
// Call APIs
|
// Call APIs
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
|
@ -138,8 +137,33 @@ export default {
|
||||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
},
|
},
|
||||||
forwardButtonAction() {
|
forwardButtonAction() {
|
||||||
// Still needs wired up when insurance-details page is created
|
this.dispatchStoreAction(
|
||||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER,
|
||||||
|
this.selectedParentAccount,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
navigateToHeritageFunnel({
|
||||||
|
shouldSaveSession: true,
|
||||||
|
pageNameToLog: "insurance-company",
|
||||||
|
loadingModal: this.$refs.loadingModal,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectParentAccountNumber(parentAccountName) {
|
||||||
|
if (!this.insuranceCompanyList) {
|
||||||
|
console.error("No insurance companies found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedItem = this.insuranceCompanyList.filter((item) => {
|
||||||
|
return item.accountName === parentAccountName;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedItem) {
|
||||||
|
this.selectedParentAccount = selectedItem[0].parentAccountNumber;
|
||||||
|
} else {
|
||||||
|
console.error("Error locating " + parentAccountName);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { nextTick } from "vue";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import { storeMutations } from "@/constants/store-mutations";
|
import { storeMutations } from "@/constants/store-mutations";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
|
||||||
jest.mock("@/assets/img/loader.gif", () => "loader.gif");
|
jest.mock("@/assets/img/loader.gif", () => "loader.gif");
|
||||||
jest.mock("@/assets/img/windshield.png", () => "windshield.png");
|
jest.mock("@/assets/img/windshield.png", () => "windshield.png");
|
||||||
|
|
@ -692,11 +693,20 @@ function setupMocks({
|
||||||
};
|
};
|
||||||
|
|
||||||
const apiPromise = Promise.resolve(apiResponses);
|
const apiPromise = Promise.resolve(apiResponses);
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getSettingValue: jest.fn((settingName) => {
|
||||||
|
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
return "false";
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
settleAllPromises.mockImplementation(() => apiPromise);
|
settleAllPromises.mockImplementation(() => apiPromise);
|
||||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||||
|
|
||||||
const mountOptions = getMountOptions(mountOptionsMockData);
|
const mountOptions = getMountOptions({ ...mountOptionsMockData, mixins: [mockMixin] });
|
||||||
mountOptions["attachTo"] = document.body; // append wrapper to document.body to test DOM methods
|
mountOptions["attachTo"] = document.body; // append wrapper to document.body to test DOM methods
|
||||||
|
|
||||||
const wrapper = shallowMount(licensePlateLookup, mountOptions);
|
const wrapper = shallowMount(licensePlateLookup, mountOptions);
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@
|
||||||
cmsWidgetName="EmailAddressQuestionWidget"
|
cmsWidgetName="EmailAddressQuestionWidget"
|
||||||
v-model="email"
|
v-model="email"
|
||||||
customInputId="email"
|
customInputId="email"
|
||||||
validationRules="email-address-required|email-address-format" />
|
:isRequired="!IsEmailOptional"
|
||||||
|
:validationRules="EmailValidationRules"
|
||||||
|
:addOptionalText="IsEmailOptional" />
|
||||||
|
|
||||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,6 @@
|
||||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||||
recyclingModalCmsWidgetName="RecycleModal" />
|
recyclingModalCmsWidgetName="RecycleModal" />
|
||||||
|
|
||||||
<promoModalQuestion
|
|
||||||
class="small mt-4"
|
|
||||||
v-model="lineItems"
|
|
||||||
:availableVaps="availableVaps"
|
|
||||||
modalWidgetName="PromoModalWidget" />
|
|
||||||
|
|
||||||
<hr class="my-5" />
|
<hr class="my-5" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -82,7 +76,6 @@ import paymentMethodQuestion from "@/layouts/payment-method/payment-method-quest
|
||||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||||
import cart from "@/fmg-components/cart/cart";
|
import cart from "@/fmg-components/cart/cart";
|
||||||
import reviewDropdown from "@/layouts/payment-method/review-dropdown/review-dropdown";
|
import reviewDropdown from "@/layouts/payment-method/review-dropdown/review-dropdown";
|
||||||
import promoModalQuestion from "@/layouts/payment-method/promo-modal-question/promo-modal-question";
|
|
||||||
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
||||||
import alert from "@/ux-components/alert/alert";
|
import alert from "@/ux-components/alert/alert";
|
||||||
|
|
||||||
|
|
@ -244,7 +237,7 @@ export default {
|
||||||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
{
|
{
|
||||||
billToAccountNumber: "87291",
|
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
||||||
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
||||||
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
||||||
serviceLocationCity: store.getters.order.serviceLocation.city,
|
serviceLocationCity: store.getters.order.serviceLocation.city,
|
||||||
|
|
@ -314,9 +307,6 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
// Line Items
|
|
||||||
const packageReqs = !!store.getters.order.lineItems.supportingItems;
|
|
||||||
|
|
||||||
// Service Location
|
// Service Location
|
||||||
const serviceLocation = store.getters.order.serviceLocation;
|
const serviceLocation = store.getters.order.serviceLocation;
|
||||||
const mobileReqs = !!(
|
const mobileReqs = !!(
|
||||||
|
|
@ -361,9 +351,7 @@ export default {
|
||||||
customer.emailAddress
|
customer.emailAddress
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs;
|
||||||
packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getPaymentMethodFromStore() {
|
getPaymentMethodFromStore() {
|
||||||
const piaType = store.getters.order.payment.piaType;
|
const piaType = store.getters.order.payment.piaType;
|
||||||
|
|
@ -405,7 +393,7 @@ export default {
|
||||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
{
|
{
|
||||||
billToAccountNumber: "87291",
|
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
||||||
providerNumber:
|
providerNumber:
|
||||||
this.$store.getters.order.serviceLocation.provider.providerNumber,
|
this.$store.getters.order.serviceLocation.provider.providerNumber,
|
||||||
appointmentType: this.$store.getters.order.serviceLocation.appointmentType,
|
appointmentType: this.$store.getters.order.serviceLocation.appointmentType,
|
||||||
|
|
@ -524,8 +512,12 @@ export default {
|
||||||
},
|
},
|
||||||
isPiaDisabled() {
|
isPiaDisabled() {
|
||||||
const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE);
|
const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE);
|
||||||
|
const piaInsurance = this.getSettingValue(experimentSettings.PIA_INSURANCE);
|
||||||
|
|
||||||
const isEnabled = piaExperience === "PIA Optional" || piaExperience === "PIA Required";
|
const isEnabled =
|
||||||
|
piaExperience === "PIA Optional" ||
|
||||||
|
piaExperience === "PIA Required" ||
|
||||||
|
piaInsurance === "true";
|
||||||
|
|
||||||
return !isEnabled;
|
return !isEnabled;
|
||||||
},
|
},
|
||||||
|
|
@ -596,7 +588,6 @@ export default {
|
||||||
cart,
|
cart,
|
||||||
alert,
|
alert,
|
||||||
paymentMethodQuestion,
|
paymentMethodQuestion,
|
||||||
promoModalQuestion,
|
|
||||||
reviewDropdown,
|
reviewDropdown,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
import { mount, shallowMount } from "@vue/test-utils";
|
|
||||||
import promoModalQuestion from "./promo-modal-question";
|
|
||||||
|
|
||||||
jest.mock("@/digital-components/textbox-question/textbox-question", () => ({
|
|
||||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
|
||||||
return widgetName[cmsFieldName];
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("@/digital-components/modal/modal", () => ({
|
|
||||||
methods: {
|
|
||||||
closeModal: jest.fn(),
|
|
||||||
resetButtonStyle: jest.fn(),
|
|
||||||
resetForm: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const modalWidgetName = "modalWidgetName";
|
|
||||||
|
|
||||||
const mockModalCmsContent = {
|
|
||||||
FooterText: "Sample modal footer text here.",
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockMixin = {
|
|
||||||
methods: {
|
|
||||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
|
||||||
if (widgetName === modalWidgetName) {
|
|
||||||
return mockModalCmsContent[cmsFieldName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("promo-modal-question.vue", () => {
|
|
||||||
it("Should reset all alerts on onModalClosed", async () => {
|
|
||||||
// Arrange
|
|
||||||
|
|
||||||
const wrapper = mount(promoModalQuestion, {
|
|
||||||
mixins: [mockMixin],
|
|
||||||
props: {
|
|
||||||
modalWidgetName: modalWidgetName,
|
|
||||||
},
|
|
||||||
attachTo: document.body,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
wrapper.vm.onModalClosed();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.displayInvalidPromoAlert).toBe(false);
|
|
||||||
expect(wrapper.vm.displayStackingPromoAlert).toBe(false);
|
|
||||||
expect(wrapper.vm.displayInShopPromoAlert).toBe(false);
|
|
||||||
expect(wrapper.vm.displaySimilarPromoAlert).toBe(false);
|
|
||||||
});
|
|
||||||
it("Should emit update:modelValue on Modal closed", async () => {
|
|
||||||
// Arrange
|
|
||||||
const lineItems = {
|
|
||||||
glassParts: [],
|
|
||||||
supportingItems: [],
|
|
||||||
vaps: [],
|
|
||||||
promos: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mount(promoModalQuestion, {
|
|
||||||
mixins: [mockMixin],
|
|
||||||
props: {
|
|
||||||
modelValue: lineItems,
|
|
||||||
modalWidgetName: modalWidgetName,
|
|
||||||
},
|
|
||||||
attachTo: document.body,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
|
|
||||||
await wrapper.vm.onModalClosed();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.emitted("update:modelValue")).toEqual([[lineItems]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -512,7 +512,7 @@ describe("payment.vue", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("handleIFrameContentWindwMessage", () => {
|
describe("handleIFrameContentWindowMessage", () => {
|
||||||
test("Navigates back if afterpay is closed", () => {
|
test("Navigates back if afterpay is closed", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const event = {
|
const event = {
|
||||||
|
|
@ -524,7 +524,7 @@ describe("payment.vue", () => {
|
||||||
wrapper.vm.backButtonAction = jest.fn();
|
wrapper.vm.backButtonAction = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.handleIFrameContentWindwMessage(event);
|
wrapper.vm.handleIFrameContentWindowMessage(event);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.backButtonAction).toBeCalled();
|
expect(wrapper.vm.backButtonAction).toBeCalled();
|
||||||
|
|
@ -539,7 +539,7 @@ describe("payment.vue", () => {
|
||||||
const wrapper = setupMocks({});
|
const wrapper = setupMocks({});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.handleIFrameContentWindwMessage(event);
|
wrapper.vm.handleIFrameContentWindowMessage(event);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.shouldBlockInteraction).toBe(true);
|
expect(wrapper.vm.shouldBlockInteraction).toBe(true);
|
||||||
|
|
|
||||||
|
|
@ -356,7 +356,7 @@ export default {
|
||||||
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
{
|
{
|
||||||
billToAccountNumber: "87291",
|
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
||||||
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
||||||
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
||||||
serviceLocationCity: store.getters.order.serviceLocation.city,
|
serviceLocationCity: store.getters.order.serviceLocation.city,
|
||||||
|
|
@ -440,9 +440,6 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
// Line Items
|
|
||||||
const packageReqs = !!store.getters.order.lineItems.supportingItems;
|
|
||||||
|
|
||||||
// Service Location
|
// Service Location
|
||||||
const serviceLocation = store.getters.order.serviceLocation;
|
const serviceLocation = store.getters.order.serviceLocation;
|
||||||
const mobileReqs = !!(
|
const mobileReqs = !!(
|
||||||
|
|
@ -492,7 +489,6 @@ export default {
|
||||||
(store.getters.order.payment.isPia || !!store.getters.order.payment.piaType);
|
(store.getters.order.payment.isPia || !!store.getters.order.payment.piaType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
packageReqs &&
|
|
||||||
serviceLocationReqs &&
|
serviceLocationReqs &&
|
||||||
isInsuranceSet &&
|
isInsuranceSet &&
|
||||||
scheduleReqs &&
|
scheduleReqs &&
|
||||||
|
|
@ -647,17 +643,19 @@ export default {
|
||||||
|
|
||||||
this.submitHopForm();
|
this.submitHopForm();
|
||||||
},
|
},
|
||||||
handleIFrameContentWindwMessage(event) {
|
handleIFrameContentWindowMessage(event) {
|
||||||
if (event.data.indexOf("afterpayClosed") > -1) {
|
if (typeof event.data === "string") {
|
||||||
this.backButtonAction();
|
if (event.data.indexOf("afterpayClosed") > -1) {
|
||||||
}
|
this.backButtonAction();
|
||||||
if (event.data.indexOf("creditCardSubmit") > -1) {
|
}
|
||||||
this.setUIBlock(true);
|
if (event.data.indexOf("creditCardSubmit") > -1) {
|
||||||
|
this.setUIBlock(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setIFrameListener() {
|
setIFrameListener() {
|
||||||
window.addEventListener("message", (event) =>
|
window.addEventListener("message", (event) =>
|
||||||
this.handleIFrameContentWindwMessage(event)
|
this.handleIFrameContentWindowMessage(event)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
setUIBlock(val) {
|
setUIBlock(val) {
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,6 @@ describe("quote.vue", () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
wrapper.vm.pricedGlassParts = [];
|
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
await wrapper.vm.forwardButtonAction();
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
|
@ -250,8 +248,8 @@ describe("quote.vue", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.pricedGlassParts !== null).toBe(true);
|
expect(wrapper.vm.lineItems !== null).toBe(true);
|
||||||
expect(wrapper.vm.supportingItems !== null).toBe(true);
|
expect(wrapper.vm.availableVaps !== null).toBe(true);
|
||||||
expect(wrapper.vm.availableLineItems !== null).toBe(true);
|
expect(wrapper.vm.availableLineItems !== null).toBe(true);
|
||||||
// This should have its own test
|
// This should have its own test
|
||||||
//expect(vm.isInsuranceSelected !== null).toBe(true);
|
//expect(vm.isInsuranceSelected !== null).toBe(true);
|
||||||
|
|
@ -510,6 +508,31 @@ describe("quote.vue", () => {
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.isInsuranceSelected).toBe(true);
|
expect(wrapper.vm.isInsuranceSelected).toBe(true);
|
||||||
});
|
});
|
||||||
|
test("On forward button action save promos", async () => {
|
||||||
|
//Arrange
|
||||||
|
store.getters.payment = {
|
||||||
|
insuranceCoverage: {},
|
||||||
|
isInsurance: false,
|
||||||
|
};
|
||||||
|
store.getters.order = {
|
||||||
|
lineItems: [],
|
||||||
|
payment: {
|
||||||
|
parentAccountNumber: 167132,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
customMountOptions: {
|
||||||
|
router: {
|
||||||
|
navigateWithSaving: jest.fn(),
|
||||||
|
},
|
||||||
|
route: { quote },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
expect(wrapper.vm.lineItems.promos !== null).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupMocks({ customMountOptions }) {
|
function setupMocks({ customMountOptions }) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
:availableLineItems="availableLineItems"
|
:availableLineItems="availableLineItems"
|
||||||
:isInsuranceSelected="isInsuranceSelected"
|
:isInsuranceSelected="isInsuranceSelected"
|
||||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||||
:activePromos="allActivePromos"
|
:activePromos="lineItems.promos"
|
||||||
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
||||||
validationRules="option-required"
|
validationRules="option-required"
|
||||||
isRequired />
|
isRequired />
|
||||||
|
|
@ -39,6 +39,14 @@
|
||||||
cmsWidgetName="AfterpayModalWidget"
|
cmsWidgetName="AfterpayModalWidget"
|
||||||
:lineItems="availableLineItems" />
|
:lineItems="availableLineItems" />
|
||||||
|
|
||||||
|
<promoModalQuestion
|
||||||
|
class="small mt-4"
|
||||||
|
v-model="lineItems"
|
||||||
|
:addableVaps="addableVaps"
|
||||||
|
pageName="quote"
|
||||||
|
:taxPromos="false"
|
||||||
|
modalWidgetName="PromoModalWidget" />
|
||||||
|
|
||||||
<textBlock
|
<textBlock
|
||||||
cmsWidgetName="quoteDisclaimer"
|
cmsWidgetName="quoteDisclaimer"
|
||||||
justifyText="left"
|
justifyText="left"
|
||||||
|
|
@ -78,20 +86,22 @@ import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
|
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import { deepClone } from "@/helpers/object-helper";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { required } from "@/helpers/validation-rules";
|
import { required } from "@/helpers/validation-rules";
|
||||||
import { errorMessages } from "@/constants/error-messages";
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
import { Form, defineRule } from "vee-validate";
|
import { Form, defineRule } from "vee-validate";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
|
||||||
import { applicationConfig } from "@/constants/application-config";
|
import { applicationConfig } from "@/constants/application-config";
|
||||||
import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states";
|
import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states";
|
||||||
import {
|
import {
|
||||||
revalidatePromosAndValidateQueryStringPromo,
|
revalidatePromosAndValidateQueryStringPromo,
|
||||||
buildToastMessagesFromRevalidateOrValidatePromoResponse,
|
buildToastMessagesFromRevalidateOrValidatePromoResponse,
|
||||||
|
createPromoSuccessAlert,
|
||||||
} from "@/helpers/promotions-helper";
|
} from "@/helpers/promotions-helper";
|
||||||
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 promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
||||||
|
|
||||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
|
|
@ -139,7 +149,9 @@ export default {
|
||||||
];
|
];
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
const nullSafeGlassParts = store.getters.order.lineItems.glassParts ?? [];
|
const lineItems = deepClone(store.getters.order.lineItems);
|
||||||
|
lineItems.vaps = lineItems.vaps ?? [];
|
||||||
|
const nullSafeGlassParts = lineItems.glassParts ?? [];
|
||||||
const availableLineItems = [
|
const availableLineItems = [
|
||||||
resultMap.rainDefense,
|
resultMap.rainDefense,
|
||||||
...resultMap.supportingItems,
|
...resultMap.supportingItems,
|
||||||
|
|
@ -161,6 +173,8 @@ export default {
|
||||||
resultMap.supportingItems,
|
resultMap.supportingItems,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
lineItems.supportingItems = resultMap.supportingItems;
|
||||||
|
const addableVaps = [...resultMap.wipers, resultMap.rainDefense];
|
||||||
|
|
||||||
// Promo logic
|
// Promo logic
|
||||||
// Populate the previous state of promos for toast message usage in "next()"
|
// Populate the previous state of promos for toast message usage in "next()"
|
||||||
|
|
@ -168,20 +182,21 @@ export default {
|
||||||
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
|
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
|
||||||
|
|
||||||
const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
|
const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
|
||||||
|
|
||||||
const { validatePromoResponse, revalidatePromoResponse } =
|
const { validatePromoResponse, revalidatePromoResponse } =
|
||||||
await revalidatePromosAndValidateQueryStringPromo(
|
await revalidatePromosAndValidateQueryStringPromo(
|
||||||
promoCodeFromQueryString,
|
promoCodeFromQueryString,
|
||||||
pricingResults,
|
pricingResults,
|
||||||
"quote"
|
"quote"
|
||||||
);
|
);
|
||||||
|
// Sync local promos with any new promos added by validate/revalidate
|
||||||
|
lineItems.promos = store.getters.lineItems.promos ?? [];
|
||||||
// End of promo logic
|
// End of promo logic
|
||||||
|
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
vm.pricedGlassParts = nullSafeGlassParts;
|
vm.addableVaps = addableVaps;
|
||||||
vm.supportingItems = resultMap.supportingItems;
|
vm.lineItems = lineItems;
|
||||||
vm.availableLineItems = pricingResults;
|
vm.availableLineItems = pricingResults;
|
||||||
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
|
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
|
||||||
|
|
||||||
|
|
@ -208,15 +223,14 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isInsuranceSelected: null,
|
isInsuranceSelected: null,
|
||||||
selectedVaps: null,
|
|
||||||
availableLineItems: null,
|
availableLineItems: null,
|
||||||
supportingItems: null,
|
lineItems: [],
|
||||||
pricedGlassParts: null,
|
addableVaps: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
allActivePromos() {
|
lineItemsCloneForWatcher() {
|
||||||
return this.$store.getters.order.lineItems.promos ?? [];
|
return Object.assign({}, this.lineItems);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -258,7 +272,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
vapsItemsSelectedAction(vapsItemsSelected) {
|
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||||
this.selectedVaps = vapsItemsSelected;
|
this.lineItems.vaps = vapsItemsSelected;
|
||||||
},
|
},
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
vehicleQuestionsMixin.methods.navigateBack(this);
|
vehicleQuestionsMixin.methods.navigateBack(this);
|
||||||
|
|
@ -276,24 +290,36 @@ export default {
|
||||||
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
this.dispatchStoreAction(
|
||||||
|
this.storeActions.SAVE_BILL_TO_ACCOUNT_NUMBER,
|
||||||
|
applicationConfig.CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER,
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.$store.getters.order.payment.parentAccountNumber !=
|
this.$store.getters.order.payment.parentAccountNumber !=
|
||||||
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER
|
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER
|
||||||
) {
|
) {
|
||||||
this.supportingItems = this.filterOutFees(this.supportingItems);
|
this.lineItems.supportingItems = this.filterOutFees(this.lineItems.supportingItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pricedGlassParts.length > 0) {
|
if (this.lineItems.glassParts?.length > 0) {
|
||||||
this.dispatchStoreAction(
|
this.dispatchStoreAction(
|
||||||
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
|
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
|
||||||
this.pricedGlassParts,
|
this.lineItems.glassParts,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
|
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.lineItems.vaps, false);
|
||||||
|
this.dispatchStoreAction(
|
||||||
|
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
|
||||||
|
{
|
||||||
|
activePromos: this.lineItems.promos,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const payment = this.$store.getters.payment;
|
const payment = this.$store.getters.payment;
|
||||||
if (payment.isInsurance) {
|
if (payment.isInsurance) {
|
||||||
|
|
@ -309,6 +335,32 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
lineItemsCloneForWatcher: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
if (
|
||||||
|
!oldValue ||
|
||||||
|
oldValue.length == 0 ||
|
||||||
|
!oldValue.vaps ||
|
||||||
|
!newValue ||
|
||||||
|
newValue.length == 0
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (oldValue.promos.length < newValue.promos.length) {
|
||||||
|
const oldPromoCodes = oldValue.promos.map(
|
||||||
|
(promoObject) => promoObject.promoCode
|
||||||
|
);
|
||||||
|
const newlyActivatedPromoCodes = newValue.promos.filter(
|
||||||
|
(newPromo) => !oldPromoCodes.includes(newPromo.promoCode)
|
||||||
|
);
|
||||||
|
const alert = createPromoSuccessAlert(newlyActivatedPromoCodes[0].promoCode);
|
||||||
|
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
navbar,
|
navbar,
|
||||||
|
|
@ -321,6 +373,7 @@ export default {
|
||||||
contentGroupModal,
|
contentGroupModal,
|
||||||
loadingModal,
|
loadingModal,
|
||||||
afterpayModalBanner,
|
afterpayModalBanner,
|
||||||
|
promoModalQuestion,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,57 @@ describe("service-package-question.vue", () => {
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.selectedPackageName).toBe("TierThree");
|
expect(wrapper.vm.selectedPackageName).toBe("TierThree");
|
||||||
});
|
});
|
||||||
|
it("should select default package if promos are added", async () => {
|
||||||
|
//Arrange
|
||||||
|
mockProps.activePromos != null;
|
||||||
|
const wrapper = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
store: {
|
||||||
|
getters: {
|
||||||
|
order: {
|
||||||
|
damage: {
|
||||||
|
isRepair: false,
|
||||||
|
glassToReplace: [{ glassLocation: "Windshield" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lineItems: {
|
||||||
|
vaps: [],
|
||||||
|
},
|
||||||
|
hasAnyNonWindshieldGlassParts: false,
|
||||||
|
payment: {
|
||||||
|
isInsurance: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//Act
|
||||||
|
wrapper.setProps({
|
||||||
|
activePromos: [
|
||||||
|
{
|
||||||
|
discountedLineItemIds: [
|
||||||
|
{
|
||||||
|
0: "428ec73c-38e4-4e14-8703-a987b0391898",
|
||||||
|
1: "79db94d7-163c-4408-a1ec-f83e58c11992",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
partType: "PROMO_DISCOUNT",
|
||||||
|
promoCode: "1WIPER0",
|
||||||
|
partNumber: "WIPER DISCOUNT",
|
||||||
|
laborAmount: 0,
|
||||||
|
sellingPrice: -10,
|
||||||
|
kitPrice: 0,
|
||||||
|
salesTax: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const selectDefaultPackageMock = jest.spyOn(wrapper.vm, "selectDefaultPackage");
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(selectDefaultPackageMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe("service-package-question.vue, matching business rules for package display", () => {
|
describe("service-package-question.vue, matching business rules for package display", () => {
|
||||||
// mock scenarios in figma:
|
// mock scenarios in figma:
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ export default {
|
||||||
this.selectDefaultPackage();
|
this.selectDefaultPackage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
activePromos() {
|
||||||
|
if (this.activePromos?.length) this.selectDefaultPackage();
|
||||||
|
},
|
||||||
selectedPackageName(newValue) {
|
selectedPackageName(newValue) {
|
||||||
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
|
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
|
||||||
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
|
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
|
||||||
|
|
|
||||||
|
|
@ -314,14 +314,13 @@ export default {
|
||||||
serviceLocation.provider.providerNumber);
|
serviceLocation.provider.providerNumber);
|
||||||
|
|
||||||
const paymentInfo = store.getters.payment.isInsurance !== null;
|
const paymentInfo = store.getters.payment.isInsurance !== null;
|
||||||
const supportingItems = store.getters.lineItems.supportingItems !== null;
|
|
||||||
|
|
||||||
const damageInfo =
|
const damageInfo =
|
||||||
store.getters.order.damage.isRepair ||
|
store.getters.order.damage.isRepair ||
|
||||||
(store.getters.order.lineItems?.glassParts != null &&
|
(store.getters.order.lineItems?.glassParts != null &&
|
||||||
store.getters.order.lineItems.glassParts.length > 0);
|
store.getters.order.lineItems.glassParts.length > 0);
|
||||||
|
|
||||||
return serviceLocationPreReqs && paymentInfo && supportingItems && damageInfo;
|
return serviceLocationPreReqs && paymentInfo && damageInfo;
|
||||||
},
|
},
|
||||||
async getAvailableDatesMethod(startDate, endDate) {
|
async getAvailableDatesMethod(startDate, endDate) {
|
||||||
const newShopTimeSlots = await getAvailableDates(
|
const newShopTimeSlots = await getAvailableDates(
|
||||||
|
|
@ -348,9 +347,14 @@ export default {
|
||||||
},
|
},
|
||||||
getSelectedTimeSlotInfo() {
|
getSelectedTimeSlotInfo() {
|
||||||
const supportingItems = this.getSupportingItems();
|
const supportingItems = this.getSupportingItems();
|
||||||
const isPremiumAppointment =
|
|
||||||
!!supportingItems.filter((lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE)
|
var isPremiumAppointment = false;
|
||||||
.length > 0;
|
if (supportingItems) {
|
||||||
|
isPremiumAppointment =
|
||||||
|
!!supportingItems.filter(
|
||||||
|
(lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE
|
||||||
|
).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
const selectedTimeSlotInfo = {
|
const selectedTimeSlotInfo = {
|
||||||
timeSlot: store.getters.order.schedule,
|
timeSlot: store.getters.order.schedule,
|
||||||
|
|
@ -461,6 +465,10 @@ export default {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
if (!supportingItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// if it's not a mobile and/or premium early bird, then make sure we remove any that may have been added
|
// if it's not a mobile and/or premium early bird, then make sure we remove any that may have been added
|
||||||
const removePremiumFeeIndex = supportingItems.findIndex(
|
const removePremiumFeeIndex = supportingItems.findIndex(
|
||||||
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,14 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
mobileFeePart.data === null ||
|
||||||
|
mobileFeePart.data === undefined ||
|
||||||
|
mobileFeePart.data === ""
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the Mobile Fee Part Price
|
// Get the Mobile Fee Part Price
|
||||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ import {
|
||||||
getServiceabilityDetails,
|
getServiceabilityDetails,
|
||||||
getShopProviderData,
|
getShopProviderData,
|
||||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||||
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
|
||||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||||
|
|
||||||
|
|
@ -348,11 +349,19 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
return (
|
// insurance drops the recycle fee on replace orders so it won't be in supportingItems
|
||||||
store.getters.lineItems.supportingItems !== null &&
|
if (store.getters.payment.isInsurance && !store.getters.order.damage.isRepair) {
|
||||||
store.getters.order.serviceLocation.zipCode !== null &&
|
return (
|
||||||
store.getters.payment.isInsurance !== null
|
store.getters.order.serviceLocation.zipCode !== null &&
|
||||||
);
|
store.getters.payment.isInsurance !== null
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
store.getters.lineItems.supportingItems !== null &&
|
||||||
|
store.getters.order.serviceLocation.zipCode !== null &&
|
||||||
|
store.getters.payment.isInsurance !== null
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setData(zipCodeData, serviceabilityDetails, mobileFeePart, shopProviderData) {
|
setData(zipCodeData, serviceabilityDetails, mobileFeePart, shopProviderData) {
|
||||||
if (zipCodeData) {
|
if (zipCodeData) {
|
||||||
|
|
@ -438,7 +447,14 @@ export default {
|
||||||
this.recalibrationInformationModal.openModal();
|
this.recalibrationInformationModal.openModal();
|
||||||
},
|
},
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
if (store.getters.order.payment.isInsurance) {
|
||||||
|
navigateToHeritageFunnel({ shouldSaveSession: false });
|
||||||
|
} else {
|
||||||
|
this.$router.navigateWithoutSaving(
|
||||||
|
this.navigationScenarios.CLICKED_BACK,
|
||||||
|
this.$route
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateAndSaveSupportingItems() {
|
updateAndSaveSupportingItems() {
|
||||||
const supportingItems = store.getters.lineItems.supportingItems;
|
const supportingItems = store.getters.lineItems.supportingItems;
|
||||||
|
|
@ -453,7 +469,7 @@ export default {
|
||||||
supportingItems[mobileFeeIndex].sellingPrice = this.mobileFeePart.sellingPrice;
|
supportingItems[mobileFeeIndex].sellingPrice = this.mobileFeePart.sellingPrice;
|
||||||
supportingItems[mobileFeeIndex].kitPrice = this.mobileFeePart.kitPrice;
|
supportingItems[mobileFeeIndex].kitPrice = this.mobileFeePart.kitPrice;
|
||||||
} else {
|
} else {
|
||||||
supportingItems.push(this.mobileFeePart);
|
if (this.mobileFeePart !== null) supportingItems.push(this.mobileFeePart);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchStoreAction(
|
this.dispatchStoreAction(
|
||||||
|
|
@ -463,7 +479,7 @@ export default {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// if it's not a mobile, then make sure we remove any that may have been added
|
// if it's not a mobile, then make sure we remove any that may have been added
|
||||||
const removeMobileFeeIndex = supportingItems.findIndex(
|
const removeMobileFeeIndex = supportingItems?.findIndex(
|
||||||
(item) => item.partType == MOBILE_FEE_PART_TYPE
|
(item) => item.partType == MOBILE_FEE_PART_TYPE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,19 +95,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
tintSelectionOptions() {
|
tintSelectionOptions() {
|
||||||
let tintOptions = [];
|
const tintOptions = Object.keys(this.featureListData).map((tintOption) => {
|
||||||
|
const buttonImage = this.getTintSourceImage(this.glassLocation, tintOption);
|
||||||
Object.keys(this.featureListData).forEach((tintOption) => {
|
return {
|
||||||
tintOptions.push({
|
|
||||||
value: tintOption,
|
value: tintOption,
|
||||||
buttonLabel: tintOption,
|
buttonLabel: tintOption,
|
||||||
buttonImage: require(`@/assets/img/tints/${this.getTintSourceImage(
|
buttonImage: buttonImage ? require(`@/assets/img/tints/${buttonImage}`) : null,
|
||||||
this.glassLocation,
|
};
|
||||||
tintOption
|
|
||||||
)}`),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return tintOptions;
|
return tintOptions;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import vinLookup from "./vin-lookup.vue";
|
||||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||||
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
|
||||||
|
|
@ -378,5 +379,11 @@ function mockOutStubFunctions(wrapper) {
|
||||||
const mockMixin = {
|
const mockMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
getCmsContent: jest.fn(() => "placeholder CMS content"),
|
getCmsContent: jest.fn(() => "placeholder CMS content"),
|
||||||
|
getSettingValue: jest.fn((settingName) => {
|
||||||
|
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
return "false";
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,9 @@
|
||||||
cmsWidgetName="EmailAddressQuestionWidget"
|
cmsWidgetName="EmailAddressQuestionWidget"
|
||||||
v-model="emailAddress"
|
v-model="emailAddress"
|
||||||
customInputId="emailAddress"
|
customInputId="emailAddress"
|
||||||
isRequired
|
:isRequired="!IsEmailOptional"
|
||||||
validationRules="email-address-required|email-address-format" />
|
:validationRules="EmailValidationRules"
|
||||||
|
:addOptionalText="IsEmailOptional" />
|
||||||
|
|
||||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||||
|
|
||||||
|
|
|
||||||
11
src/main.js
11
src/main.js
|
|
@ -13,6 +13,10 @@ import "../node_modules/jquery-ui/dist/jquery-ui.min.js";
|
||||||
// Make jQuery avaialble globally
|
// Make jQuery avaialble globally
|
||||||
window.$ = window.jQuery = require("jquery");
|
window.$ = window.jQuery = require("jquery");
|
||||||
|
|
||||||
|
import Logger from "@/helpers/logger";
|
||||||
|
// Instantiate global logging object
|
||||||
|
global.$logger = new Logger();
|
||||||
|
|
||||||
// Vue App Setup
|
// Vue App Setup
|
||||||
const vueApp = createApp(App);
|
const vueApp = createApp(App);
|
||||||
|
|
||||||
|
|
@ -24,4 +28,11 @@ vueApp.mixin(baseMixin);
|
||||||
vueApp.mixin(analyticsMixin);
|
vueApp.mixin(analyticsMixin);
|
||||||
vueApp.mixin(experimentMixin);
|
vueApp.mixin(experimentMixin);
|
||||||
|
|
||||||
|
// Vue Error Handling
|
||||||
|
vueApp.config.errorHandler = (err, vm, info) => {
|
||||||
|
global.$logger.logError(
|
||||||
|
`Page Name - ${vm.getPageName()} - ${info}: ${err.message}\n${err.stack}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
vueApp.mount("#app");
|
vueApp.mount("#app");
|
||||||
|
|
|
||||||
|
|
@ -120,77 +120,182 @@ export default {
|
||||||
await this.logPageView(analyticsPageEvents.ENTRY);
|
await this.logPageView(analyticsPageEvents.ENTRY);
|
||||||
},
|
},
|
||||||
|
|
||||||
pushSubmittedOrderToDataLayer() {
|
pushOrderToDataLayer() {
|
||||||
// check if submitted order exists; exit if not.
|
// helper check for if an object is defined (but maybe falsey)
|
||||||
const hasSubmittedOrder = store.getters.hasSubmittedOrder;
|
const isDefined = (x) => x !== null && x !== undefined;
|
||||||
|
|
||||||
if (!hasSubmittedOrder) {
|
// Get correct order object
|
||||||
return;
|
const hasSubmittedOrder = store.getters.hasSubmittedOrder;
|
||||||
|
const order = hasSubmittedOrder ? store.getters.submittedOrder : store.getters.order;
|
||||||
|
|
||||||
|
// Begin assembling payload for data layer
|
||||||
|
const payload = {};
|
||||||
|
|
||||||
|
// Service Zip
|
||||||
|
if (
|
||||||
|
order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||||
|
isDefined(order.serviceLocation.zipCode)
|
||||||
|
) {
|
||||||
|
payload.serviceZipCode = order.serviceLocation.zipCode;
|
||||||
|
} else if (
|
||||||
|
isDefined(order.serviceLocation.appointmentType) &&
|
||||||
|
order.serviceLocation.appointmentType !== AppointmentTypeStrings.MOBILE &&
|
||||||
|
isDefined(order.serviceLocation.provider.address.zipCode)
|
||||||
|
) {
|
||||||
|
payload.serviceZipCode = order.serviceLocation.provider.address.zipCode;
|
||||||
|
} else {
|
||||||
|
payload.serviceZipCode = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const order = store.getters.submittedOrder;
|
// Damage Type
|
||||||
|
if (isDefined(order.damage.isRepair)) {
|
||||||
|
payload.damageType = order.damage.isRepair ? "repair" : "replace";
|
||||||
|
} else {
|
||||||
|
payload.damageType = "";
|
||||||
|
}
|
||||||
|
|
||||||
// assemble data for payload
|
// Account Type
|
||||||
// // reduce promocode array
|
if (isDefined(order.payment.isInsurance)) {
|
||||||
|
payload.accountType = order.payment.isInsurance ? "insurance" : "cash";
|
||||||
|
} else {
|
||||||
|
payload.accountType = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promo Codes
|
||||||
const promos = order.lineItems.promos ?? [];
|
const promos = order.lineItems.promos ?? [];
|
||||||
const promoCodes = promos.map((promo) => promo.promoCode);
|
if (promos.length === 0) {
|
||||||
const promoString =
|
payload.promoCodes = "";
|
||||||
promoCodes.length === 0 ? "" : promoCodes.reduce((prev, next) => `${prev},${next}`);
|
} else {
|
||||||
|
const promoCodes = promos.map((promo) => promo.promoCode);
|
||||||
|
const promoString = promoCodes.reduce((prev, next) => `${prev},${next}`);
|
||||||
|
payload.promoCodes = promoString;
|
||||||
|
}
|
||||||
|
|
||||||
// // reduce glass array
|
// Vehicle info
|
||||||
const glassToReplace = order.damage.glassToReplace ?? [];
|
if (isDefined(order.vehicle.year)) {
|
||||||
const glassToReplaceNames = glassToReplace.map(
|
// Ensure cast to string.
|
||||||
(glassPiece) => `${glassPiece.glassLocation}/${glassPiece.glassName}`
|
payload.vehicleYear = `${order.vehicle.year}`;
|
||||||
);
|
} else {
|
||||||
const glassString =
|
payload.vehicleYear = "";
|
||||||
glassToReplaceNames.length === 0
|
}
|
||||||
? ""
|
|
||||||
: glassToReplaceNames.reduce((prev, next) => `${prev},${next}`);
|
|
||||||
|
|
||||||
// // calculate subtotal
|
if (isDefined(order.vehicle.make)) {
|
||||||
const lineItems = order.lineItems;
|
payload.vehicleMake = order.vehicle.make;
|
||||||
|
} else {
|
||||||
|
payload.vehicleMake = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(order.vehicle.model)) {
|
||||||
|
payload.vehicleModel = order.vehicle.model;
|
||||||
|
} else {
|
||||||
|
payload.vehicleModel = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(order.vehicle.style)) {
|
||||||
|
payload.vehicleStyle = order.vehicle.style;
|
||||||
|
} else {
|
||||||
|
payload.vehicleStyle = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glass pieces
|
||||||
|
const glass = order.damage.glassToReplace ?? [];
|
||||||
|
if (glass.length === 0) {
|
||||||
|
payload.glassToReplace = "";
|
||||||
|
} else {
|
||||||
|
const glassNames = glass.map((g) => `${g.glassLocation}/${g.glassName}`);
|
||||||
|
const glassString = glassNames.reduce((prev, next) => `${prev},${next}`);
|
||||||
|
|
||||||
|
payload.glassToReplace = glassString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work Order Id
|
||||||
|
if (order.workOrderId) {
|
||||||
|
const parsedId = parseInt(order.workOrderId);
|
||||||
|
if (!isNaN(parsedId)) {
|
||||||
|
payload.workOrderId = parsedId;
|
||||||
|
} else {
|
||||||
|
payload.workOrderId = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload.workOrderId = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider Ctu
|
||||||
|
if (isDefined(order.serviceLocation.zipCodeCtu)) {
|
||||||
|
const parsedCtu = parseInt(order.serviceLocation.zipCodeCtu);
|
||||||
|
if (!isNaN(parsedCtu)) {
|
||||||
|
payload.providerCtu = parseInt(order.serviceLocation.zipCodeCtu);
|
||||||
|
} else {
|
||||||
|
payload.providerCtu = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload.providerCtu = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work Order Number
|
||||||
|
if (order.workOrderNumber) {
|
||||||
|
payload.orderNumber = order.workOrderNumber;
|
||||||
|
} else {
|
||||||
|
payload.orderNumber = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pricing
|
||||||
|
// Only fire for completed orders?
|
||||||
|
|
||||||
|
const lineItems = order.lineItems ?? {};
|
||||||
const combinedLineItems = [
|
const combinedLineItems = [
|
||||||
...(lineItems.glassParts ?? []),
|
...(lineItems.glassParts ?? []),
|
||||||
...(lineItems.supportingItems ?? []),
|
...(lineItems.supportingItems ?? []),
|
||||||
...(lineItems.vaps ?? []),
|
...(lineItems.vaps ?? []),
|
||||||
...(lineItems.promos ?? []),
|
...(lineItems.promos ?? []),
|
||||||
];
|
];
|
||||||
const subtotal = baseMixin.methods
|
|
||||||
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
|
|
||||||
.toFixed(2);
|
|
||||||
|
|
||||||
// // get correct zip code
|
const isPricingAvailable =
|
||||||
const providerZip = order.serviceLocation.provider.address.zipCode;
|
combinedLineItems.length > 0 &&
|
||||||
const serviceZip =
|
combinedLineItems.every(
|
||||||
order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|
(lineItem) =>
|
||||||
? order.serviceLocation.zipCode
|
isDefined(lineItem.kitPrice) &&
|
||||||
: providerZip;
|
isDefined(lineItem.laborAmount) &&
|
||||||
|
isDefined(lineItem.sellingPrice)
|
||||||
|
);
|
||||||
|
const isTaxAvailable =
|
||||||
|
isPricingAvailable &&
|
||||||
|
combinedLineItems.every((lineItem) => isDefined(lineItem.salesTax));
|
||||||
|
|
||||||
// // calculate total
|
if (isPricingAvailable) {
|
||||||
const total = baseMixin.methods
|
const subtotal = baseMixin.methods
|
||||||
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, true)
|
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
|
||||||
.toFixed(2);
|
.toFixed(2);
|
||||||
|
|
||||||
const payload = {
|
payload.priceSubTotal = parseFloat(subtotal);
|
||||||
serviceZipCode: serviceZip,
|
} else {
|
||||||
damageType: order.damage.isRepair ? "repair" : "replace",
|
payload.priceSubTotal = "";
|
||||||
accountType: order.payment.isInsurance ? "insurance" : "cash",
|
}
|
||||||
promoCodes: promoString,
|
|
||||||
vehicleYear: `${order.vehicle.year}`,
|
if (isTaxAvailable) {
|
||||||
vehicleMake: order.vehicle.make,
|
const total = baseMixin.methods
|
||||||
vehicleModel: order.vehicle.model,
|
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, true)
|
||||||
vehicleStyle: order.vehicle.style,
|
.toFixed(2);
|
||||||
glassToReplace: glassString,
|
|
||||||
workOrderId: parseInt(order.workOrderId),
|
payload.priceTotal = parseFloat(total);
|
||||||
providerCtu: parseInt(order.serviceLocation.zipCodeCtu),
|
} else {
|
||||||
orderNumber: order.workOrderNumber,
|
payload.priceTotal = "";
|
||||||
priceTotal: parseFloat(total),
|
}
|
||||||
priceSubTotal: parseFloat(subtotal),
|
|
||||||
isRecalibrationOnOrder: store.getters.isRecalibrationOnSubmittedOrder,
|
// Recalibration
|
||||||
appointmentType: order.serviceLocation.appointmentType,
|
if (hasSubmittedOrder) {
|
||||||
};
|
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedOrder;
|
||||||
|
} else {
|
||||||
|
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appointment Type
|
||||||
|
if (isDefined(order.serviceLocation.appointmentType)) {
|
||||||
|
payload.appointmentType = order.serviceLocation.appointmentType;
|
||||||
|
} else {
|
||||||
|
payload.appointmentType = "";
|
||||||
|
}
|
||||||
|
|
||||||
// push to data layer.
|
|
||||||
pushToDataLayerIfDefined(payload);
|
pushToDataLayerIfDefined(payload);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,27 +38,38 @@ const parts = {
|
||||||
requiresRecalibration: true,
|
requiresRecalibration: true,
|
||||||
salesTax: 63.86,
|
salesTax: 63.86,
|
||||||
sellingPrice: 791.46,
|
sellingPrice: 791.46,
|
||||||
|
kitPrice: 0,
|
||||||
|
laborAmount: 0,
|
||||||
},
|
},
|
||||||
frontWipers: {
|
frontWipers: {
|
||||||
name: "front wipers",
|
name: "front wipers",
|
||||||
partNumber: "SBB16",
|
partNumber: "SBB16",
|
||||||
description: "SAFELITE BEAM BLADE 16",
|
description: "SAFELITE BEAM BLADE 16",
|
||||||
partType: "FRONT WIPER",
|
partType: "FRONT WIPER",
|
||||||
price: 32.64,
|
sellingPrice: 32.64,
|
||||||
|
kitPrice: 0,
|
||||||
|
laborAmount: 0,
|
||||||
|
salesTax: 1,
|
||||||
},
|
},
|
||||||
rearWipers: {
|
rearWipers: {
|
||||||
name: "rear wipers",
|
name: "rear wipers",
|
||||||
partNumber: "SBBR12A",
|
partNumber: "SBBR12A",
|
||||||
description: "SAFELITE REAR BLADE 12A",
|
description: "SAFELITE REAR BLADE 12A",
|
||||||
partType: "REAR WIPER",
|
partType: "REAR WIPER",
|
||||||
price: 24.48,
|
sellingPrice: 24.48,
|
||||||
|
kitPrice: 0,
|
||||||
|
laborAmount: 0,
|
||||||
|
salesTax: 2,
|
||||||
},
|
},
|
||||||
rainDefense: {
|
rainDefense: {
|
||||||
name: "rain defense",
|
name: "rain defense",
|
||||||
partNumber: "RAIN DEFENSE",
|
partNumber: "RAIN DEFENSE",
|
||||||
description: null,
|
description: null,
|
||||||
partType: "RAIN DEFENSE",
|
partType: "RAIN DEFENSE",
|
||||||
price: 35.5,
|
sellingPrice: 35.5,
|
||||||
|
kitPrice: 0,
|
||||||
|
laborAmount: 0,
|
||||||
|
salesTax: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -324,10 +335,10 @@ describe("analyticsMixin.js", () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("pushSubmittedOrderToDataLayer", () => {
|
describe("pushOrderToDataLayer", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.getters.hasSubmittedOrder = true;
|
store.getters.hasSubmittedOrder = false;
|
||||||
store.getters.submittedOrder = {
|
store.getters.order = {
|
||||||
vehicle: {
|
vehicle: {
|
||||||
year: "2020",
|
year: "2020",
|
||||||
make: "acura",
|
make: "acura",
|
||||||
|
|
@ -342,8 +353,8 @@ describe("analyticsMixin.js", () => {
|
||||||
address2: "add2",
|
address2: "add2",
|
||||||
city: "city",
|
city: "city",
|
||||||
state: "state",
|
state: "state",
|
||||||
zipCode: "zip",
|
zipCode: "11111",
|
||||||
zipCodeCtu: "zipCtu",
|
zipCodeCtu: "11110",
|
||||||
appointmentType: "IN_SHOP",
|
appointmentType: "IN_SHOP",
|
||||||
isVehicleProtected: true,
|
isVehicleProtected: true,
|
||||||
provider: {
|
provider: {
|
||||||
|
|
@ -352,8 +363,8 @@ describe("analyticsMixin.js", () => {
|
||||||
streetAddress: "add3",
|
streetAddress: "add3",
|
||||||
city: "city2",
|
city: "city2",
|
||||||
state: "state2",
|
state: "state2",
|
||||||
zipCode: "zip2",
|
zipCode: "22222",
|
||||||
zipCodeCtu: "zipCtu2",
|
zipCodeCtu: "22220",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
techNotes: "",
|
techNotes: "",
|
||||||
|
|
@ -368,13 +379,28 @@ describe("analyticsMixin.js", () => {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: false,
|
isRepair: false,
|
||||||
numberOfChips: null,
|
numberOfChips: null,
|
||||||
glassToReplace: [{ glassName: "single", location: "windshield" }],
|
glassToReplace: [{ glassName: "single", glassLocation: "windshield" }],
|
||||||
},
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: [parts.windshield],
|
glassParts: [parts.windshield],
|
||||||
supportingItems: [],
|
supportingItems: [],
|
||||||
vaps: [parts.frontWipers],
|
vaps: [parts.frontWipers],
|
||||||
promos: [{ promoCode: "promoTEST" }, { promoCode: "promoTEST2" }],
|
promos: [
|
||||||
|
{
|
||||||
|
promoCode: "promoTEST",
|
||||||
|
kitPrice: 0,
|
||||||
|
sellingPrice: 0,
|
||||||
|
laborAmount: 0,
|
||||||
|
salesTax: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
promoCode: "promoTEST2",
|
||||||
|
kitPrice: 0,
|
||||||
|
sellingPrice: 0,
|
||||||
|
laborAmount: 0,
|
||||||
|
salesTax: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
payment: {
|
payment: {
|
||||||
isInsurance: false,
|
isInsurance: false,
|
||||||
|
|
@ -397,6 +423,8 @@ describe("analyticsMixin.js", () => {
|
||||||
workOrderNumber: "01820-111111",
|
workOrderNumber: "01820-111111",
|
||||||
workOrderId: "222222222222",
|
workOrderId: "222222222222",
|
||||||
};
|
};
|
||||||
|
store.getters.isRecalibrationOnOrder = true;
|
||||||
|
store.getters.isRecalibrationOnSubmittedOrder = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Pushes to data layer if nominal", () => {
|
test("Pushes to data layer if nominal", () => {
|
||||||
|
|
@ -408,28 +436,203 @@ describe("analyticsMixin.js", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
analyticsMixin.methods.pushOrderToDataLayer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(mockDataLayerFn).toHaveBeenCalled();
|
expect(mockDataLayerFn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Does not push to data layer if no submitted order available.", () => {
|
test("Pushes populated data to data layer", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
store.getters.hasSubmittedOrder = false;
|
window.dataLayer = [];
|
||||||
store.getters.submittedOrder = undefined;
|
|
||||||
|
|
||||||
const mockDataLayerFn = jest.fn();
|
// Act
|
||||||
|
analyticsMixin.methods.pushOrderToDataLayer();
|
||||||
|
|
||||||
window.dataLayer = {
|
const result = window.dataLayer[0];
|
||||||
push: mockDataLayerFn,
|
|
||||||
|
// Assert
|
||||||
|
console.log(result);
|
||||||
|
const allFieldsPopulated = Object.keys(result).every(
|
||||||
|
(key) => result[key] === false || !!result[key]
|
||||||
|
);
|
||||||
|
expect(allFieldsPopulated).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Pushes correct data when submitted order is present", () => {
|
||||||
|
// Arrange
|
||||||
|
window.dataLayer = [];
|
||||||
|
store.getters.hasSubmittedOrder = true;
|
||||||
|
store.getters.submittedOrder = store.getters.order;
|
||||||
|
store.getters.order = {
|
||||||
|
vehicle: {
|
||||||
|
year: null,
|
||||||
|
make: null,
|
||||||
|
model: null,
|
||||||
|
style: null,
|
||||||
|
carId: null,
|
||||||
|
category: null,
|
||||||
|
vin: null,
|
||||||
|
},
|
||||||
|
serviceLocation: {
|
||||||
|
address: null,
|
||||||
|
address2: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zipCode: null,
|
||||||
|
zipCodeCtu: null,
|
||||||
|
appointmentType: null,
|
||||||
|
isVehicleProtected: null,
|
||||||
|
provider: {
|
||||||
|
providerNumber: null,
|
||||||
|
address: {
|
||||||
|
streetAddress: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zipCode: null,
|
||||||
|
zipCodeCtu: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
techNotes: null,
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
firstName: null,
|
||||||
|
lastName: null,
|
||||||
|
emailAddress: null,
|
||||||
|
phoneNumber: null,
|
||||||
|
isSmsOptIn: null,
|
||||||
|
},
|
||||||
|
damage: {
|
||||||
|
isRepair: null,
|
||||||
|
numberOfChips: null,
|
||||||
|
glassToReplace: null,
|
||||||
|
},
|
||||||
|
lineItems: {
|
||||||
|
glassParts: null,
|
||||||
|
supportingItems: null,
|
||||||
|
vaps: null,
|
||||||
|
promos: null,
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
isInsurance: null,
|
||||||
|
insuranceCoverage: {
|
||||||
|
isVerified: null,
|
||||||
|
coverageStatus: null,
|
||||||
|
coverageVerificationType: null,
|
||||||
|
},
|
||||||
|
isPia: null,
|
||||||
|
piaType: null,
|
||||||
|
inactivePromos: null,
|
||||||
|
},
|
||||||
|
schedule: {
|
||||||
|
date: null,
|
||||||
|
startTime: null,
|
||||||
|
endTime: null,
|
||||||
|
jobMinMinutes: null,
|
||||||
|
jobMaxMinutes: null,
|
||||||
|
},
|
||||||
|
workOrderNumber: null,
|
||||||
|
workOrderId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
analyticsMixin.methods.pushOrderToDataLayer();
|
||||||
|
|
||||||
|
const result = window.dataLayer[0];
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(mockDataLayerFn).not.toHaveBeenCalled();
|
console.log(result);
|
||||||
|
const allFieldsPopulated = Object.keys(result).every(
|
||||||
|
(key) => result[key] === false || !!result[key]
|
||||||
|
);
|
||||||
|
expect(allFieldsPopulated).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Pushes default values when data is missing", () => {
|
||||||
|
// Arrange
|
||||||
|
window.dataLayer = [];
|
||||||
|
store.getters.order = {
|
||||||
|
vehicle: {
|
||||||
|
year: null,
|
||||||
|
make: null,
|
||||||
|
model: null,
|
||||||
|
style: null,
|
||||||
|
carId: null,
|
||||||
|
category: null,
|
||||||
|
vin: null,
|
||||||
|
},
|
||||||
|
serviceLocation: {
|
||||||
|
address: null,
|
||||||
|
address2: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zipCode: null,
|
||||||
|
zipCodeCtu: null,
|
||||||
|
appointmentType: null,
|
||||||
|
isVehicleProtected: null,
|
||||||
|
provider: {
|
||||||
|
providerNumber: null,
|
||||||
|
address: {
|
||||||
|
streetAddress: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zipCode: null,
|
||||||
|
zipCodeCtu: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
techNotes: null,
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
firstName: null,
|
||||||
|
lastName: null,
|
||||||
|
emailAddress: null,
|
||||||
|
phoneNumber: null,
|
||||||
|
isSmsOptIn: null,
|
||||||
|
},
|
||||||
|
damage: {
|
||||||
|
isRepair: null,
|
||||||
|
numberOfChips: null,
|
||||||
|
glassToReplace: null,
|
||||||
|
},
|
||||||
|
lineItems: {
|
||||||
|
glassParts: null,
|
||||||
|
supportingItems: null,
|
||||||
|
vaps: null,
|
||||||
|
promos: null,
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
isInsurance: null,
|
||||||
|
insuranceCoverage: {
|
||||||
|
isVerified: null,
|
||||||
|
coverageStatus: null,
|
||||||
|
coverageVerificationType: null,
|
||||||
|
},
|
||||||
|
isPia: null,
|
||||||
|
piaType: null,
|
||||||
|
inactivePromos: null,
|
||||||
|
},
|
||||||
|
schedule: {
|
||||||
|
date: null,
|
||||||
|
startTime: null,
|
||||||
|
endTime: null,
|
||||||
|
jobMinMinutes: null,
|
||||||
|
jobMaxMinutes: null,
|
||||||
|
},
|
||||||
|
workOrderNumber: null,
|
||||||
|
workOrderId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
analyticsMixin.methods.pushOrderToDataLayer();
|
||||||
|
|
||||||
|
const result = window.dataLayer[0];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
console.log(result);
|
||||||
|
const allFieldsPopulatedOrDefault = Object.keys(result).every(
|
||||||
|
(key) => result[key] === false || !!result[key] || result[key] === ""
|
||||||
|
);
|
||||||
|
expect(allFieldsPopulatedOrDefault).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Glass and Promo strings correctly formatted", () => {
|
test("Glass and Promo strings correctly formatted", () => {
|
||||||
|
|
@ -437,7 +640,7 @@ describe("analyticsMixin.js", () => {
|
||||||
window.dataLayer = [];
|
window.dataLayer = [];
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
|
analyticsMixin.methods.pushOrderToDataLayer();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
const glassString = window.dataLayer[0].glassToReplace;
|
const glassString = window.dataLayer[0].glassToReplace;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,20 @@ import { storeActions } from "@/constants/store-actions.js";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||||
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
||||||
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
computed: {
|
||||||
|
IsEmailOptional() {
|
||||||
|
const emailOptional = this.getSettingValue(experimentSettings.IS_EMAIL_OPTIONAL);
|
||||||
|
return emailOptional === "true";
|
||||||
|
},
|
||||||
|
EmailValidationRules() {
|
||||||
|
return this.IsEmailOptional
|
||||||
|
? "email-address-format"
|
||||||
|
: "email-address-required|email-address-format";
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async navigateForwardWithSingleCarMatch() {
|
async navigateForwardWithSingleCarMatch() {
|
||||||
const pageName = this.$options?.name;
|
const pageName = this.$options?.name;
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,14 @@ import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
import { experimentTriggers } from "../constants/experiments";
|
import { experimentTriggers } from "../constants/experiments";
|
||||||
import { applicationConfig } from "../constants/application-config";
|
import { applicationConfig } from "../constants/application-config";
|
||||||
import { shouldStripPromoQueryString } from "@/helpers/promotions-helper";
|
import { shouldStripPromoQueryString } from "@/helpers/promotions-helper";
|
||||||
|
import bailout from "@/layouts/bailout/bailout";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
|
{
|
||||||
|
path: "/bailout",
|
||||||
|
name: "bailout",
|
||||||
|
component: bailout,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "root",
|
name: "root",
|
||||||
|
|
@ -66,9 +72,28 @@ const routes = [
|
||||||
// the saveSessionPromise will no longer point to a valid promise
|
// the saveSessionPromise will no longer point to a valid promise
|
||||||
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||||
|
|
||||||
if (!to.query.fmgPage.startsWith("payment")) {
|
if (!to.query.fmgPage?.startsWith("payment")) {
|
||||||
//payment pages used to return from safelitehop so exclude here
|
//payment pages used to return from safelitehop so exclude here
|
||||||
// Remove the parameter after quote release
|
// Remove the parameter after quote release
|
||||||
|
|
||||||
|
// for testing end to end locally, see if there is a referralNumber on the querystring
|
||||||
|
// that could have changed in heritage and then stick it in the store and then update the cookie
|
||||||
|
// so load-session runs properly. this can be removed when heritage goes away
|
||||||
|
const referralNumber = getQuerystringParameter(
|
||||||
|
queryStrings.REFERRAL_NUMBER
|
||||||
|
);
|
||||||
|
const parentAccount = getQuerystringParameter(queryStrings.PARENT_ACCOUNT);
|
||||||
|
const correlationId = getQuerystringParameter(queryStrings.CORRELATION_ID);
|
||||||
|
if (referralNumber) {
|
||||||
|
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||||
|
store.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccount);
|
||||||
|
store.commit(
|
||||||
|
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
|
||||||
|
correlationId
|
||||||
|
);
|
||||||
|
updateOrCreateFunnelCookie();
|
||||||
|
}
|
||||||
|
|
||||||
const loadSessionResponse = await loadSessionIfPresent(
|
const loadSessionResponse = await loadSessionIfPresent(
|
||||||
to.query.isInsurance != null
|
to.query.isInsurance != null
|
||||||
? to.query.isInsurance == "true"
|
? to.query.isInsurance == "true"
|
||||||
|
|
@ -103,6 +128,7 @@ const routes = [
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arePagePrerequisitesValid(component)) {
|
if (!arePagePrerequisitesValid(component)) {
|
||||||
|
console.log("Page prereq error: " + component.default.name);
|
||||||
GoToFunnelStartOn404(next);
|
GoToFunnelStartOn404(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,6 +136,7 @@ const routes = [
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isExistingFmgPageName(to.query.fmgPage)) {
|
if (!isExistingFmgPageName(to.query.fmgPage)) {
|
||||||
|
console.log("Not an existing fmg page name:" + to.query.fmgPage);
|
||||||
GoToFunnelStartOn404(next);
|
GoToFunnelStartOn404(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,6 +158,9 @@ const routes = [
|
||||||
.components.default();
|
.components.default();
|
||||||
|
|
||||||
if (!arePagePrerequisitesValid(nextComponent)) {
|
if (!arePagePrerequisitesValid(nextComponent)) {
|
||||||
|
console.log(
|
||||||
|
"Page Prereqs not valid for next component: " + nextComponent.default.name
|
||||||
|
);
|
||||||
GoToFunnelStartOn404(next);
|
GoToFunnelStartOn404(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +171,7 @@ const routes = [
|
||||||
params: to.params,
|
params: to.params,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
global.$logger.logError(`Routing ${error.stack}`);
|
||||||
|
|
||||||
if (to.query?.fmgPage === funnelStartPageName) {
|
if (to.query?.fmgPage === funnelStartPageName) {
|
||||||
deleteFunnelCookie();
|
deleteFunnelCookie();
|
||||||
|
|
@ -220,6 +250,9 @@ router.afterEach(async (to, from) => {
|
||||||
|
|
||||||
// Push experiments to Data Layer
|
// Push experiments to Data Layer
|
||||||
analyticsMixin.methods.pushExperimentsToDataLayer();
|
analyticsMixin.methods.pushExperimentsToDataLayer();
|
||||||
|
|
||||||
|
// Push current order status to Data Layer
|
||||||
|
analyticsMixin.methods.pushOrderToDataLayer();
|
||||||
});
|
});
|
||||||
|
|
||||||
router.navigateWithoutSaving = (
|
router.navigateWithoutSaving = (
|
||||||
|
|
@ -321,6 +354,7 @@ async function navigate(
|
||||||
const hasZip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
const hasZip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
||||||
const zip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
const zip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
||||||
const promo = getQuerystringParameter(queryStrings.PROMO);
|
const promo = getQuerystringParameter(queryStrings.PROMO);
|
||||||
|
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasZip &&
|
hasZip &&
|
||||||
|
|
@ -336,6 +370,10 @@ async function navigate(
|
||||||
queryStringsObject[queryStrings.PROMO] = promo;
|
queryStringsObject[queryStrings.PROMO] = promo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pageError) {
|
||||||
|
queryStringsObject[queryStrings.PAGE_ERROR] = pageError;
|
||||||
|
}
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
name: "root",
|
name: "root",
|
||||||
query: Object.assign(optionalQuery, queryStringsObject),
|
query: Object.assign(optionalQuery, queryStringsObject),
|
||||||
|
|
@ -428,7 +466,38 @@ async function DisplayPageError() {
|
||||||
);
|
);
|
||||||
|
|
||||||
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||||
location.reload();
|
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
|
||||||
|
|
||||||
|
// we use the pageError querystring as a counter to how many times a user has experienced an error and been routed here.
|
||||||
|
// once they receive more than 1 pageerrors, we'll reset their state to hopefully correct any issues they may be having.
|
||||||
|
var navPage = fmgPageValues.VEHICLE;
|
||||||
|
if (store.getters.payment.insuranceCoverage.isVerified) {
|
||||||
|
navPage = fmgPageValues.VEHICLE_DAMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageError) {
|
||||||
|
var errorCount = Number(pageError);
|
||||||
|
if (errorCount > 1) {
|
||||||
|
deleteFunnelCookie();
|
||||||
|
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
path: "/",
|
||||||
|
query: { fmgPage: navPage },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorCount++;
|
||||||
|
router.push({
|
||||||
|
path: "/",
|
||||||
|
query: { fmgPage: navPage, pageError: errorCount },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
path: "/",
|
||||||
|
query: { fmgPage: navPage, pageError: "1" },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExistingFmgPageName(pageName) {
|
function isExistingFmgPageName(pageName) {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import {
|
||||||
} from "@/helpers/promotions-helper";
|
} from "@/helpers/promotions-helper";
|
||||||
import { getDateDifferenceInDays } from "@/helpers/date-helper";
|
import { getDateDifferenceInDays } from "@/helpers/date-helper";
|
||||||
import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations";
|
import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations";
|
||||||
|
import { coverageStatusValue } from "@/constants/coverage-status";
|
||||||
// Export State
|
// Export State
|
||||||
const getDefaultState = () => {
|
const getDefaultState = () => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -83,6 +84,8 @@ const getDefaultState = () => {
|
||||||
partQuestionAnswers: null,
|
partQuestionAnswers: null,
|
||||||
moldingQuestionAnswers: null,
|
moldingQuestionAnswers: null,
|
||||||
capabilityQuestionAnswers: null,
|
capabilityQuestionAnswers: null,
|
||||||
|
dateOfLoss: null,
|
||||||
|
damageCause: null,
|
||||||
},
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: null,
|
glassParts: null,
|
||||||
|
|
@ -122,9 +125,10 @@ const getDefaultState = () => {
|
||||||
policy: {
|
policy: {
|
||||||
currentDeductible: 0,
|
currentDeductible: 0,
|
||||||
policyNumber: null,
|
policyNumber: null,
|
||||||
dateOfLoss: null,
|
|
||||||
isItac: false,
|
isItac: false,
|
||||||
additionalAuthFlag: null,
|
additionalAuthFlag: null,
|
||||||
|
isNoComp: false,
|
||||||
|
insuranceCompanyName: null,
|
||||||
},
|
},
|
||||||
schedule: {
|
schedule: {
|
||||||
date: null,
|
date: null,
|
||||||
|
|
@ -248,6 +252,9 @@ export const mutations = {
|
||||||
updateParentAcctNumber(state, parentAcctNumber) {
|
updateParentAcctNumber(state, parentAcctNumber) {
|
||||||
state.order.payment.parentAccountNumber = parentAcctNumber;
|
state.order.payment.parentAccountNumber = parentAcctNumber;
|
||||||
},
|
},
|
||||||
|
updateBillToAcctNumber(state, billToAcctNumber) {
|
||||||
|
state.order.payment.billToAccountNumber = billToAcctNumber;
|
||||||
|
},
|
||||||
updateEON(state, eon) {
|
updateEON(state, eon) {
|
||||||
state.order.eon = eon;
|
state.order.eon = eon;
|
||||||
},
|
},
|
||||||
|
|
@ -507,6 +514,8 @@ export const mutations = {
|
||||||
state.order.damage.glassToReplace = sessionInformation.order.damage.glassToReplace;
|
state.order.damage.glassToReplace = sessionInformation.order.damage.glassToReplace;
|
||||||
state.order.damage.isRepair = sessionInformation.order.damage.isRepair;
|
state.order.damage.isRepair = sessionInformation.order.damage.isRepair;
|
||||||
state.order.damage.numberOfChips = sessionInformation.order.damage.numberOfChips;
|
state.order.damage.numberOfChips = sessionInformation.order.damage.numberOfChips;
|
||||||
|
state.order.damage.dateOfLoss = sessionInformation.order.damage?.dateOfLoss;
|
||||||
|
state.order.damage.damageCause = sessionInformation.order.damage?.damageCause;
|
||||||
|
|
||||||
state.order.damage.partQuestionAnswers =
|
state.order.damage.partQuestionAnswers =
|
||||||
sessionInformation.order.damage.partQuestionAnswers;
|
sessionInformation.order.damage.partQuestionAnswers;
|
||||||
|
|
@ -523,6 +532,8 @@ export const mutations = {
|
||||||
|
|
||||||
state.order.payment.parentAccountNumber =
|
state.order.payment.parentAccountNumber =
|
||||||
sessionInformation.order.payment.parentAccountNumber;
|
sessionInformation.order.payment.parentAccountNumber;
|
||||||
|
state.order.payment.billToAccountNumber =
|
||||||
|
sessionInformation.order.payment.billToAccountNumber;
|
||||||
state.order.payment.inactivePromos = sessionInformation.order.payment.inactivePromos;
|
state.order.payment.inactivePromos = sessionInformation.order.payment.inactivePromos;
|
||||||
|
|
||||||
state.order.serviceLocation.address =
|
state.order.serviceLocation.address =
|
||||||
|
|
@ -557,8 +568,9 @@ export const mutations = {
|
||||||
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
|
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
|
||||||
state.order.payment.insuranceCoverage.isVerified =
|
state.order.payment.insuranceCoverage.isVerified =
|
||||||
sessionInformation?.order.payment.insuranceCoverage.isVerified;
|
sessionInformation?.order.payment.insuranceCoverage.isVerified;
|
||||||
state.order.payment.insuranceCoverage.coverageStatus =
|
state.order.payment.insuranceCoverage.coverageStatus = coverageStatusValue(
|
||||||
sessionInformation?.order.payment.insuranceCoverage.coverageStatus;
|
sessionInformation?.order.payment.insuranceCoverage.coverageStatus
|
||||||
|
);
|
||||||
state.order.payment.insuranceCoverage.coverageVerificationType =
|
state.order.payment.insuranceCoverage.coverageVerificationType =
|
||||||
sessionInformation?.order.payment.insuranceCoverage.coverageVerificationType;
|
sessionInformation?.order.payment.insuranceCoverage.coverageVerificationType;
|
||||||
|
|
||||||
|
|
@ -573,12 +585,25 @@ export const mutations = {
|
||||||
state.applicationUser.pageData = sessionInformation.applicationUser.pageData;
|
state.applicationUser.pageData = sessionInformation.applicationUser.pageData;
|
||||||
state.applicationUser.lastPage = sessionInformation.applicationUser.lastPage;
|
state.applicationUser.lastPage = sessionInformation.applicationUser.lastPage;
|
||||||
|
|
||||||
|
if (sessionInformation.applicationUser.savedSessionId) {
|
||||||
|
state.applicationUser.savedSessionId =
|
||||||
|
sessionInformation.applicationUser.savedSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
state.order.schedule.date = sessionInformation.order.schedule?.date;
|
state.order.schedule.date = sessionInformation.order.schedule?.date;
|
||||||
state.order.schedule.startTime = sessionInformation.order.schedule?.startTime;
|
state.order.schedule.startTime = sessionInformation.order.schedule?.startTime;
|
||||||
state.order.schedule.endTime = sessionInformation.order.schedule?.endTime;
|
state.order.schedule.endTime = sessionInformation.order.schedule?.endTime;
|
||||||
state.order.schedule.routeCode = sessionInformation.order.schedule?.routeCode;
|
state.order.schedule.routeCode = sessionInformation.order.schedule?.routeCode;
|
||||||
state.order.schedule.jobMaxMinutes = sessionInformation.order.schedule?.jobMaxMinutes;
|
state.order.schedule.jobMaxMinutes = sessionInformation.order.schedule?.jobMaxMinutes;
|
||||||
state.order.schedule.jobMinMinutes = sessionInformation.order.schedule?.jobMinMinutes;
|
state.order.schedule.jobMinMinutes = sessionInformation.order.schedule?.jobMinMinutes;
|
||||||
|
|
||||||
|
state.order.policy.currentDeductible = sessionInformation.order.policy?.currentDeductible;
|
||||||
|
state.order.policy.additionalAuthFlag = sessionInformation.order.policy?.additionalAuthFlag;
|
||||||
|
state.order.policy.isItac = sessionInformation.order.policy?.isItac;
|
||||||
|
state.order.policy.policyNumber = sessionInformation.order.policy?.policyNumber;
|
||||||
|
state.order.policy.insuranceCompanyName =
|
||||||
|
sessionInformation.order.policy?.insuranceCompanyName;
|
||||||
|
state.order.policy.isNoComp = sessionInformation.order.policy?.noComprehensive;
|
||||||
},
|
},
|
||||||
updateExperiments(state, experiments) {
|
updateExperiments(state, experiments) {
|
||||||
state.applicationUser.experiments = experiments;
|
state.applicationUser.experiments = experiments;
|
||||||
|
|
@ -1076,6 +1101,10 @@ export const actions = {
|
||||||
parentAccountNumber,
|
parentAccountNumber,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
if (!pageName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
|
|
@ -1097,14 +1126,9 @@ export const actions = {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false,
|
logApiCall: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
return response;
|
||||||
return response;
|
});
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.log("Analytics Service Error: " + error.data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
logCustomEvent(
|
logCustomEvent(
|
||||||
|
|
@ -1124,6 +1148,10 @@ export const actions = {
|
||||||
parentAccountNumber,
|
parentAccountNumber,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
if (!pageName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
|
|
@ -1147,14 +1175,9 @@ export const actions = {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false,
|
logApiCall: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
return response;
|
||||||
return response;
|
});
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.log("Analytics Service Error: " + error.data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
initializeSession(context, { userId, deviceId, sessionId, userAgent, referrer }) {
|
initializeSession(context, { userId, deviceId, sessionId, userAgent, referrer }) {
|
||||||
var payload = {
|
var payload = {
|
||||||
|
|
@ -1175,14 +1198,9 @@ export const actions = {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false,
|
logApiCall: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
return response;
|
||||||
return response;
|
});
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.log("Analytics Service Error: " + error.data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Misc Actions
|
// Misc Actions
|
||||||
|
|
@ -1343,7 +1361,7 @@ export const actions = {
|
||||||
getMobileFeePart(context, { pageNameToLog }) {
|
getMobileFeePart(context, { pageNameToLog }) {
|
||||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||||
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
||||||
const billToAccountNumber = 87291; // TODO: MAKE THIS REAL
|
const billToAccountNumber = context.getters.payment.billToAccountNumber;
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetMobileFeePart.method,
|
method: endpoints.GetMobileFeePart.method,
|
||||||
|
|
@ -1354,15 +1372,19 @@ export const actions = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||||
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems.map(
|
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems?.map(
|
||||||
(lineItem) => ({
|
(lineItem) => ({
|
||||||
partNumber: lineItem.partNumber,
|
partNumber: lineItem.partNumber,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
|
|
||||||
lineItemsWithOnlyPartNumbers,
|
var lineItems = null;
|
||||||
"lineItems"
|
if (lineItemsWithOnlyPartNumbers) {
|
||||||
);
|
lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
|
||||||
|
lineItemsWithOnlyPartNumbers,
|
||||||
|
"lineItems"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const vehicle = context.getters.vehicle;
|
const vehicle = context.getters.vehicle;
|
||||||
const carId = vehicle.carId;
|
const carId = vehicle.carId;
|
||||||
|
|
@ -1374,9 +1396,18 @@ export const actions = {
|
||||||
"glassPieces"
|
"glassPieces"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}`;
|
||||||
|
if (lineItems) {
|
||||||
|
endPoint += `&${lineItems}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glassPieces) {
|
||||||
|
endPoint += `&${glassPieces}`;
|
||||||
|
}
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetServiceabilityDetails.method,
|
method: endpoints.GetServiceabilityDetails.method,
|
||||||
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&${lineItems}&${glassPieces}`,
|
endpoint: endPoint,
|
||||||
logApiCall: true,
|
logApiCall: true,
|
||||||
pageNameToLog: pageNameToLog,
|
pageNameToLog: pageNameToLog,
|
||||||
});
|
});
|
||||||
|
|
@ -1453,7 +1484,7 @@ export const actions = {
|
||||||
) {
|
) {
|
||||||
const order = context.state.order;
|
const order = context.state.order;
|
||||||
const vehicle = context.state.order.vehicle;
|
const vehicle = context.state.order.vehicle;
|
||||||
|
const payment = context.state.order.payment;
|
||||||
let lineItems = [
|
let lineItems = [
|
||||||
...(order.lineItems.supportingItems ?? []),
|
...(order.lineItems.supportingItems ?? []),
|
||||||
...(order.lineItems.vaps ?? []),
|
...(order.lineItems.vaps ?? []),
|
||||||
|
|
@ -1475,15 +1506,16 @@ export const actions = {
|
||||||
endDate: endDate,
|
endDate: endDate,
|
||||||
shopAppointmentType: shopAppointmentType,
|
shopAppointmentType: shopAppointmentType,
|
||||||
applicationName: applicationConfig.APPLICATION_NAME,
|
applicationName: applicationConfig.APPLICATION_NAME,
|
||||||
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
parentAccountNumber: payment.parentAccountNumber,
|
||||||
carId: vehicle.carId,
|
carId: vehicle.carId,
|
||||||
lineItems: lineItems,
|
lineItems: lineItems,
|
||||||
glassPieces: glassPieces,
|
glassPieces: glassPieces,
|
||||||
eon: order.eon,
|
eon: order.eon,
|
||||||
|
billToAccountNumber: context.getters.payment.billToAccountNumber,
|
||||||
coverage: {
|
coverage: {
|
||||||
status: "",
|
status: payment.insuranceCoverage.coverageStatus,
|
||||||
deductible: 0,
|
deductible: order.policy.currentDeductible,
|
||||||
additionalAuthFlag: "",
|
additionalAuthFlag: order.policy.additionalAuthFlag,
|
||||||
},
|
},
|
||||||
partSelection: {
|
partSelection: {
|
||||||
hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
|
hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
|
||||||
|
|
@ -1521,6 +1553,7 @@ export const actions = {
|
||||||
getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) {
|
getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) {
|
||||||
const order = context.state.order;
|
const order = context.state.order;
|
||||||
const vehicle = context.state.order.vehicle;
|
const vehicle = context.state.order.vehicle;
|
||||||
|
const payment = context.state.order.payment;
|
||||||
let lineItems = [
|
let lineItems = [
|
||||||
...(order.lineItems.supportingItems ?? []),
|
...(order.lineItems.supportingItems ?? []),
|
||||||
...(order.lineItems.vaps ?? []),
|
...(order.lineItems.vaps ?? []),
|
||||||
|
|
@ -1539,15 +1572,16 @@ export const actions = {
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: endDate,
|
endDate: endDate,
|
||||||
applicationName: applicationConfig.APPLICATION_NAME,
|
applicationName: applicationConfig.APPLICATION_NAME,
|
||||||
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
parentAccountNumber: payment.parentAccountNumber,
|
||||||
carId: vehicle.carId,
|
carId: vehicle.carId,
|
||||||
lineItems: lineItems,
|
lineItems: lineItems,
|
||||||
glassPieces: glassPieces,
|
glassPieces: glassPieces,
|
||||||
eon: order.eon,
|
eon: order.eon,
|
||||||
|
billToAccountNumber: context.getters.payment.billToAccountNumber,
|
||||||
coverage: {
|
coverage: {
|
||||||
status: "",
|
status: payment.insuranceCoverage.coverageStatus,
|
||||||
deductible: 0,
|
deductible: order.policy.currentDeductible,
|
||||||
additionalAuthFlag: "",
|
additionalAuthFlag: order.policy.additionalAuthFlag,
|
||||||
},
|
},
|
||||||
partSelection: {
|
partSelection: {
|
||||||
hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
|
hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
|
||||||
|
|
@ -1566,6 +1600,7 @@ export const actions = {
|
||||||
},
|
},
|
||||||
zipCode: order.serviceLocation.zipCode,
|
zipCode: order.serviceLocation.zipCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetMobileTimeSlots.method,
|
method: endpoints.GetMobileTimeSlots.method,
|
||||||
endpoint: endpoints.GetMobileTimeSlots.url,
|
endpoint: endpoints.GetMobileTimeSlots.url,
|
||||||
|
|
@ -2117,6 +2152,9 @@ export const actions = {
|
||||||
saveParentAccountNumber(context, parentAccountNumber) {
|
saveParentAccountNumber(context, parentAccountNumber) {
|
||||||
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber);
|
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber);
|
||||||
},
|
},
|
||||||
|
saveBillToAccountNumber(context, billToAccountNumber) {
|
||||||
|
context.commit(storeMutations.UPDATE_BILL_TO_ACCT_NUMBER, billToAccountNumber);
|
||||||
|
},
|
||||||
|
|
||||||
saveSupportingItems(context, supportingItems) {
|
saveSupportingItems(context, supportingItems) {
|
||||||
if (!deepEqual(supportingItems, context.state.order.lineItems.supportingItems)) {
|
if (!deepEqual(supportingItems, context.state.order.lineItems.supportingItems)) {
|
||||||
|
|
@ -2210,6 +2248,7 @@ export const actions = {
|
||||||
|
|
||||||
const vehicle = context.getters.order.vehicle;
|
const vehicle = context.getters.order.vehicle;
|
||||||
|
|
||||||
|
// TODO: Insurance pricing...`ParentAccountNumber=${context.getters.order.payment.parentAccountNumber}`
|
||||||
let queryString =
|
let queryString =
|
||||||
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
|
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
|
||||||
`&CTU=${ctuToUse}` +
|
`&CTU=${ctuToUse}` +
|
||||||
|
|
@ -2320,6 +2359,9 @@ export const actions = {
|
||||||
) {
|
) {
|
||||||
const order = context.getters.order;
|
const order = context.getters.order;
|
||||||
lineItemsToUse = lineItemsToUse ?? order.lineItems;
|
lineItemsToUse = lineItemsToUse ?? order.lineItems;
|
||||||
|
addGuidToLineItemsIfNotAlreadyThere(lineItemsToUse.vaps);
|
||||||
|
syncLineItemIds(addableVaps, lineItemsToUse.vaps);
|
||||||
|
// addableVaps still won't have IDs if they weren't already in lineItemsToUse
|
||||||
addGuidToLineItemsIfNotAlreadyThere(addableVaps);
|
addGuidToLineItemsIfNotAlreadyThere(addableVaps);
|
||||||
const requestObject = {
|
const requestObject = {
|
||||||
promoCode: promoCode,
|
promoCode: promoCode,
|
||||||
|
|
@ -2430,7 +2472,7 @@ export const actions = {
|
||||||
logApiCall: true,
|
logApiCall: true,
|
||||||
pageNameToLog: pageNameToLog,
|
pageNameToLog: pageNameToLog,
|
||||||
});
|
});
|
||||||
console.log(insuranceCompanyList);
|
|
||||||
return insuranceCompanyList.data.sort((a, b) => {
|
return insuranceCompanyList.data.sort((a, b) => {
|
||||||
const nameA = a.accountName.toUpperCase();
|
const nameA = a.accountName.toUpperCase();
|
||||||
const nameB = b.accountName.toUpperCase();
|
const nameB = b.accountName.toUpperCase();
|
||||||
|
|
@ -2860,12 +2902,18 @@ function syncLineItemIds(lineItemsWithoutIds, lineItemsWithIds) {
|
||||||
if (!lineItemsWithIds || !lineItemsWithoutIds) {
|
if (!lineItemsWithIds || !lineItemsWithoutIds) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var clonedLineItemsWithIds = deepClone(lineItemsWithIds);
|
||||||
lineItemsWithoutIds.forEach((noId) => {
|
lineItemsWithoutIds.forEach((noId) => {
|
||||||
lineItemsWithIds.forEach((withId) => {
|
var matchedLineItemIndex = -1;
|
||||||
|
clonedLineItemsWithIds.forEach((withId, index) => {
|
||||||
if (noId.partNumber === withId.partNumber && noId.partType === withId.partType) {
|
if (noId.partNumber === withId.partNumber && noId.partType === withId.partType) {
|
||||||
|
matchedLineItemIndex = index;
|
||||||
noId.id = withId.id;
|
noId.id = withId.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (matchedLineItemIndex > -1) {
|
||||||
|
clonedLineItemsWithIds.splice(matchedLineItemIndex, 1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2929,11 +2929,135 @@ describe("Actions", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("validateOrderPromoAndSaveServerData", () => {
|
describe("validateOrderPromoAndSaveServerData", () => {
|
||||||
|
it("should add GUIDs if not there to provided lineItemsToUse.vaps before sending the http call", async () => {
|
||||||
|
// Arrange
|
||||||
|
const context = state;
|
||||||
|
const promoCode = "testPromo";
|
||||||
|
const lineItemsToUse = { vaps: [{ partNumber: 1 }], promos: [2] };
|
||||||
|
const addableVaps = [{ partNumber: "addableVap" }];
|
||||||
|
|
||||||
|
context["getters"] = {
|
||||||
|
order: {
|
||||||
|
serviceLocation: {
|
||||||
|
appointmentType: "test",
|
||||||
|
state: "test",
|
||||||
|
zipCodeCtu: "test",
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
carId: "test",
|
||||||
|
year: "test",
|
||||||
|
},
|
||||||
|
referralCorrelationId: "test",
|
||||||
|
eon: "test",
|
||||||
|
damage: {
|
||||||
|
isRepair: true,
|
||||||
|
glassToReplace: null,
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
parentAccountNumber: "test",
|
||||||
|
},
|
||||||
|
referralSequenceNumber: "test",
|
||||||
|
lineItems: {
|
||||||
|
serverData: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
crypto.randomUUID = jest.fn(() => "GUID");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
actions.validateOrderPromoAndSaveServerData(context, {
|
||||||
|
payload: {
|
||||||
|
promoCode: promoCode,
|
||||||
|
lineItemsToUse: lineItemsToUse,
|
||||||
|
addableVaps: addableVaps,
|
||||||
|
},
|
||||||
|
pageNameToLog: "test",
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const vapsItem = firstCallArgs[0].payload.order.lineItemsOnOrder.filter(
|
||||||
|
(item) => item.partNumber == 1
|
||||||
|
)[0];
|
||||||
|
expect(vapsItem.id).toEqual("GUID");
|
||||||
|
});
|
||||||
|
it("should not duplicate ids during syncing if there are two identical vaps items", async () => {
|
||||||
|
// Arrange
|
||||||
|
const context = state;
|
||||||
|
const promoCode = "testPromo";
|
||||||
|
// AddableVaps will sync its ids to vaps already on the order
|
||||||
|
const lineItemsToUse = {
|
||||||
|
vaps: [
|
||||||
|
{ partNumber: "SBB22", id: "GUID1" },
|
||||||
|
{ partNumber: "SBB22", id: "GUID2" },
|
||||||
|
],
|
||||||
|
promos: [2],
|
||||||
|
};
|
||||||
|
const addableVaps = [{ partNumber: "SBB22" }, { partNumber: "SBB22" }];
|
||||||
|
|
||||||
|
context["getters"] = {
|
||||||
|
order: {
|
||||||
|
serviceLocation: {
|
||||||
|
appointmentType: "test",
|
||||||
|
state: "test",
|
||||||
|
zipCodeCtu: "test",
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
carId: "test",
|
||||||
|
year: "test",
|
||||||
|
},
|
||||||
|
referralCorrelationId: "test",
|
||||||
|
eon: "test",
|
||||||
|
damage: {
|
||||||
|
isRepair: true,
|
||||||
|
glassToReplace: null,
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
parentAccountNumber: "test",
|
||||||
|
},
|
||||||
|
referralSequenceNumber: "test",
|
||||||
|
lineItems: {
|
||||||
|
serverData: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
crypto.randomUUID = jest.fn(() => "GUID");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
actions.validateOrderPromoAndSaveServerData(context, {
|
||||||
|
payload: {
|
||||||
|
promoCode: promoCode,
|
||||||
|
lineItemsToUse: lineItemsToUse,
|
||||||
|
addableVaps: addableVaps,
|
||||||
|
},
|
||||||
|
pageNameToLog: "test",
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const vapsItems = firstCallArgs[0].payload.addableVaps.filter(
|
||||||
|
(item) => item.partNumber == "SBB22"
|
||||||
|
);
|
||||||
|
expect(vapsItems[1].id).toEqual("GUID1");
|
||||||
|
expect(vapsItems[0].id).toEqual("GUID2");
|
||||||
|
});
|
||||||
it("should add GUIDs if not there to provided addableVaps before sending the http call", async () => {
|
it("should add GUIDs if not there to provided addableVaps before sending the http call", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const context = state;
|
const context = state;
|
||||||
const promoCode = "testPromo";
|
const promoCode = "testPromo";
|
||||||
const lineItemsToUse = { vaps: [1], promos: [2] };
|
const lineItemsToUse = { vaps: [{ partNumber: 1 }], promos: [2] };
|
||||||
const addableVaps = [{ partNumber: "addableVap" }];
|
const addableVaps = [{ partNumber: "addableVap" }];
|
||||||
|
|
||||||
context["getters"] = {
|
context["getters"] = {
|
||||||
|
|
@ -3013,7 +3137,7 @@ describe("Actions", () => {
|
||||||
},
|
},
|
||||||
referralSequenceNumber: "test",
|
referralSequenceNumber: "test",
|
||||||
lineItems: {
|
lineItems: {
|
||||||
vaps: [1],
|
vaps: [{ partNumber: 1 }],
|
||||||
promos: [2],
|
promos: [2],
|
||||||
serverData: "test",
|
serverData: "test",
|
||||||
},
|
},
|
||||||
|
|
@ -3026,7 +3150,7 @@ describe("Actions", () => {
|
||||||
|
|
||||||
crypto.randomUUID = jest.fn(() => "GUID");
|
crypto.randomUUID = jest.fn(() => "GUID");
|
||||||
|
|
||||||
const expectedLineItemsOnOrder = [1, 2];
|
const expectedLineItemsOnOrder = [{ partNumber: 1, id: "GUID" }, 2];
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
actions.validateOrderPromoAndSaveServerData(context, {
|
actions.validateOrderPromoAndSaveServerData(context, {
|
||||||
|
|
@ -3073,7 +3197,7 @@ describe("Actions", () => {
|
||||||
},
|
},
|
||||||
referralSequenceNumber: "test",
|
referralSequenceNumber: "test",
|
||||||
lineItems: {
|
lineItems: {
|
||||||
vaps: [1],
|
vaps: [{ partNumber: 1 }],
|
||||||
promos: [2],
|
promos: [2],
|
||||||
serverData: "test",
|
serverData: "test",
|
||||||
},
|
},
|
||||||
|
|
@ -3131,7 +3255,7 @@ describe("Actions", () => {
|
||||||
},
|
},
|
||||||
referralSequenceNumber: "test",
|
referralSequenceNumber: "test",
|
||||||
lineItems: {
|
lineItems: {
|
||||||
vaps: [1],
|
vaps: [{ partNumber: 1 }],
|
||||||
promos: [2],
|
promos: [2],
|
||||||
serverData: "test",
|
serverData: "test",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
class="d-flex w-100 align-items-center px-2 h-100 list-card-content button-content rounded-3"
|
class="d-flex w-100 align-items-center px-2 h-100 list-card-content button-content rounded-3"
|
||||||
:class="labelClasses">
|
:class="labelClasses">
|
||||||
<img
|
<img
|
||||||
|
v-if="buttonImage"
|
||||||
:id="buttonImageId"
|
:id="buttonImageId"
|
||||||
:class="!isWide ? 'order-1' : 'ms-auto order-3'"
|
:class="!isWide ? 'order-1' : 'ms-auto order-3'"
|
||||||
:src="buttonImage"
|
:src="buttonImage"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
process.env.VUE_APP_CONSUMER_CF_DISTRO = "https://digitalapi.dev.safelite.io";
|
process.env.VUE_APP_CONSUMER_CF_DISTRO = "https://digitalapi.dev.safelite.io";
|
||||||
process.env.VUE_APP_HERITAGE_FUNNEL = "http://localhost:38000/default.aspx";
|
//process.env.VUE_APP_HERITAGE_FUNNEL = "http://localhost:38000/default.aspx";
|
||||||
|
process.env.VUE_APP_HERITAGE_FUNNEL = "https://fixmyglassdev.safelite.com/default.aspx";
|
||||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo";
|
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo";
|
||||||
process.env.VUE_APP_CURRENT_ENVIRONMENT = "Localhost";
|
process.env.VUE_APP_CURRENT_ENVIRONMENT = "Localhost";
|
||||||
process.env.VUE_APP_MY_ACCOUNT = "https://myaccountdev.safelite.com/";
|
process.env.VUE_APP_MY_ACCOUNT = "https://myaccountdev.safelite.com/";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue