CASH-291 The issue here is that when computing the tier one price for determining which tab to select. we are including the service package discount of $50 bringing the tier one price to $287(337-50), which is less than $300 and causing the cash tab to be selected. This is not the tier one price we show but only used in the computation for determining which tab to default.
569 lines
23 KiB
Vue
569 lines
23 KiB
Vue
<template>
|
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
|
<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 mt-4">
|
|
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
|
<textboxQuestion
|
|
class="mb-2 mt-5"
|
|
cmsWidgetName="VinNumberQuestionWidget"
|
|
v-model="vin"
|
|
customInputId="vin"
|
|
isRequired
|
|
:validationRules="!vinPopulatedOnPageLoad ? 'vin-required|vin-format' : ''"
|
|
:isDisabled="vinPopulatedOnPageLoad"
|
|
maxLength="17"
|
|
:mask="vinMask"
|
|
includeImageQuestion
|
|
:imageQuestionSubmitHandler="getVinFromImage"
|
|
:maxFileSize="imageUploadMaxFileSize"
|
|
@image-lookup-error="displayVinScanAlert"
|
|
@image-validity-error="displayVinScanAlert"
|
|
data-test="vin-lookup-field"
|
|
ref="vinLookupQuestion" />
|
|
|
|
<alert
|
|
cmsWidgetName="AlertVinScanFailed"
|
|
v-if="displayVinScanFailedAlert"
|
|
alertClass="alert-danger" />
|
|
|
|
<vinInformation class="mb-4" />
|
|
|
|
<textboxQuestion
|
|
class="mb-4"
|
|
cmsWidgetName="ServiceZipQuestionWidget"
|
|
v-model="serviceZipCode"
|
|
customInputId="serviceZipCode"
|
|
mask="#####"
|
|
isRequired
|
|
validationRules="zip-required|zip-format" />
|
|
|
|
<textboxQuestion
|
|
cmsWidgetName="EmailAddressQuestionWidget"
|
|
v-model="emailOrSms"
|
|
customInputId="emailOrSms"
|
|
:isRequired="!IsEmailOptional"
|
|
:validationRules="EmailOrSmsValidationRules"
|
|
:addOptionalText="IsEmailOptional" />
|
|
|
|
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
|
|
|
<alert
|
|
ref="alertInvalidZip"
|
|
v-if="displayInvalidZipAlert"
|
|
class="my-4"
|
|
cmsWidgetName="AlertInvalidZipWidget"
|
|
alertClass="alert-danger"
|
|
v-bind:isDismissible="false" />
|
|
|
|
<alert
|
|
class="my-4"
|
|
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
|
|
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
|
|
v-model="customAlertData"
|
|
v-if="
|
|
vinPopulatedOnPageLoad &&
|
|
isInsuranceVerified &&
|
|
!displayInvalidZipAlert &&
|
|
!displayNonServiceableZipAlert
|
|
"
|
|
alertClass="alert-success" />
|
|
|
|
<alert
|
|
class="my-4"
|
|
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
|
:manualCopy="AlertMatchedDifferentVehicleBody"
|
|
v-model="customAlertData"
|
|
v-if="displayMatchedDifferentVehicleAlert"
|
|
alertClass="alert-warning" />
|
|
|
|
<alert
|
|
class="my-4"
|
|
:manualHeadline="AlertNonServiceableZipHeader"
|
|
:manualCopy="AlertNonServiceableZipBody"
|
|
v-model="customAlertData"
|
|
v-if="displayNonServiceableZipAlert"
|
|
alertClass="alert-danger" />
|
|
|
|
<alert
|
|
class="my-4"
|
|
v-model="customAlertData"
|
|
v-if="displayVinNotFoundAlert"
|
|
alertClass="alert-danger"
|
|
cmsWidgetName="AlertVinNotFoundWidget" />
|
|
|
|
<alert
|
|
class="my-4"
|
|
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
|
|
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
|
|
v-model="customAlertData"
|
|
v-if="
|
|
vinPopulatedOnPageLoad &&
|
|
!isInsuranceVerified &&
|
|
!displayInvalidZipAlert &&
|
|
!displayNonServiceableZipAlert
|
|
"
|
|
alertClass="alert-success" />
|
|
|
|
<navbar
|
|
cmsWidgetName="FunnelFooterWidget"
|
|
ref="navbar"
|
|
:isForwardActionDisabled="!meta.valid"
|
|
@back-clicked="backButtonAction"
|
|
@ForwardClicked="forwardButtonAction" />
|
|
</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 funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
|
import alert from "@/ux-components/alert/alert";
|
|
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
|
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
|
|
import textBlock from "@/digital-components/text-block/text-block";
|
|
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";
|
|
import { storeActions } from "@/constants/store-actions";
|
|
import { errorMessages } from "@/constants/error-messages";
|
|
import {
|
|
getDamageString,
|
|
getIsWindshieldOnly,
|
|
isGlassAvailableForCarId,
|
|
} from "@/helpers/damage-helper";
|
|
import { required, regex } from "@/helpers/validation-rules";
|
|
import { Form, defineRule } from "vee-validate";
|
|
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
|
import { routerParams } from "@/router/router-constants/router-params";
|
|
import baseMixin from "@/mixins/base-mixin.js";
|
|
import store from "@/store";
|
|
import vinPagesMixin from "@/mixins/vin-pages-mixin";
|
|
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
|
|
|
// DEFINE VALIDATION RULES
|
|
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
|
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
|
|
defineRule("email-sms-required", required(errorMessages.EMAIL_SMS_REQUIRED));
|
|
defineRule(
|
|
"email-sms-format",
|
|
regex(
|
|
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$|^\(?(\d{3})\)?[-. ]?(\d{3})[-. ]?(\d{4})$/,
|
|
errorMessages.EMAIL_SMS_FORMAT
|
|
)
|
|
);
|
|
defineRule("vin-required", required(errorMessages.VIN_REQUIRED));
|
|
defineRule("vin-format", regex(/^[a-hA-Hj-nJ-NpPr-zR-Z0-9]{17}$/, errorMessages.VIN_FORMAT));
|
|
|
|
export default {
|
|
name: "vin-lookup",
|
|
mixins: [vinPagesMixin],
|
|
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);
|
|
if (store.getters.externalParameterState?.isExternalParameter) {
|
|
baseMixin.methods.ResetExternalParamsAndHideModal();
|
|
}
|
|
});
|
|
},
|
|
data() {
|
|
return {
|
|
vin: this.getVinFromStore(),
|
|
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
|
|
emailOrSms: this.getEmailOrSmsFromStore(),
|
|
isCarIdDifferent: false,
|
|
customAlertData: {},
|
|
previouslyEnteredCarId: "",
|
|
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
|
|
isSelectedGlassAvailableForVehicle: true,
|
|
displayInvalidZipAlert: false,
|
|
displayNonServiceableZipAlert: false,
|
|
displayVinNotFoundAlert: false,
|
|
displayMatchedDifferentVehicleAlert: false,
|
|
displayVinScanFailedAlert: false,
|
|
};
|
|
},
|
|
methods: {
|
|
arePagePrerequisitesValid() {
|
|
return store.getters.vehicle.carId !== null;
|
|
},
|
|
getEmailOrSmsFromStore() {
|
|
return this.$store.getters.emailOrSms;
|
|
},
|
|
getVinFromStore() {
|
|
return this.$store.getters.vehicle.vin;
|
|
},
|
|
getZipFromStore() {
|
|
return this.$store.getters.order.serviceLocation.zipCode;
|
|
},
|
|
attachCustomEvents() {
|
|
this.prependActionToMethod(this, this.forwardButtonAction, () => {
|
|
this.pushEventToGA(
|
|
this.$route.query[this.queryStrings.FMG_PAGE],
|
|
this.GaActions.SUBMITTED,
|
|
this.GaLabels.VIN_LOOKUP,
|
|
true
|
|
);
|
|
});
|
|
},
|
|
backButtonAction() {
|
|
if (this.$store.getters.vehicle.vin) {
|
|
this.$router.navigateWithoutSaving(
|
|
this.navigationScenarios.CLICKED_BACK_WITH_VIN,
|
|
this.$route
|
|
);
|
|
} else {
|
|
this.$router.navigateWithoutSaving(
|
|
this.navigationScenarios.CLICKED_BACK,
|
|
this.$route
|
|
);
|
|
}
|
|
},
|
|
async forwardButtonAction() {
|
|
this.resetAlerts();
|
|
|
|
// If this is a new VIN Lookup, do both a Vehicle Lookup and a Zip Validation
|
|
if (!this.vinPopulatedOnPageLoad) {
|
|
const vehicleLookupResponse = this.dispatchStoreActionWithLogging(
|
|
storeActions.LOOKUP_VEHICLE_BY_VIN,
|
|
{ vin: this.vin },
|
|
"vin-lookup"
|
|
);
|
|
|
|
// Settle promises and get results
|
|
const promiseResultMap = [
|
|
{
|
|
resultKey: "vehicleLookupResponse",
|
|
promise: vehicleLookupResponse,
|
|
},
|
|
{
|
|
resultKey: "zipCodeData",
|
|
promise: this.getZipCodeData(this.serviceZipCode),
|
|
},
|
|
];
|
|
|
|
const resultMap = await settleAllPromises(promiseResultMap);
|
|
|
|
// If a Service Zip is entered and it is an invalid zip code (ex. 11111) then show an alert
|
|
const isZipValid = resultMap.zipCodeData.isValid;
|
|
if (this.serviceZipCode && !isZipValid) {
|
|
this.displayInvalidZipAlert = true;
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
this.displayInvalidZipAlert = false;
|
|
|
|
// If either lookup fails, remove the loader and stop processing the page.
|
|
if (!resultMap.vehicleLookupResponse || !resultMap.zipCodeData.isServiceable) {
|
|
// If the vehicle result is undefined, the vin entered was invalid.
|
|
if (!resultMap.vehicleLookupResponse) {
|
|
this.displayVinNotFoundAlert = true;
|
|
}
|
|
|
|
// Check if Service Zip entered is serviceable, if not display an alert
|
|
if (!resultMap.zipCodeData.isServiceable) {
|
|
this.displayNonServiceableZipAlert = true;
|
|
}
|
|
|
|
// Remove loader and stop processing the page.
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
|
|
// Check if the CarId is different from the lookup vs what is in state currently.
|
|
this.isCarIdDifferent =
|
|
resultMap.vehicleLookupResponse.carId !== this.$store.getters.vehicle.carId;
|
|
|
|
if (
|
|
this.isCarIdDifferent &&
|
|
resultMap.vehicleLookupResponse.carId !== this.previouslyEnteredCarId
|
|
) {
|
|
this.previouslyEnteredCarId = resultMap.vehicleLookupResponse.carId;
|
|
this.customAlertData.vehicleInfo = resultMap.vehicleLookupResponse;
|
|
this.displayMatchedDifferentVehicleAlert = true;
|
|
|
|
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
|
resultMap.vehicleLookupResponse.carId,
|
|
"vin-lookup"
|
|
);
|
|
|
|
// Update button "Continue with..."
|
|
this.$refs.navbar.updateButtonText(
|
|
`Continue with ${resultMap.vehicleLookupResponse.year} ${resultMap.vehicleLookupResponse.make} ${resultMap.vehicleLookupResponse.model}`
|
|
);
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
|
|
// Save vin, vehicle, customer and service information
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_VIN_LOOKUP,
|
|
{
|
|
isSelectedGlassAvailableForVehicle: this.isSelectedGlassAvailableForVehicle,
|
|
vehicleInfo: Object.assign(resultMap.vehicleLookupResponse, {
|
|
vin: this.vin,
|
|
}),
|
|
},
|
|
false
|
|
);
|
|
|
|
// if no value due to field being optional, blank both phone and email address
|
|
if (!this.emailOrSms) {
|
|
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false);
|
|
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, "", false);
|
|
} else {
|
|
if (vinPagesMixin.methods.isPhoneNumber(this.emailOrSms)) {
|
|
const phone = this.emailOrSms.replace(/[()]/g, "");
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_PHONE_NUMBER,
|
|
phone,
|
|
false
|
|
);
|
|
} else {
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_EMAIL,
|
|
this.emailOrSms,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_PAGE_DATA,
|
|
{
|
|
page: fmgPageValues.EMAIL_SMS_PAGES,
|
|
data: { emailOrSmsValue: this.emailOrSms },
|
|
},
|
|
false
|
|
);
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
|
|
{
|
|
state: resultMap.zipCodeData.state,
|
|
zipCode: this.serviceZipCode,
|
|
zipCodeCtu: resultMap.zipCodeData.zipCodeCtu,
|
|
},
|
|
false
|
|
);
|
|
|
|
await this.getAndSaveBillToAccountNumber();
|
|
return await this.navigateForward();
|
|
}
|
|
|
|
// If a VIN has already been found. Validate the Service Zip (in case of changes)
|
|
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
|
|
|
|
// Check if Service Zip entered is serviceable then save the ZIP info
|
|
if (zipCodeData.isServiceable) {
|
|
//Only save the zipCode, state, and zipCodeCtu if the zip changed or we lack zipCodeCtu
|
|
if (
|
|
this.$store.getters.order.serviceLocation.zipCode != this.serviceZipCode ||
|
|
!this.$store.getters.order.serviceLocation.zipCodeCtu
|
|
) {
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
|
|
{
|
|
state: zipCodeData.state,
|
|
zipCode: this.serviceZipCode,
|
|
zipCodeCtu: zipCodeData.zipCodeCtu,
|
|
},
|
|
false
|
|
);
|
|
}
|
|
|
|
// if no value due to field being optional, blank both phone and email address
|
|
if (!this.emailOrSms) {
|
|
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false);
|
|
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, "", false);
|
|
} else {
|
|
if (vinPagesMixin.methods.isPhoneNumber(this.emailOrSms)) {
|
|
const phone = this.emailOrSms.replace(/[()]/g, "");
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_PHONE_NUMBER,
|
|
phone,
|
|
false
|
|
);
|
|
} else {
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_EMAIL,
|
|
this.emailOrSms,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_PAGE_DATA,
|
|
{
|
|
page: fmgPageValues.EMAIL_SMS_PAGES,
|
|
data: { emailOrSmsValue: this.emailOrSms },
|
|
},
|
|
false
|
|
);
|
|
|
|
return await this.navigateForward();
|
|
}
|
|
|
|
// If the Service Zip is NOT serviceable then show an alert
|
|
if (zipCodeData.isValid) {
|
|
this.displayNonServiceableZipAlert = true;
|
|
} else {
|
|
this.displayInvalidZipAlert = true;
|
|
}
|
|
|
|
return this.$refs.navbar.removeLoader();
|
|
},
|
|
async navigateForward() {
|
|
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
|
|
this.$router.navigateWithSaving(
|
|
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
|
|
this.$route,
|
|
{},
|
|
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
|
|
);
|
|
} else {
|
|
await this.navigateForwardWithSingleCarMatch();
|
|
}
|
|
},
|
|
displayVinScanAlert() {
|
|
this.displayVinScanFailedAlert = true;
|
|
},
|
|
getVinFromImage(image) {
|
|
return new Promise((resolve, reject) => {
|
|
this.dispatchStoreActionWithLogging(
|
|
storeActions.LOOKUP_VIN_BY_IMAGE,
|
|
image,
|
|
"vin-lookup"
|
|
)
|
|
.then((response) => {
|
|
if (response.data.length > 0) {
|
|
resolve(response.data[0]);
|
|
} else {
|
|
reject("No VINs detected.");
|
|
}
|
|
})
|
|
.catch(() => {
|
|
reject("An error occurred during the lookup.");
|
|
});
|
|
});
|
|
},
|
|
resetAlerts() {
|
|
this.displayMatchedDifferentVehicleAlert = false;
|
|
this.displayNonServiceableZipAlert = false;
|
|
this.displayInvalidZipAlert = false;
|
|
this.displayVinNotFoundAlert = false;
|
|
this.displayVinScanFailedAlert = false;
|
|
},
|
|
},
|
|
mounted() {
|
|
this.attachCustomEvents();
|
|
},
|
|
computed: {
|
|
AlertMatchedDifferentVehicleHeader() {
|
|
return this.getCmsContent(
|
|
"AlertMatchedDifferentVehicleWidget",
|
|
"HeadlineText"
|
|
).replaceAll("{custom:damage}", getDamageString());
|
|
},
|
|
AlertMatchedDifferentVehicleBody() {
|
|
return this.getCmsContent("AlertMatchedDifferentVehicleWidget", "BodyText")
|
|
.replaceAll("{custom:damage}", getDamageString())
|
|
.replaceAll("{custom:vinlookupYear}", this.customAlertData?.vehicleInfo?.year)
|
|
.replaceAll("{custom:vinlookupMake}", this.customAlertData?.vehicleInfo?.make)
|
|
.replaceAll("{custom:vinlookupModel}", this.customAlertData?.vehicleInfo?.model);
|
|
},
|
|
AlertNonServiceableZipHeader() {
|
|
return this.getCmsContent("AlertNonServiceableZipWidget", "HeadlineText").replaceAll(
|
|
"{custom:serviceZip}",
|
|
this.serviceZipCode
|
|
);
|
|
},
|
|
AlertNonServiceableZipBody() {
|
|
return this.getCmsContent("AlertNonServiceableZipWidget", "BodyText");
|
|
},
|
|
AlertPerfectMatchInsuranceNotVerifiedHeader() {
|
|
return this.getCmsContent("AlertPerfectMatchInsuranceNotVerified", "HeadlineText");
|
|
},
|
|
AlertPerfectMatchInsuranceNotVerifiedBody() {
|
|
return this.getCmsContent(
|
|
"AlertPerfectMatchInsuranceNotVerified",
|
|
"BodyText"
|
|
).replaceAll("{custom:damage}", getDamageString());
|
|
},
|
|
AlertPerfectMatchInsuranceVerifiedHeader() {
|
|
return this.getCmsContent("AlertPerfectMatchInsuranceVerified", "HeadlineText");
|
|
},
|
|
AlertPerfectMatchInsuranceVerifiedBody() {
|
|
return this.getCmsContent("AlertPerfectMatchInsuranceVerified", "BodyText").replaceAll(
|
|
"{custom:damage}",
|
|
getIsWindshieldOnly()
|
|
);
|
|
},
|
|
isInsuranceVerified() {
|
|
return (
|
|
store.getters.payment.insuranceCoverage.isVerified ||
|
|
getFunnelCookie().HasDelayedClaimRegistration
|
|
);
|
|
},
|
|
vinMask() {
|
|
if (this.vinPopulatedOnPageLoad) {
|
|
const lastSixChars = this.vin.substring(11, this.vin.length);
|
|
return `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`;
|
|
} else {
|
|
return "XXXXXXXXXXXXXXXXX";
|
|
}
|
|
},
|
|
imageUploadMaxFileSize() {
|
|
let kiloBytes = 5140;
|
|
|
|
return kiloBytes * 1028;
|
|
},
|
|
},
|
|
watch: {
|
|
vin() {
|
|
this.displayVinNotFoundAlert = false;
|
|
this.displayVinScanFailedAlert = false;
|
|
this.$refs.navbar.updateButtonText(
|
|
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
|
);
|
|
},
|
|
serviceZipCode() {
|
|
this.displayNonServiceableZipAlert = false;
|
|
},
|
|
},
|
|
components: {
|
|
funnelHeader,
|
|
navbar,
|
|
funnelSubHeader,
|
|
textboxQuestion,
|
|
alert,
|
|
vinInformation,
|
|
textBlock,
|
|
Form,
|
|
loadingModal,
|
|
},
|
|
};
|
|
</script>
|