diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 9f4c60d61..6b1e69609 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -70,6 +70,10 @@ const endpoints = { url: "/parts/api/v1/parts/rain-defense", method: "GET", }, + GetMobileFeePart: { + url: "/parts/api/v1/parts/mobile-fee", + method: "GET", + }, GetSupportingItems: { url: "/parts/api/v1/parts/supporting-items", method: "POST", diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 428280d40..eb781b020 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -27,6 +27,7 @@ const storeActions = { GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions", GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer", GET_MOLDING_QUESTIONS: "getMoldingQuestions", + GET_MOBILE_FEE_PART: "getMobileFeePart", SAVE_SESSION: "saveSession", LOAD_SESSION: "loadSession", UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse", diff --git a/src/digital-components/dropdown-question/dropdown-question.vue b/src/digital-components/dropdown-question/dropdown-question.vue index bdc36a0d8..e122837c4 100644 --- a/src/digital-components/dropdown-question/dropdown-question.vue +++ b/src/digital-components/dropdown-question/dropdown-question.vue @@ -6,7 +6,7 @@ :for="inputId" :aria-label="questionText" class="form-label" - v-html="labelText"> + v-html="questionText"> import { useField, validate } from "vee-validate"; -import { storeActions } from "@/constants/store-actions"; export default { name: "textbox-question", @@ -59,7 +58,7 @@ export default { default: true, }, modelValue: String, - inputId: String, + customInputId: String, isDisabled: Boolean, isRequired: Boolean, hasIcon: Boolean, // If input has an icon @@ -69,7 +68,6 @@ export default { type: String, default: "", }, - handleOnInput: Boolean, validationRules: String, cmsWidgetName: String, maxLength: String, @@ -78,6 +76,8 @@ export default { includeSearchIcon: Boolean, }, setup(props) { + const inputId = !props.customInputId ? `input-${crypto.randomUUID()}` : props.customInputId; + const propsClone = Object.assign({}, props); const modelValue = propsClone.modelValue; let initialValue; @@ -98,12 +98,13 @@ export default { }; const { errorMessage, handleBlur, handleChange, meta, validate, errors } = useField( - props.inputId, + inputId, props.validationRules, fieldOptions ); return { + inputId, errorMessage, handleBlur, handleChange, @@ -124,30 +125,9 @@ export default { this.$emit("update:modelValue", newValue); }, }, - labelText: { - get: function () { - const noBreakChar = "⁠"; - var questionText = ""; - if (this.disableAutoFill) { - var words = this.questionText.toString().split(/[ ]+/); - words.forEach(function (word) { - const position = 1; - word = [ - word.toString().slice(0, position), - noBreakChar, - word.toString().slice(position), - ].join(""); - questionText += `${word} `; - }); - - questionText = questionText.trimEnd(); - } else { - questionText = this.questionText.toString(); - } - - return questionText; - }, - }, + }, + mounted() { + this.$emit("textboxQuestionEvent.inputIdAssigned", this.inputId); }, watch: { async value(newValue) { diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue index aed1b5147..c37e596ef 100644 --- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue +++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue @@ -7,11 +7,10 @@ cmsWidgetName="StreetAddressQuestionWidget" v-model="addressModel.streetAddress" ref="autocomplete" - inputId="autocomplete" + customInputId="autocomplete" placeholderText="Search" aria-haspopup="" hasIcon - disableAutoFill validationRules="street-address-required" @keydown.enter.prevent /> @@ -25,9 +24,7 @@ + ref="apartmentNumberOrBusinessName" /> @@ -38,8 +35,6 @@ cmsWidgetName="CityQuestionWidget" v-model="addressModel.city" ref="city" - inputId="cbf28188fdf2436688fd735915f7ee56" - disableAutoFill validationRules="city-required" /> @@ -51,9 +46,7 @@ cmsWidgetName="StateQuestionWidget" v-model="addressModel.state" ref="state" - inputId="8fdf9dc2e13e430eb57529499dceb3eb" :options="stateOptions" - disableAutoFill validationRules="state-required" />
@@ -61,9 +54,7 @@ cmsWidgetName="ZipQuestionWidget" v-model="addressModel.zipCode" ref="zipCode" - inputId="01a9a1c2de0b4c9da8e023c9ae3be498" mask="#####" - disableAutoFill validationRules="zip-code-required|zip-code-format" />
@@ -207,6 +198,7 @@ export default { }, methods: { setupAddressLookup() { + this.showAddressFields = false; if ( this.addressModel.streetAddress && this.addressModel.city && @@ -223,7 +215,7 @@ export default { const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY; this.$loadScript( - `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places` + `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=Function.prototype` ) .then(() => { // Script is loaded, initialize the autocomplete textbox diff --git a/src/layouts/address-lookup/customer-questions/customer-questions.vue b/src/layouts/address-lookup/customer-questions/customer-questions.vue index cfc121731..325f8c380 100644 --- a/src/layouts/address-lookup/customer-questions/customer-questions.vue +++ b/src/layouts/address-lookup/customer-questions/customer-questions.vue @@ -7,7 +7,6 @@ v-model="customerModel.firstName" ref="firstName" inputId="08497a2efd9a4a73a70360ab47b4838d" - disableAutoFill validationRules="first-name-required" /> @@ -18,7 +17,6 @@ v-model="customerModel.lastName" ref="lastName" inputId="0030e56a57e74a4ab92de7fb8e97fec5" - disableAutoFill validationRules="last-name-required" /> @@ -29,7 +27,6 @@ v-model="customerModel.emailAddress" ref="emailAddress" inputId="00450a91b8964a768ce3992e6feb890f" - disableAutoFill validationRules="email-address-required|email-address-format" /> diff --git a/src/layouts/estimate/estimate.vue b/src/layouts/estimate/estimate.vue index 36a52a2ff..e92fae541 100644 --- a/src/layouts/estimate/estimate.vue +++ b/src/layouts/estimate/estimate.vue @@ -34,7 +34,6 @@ inputId="serviceZipCode" mask="#####" isRequired - disableAutoFill validationRules="zip-required|zip-format" /> @@ -45,7 +44,6 @@ v-model="emailAddress" inputId="emailAddress" isRequired - disableAutoFill validationRules="email-address-required|email-address-format" /> diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue index 78310096a..d1aa4dd4b 100644 --- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue +++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue @@ -15,23 +15,33 @@ @click-event="openModal" aria-label="Modal window" /> - + + @@ -43,29 +53,17 @@ import alert from "@/ux-components/alert/alert"; import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions"; import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location-modal-questions/vehicle-protected-question/vehicle-protected-question"; import textBlock from "@/digital-components/text-block/text-block"; - -// Supporting Files -import { useForm } from "vee-validate"; - -// Define Validation Rules +import baseMixin from "@/mixins/base-mixin.js"; +import { storeActions } from "@/constants/store-actions"; export default { name: "mobile-location-modal-questions", emits: ["update:modelValue"], // The component emits an event data() { return { - mobileLocationQuestionsInput: { - addressQuestions: { - streetAddress: "", - apartmentNumberOrBusinessName: "", - city: "", - state: "", - zipCode: "", - }, - isVehicleProtected: null, - isZipServiceableMobile: null, - isZipServiceableInShop: null, - }, + internalModel: this.copyModel(this.modelValue), + displayInvalidZipAlert: false, + mobileFee: "", }; }, props: { @@ -79,11 +77,11 @@ export default { state: "", zipCode: "", }, - isVehicleProtected: Boolean, - isZipServiceableMobile: Boolean, - isZipServiceableInShop: Boolean, + isVehicleProtected: null, }), }, + isZipServiceableMobile: Boolean, + isZipServiceableInShop: Boolean, serviceZipCode: String, linkWidgetName: String, modalWidgetName: String, @@ -92,19 +90,27 @@ export default { alertNonServiceableZipWidgetName: String, alertInvalidZipWidgetName: String, }, + async mounted() { + this.mobileFee = await this.getMobileFee(); + }, computed: { - getModalId() { - return "#" + this.modalWidgetName; + mobileFeeText() { + const cmsContentText = this.getCmsContent("MobileFeeDisclaimerWidget", "Text"); + return cmsContentText.replaceAll("{custom:mobileFee}", this.mobileFee); }, mobileLocationLinkPromptText() { return this.getCmsContent(this.linkWidgetName, "HeaderText"); }, mobileLocationLinkText() { if ( - this.addressModel.streetAddress != "" && - this.addressModel.city != "" && - this.addressModel.state != "" && - this.addressModel.zipCode != "" + this.addressModel.streetAddress !== null && + this.addressModel.streetAddress !== "" && + this.addressModel.city !== null && + this.addressModel.city !== "" && + this.addressModel.state !== null && + this.addressModel.state !== "" && + this.addressModel.zipCode !== null && + this.addressModel.zipCode !== "" ) { return `${this.addressModel.streetAddress}\n${this.addressModel.city}, ${this.addressModel.state} ${this.addressModel.zipCode}`; } @@ -119,64 +125,96 @@ export default { modalName() { return this.modalWidgetName; }, - mobileLocationQuestionsModel: { - get: function () { - return this.modelValue; - }, - set: function (newValue) { - this.$emit("update:modelValue", newValue); - }, - }, addressModel: { get: function () { - return this.mobileLocationQuestionsInput.addressQuestions; + return this.modelValue.addressQuestions; }, }, }, methods: { + copyModel(modelToCopy) { + return { + addressQuestions: { + streetAddress: modelToCopy.addressQuestions.streetAddress, + apartmentNumberOrBusinessName: + modelToCopy.addressQuestions.apartmentNumberOrBusinessName, + city: modelToCopy.addressQuestions.city, + state: modelToCopy.addressQuestions.state, + zipCode: modelToCopy.addressQuestions.zipCode, + }, + isVehicleProtected: modelToCopy.isVehicleProtected, + isZipServiceableMobile: modelToCopy.isZipServiceableMobile, + isZipServiceableInShop: modelToCopy.isZipServiceableInShop, + }; + }, + + async getMobileFee() { + // Get the Mobile Fee Part + const mobileFeePart = await baseMixin.methods.dispatchStoreAction( + storeActions.GET_MOBILE_FEE_PART, + null, + false + ); + + // Get the Mobile Fee Part Price + const pricingResults = await baseMixin.methods.dispatchStoreAction( + storeActions.PRICE_ORDER_ITEMS, + [mobileFeePart.data], + false + ); + + return await baseMixin.methods.getTotalLineItemPrice(pricingResults[0]); + }, + openModal() { this.$refs[this.modalName].openModal(); }, - onModalClosed() { - this.mobileLocationQuestionsInput = this.mobileLocationQuestionsModel; - }, + closeModal() { this.$refs[this.modalName].closeModal(); }, + + resetComponent() { + this.resetModel(); + + // Reset the validation form + this.$refs[this.modalName].form.resetForm(); + + // Reinitialize the Address Auto Complete + this.$refs.addressQuestions.setupAddressLookup(); + }, + resetModel() { // Address - this.addressModel.streetAddress = ""; - this.addressModel.apartmentNumberOrBusinessName = ""; - this.addressModel.city = ""; - this.addressModel.state = ""; - this.addressModel.zipCode = ""; + this.internalModel.addressQuestions.streetAddress = ""; + this.internalModel.addressQuestions.apartmentNumberOrBusinessName = ""; + this.internalModel.addressQuestions.city = ""; + this.internalModel.addressQuestions.state = ""; + this.internalModel.addressQuestions.zipCode = ""; // Is Vehicle Protected - this.mobileLocationQuestionsInput.isVehicleProtected = null; + this.internalModel.isVehicleProtected = null; }, - async setMobileLocation() { - this.mobileLocationQuestionsModel = { - addressQuestions: { - streetAddress: this.addressModel.streetAddress, - apartmentNumberOrBusinessName: this.addressModel.apartmentNumberOrBusinessName, - city: this.addressModel.city, - state: this.addressModel.state, - zipCode: this.addressModel.zipCode, - }, - isVehicleProtected: this.mobileLocationQuestionsInput.isVehicleProtected, - isZipServiceableMobile: this.mobileLocationQuestionsInput.isZipServiceableMobile, - isZipServiceableInShop: this.mobileLocationQuestionsInput.isZipServiceableInShop, - }; - this.closeModal(); + async setMobileLocation() { + // Validate the Zip Code + const zipCodeData = await this.getZipCodeData( + this.internalModel.addressQuestions.zipCode + ); + + if (!zipCodeData.isValid) { + this.displayInvalidZipAlert = true; + } else { + // Update the page level model + this.$emit("update:modelValue", this.internalModel); + + this.closeModal(); + } }, }, watch: { - serviceZipCode: { - handler() { - // if they modify the Service Zip Code, clear the model - this.resetModel(); - }, + modelValue(newValue) { + this.internalModel = this.copyModel(newValue); }, }, components: { @@ -185,6 +223,7 @@ export default { addressQuestions, vehicleProtectedQuestion, textBlock, + alert, }, }; diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 209a2f0fd..6406519dd 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -5,13 +5,15 @@ + v-model="internalModel.zipCode" + v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }" + :cmsWidgetName="textboxQuestionWidgetName" /> ({ - serviceZipQuestion: { - state: "", - zipCode: "", - isServiceable: null, - }, + state: "", + zipCode: "", + isServiceable: null, }), }, linkWidgetName: String, @@ -87,14 +78,6 @@ export default { modalName() { return this.modalWidgetName; }, - serviceZipModel: { - get: function () { - return this.modelValue; - }, - set: function (newValue) { - this.$emit("update:modelValue", newValue); - }, - }, }, methods: { resetAlerts() { @@ -107,10 +90,18 @@ export default { }, focusOnZipInput() { - const input = document.getElementById("serviceZipCodeTextBoxQuestion"); + const input = document.getElementById(this.serviceZipCodeTextInputId); input.focus(); }, + copyModel(modelToCopy) { + return { + state: modelToCopy.state, + zipCode: modelToCopy.zipCode, + isServiceable: modelToCopy.isServiceable, + }; + }, + openModal() { this.$refs[this.modalName].openModal(); }, @@ -119,38 +110,49 @@ export default { this.$refs[this.modalName].closeModal(); }, - onModalOpened() { - this.serviceZipCodeInput = this.serviceZipModel.zipCode; - this.focusOnZipInput(); + onInputIdAssigned(inputId) { + this.serviceZipCodeTextInputId = inputId; + + // bubble up the event to the parent + this.$emit("textboxQuestionEvent.inputIdAssigned", inputId); }, - onModalClosed() { - this.serviceZipCodeInput = this.serviceZipModel.zipCode; - this.resetsOnZipInput(); + onModalOpened() { + this.focusOnZipInput(); }, async setZipCode() { this.resetAlerts(); - const zipCodeData = await this.getZipCodeData(this.serviceZipCodeInput); + const zipCodeData = await this.getZipCodeData(this.internalModel.zipCode); if (!zipCodeData.isValid) { this.displayInvalidZipAlert = true; this.focusOnZipInput(); } else { - this.serviceZipModel = { - state: zipCodeData.state, - zipCode: this.serviceZipCodeInput, - isServiceable: zipCodeData.isServiceable, - }; + this.internalModel.state = zipCodeData.state; + this.internalModel.isServiceable = zipCodeData.isServiceable; + + // Notify the page that the service zip has been updated to clear Mobile Location form + if (this.internalModel.zipCode !== this.modelValue.zipCode) { + this.$emit("service-zip-updated"); + } + + // Update the page level model + this.$emit("update:modelValue", this.internalModel); this.closeModal(); } }, }, watch: { - serviceZipCodeInput() { - this.resetsOnZipInput(); + "internalModel.zipCode": { + handler() { + this.resetsOnZipInput(); + }, + }, + modelValue(newValue) { + this.internalModel = this.copyModel(newValue); }, }, components: { diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-question.vue b/src/layouts/service-location/service-zip-modal-question/service-zip-question.vue index 4ebe39ed1..979094523 100644 --- a/src/layouts/service-location/service-zip-modal-question/service-zip-question.vue +++ b/src/layouts/service-location/service-zip-modal-question/service-zip-question.vue @@ -3,7 +3,7 @@ ref="zipInputTextQuestion" :cmsWidgetName="cmsWidgetName" v-model="value" - inputId="serviceZipCodeTextBoxQuestion" + inputId="serviceZipCode" questionAlignment="center" mask="#####" :displayQuestionText="false" diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 0c49dc262..11d7ac2ca 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -15,7 +15,6 @@ v-model="vin" inputId="vin" isRequired - disableAutoFill validationRules="vin-required|vin-format" :isDisabled="vinPopulatedOnPageLoad" maxLength="17" @@ -35,7 +34,6 @@ inputId="serviceZipCode" mask="#####" isRequired - disableAutoFill validationRules="zip-required|zip-format" /> @@ -46,7 +44,6 @@ v-model="emailAddress" inputId="emailAddress" isRequired - disableAutoFill validationRules="email-address-required|email-address-format" /> diff --git a/src/store/index.js b/src/store/index.js index 93a3e5a25..5f4d7c1f5 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -907,6 +907,13 @@ export const actions = { }); }, + getMobileFeePart(context) { + return globalMethods.callMockHttpClient({ + method: endpoints.GetMobileFeePart.method, + endpoint: "https://run.mocky.io/v3/795de4cb-d014-48b4-9338-dab33261adce", //TODO: Remove Mocky Endpoint + }); + }, + getSupportingItems(context) { const glassPartsArray = context.getters.lineItems.glassParts ?? []; const carId = context.getters.vehicle.carId;