WIP Insurance search page w/ jQuery and jQuery UI.

This commit is contained in:
Bryan Mauger 2024-02-16 12:44:10 -05:00
parent 7af5e5eb11
commit f9462dcc09
9 changed files with 198 additions and 328 deletions

View file

@ -25,6 +25,7 @@ module.exports = {
"!src/layouts/payment/*.vue", // Temp test exclusion while in development "!src/layouts/payment/*.vue", // Temp test exclusion while in development
"!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development "!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development
"!src/layouts/insurance/*.vue", // Temp test exclusion while in development "!src/layouts/insurance/*.vue", // Temp test exclusion while in development
"!src/layouts/insurance-lookup/*.vue", // Temp test exclusion while in development
// END // END
], // ! means exclude from coverage. ], // ! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],

28
package-lock.json generated
View file

@ -16,6 +16,8 @@
"http-status-codes": "^2.1.4", "http-status-codes": "^2.1.4",
"iframe-resizer": "^4.3.7", "iframe-resizer": "^4.3.7",
"jest-junit": "^13.0.0", "jest-junit": "^13.0.0",
"jquery": "^3.7.1",
"jquery-ui": "^1.13.2",
"maska": "^1.5.0", "maska": "^1.5.0",
"vee-validate": "^4.5.7", "vee-validate": "^4.5.7",
"vue": "^3.0.0", "vue": "^3.0.0",
@ -16173,6 +16175,19 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/jquery-ui": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.2.tgz",
"integrity": "sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==",
"dependencies": {
"jquery": ">=1.8.0 <4.0.0"
}
},
"node_modules/js-beautify": { "node_modules/js-beautify": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.0.tgz", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.0.tgz",
@ -37364,6 +37379,19 @@
} }
} }
}, },
"jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"jquery-ui": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.2.tgz",
"integrity": "sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==",
"requires": {
"jquery": ">=1.8.0 <4.0.0"
}
},
"js-beautify": { "js-beautify": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.0.tgz", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.0.tgz",

View file

@ -19,6 +19,8 @@
"http-status-codes": "^2.1.4", "http-status-codes": "^2.1.4",
"iframe-resizer": "^4.3.7", "iframe-resizer": "^4.3.7",
"jest-junit": "^13.0.0", "jest-junit": "^13.0.0",
"jquery": "^3.7.1",
"jquery-ui": "^1.13.2",
"maska": "^1.5.0", "maska": "^1.5.0",
"vee-validate": "^4.5.7", "vee-validate": "^4.5.7",
"vue": "^3.0.0", "vue": "^3.0.0",

View file

@ -0,0 +1,161 @@
<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">
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-4" />
<textboxQuestion
v-model="selectedQuery"
customInputId="autocomplete"
class="mb-4 mt-5"
cmsWidgetName="InsuranceCoQuestionWidget"
includeSearchIcon
cornerStyle="rounded" />
<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 textboxQuestion from "@/digital-components/textbox-question/textbox-question";
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";
import $ from "jquery";
// 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: "insurance-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 {
availableTags: ["Allstate", "Liberty Mutual", "State Farm", "Farmers", "Progressive"],
selectedQuery: "",
};
},
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
},
handleAutocompleteSelect(value) {
this.selectedQuery = value;
}
},
mounted() {
const select = this.handleAutocompleteSelect;
$("#autocomplete").autocomplete({
source: this.availableTags,
position: {
my: 'left top+6',
},
select: function( event, ui ) {
select(ui.item.value);
}
});
},
components: {
funnelHeader,
navbar,
funnelSubHeader,
textboxQuestion,
Form,
loadingModal,
},
};
</script>
<style lang="scss">
ul {
&.ui-autocomplete {
position: absolute;
list-style-type: none;
margin: 0;
padding: 0;
background: $white;
border: 1px solid $gray;
border-radius: .25rem;
li {
padding: .125rem .5rem;
&:hover {
background-color: $gray-100;
cursor: pointer;
}
}
}
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
</style>

View file

@ -1,326 +0,0 @@
<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">
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-4" />
<textboxQuestion
class="mb-2 mt-5"
cmsWidgetName="InsuranceCoQuestionWidget"
includeSearchIcon
cornerStyle="rounded" />
<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 textboxQuestion from "@/digital-components/textbox-question/textbox-question";
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: "insurance",
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() {
// route to move backwards
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();
},
components: {
funnelHeader,
navbar,
funnelSubHeader,
textboxQuestion,
Form,
loadingModal,
},
};
</script>

View file

@ -8,6 +8,10 @@ import baseMixin from "@/mixins/base-mixin.js";
import analyticsMixin from "@/mixins/analytics-mixin.js"; import analyticsMixin from "@/mixins/analytics-mixin.js";
import experimentMixin from "@/mixins/experiment-mixin.js"; import experimentMixin from "@/mixins/experiment-mixin.js";
import "../node_modules/bootstrap/dist/js/bootstrap.js"; import "../node_modules/bootstrap/dist/js/bootstrap.js";
import "../node_modules/jquery-ui/dist/jquery-ui.min.js";
// Make jQuery avaialble globally
window.$ = window.jQuery = require("jquery");
// Vue App Setup // Vue App Setup
const vueApp = createApp(App); const vueApp = createApp(App);

View file

@ -11,7 +11,7 @@ const fmgPageValues = {
ESTIMATE: "estimate", ESTIMATE: "estimate",
ADDRESS_VEHICLES: "address-vehicles", ADDRESS_VEHICLES: "address-vehicles",
QUOTE: "quote", QUOTE: "quote",
INSURANCE: "insurance", INSURANCE_LOOKUP: "insurance-lookup",
SERVICE_LOCATION: "service-location", SERVICE_LOCATION: "service-location",
HERITAGE: "heritage", HERITAGE: "heritage",
SCHEDULE: "schedule", SCHEDULE: "schedule",

View file

@ -391,7 +391,7 @@ const routingTable = function (store) {
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_INSURANCE, scenario: navigationScenarios.CLICKED_FORWARD_WITH_INSURANCE,
destinationFmgPageValue: fmgPageValues.INSURANCE, destinationFmgPageValue: fmgPageValues.INSURANCE_LOOKUP,
}, },
], ],
}, },