DigitalConsumer.FixMyGlass/src/layouts/address-lookup/address-lookup.vue

360 lines
17 KiB
Vue

<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" autocomplete="off">
<div class="page-container-grouped-styles">
<loadingModal ref="loadingModal" />
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
<vehicleBanner cmsWidgetName="VehicleBannerWidget" ref="vehicleBanner" :displayGenericVehicleImage=false />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
<div class="fade-on-route-transition sub-container make-tall">
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
<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="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 mb-4">
<div class="col">
<textboxQuestion cmsWidgetName="ServiceZipQuestionWidget" v-model="serviceZipCode" ref="serviceZip" inputId="7add1b26df344f2caf1678de5797803f" aria-haspopup="" mask="#####" validationRules="service-zip-required|service-zip-format" />
</div>
</div>
</div>
</transition>
<funnel-footer cmsWidgetName="FunnelFooterWidget" ref="funnelFooter" :isDisabled="!meta.valid" @ForwardClicked="forwardButtonAction" @back-clicked="backButtonAction" :isForwardActionDisabled="!meta.valid" />
</div>
</div>
</Form>
</template>
<script>
// Components
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-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 "@/common-components/textbox-question/textbox-question";
import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
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 store from "@/store";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
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);
});
},
data() {
return {
customerQuestions: {
addressQuestions: {
streetAddress: this.getRegistrationAddressFromStore(),
city: this.getRegistrationCityFromStore(),
state: this.getRegistrationStateFromStore(),
zipCode: this.getRegistrationZipFromStore(),
},
firstName: this.getRegistrationFirstNameFromStore(),
lastName: this.getRegistrationLastNameFromStore(),
emailAddress: this.getEmailFromStore(),
},
serviceZipCode: this.getServiceZipFromStore(),
displayNonServiceableZipAlert: false,
displayVinNotFoundAlert: false,
displayMatchedDifferentVehicleAlert: false,
displayVinLookupByHomeAddressNotAllowedAlert: false,
previouslyEnteredCarId: "",
isSelectedGlassAvailableForVehicle: false,
customAlertData: {},
showServiceZipField: this.getServiceZipFromStore(),
isZipServiceable: false,
}
},
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
},
backButtonAction() {
// route to move backwards
this.$router.navigate(
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;
},
getEmailFromStore() {
return this.$store.getters.order.customer.emailAddress;
},
getServiceZipFromStore() {
return this.$store.getters.order.serviceLocation.zipCode;
},
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.dispatchStoreAction(
storeActions.LOOKUP_VIN_BY_ADDRESS, {
licenseLastName: this.customerQuestions.lastName,
licenseStreetAddress: this.customerQuestions.addressQuestions.streetAddress,
licenseZip: this.customerQuestions.addressQuestions.zipCode,
licenseState: this.customerQuestions.addressQuestions.state
}, false);
// Settle promises and get results
const promiseResultMap = [{
resultKey: "vinLookupResponse",
promise: vinLookupResponse
},
{
resultKey: "serviceZipValidationResponse",
promise: this.serviceZipCode ?
this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {zip: this.serviceZipCode}) :
this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {zip: this.customerQuestions.addressQuestions.zipCode})
}
];
const resultMap = await settleAllPromises(promiseResultMap);
// Verify if the service zip code or registration zip code provided is serviceable
if (!resultMap.vinLookupResponse.isStatePermissible) {
// State Restrictions forbid lookup by address
this.displayVinLookupByHomeAddressNotAllowedAlert = true;
return this.$refs.funnelFooter.removeLoader();
}
// If the neither 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.funnelFooter.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;
}
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);
// Update button "Continue with..."
this.$refs.funnelFooter.updateButtonText(`Continue with ${carFound.year} ${carFound.make} ${carFound.model}`);
return this.$refs.funnelFooter.removeLoader();
}
if (!this.isZipServiceable) {
return;
}
// update data if the zip or service zip is serviceable
vehicleInfoToCommit = Object.assign(carFound, { vin: carsFound[0].vin });
} else if (carsFound.length > 1) {
// Multiple VINS found
if (!this.isZipServiceable) {
return;
}
// 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.funnelFooter.removeLoader();
}
// 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,
registrationInfo: {
firstName: this.customerQuestions.firstName,
lastName: this.customerQuestions.lastName,
address: this.customerQuestions.addressQuestions.streetAddress,
city: this.customerQuestions.addressQuestions.city,
state: resultMap.serviceZipValidationResponse.state,
zipCode: this.customerQuestions.addressQuestions.zipCode
}
}, false);
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.customerQuestions.emailAddress, false);
await this.dispatchStoreAction(storeActions.SAVE_SERVICE_LOCATION, {
address: this.customerQuestions.addressQuestions.streetAddress,
city: this.customerQuestions.addressQuestions.city,
zipCode: this.serviceZipCode,
state: resultMap.serviceZipValidationResponse.state,
}, false);
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.navigate(this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, this.$route, {}, {[routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true});
} else if (matchingCars.length === 1) {
await this.navigateForwardWithSingleCarMatch();
} else {
this.$router.navigate(this.navigationScenarios.CONTINUING_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.funnelFooter.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,
funnelFooter,
vehicleBanner,
funnelSubHeader,
customerQuestions,
textboxQuestion,
alert,
loadingModal,
Form
},
};
</script>