DigitalConsumer.FixMyGlass/src/layouts/vin-lookup/vin-lookup.vue
2023-10-02 11:05:53 -04:00

514 lines
21 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" />
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<textboxQuestion
class="mb-4 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="emailAddress"
customInputId="emailAddress"
isRequired
validationRules="email-address-required|email-address-format" />
<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 vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
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 store from "@/store";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
// 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-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
defineRule(
"email-address-format",
regex(
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
errorMessages.EMAIL_ADDRESS_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);
});
},
data() {
return {
vin: this.getVinFromStore(),
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
emailAddress: this.getEmailFromStore(),
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;
},
getEmailFromStore() {
return this.$store.getters.order.customer.emailAddress;
},
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
);
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
state: resultMap.zipCodeData.state,
zipCode: this.serviceZipCode,
zipCodeCtu: resultMap.zipCodeData.zipCodeCtu,
},
false
);
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
);
}
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, 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,
vehicleBanner,
funnelSubHeader,
textboxQuestion,
alert,
vinInformation,
textBlock,
Form,
loadingModal,
},
};
</script>