Some updates for validation on zip entry/shop search
This commit is contained in:
parent
21e9127b50
commit
d85c0f6c38
16 changed files with 255 additions and 512 deletions
|
|
@ -25,6 +25,11 @@ const errorMessages = Object.freeze({
|
||||||
EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.',
|
EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.',
|
||||||
SERVICE_ZIP_REQUIRED: 'ZIP code is required',
|
SERVICE_ZIP_REQUIRED: 'ZIP code is required',
|
||||||
SERVICE_ZIP_FORMAT: 'Invalid ZIP, please enter a new ZIP (5 characters)',
|
SERVICE_ZIP_FORMAT: 'Invalid ZIP, please enter a new ZIP (5 characters)',
|
||||||
|
MOBILE_SERVICE_ZIP_FORMAT: 'Invalid ZIP code',
|
||||||
|
NO_SERVICE_IN_ZIP: (zip) => `We're sorry, but we don't offer service in ${zip} at this time.`,
|
||||||
|
PROVIDER_REQUIRED: 'Please select a provider.',
|
||||||
|
INVALID_ZIP: 'The ZIP you entered was invalid. Please enter a valid ZIP.',
|
||||||
|
ZIP_CODE_NOT_SERVICED_FOR_VEHICLE: 'We do not currently offer glass service for your vehicle in this ZIP code. Please try another ZIP code.',
|
||||||
VIN_REQUIRED: 'Please enter your VIN',
|
VIN_REQUIRED: 'Please enter your VIN',
|
||||||
VIN_FORMAT:
|
VIN_FORMAT:
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,11 @@ export default {
|
||||||
this.resetField({
|
this.resetField({
|
||||||
value: newValue
|
value: newValue
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
answers() {
|
||||||
|
this.resetField({
|
||||||
|
value: this.modelValue
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,10 @@ export default {
|
||||||
modalPosition: {
|
modalPosition: {
|
||||||
type: String,
|
type: String,
|
||||||
default: modalPositions.edge
|
default: modalPositions.edge
|
||||||
|
},
|
||||||
|
allowInvalidSubmit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['footer-button-event', 'isModalOpened'],
|
emits: ['footer-button-event', 'isModalOpened'],
|
||||||
|
|
@ -123,7 +127,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
async validateAndEmit() {
|
async validateAndEmit() {
|
||||||
const validationResult = await this.validate();
|
const validationResult = await this.validate();
|
||||||
if (validationResult.valid) {
|
if (validationResult.valid || this.allowInvalidSubmit) {
|
||||||
this.$emit('footer-button-event');
|
this.$emit('footer-button-event');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
customText: String, // used to allow the insert of token values into textblock
|
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
|
typeStyle: String, // h1-h6, body, small, label, caption, disclaimer
|
||||||
// (see Figma or Confluence documentation)
|
// (see Figma or Confluence documentation)
|
||||||
fontWeight: String, // bold=500, default is 400
|
fontWeight: String, // bold=500, default is 400
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
|
|
@ -109,5 +109,12 @@ export default {
|
||||||
&.dark {
|
&.dark {
|
||||||
color: $black;
|
color: $black;
|
||||||
}
|
}
|
||||||
|
&.disclaimer {
|
||||||
|
color: $lighter-gray;
|
||||||
|
font-weight: $font-weight-light;
|
||||||
|
font-size: .8125rem;
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: .625rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -250,8 +250,10 @@ export default {
|
||||||
this.$emit('click-event');
|
this.$emit('click-event');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clickedTextButton() {
|
async clickedTextButton() {
|
||||||
if (this.meta.valid) {
|
const result = await validate(this.value, this.validationRules);
|
||||||
|
if (result.valid) {
|
||||||
|
this.handleChange(this.value);
|
||||||
this.$emit('click-event');
|
this.$emit('click-event');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -378,8 +380,8 @@ input::-webkit-date-and-time-value {
|
||||||
.form-control {
|
.form-control {
|
||||||
border: $border-input;
|
border: $border-input;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
min-height: 3rem;
|
min-height: 2.8125rem;
|
||||||
max-height: 3rem;
|
max-height: 2.8125rem;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
letter-spacing: inherit;
|
letter-spacing: inherit;
|
||||||
box-shadow: $box-shadow-input;
|
box-shadow: $box-shadow-input;
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export default {
|
||||||
const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker');
|
const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker');
|
||||||
markers?.forEach((marker, index) => {
|
markers?.forEach((marker, index) => {
|
||||||
const pinElement = new PinElement({
|
const pinElement = new PinElement({
|
||||||
glyph: String.fromCharCode('A'.charCodeAt(0) + index),
|
glyphText: String.fromCharCode('A'.charCodeAt(0) + index),
|
||||||
glyphColor: '#000000',
|
glyphColor: '#000000',
|
||||||
borderColor: '#000000'
|
borderColor: '#000000'
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
exports[`Shop list button should render correctly with all relevant props 1`] = `
|
exports[`Shop list button should render correctly with all relevant props 1`] = `
|
||||||
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true" selectedvalue="selected value">
|
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true" selectedvalue="selected value">
|
||||||
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" buttonlabel="Label of button" buttonlabelsubcopy="Sub copy of button" buttonbodycopy="button body copy" screenreaderonlytext="screen reader only text" additionalbuttondata="[object Object]" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" buttonlabel="Label of button" buttonlabelsubcopy="Sub copy of button" buttonbodycopy="button body copy" screenreaderonlytext="screen reader only text" additionalbuttondata="[object Object]" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
||||||
</transition-stub>
|
</transition-stub>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Shop list button should render correctly with required props 1`] = `
|
exports[`Shop list button should render correctly with required props 1`] = `
|
||||||
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true">
|
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true">
|
||||||
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" alttext="" iswide="false" value="1234"></base-input-button-stub>
|
||||||
</transition-stub>
|
</transition-stub>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,16 +57,12 @@ export default {
|
||||||
return retCms;
|
return retCms;
|
||||||
},
|
},
|
||||||
answersToDisplay() {
|
answersToDisplay() {
|
||||||
const shouldShowMobile = this.isServiceableMobile && this.isItacOrNoComp;
|
const shouldShowMobile = this.isServiceableMobile;
|
||||||
const shouldShowMobileNotITACNotNoComp = this.isServiceableMobile && !this.isItacOrNoComp;
|
|
||||||
const shouldShowInshop = this.isServiceableInshop;
|
const shouldShowInshop = this.isServiceableInshop;
|
||||||
const shouldShowDropoff = this.isServiceableInshop && !useMainStore().damage.isRepair;
|
|
||||||
return this.answersFromCms
|
return this.answersFromCms
|
||||||
? this.answersFromCms.filter((answer) => (
|
? this.answersFromCms.filter((answer) => (
|
||||||
(answer.Name === AppointmentTypeStrings.IN_SHOP && shouldShowInshop)
|
(answer.Name === AppointmentTypeStrings.IN_SHOP && shouldShowInshop)
|
||||||
|| (answer.Name === AppointmentTypeStrings.MOBILE && shouldShowMobile)
|
|| (answer.Name === AppointmentTypeStrings.MOBILE && shouldShowMobile)
|
||||||
|| (answer.Name === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP && shouldShowMobileNotITACNotNoComp)
|
|
||||||
|| (answer.Name === AppointmentTypeStrings.DROP_OFF && shouldShowDropoff)
|
|
||||||
))
|
))
|
||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
|
|
@ -97,14 +93,9 @@ export default {
|
||||||
// If there is only one option to display and that option is 'Mobile' then select it
|
// If there is only one option to display and that option is 'Mobile' then select it
|
||||||
if (
|
if (
|
||||||
newValue.length === 1
|
newValue.length === 1
|
||||||
&& newValue.findIndex((answer) => (answer.Name === AppointmentTypeStrings.MOBILE
|
&& newValue.findIndex((answer) => (answer.Name === AppointmentTypeStrings.MOBILE)) !== -1
|
||||||
|| answer.Name === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) !== -1
|
|
||||||
) {
|
) {
|
||||||
if (this.isItacOrNoComp) {
|
this.selectedValues = AppointmentTypeStrings.MOBILE;
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE;
|
|
||||||
} else {
|
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
immediate: true
|
immediate: true
|
||||||
|
|
@ -112,11 +103,7 @@ export default {
|
||||||
isMobileOnly: {
|
isMobileOnly: {
|
||||||
handler(newValue) {
|
handler(newValue) {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
if (this.isItacOrNoComp) {
|
this.selectedValues = AppointmentTypeStrings.MOBILE;
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE;
|
|
||||||
} else {
|
|
||||||
this.selectedValues = AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,26 +62,39 @@
|
||||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||||
validationRules="option-required" />
|
validationRules="option-required" />
|
||||||
</div>
|
</div>
|
||||||
<mobileLocationModalQuestions
|
|
||||||
v-if="isMobile"
|
|
||||||
ref="mobileLocationQuestions"
|
|
||||||
v-model="mobileLocationQuestions"
|
|
||||||
customComponentId="mobileLocationQuestions"
|
|
||||||
:mobileFeePart="mobileFeePart"
|
|
||||||
validationRules="mobile-location-required"
|
|
||||||
linkWidgetName="MobileLocationLinkWidget"
|
|
||||||
modalWidgetName="MobileLocationModalWidget"
|
|
||||||
@updated-mobile-fee-part="setMobileFeePart"
|
|
||||||
@updated-serviceability="setServiceabilityDetails"
|
|
||||||
@updated-contains-military-base="setContainsMilitaryBase"
|
|
||||||
@updated-mobile-ctu="setCtuForMobile" />
|
|
||||||
<shopAddress
|
<shopAddress
|
||||||
|
v-if="isInshop"
|
||||||
v-model="selectedProvider"
|
v-model="selectedProvider"
|
||||||
:serviceZipcode="zipCode"
|
:serviceZipcode="zipCode"
|
||||||
:isDisplayed="isInshop"
|
:isDisplayed="isInshop"
|
||||||
:selectedAppointmentType="selectedAppointmentType"
|
:selectedAppointmentType="selectedAppointmentType"
|
||||||
modalWidgetName="ChangeLocationModalWidget"
|
modalWidgetName="ChangeLocationModalWidget"
|
||||||
cmsWidgetName="ShopAddressWidget" />
|
cmsWidgetName="ShopAddressWidget"
|
||||||
|
@zip-updated="handleInShopZipUpdated" />
|
||||||
|
<div
|
||||||
|
v-if="isMobile">
|
||||||
|
<serviceZipQuestion
|
||||||
|
ref="mobileServiceZipCodeQuestion"
|
||||||
|
v-model="mobileZipCode"
|
||||||
|
customInputId="mobileServiceZipCode"
|
||||||
|
class="mobile-service-zip-question"
|
||||||
|
:placeholderText="mobileZipPlaceholder"
|
||||||
|
:serviceZipFormatErrorMessage="errorMessages.MOBILE_SERVICE_ZIP_FORMAT"
|
||||||
|
:hasError="mobileZipError !== ''"
|
||||||
|
cmsWidgetName="MobileZipWidget" />
|
||||||
|
<div
|
||||||
|
v-if="mobileZipError"
|
||||||
|
ref="errorMessageDiv"
|
||||||
|
class="row my-1 form-test-error">
|
||||||
|
<span
|
||||||
|
class="d-inline-flex mt-0"
|
||||||
|
role="alert">{{ mobileZipError }}</span>
|
||||||
|
</div>
|
||||||
|
<textBlock
|
||||||
|
v-if="displayMobileFeeDisclaimer"
|
||||||
|
cmsWidgetName="MobileFeeDisclaimerWidget"
|
||||||
|
typeStyle="disclaimer" />
|
||||||
|
</div>
|
||||||
<contentGroupModal
|
<contentGroupModal
|
||||||
:ref="RECAL_MODAL_REF_NAME"
|
:ref="RECAL_MODAL_REF_NAME"
|
||||||
cssModalHeadlineClass="text-center"
|
cssModalHeadlineClass="text-center"
|
||||||
|
|
@ -111,35 +124,23 @@ import { useMainStore } from '@/store';
|
||||||
import {
|
import {
|
||||||
getPricedMobileFeePart,
|
getPricedMobileFeePart,
|
||||||
getServiceabilityDetails,
|
getServiceabilityDetails,
|
||||||
getZipCodeData
|
getZipCodeData,
|
||||||
|
getMobileZipCodeData
|
||||||
} from '@/helpers/service-location-helper';
|
} from '@/helpers/service-location-helper';
|
||||||
|
|
||||||
// Import Component
|
// Import Component
|
||||||
import alert from '@/ux-components/alert/alert.vue';
|
import alert from '@/ux-components/alert/alert.vue';
|
||||||
|
import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||||
import appointmentTypeQuestion from '@/layouts/service-location/appointment-type-question/appointment-type-question.vue';
|
import appointmentTypeQuestion from '@/layouts/service-location/appointment-type-question/appointment-type-question.vue';
|
||||||
import baseFormMixin from '@/mixins/base-form-mixin';
|
import baseFormMixin from '@/mixins/base-form-mixin';
|
||||||
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
|
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
|
||||||
import mobileLocationModalQuestions from '@/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue';
|
|
||||||
import { Form, defineRule } from 'vee-validate';
|
import { Form, defineRule } from 'vee-validate';
|
||||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
import shopAddress from './shop-address/shop-address.vue';
|
import shopAddress from '@/layouts/service-location/shop-address/shop-address.vue';
|
||||||
|
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
|
||||||
// DEFINE VALIDATION RULES
|
import widgetFields from '@/constants/cms-widget-fields';
|
||||||
defineRule('mobile-location-required', (value) => {
|
|
||||||
if (
|
|
||||||
value.addressQuestions.streetAddress === ''
|
|
||||||
|| value.addressQuestions.city === ''
|
|
||||||
|| value.addressQuestions.state === ''
|
|
||||||
|| value.addressQuestions.zipCode === ''
|
|
||||||
|| value.isVehicleProtected == null
|
|
||||||
) {
|
|
||||||
return errorMessages.MOBILE_LOCATION_REQUIRED;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
defineRule('selection-required', required(errorMessages.OPTION_REQUIRED));
|
|
||||||
|
|
||||||
const RECAL_MODAL_REF_NAME = 'RecalModal';
|
const RECAL_MODAL_REF_NAME = 'RecalModal';
|
||||||
const SITE_FOOTER_REF_NAME = 'siteFooter';
|
const SITE_FOOTER_REF_NAME = 'siteFooter';
|
||||||
|
|
@ -148,15 +149,16 @@ export default {
|
||||||
name: 'service-location',
|
name: 'service-location',
|
||||||
components: {
|
components: {
|
||||||
alert,
|
alert,
|
||||||
|
textBlock,
|
||||||
appointmentTypeQuestion,
|
appointmentTypeQuestion,
|
||||||
contentGroupModal,
|
contentGroupModal,
|
||||||
mobileLocationModalQuestions,
|
|
||||||
siteFooter,
|
siteFooter,
|
||||||
siteHeader,
|
siteHeader,
|
||||||
siteSubHeader,
|
siteSubHeader,
|
||||||
// eslint-disable-next-line vue/no-reserved-component-names
|
// eslint-disable-next-line vue/no-reserved-component-names
|
||||||
Form,
|
Form,
|
||||||
shopAddress
|
shopAddress,
|
||||||
|
serviceZipQuestion
|
||||||
},
|
},
|
||||||
mixins: [baseFormMixin],
|
mixins: [baseFormMixin],
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -232,8 +234,12 @@ export default {
|
||||||
mobileProviderNumber: null,
|
mobileProviderNumber: null,
|
||||||
zipContainsMilitaryBase: false,
|
zipContainsMilitaryBase: false,
|
||||||
zipCodeCtu: null,
|
zipCodeCtu: null,
|
||||||
|
mobileZipCode: '',
|
||||||
|
mobileZipError: '',
|
||||||
|
showMobileOption: false,
|
||||||
RECAL_MODAL_REF_NAME,
|
RECAL_MODAL_REF_NAME,
|
||||||
SITE_FOOTER_REF_NAME
|
SITE_FOOTER_REF_NAME,
|
||||||
|
errorMessages
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -246,35 +252,6 @@ export default {
|
||||||
answersFromCms() {
|
answersFromCms() {
|
||||||
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
|
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
|
||||||
},
|
},
|
||||||
mobileLocationQuestions: {
|
|
||||||
get() {
|
|
||||||
return {
|
|
||||||
addressQuestions: {
|
|
||||||
streetAddress: this.streetAddress,
|
|
||||||
streetAddress2: this.streetAddress2,
|
|
||||||
city: this.city,
|
|
||||||
state: this.state,
|
|
||||||
zipCode: this.zipCode
|
|
||||||
},
|
|
||||||
isVehicleProtected: this.isVehicleProtected
|
|
||||||
};
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
this.streetAddress = newValue.addressQuestions.streetAddress;
|
|
||||||
this.streetAddress2 = newValue.addressQuestions.streetAddress2;
|
|
||||||
this.city = newValue.addressQuestions.city;
|
|
||||||
this.state = newValue.addressQuestions.state;
|
|
||||||
this.zipCode = newValue.addressQuestions.zipCode;
|
|
||||||
this.isVehicleProtected = newValue.isVehicleProtected;
|
|
||||||
|
|
||||||
if (newValue.zipCode !== this.zipCode) {
|
|
||||||
if (!this.isMobile) {
|
|
||||||
this.selectedAppointmentType = null;
|
|
||||||
}
|
|
||||||
this.selectedProvider = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isServiceableMobile() {
|
isServiceableMobile() {
|
||||||
if (this.isRecalibrationServiceableMobile !== null) {
|
if (this.isRecalibrationServiceableMobile !== null) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -301,15 +278,10 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isAppointmentTypeDisplayed() {
|
isAppointmentTypeDisplayed() {
|
||||||
return this.zipCode && !this.displayNoShopsAlert;
|
return this.zipCode && !this.displayNoShopsAlert && this.showMobileOption;
|
||||||
},
|
},
|
||||||
isMobile() {
|
isMobile() {
|
||||||
return (
|
return this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|
||||||
this.selectedAppointmentType
|
|
||||||
=== AppointmentTypeStrings.MOBILE
|
|
||||||
|| this.selectedAppointmentType
|
|
||||||
=== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
requiresInshopRecalibration() {
|
requiresInshopRecalibration() {
|
||||||
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
|
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
|
||||||
|
|
@ -341,6 +313,9 @@ export default {
|
||||||
displayServiceableMobileOnly() {
|
displayServiceableMobileOnly() {
|
||||||
return this.isServiceableMobile && !this.isServiceableInshop;
|
return this.isServiceableMobile && !this.isServiceableInshop;
|
||||||
},
|
},
|
||||||
|
displayMobileFeeDisclaimer() {
|
||||||
|
return this.mainStore.isNoComp || this.mainStore.isITAC;
|
||||||
|
},
|
||||||
navigateBackScenario() {
|
navigateBackScenario() {
|
||||||
const { isNoComp, isITAC } = useMainStore();
|
const { isNoComp, isITAC } = useMainStore();
|
||||||
return isNoComp || isITAC
|
return isNoComp || isITAC
|
||||||
|
|
@ -349,7 +324,10 @@ export default {
|
||||||
},
|
},
|
||||||
isBigTruck() {
|
isBigTruck() {
|
||||||
return this.mainStore.order.vehicle.isBigTruck;
|
return this.mainStore.order.vehicle.isBigTruck;
|
||||||
}
|
},
|
||||||
|
mobileZipPlaceholder() {
|
||||||
|
return this.getCmsContent('MobileZipPlaceHolderWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
|
|
@ -445,6 +423,7 @@ export default {
|
||||||
|
|
||||||
if (serviceabilityDetails) {
|
if (serviceabilityDetails) {
|
||||||
this.setServiceabilityDetails(serviceabilityDetails);
|
this.setServiceabilityDetails(serviceabilityDetails);
|
||||||
|
this.showMobileOption = this.isServiceableMobile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mobileFeePart) {
|
if (mobileFeePart) {
|
||||||
|
|
@ -503,6 +482,65 @@ export default {
|
||||||
serviceabilityDetails.isGlassServiceableMobile;
|
serviceabilityDetails.isGlassServiceableMobile;
|
||||||
this.isRecalibrationServiceableMobile =
|
this.isRecalibrationServiceableMobile =
|
||||||
serviceabilityDetails.isRecalibrationServiceableMobile;
|
serviceabilityDetails.isRecalibrationServiceableMobile;
|
||||||
|
},
|
||||||
|
async updateMobileZip() {
|
||||||
|
this.mobileZipError = '';
|
||||||
|
|
||||||
|
const zipCodeData = await getMobileZipCodeData(this.mobileZipCode);
|
||||||
|
|
||||||
|
if (!zipCodeData.isValid) {
|
||||||
|
this.mobileZipError = errorMessages.INVALID_ZIP;
|
||||||
|
} else if (!zipCodeData.isServiceable) {
|
||||||
|
this.mobileZipError = errorMessages.NO_SERVICE_IN_ZIP(this.mobileZipCode);
|
||||||
|
} else {
|
||||||
|
this.zipCode = this.mobileZipCode;
|
||||||
|
this.setCtuForMobile(zipCodeData.zipCodeCtu);
|
||||||
|
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
|
||||||
|
|
||||||
|
const serviceabilityDetailsPromise = getServiceabilityDetails(this.mobileZipCode);
|
||||||
|
serviceabilityDetailsPromise.then((result) => {
|
||||||
|
const details = result.data;
|
||||||
|
if (details) {
|
||||||
|
this.setServiceabilityDetails(details);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const providersPromise = useMainStore().getProviders(this.mobileZipCode);
|
||||||
|
providersPromise.then((result) => {
|
||||||
|
const providers = result.data;
|
||||||
|
if (providers) {
|
||||||
|
this.setMobileProviderNumber(providers.mobileProviderNumber);
|
||||||
|
if(providers.shopProviders.length > 0) {
|
||||||
|
this.selectedProvider = providers.shopProviders[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleInShopZipUpdated(newZip) {
|
||||||
|
this.zipCode = newZip;
|
||||||
|
const zipCodeData = await getZipCodeData(this.zipCode);
|
||||||
|
this.setCtuForMobile(zipCodeData.zipCodeCtu);
|
||||||
|
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
|
||||||
|
|
||||||
|
const serviceabilityDetailsPromise = getServiceabilityDetails(this.zipCode);
|
||||||
|
serviceabilityDetailsPromise.then((result) => {
|
||||||
|
const details = result.data;
|
||||||
|
if (details) {
|
||||||
|
this.setServiceabilityDetails(details);
|
||||||
|
this.showMobileOption = this.isServiceableMobile;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
mobileZipCode(newZip) {
|
||||||
|
if(newZip !== '') {
|
||||||
|
this.updateMobileZip();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedAppointmentType() {
|
||||||
|
this.mobileZipCode = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -510,7 +548,6 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$page-side-padding: 1.5rem;
|
$page-side-padding: 1.5rem;
|
||||||
|
|
||||||
.iss-heritage-container-width {
|
.iss-heritage-container-width {
|
||||||
.service-location-container {
|
.service-location-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -524,42 +561,12 @@ $page-side-padding: 1.5rem;
|
||||||
.subheader {
|
.subheader {
|
||||||
margin: 1.25rem 0;
|
margin: 1.25rem 0;
|
||||||
}
|
}
|
||||||
}
|
.form-test-error {
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
.question-text {
|
|
||||||
span {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
}
|
.mobile-service-zip-question {
|
||||||
.list-card-content p {
|
:deep(.form-test-error span) {
|
||||||
&:first-of-type {
|
font-weight: $font-weight-bold;
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
&:not(:nth-of-type(1)) {
|
|
||||||
line-height: 1.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.choose-option {
|
|
||||||
.button-question > div {
|
|
||||||
&:first-of-type {
|
|
||||||
margin-bottom: 0.95rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button-question {
|
|
||||||
.row.form-test-error {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
padding-left: 0rem !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.service-location-button-question {
|
|
||||||
.question-text {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
|
|
||||||
span {
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
|
||||||
|
|
||||||
describe('service-zip-question.vue', () => {
|
describe('service-zip-question.vue', () => {
|
||||||
it('Should get the modelValue', async () => {
|
it('Should get the modelValue', async () => {
|
||||||
|
|
@ -12,8 +13,8 @@ describe('service-zip-question.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const modelValueText = wrapper.vm.value;
|
const modelValueText = wrapper.vm.internalZipcode;
|
||||||
wrapper.vm.value = 'test also';
|
wrapper.vm.internalZipcode = 'test also';
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(modelValueText).toEqual('test');
|
expect(modelValueText).toEqual('test');
|
||||||
|
|
@ -30,7 +31,8 @@ describe('service-zip-question.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.value = 'test also';
|
wrapper.vm.internalZipcode = 'test also';
|
||||||
|
wrapper.vm.updateModelValue();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.emitted('update:modelValue')).toEqual([['test also']]);
|
expect(wrapper.emitted('update:modelValue')).toEqual([['test also']]);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
cornerStyle="rounded"
|
cornerStyle="rounded"
|
||||||
mask="#####"
|
mask="#####"
|
||||||
isRequired
|
isRequired
|
||||||
|
:hasError="hasError"
|
||||||
@clickEvent="updateModelValue"
|
@clickEvent="updateModelValue"
|
||||||
validationRules="zip-required|zip-format" />
|
validationRules="zip-required|zip-format" />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -21,7 +22,6 @@ import errorMessages from '@/constants/error-messages';
|
||||||
|
|
||||||
// Define Validation Rules
|
// Define Validation Rules
|
||||||
defineRule('zip-required', required(errorMessages.SERVICE_ZIP_REQUIRED));
|
defineRule('zip-required', required(errorMessages.SERVICE_ZIP_REQUIRED));
|
||||||
defineRule('zip-format', regex(/^\d{5}$/, errorMessages.SERVICE_ZIP_FORMAT));
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'service-zip-question',
|
name: 'service-zip-question',
|
||||||
|
|
@ -31,7 +31,15 @@ export default {
|
||||||
// TODO: Correct prop def.
|
// TODO: Correct prop def.
|
||||||
props: {
|
props: {
|
||||||
modelValue: String,
|
modelValue: String,
|
||||||
cmsWidgetName: String
|
cmsWidgetName: String,
|
||||||
|
serviceZipFormatErrorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: errorMessages.SERVICE_ZIP_FORMAT
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -42,16 +50,20 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
updateModelValue() {
|
updateModelValue() {
|
||||||
this.$emit('update:modelValue', this.internalZipcode);
|
this.$emit('update:modelValue', this.internalZipcode);
|
||||||
this.internalZipcode = '';
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
defineRule('zip-format', regex(/^\d{5}$/, this.serviceZipFormatErrorMessage));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style lang="scss" scoped>
|
||||||
.textbox-question {
|
.textbox-question.has-success {
|
||||||
:deep(.form-label) {
|
:deep(input.form-control) {
|
||||||
font-weight: $font-weight-normal;
|
border: $border-input;
|
||||||
color: $darker-gray;
|
&:focus {
|
||||||
|
border: $border-input-focus;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,6 @@
|
||||||
v-if="isDisplayed"
|
v-if="isDisplayed"
|
||||||
class="shop-address"
|
class="shop-address"
|
||||||
aria-live="polite">
|
aria-live="polite">
|
||||||
<alert
|
|
||||||
v-if="displayDropoffInformation"
|
|
||||||
ref="alertDropoffInformation"
|
|
||||||
class="mb-4 drop-off-alert"
|
|
||||||
cmsWidgetName="AlertDropoffInformationWidget"
|
|
||||||
alertClass="alert-info"
|
|
||||||
:isDismissible="false" />
|
|
||||||
<div class="address-header">{{ questionText }}</div>
|
<div class="address-header">{{ questionText }}</div>
|
||||||
<div class="current-address">
|
<div class="current-address">
|
||||||
<div class="address-line">
|
<div class="address-line">
|
||||||
|
|
@ -32,9 +25,9 @@
|
||||||
:ref="modalName"
|
:ref="modalName"
|
||||||
:headerText="modalHeaderText"
|
:headerText="modalHeaderText"
|
||||||
:onModalOpenedCallback="onModalOpened"
|
:onModalOpenedCallback="onModalOpened"
|
||||||
:onModalClosedCallback="onModalClosed"
|
|
||||||
:footerButtonText="modalFooterText"
|
:footerButtonText="modalFooterText"
|
||||||
:modalPosition="modalPositions.center"
|
:modalPosition="modalPositions.center"
|
||||||
|
:allowInvalidSubmit="true"
|
||||||
@footerButtonEvent="updateSelectedProvider">
|
@footerButtonEvent="updateSelectedProvider">
|
||||||
<googleMap
|
<googleMap
|
||||||
id="map"
|
id="map"
|
||||||
|
|
@ -49,7 +42,7 @@
|
||||||
:variant="dropdownVariants.compact"
|
:variant="dropdownVariants.compact"
|
||||||
:options="searchRadiusOptions" />
|
:options="searchRadiusOptions" />
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
ref="buttonQuestion"
|
ref="shopListButtonQuestion"
|
||||||
v-model="internalProviderNumber"
|
v-model="internalProviderNumber"
|
||||||
buttonTypeString="shopListButton"
|
buttonTypeString="shopListButton"
|
||||||
:buttonTypeObject="shopListButton"
|
:buttonTypeObject="shopListButton"
|
||||||
|
|
@ -65,17 +58,18 @@
|
||||||
</span>
|
</span>
|
||||||
<serviceZipQuestion
|
<serviceZipQuestion
|
||||||
v-if="renderServiceZip"
|
v-if="renderServiceZip"
|
||||||
ref="serviceZipCodeQuestion"
|
:ref="SERVICE_ZIP_QUESTION_REF_NAME"
|
||||||
v-model="internalZipcode"
|
v-model="internalZipcode"
|
||||||
customInputId="serviceZipCode"
|
customInputId="serviceZipCode"
|
||||||
:cmsWidgetName="textboxQuestionWidgetName" />
|
:cmsWidgetName="textboxQuestionWidgetName" />
|
||||||
<alert
|
<div
|
||||||
v-if="displayInvalidZipAlert"
|
v-if="errorMessage"
|
||||||
ref="alertInvalidZip"
|
ref="errorMessageDiv"
|
||||||
class="my-4"
|
class="row my-1 form-test-error">
|
||||||
:cmsWidgetName="alertInvalidZipWidgetName"
|
<span
|
||||||
alertClass="alert-danger"
|
class="d-inline-flex mt-0"
|
||||||
:isDismissible="false" />
|
role="alert">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
</modal>
|
</modal>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
@ -96,11 +90,17 @@ import googleMap from '@/iss-components/google-map/google-map.vue';
|
||||||
import baseMixin from '@/mixins/base-mixin.js';
|
import baseMixin from '@/mixins/base-mixin.js';
|
||||||
import { toTitleCase } from '@/helpers/text-helper.js';
|
import { toTitleCase } from '@/helpers/text-helper.js';
|
||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import { getServiceabilityDetails, getZipCodeData,getAvailabilityRating } from '@/helpers/service-location-helper';
|
import { getAvailabilityRating } from '@/helpers/service-location-helper';
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
import widgetFields from '@/constants/cms-widget-fields';
|
import widgetFields from '@/constants/cms-widget-fields';
|
||||||
import { dropdownVariants, modalPositions } from '@/constants/component-variants';
|
import { dropdownVariants, modalPositions } from '@/constants/component-variants';
|
||||||
|
import errorMessages from '@/constants/error-messages';
|
||||||
|
import { defineRule } from 'vee-validate';
|
||||||
|
import { required } from '@/helpers/validation-rules';
|
||||||
|
|
||||||
|
defineRule('option-required', required(errorMessages.PROVIDER_REQUIRED));
|
||||||
|
|
||||||
|
const SERVICE_ZIP_QUESTION_REF_NAME = 'serviceZipCodeQuestion';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'shop-address',
|
name: 'shop-address',
|
||||||
|
|
@ -130,21 +130,21 @@ export default {
|
||||||
return {
|
return {
|
||||||
toTitleCase,
|
toTitleCase,
|
||||||
serviceZipCodeTextInputId: '',
|
serviceZipCodeTextInputId: '',
|
||||||
displayInvalidZipAlert: false,
|
|
||||||
internalZipcode: this.serviceZipcode,
|
internalZipcode: this.serviceZipcode,
|
||||||
internalProviderNumber: this.modelValue.providerNumber,
|
internalProviderNumber: this.modelValue.providerNumber,
|
||||||
searchRadiusQuestionWidgetName: 'SearchRadiusQuestionWidget',
|
searchRadiusQuestionWidgetName: 'SearchRadiusQuestionWidget',
|
||||||
textboxQuestionWidgetName: 'ChangeLocationZipQuestionWidget',
|
textboxQuestionWidgetName: 'ChangeLocationZipQuestionWidget',
|
||||||
alertInvalidZipWidgetName: 'AlertInvalidZipWidget',
|
|
||||||
shopListButton: markRaw(shopListButton),
|
shopListButton: markRaw(shopListButton),
|
||||||
searchRadiusInMiles: 100,
|
searchRadiusInMiles: "25",
|
||||||
nearbyShops: [],
|
nearbyShops: [],
|
||||||
modalPositions,
|
modalPositions,
|
||||||
dropdownVariants,
|
dropdownVariants,
|
||||||
renderServiceZip: true
|
renderServiceZip: true,
|
||||||
|
errorMessage: '',
|
||||||
|
SERVICE_ZIP_QUESTION_REF_NAME
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue', 'zip-updated'],
|
||||||
computed: {
|
computed: {
|
||||||
questionText() {
|
questionText() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
|
return this.getCmsContent(this.cmsWidgetName, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
|
||||||
|
|
@ -199,12 +199,17 @@ export default {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
searchRadiusOptions() {
|
searchRadiusArray() {
|
||||||
const options = this.getCmsContent(this.searchRadiusQuestionWidgetName, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS);
|
const options = this.getCmsContent(this.searchRadiusQuestionWidgetName, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS);
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
searchRadiusOptions() {
|
||||||
const optionsObj = {};
|
const optionsObj = {};
|
||||||
options.forEach(option => {
|
if(this.searchRadiusArray) {
|
||||||
optionsObj[option.Name] = option.Text;
|
this.searchRadiusArray.forEach(option => {
|
||||||
});
|
optionsObj[option.Name] = option.Text;
|
||||||
|
});
|
||||||
|
}
|
||||||
return optionsObj;
|
return optionsObj;
|
||||||
},
|
},
|
||||||
providerAddresses() {
|
providerAddresses() {
|
||||||
|
|
@ -217,19 +222,30 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
async internalZipcode() {
|
async internalZipcode() {
|
||||||
await this.resetsOnZipInput();
|
const shopsUpdated = await this.updateShops();
|
||||||
|
if (shopsUpdated) {
|
||||||
|
this.$refs[SERVICE_ZIP_QUESTION_REF_NAME].internalZipcode = '';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async searchRadiusInMiles() {
|
async searchRadiusInMiles() {
|
||||||
this.nearbyShops = await this.getNearbyShops(this.searchRadiusInMiles);
|
await this.updateShops();
|
||||||
|
},
|
||||||
|
nearbyShops(newShops) {
|
||||||
|
if (!newShops.find(shop => shop.providerNumber === this.internalProviderNumber)) {
|
||||||
|
this.internalProviderNumber = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resetAlerts() {
|
async updateShops() {
|
||||||
this.displayInvalidZipAlert = false;
|
this.errorMessage = '';
|
||||||
},
|
const result = await this.getNearbyShops(this.searchRadiusInMiles);
|
||||||
async resetsOnZipInput() {
|
if (result.length === 0) {
|
||||||
this.resetAlerts();
|
this.errorMessage = errorMessages.ZIP_CODE_NOT_SERVICED_FOR_VEHICLE;
|
||||||
this.nearbyShops = await this.getNearbyShops(this.searchRadiusInMiles);
|
}
|
||||||
|
else {
|
||||||
|
this.nearbyShops = await this.getNearbyShops(this.searchRadiusInMiles);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
openModal() {
|
openModal() {
|
||||||
this.$refs[this.modalName].openModal();
|
this.$refs[this.modalName].openModal();
|
||||||
|
|
@ -239,26 +255,19 @@ export default {
|
||||||
},
|
},
|
||||||
onModalOpened() {
|
onModalOpened() {
|
||||||
this.internalZipcode = this.serviceZipcode;
|
this.internalZipcode = this.serviceZipcode;
|
||||||
this.searchRadiusInMiles = 25;
|
this.searchRadiusInMiles = "25";
|
||||||
this.resetsOnZipInput().then(() => {
|
this.updateShops().then(() => {
|
||||||
this.internalProviderNumber = this.nearbyShops[0]?.providerNumber;
|
this.internalProviderNumber = this.nearbyShops[0]?.providerNumber;
|
||||||
});
|
});
|
||||||
this.forceServiceZipRerender();
|
this.forceServiceZipRerender();
|
||||||
},
|
},
|
||||||
onModalClosed() {
|
|
||||||
this.internalZipcode = this.serviceZipcode;
|
|
||||||
this.resetsOnZipInput().then(() => {
|
|
||||||
this.internalProviderNumber = this.nearbyShops[0]?.providerNumber;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async updateSelectedProvider() {
|
async updateSelectedProvider() {
|
||||||
if (this.internalProviderNumber !== this.modelValue.providerNumber) {
|
const selectedShop = this.nearbyShops.find(shop => shop.providerNumber === this.internalProviderNumber);
|
||||||
const selectedShop = this.nearbyShops.find(shop => shop.providerNumber === this.internalProviderNumber);
|
if (selectedShop) {
|
||||||
if (selectedShop) {
|
this.$emit('update:modelValue', selectedShop);
|
||||||
this.$emit('update:modelValue', selectedShop);
|
this.$emit('zip-updated', this.internalZipcode);
|
||||||
}
|
this.closeModal();
|
||||||
}
|
}
|
||||||
this.closeModal();
|
|
||||||
},
|
},
|
||||||
async getNearbyShops(radiusInMiles) {
|
async getNearbyShops(radiusInMiles) {
|
||||||
const result = await useMainStore().getProviders(this.internalZipcode, radiusInMiles);
|
const result = await useMainStore().getProviders(this.internalZipcode, radiusInMiles);
|
||||||
|
|
@ -336,5 +345,22 @@ export default {
|
||||||
:deep(#map) {
|
:deep(#map) {
|
||||||
height: 27rem;
|
height: 27rem;
|
||||||
}
|
}
|
||||||
|
:deep(.textbox-question) {
|
||||||
|
&.has-error {
|
||||||
|
input.form-control {
|
||||||
|
border: $border-input;
|
||||||
|
&:focus {
|
||||||
|
border: $border-input-focus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-label {
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
color: $darker-gray;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,294 +0,0 @@
|
||||||
<template>
|
|
||||||
<transition
|
|
||||||
name="fade"
|
|
||||||
mode="out-in">
|
|
||||||
<div
|
|
||||||
v-if="isDisplayed"
|
|
||||||
class="shop-question"
|
|
||||||
aria-live="polite">
|
|
||||||
<alert
|
|
||||||
v-if="displayDropoffInformation"
|
|
||||||
ref="alertDropoffInformation"
|
|
||||||
class="mb-4 drop-off-alert"
|
|
||||||
cmsWidgetName="AlertDropoffInformationWidget"
|
|
||||||
alertClass="alert-info"
|
|
||||||
:isDismissible="false" />
|
|
||||||
<buttonQuestion
|
|
||||||
ref="buttonQuestion"
|
|
||||||
v-model="selectedProviderNumber"
|
|
||||||
buttonTypeString="shopListButton"
|
|
||||||
:buttonTypeObject="shopListButton"
|
|
||||||
class="radioQuestion"
|
|
||||||
:questionText="questionText"
|
|
||||||
:answers="answers"
|
|
||||||
groupName="chooseShop"
|
|
||||||
textPosition="text-start"
|
|
||||||
isRequired
|
|
||||||
validationRules="option-required"
|
|
||||||
:additionalButtonData="additionalButtonData" />
|
|
||||||
<textLink
|
|
||||||
v-show="displaySeeMoreLocationsLink"
|
|
||||||
id="showMoreShopsId"
|
|
||||||
ref="showMoreShopsLink"
|
|
||||||
class="show-more-shops-link"
|
|
||||||
cmsWidgetName="ShowMoreShopsLinkWidget"
|
|
||||||
linkType="text"
|
|
||||||
:text="showMoreShopsLinkText"
|
|
||||||
href="#!"
|
|
||||||
:aria-label="showMoreShopsLinkText"
|
|
||||||
@clickEvent="getNextShopsFromList" />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Components
|
|
||||||
import alert from '@/ux-components/alert/alert.vue';
|
|
||||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
|
||||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
|
||||||
|
|
||||||
// Supporting files
|
|
||||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
|
|
||||||
import baseMixin from '@/mixins/base-mixin.js';
|
|
||||||
import { defineRule } from 'vee-validate';
|
|
||||||
import { required } from '@/helpers/validation-rules';
|
|
||||||
import errorMessages from '@/constants/error-messages';
|
|
||||||
import { useMainStore } from '@/store/index.js';
|
|
||||||
import { markRaw, nextTick } from 'vue';
|
|
||||||
import { getAvailabilityRating } from '@/helpers/service-location-helper';
|
|
||||||
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
|
|
||||||
|
|
||||||
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'shop-question',
|
|
||||||
components: {
|
|
||||||
alert,
|
|
||||||
buttonQuestion,
|
|
||||||
textLink
|
|
||||||
},
|
|
||||||
mixins: [baseMixin],
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: () => null
|
|
||||||
},
|
|
||||||
selectedAppointmentType: String,
|
|
||||||
cmsWidgetName: String,
|
|
||||||
validationRules: String,
|
|
||||||
isDisplayed: Boolean
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue', 'updatedMobileProviderNumber'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
shopProviders: [],
|
|
||||||
shopListButton: markRaw(shopListButton),
|
|
||||||
answers: [],
|
|
||||||
shopIndex: 0,
|
|
||||||
displaySeeMoreLocationsLink: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
questionText() {
|
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
|
||||||
},
|
|
||||||
selectedValue: {
|
|
||||||
get() {
|
|
||||||
return this.modelValue;
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
this.$emit('update:modelValue', newValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedProviderNumber: {
|
|
||||||
get() {
|
|
||||||
return this.selectedValue?.providerNumber;
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
// Button Question only supports Number, or String data types so we must get the full object to emit
|
|
||||||
this.selectedValue = this.getSelectedProviderObject(newValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
displayDropoffInformation() {
|
|
||||||
return this.selectedAppointmentType === 'Dropoff';
|
|
||||||
},
|
|
||||||
showMoreShopsLinkText() {
|
|
||||||
return this.getCmsContent('ShowMoreShopsLinkWidget', 'Text');
|
|
||||||
},
|
|
||||||
additionalButtonData() {
|
|
||||||
const startDate = new Date();
|
|
||||||
const endDate = new Date();
|
|
||||||
endDate.setDate(startDate.getDate() + 6);
|
|
||||||
|
|
||||||
const formattedStartDate = startDate.toISOString().split('T')[0];
|
|
||||||
const formattedEndDate = endDate.toISOString().split('T')[0];
|
|
||||||
|
|
||||||
return {
|
|
||||||
displayAvailabilityIndicators: true,
|
|
||||||
availabilityRatingCallback: getAvailabilityRating,
|
|
||||||
startDate: formattedStartDate,
|
|
||||||
endDate: formattedEndDate,
|
|
||||||
shopAppointmentType: this.selectedAppointmentType
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selectedAppointmentType: {
|
|
||||||
async handler(newValue) {
|
|
||||||
this.resetAnswers();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
this.selectedProviderNumber = null;
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
if (newValue !== AppointmentTypeStrings.MOBILE && newValue !== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
|
|
||||||
this.getNextShopsFromList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shopProviders: {
|
|
||||||
async handler(newValue) {
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
if (this.selectedAppointmentType) {
|
|
||||||
const selectedShopIndex = this.getSelectedProviderIndex(
|
|
||||||
newValue,
|
|
||||||
this.selectedProviderNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedShopIndex >= 3) {
|
|
||||||
await this.getNextShopsFromList(selectedShopIndex + 1);
|
|
||||||
} else {
|
|
||||||
await this.getNextShopsFromList();
|
|
||||||
await nextTick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadInitialData(serviceZipCode) {
|
|
||||||
return this.loadData(serviceZipCode);
|
|
||||||
},
|
|
||||||
loadData(serviceZipCode) {
|
|
||||||
return useMainStore().getProviders(serviceZipCode);
|
|
||||||
},
|
|
||||||
initializeComponent(shopQuestionInitialData) {
|
|
||||||
this.shopProviders = shopQuestionInitialData.shopProviders;
|
|
||||||
},
|
|
||||||
async getNextShopsFromList(numberToGet = 3) {
|
|
||||||
const shopIterator = (array, n) => {
|
|
||||||
const l = array.length;
|
|
||||||
return () => {
|
|
||||||
const end = this.shopIndex + n;
|
|
||||||
const part = array.slice(this.shopIndex, end);
|
|
||||||
this.shopIndex = end < l ? end : this.shopProviders.length;
|
|
||||||
return part;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const toTitleCase = (str) => str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
|
||||||
|
|
||||||
const nextShop = shopIterator(this.shopProviders, numberToGet);
|
|
||||||
|
|
||||||
// Map API result data
|
|
||||||
const mappedData = nextShop().map((shopProvider) => {
|
|
||||||
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
|
|
||||||
const city = toTitleCase(shopProvider.address.city);
|
|
||||||
const { state } = shopProvider.address;
|
|
||||||
const { zipCode } = shopProvider.address;
|
|
||||||
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
|
|
||||||
|
|
||||||
return {
|
|
||||||
buttonLabel: city,
|
|
||||||
buttonLabelSubCopy: `${distanceInMiles} mi`,
|
|
||||||
buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`,
|
|
||||||
value: shopProvider.providerNumber
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.answers.length === 0) {
|
|
||||||
this.answers = mappedData;
|
|
||||||
} else {
|
|
||||||
mappedData.forEach((shop) => {
|
|
||||||
this.answers.push(shop);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
if (this.shopIndex === this.shopProviders.length) {
|
|
||||||
this.displaySeeMoreLocationsLink = false;
|
|
||||||
} else {
|
|
||||||
this.displaySeeMoreLocationsLink = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
this.scrollToPageBottom();
|
|
||||||
},
|
|
||||||
resetAnswers() {
|
|
||||||
this.answers = [];
|
|
||||||
this.shopIndex = 0;
|
|
||||||
},
|
|
||||||
async reloadShopData(serviceZipCode) {
|
|
||||||
const result = await this.loadData(serviceZipCode);
|
|
||||||
this.initializeComponent(result.data);
|
|
||||||
|
|
||||||
if (result.data?.mobileProviderNumber
|
|
||||||
&& (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|
|
||||||
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) {
|
|
||||||
this.$emit('updatedMobileProviderNumber', result.data.mobileProviderNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetAnswers();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
await this.getNextShopsFromList();
|
|
||||||
},
|
|
||||||
getSelectedProviderObject(providerNumber) {
|
|
||||||
const provider = this.shopProviders?.find((p) => p.providerNumber === providerNumber);
|
|
||||||
return provider;
|
|
||||||
},
|
|
||||||
getSelectedProviderIndex(providers, selectedProviderNumber) {
|
|
||||||
const index = providers.findIndex((p) => p.providerNumber === selectedProviderNumber);
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import "@/styles/ux-variables-svg-strings.scss";
|
|
||||||
|
|
||||||
.shop-question {
|
|
||||||
margin-top: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.button-question {
|
|
||||||
.question-text {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drop-off-alert {
|
|
||||||
background-image: url($svg-drop-off-alert);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 0.75rem;
|
|
||||||
background-position: 0.5rem 0.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 0.5rem 0.5rem 0.5rem 1.5rem !important;
|
|
||||||
gap: 0.25rem;
|
|
||||||
|
|
||||||
.alert-heading {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1798,7 +1798,6 @@ export const useMainStore = defineStore({
|
||||||
this.order.vehicle.registration.lastName = registrationInfo?.lastName;
|
this.order.vehicle.registration.lastName = registrationInfo?.lastName;
|
||||||
},
|
},
|
||||||
updateServiceLocation(serviceLocationInfo) {
|
updateServiceLocation(serviceLocationInfo) {
|
||||||
console.log('Updating service location info:', serviceLocationInfo);
|
|
||||||
this.order.serviceLocation.address = serviceLocationInfo.address;
|
this.order.serviceLocation.address = serviceLocationInfo.address;
|
||||||
this.order.serviceLocation.address2 = serviceLocationInfo.address2;
|
this.order.serviceLocation.address2 = serviceLocationInfo.address2;
|
||||||
this.order.serviceLocation.city = serviceLocationInfo.city;
|
this.order.serviceLocation.city = serviceLocationInfo.city;
|
||||||
|
|
@ -2708,7 +2707,6 @@ export const useMainStore = defineStore({
|
||||||
},
|
},
|
||||||
|
|
||||||
saveServiceLocation(serviceLocationInfo) {
|
saveServiceLocation(serviceLocationInfo) {
|
||||||
console.log('Saving service location info:', serviceLocationInfo);
|
|
||||||
if (this.order.serviceLocation) {
|
if (this.order.serviceLocation) {
|
||||||
if (
|
if (
|
||||||
serviceLocationInfo.zipCode !== this.order.serviceLocation.zipCode
|
serviceLocationInfo.zipCode !== this.order.serviceLocation.zipCode
|
||||||
|
|
@ -2719,7 +2717,6 @@ export const useMainStore = defineStore({
|
||||||
|| serviceLocationInfo.appointmentType
|
|| serviceLocationInfo.appointmentType
|
||||||
!== this.order.serviceLocation.appointmentType
|
!== this.order.serviceLocation.appointmentType
|
||||||
) {
|
) {
|
||||||
console.log('Service location info has changed, resetting dependent state.');
|
|
||||||
this.resetSchedule();
|
this.resetSchedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,21 +115,3 @@ caption,
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Urbanist";
|
|
||||||
src:
|
|
||||||
url("@/assets/fonts/Urbanist-MediumItalic.woff2") format("woff2"),
|
|
||||||
url("@/assets/fonts/Urbanist-MediumItalic.woff") format("woff");
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Urbanist";
|
|
||||||
src:
|
|
||||||
url("@/assets/fonts/Urbanist-BoldItalic.woff2") format("woff2"),
|
|
||||||
url("@/assets/fonts/Urbanist-BoldItalic.woff") format("woff");
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
@ -76,6 +76,7 @@ $heritage-blue-secondary: #167cac;
|
||||||
|
|
||||||
// Darker gray
|
// Darker gray
|
||||||
$darker-gray: #525656;
|
$darker-gray: #525656;
|
||||||
|
$lighter-gray: #9aa1a3;
|
||||||
|
|
||||||
// scss-docs-start colors-map
|
// scss-docs-start colors-map
|
||||||
$colors: (
|
$colors: (
|
||||||
|
|
@ -220,5 +221,5 @@ $enable-important-utilities: false;
|
||||||
// Letter Spacing
|
// Letter Spacing
|
||||||
$letter-spacing-primary: 0.03em;
|
$letter-spacing-primary: 0.03em;
|
||||||
// Borders
|
// Borders
|
||||||
$border-input: 1px solid rgba(179, 180, 181);
|
$border-input: 1px solid $gray-350;
|
||||||
$border-input-focus: 2.5px solid $heritage-blue-primary;
|
$border-input-focus: 2.5px solid $heritage-blue-primary;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue