Add search radius to change location modal + related style updates

This commit is contained in:
Alex Humphries 2026-01-09 16:00:54 -05:00
parent 6eab5c2040
commit bcc156ec29
13 changed files with 113 additions and 47 deletions

View file

@ -1,9 +0,0 @@
const buttonVariants = Object.freeze({
primary: 'primary',
secondary: 'secondary',
success: 'success',
link: 'link',
navigation: 'navigation'
});
export default buttonVariants;

View file

@ -0,0 +1,15 @@
export const buttonVariants = Object.freeze({
primary: 'primary',
secondary: 'secondary',
success: 'success',
link: 'link',
navigation: 'navigation'
});
export const dropdownVariants = Object.freeze({
primary: 'primary',
compact: 'compact'
});
export const modalPositions = Object.freeze({
center: 'center',
edge: 'edge'
});

View file

@ -23,8 +23,8 @@ const errorMessages = Object.freeze({
LAST_NAME_REQUIRED: 'Please enter your last name',
EMAIL_ADDRESS_REQUIRED: 'Email is required.',
EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.',
SERVICE_ZIP_REQUIRED: 'Please enter your service ZIP',
SERVICE_ZIP_FORMAT: 'Please enter a valid service ZIP',
SERVICE_ZIP_REQUIRED: 'ZIP code is required',
SERVICE_ZIP_FORMAT: 'Invalid ZIP, please enter a new ZIP (5 characters)',
VIN_REQUIRED: 'Please enter your VIN',
VIN_FORMAT:
// eslint-disable-next-line max-len

View file

@ -2,8 +2,9 @@
<div
class="dropdown-question"
:class="{
'has-error' : (meta.touched && errors && errors.length && !isDisabled) || hasError,
'has-success': (meta.touched && (!errors || !errors.length) && !isDisabled)
'has-error' : (meta?.touched && errors && errors.length && !isDisabled) || hasError,
'has-success': (meta?.touched && (!errors || !errors.length) && !isDisabled),
[`dropdown-${variant}`]: true
}">
<label
:for="inputId"
@ -34,7 +35,7 @@
</option>
</select>
<div
v-show="meta.touched && errorMessage && !isDisabled"
v-show="meta?.touched && errorMessage && !isDisabled"
class="row mt-1 form-test-error">
<span role="alert">{{ errorMessage }}</span>
</div>
@ -42,6 +43,7 @@
</template>
<script>
import { dropdownVariants } from '@/constants/component-variants';
import { useField } from 'vee-validate';
export default {
@ -59,7 +61,11 @@ export default {
validationRules: String,
cmsWidgetName: String,
hasError: Boolean,
placeHolderText: String
placeHolderText: String,
variant: {
type: String,
default: dropdownVariants.primary
}
},
emits: ['update:modelValue'],
setup(props) {
@ -154,5 +160,30 @@ export default {
filter: grayscale(100%);
}
}
&.dropdown-compact {
display: flex;
align-items: center;
justify-content: end;
margin-bottom: 1.25rem;
.form-label {
color: $darker-gray;
font-weight: $font-weight-light;
margin: 0;
}
.form-select {
color: $heritage-blue-secondary;
background-position-x: 98%;
padding: 0;
padding-left: .3125rem;
width: 5.625rem;
min-height: auto;
border: none;
box-shadow: none;
&:focus,
&:focus-visible {
border: .125rem solid $black;
}
}
}
}
</style>

View file

@ -13,8 +13,8 @@
}">
<div class="modal-dialog"
:class="{
'modal-dialog-edge': modalPosition === 'edge',
'modal-dialog-centered': modalPosition === 'center'
'modal-dialog-edge': modalPosition === modalPositions.edge,
'modal-dialog-centered': modalPosition === modalPositions.center
}">
<div
class="modal-content"
@ -60,6 +60,7 @@
import ButtonMain from '@/ux-components/button-main/button-main.vue';
import { Modal } from 'bootstrap';
import { useForm } from 'vee-validate';
import { modalPositions } from '@/constants/component-variants';
export default {
// eslint-disable-next-line vue/multi-word-component-names
@ -84,7 +85,7 @@ export default {
},
modalPosition: {
type: String,
default: 'edge'
default: modalPositions.edge
}
},
emits: ['footer-button-event', 'isModalOpened'],
@ -102,7 +103,8 @@ export default {
modalId,
meta,
validate,
resetForm
resetForm,
modalPositions
};
},
computed: {

View file

@ -67,7 +67,7 @@
ref="buttonMain"
variant="primary"
:buttonText="buttonText"
@clickEvent="buttonCallback">
@clickEvent="clickedTextButton">
</buttonMain>
</div>
<div
@ -249,6 +249,11 @@ export default {
if (this.meta.valid) {
this.$emit('click-event');
}
},
clickedTextButton() {
if (this.meta.valid) {
this.$emit('click-event');
}
}
}
};
@ -344,6 +349,13 @@ input::-webkit-date-and-time-value {
display: flex;
input[type='text'] {
border-radius: 1.5rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
min-width: 6.875rem;
}
}
}

View file

@ -70,7 +70,7 @@ import coverageType from '@/constants/coverage-type';
import alert from '@/ux-components/alert/alert.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants';
import { buttonVariants } from '@/constants/component-variants';
export default {
name: 'policy-vehicles',

View file

@ -124,7 +124,6 @@ import { Form, defineRule } from 'vee-validate';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import serviceZipModalQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue';
import shopAddress from './shop-address/shop-address.vue';
// DEFINE VALIDATION RULES
@ -157,7 +156,6 @@ export default {
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
serviceZipModalQuestion,
shopAddress
},
mixins: [baseFormMixin],
@ -169,7 +167,6 @@ export default {
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
const getGlassFeesPromise = useMainStore().getGlassFees();
console.log('Fetching providers for zip code:', serviceZipCode);
const providersPromise = useMainStore().getProviders(serviceZipCode);
// Settle promises and get results
@ -417,9 +414,6 @@ export default {
);
},
getServiceZipCodeFromStore() {
console.log('Getting service zip code from store');
console.log(useMainStore().order.serviceLocation.zipCode);
console.log(useMainStore().order.customer.address.zipCode);
return (
useMainStore().order.serviceLocation.zipCode
|| useMainStore().order.customer.address.zipCode

View file

@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
import serviceZipQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue';
describe('service-zip-question.vue', () => {
it('Should get the modelValue', async () => {

View file

@ -7,7 +7,7 @@
cornerStyle="rounded"
mask="#####"
isRequired
:buttonCallback="updateModelValue"
@clickEvent="updateModelValue"
validationRules="zip-required|zip-format" />
</template>

View file

@ -34,8 +34,15 @@
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
:footerButtonText="modalFooterText"
:modalPosition="'center'"
:modalPosition="modalPositions.center"
@footerButtonEvent="updateSelectedProvider">
<dropdownQuestion
inputId="searchRadiusDropdown"
ref="searchRadiusQuestion"
v-model="searchRadiusInMiles"
:cmsWidgetName="searchRadiusQuestionWidgetName"
:variant="dropdownVariants.compact"
:options="searchRadiusOptions" />
<buttonQuestion
ref="buttonQuestion"
v-model="internalProviderNumber"
@ -55,8 +62,7 @@
ref="serviceZipCodeQuestion"
v-model="internalZipcode"
customInputId="serviceZipCode"
:cmsWidgetName="textboxQuestionWidgetName"
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }" />
:cmsWidgetName="textboxQuestionWidgetName" />
<alert
v-if="displayInvalidZipAlert"
ref="alertInvalidZip"
@ -77,6 +83,7 @@ import modal from '@/digital-components/modal/modal.vue';
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
// Supporting files
import baseMixin from '@/mixins/base-mixin.js';
@ -84,6 +91,8 @@ import { toTitleCase } from '@/helpers/text-helper.js';
import { markRaw, nextTick } from 'vue';
import { getServiceabilityDetails, getZipCodeData,getAvailabilityRating } from '@/helpers/service-location-helper';
import { useMainStore } from '@/store';
import widgetFields from '@/constants/cms-widget-fields';
import { dropdownVariants, modalPositions } from '@/constants/component-variants';
export default {
@ -93,7 +102,8 @@ export default {
buttonMain,
modal,
serviceZipQuestion,
buttonQuestion
buttonQuestion,
dropdownQuestion
},
mixins: [baseMixin],
props: {
@ -115,32 +125,35 @@ export default {
displayInvalidZipAlert: false,
internalZipcode: this.serviceZipcode,
internalProviderNumber: this.modelValue.providerNumber,
searchRadiusQuestionWidgetName: 'SearchRadiusQuestionWidget',
textboxQuestionWidgetName: 'ChangeLocationZipQuestionWidget',
alertInvalidZipWidgetName: 'AlertInvalidZipWidget',
shopListButton: markRaw(shopListButton),
searchRadiusInMiles: 25,
nearbyShops: []
searchRadiusInMiles: 100,
nearbyShops: [],
modalPositions,
dropdownVariants
};
},
emits: ['update:modelValue'],
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
return this.getCmsContent(this.cmsWidgetName, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
},
showMoreShopsLinkText() {
return this.getCmsContent('ShowMoreShopsLinkWidget', 'Text');
return this.getCmsContent('ShowMoreShopsLinkWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
displayedShopName() {
if(this.modelValue.isSafeliteShop) {
return this.getCmsContent('SafeliteShopNameWidget', 'Text');
return this.getCmsContent('SafeliteShopNameWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
}
return toTitleCase(this.modelValue.companyName);
},
modalHeaderText() {
return this.getCmsContent(this.modalWidgetName, 'HeaderText').replaceAll('{custom:serviceZipcode}', this.internalZipcode);
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT).replaceAll('{custom:serviceZipcode}', this.internalZipcode);
},
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, 'FooterText');
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.FOOTER_TEXT);
},
modalName() {
return this.modalWidgetName;
@ -162,7 +175,7 @@ export default {
};
},
serviceZipPrompt() {
return this.getCmsContent(this.modalWidgetName, 'BodyText2');
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
},
nearbyShopsData() {
return this.nearbyShops.map((shopProvider) => {
@ -179,11 +192,22 @@ export default {
value: shopProvider.providerNumber
};
});
},
searchRadiusOptions() {
const options = this.getCmsContent(this.searchRadiusQuestionWidgetName, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS);
const optionsObj = {};
options.forEach(option => {
optionsObj[option.Name] = option.Text;
});
return optionsObj;
}
},
watch: {
internalZipcode() {
this.resetsOnZipInput();
this.refreshShops();
},
async searchRadiusInMiles() {
this.nearbyShops = await this.getNearbyShops(this.searchRadiusInMiles);
}
},
methods: {
@ -200,11 +224,9 @@ export default {
closeModal() {
this.$refs[this.modalName].closeModal();
},
onInputIdAssigned(inputId) {
this.serviceZipCodeTextInputId = inputId;
},
onModalOpened() {
this.internalZipcode = this.serviceZipcode;
this.searchRadiusInMiles = 25;
this.resetsOnZipInput().then(() => {
this.internalProviderNumber = this.nearbyShops[0]?.providerNumber;
});

View file

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants';
import { buttonVariants } from '@/constants/component-variants';
import buttonSizes from '@/constants/button-sizes';
/** @ignore */

View file

@ -9,7 +9,7 @@
</template>
<script>
import buttonVariants from '@/constants/button-variants';
import { buttonVariants } from '@/constants/component-variants';
import buttonSizes from '@/constants/button-sizes';
export default {