519 lines
22 KiB
Vue
519 lines
22 KiB
Vue
<template>
|
|
<Form
|
|
@submit="onSubmit"
|
|
@invalid-submit="onInvalidSubmit"
|
|
ref="theForm"
|
|
v-slot="{ meta }"
|
|
autocomplete="off">
|
|
<div class="container-fluid page-container-grouped-styles" id="address-lookup">
|
|
<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" ref="funnelSubHeader" />
|
|
|
|
<customerQuestions
|
|
ref="customerQuestions"
|
|
v-model="customerQuestions"
|
|
:validationRules="EmailOrSmsValidationRules"
|
|
:isEmailOptional="IsEmailOptional" />
|
|
<alert
|
|
ref="alertVinNotFound"
|
|
v-if="displayVinNotFoundAlert"
|
|
class="mb-4"
|
|
cmsWidgetName="AlertVinNotFoundWidget"
|
|
alertClass="alert-danger"
|
|
v-bind:isDismissible="false" />
|
|
|
|
<alert
|
|
ref="alertMatchedDifferentVehicle"
|
|
v-if="displayMatchedDifferentVehicleAlert"
|
|
class="mb-4"
|
|
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
|
:manualCopy="AlertMatchedDifferentVehicleBody"
|
|
alertClass="alert-warning"
|
|
v-bind:isDismissible="false" />
|
|
|
|
<alert
|
|
ref="alertInvalidZip"
|
|
v-if="displayInvalidZipAlert"
|
|
class="mb-4"
|
|
cmsWidgetName="AlertInvalidZipWidget"
|
|
alertClass="alert-danger"
|
|
v-bind:isDismissible="false" />
|
|
|
|
<alert
|
|
ref="alertNonServiceableZip"
|
|
v-if="displayNonServiceableZipAlert"
|
|
class="mb-4"
|
|
alertClass="alert-danger"
|
|
:manualHeadline="AlertNonServiceableZipHeader"
|
|
:manualCopy="AlertNonServiceableZipBody"
|
|
v-bind:isDismissible="false" />
|
|
|
|
<alert
|
|
ref="alertVinLookupsByHomeAddressNotAllowed"
|
|
v-if="displayVinLookupByHomeAddressNotAllowedAlert"
|
|
class="mb-4"
|
|
cmsWidgetName="AlertVinLookupsByHomeAddressNotAllowedWidget"
|
|
alertClass="alert-danger"
|
|
v-bind:isDismissible="false" />
|
|
|
|
<transition name="fade" mode="out-in">
|
|
<div
|
|
class="service-zip-field"
|
|
v-if="showServiceZipField"
|
|
aria-live="polite">
|
|
<div class="row mt-4 mb-1">
|
|
<div class="col">
|
|
<textboxQuestion
|
|
cmsWidgetName="ServiceZipQuestionWidget"
|
|
v-model="serviceZipCode"
|
|
ref="serviceZip"
|
|
customInputId="serviceZip"
|
|
aria-haspopup=""
|
|
mask="#####"
|
|
validationRules="service-zip-required|service-zip-format" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
<navbar
|
|
cmsWidgetName="FunnelFooterWidget"
|
|
ref="navbar"
|
|
:isDisabled="!meta.valid"
|
|
@ForwardClicked="forwardButtonAction"
|
|
@back-clicked="backButtonAction"
|
|
:isForwardActionDisabled="!meta.valid" />
|
|
</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 customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions";
|
|
import alert from "@/ux-components/alert/alert";
|
|
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
|
|
|
import { Form, defineRule } from "vee-validate";
|
|
import { required, regex } from "@/helpers/validation-rules";
|
|
import { errorMessages } from "@/constants/error-messages";
|
|
|
|
// Supporting files
|
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
|
import { storeActions } from "@/constants/store-actions";
|
|
import { routerParams } from "@/router/router-constants/router-params";
|
|
import { getDamageString, isGlassAvailableForCarId } from "@/helpers/damage-helper";
|
|
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 - Note: Additional rules are defined in Customer Questions and Address Questions components
|
|
defineRule("service-zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
|
defineRule(
|
|
"service-zip-format",
|
|
regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT)
|
|
);
|
|
|
|
export default {
|
|
name: "address-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 {
|
|
customerQuestions: {
|
|
addressQuestions: {
|
|
streetAddress: this.getRegistrationAddressFromStore(),
|
|
city: this.getRegistrationCityFromStore(),
|
|
state: this.getRegistrationStateFromStore(),
|
|
zipCode: this.getRegistrationZipFromStore(),
|
|
},
|
|
firstName: this.getRegistrationFirstNameFromStore(),
|
|
lastName: this.getRegistrationLastNameFromStore(),
|
|
emailOrSms: this.getEmailOrSmsFromStore(),
|
|
},
|
|
serviceZipCode: this.getServiceZipFromStore(),
|
|
carId: this.getCarIdfromStore(),
|
|
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
|
|
displayNonServiceableZipAlert: false,
|
|
displayVinNotFoundAlert: false,
|
|
displayMatchedDifferentVehicleAlert: false,
|
|
displayVinLookupByHomeAddressNotAllowedAlert: false,
|
|
previouslyEnteredCarId: "",
|
|
isCarIdDifferent: false,
|
|
isSelectedGlassAvailableForVehicle: true,
|
|
customAlertData: {},
|
|
displayInvalidZipAlert: false,
|
|
showServiceZipField: this.getServiceZipFromStore(),
|
|
isZipServiceable: false,
|
|
};
|
|
},
|
|
methods: {
|
|
arePagePrerequisitesValid() {
|
|
return store.getters.vehicle.carId !== null;
|
|
},
|
|
backButtonAction() {
|
|
// route to move backwards
|
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
|
},
|
|
attachCustomEvents() {
|
|
this.prependActionToMethod(this, this.forwardButtonAction, () => {
|
|
this.pushEventToGA(
|
|
this.$route.query[this.queryStrings.FMG_PAGE],
|
|
this.GaActions.SUBMITTED,
|
|
this.GaLabels.ADDRESS_LOOKUP,
|
|
true
|
|
);
|
|
});
|
|
},
|
|
getRegistrationAddressFromStore() {
|
|
return this.$store.getters.vehicle.registration.address;
|
|
},
|
|
getRegistrationCityFromStore() {
|
|
return this.$store.getters.vehicle.registration.city;
|
|
},
|
|
getRegistrationStateFromStore() {
|
|
return this.$store.getters.vehicle.registration.state;
|
|
},
|
|
getRegistrationZipFromStore() {
|
|
return this.$store.getters.vehicle.registration.zipCode;
|
|
},
|
|
getRegistrationFirstNameFromStore() {
|
|
return this.$store.getters.vehicle.registration.firstName;
|
|
},
|
|
getRegistrationLastNameFromStore() {
|
|
return this.$store.getters.vehicle.registration.lastName;
|
|
},
|
|
getEmailOrSmsFromStore() {
|
|
return this.$store.getters.emailOrSms;
|
|
},
|
|
getServiceZipFromStore() {
|
|
return this.$store.getters.order.serviceLocation.zipCode;
|
|
},
|
|
getCarIdfromStore() {
|
|
return store.getters.vehicle.carId;
|
|
},
|
|
getIsVehicleHeavyTruckServiceableFromStore() {
|
|
return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService;
|
|
},
|
|
async findClosestApplicableShops() {
|
|
return await this.dispatchStoreActionWithLogging(
|
|
storeActions.GET_CLOSEST_APPLICABLE_SHOPS,
|
|
{ zip: this.serviceZipCode, carId: this.carId },
|
|
"address-lookup"
|
|
);
|
|
},
|
|
async forwardButtonAction() {
|
|
this.resetWarningsAndErrors();
|
|
|
|
// Vehicle info change in the flow, use a variable to keep track and commit to state at the end.
|
|
let vehicleInfoToCommit = {};
|
|
|
|
const vinLookupResponse = this.dispatchStoreActionWithLogging(
|
|
storeActions.LOOKUP_VIN_BY_ADDRESS,
|
|
{
|
|
licenseLastName: this.customerQuestions.lastName,
|
|
licenseStreetAddress: this.customerQuestions.addressQuestions.streetAddress,
|
|
licenseZip: this.customerQuestions.addressQuestions.zipCode,
|
|
licenseState: this.customerQuestions.addressQuestions.state,
|
|
},
|
|
"address-lookup",
|
|
false
|
|
);
|
|
|
|
// Settle promises and get results
|
|
const promiseResultMap = [
|
|
{
|
|
resultKey: "vinLookupResponse",
|
|
promise: vinLookupResponse,
|
|
},
|
|
{
|
|
resultKey: "serviceZipValidationResponse",
|
|
promise: this.serviceZipCode
|
|
? this.dispatchStoreActionWithLogging(
|
|
storeActions.VALIDATE_ZIP,
|
|
{
|
|
zip: this.serviceZipCode,
|
|
},
|
|
"address-lookup"
|
|
)
|
|
: this.dispatchStoreActionWithLogging(
|
|
storeActions.VALIDATE_ZIP,
|
|
{
|
|
zip: this.customerQuestions.addressQuestions.zipCode,
|
|
},
|
|
"address-lookup"
|
|
),
|
|
},
|
|
];
|
|
|
|
const resultMap = await settleAllPromises(promiseResultMap);
|
|
// If VIN Lookup by address is forbidden by State Restrictions then show an alert
|
|
if (!resultMap.vinLookupResponse.isStatePermissible) {
|
|
// State Restrictions forbid lookup by address
|
|
this.displayVinLookupByHomeAddressNotAllowedAlert = true;
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
|
|
// If a Service Zip is entered and it is an invalid zip code (ex. 11111) then show an alert
|
|
const isZipValid = resultMap.serviceZipValidationResponse.isValid;
|
|
if (this.serviceZipCode && !isZipValid) {
|
|
this.displayInvalidZipAlert = true;
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
this.displayInvalidZipAlert = false;
|
|
const carsFound = resultMap.vinLookupResponse.vinVehicles;
|
|
|
|
// Handle cases for different amounts of VINS found for the address.
|
|
if (carsFound.length == 1) {
|
|
// Single VIN found
|
|
const carFound = carsFound[0].vehicle;
|
|
|
|
this.isCarIdDifferent = carFound.carId !== this.$store.getters.vehicle.carId;
|
|
if (this.isCarIdDifferent && carFound.carId !== this.previouslyEnteredCarId) {
|
|
// Display Alert
|
|
this.previouslyEnteredCarId = carFound.carId;
|
|
this.customAlertData.vehicleInfo = carFound;
|
|
this.displayMatchedDifferentVehicleAlert = true;
|
|
|
|
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
|
carFound.carId,
|
|
"address-lookup"
|
|
);
|
|
|
|
// Update button "Continue with..."
|
|
this.$refs.navbar.updateButtonText(
|
|
`Continue with ${carFound.year} ${carFound.make} ${carFound.model}`
|
|
);
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
|
|
// update data if the zip or service zip is serviceable
|
|
vehicleInfoToCommit = Object.assign(carFound, { vin: carsFound[0].vin });
|
|
} else if (carsFound.length > 1) {
|
|
// If multiple cars were found and one and only one of them matches the carId entered, save the vehicle info
|
|
// so we can go to the Heritage Funnel directly
|
|
const matchingCars = carsFound.filter(
|
|
(vin) => vin.vehicle.carId === this.$store.getters.vehicle.carId
|
|
);
|
|
|
|
if (matchingCars.length === 1) {
|
|
vehicleInfoToCommit = Object.assign(matchingCars[0].vehicle, {
|
|
vin: matchingCars[0].vin,
|
|
});
|
|
}
|
|
} else {
|
|
// No VINS found.
|
|
this.displayVinNotFoundAlert = true;
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
|
|
// If the either the registration zip code or service zip code are not serviceable
|
|
this.isZipServiceable = resultMap.serviceZipValidationResponse.isServiceable;
|
|
if (!this.isZipServiceable) {
|
|
this.displayNonServiceableZipAlert = true;
|
|
this.showServiceZipField = true;
|
|
return this.$refs.navbar.removeLoader();
|
|
}
|
|
|
|
// If the registration zip code is serviceable and nothing was entered for the service zip code
|
|
// then set the service zip code to the registration zip code
|
|
if (!this.serviceZipCode) {
|
|
this.serviceZipCode = this.customerQuestions.addressQuestions.zipCode;
|
|
}
|
|
|
|
// Save vehicle, customer, service and registration information
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_REGISTRATION_ADDRESS_LOOKUP,
|
|
{
|
|
isSelectedGlassAvailableForVehicle: this.isSelectedGlassAvailableForVehicle,
|
|
vehicleInfo:
|
|
Object.keys(vehicleInfoToCommit).length === 0
|
|
? this.$store.getters.vehicle
|
|
: vehicleInfoToCommit,
|
|
customerInfo: {
|
|
firstName: this.customerQuestions.firstName,
|
|
lastName: this.customerQuestions.lastName,
|
|
},
|
|
},
|
|
false
|
|
);
|
|
|
|
// if no value due to field being optional, blank both phone and email address
|
|
if (!this.customerQuestions.emailOrSms) {
|
|
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false);
|
|
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, "", false);
|
|
} else {
|
|
if (vinPagesMixin.methods.isPhoneNumber(this.customerQuestions.emailOrSms)) {
|
|
const phone = this.customerQuestions.emailOrSms.replace(/[()]/g, "");
|
|
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, phone, false);
|
|
} else {
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_EMAIL,
|
|
this.customerQuestions.emailOrSms,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_PAGE_DATA,
|
|
{
|
|
page: fmgPageValues.EMAIL_SMS_PAGES,
|
|
data: { emailOrSmsValue: this.customerQuestions.emailOrSms },
|
|
},
|
|
false
|
|
);
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
|
|
{
|
|
state: resultMap.serviceZipValidationResponse.state,
|
|
zipCode: this.serviceZipCode,
|
|
zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu,
|
|
},
|
|
false
|
|
);
|
|
await this.getAndSaveBillToAccountNumber();
|
|
|
|
return await this.navigateForward(carsFound);
|
|
},
|
|
async navigateForward(carsFound) {
|
|
// Match vehicles found to vehicles in state.
|
|
const matchingCars = carsFound.filter(
|
|
(car) => car.vehicle.carId === this.$store.getters.vehicle.carId
|
|
);
|
|
|
|
// If a different vehicle is found than the one entered and the selected glass is not available for that vehicle then navigate back to "vehicle-damage"
|
|
// display vehicle changed alert on that page.
|
|
if (
|
|
this.isCarIdDifferent &&
|
|
!this.isSelectedGlassAvailableForVehicle &&
|
|
matchingCars.length === 1
|
|
) {
|
|
this.$router.navigateWithSaving(
|
|
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
|
|
this.$route,
|
|
{},
|
|
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
|
|
);
|
|
} else if (matchingCars.length === 1) {
|
|
await this.navigateForwardWithSingleCarMatch();
|
|
} else {
|
|
this.$router.navigateWithSaving(
|
|
this.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
|
|
this.$route,
|
|
{},
|
|
{},
|
|
carsFound
|
|
);
|
|
}
|
|
},
|
|
resetWarningsAndErrors() {
|
|
this.displayVinNotFoundAlert = false;
|
|
this.displayNonServiceableZipAlert = false;
|
|
this.displayMatchedDifferentVehicleAlert = false;
|
|
this.displayVinLookupByHomeAddressNotAllowedAlert = false;
|
|
},
|
|
},
|
|
mounted() {
|
|
this.attachCustomEvents();
|
|
},
|
|
computed: {
|
|
AlertNonServiceableZipHeader() {
|
|
const zipCode = this.serviceZipCode
|
|
? this.serviceZipCode
|
|
: this.customerQuestions.addressQuestions.zipCode;
|
|
const text = this.getCmsContent(
|
|
"AlertNonServiceableZipWidget",
|
|
"HeadlineText"
|
|
).replaceAll("{custom:serviceZip}", zipCode);
|
|
return text;
|
|
},
|
|
AlertNonServiceableZipBody() {
|
|
return this.getCmsContent("AlertNonServiceableZipWidget", "BodyText");
|
|
},
|
|
AlertMatchedDifferentVehicleHeader() {
|
|
return this.getCmsContent(
|
|
"AlertMatchedDifferentVehicleWidget",
|
|
"HeadlineText"
|
|
).replaceAll("{custom:glassText}", getDamageString());
|
|
},
|
|
AlertMatchedDifferentVehicleBody() {
|
|
const vinYmmFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
|
|
const vinYmmExpected = `${this.$store.getters.vehicle.year} ${this.$store.getters.vehicle.make} ${this.$store.getters.vehicle.model}`;
|
|
|
|
return this.getCmsContent("AlertMatchedDifferentVehicleWidget", "BodyText")
|
|
.replaceAll("{custom:glassText}", getDamageString())
|
|
.replaceAll("{custom:vinYmmFound}", vinYmmFound)
|
|
.replaceAll("{custom:vinYmmExpected}", vinYmmExpected);
|
|
},
|
|
},
|
|
watch: {
|
|
customerQuestions: {
|
|
handler(newValue) {
|
|
// if they modify one of the lookup fields (address, city, state, zipCode, or lastName), then modify the button text back to "Get my personalized quote"
|
|
this.$refs.navbar.updateButtonText(
|
|
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
|
);
|
|
this.showServiceZipField = false;
|
|
this.resetWarningsAndErrors();
|
|
},
|
|
deep: true,
|
|
},
|
|
serviceZipCode: {
|
|
handler(newValue) {
|
|
// If they modify the service zip code, then hide the error message.
|
|
this.displayNonServiceableZipAlert = false;
|
|
},
|
|
},
|
|
showServiceZipField: {
|
|
handler(newValue) {
|
|
// If the Service Zip Code field is ever hidden, clear out it's value
|
|
if (!newValue) {
|
|
this.serviceZipCode = null;
|
|
}
|
|
},
|
|
},
|
|
},
|
|
components: {
|
|
funnelHeader,
|
|
navbar,
|
|
funnelSubHeader,
|
|
customerQuestions,
|
|
textboxQuestion,
|
|
alert,
|
|
Form,
|
|
},
|
|
};
|
|
</script>
|