Merge branch 'feature/CSR-1088' into feature/CSR-1012

This commit is contained in:
Leah Schumann 2023-02-20 13:28:34 -05:00
commit cd9a589110
16 changed files with 257 additions and 267 deletions

View file

@ -70,6 +70,10 @@ const endpoints = {
url: "/parts/api/v1/parts/rain-defense", url: "/parts/api/v1/parts/rain-defense",
method: "GET", method: "GET",
}, },
GetMobileFeePart: {
url: "/parts/api/v1/parts/mobile-fee",
method: "GET",
},
GetSupportingItems: { GetSupportingItems: {
url: "/parts/api/v1/parts/supporting-items", url: "/parts/api/v1/parts/supporting-items",
method: "POST", method: "POST",

View file

@ -27,6 +27,7 @@ const storeActions = {
GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions", GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions",
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer", GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer",
GET_MOLDING_QUESTIONS: "getMoldingQuestions", GET_MOLDING_QUESTIONS: "getMoldingQuestions",
GET_MOBILE_FEE_PART: "getMobileFeePart",
SAVE_SESSION: "saveSession", SAVE_SESSION: "saveSession",
LOAD_SESSION: "loadSession", LOAD_SESSION: "loadSession",
UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse", UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse",

View file

@ -6,7 +6,7 @@
:for="inputId" :for="inputId"
:aria-label="questionText" :aria-label="questionText"
class="form-label" class="form-label"
v-html="labelText"></label> v-html="questionText"></label>
<select <select
v-model="selectedOption" v-model="selectedOption"
class="form-select" class="form-select"
@ -35,20 +35,23 @@ export default {
name: "dropdown-question", name: "dropdown-question",
props: { props: {
modelValue: String, modelValue: String,
inputId: String, customInputId: String,
options: { options: {
type: Object, type: Object,
required: true, required: true,
}, },
isDisabled: Boolean, isDisabled: Boolean,
isRequired: Boolean, isRequired: Boolean,
disableAutoFill: Boolean,
validationRules: String, validationRules: String,
cmsWidgetName: String, cmsWidgetName: String,
hasError: Boolean, hasError: Boolean,
placeHolderText: String, placeHolderText: String,
}, },
setup(props) { setup(props) {
const inputId = !props.customInputId
? `dropdown-${crypto.randomUUID()}`
: props.customInputId;
const propsClone = Object.assign({}, props); const propsClone = Object.assign({}, props);
const modelValue = propsClone.modelValue; const modelValue = propsClone.modelValue;
let initialValue; let initialValue;
@ -69,12 +72,13 @@ export default {
}; };
const { errorMessage, handleBlur, handleChange, meta, errors } = useField( const { errorMessage, handleBlur, handleChange, meta, errors } = useField(
props.inputId, inputId,
props.validationRules, props.validationRules,
fieldOptions fieldOptions
); );
return { return {
inputId,
errorMessage, errorMessage,
handleBlur, handleBlur,
handleChange, handleChange,
@ -82,6 +86,9 @@ export default {
errors, errors,
}; };
}, },
mounted() {
this.$emit("dropdownQuestionEvent.inputIdAssigned", this.inputId);
},
computed: { computed: {
questionText() { questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText"); return this.getCmsContent(this.cmsWidgetName, "QuestionText");
@ -94,30 +101,6 @@ export default {
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
}, },
}, },
labelText: {
get: function () {
const noBreakChar = "&NoBreak;";
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;
},
},
}, },
watch: { watch: {
selectedOption(newValue) { selectedOption(newValue) {

View file

@ -60,22 +60,23 @@ export default {
setup() { setup() {
const modalId = `modal-${crypto.randomUUID()}`; const modalId = `modal-${crypto.randomUUID()}`;
const modalForm = useForm(); const form = useForm();
const isTouched = useIsFormTouched(); const isFormTouched = useIsFormTouched();
const isDirty = useIsFormDirty(); const isFormDirty = useIsFormDirty();
const isValid = useIsFormValid(); const isFormValid = useIsFormValid();
return { return {
modalId, modalId,
modalForm, form,
isTouched, isFormTouched,
isDirty, isFormDirty,
isValid, isFormValid,
}; };
}, },
methods: { methods: {
async validateAndEmit() { async validateAndEmit() {
if (!this.isFooterButtonDisabled) { const validationResult = await this.form.validate();
if (validationResult.valid) {
this.$emit("footer-button-event"); this.$emit("footer-button-event");
} }
this.resetButtonStyle(); this.resetButtonStyle();
@ -101,10 +102,10 @@ export default {
}, },
computed: { computed: {
isFooterButtonDisabled() { isFooterButtonDisabled() {
if (!this.isTouched) { if (!this.isFormTouched) {
return !this.isValid; return !this.isFormValid;
} }
return !this.isDirty || !this.isValid; return !this.isFormDirty || !this.isFormValid;
}, },
}, },
components: { components: {

View file

@ -9,6 +9,7 @@
export default { export default {
name: "textBlock", name: "textBlock",
props: { props: {
customText: String, // used to allow the insert of token values into textblock
justifyText: String, // left, right, center justifyText: String, // left, right, center
typeStyle: String, // h1-h6, body, small, label, caption (see Figma or Confluence documentation) typeStyle: String, // h1-h6, body, small, label, caption (see Figma or Confluence documentation)
fontWeight: String, // bold=500, default is 400 fontWeight: String, // bold=500, default is 400
@ -16,6 +17,9 @@ export default {
}, },
computed: { computed: {
TextBlockCopy() { TextBlockCopy() {
if (this.customText) {
return this.customText;
}
return this.getCmsContent(this.cmsWidgetName, "Text"); return this.getCmsContent(this.cmsWidgetName, "Text");
}, },
}, },

View file

@ -73,24 +73,6 @@ describe("textboxQuestion.vue", () => {
expect(paragraph.attributes("class")).toContain("form-control"); expect(paragraph.attributes("class")).toContain("form-control");
}); });
it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
},
},
mixins: [mockMixin],
});
// Act
const label = wrapper.find("label");
// Assert
expect(label.text()).toContain(questionText);
});
it("Should return input id as the id of the input field", async () => { it("Should return input id as the id of the input field", async () => {
// Arrange // Arrange
const wrapper = shallowMount(textboxQuestion, { const wrapper = shallowMount(textboxQuestion, {

View file

@ -6,7 +6,7 @@
:aria-label="questionText" :aria-label="questionText"
class="form-label" class="form-label"
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']" :class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
v-html="labelText"></label> v-html="questionText"></label>
<div class="input-wrapper" :class="[includeSearchIcon ? 'has-search-icon' : '']"> <div class="input-wrapper" :class="[includeSearchIcon ? 'has-search-icon' : '']">
<input <input
class="form-control" class="form-control"
@ -41,7 +41,6 @@
<script> <script>
import { useField, validate } from "vee-validate"; import { useField, validate } from "vee-validate";
import { storeActions } from "@/constants/store-actions";
export default { export default {
name: "textbox-question", name: "textbox-question",
@ -59,7 +58,7 @@ export default {
default: true, default: true,
}, },
modelValue: String, modelValue: String,
inputId: String, customInputId: String,
isDisabled: Boolean, isDisabled: Boolean,
isRequired: Boolean, isRequired: Boolean,
hasIcon: Boolean, // If input has an icon hasIcon: Boolean, // If input has an icon
@ -69,7 +68,6 @@ export default {
type: String, type: String,
default: "", default: "",
}, },
handleOnInput: Boolean,
validationRules: String, validationRules: String,
cmsWidgetName: String, cmsWidgetName: String,
maxLength: String, maxLength: String,
@ -78,6 +76,8 @@ export default {
includeSearchIcon: Boolean, includeSearchIcon: Boolean,
}, },
setup(props) { setup(props) {
const inputId = !props.customInputId ? `input-${crypto.randomUUID()}` : props.customInputId;
const propsClone = Object.assign({}, props); const propsClone = Object.assign({}, props);
const modelValue = propsClone.modelValue; const modelValue = propsClone.modelValue;
let initialValue; let initialValue;
@ -98,12 +98,13 @@ export default {
}; };
const { errorMessage, handleBlur, handleChange, meta, validate, errors } = useField( const { errorMessage, handleBlur, handleChange, meta, validate, errors } = useField(
props.inputId, inputId,
props.validationRules, props.validationRules,
fieldOptions fieldOptions
); );
return { return {
inputId,
errorMessage, errorMessage,
handleBlur, handleBlur,
handleChange, handleChange,
@ -124,30 +125,9 @@ export default {
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
}, },
}, },
labelText: { },
get: function () { mounted() {
const noBreakChar = "&NoBreak;"; this.$emit("textboxQuestionEvent.inputIdAssigned", this.inputId);
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;
},
},
}, },
watch: { watch: {
async value(newValue) { async value(newValue) {

View file

@ -7,11 +7,10 @@
cmsWidgetName="StreetAddressQuestionWidget" cmsWidgetName="StreetAddressQuestionWidget"
v-model="addressModel.streetAddress" v-model="addressModel.streetAddress"
ref="autocomplete" ref="autocomplete"
inputId="autocomplete" customInputId="autocomplete"
placeholderText="Search" placeholderText="Search"
aria-haspopup="" aria-haspopup=""
hasIcon hasIcon
disableAutoFill
validationRules="street-address-required" validationRules="street-address-required"
@keydown.enter.prevent /> @keydown.enter.prevent />
</div> </div>
@ -25,9 +24,7 @@
<textboxQuestion <textboxQuestion
cmsWidgetName="ApartmentNumberOrBusinessNameQuestionWidget" cmsWidgetName="ApartmentNumberOrBusinessNameQuestionWidget"
v-model="addressModel.apartmentNumberOrBusinessName" v-model="addressModel.apartmentNumberOrBusinessName"
ref="apartmentNumberOrBusinessName" ref="apartmentNumberOrBusinessName" />
inputId="639b0aaa35f64609ad6995f86a74322b"
disableAutoFill />
</div> </div>
</div> </div>
</transition> </transition>
@ -38,8 +35,6 @@
cmsWidgetName="CityQuestionWidget" cmsWidgetName="CityQuestionWidget"
v-model="addressModel.city" v-model="addressModel.city"
ref="city" ref="city"
inputId="cbf28188fdf2436688fd735915f7ee56"
disableAutoFill
validationRules="city-required" /> validationRules="city-required" />
</div> </div>
</div> </div>
@ -51,9 +46,7 @@
cmsWidgetName="StateQuestionWidget" cmsWidgetName="StateQuestionWidget"
v-model="addressModel.state" v-model="addressModel.state"
ref="state" ref="state"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="stateOptions" :options="stateOptions"
disableAutoFill
validationRules="state-required" /> validationRules="state-required" />
</div> </div>
<div class="col"> <div class="col">
@ -61,9 +54,7 @@
cmsWidgetName="ZipQuestionWidget" cmsWidgetName="ZipQuestionWidget"
v-model="addressModel.zipCode" v-model="addressModel.zipCode"
ref="zipCode" ref="zipCode"
inputId="01a9a1c2de0b4c9da8e023c9ae3be498"
mask="#####" mask="#####"
disableAutoFill
validationRules="zip-code-required|zip-code-format" /> validationRules="zip-code-required|zip-code-format" />
</div> </div>
</div> </div>
@ -207,6 +198,7 @@ export default {
}, },
methods: { methods: {
setupAddressLookup() { setupAddressLookup() {
this.showAddressFields = false;
if ( if (
this.addressModel.streetAddress && this.addressModel.streetAddress &&
this.addressModel.city && this.addressModel.city &&
@ -223,7 +215,7 @@ export default {
const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY; const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
this.$loadScript( 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(() => { .then(() => {
// Script is loaded, initialize the autocomplete textbox // Script is loaded, initialize the autocomplete textbox

View file

@ -7,7 +7,6 @@
v-model="customerModel.firstName" v-model="customerModel.firstName"
ref="firstName" ref="firstName"
inputId="08497a2efd9a4a73a70360ab47b4838d" inputId="08497a2efd9a4a73a70360ab47b4838d"
disableAutoFill
validationRules="first-name-required" /> validationRules="first-name-required" />
</div> </div>
</div> </div>
@ -18,7 +17,6 @@
v-model="customerModel.lastName" v-model="customerModel.lastName"
ref="lastName" ref="lastName"
inputId="0030e56a57e74a4ab92de7fb8e97fec5" inputId="0030e56a57e74a4ab92de7fb8e97fec5"
disableAutoFill
validationRules="last-name-required" /> validationRules="last-name-required" />
</div> </div>
</div> </div>
@ -29,7 +27,6 @@
v-model="customerModel.emailAddress" v-model="customerModel.emailAddress"
ref="emailAddress" ref="emailAddress"
inputId="00450a91b8964a768ce3992e6feb890f" inputId="00450a91b8964a768ce3992e6feb890f"
disableAutoFill
validationRules="email-address-required|email-address-format" /> validationRules="email-address-required|email-address-format" />
</div> </div>
</div> </div>

View file

@ -34,7 +34,6 @@
inputId="serviceZipCode" inputId="serviceZipCode"
mask="#####" mask="#####"
isRequired isRequired
disableAutoFill
validationRules="zip-required|zip-format" /> validationRules="zip-required|zip-format" />
</div> </div>
</div> </div>
@ -45,7 +44,6 @@
v-model="emailAddress" v-model="emailAddress"
inputId="emailAddress" inputId="emailAddress"
isRequired isRequired
disableAutoFill
validationRules="email-address-required|email-address-format" /> validationRules="email-address-required|email-address-format" />
</div> </div>
</div> </div>

View file

@ -15,23 +15,33 @@
@click-event="openModal" @click-event="openModal"
aria-label="Modal window" /> aria-label="Modal window" />
</div> </div>
<textBlock cmsWidgetName="MobileFeeDisclaimerWidget" typeStyle="caption" /> <textBlock
:customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" />
</div> </div>
<modal <modal
:ref="modalName" :ref="modalName"
:headerText="modalHeaderText" :headerText="modalHeaderText"
:onModalOpenedCallback="onModalOpened" :onModalClosedCallback="onModalClosed"
:footerButtonText="modalFooterText" :footerButtonText="modalFooterText"
@footer-button-event="setMobileLocation"> @footer-button-event="setMobileLocation">
<addressQuestions <addressQuestions
ref="addressQuestions" ref="addressQuestions"
v-model="mobileLocationQuestionsInput.addressQuestions" v-model="internalModel.addressQuestions"
captureApartmentNumberOrBusinessName="true" /> captureApartmentNumberOrBusinessName="true" />
<vehicleProtectedQuestion <vehicleProtectedQuestion
ref="vehicleProtectedQuestion" ref="vehicleProtectedQuestion"
v-model="mobileLocationQuestionsInput.isVehicleProtected" v-model="internalModel.isVehicleProtected"
cmsWidgetName="VehicleProtectedQuestionWidget" /> cmsWidgetName="VehicleProtectedQuestionWidget" />
<textBlock cmsWidgetName="WorkspaceRequirementsWidget" typeStyle="caption" /> <textBlock cmsWidgetName="WorkspaceRequirementsWidget" typeStyle="caption" />
<alert
ref="alertInvalidZip"
v-if="displayInvalidZipAlert"
class="my-4"
cmsWidgetName="AlertInvalidZipWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
</modal> </modal>
</template> </template>
@ -43,29 +53,17 @@ import alert from "@/ux-components/alert/alert";
import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions"; 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 vehicleProtectedQuestion from "@/layouts/service-location/mobile-location-modal-questions/vehicle-protected-question/vehicle-protected-question";
import textBlock from "@/digital-components/text-block/text-block"; import textBlock from "@/digital-components/text-block/text-block";
import baseMixin from "@/mixins/base-mixin.js";
// Supporting Files import { storeActions } from "@/constants/store-actions";
import { useForm } from "vee-validate";
// Define Validation Rules
export default { export default {
name: "mobile-location-modal-questions", name: "mobile-location-modal-questions",
emits: ["update:modelValue"], // The component emits an event emits: ["update:modelValue"], // The component emits an event
data() { data() {
return { return {
mobileLocationQuestionsInput: { internalModel: this.copyModel(this.modelValue),
addressQuestions: { displayInvalidZipAlert: false,
streetAddress: "", mobileFee: "",
apartmentNumberOrBusinessName: "",
city: "",
state: "",
zipCode: "",
},
isVehicleProtected: null,
isZipServiceableMobile: null,
isZipServiceableInShop: null,
},
}; };
}, },
props: { props: {
@ -79,11 +77,11 @@ export default {
state: "", state: "",
zipCode: "", zipCode: "",
}, },
isVehicleProtected: Boolean, isVehicleProtected: null,
isZipServiceableMobile: Boolean,
isZipServiceableInShop: Boolean,
}), }),
}, },
isZipServiceableMobile: Boolean,
isZipServiceableInShop: Boolean,
serviceZipCode: String, serviceZipCode: String,
linkWidgetName: String, linkWidgetName: String,
modalWidgetName: String, modalWidgetName: String,
@ -92,19 +90,27 @@ export default {
alertNonServiceableZipWidgetName: String, alertNonServiceableZipWidgetName: String,
alertInvalidZipWidgetName: String, alertInvalidZipWidgetName: String,
}, },
async mounted() {
this.mobileFee = await this.getMobileFee();
},
computed: { computed: {
getModalId() { mobileFeeText() {
return "#" + this.modalWidgetName; const cmsContentText = this.getCmsContent("MobileFeeDisclaimerWidget", "Text");
return cmsContentText.replaceAll("{custom:mobileFee}", this.mobileFee);
}, },
mobileLocationLinkPromptText() { mobileLocationLinkPromptText() {
return this.getCmsContent(this.linkWidgetName, "HeaderText"); return this.getCmsContent(this.linkWidgetName, "HeaderText");
}, },
mobileLocationLinkText() { mobileLocationLinkText() {
if ( if (
this.addressModel.streetAddress != "" && this.addressModel.streetAddress !== null &&
this.addressModel.city != "" && this.addressModel.streetAddress !== "" &&
this.addressModel.state != "" && this.addressModel.city !== null &&
this.addressModel.zipCode != "" 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}`; return `${this.addressModel.streetAddress}\n${this.addressModel.city}, ${this.addressModel.state} ${this.addressModel.zipCode}`;
} }
@ -119,64 +125,96 @@ export default {
modalName() { modalName() {
return this.modalWidgetName; return this.modalWidgetName;
}, },
mobileLocationQuestionsModel: {
get: function () {
return this.modelValue;
},
set: function (newValue) {
this.$emit("update:modelValue", newValue);
},
},
addressModel: { addressModel: {
get: function () { get: function () {
return this.mobileLocationQuestionsInput.addressQuestions; return this.modelValue.addressQuestions;
}, },
}, },
}, },
methods: { 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() { openModal() {
this.$refs[this.modalName].openModal(); this.$refs[this.modalName].openModal();
}, },
onModalClosed() {
this.mobileLocationQuestionsInput = this.mobileLocationQuestionsModel;
},
closeModal() { closeModal() {
this.$refs[this.modalName].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() { resetModel() {
// Address // Address
this.addressModel.streetAddress = ""; this.internalModel.addressQuestions.streetAddress = "";
this.addressModel.apartmentNumberOrBusinessName = ""; this.internalModel.addressQuestions.apartmentNumberOrBusinessName = "";
this.addressModel.city = ""; this.internalModel.addressQuestions.city = "";
this.addressModel.state = ""; this.internalModel.addressQuestions.state = "";
this.addressModel.zipCode = ""; this.internalModel.addressQuestions.zipCode = "";
// Is Vehicle Protected // 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: { watch: {
serviceZipCode: { modelValue(newValue) {
handler() { this.internalModel = this.copyModel(newValue);
// if they modify the Service Zip Code, clear the model
this.resetModel();
},
}, },
}, },
components: { components: {
@ -185,6 +223,7 @@ export default {
addressQuestions, addressQuestions,
vehicleProtectedQuestion, vehicleProtectedQuestion,
textBlock, textBlock,
alert,
}, },
}; };
</script> </script>

View file

@ -5,13 +5,15 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<serviceZipModalQuestion <serviceZipModalQuestion
v-model="serviceZipQuestionModel" v-model="serviceZipCodeQuestion"
@service-zip-updated="resetMobileLocation"
linkWidgetName="ServiceZipLinkWidget" linkWidgetName="ServiceZipLinkWidget"
modalWidgetName="ServiceZipModalWidget" modalWidgetName="ServiceZipModalWidget"
textboxQuestionWidgetName="ServiceZipQuestionWidget" textboxQuestionWidgetName="ServiceZipQuestionWidget"
alertInvalidZipWidgetName="AlertInvalidZipWidget" /> alertInvalidZipWidgetName="AlertInvalidZipWidget" />
<mobileLocationModalQuestions <mobileLocationModalQuestions
v-model="mobileLocationQuestionsModel" v-model="mobileLocationQuestions"
ref="mobileLocationModalQuestions"
linkWidgetName="MobileLocationLinkWidget" linkWidgetName="MobileLocationLinkWidget"
modalWidgetName="MobileLocationModalWidget" modalWidgetName="MobileLocationModalWidget"
mobileFeeDisclaimerWidgetName="MobileFeeDisclaimerWidget" mobileFeeDisclaimerWidgetName="MobileFeeDisclaimerWidget"
@ -38,20 +40,29 @@ import { Form } from "vee-validate";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import store from "@/store";
export default { export default {
name: "service-location", name: "service-location",
data() { data() {
return { return {
isServiceable: null,
streetAddress: "",
apartmentNumberOrBusinessName: "",
city: "",
state: "",
zipCode: "",
isVehicleProtected: null,
isZipServiceableMobile: null, isZipServiceableMobile: null,
isZipServiceableInShop: null, isZipServiceableInShop: null,
serviceZipCodeQuestion: {
state: this.getServiceStateFromStore(),
zipCode: this.getServiceZipCodeFromStore(),
isServiceable: this.getIsServiceableFromStore(),
},
mobileLocationQuestions: {
addressQuestions: {
streetAddress: this.getRegistrationAddressFromStore(),
apartmentNumberOrBusinessName: "",
city: this.getRegistrationCityFromStore(),
state: this.getRegistrationStateFromStore(),
zipCode: this.getRegistrationZipFromStore(),
},
isVehicleProtected: null,
},
}; };
}, },
@ -74,54 +85,46 @@ export default {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
}); });
}, },
computed: {
serviceZipQuestionModel: {
get: function () {
return {
state: this.state,
zipCode: this.zipCode,
isServiceable: this.isServiceable,
};
},
set: function (newValue) {
this.state = newValue.state;
this.zipCode = newValue.zipCode;
this.isServiceable = newValue.isServiceable;
},
},
mobileLocationQuestionsModel: {
get: function () {
return {
addressQuestions: {
streetAddress: this.streetAddress,
apartmentNumberOrBusinessName: this.apartmentNumberOrBusinessName,
city: this.city,
state: this.state,
zipCode: this.zipCode,
},
isVehicleProtected: this.isVehicleProtected,
isZipServiceableMobile: this.isZipServiceableMobile,
isZipServiceableInShop: this.isZipServiceableInShop,
};
},
set: function (newValue) {
this.streetAddress = newValue.addressQuestions.streetAddress;
this.apartmentNumberOrBusinessName =
newValue.addressQuestions.apartmentNumberOrBusinessName;
this.city = newValue.addressQuestions.city;
this.state = newValue.addressQuestions.state;
this.zipCode = newValue.addressQuestions.zipCode;
this.isVehicleProtected = newValue.isVehicleProtected;
this.isZipServiceableMobile = newValue.isZipServiceableMobile;
this.isZipServiceableInShop = newValue.isZipServiceableInShop;
},
},
},
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return true; return 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;
},
getServiceZipCodeFromStore() {
return this.$store.getters.order.serviceLocation.zipCode;
},
getServiceStateFromStore() {
return this.$store.getters.order.serviceLocation.state;
},
getIsServiceableFromStore() {
return this.$store.getters.order.serviceLocation.isServiceable;
},
resetMobileLocation() {
this.mobileLocationQuestions = {
addressQuestions: {
streetAddress: "",
apartmentNumberOrBusinessName: "",
city: "",
state: "",
zipCode: "",
},
isVehicleProtected: null,
};
this.$refs.mobileLocationModalQuestions.resetComponent();
},
backButtonAction() { backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
}, },

View file

@ -19,8 +19,9 @@
@footer-button-event="setZipCode"> @footer-button-event="setZipCode">
<serviceZipQuestion <serviceZipQuestion
ref="serviceZipQuestion" ref="serviceZipQuestion"
:cmsWidgetName="textboxQuestionWidgetName" v-model="internalModel.zipCode"
v-model="serviceZipCodeInput" /> v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }"
:cmsWidgetName="textboxQuestionWidgetName" />
<alert <alert
ref="alertInvalidZip" ref="alertInvalidZip"
v-if="displayInvalidZipAlert" v-if="displayInvalidZipAlert"
@ -38,20 +39,12 @@ import serviceZipQuestion from "@/layouts/service-location/service-zip-modal-que
import modal from "@/digital-components/modal/modal"; import modal from "@/digital-components/modal/modal";
import alert from "@/ux-components/alert/alert"; import alert from "@/ux-components/alert/alert";
//Supporting Files
import { defineRule, useForm } from "vee-validate";
import { required, regex } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
// 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));
export default { export default {
name: "service-zip-modal-question", name: "service-zip-modal-question",
data() { data() {
return { return {
serviceZipCodeInput: null, internalModel: this.copyModel(this.modelValue),
serviceZipCodeTextInputId: "",
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
}; };
}, },
@ -59,11 +52,9 @@ export default {
modelValue: { modelValue: {
type: Object, type: Object,
default: () => ({ default: () => ({
serviceZipQuestion: { state: "",
state: "", zipCode: "",
zipCode: "", isServiceable: null,
isServiceable: null,
},
}), }),
}, },
linkWidgetName: String, linkWidgetName: String,
@ -87,14 +78,6 @@ export default {
modalName() { modalName() {
return this.modalWidgetName; return this.modalWidgetName;
}, },
serviceZipModel: {
get: function () {
return this.modelValue;
},
set: function (newValue) {
this.$emit("update:modelValue", newValue);
},
},
}, },
methods: { methods: {
resetAlerts() { resetAlerts() {
@ -107,10 +90,18 @@ export default {
}, },
focusOnZipInput() { focusOnZipInput() {
const input = document.getElementById("serviceZipCodeTextBoxQuestion"); const input = document.getElementById(this.serviceZipCodeTextInputId);
input.focus(); input.focus();
}, },
copyModel(modelToCopy) {
return {
state: modelToCopy.state,
zipCode: modelToCopy.zipCode,
isServiceable: modelToCopy.isServiceable,
};
},
openModal() { openModal() {
this.$refs[this.modalName].openModal(); this.$refs[this.modalName].openModal();
}, },
@ -119,38 +110,49 @@ export default {
this.$refs[this.modalName].closeModal(); this.$refs[this.modalName].closeModal();
}, },
onModalOpened() { onInputIdAssigned(inputId) {
this.serviceZipCodeInput = this.serviceZipModel.zipCode; this.serviceZipCodeTextInputId = inputId;
this.focusOnZipInput();
// bubble up the event to the parent
this.$emit("textboxQuestionEvent.inputIdAssigned", inputId);
}, },
onModalClosed() { onModalOpened() {
this.serviceZipCodeInput = this.serviceZipModel.zipCode; this.focusOnZipInput();
this.resetsOnZipInput();
}, },
async setZipCode() { async setZipCode() {
this.resetAlerts(); this.resetAlerts();
const zipCodeData = await this.getZipCodeData(this.serviceZipCodeInput); const zipCodeData = await this.getZipCodeData(this.internalModel.zipCode);
if (!zipCodeData.isValid) { if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true; this.displayInvalidZipAlert = true;
this.focusOnZipInput(); this.focusOnZipInput();
} else { } else {
this.serviceZipModel = { this.internalModel.state = zipCodeData.state;
state: zipCodeData.state, this.internalModel.isServiceable = zipCodeData.isServiceable;
zipCode: this.serviceZipCodeInput,
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(); this.closeModal();
} }
}, },
}, },
watch: { watch: {
serviceZipCodeInput() { "internalModel.zipCode": {
this.resetsOnZipInput(); handler() {
this.resetsOnZipInput();
},
},
modelValue(newValue) {
this.internalModel = this.copyModel(newValue);
}, },
}, },
components: { components: {

View file

@ -3,7 +3,7 @@
ref="zipInputTextQuestion" ref="zipInputTextQuestion"
:cmsWidgetName="cmsWidgetName" :cmsWidgetName="cmsWidgetName"
v-model="value" v-model="value"
inputId="serviceZipCodeTextBoxQuestion" inputId="serviceZipCode"
questionAlignment="center" questionAlignment="center"
mask="#####" mask="#####"
:displayQuestionText="false" :displayQuestionText="false"

View file

@ -15,7 +15,6 @@
v-model="vin" v-model="vin"
inputId="vin" inputId="vin"
isRequired isRequired
disableAutoFill
validationRules="vin-required|vin-format" validationRules="vin-required|vin-format"
:isDisabled="vinPopulatedOnPageLoad" :isDisabled="vinPopulatedOnPageLoad"
maxLength="17" maxLength="17"
@ -35,7 +34,6 @@
inputId="serviceZipCode" inputId="serviceZipCode"
mask="#####" mask="#####"
isRequired isRequired
disableAutoFill
validationRules="zip-required|zip-format" /> validationRules="zip-required|zip-format" />
</div> </div>
</div> </div>
@ -46,7 +44,6 @@
v-model="emailAddress" v-model="emailAddress"
inputId="emailAddress" inputId="emailAddress"
isRequired isRequired
disableAutoFill
validationRules="email-address-required|email-address-format" /> validationRules="email-address-required|email-address-format" />
</div> </div>
</div> </div>

View file

@ -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) { getSupportingItems(context) {
const glassPartsArray = context.getters.lineItems.glassParts ?? []; const glassPartsArray = context.getters.lineItems.glassParts ?? [];
const carId = context.getters.vehicle.carId; const carId = context.getters.vehicle.carId;