INSR-7771 | Use new endpoint, update error handling

This commit is contained in:
scottkiener-at-safelite 2026-02-06 10:17:18 -05:00
parent c6df7d9771
commit 2eb985cd7d
6 changed files with 96 additions and 121 deletions

View file

@ -84,6 +84,10 @@ const endpoints = Object.freeze({
url: `${LOCATION_BASE_URL}/providers`, url: `${LOCATION_BASE_URL}/providers`,
method: 'GET' method: 'GET'
}, },
GetTpaAndSafeliteProviders: {
url: `${LOCATION_BASE_URL}/tpa-and-safelite-providers`,
method: 'GET'
},
GetCapabilityQuestions: { GetCapabilityQuestions: {
url: `${PARTS_BASE_URL}/capability-questions`, url: `${PARTS_BASE_URL}/capability-questions`,
method: 'GET' method: 'GET'

View file

@ -60,7 +60,10 @@ const errorMessages = Object.freeze({
STYLE_REQUIRED: 'Vehicle style is required.', STYLE_REQUIRED: 'Vehicle style is required.',
MOBILE_LOCATION_REQUIRED: 'Please enter your service address', MOBILE_LOCATION_REQUIRED: 'Please enter your service address',
DATE_REQUIRED: 'Please select a date', DATE_REQUIRED: 'Please select a date',
TIME_REQUIRED: 'Please select an appointment time.' TIME_REQUIRED: 'Please select an appointment time.',
TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.',
TPA_SEARCH_SHOP_FORMAT: 'Please enter a valid shop name.',
TPA_SEARCH_ZIP_FORMAT: 'Please enter a valid Zip code.'
}); });
export default errorMessages; export default errorMessages;

View file

@ -26,7 +26,8 @@ const globalRules = Object.freeze({
EXTENSION_FORMAT: 'extension-format', EXTENSION_FORMAT: 'extension-format',
ZIP_CODE_REQUIRED: 'zip-code-required', ZIP_CODE_REQUIRED: 'zip-code-required',
ZIP_CODE_SEARCH_FORMAT: 'zip-code-search-format', ZIP_CODE_SEARCH_FORMAT: 'zip-code-search-format',
OPTION_REQUIRED: 'option-required' OPTION_REQUIRED: 'option-required',
TPA_SEARCH_FORMAT: 'tpa-search-format'
}); });
export default globalRules; export default globalRules;

View file

@ -123,6 +123,28 @@ function defineGlobalZipCodeRules() {
defineRule(globalRules.ZIP_CODE_SEARCH_FORMAT, regex(/^\d{5}$/, errorMessages.ZIP_FORMAT)); defineRule(globalRules.ZIP_CODE_SEARCH_FORMAT, regex(/^\d{5}$/, errorMessages.ZIP_FORMAT));
} }
/**
* @summary Define global rules related to the zip code/shop name search on TPA Search page
*/
function defineGlobalTpaSearchRules() {
defineRule(globalRules.TPA_SEARCH_FORMAT, (value) => {
if (value.length === 0) {
return errorMessages.TPA_SEARCH_REQUIRED;
}
// Non Numeric
if (isNaN(value)) {
if (value.length < 3) {
return errorMessages.TPA_SEARCH_SHOP_FORMAT;
}
} else {
if (value.length != 5) {
return errorMessages.TPA_SEARCH_ZIP_FORMAT;
}
}
return true;
});
}
/** /**
* @function defineGlobalRules * @function defineGlobalRules
* @summary Define all global rules * @summary Define all global rules
@ -136,6 +158,6 @@ export default function defineGlobalRules() {
defineGlobalPolicyNumberRules(); defineGlobalPolicyNumberRules();
defineGlobalPolicyZipCodeRules(); defineGlobalPolicyZipCodeRules();
defineGlobalZipCodeRules(); defineGlobalZipCodeRules();
defineGlobalTpaSearchRules();
defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED)); defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED));
} }

View file

@ -27,14 +27,14 @@
<div class="mb-5"> <div class="mb-5">
<textboxQuestion <textboxQuestion
id="tpaSearchQuestionField" id="tpaSearchQuestionField"
v-model="zipCode" v-model="tpaSearchValue"
inputId="tpaSearchQuestionFieldInput" inputId="tpaSearchQuestionFieldInput"
:cmsWidgetName="widget.tpaSearchQuestion" :cmsWidgetName="widget.tpaSearchQuestion"
:includeSearchIcon="true" :includeSearchIcon="true"
:displayQuestionText="false" :displayQuestionText="false"
isRequired isRequired
:isDisabled="reloadingProviders" :isDisabled="reloadingProviders"
:validationRules="rules.zipCode" :validationRules="rules.tpaSearch"
@clickEvent="searchClick" /> @clickEvent="searchClick" />
</div> </div>
</Form> </Form>
@ -52,10 +52,10 @@
@invalidSubmit="onInvalidSubmit"> @invalidSubmit="onInvalidSubmit">
<dropdownQuestion <dropdownQuestion
id="searchRadiusFilter" id="searchRadiusFilter"
v-model="filter" v-model="radiusFilter"
:cmsWidgetName="widget.filterByQuestion" :cmsWidgetName="widget.filterByQuestion"
inputId="searchRadiusFilterInput" inputId="searchRadiusFilterInput"
:options="filterOptions" :options="radiusFilterOptions"
disableAutoFill disableAutoFill
:isDisabled="reloadingProviders" :isDisabled="reloadingProviders"
:validationRules="rules.filter" /> :validationRules="rules.filter" />
@ -135,53 +135,8 @@ import widgetFields from '@/constants/cms-widget-fields.js';
import { shallowRef } from 'vue'; import { shallowRef } from 'vue';
import { toTitleCase, toDisplayPhoneNumber } from '@/helpers/text-helper.js'; import { toTitleCase, toDisplayPhoneNumber } from '@/helpers/text-helper.js';
import bailoutMessage from '@/constants/bailoutMessage'; import bailoutMessage from '@/constants/bailoutMessage';
import settleAllPromises from '@/helpers/layout-helper';
const radiusFilterPairs = [
{ radius: 15, filter: '15 miles' },
{ radius: 25, filter: '25 miles' },
{ radius: 50, filter: '50 miles' },
{ radius: 100, filter: '100 miles' }
];
function convertRadiusFilterToInteger(filter) {
const pair = radiusFilterPairs.find((p) => p.filter === filter);
return pair?.radius ?? 0;
}
async function getInitialSearchData() {
const customerZipCode = useMainStore().order.customer.address.zipCode;
const serviceLocationZipCode = useMainStore().order.serviceLocation.zipCode;
const zipCode = serviceLocationZipCode ?? customerZipCode;
const { searchFilter } = useMainStore().order.serviceLocation;
let { providerNumber } = useMainStore().order.serviceLocation.provider;
let providers = [];
let radius = 0;
if (searchFilter) {
radius = convertRadiusFilterToInteger(searchFilter);
const result = await useMainStore().getTpaProviders(zipCode, radius);
providers = result?.data?.shopProviders ?? [];
return { zipCode, filter: searchFilter, providers, providerNumber };
}
providerNumber = null;
let pairIndex = 0;
let filter = '';
while (providers.length === 0 && pairIndex < radiusFilterPairs.length) {
const pair = radiusFilterPairs[pairIndex];
radius = pair.radius;
filter = pair.filter;
// eslint-disable-next-line no-await-in-loop
const result = await useMainStore().getTpaProviders(zipCode, radius);
providers = result?.data?.shopProviders ?? [];
pairIndex += 1;
}
return { zipCode, filter, providers, providerNumber };
}
export default { export default {
name: 'tpa-search', name: 'tpa-search',
@ -200,23 +155,35 @@ export default {
}, },
mixins: [BaseFormMixin], mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
const { zipCode, filter, providers, providerNumber } = const zipCode = useMainStore().order.customer.address.zipCode;
await getInitialSearchData(); const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const cmsContent = await fetchCmsContentForPage(to.query.issPage); const providersPromise = useMainStore().getTpaAndSafeliteProviders(zipCode);
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
},
{
resultKey: 'providers',
promise: providersPromise
}
];
const resultMap = await settleAllPromises(promiseResultMap);
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.setInitialSearchData(zipCode, filter, providers, providerNumber); vm.tpaSearchValue = ""; // TODO: use page data to set the value
vm.mapZipCode = zipCode;
vm.providers = resultMap.providers;
}); });
}, },
data() { data() {
return { return {
dataLoaded: false, dataLoaded: false,
zipCode: null, tpaSearchValue: null,
mapZipCode: null, mapZipCode: null,
filter: '', radiusFilter: useMainStore().order.serviceLocation.tpaSearchRadius ?? 25, // TODO: use page data to set the value
providers: [], providers: [],
selectedProviderNumber: '', selectedProviderNumber: useMainStore().order.serviceLocation.provider.providerNumber,
reloadingProviders: false, reloadingProviders: false,
additionalButtonData: { additionalButtonData: {
displayAvailabilityIndicators: false displayAvailabilityIndicators: false
@ -231,7 +198,7 @@ export default {
siteFooter: 'SiteFooterWidget' siteFooter: 'SiteFooterWidget'
}, },
rules: { rules: {
zipCode: `${globalRules.ZIP_CODE_REQUIRED}|${globalRules.ZIP_CODE_SEARCH_FORMAT}`, tpaSearch: globalRules.TPA_SEARCH_FORMAT,
filter: globalRules.OPTION_REQUIRED, // TODO do we even need this? filter: globalRules.OPTION_REQUIRED, // TODO do we even need this?
provider: globalRules.OPTION_REQUIRED provider: globalRules.OPTION_REQUIRED
}, },
@ -239,17 +206,16 @@ export default {
}; };
}, },
computed: { computed: {
filterOptions() { radiusFilterOptions() {
const filterByAnswers = const radiusFilterOptions = this.getCmsContent(
this.getCmsContent(
this.widget.filterByQuestion, this.widget.filterByQuestion,
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
) ?? []; ) ?? [];
const filterByAnswersObj = {}; const options = {};
[...filterByAnswers].forEach((answer) => { for (const option of radiusFilterOptions) {
filterByAnswersObj[answer.Name] = answer.Name; options[option.Name] = option.Text;
}); }
return filterByAnswersObj; return options;
}, },
tpaSearchQuestionLabel() { tpaSearchQuestionLabel() {
return this.getCmsContent( return this.getCmsContent(
@ -269,17 +235,17 @@ export default {
widgetFields.TEXT_BLOCK_WIDGET.TEXT widgetFields.TEXT_BLOCK_WIDGET.TEXT
); );
}, },
radiusInMiles() {
return convertRadiusFilterToInteger(this.filter);
},
noNetworkShopsAlertHeaderText() { noNetworkShopsAlertHeaderText() {
return this.getCmsContent( return this.getCmsContent(
this.widget.noNetworkShopsAlert, this.widget.noNetworkShopsAlert,
widgetFields.ALERT_WIDGET.HEADLINE_TEXT widgetFields.ALERT_WIDGET.HEADLINE_TEXT
)?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles); )?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles);
}, },
radiusFilteredAndCappedProviders() {
return this.providers.filter((provider) => provider.distanceInMiles <= this.radiusFilter).slice(0, 5);
},
providerAddresses() { providerAddresses() {
return this.providers?.map((provider) => ({ return this.radiusFilteredAndCappedProviders?.map((provider) => ({
title: provider.companyName, title: provider.companyName,
fullAddress: this.getFullProviderAddress(provider), fullAddress: this.getFullProviderAddress(provider),
addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)] addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)]
@ -287,7 +253,7 @@ export default {
}, },
providerButtonData() { providerButtonData() {
return ( return (
this.providers?.map((provider) => this.radiusFilteredAndCappedProviders?.map((provider) =>
this.getShopButtonDataFromProvider(provider)) ?? [] this.getShopButtonDataFromProvider(provider)) ?? []
); );
}, },
@ -298,14 +264,6 @@ export default {
} }
}, },
watch: { watch: {
async filter() {
if (this.dataLoaded) {
await this.getProviderButtonData().then((providers) => {
this.providers = providers;
});
this.mapZipCode = this.zipCode;
}
},
providers(newProviders) { providers(newProviders) {
if (this.dataLoaded) { if (this.dataLoaded) {
this.selectedProviderNumber = this.selectedProviderNumber =
@ -318,7 +276,7 @@ export default {
const provider = this.providers?.find((p) => p.providerNumber === newNumber); const provider = this.providers?.find((p) => p.providerNumber === newNumber);
if (provider && this.dataLoaded) { if (provider && this.dataLoaded) {
useMainStore().updateServiceLocation({ useMainStore().updateServiceLocation({
searchFilter: this.filter, tpaSearchRadius: this.radiusFilter,
zipCode: this.zipCode, zipCode: this.zipCode,
provider: { provider: {
providerNumber: provider?.providerNumber, providerNumber: provider?.providerNumber,
@ -342,13 +300,6 @@ export default {
} }
}, },
methods: { methods: {
setInitialSearchData(zipCode, filter, providers, providerNumber) {
this.zipCode = zipCode;
this.mapZipCode = zipCode;
this.filter = filter;
this.providers = providers;
this.selectedProviderNumber = providerNumber;
},
getFullProviderAddress(provider) { getFullProviderAddress(provider) {
const addressLine1 = this.getProviderAddress(provider); const addressLine1 = this.getProviderAddress(provider);
const addressLine2 = this.getProviderCityZipState(provider); const addressLine2 = this.getProviderCityZipState(provider);
@ -379,14 +330,14 @@ export default {
} }
return addressLine2; return addressLine2;
}, },
async getProviderButtonData() { async getProviderButtonData(shopName = "") {
this.reloadingProviders = true; this.reloadingProviders = true;
const getTpaProvidersResult = await useMainStore().getTpaProviders( const getTpaProvidersResult = await useMainStore().getTpaAndSafeliteProviders(
this.zipCode, this.mapZipCode,
this.radiusInMiles shopName
); );
this.reloadingProviders = false; this.reloadingProviders = false;
return getTpaProvidersResult?.data?.shopProviders ?? []; return getTpaProvidersResult?.data ?? [];
}, },
doNotSeeMyShopLinkClick() { doNotSeeMyShopLinkClick() {
this.mainStore.setBailout(bailoutMessage.doNotSeeMyShop()); this.mainStore.setBailout(bailoutMessage.doNotSeeMyShop());
@ -396,8 +347,12 @@ export default {
); );
}, },
async searchClick() { async searchClick() {
this.providers = await this.getProviderButtonData(); if (isNaN(this.tpaSearchValue)) {
this.mapZipCode = this.zipCode; this.providers = await this.getProviderButtonData(this.tpaSearchValue);
} else {
this.mapZipCode = this.tpaSearchValue;
this.providers = await this.getProviderButtonData();
}
}, },
forwardButtonAction() { forwardButtonAction() {
if (this.selectedProviderIsSafeliteShop) { if (this.selectedProviderIsSafeliteShop) {
@ -409,14 +364,6 @@ export default {
.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP; .CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP;
this.$router.navigate(scenario, this.$route); this.$router.navigate(scenario, this.$route);
}, },
getCustomValueFromString(str) {
switch (str) {
case 'radiusInMiles':
return this.radiusInMiles;
default:
return null;
}
},
getShopButtonDataFromProvider(provider) { getShopButtonDataFromProvider(provider) {
const cellNumber = toDisplayPhoneNumber(provider?.phoneNumber); const cellNumber = toDisplayPhoneNumber(provider?.phoneNumber);
const distance = const distance =

View file

@ -142,7 +142,7 @@ export const getDefaultState = () => ({
appointmentType: null, appointmentType: null,
isVehicleProtected: null, isVehicleProtected: null,
IsSafeliteProvider: null, IsSafeliteProvider: null,
searchFilter: null, tpaSearchRadius: null,
provider: { provider: {
providerNumber: null, providerNumber: null,
address: { address: {
@ -1032,23 +1032,21 @@ export const useMainStore = defineStore({
}); });
}, },
getTpaProviders(zipCode, radius) { getTpaAndSafeliteProviders(zipCode, provderNameSearchString = "") {
const { carId, isBigTruck } = this.order.vehicle;
const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
const { parentAccountNumber } = this.order; const { parentAccountNumber } = this.order;
const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts); const carId = this.order.vehicle.carId;
const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null; const damageType = this.damage.isRepair ? 'Repair' : 'Install';
const safeliteOnly = false;
let url = `${endpoints.GetProviders.url}/${zipCode}/${damageType}/${radius}/${parentAccountNumber}/${safeliteOnly}/${carId}`; const parts = getLineItemsFlattened(this.order.lineItems.glassParts);
if (windshieldPartWithRecal) { const partNumbers = parts.map((part) => part.partNumber).join(',');
url += `/${windshieldPartWithRecal.partNumber}`;
let url = `${endpoints.GetTpaAndSafeliteProviders.url}/${zipCode}?accountNumber=${parentAccountNumber}&carId=${carId}&damageType=${damageType}&partNumbers=${partNumbers}`;
if (provderNameSearchString) {
url += `&providerNameSearchString=${provderNameSearchString}`;
} }
url += `/${isBigTruck}`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
globalMethods.callHttpClient({ globalMethods.callHttpClient({
method: endpoints.GetProviders.method, method: endpoints.GetTpaAndSafeliteProviders.method,
endpoint: url endpoint: url
}).then((response) => resolve(response), (error) => reject(error)); }).then((response) => resolve(response), (error) => reject(error));
}); });
@ -1822,7 +1820,7 @@ export const useMainStore = defineStore({
companyName: serviceLocationInfo.provider?.companyName, companyName: serviceLocationInfo.provider?.companyName,
phoneNumber: serviceLocationInfo.provider?.phoneNumber phoneNumber: serviceLocationInfo.provider?.phoneNumber
}; };
this.order.serviceLocation.searchFilter = serviceLocationInfo.searchFilter; this.order.serviceLocation.tpaSearchRadius = serviceLocationInfo.tpaSearchRadius;
}, },
resetState() { resetState() {