More alert/error/etc updates

This commit is contained in:
Alex Humphries 2026-01-16 12:20:52 -05:00
parent 253044d502
commit d6762e85a8
6 changed files with 119 additions and 57 deletions

View file

@ -26,7 +26,7 @@ const errorMessages = Object.freeze({
SERVICE_ZIP_REQUIRED: 'ZIP code is required',
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.`,
NO_SERVICE_IN_AREA: (area) => `We're sorry, but we don't offer service in ${area} 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.',

View file

@ -9,7 +9,7 @@
ref="buttonQuestion"
v-model="selectedValues"
customButtonQuestionId="appointmentTypeQuestion"
:answers="answersFromCms"
:answers="answersToDisplay"
:groupName="groupName"
:suppressError="suppressError"
:validationRules="validationRules"
@ -45,8 +45,15 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
answersFromCms() {
const retCms = this.getCmsContent(this.cmsWidgetName, 'Answers');
return retCms;
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
answersToDisplay() {
return this.answersFromCms
? this.answersFromCms.filter((answer) => (
(answer.Name === AppointmentTypeStrings.IN_SHOP)
|| (answer.Name === AppointmentTypeStrings.MOBILE)
))
: [];
},
selectedValues: {
get() {

View file

@ -13,18 +13,6 @@
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="subheader" />
<alert
v-if="displayServiceableMobileOnly"
ref="alertMobileOnly"
class="my-5"
cmsWidgetName="AlertMobileOnlyWidget"
alertClass="alert-warning" />
<alert
v-if="displayServiceableInshopOnly"
ref="alertInshopOnly"
cmsWidgetName="AlertInshopOnlyWidget"
:isCollapsible="true"
alertClass="alert-warning" />
<alert
v-if="displayRecalibrationWarning"
ref="alertRecalNoMobile"
@ -32,13 +20,6 @@
cmsWidgetName="AlertRecalNoMobileWidget"
alertClass="alert-warning"
@text-link-clicked="openModalAction" />
<alert
v-if="displayNoShopsAlert"
ref="alertNoShops"
class="my-5"
:isCollapsible="true"
cmsWidgetName="AlertNoShopsWidget"
alertClass="alert-warning" />
<alert
v-if="displayBigTruckNoShops"
ref="alertBigTruckNoShops"
@ -47,10 +28,25 @@
alertClass="alert-warning" />
<div class="appointment-type">
<div class="appointment-type-question-text d-flex">
<span class="w-100">{{ appointmentQuestionText }}</span>
<span class="w-100 recal-text" v-if="recalibrationRequired">{{ scheduleRecalText }}</span>
<span class="w-100" v-if="!requiresInshopRecalibration">{{ appointmentQuestionText }}</span>
</div>
<alert
v-if="displayServiceableInshopOnly"
ref="alertInshopOnly"
cmsWidgetName="AlertInshopOnlyWidget"
:manualCopy="inShopOnlyCopy"
:isCollapsible="true"
alertClass="alert-warning" />
<alert
v-if="displayServiceableMobileOnly"
ref="alertMobileOnly"
cmsWidgetName="AlertMobileOnlyWidget"
:manualCopy="mobileOnlyCopy"
:isCollapsible="true"
alertClass="alert-warning" />
<appointmentTypeQuestion
v-if="showMobileOption"
v-if="!requiresInshopRecalibration && isServiceableMobile"
ref="appointmentTypeQuestion"
v-model="selectedAppointmentType"
groupName="appointmentTypeQuestion"
@ -66,6 +62,12 @@
modalWidgetName="ChangeLocationModalWidget"
cmsWidgetName="ShopAddressWidget"
@zip-updated="handleInShopZipUpdated" />
<alert
v-if="selectedProvider === null"
ref="alertNoAvailableShops"
cmsWidgetName="AlertNoAvailableShopsWidget"
:isCollapsible="true"
alertClass="alert-danger" />
</div>
<div
v-if="isMobile">
@ -105,6 +107,7 @@
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="disclaimer" />
</div>
<!-- INTEGRATION TODO: Put this right under the calendar -->
<contentGroupModal
:ref="RECAL_MODAL_REF_NAME"
cssModalHeadlineClass="text-center"
@ -136,6 +139,7 @@ import {
getZipCodeData,
getMobileZipCodeData
} from '@/helpers/service-location-helper';
import { toTitleCase } from '@/helpers/text-helper.js';
// Import Component
import alert from '@/ux-components/alert/alert.vue';
@ -232,7 +236,6 @@ export default {
city: this.getServiceCityFromStore(),
state: this.getServiceStateFromStore(),
zipCode: this.getServiceZipCodeFromStore(),
isVehicleProtected: this.getIsVehicleProtectedFromStore(),
isGlassServiceableInshop: null,
isRecalibrationServiceableInshop: null,
isGlassServiceableMobile: null,
@ -245,7 +248,6 @@ export default {
zipCodeCtu: null,
mobileZipCode: '',
mobileZipError: '',
showMobileOption: false,
militaryBaseWarningExpanded: false,
RECAL_MODAL_REF_NAME,
SITE_FOOTER_REF_NAME,
@ -313,12 +315,11 @@ export default {
displayServiceableInshopOnly() {
return (
!this.displayRecalibrationWarning
&& this.isServiceableInshop
&& !this.isServiceableMobile
);
},
displayServiceableMobileOnly() {
return this.isServiceableMobile && !this.isServiceableInshop;
return this.isServiceableMobile && !this.isServiceableInshop && this.displayRecalibrationWarning;
},
displayMobileFeeDisclaimer() {
return this.mainStore.isNoComp || this.mainStore.isITAC;
@ -342,7 +343,27 @@ export default {
return this.getCmsContent('AlertMilitaryBaseZipWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
},
appointmentQuestionText() {
return this.getCmsContent('AppointmentTypeQuestionWidget', widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
},
scheduleRecalText() {
if(this.requiresInshopRecalibration) {
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
} else {
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT);
}
},
inShopOnlyCopy() {
let content = this.getCmsContent('AlertInshopOnlyWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
content = content.replace('{custom:city}', toTitleCase(this.city));
return content;
},
mobileOnlyCopy() {
let content = this.getCmsContent('AlertMobileOnlyWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
content = content.replace('{custom:city}', toTitleCase(this.city));
return content;
},
recalibrationRequired() {
return this.isRecalibrationServiceableMobile !== null;
}
},
methods: {
@ -379,7 +400,6 @@ export default {
zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType,
isVehicleProtected: this.isVehicleProtected,
provider
});
@ -413,9 +433,6 @@ export default {
|| useMainStore().order.customer.address.zipCode
);
},
getIsVehicleProtectedFromStore() {
return useMainStore().order.serviceLocation.isVehicleProtected;
},
getSelectedAppointmentType() {
return useMainStore().order.serviceLocation.appointmentType;
},
@ -435,14 +452,11 @@ export default {
if (zipCodeData) {
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
this.zipCodeCtu = zipCodeData.zipCodeCtu;
this.city = zipCodeData.city;
}
if (serviceabilityDetails) {
this.setServiceabilityDetails(serviceabilityDetails);
this.showMobileOption = this.isServiceableMobile;
if (!this.showMobileOption) {
this.selectedAppointmentType = AppointmentTypeStrings.IN_SHOP;
}
}
if (mobileFeePart) {
@ -464,6 +478,9 @@ export default {
if(providers.shopProviders.length > 0 && !foundMatch) {
this.selectedProvider = providers.shopProviders[0];
}
else {
this.selectedProvider = null;
}
}
if (glassFees) {
@ -485,13 +502,6 @@ export default {
setMobileProviderNumber(providerNumber) {
this.mobileProviderNumber = providerNumber;
},
resetMobileLocation() {
this.streetAddress = '';
this.streetAddress2 = '';
this.city = '';
this.isVehicleProtected = null;
},
setServiceabilityDetails(serviceabilityDetails) {
this.isGlassServiceableInshop =
serviceabilityDetails.isGlassServiceableInshop;
@ -501,6 +511,9 @@ export default {
serviceabilityDetails.isGlassServiceableMobile;
this.isRecalibrationServiceableMobile =
serviceabilityDetails.isRecalibrationServiceableMobile;
if (!this.isServiceableMobile) {
this.selectedAppointmentType = AppointmentTypeStrings.IN_SHOP;
}
},
async updateMobileZip() {
this.mobileZipError = '';
@ -510,8 +523,10 @@ export default {
if (!zipCodeData.isValid) {
this.mobileZipError = errorMessages.INVALID_ZIP;
} else if (!zipCodeData.isServiceable) {
this.mobileZipError = errorMessages.NO_SERVICE_IN_ZIP(this.mobileZipCode);
this.mobileZipError = errorMessages.NO_SERVICE_IN_AREA(toTitleCase(zipCodeData.city));
} else {
console.log('zipCodeData:', zipCodeData);
this.city = zipCodeData.city;
this.zipCode = this.mobileZipCode;
this.setCtuForMobile(zipCodeData.zipCodeCtu);
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
@ -532,6 +547,9 @@ export default {
if(providers.shopProviders.length > 0) {
this.selectedProvider = providers.shopProviders[0];
}
else {
this.selectedProvider = null;
}
}
});
}
@ -539,6 +557,8 @@ export default {
async handleInShopZipUpdated(newZip) {
this.zipCode = newZip;
const zipCodeData = await getZipCodeData(this.zipCode);
console.log('zipCodeData:', zipCodeData);
this.city = zipCodeData.city;
this.setCtuForMobile(zipCodeData.zipCodeCtu);
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
@ -547,7 +567,6 @@ export default {
const details = result.data;
if (details) {
this.setServiceabilityDetails(details);
this.showMobileOption = this.isServiceableMobile;
}
});
}
@ -589,6 +608,11 @@ $page-side-padding: 1.5rem;
margin-bottom: 1rem;
font-size: 1rem;
line-height: 1.625rem;
flex-direction: column;
.recal-text {
margin-bottom: 1.25rem;
font-weight: $font-weight-bold;
}
}
.mobile-service-zip-question {
:deep(.form-test-error span) {

View file

@ -6,7 +6,9 @@
class="shop-address"
aria-live="polite">
<div class="address-header">{{ questionText }}</div>
<div class="current-address">
<div
v-if="modelValue"
class="current-address">
<div class="address-line">
<span class="shop-name">{{ displayedShopName }}</span>
<span class="shop-distance">{{ modelValue.distanceInMiles?.toFixed(2) }} mi</span>
@ -16,10 +18,22 @@
{{ getProviderCityZipState(modelValue) }}
</div>
</div>
<div
v-else
class="no-shops-found">
{{ `No shops found within 100 miles of ${internalZipcode}` }}
</div>
<buttonMain
class="more-shops-button"
:buttonText="showMoreShopsLinkText"
:variant="'primary'"
@clickEvent="openModal" />
<alert
class="alert-shop-distance"
v-if="modelValue?.distanceInMiles > 30"
ref="alertShopDistance"
cmsWidgetName="AlertShopDistanceWidget"
alertClass="alert-warning" />
<modal
:ref="modalName"
:headerText="modalHeaderText"
@ -129,7 +143,7 @@ export default {
toTitleCase,
serviceZipCodeTextInputId: '',
internalZipcode: this.serviceZipcode,
internalProviderNumber: this.modelValue.providerNumber,
internalProviderNumber: this.modelValue?.providerNumber,
searchRadiusQuestionWidgetName: 'SearchRadiusQuestionWidget',
textboxQuestionWidgetName: 'ChangeLocationZipQuestionWidget',
shopListButton: markRaw(shopListButton),
@ -335,6 +349,18 @@ export default {
.current-address {
margin: .625rem 0;
}
.no-shops-found {
font-size: .75rem;
margin: .75rem 0rem;
font-weight: $font-weight-bold;
line-height: 1.375rem;
}
.more-shops-button {
margin-bottom: 1.25rem;
}
.alert-shop-distance {
margin-top: 2.5rem;
}
:deep(.modal-title) {
font-weight: 400;
display: block;

View file

@ -212,6 +212,8 @@ $alert-color-scale: 40%;
$alert-yellow-bg: #fef6e8;
$alert-yellow-color: #e86421;
$alert-red-bg: #fbe9e8;
$alert-red-color: $red;
//Modal animation
// This affects all [Bootstrap] modals

View file

@ -41,17 +41,18 @@
</button>
</div>
<div class="alert-body"
v-show="alertCopy"
:id="collapseId">
<template
v-for="paragraph in splitAlertCopyForParagraphTag"
:key="paragraph">
<p
v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)"
class="m-0 text-body small"
class="m-0 text-body"
v-html="paragraph"></p>
<p
v-else
class="m-0 text-body small">
class="m-0 text-body">
<template v-if="doesCopyContainRouterLink(paragraph)">
<textBlock
:customText="paragraph"
@ -162,8 +163,9 @@ export default {
},
mounted() {
this.ensureAlertIsInViewPort();
this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId));
if (this.isCollapsible) {
this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId));
}
},
methods: {
doesCopyContainRouterLink,
@ -234,8 +236,9 @@ export default {
height: 100%;
width: 100%;
transition: transform 0.3s;
transform: rotate(180deg);
&.rotated {
transform: rotate(180deg);
transform: none;
}
}
a {
@ -271,10 +274,10 @@ export default {
}
}
&.alert-danger {
background-color: $red-100;
border-color: $red-200;
background-color: $alert-red-bg;
border-color: $alert-red-color;
.alert-body {
border-top-color: $red-200;
border-top-color: $alert-red-color;
}
svg {
fill: $red-600;