Merge branch 'develop' into feature/kroell/INSR-8105

This commit is contained in:
Katie Kroell 2026-02-18 14:39:57 -05:00
commit 933dfcc5d4
18 changed files with 225 additions and 130 deletions

View file

@ -23,7 +23,10 @@ const applicationConfig = Object.freeze({
YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60', YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60',
OUTLOOK_CALENDAR: OUTLOOK_CALENDAR:
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent', 'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent',
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging" FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
BAILOUT_ON_APPLICATION_ERROR: true,
BAILOUT_ON_API_ERROR: true,
BAILOUT_ON_ROUTER_ERROR: true
}); });
export default applicationConfig; export default applicationConfig;

View file

@ -12,7 +12,10 @@ const bailoutCode = Object.freeze({
NoPartsAvailable: 10, NoPartsAvailable: 10,
PartsServiceError: 11, PartsServiceError: 11,
SafeliteNotTheProvider: 12, SafeliteNotTheProvider: 12,
VehicleYMMSLookupError: 13 VehicleYMMSLookupError: 13,
ApplicationError: 14,
ApiError: 15,
RouterError: 16
}); });
export default bailoutCode; export default bailoutCode;

View file

@ -17,6 +17,18 @@ const bailoutMessage = Object.freeze({
code: bailoutCode.Unknown, code: bailoutCode.Unknown,
message: `An unknown bailout occurred: ${getItemData(error)}` message: `An unknown bailout occurred: ${getItemData(error)}`
}), }),
applicationError: (error) => ({
code: bailoutCode.ApplicationError,
message: `An application error occurred: ${getItemData(error)}`
}),
apiError: (error) => ({
code: bailoutCode.ApiError,
message: `An API error occurred: ${getItemData(error)}`
}),
routerError: (error) => ({
code: bailoutCode.RouterError,
message: `A router error occurred: ${getItemData(error)}`
}),
saveSessionError: (error) => ({ saveSessionError: (error) => ({
code: bailoutCode.SaveSessionError, code: bailoutCode.SaveSessionError,
message: `An error occurred during save session: ${getItemData(error)}` message: `An error occurred during save session: ${getItemData(error)}`

View file

@ -10,7 +10,7 @@
When would you like service? When would you like service?
</div> </div>
<div class="appointment-estimate-header"> <div class="appointment-estimate-header">
Your service will take approximately <span>{{ appointmentEstimate }}</span> Your service will take approximately <span>{{ appointmentEstimate }}</span>.
</div> </div>
<div <div
class="appointment-date-picker-container" class="appointment-date-picker-container"
@ -558,8 +558,12 @@ export default {
this.selectedDate = morningDateString; this.selectedDate = morningDateString;
this.selectedTimeOfDayGrouping = 'morning'; this.selectedTimeOfDayGrouping = 'morning';
this.selectableTimeSlotsData = selectableDateObj.morningTimeSlots; this.selectableTimeSlotsData = selectableDateObj.morningTimeSlots;
this.selectedTime = null; if (selectableDateObj.morningTimeSlots.length === 1) {
this.selectedTimeSlot = null; this.selectTimeSlotForDay(selectableDateObj.morningTimeSlots[0]);
} else {
this.selectedTime = null;
this.selectedTimeSlot = null;
}
} }
}, },
showAfternoonAvailabilityForIndex(index) { showAfternoonAvailabilityForIndex(index) {
@ -582,8 +586,12 @@ export default {
this.selectedDate = afternoonDateString; this.selectedDate = afternoonDateString;
this.selectedTimeOfDayGrouping = 'afternoon'; this.selectedTimeOfDayGrouping = 'afternoon';
this.selectableTimeSlotsData = selectableDateObj.afternoonTimeSlots; this.selectableTimeSlotsData = selectableDateObj.afternoonTimeSlots;
this.selectedTimeSlot = null; if (selectableDateObj.afternoonTimeSlots.length === 1) {
this.selectedTime = null; this.selectTimeSlotForDay(selectableDateObj.afternoonTimeSlots[0]);
} else {
this.selectedTime = null;
this.selectedTimeSlot = null;
}
} }
}, },
timeSlotFocused(timeSlot) { timeSlotFocused(timeSlot) {

View file

@ -15,10 +15,6 @@ axios.interceptors.response.use(
if (typeof error.response === 'undefined') { if (typeof error.response === 'undefined') {
// The request was not made, could be a bad url, bad connection or a CORS error. // The request was not made, could be a bad url, bad connection or a CORS error.
rejectionError = { rejectionError = {
message:
'A network error occurred. '
+ 'This could be a CORS issue or a dropped internet connection. '
+ 'It is impossible for us to know.',
cause: error, cause: error,
response: error, response: error,
message: axiosResponseInterceptorMessages.NETWORK_ERROR message: axiosResponseInterceptorMessages.NETWORK_ERROR
@ -51,7 +47,7 @@ axios.interceptors.response.use(
); );
export default { export default {
callHttpClient({ method, endpoint, payload, logApiCall = true }) { callHttpClient({ method, endpoint, payload, logApiCall = true, bailoutOnError = true }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const store = useMainStore(); const store = useMainStore();
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO; const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
@ -66,9 +62,10 @@ export default {
[headerKeys.SESSION_SEQUENCE_NUMBER]: sessionKey [headerKeys.SESSION_SEQUENCE_NUMBER]: sessionKey
}; };
const url = cfDistroUrl + endpoint;
axios({ axios({
method, method,
url: cfDistroUrl + endpoint, url,
data: payloadAndAnalyticsData, data: payloadAndAnalyticsData,
crossDomain: true, crossDomain: true,
responseType: 'json', responseType: 'json',
@ -97,10 +94,11 @@ export default {
} }
if (error.response.status !== 404) { if (error.response.status !== 404) {
global.$logger.logError( global.$logger.logError(`${method}: ${endpoint}: ${error.message}`, error.response);
`${method}: ${endpoint}: ${error.message}`, if (bailoutOnError && global.bailoutOnAxiosError !== undefined)
error.response {
); global.bailoutOnAxiosError({ url, error });
}
} }
return reject(error.response); return reject(error.response);
} }

View file

@ -9,13 +9,12 @@
v-model="selectedAnswers" v-model="selectedAnswers"
isRequired isRequired
:isMetaValid="meta.valid" :isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData" :questionsData="questionsData"
:validationRules="rules.optionRequired" :validationRules="rules.optionRequired"
:index="currentGlassIndex" :index="currentGlassIndex"
@forwardButtonAction="forwardButtonAction" @forwardButtonAction="forwardButtonAction"
@backClick="navigateBackByVehicleQuestions" /> @backClick="navigateBackByVehicleQuestions"
@needHelpClick="requestCallbackBailout" />
</Form> </Form>
</template> </template>
<script> <script>
@ -31,6 +30,8 @@ import { useMainStore } from '@/store';
import globalRules from '@/constants/global-rules'; import globalRules from '@/constants/global-rules';
import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin'; import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin';
import questionsPageLayout from '@/iss-components/questions-page-layout/questions-page-layout.vue'; import questionsPageLayout from '@/iss-components/questions-page-layout/questions-page-layout.vue';
import bailoutMessage from '@/constants/bailoutMessage';
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
export default { export default {
name: 'capability-questions', name: 'capability-questions',
@ -67,18 +68,6 @@ export default {
}; };
}, },
computed: { computed: {
AlertFewMoreQuestionsHeader() {
return this.getCmsContent(
'AdditionalPartsQuestionsAlert',
'HeadlineText'
);
},
AlertFewMoreQuestionsCopy() {
return this.getCmsContent(
'AdditionalPartsQuestionsAlert',
'BodyText'
);
},
partsOrQuestionsData() { partsOrQuestionsData() {
return useMainStore().pageData(issPageValues.CAPABILITY_QUESTIONS) return useMainStore().pageData(issPageValues.CAPABILITY_QUESTIONS)
.partsOrQuestions; .partsOrQuestions;
@ -166,6 +155,10 @@ export default {
}); });
this.navigateForward(this.partsOrQuestionsData, null); this.navigateForward(this.partsOrQuestionsData, null);
},
requestCallbackBailout() {
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route);
} }
} }
}; };

View file

@ -10,8 +10,6 @@
v-model="selectedAnswers" v-model="selectedAnswers"
isRequired isRequired
:isMetaValid="meta.valid" :isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData" :questionsData="questionsData"
:validationRules="rules.optionRequired" :validationRules="rules.optionRequired"
:index="currentGlassIndex" :index="currentGlassIndex"
@ -71,18 +69,6 @@ export default {
}; };
}, },
computed: { computed: {
AlertFewMoreQuestionsHeader() {
return this.getCmsContent(
'AdditionalPartsQuestionsAlert',
'HeadlineText'
);
},
AlertFewMoreQuestionsCopy() {
return this.getCmsContent(
'AdditionalPartsQuestionsAlert',
'BodyText'
);
},
partsOrQuestionsData() { partsOrQuestionsData() {
return useMainStore().pageData(issPageValues.MOLDING_QUESTIONS).partsOrQuestions; return useMainStore().pageData(issPageValues.MOLDING_QUESTIONS).partsOrQuestions;
} }

View file

@ -17,6 +17,7 @@
<div class="service-location-content-container"> <div class="service-location-content-container">
<serviceLocation <serviceLocation
ref="serviceLocation" ref="serviceLocation"
:schedulingInshopAvailabilityRating="inShopAvailabilityRating"
@appointmentTypeChanged="appointmentTypeChangedFromServiceLocation" @appointmentTypeChanged="appointmentTypeChangedFromServiceLocation"
@cityUpdated="cityUpdatedFromServiceLocation" @cityUpdated="cityUpdatedFromServiceLocation"
@inShopZipUpdated="inShopZipUpdatedFromServiceLocation" @inShopZipUpdated="inShopZipUpdatedFromServiceLocation"
@ -231,7 +232,7 @@ export default {
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode); const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode); const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
const getGlassFeesPromise = useMainStore().getGlassFees(); const getGlassFeesPromise = useMainStore().getGlassFees();
const providersPromise = useMainStore().getSafeliteProviders(serviceZipCode); const providersPromise = useMainStore().getSafeliteProviders(serviceZipCode, 150);
const premiumFeePromise = useMainStore().getMobilePremiumFee(); const premiumFeePromise = useMainStore().getMobilePremiumFee();
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => { const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
@ -275,25 +276,39 @@ export default {
// use resultMap to populate layout content. // use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const providersUnderOneHundredMiles = resultMap.providers?.shopProviders?.filter((provider) => provider.distanceMiles <= 100);
const serviceLocationData = { const serviceLocationData = {
defaultMobileZipCode: isMobileAppointment defaultMobileZipCode: isMobileAppointment
? storeServiceLocation?.zipCode ? storeServiceLocation?.zipCode
: '', : '',
glassFees: resultMap.glassFees, glassFees: resultMap.glassFees,
mobileFeePart: resultMap.mobileFeePart, mobileFeePart: resultMap.mobileFeePart,
providers: resultMap.providers, providers: {
...resultMap.providers,
shopProviders: providersUnderOneHundredMiles || []
},
serviceabilityDetails: resultMap.serviceabilityDetails, serviceabilityDetails: resultMap.serviceabilityDetails,
zipCodeData: resultMap.zipCodeData zipCodeData: resultMap.zipCodeData
}; };
useMainStore().updateIsSafeliteProvider(true); useMainStore().updateIsSafeliteProvider(true);
next(async (vm) => { next(async (vm) => {
const providerToUse = resultMap.providers?.shopProviders const foundProviderInFullList = resultMap.providers?.shopProviders
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || resultMap.providers?.shopProviders[0]; .find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || resultMap.providers?.shopProviders[0];
const foundProviderInHundredMiles = providersUnderOneHundredMiles
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || providersUnderOneHundredMiles[0];
if (!foundProviderInHundredMiles && !foundProviderInFullList) {
if (storeSelectedProvider?.providerNumber && resultMap.providers?.shopProviders) {
serviceLocationData.providers?.shopProviders.push(storeSelectedProvider);
}
} else if (!foundProviderInHundredMiles && foundProviderInFullList) {
serviceLocationData.providers?.shopProviders.push(foundProviderInFullList);
}
const initialServiceLocationObj = { const initialServiceLocationObj = {
mobileProviderNumber: isMobileAppointment mobileProviderNumber: isMobileAppointment
? storeSelectedProvider?.providerNumber ? storeSelectedProvider?.providerNumber
: null, : null,
provider: isMobileAppointment ? null : providerToUse, provider: isMobileAppointment ? null : foundProviderInFullList,
zipCode: useMainStore().order.serviceLocation.zipCode || useMainStore().order.customer.address.zipCode, zipCode: useMainStore().order.serviceLocation.zipCode || useMainStore().order.customer.address.zipCode,
zipCodeCtu: resultMap.zipCodeData?.zipCodeCtu zipCodeCtu: resultMap.zipCodeData?.zipCodeCtu
}; };
@ -310,6 +325,7 @@ export default {
}, },
data() { data() {
return { return {
inShopAvailabilityRating: 'high',
inShopDatesData: [], inShopDatesData: [],
isMobileView: false, isMobileView: false,
mobileDatesData: [], mobileDatesData: [],
@ -364,6 +380,27 @@ export default {
return useMainStore().lineItems.supportingItems; return useMainStore().lineItems.supportingItems;
} }
}, },
watch: {
inShopDatesData(newData) {
if (newData?.initialShopTimeSlotsResponse?.days?.length > 0) {
const numberOfDaysNeededToBeHighAvailability = 2;
const availabilityEndDate = new Date();
availabilityEndDate.setDate(availabilityEndDate.getDate() + 6);
const datesInAvailabilityRange = newData.initialShopTimeSlotsResponse.days.filter((day) => {
const dayDate = new Date(`${day.date}T00:00:00`);
return dayDate <= availabilityEndDate;
});
const isGoodAvailability =
datesInAvailabilityRange.filter((x) => x.timeSlots.length > 0).length
>= numberOfDaysNeededToBeHighAvailability;
this.inShopAvailabilityRating = isGoodAvailability ? 'high' : 'low';
} else {
this.inShopAvailabilityRating = 'low';
}
}
},
mounted() { mounted() {
showIssLoadingModal(true); showIssLoadingModal(true);
this.mql = window.matchMedia('(min-width: 1200px)'); this.mql = window.matchMedia('(min-width: 1200px)');
@ -453,9 +490,6 @@ export default {
}, },
getAvailableDates, getAvailableDates,
async getDatePickerInitialData(defaultProviderNumber = null) { async getDatePickerInitialData(defaultProviderNumber = null) {
this.inShopDatesData = [];
this.mobileDatesData = [];
let todayDateString; let todayDateString;
const todayDateObject = new Date(); const todayDateObject = new Date();
const calendarViewDirection = 'future'; const calendarViewDirection = 'future';
@ -504,7 +538,20 @@ export default {
preSelectedDate, preSelectedDate,
initialDaysLoaded: endDateObject.newDaysLoaded || initialDays, initialDaysLoaded: endDateObject.newDaysLoaded || initialDays,
daysFromStart: endDateObject.daysFromStart || null daysFromStart: endDateObject.daysFromStart || null
})); }))
.catch(() => {
console.warn('Error fetching initial available dates...');
return {
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: { days: [] },
preSelectedDate,
initialDaysLoaded: endDateObject.newDaysLoaded || initialDays,
daysFromStart: endDateObject.daysFromStart || null
};
});
if (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE if (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) { || this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
@ -532,6 +579,7 @@ export default {
} }
} else { } else {
this.inShopDatesData = initialData; this.inShopDatesData = initialData;
this.mobileDatesData = [];
} }
return initialData; return initialData;
@ -618,12 +666,20 @@ export default {
} }
} }
}, },
providerChangedFromServiceLocation(newProvider) { async providerChangedFromServiceLocation(newProvider) {
this.selectedProvider = newProvider?.provider; this.selectedProvider = newProvider?.provider;
if (newProvider?.refreshDatePicker && newProvider.provider) {
this.mainStore.updateServiceLocationProvider(newProvider.provider);
if (newProvider?.refreshDatePicker) {
showIssLoadingModal(true); showIssLoadingModal(true);
this.refreshDatePicker(); await this.mainStore.getBillToInfo(newProvider.provider?.providerNumber)
.then(() => {
this.refreshDatePicker();
})
.catch(() => {
console.warn('Error fetching bill to info...');
showIssLoadingModal(false);
});
} }
}, },
async refreshDatePicker() { async refreshDatePicker() {

View file

@ -129,7 +129,6 @@ import errorMessages from '@/constants/error-messages';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import showIssLoadingModal from '@/helpers/loading-modal-helper.js'; import showIssLoadingModal from '@/helpers/loading-modal-helper.js';
import { import {
getAvailabilityRating,
getServiceabilityDetails, getServiceabilityDetails,
getZipCodeData, getZipCodeData,
getMobileZipCodeData getMobileZipCodeData
@ -160,6 +159,12 @@ export default {
}, },
mixins: [baseFormMixin], mixins: [baseFormMixin],
emits: ['appointment-type-changed', 'city-updated', 'in-shop-zip-updated', 'mobile-zip-updated', 'provider-changed'], emits: ['appointment-type-changed', 'city-updated', 'in-shop-zip-updated', 'mobile-zip-updated', 'provider-changed'],
props: {
schedulingInshopAvailabilityRating: {
type: String,
default: 'high'
}
},
setup() { setup() {
const mainStore = useMainStore(); const mainStore = useMainStore();
return { mainStore }; return { mainStore };
@ -198,7 +203,7 @@ export default {
return this.isBigTruck && !this.isServiceableInshop && !this.isServiceableMobile; return this.isBigTruck && !this.isServiceableInshop && !this.isServiceableMobile;
}, },
displayLowAvailabilityInshop() { displayLowAvailabilityInshop() {
return this.availabilityRating === 'low' && this.isInshop; return this.availabilityRating === 'low' && this.isInshop && this.selectedProvider !== null;
}, },
displayMilitaryZipAlert() { displayMilitaryZipAlert() {
return this.zipContainsMilitaryBase && this.isServiceableMobile; return this.zipContainsMilitaryBase && this.isServiceableMobile;
@ -307,10 +312,11 @@ export default {
this.$emit('mobile-zip-updated', updateMobileZipServiceLocationObj); this.$emit('mobile-zip-updated', updateMobileZipServiceLocationObj);
} }
}, },
schedulingInshopAvailabilityRating(newRating) {
this.availabilityRating = newRating;
},
selectedAppointmentType(newValue, oldValue) { selectedAppointmentType(newValue, oldValue) {
if (this.isInshop && this.selectedProvider && this.availabilityRating === null) { if (this.isInshop && this.selectedProvider && this.availabilityRating === null) {
this.refreshAvailabilityRating();
const appointmenTypeServiceLocationObj = { const appointmenTypeServiceLocationObj = {
appointmentType: newValue, appointmentType: newValue,
mobileProviderNumber: null, mobileProviderNumber: null,
@ -342,10 +348,6 @@ export default {
refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null
}; };
if (appointmentIsInshop && newProvider?.providerNumber != null) {
this.refreshAvailabilityRating();
}
if (newProvider?.providerNumber !== oldProvider?.providerNumber) { if (newProvider?.providerNumber !== oldProvider?.providerNumber) {
this.$emit('provider-changed', returnedProvider); this.$emit('provider-changed', returnedProvider);
} }
@ -444,24 +446,6 @@ export default {
this.$emit('in-shop-zip-updated', inShopZipServiceLocationObj); this.$emit('in-shop-zip-updated', inShopZipServiceLocationObj);
}); });
}, },
refreshAvailabilityRating() {
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];
getAvailabilityRating(
formattedStartDate,
formattedEndDate,
AppointmentTypeStrings.IN_SHOP,
this.selectedProvider ? this.selectedProvider.providerNumber : null
).then((rating) => {
this.availabilityRating = rating;
}).catch(() => {
this.availabilityRating = null;
});
},
setCtuForMobile(val) { setCtuForMobile(val) {
this.zipCodeCtu = val; this.zipCodeCtu = val;
}, },
@ -636,5 +620,8 @@ $page-side-padding: 1.5rem;
} }
} }
} }
:deep(.alert-shop-distance) {
margin-bottom: 0;
}
} }
</style> </style>

View file

@ -68,8 +68,7 @@
groupName="chooseShop" groupName="chooseShop"
textPosition="text-start" textPosition="text-start"
isRequired isRequired
validationRules="option-required" validationRules="option-required" />
:additionalButtonData="additionalButtonData" />
<span class="service-zip-prompt"> <span class="service-zip-prompt">
{{ serviceZipPrompt }} {{ serviceZipPrompt }}
</span> </span>
@ -108,7 +107,6 @@ import baseMixin from '@/mixins/base-mixin.js';
import showIssLoadingModal from '@/helpers/loading-modal-helper.js'; import showIssLoadingModal from '@/helpers/loading-modal-helper.js';
import { toTitleCase } from '@/helpers/text-helper.js'; import { toTitleCase } from '@/helpers/text-helper.js';
import { markRaw } from 'vue'; import { markRaw } from 'vue';
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';
@ -185,22 +183,6 @@ export default {
modalName() { modalName() {
return this.modalWidgetName; return this.modalWidgetName;
}, },
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: false,
availabilityRatingCallback: getAvailabilityRating,
startDate: formattedStartDate,
endDate: formattedEndDate,
shopAppointmentType: 'InshopOrDropoff'
};
},
serviceZipPrompt() { serviceZipPrompt() {
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2); return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
}, },
@ -271,6 +253,7 @@ export default {
currentSearchIndex += 1; currentSearchIndex += 1;
} }
if (result.length === 0) { if (result.length === 0) {
this.nearbyShops = [];
this.errorMessage = errorMessages.ZIP_CODE_NOT_SERVICED_FOR_VEHICLE; this.errorMessage = errorMessages.ZIP_CODE_NOT_SERVICED_FOR_VEHICLE;
} else { } else {
if (!result.find((shop) => shop.providerNumber === this.internalProviderNumber)) { if (!result.find((shop) => shop.providerNumber === this.internalProviderNumber)) {

View file

@ -355,7 +355,7 @@ describe('tpa-search.vue', () => {
// Assert // Assert
expect(result.length).toBe(1); expect(result.length).toBe(1);
expect(result[0].title).toBe('test shop'); expect(result[0].title).toBe('Test Shop');
expect(result[0].addressLines.length).toBe(2); expect(result[0].addressLines.length).toBe(2);
}); });
@ -395,7 +395,7 @@ describe('tpa-search.vue', () => {
// Assert // Assert
expect(result.length).toBe(1); expect(result.length).toBe(1);
expect(result[0].buttonLabel).toBe('auto glass shop'); expect(result[0].buttonLabel).toBe('Auto Glass Shop');
expect(result[0].buttonLabelSubCopy).toBe('5.8 mi'); expect(result[0].buttonLabelSubCopy).toBe('5.8 mi');
expect(result[0].value).toBe('12345'); expect(result[0].value).toBe('12345');
}); });
@ -616,7 +616,7 @@ describe('tpa-search.vue', () => {
const result = wrapper.vm.getShopButtonDataFromProvider(provider); const result = wrapper.vm.getShopButtonDataFromProvider(provider);
// Assert // Assert
expect(result.buttonLabel).toBe('test shop'); expect(result.buttonLabel).toBe('Test Shop');
expect(result.buttonLabelSubCopy).toBe('5.8 mi'); expect(result.buttonLabelSubCopy).toBe('5.8 mi');
expect(result.value).toBe('12345'); expect(result.value).toBe('12345');
expect(result.buttonBodyCopy).toContain('<br>'); expect(result.buttonBodyCopy).toContain('<br>');

View file

@ -265,7 +265,7 @@ export default {
}, },
providerAddresses() { providerAddresses() {
return this.radiusFilteredAndCappedProviders?.map((provider) => ({ return this.radiusFilteredAndCappedProviders?.map((provider) => ({
title: provider.companyName.toLowerCase(), title: toTitleCase(provider.companyName),
fullAddress: this.getFullProviderAddress(provider), fullAddress: this.getFullProviderAddress(provider),
addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)] addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)]
})) ?? []; })) ?? [];
@ -395,7 +395,7 @@ export default {
: null; : null;
return { return {
buttonLabel: provider?.companyName.toLowerCase() ?? '', buttonLabel: toTitleCase(provider?.companyName) ?? '',
buttonLabelSubCopy: distance === null ? '' : `${distance} mi`, buttonLabelSubCopy: distance === null ? '' : `${distance} mi`,
buttonBodyCopy: `${this.getFullProviderAddress(provider)}<br>${ buttonBodyCopy: `${this.getFullProviderAddress(provider)}<br>${
cellNumber ?? '' cellNumber ?? ''

View file

@ -14,6 +14,8 @@ import router from './router';
import App from './App.vue'; import App from './App.vue';
import Logger from "@/helpers/logger"; import Logger from "@/helpers/logger";
import bailoutMessage from '@/constants/bailoutMessage';
import applicationConfig from '@/constants/application-config';
// Instantiate global logging object // Instantiate global logging object
global.$logger = new Logger(); global.$logger = new Logger();
@ -46,15 +48,26 @@ function getPageName(vm) {
// Vue Error Handling // Vue Error Handling
vueApp.config.errorHandler = (err, vm, info) => { vueApp.config.errorHandler = (err, vm, info) => {
const pageName = getPageName(vm); const pageName = getPageName(vm);
global.$logger.logError( global.$logger.logError(`Page Name - ${pageName} - ${info}: ${err.message}\n${err.stack}`);
`Page Name - ${pageName} - ${info}: ${err.message}\n${err.stack}` if (applicationConfig.BAILOUT_ON_APPLICATION_ERROR) {
); router.navigateBailout(bailoutMessage.applicationError(`[${pageName}] ${info}: ${err.message}\n${err.stack}`));
}
}; };
// Vue Router Error Handling // Vue Router Error Handling
router.onError((err) => { router.onError((err) => {
global.$logger.logError(err.message, err.cause); global.$logger.logError(err.message, err.cause);
if (applicationConfig.BAILOUT_ON_ROUTER_ERROR) {
router.navigateBailout(bailoutMessage.routerError(`${err.message}\n${err.stack}`));
}
}); });
global.bailoutOnAxiosError = (error) => {
if (applicationConfig.BAILOUT_ON_API_ERROR) {
router.navigateBailout(bailoutMessage.apiError(error));
}
}
vueApp.mount('#app'); vueApp.mount('#app');
// define global rules // define global rules

View file

@ -281,7 +281,10 @@ function navigate(
} }
// Match our maps up and navigate if we have a destination. // Match our maps up and navigate if we have a destination.
const matchingScenarioMap = getNavigationMap(scenario, currentRoute); let matchingScenarioMap = getNavigationMap(scenario, currentRoute);
if (!matchingScenarioMap && scenario === navigationScenarios.BAILOUT) {
matchingScenarioMap = { destinationIssPageValue: issPageValues.BAILOUT_PAGE }
}
if (!matchingScenarioMap) { if (!matchingScenarioMap) {
window.console.error('No matching scenario found. Please review the routing table.'); window.console.error('No matching scenario found. Please review the routing table.');
@ -332,6 +335,18 @@ function navigateToUrl(url, optionalQuery = {}) {
window.location.assign(externalUrl); window.location.assign(externalUrl);
} }
router.navigateBailout = (bailoutData = null) => {
if (bailoutData != null && !useMainStore().isBailout) {
useMainStore().setBailout(bailoutData)
}
router.navigate(
navigationScenarios.BAILOUT,
router.currentRoute.value,
{},
{ [routerParams.SKIP_SAVE_SESSION]: true }
);
}
// Get navigation map depending on the scenario and the current 'page' you're on. // Get navigation map depending on the scenario and the current 'page' you're on.
function getNavigationMap(scenario, currentRoute) { function getNavigationMap(scenario, currentRoute) {
const issPageValue = currentRoute.query.issPage; const issPageValue = currentRoute.query.issPage;

View file

@ -114,6 +114,7 @@ const navigationScenarios = Object.freeze({
// Bailout // Bailout
CLICKED_FORWARD_WITH_BAILOUT: 'CLICKED_FORWARD_WITH_BAILOUT', CLICKED_FORWARD_WITH_BAILOUT: 'CLICKED_FORWARD_WITH_BAILOUT',
CLICKED_NEED_HELP_WITH_BAILOUT: 'CLICKED_NEED_HELP_WITH_BAILOUT', CLICKED_NEED_HELP_WITH_BAILOUT: 'CLICKED_NEED_HELP_WITH_BAILOUT',
BAILOUT: 'BAILOUT'
}); });
export default navigationScenarios; export default navigationScenarios;

View file

@ -192,6 +192,10 @@ const routingTable = () => [
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
}, },
{
scenario: navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{ {
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
@ -233,6 +237,10 @@ const routingTable = () => [
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
}, },
{
scenario: navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{ {
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
@ -262,6 +270,10 @@ const routingTable = () => [
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
}, },
{
scenario: navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{ {
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
@ -295,6 +307,10 @@ const routingTable = () => [
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
}, },
{
scenario: navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{ {
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS, scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
@ -314,6 +330,10 @@ const routingTable = () => [
{ {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
},
{
scenario: navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
} }
] ]
}, },

View file

@ -36,4 +36,19 @@ describe('Router', () => {
// Assert // Assert
expect(router.push.mock.calls[0][0].state).toBe(parameters); expect(router.push.mock.calls[0][0].state).toBe(parameters);
}); });
it('Should route from CAPABILITY_QUESTIONS to BAILOUT_PAGE on CLICKED_NEED_HELP_WITH_BAILOUT', () => {
const scenario = navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT;
const currentRoute = { query: { issPage: issPageValues.CAPABILITY_QUESTIONS } };
router.push = jest.fn();
// Act
router.navigate(scenario, currentRoute);
// Assert
expect(router.push).toHaveBeenCalled();
expect(router.push.mock.calls[0][0].query.issPage).toBe(issPageValues.BAILOUT_PAGE);
});
}); });

View file

@ -879,8 +879,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetMobilePremiumFee.method, method: endpoints.GetMobilePremiumFee.method,
endpoint: `${endpoints.GetMobilePremiumFee.url}/${paymentType}/${damageType}`, endpoint: `${endpoints.GetMobilePremiumFee.url}/${paymentType}/${damageType}`
logApiCall: true
}); });
}, },
getMobileTimeSlots(startDate, endDate, zipCodeOverride = null) { getMobileTimeSlots(startDate, endDate, zipCodeOverride = null) {
@ -935,14 +934,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetMobileTimeSlots.method, method: endpoints.GetMobileTimeSlots.method,
endpoint: endpoints.GetMobileTimeSlots.url, endpoint: endpoints.GetMobileTimeSlots.url,
payload, payload
logApiCall: true,
additionalSuccessEventDataHandler: (response) =>
getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers,
zipCodeOverride ?? order.serviceLocation.zipCode,
response.data.days?.[0]?.date
)
}); });
}, },
getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber) { getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber) {
@ -998,8 +990,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetShopTimeSlots.method, method: endpoints.GetShopTimeSlots.method,
endpoint: endpoints.GetShopTimeSlots.url, endpoint: endpoints.GetShopTimeSlots.url,
payload, payload
additionalSuccessEventDataHandler: (response) => provisionalTriggersToString(response.data.provisionalTriggers)
}); });
}, },
async getWipers() { async getWipers() {
@ -1553,8 +1544,7 @@ export const useMainStore = defineStore({
method: endpoints.SaveSession.method, method: endpoints.SaveSession.method,
endpoint: endpoints.SaveSession.url, endpoint: endpoints.SaveSession.url,
payload, payload,
additionalSuccessEventDataHandler: () => bailoutOnError: false
`Email provided: ${customer.emailAddress ? 'true' : 'false'}`
}).then((response) => { }).then((response) => {
if (loadedFromDupeCheck) { if (loadedFromDupeCheck) {
this.order.loadedSessionClearedPreviousData = true; this.order.loadedSessionClearedPreviousData = true;
@ -1858,7 +1848,20 @@ export const useMainStore = defineStore({
} }
this.order.serviceLocation.tpaSearchRadius = serviceLocationInfo.tpaSearchRadius; this.order.serviceLocation.tpaSearchRadius = serviceLocationInfo.tpaSearchRadius;
}, },
updateServiceLocationProvider(providerInfo) {
this.order.serviceLocation.provider = {
providerNumber: providerInfo?.providerNumber,
address: {
streetAddress: providerInfo?.address?.streetAddress,
city: providerInfo?.address?.city,
state: providerInfo?.address?.state,
zipCode: providerInfo?.address?.zipCode,
zipCodeCtu: providerInfo?.address?.zipCodeCtu
},
companyName: providerInfo?.companyName,
phoneNumber: providerInfo?.phoneNumber
};
},
resetState() { resetState() {
Object.assign(this, getDefaultState()); Object.assign(this, getDefaultState());
}, },
@ -2466,8 +2469,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetAlertReasons.method, method: endpoints.GetAlertReasons.method,
endpoint: `${endpoints.GetAlertReasons.url}/${ctu}`, endpoint: `${endpoints.GetAlertReasons.url}/${ctu}`,
payload: {}, payload: {}
logApiCall: true
}); });
}, },
@ -2695,12 +2697,12 @@ export const useMainStore = defineStore({
} }
}, },
async getBillToInfo() { async getBillToInfo(componentProviderNumber = null) {
const { order, issConfig } = this; const { order, issConfig } = this;
try { try {
const params = new URLSearchParams({ const params = new URLSearchParams({
parentAccountNumber: order.parentAccountNumber.toString(), parentAccountNumber: order.parentAccountNumber.toString(),
providerNumber: this.providerNumber, providerNumber: componentProviderNumber || this.providerNumber,
typeOfClaim: 'GLASS ONLY', typeOfClaim: 'GLASS ONLY',
lineOfBusiness: 'PERSONAL', lineOfBusiness: 'PERSONAL',
isItac: this.isITAC isItac: this.isITAC