From 2eb985cd7d1ece210ec3fffa47737a7732a500af Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Fri, 6 Feb 2026 10:17:18 -0500 Subject: [PATCH 01/55] INSR-7771 | Use new endpoint, update error handling --- src/constants/endpoints.js | 4 + src/constants/error-messages.js | 5 +- src/constants/global-rules.js | 3 +- src/helpers/global-rule-definer.js | 24 +++- src/layouts/tpa-search/tpa-search.vue | 155 +++++++++----------------- src/store/index.js | 26 ++--- 6 files changed, 96 insertions(+), 121 deletions(-) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index c55410da..63ca9e2d 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -84,6 +84,10 @@ const endpoints = Object.freeze({ url: `${LOCATION_BASE_URL}/providers`, method: 'GET' }, + GetTpaAndSafeliteProviders: { + url: `${LOCATION_BASE_URL}/tpa-and-safelite-providers`, + method: 'GET' + }, GetCapabilityQuestions: { url: `${PARTS_BASE_URL}/capability-questions`, method: 'GET' diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index 71e7d515..868b8e13 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -60,7 +60,10 @@ const errorMessages = Object.freeze({ STYLE_REQUIRED: 'Vehicle style is required.', MOBILE_LOCATION_REQUIRED: 'Please enter your service address', 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; diff --git a/src/constants/global-rules.js b/src/constants/global-rules.js index e82b118a..6dc75766 100644 --- a/src/constants/global-rules.js +++ b/src/constants/global-rules.js @@ -26,7 +26,8 @@ const globalRules = Object.freeze({ EXTENSION_FORMAT: 'extension-format', ZIP_CODE_REQUIRED: 'zip-code-required', 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; diff --git a/src/helpers/global-rule-definer.js b/src/helpers/global-rule-definer.js index f5a2335b..227fc0bb 100644 --- a/src/helpers/global-rule-definer.js +++ b/src/helpers/global-rule-definer.js @@ -123,6 +123,28 @@ function defineGlobalZipCodeRules() { 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 * @summary Define all global rules @@ -136,6 +158,6 @@ export default function defineGlobalRules() { defineGlobalPolicyNumberRules(); defineGlobalPolicyZipCodeRules(); defineGlobalZipCodeRules(); - + defineGlobalTpaSearchRules(); defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED)); } diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 9476a7ad..2eed76f9 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -27,14 +27,14 @@
@@ -52,10 +52,10 @@ @invalidSubmit="onInvalidSubmit"> @@ -135,53 +135,8 @@ import widgetFields from '@/constants/cms-widget-fields.js'; import { shallowRef } from 'vue'; import { toTitleCase, toDisplayPhoneNumber } from '@/helpers/text-helper.js'; 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 { name: 'tpa-search', @@ -200,23 +155,35 @@ export default { }, mixins: [BaseFormMixin], async beforeRouteEnter(to, from, next) { - const { zipCode, filter, providers, providerNumber } = - await getInitialSearchData(); - const cmsContent = await fetchCmsContentForPage(to.query.issPage); - + const zipCode = useMainStore().order.customer.address.zipCode; + const cmsContentPromise = 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) => { - vm.setCmsContent(cmsContent); - vm.setInitialSearchData(zipCode, filter, providers, providerNumber); + vm.setCmsContent(resultMap.cmsContent); + vm.tpaSearchValue = ""; // TODO: use page data to set the value + vm.mapZipCode = zipCode; + vm.providers = resultMap.providers; }); }, data() { return { dataLoaded: false, - zipCode: null, + tpaSearchValue: null, mapZipCode: null, - filter: '', + radiusFilter: useMainStore().order.serviceLocation.tpaSearchRadius ?? 25, // TODO: use page data to set the value providers: [], - selectedProviderNumber: '', + selectedProviderNumber: useMainStore().order.serviceLocation.provider.providerNumber, reloadingProviders: false, additionalButtonData: { displayAvailabilityIndicators: false @@ -231,7 +198,7 @@ export default { siteFooter: 'SiteFooterWidget' }, 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? provider: globalRules.OPTION_REQUIRED }, @@ -239,17 +206,16 @@ export default { }; }, computed: { - filterOptions() { - const filterByAnswers = - this.getCmsContent( + radiusFilterOptions() { + const radiusFilterOptions = this.getCmsContent( this.widget.filterByQuestion, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS ) ?? []; - const filterByAnswersObj = {}; - [...filterByAnswers].forEach((answer) => { - filterByAnswersObj[answer.Name] = answer.Name; - }); - return filterByAnswersObj; + const options = {}; + for (const option of radiusFilterOptions) { + options[option.Name] = option.Text; + } + return options; }, tpaSearchQuestionLabel() { return this.getCmsContent( @@ -269,17 +235,17 @@ export default { widgetFields.TEXT_BLOCK_WIDGET.TEXT ); }, - radiusInMiles() { - return convertRadiusFilterToInteger(this.filter); - }, noNetworkShopsAlertHeaderText() { return this.getCmsContent( this.widget.noNetworkShopsAlert, widgetFields.ALERT_WIDGET.HEADLINE_TEXT )?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles); }, + radiusFilteredAndCappedProviders() { + return this.providers.filter((provider) => provider.distanceInMiles <= this.radiusFilter).slice(0, 5); + }, providerAddresses() { - return this.providers?.map((provider) => ({ + return this.radiusFilteredAndCappedProviders?.map((provider) => ({ title: provider.companyName, fullAddress: this.getFullProviderAddress(provider), addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)] @@ -287,7 +253,7 @@ export default { }, providerButtonData() { return ( - this.providers?.map((provider) => + this.radiusFilteredAndCappedProviders?.map((provider) => this.getShopButtonDataFromProvider(provider)) ?? [] ); }, @@ -298,14 +264,6 @@ export default { } }, watch: { - async filter() { - if (this.dataLoaded) { - await this.getProviderButtonData().then((providers) => { - this.providers = providers; - }); - this.mapZipCode = this.zipCode; - } - }, providers(newProviders) { if (this.dataLoaded) { this.selectedProviderNumber = @@ -318,7 +276,7 @@ export default { const provider = this.providers?.find((p) => p.providerNumber === newNumber); if (provider && this.dataLoaded) { useMainStore().updateServiceLocation({ - searchFilter: this.filter, + tpaSearchRadius: this.radiusFilter, zipCode: this.zipCode, provider: { providerNumber: provider?.providerNumber, @@ -342,13 +300,6 @@ export default { } }, methods: { - setInitialSearchData(zipCode, filter, providers, providerNumber) { - this.zipCode = zipCode; - this.mapZipCode = zipCode; - this.filter = filter; - this.providers = providers; - this.selectedProviderNumber = providerNumber; - }, getFullProviderAddress(provider) { const addressLine1 = this.getProviderAddress(provider); const addressLine2 = this.getProviderCityZipState(provider); @@ -379,14 +330,14 @@ export default { } return addressLine2; }, - async getProviderButtonData() { + async getProviderButtonData(shopName = "") { this.reloadingProviders = true; - const getTpaProvidersResult = await useMainStore().getTpaProviders( - this.zipCode, - this.radiusInMiles + const getTpaProvidersResult = await useMainStore().getTpaAndSafeliteProviders( + this.mapZipCode, + shopName ); this.reloadingProviders = false; - return getTpaProvidersResult?.data?.shopProviders ?? []; + return getTpaProvidersResult?.data ?? []; }, doNotSeeMyShopLinkClick() { this.mainStore.setBailout(bailoutMessage.doNotSeeMyShop()); @@ -396,8 +347,12 @@ export default { ); }, async searchClick() { - this.providers = await this.getProviderButtonData(); - this.mapZipCode = this.zipCode; + if (isNaN(this.tpaSearchValue)) { + this.providers = await this.getProviderButtonData(this.tpaSearchValue); + } else { + this.mapZipCode = this.tpaSearchValue; + this.providers = await this.getProviderButtonData(); + } }, forwardButtonAction() { if (this.selectedProviderIsSafeliteShop) { @@ -409,14 +364,6 @@ export default { .CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP; this.$router.navigate(scenario, this.$route); }, - getCustomValueFromString(str) { - switch (str) { - case 'radiusInMiles': - return this.radiusInMiles; - default: - return null; - } - }, getShopButtonDataFromProvider(provider) { const cellNumber = toDisplayPhoneNumber(provider?.phoneNumber); const distance = diff --git a/src/store/index.js b/src/store/index.js index 5fb37e57..48b73e89 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -142,7 +142,7 @@ export const getDefaultState = () => ({ appointmentType: null, isVehicleProtected: null, IsSafeliteProvider: null, - searchFilter: null, + tpaSearchRadius: null, provider: { providerNumber: null, address: { @@ -1032,23 +1032,21 @@ export const useMainStore = defineStore({ }); }, - getTpaProviders(zipCode, radius) { - const { carId, isBigTruck } = this.order.vehicle; - const damageType = this.damage.isRepair ? 'Repair' : 'Replace'; + getTpaAndSafeliteProviders(zipCode, provderNameSearchString = "") { const { parentAccountNumber } = this.order; - const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts); - const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null; - const safeliteOnly = false; + const carId = this.order.vehicle.carId; + const damageType = this.damage.isRepair ? 'Repair' : 'Install'; + + const parts = getLineItemsFlattened(this.order.lineItems.glassParts); + const partNumbers = parts.map((part) => part.partNumber).join(','); - let url = `${endpoints.GetProviders.url}/${zipCode}/${damageType}/${radius}/${parentAccountNumber}/${safeliteOnly}/${carId}`; - if (windshieldPartWithRecal) { - 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) => { globalMethods.callHttpClient({ - method: endpoints.GetProviders.method, + method: endpoints.GetTpaAndSafeliteProviders.method, endpoint: url }).then((response) => resolve(response), (error) => reject(error)); }); @@ -1822,7 +1820,7 @@ export const useMainStore = defineStore({ companyName: serviceLocationInfo.provider?.companyName, phoneNumber: serviceLocationInfo.provider?.phoneNumber }; - this.order.serviceLocation.searchFilter = serviceLocationInfo.searchFilter; + this.order.serviceLocation.tpaSearchRadius = serviceLocationInfo.tpaSearchRadius; }, resetState() { From 4f7cfbc32c3f06dde398d883ca9c3b9a40748580 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Mon, 9 Feb 2026 10:34:10 -0500 Subject: [PATCH 02/55] Merge Conflict Cleanup and Subheader styling --- src/constants/error-messages.js | 6 +++--- src/constants/global-rules.js | 2 +- src/layouts/tpa-search/tpa-search.vue | 23 +++++++++++++++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index c63369af..1705ff1f 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -67,10 +67,10 @@ const errorMessages = Object.freeze({ DATE_REQUIRED: 'Please select a date', TIME_REQUIRED: 'Please select an appointment time.', PRIMARY_PHONE_REQUIRED: 'Primary phone number is required.', - BAILOUT_EMAIL_ADDRESS_REQUIRED: 'Email address is required.' - TPA_SEARCH_ZIP_FORMAT: 'Please enter a valid Zip code.' + BAILOUT_EMAIL_ADDRESS_REQUIRED: 'Email address is required.', + TPA_SEARCH_ZIP_FORMAT: 'Please enter a valid Zip code.', TPA_SEARCH_SHOP_FORMAT: 'Please enter a valid shop name.', - TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.', + TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.' }); export default errorMessages; diff --git a/src/constants/global-rules.js b/src/constants/global-rules.js index 1123fd02..840f8db3 100644 --- a/src/constants/global-rules.js +++ b/src/constants/global-rules.js @@ -28,7 +28,7 @@ const globalRules = Object.freeze({ ZIP_CODE_SEARCH_FORMAT: 'zip-code-search-format', OPTION_REQUIRED: 'option-required', PRIMARY_PHONE_REQUIRED: 'primary-phone-required', - BAILOUT_EMAIL_ADDRESS_REQUIRED: 'bailout-email-address-required' + BAILOUT_EMAIL_ADDRESS_REQUIRED: 'bailout-email-address-required', TPA_SEARCH_FORMAT: 'tpa-search-format' }); diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 5a67cc20..15f54c50 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -15,13 +15,13 @@
@@ -389,4 +389,23 @@ export default { .darker-gray { color: map-get($colors, 'darker-gray'); } + +#tpaSearchQuestionLabel { + margin-bottom: 10px; +} +:deep(.tpa-search-container .textbox-question .input-wrapper.has-search-icon) { + #tpaSearchQuestionFieldInput { + border-radius: 72px; + border-color: #0070d1; + box-shadow: rgba(0, 0, 0, 0.2) 0rem 0.0625rem .25rem 0rem; + } + button { + border-radius: 0 72px 72px 0; + border-color: #0070d1; + &:focus { + border-width: 1px; + } + } +} + From 46e3ef883f78b0dc19bd64889843ade7953278d3 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Mon, 9 Feb 2026 11:18:17 -0500 Subject: [PATCH 03/55] Search input styling --- .../textbox-question/textbox-question.vue | 25 +++++++++++++------ src/layouts/tpa-search/tpa-search.vue | 14 ----------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/digital-components/textbox-question/textbox-question.vue b/src/digital-components/textbox-question/textbox-question.vue index 85bef043..d57451cf 100644 --- a/src/digital-components/textbox-question/textbox-question.vue +++ b/src/digital-components/textbox-question/textbox-question.vue @@ -310,22 +310,31 @@ input::-webkit-date-and-time-value { .input-wrapper { position: relative; &.has-search-icon { + display:flex; + .form-control { + &:focus, &:focus-within { + border: 1px solid #0070d1; + } + max-height: 48px; + } input[type='text'] { - border-radius: $border-radius; + border-color: #0070d1; + box-shadow: rgba(0, 0, 0, 0.2) 0rem 0.0625rem .25rem 0rem; + border-right: none; + border-radius: 72px 0 0 72px; + height: 48px; + &:focus, &:focus-within { + border-right:none; + } } button[type='submit'] { - position: absolute; - top: 50%; - transform: translateY(-50%); - right: 0; background-image: url($svg-search-icon); background-repeat: no-repeat; background-position: center; - border-radius: 0 $border-radius-lg $border-radius-lg 0; + border-radius: 0 72px 72px 0; background-color: $blue-100; width: 2.75rem; - height: 100%; - border: 1px solid $gray-500; + border: 1px solid #0070d1; border-left: none; display: flex; } diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 15f54c50..9972c8d6 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -393,19 +393,5 @@ export default { #tpaSearchQuestionLabel { margin-bottom: 10px; } -:deep(.tpa-search-container .textbox-question .input-wrapper.has-search-icon) { - #tpaSearchQuestionFieldInput { - border-radius: 72px; - border-color: #0070d1; - box-shadow: rgba(0, 0, 0, 0.2) 0rem 0.0625rem .25rem 0rem; - } - button { - border-radius: 0 72px 72px 0; - border-color: #0070d1; - &:focus { - border-width: 1px; - } - } -} From 9ba52b9d48174d5fbd558d7dcfd1ca896e7feda2 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Mon, 9 Feb 2026 11:19:45 -0500 Subject: [PATCH 04/55] Map styling --- src/layouts/tpa-search/tpa-search.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 9972c8d6..3c018f47 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -394,4 +394,8 @@ export default { margin-bottom: 10px; } +#map { + height: 435px; +} + From f85245f02dd396b56e363a0d61f6eecd300e6457 Mon Sep 17 00:00:00 2001 From: Katie Kroell Date: Mon, 9 Feb 2026 12:32:31 -0500 Subject: [PATCH 05/55] initial styling changes --- .../site-sub-header/site-sub-header.vue | 4 +- .../service-packages/service-packages.vue | 49 +++++++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/iss-components/site-sub-header/site-sub-header.vue b/src/iss-components/site-sub-header/site-sub-header.vue index 8695f341..1dc202c3 100644 --- a/src/iss-components/site-sub-header/site-sub-header.vue +++ b/src/iss-components/site-sub-header/site-sub-header.vue @@ -169,8 +169,8 @@ p { color: $darker-gray; } &.service-packages-subtext { - font-weight: 500; - line-height: 24px; + font-weight: 400; + line-height: 26px; span { font-size: 1rem; } diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue index 1bed0a7c..2bfe5eea 100644 --- a/src/layouts/service-packages/service-packages.vue +++ b/src/layouts/service-packages/service-packages.vue @@ -11,9 +11,8 @@
-

+ + :isForwardButtonHidden="true" + @backClicked="navigateBack" /> +

Date: Mon, 9 Feb 2026 14:21:37 -0500 Subject: [PATCH 06/55] Shop selection styling --- .../tpa-shop-list-button.vue | 245 ++++++++++++++++++ src/layouts/tpa-search/tpa-search.vue | 10 +- 2 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue diff --git a/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue b/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue new file mode 100644 index 00000000..b8b0c803 --- /dev/null +++ b/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue @@ -0,0 +1,245 @@ + + + + + \ No newline at end of file diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 3c018f47..e684bb20 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -73,7 +73,7 @@ id="selectProviderQuestion" v-model="selectedProviderNumber" buttonTypeString="shopListButton" - :buttonTypeObject="shopListButton" + :buttonTypeObject="tpaShopListButton" class="radioQuestion" :answers="providerButtonData" groupName="chooseShop" @@ -123,7 +123,7 @@ import textLink from '@/ux-components/text-link/text-link.vue'; import alert from '@/ux-components/alert/alert.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import googleMap from '@/iss-components/google-map/google-map.vue'; -import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue'; +import tpaShopListButton from '@/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue'; import loader from '@/ux-components/loader/loader.vue'; // Supporting files @@ -202,7 +202,7 @@ export default { filter: globalRules.OPTION_REQUIRED, // TODO do we even need this? provider: globalRules.OPTION_REQUIRED }, - shopListButton: shallowRef(shopListButton) + tpaShopListButton: shallowRef(tpaShopListButton) }; }, computed: { @@ -246,7 +246,7 @@ export default { }, providerAddresses() { return this.radiusFilteredAndCappedProviders?.map((provider) => ({ - title: provider.companyName, + title: provider.companyName.toLowerCase(), fullAddress: this.getFullProviderAddress(provider), addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)] })) ?? []; @@ -373,7 +373,7 @@ export default { : null; return { - buttonLabel: provider?.companyName ?? '', + buttonLabel: provider?.companyName.toLowerCase() ?? '', buttonLabelSubCopy: distance === null ? '' : `${distance} mi`, buttonBodyCopy: `${this.getFullProviderAddress(provider)}
${ cellNumber ?? '' From 979f2015f578521b4b1ae80a6813e4ce49b3a713 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Mon, 9 Feb 2026 14:38:45 -0500 Subject: [PATCH 07/55] Need Help link styling --- src/layouts/tpa-search/tpa-search.vue | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index e684bb20..215f0750 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -105,6 +105,15 @@ :isForwardActionDisabled="!meta.valid" @ForwardClicked="forwardButtonAction" @backClicked="navigateBack" /> +
@@ -195,7 +204,8 @@ export default { filterByQuestion: 'FilterByQuestion', noNetworkShopsAlert: 'NoNetworkShopsAlertWidget', shopNotListedLink: 'ShopNotListedLink', - siteFooter: 'SiteFooterWidget' + siteFooter: 'SiteFooterWidget', + needHelpLink: 'NeedHelpLink' }, rules: { tpaSearch: globalRules.TPA_SEARCH_FORMAT, @@ -223,6 +233,9 @@ export default { widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT ); }, + needHelpLinkText() { + return this.getCmsContent(this.widget.needHelpLink, widgetFields.TEXT_BLOCK_WIDGET.TEXT); + }, searchInstructionsText() { return this.getCmsContent( this.widget.searchInstructions, @@ -398,4 +411,8 @@ export default { height: 435px; } +.need-help-link-container { + margin-bottom: 35px; +} + From 0017c49a6d16f17e4ab99097d68da3d955e6f369 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Mon, 9 Feb 2026 14:40:17 -0500 Subject: [PATCH 08/55] Need help functionality --- src/layouts/tpa-search/tpa-search.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 215f0750..befd7772 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -112,7 +112,7 @@ linkType="navigation" :text="needHelpLinkText" href="#" - @clickEvent="handleNeedHelpClick" /> + @clickEvent="needHelpLinkClick" /> @@ -352,7 +352,7 @@ export default { this.reloadingProviders = false; return getTpaProvidersResult?.data ?? []; }, - doNotSeeMyShopLinkClick() { + needHelpLinkClick() { this.mainStore.setBailout(bailoutMessage.RequestCallback()); this.$router.navigate( this.navigationScenarios.CLICKED_NEED_HELP, From a01c66aaede79f853955485716c32498582bad48 Mon Sep 17 00:00:00 2001 From: Katie Kroell Date: Mon, 9 Feb 2026 15:15:25 -0500 Subject: [PATCH 09/55] styling WIP --- .../button-question/button-question.vue | 9 ++++++++- .../service-package-question.vue | 4 ++-- .../service-package-radio/service-package-radio.vue | 5 +++++ src/layouts/service-packages/service-packages.vue | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index ca731b44..e8e552b3 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -127,6 +127,7 @@ export default { isOverflowScrollable: Boolean, isWide: Boolean, isCashOrInsurance: Boolean, + isHorizontalLayout: Boolean, modelValue: [Array, Number, String], value: [Number, String], validationRules: String, @@ -211,7 +212,10 @@ export default { classes = 'ui-radio d-flex'; break; case 'servicePackageRadio': - classes = 'package-main'; + classes = 'package-main d-flex flex-column'; + if (this.isHorizontalLayout) { + classes += ' flex-md-row gap-3'; + } break; case 'providerPrefRadio': classes = 'option-main'; @@ -231,6 +235,9 @@ export default { break; case 'servicePackageRadio': classes = 'package-wrapper'; + if (this.isHorizontalLayout) { + classes += ' flex-fill'; + } break; case 'providerPrefRadio': classes = 'option-wrapper'; diff --git a/src/layouts/service-packages/service-package-question/service-package-question.vue b/src/layouts/service-packages/service-package-question/service-package-question.vue index 9c0ee168..fa00e58a 100644 --- a/src/layouts/service-packages/service-package-question/service-package-question.vue +++ b/src/layouts/service-packages/service-package-question/service-package-question.vue @@ -7,7 +7,8 @@ buttonTypeString="servicePackageRadio" :buttonTypeObject="servicePackageRadio" :validationRules="validationRules" - :isRequired="isRequired" /> + :isRequired="isRequired" + :isHorizontalLayout="true" /> + + diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index 48776a09..4f3ad667 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -143,7 +143,8 @@ export default { width: 100%; padding: 1rem; border: 1px solid $gray-300; - box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2); + //box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2); + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); border-radius: 0.5rem; overflow: hidden; min-height: 60px; diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue index d52893b5..d5c959ec 100644 --- a/src/layouts/service-packages/service-packages.vue +++ b/src/layouts/service-packages/service-packages.vue @@ -24,21 +24,23 @@ isRequired @vapsItemsSelected="vapsItemsSelectedAction" @link-event="openModalAction" /> - +
+ +
-

+

From fb7b1ce5edafc043d964f6383edecb21819c597a Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Tue, 10 Feb 2026 15:36:57 -0500 Subject: [PATCH 11/55] Final styling --- .../tpa-shop-list-button.vue | 18 ++++------ src/layouts/tpa-search/tpa-search.vue | 36 +++++++++++++++---- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue b/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue index b8b0c803..47a16733 100644 --- a/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue +++ b/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue @@ -5,7 +5,7 @@ + buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 label-margin-bottom no-hover">
@@ -121,19 +121,10 @@ export default { input[type="checkbox"] { position: static; //override bootstrap - // &:focus-visible + .list-button-content { - // box-shadow: 0 0 0 2.5px $blue; - // } - // &:focus + .list-button-content { - // box-shadow: 0 0 0 2.5px $blue; - // } &:checked + .list-button-content { - background: $blue-100; - box-shadow: 0 0 0 1px $blue; + background: #e7f1f6; + border: 1px solid #0070d1; } - // &:checked:focus + .list-button-content { - // box-shadow: 0 0 0 2.5px $blue; - // } } } .list-button-content { @@ -242,4 +233,7 @@ export default { text-align: left; } } +div.col:not(:last-child) >.label-margin-bottom{ + margin-bottom: 16px; +} \ No newline at end of file diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index befd7772..01c855ef 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -99,7 +99,9 @@ :text="shopNotListedModalLink" @clickEvent="doNotSeeMyShopLinkClick" />
+
{ vm.setCmsContent(resultMap.cmsContent); - vm.tpaSearchValue = ""; // TODO: use page data to set the value vm.mapZipCode = zipCode; - vm.providers = resultMap.providers; + vm.providers = resultMap.providers; + if (pageData) { + vm.tpaSearchValue = pageData?.tpaSearchValue ?? vm.mapZipCode; + vm.radiusFilter = pageData?.radiusFilter ?? 25; + } }); }, data() { return { dataLoaded: false, + pageData: null, tpaSearchValue: null, mapZipCode: null, - radiusFilter: useMainStore().order.serviceLocation.tpaSearchRadius ?? 25, // TODO: use page data to set the value + radiusFilter: null, providers: [], selectedProviderNumber: useMainStore().order.serviceLocation.provider.providerNumber, reloadingProviders: false, @@ -252,7 +260,7 @@ export default { return this.getCmsContent( this.widget.noNetworkShopsAlert, widgetFields.ALERT_WIDGET.HEADLINE_TEXT - )?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles); + )?.replaceAll('{custom:radiusInMiles}', this.radiusFilter + " miles"); }, radiusFilteredAndCappedProviders() { return this.providers.filter((provider) => provider.distanceInMiles <= this.radiusFilter).slice(0, 5); @@ -289,7 +297,6 @@ export default { const provider = this.providers?.find((p) => p.providerNumber === newNumber); if (provider && this.dataLoaded) { useMainStore().updateServiceLocation({ - tpaSearchRadius: this.radiusFilter, zipCode: this.zipCode, provider: { providerNumber: provider?.providerNumber, @@ -375,6 +382,10 @@ export default { ? this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP : this.navigationScenarios .CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP; + // Do not save the zip code as the tpa search value, prevents an outdated zipcode from being used + // as the default search value if customer goes back and changes zip earlier in the flow + const tpaSearchValueToSave = this.tpaSearchValue === this.mapZipCode ? '' : this.tpaSearchValue; + this.savePageDataToStore(issPageValues.TPA_SEARCH, {tpaSearchValue: tpaSearchValueToSave, radiusFilter: this.radiusFilter}); this.$router.navigate(scenario, this.$route); }, getShopButtonDataFromProvider(provider) { @@ -411,7 +422,20 @@ export default { height: 435px; } +#providerSelectionForm { + #alertNoNetworkProviders { + :deep(.alert) { + margin-bottom: 0px; + } + } +} + +hr { + margin: 22px 0; +} + .need-help-link-container { + margin-top:30px; margin-bottom: 35px; } From 2fa950f32f550ef7fd813e69e3d1ecc1f060ecf6 Mon Sep 17 00:00:00 2001 From: Katie Kroell Date: Tue, 10 Feb 2026 15:48:15 -0500 Subject: [PATCH 12/55] styling updates --- .../service-package-radio.vue | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index 4f3ad667..4e1cf8f1 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -10,9 +10,6 @@

-

+
+ +
@@ -142,7 +144,7 @@ export default { cursor: pointer; width: 100%; padding: 1rem; - border: 1px solid $gray-300; + border: 0.666667px solid $gray-300; //box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); border-radius: 0.5rem; @@ -163,7 +165,7 @@ export default { top: 5px; margin-right: 1rem; border-radius: 50%; - border: 1px solid $gray-500; + border: 0.666667px solid $gray-500; width: 16px; height: 16px; min-width: 16px; @@ -184,7 +186,7 @@ export default { &:hover { + .package-label { &:before { - border: 1px solid #8e9292; + border: 0.666667px solid #8e9292; box-shadow: 0px 0px 0px 4px #9fcee6, 0px 1px 4px rgba(0, 0, 0, 0.2); } } @@ -206,20 +208,20 @@ export default { } + .package-label { - background-color: $blue-100; - border: 1px solid $blue; + background-color: $background-color-selected; + border: 0.666667px solid $heritage-blue-secondary; max-height: 500px; } + .package-label { &:before { - box-shadow: 0px 0px 0px 1px $blue; + box-shadow: 0px 0px 0px 1px $heritage-blue-secondary; } } + .package-label { &:after { - background: $blue; + background: $heritage-blue-secondary; } } } @@ -227,16 +229,18 @@ export default { &:focus { + .package-label { &:before { - border: 2px solid $blue; + border: 2px solid $heritage-blue-secondary; } } } } .package-footer { - color: $red; - font-weight: $font-weight-bold; // 600 in fmg - margin-top: 0.5rem; // not in fmg + color: #af2117; + font-weight: $font-weight-bold; + margin-top: 20px; + font-size: 0.875rem; + line-height: 21px; } .package-specs { @@ -249,32 +253,29 @@ export default { max-height: 500px; } p { - font-weight: $font-weight-bold; // 600 in fmg + color: #4d4e53; + font-weight: 600; display: flex; justify-content: space-between; - span { - &.pricing-info { - color: $green; - font-size: 0.875rem; - } - } - &.sub-label { color: $green; text-transform: uppercase; font-size: 0.75rem; + font-weight: $font-weight-bold; } } ul { - margin: 1rem 0 0 -.6rem; // .9375rem in fmg + margin:0.5rem 0 0 -.6rem; // .9375rem in fmg padding: 0; + color: $darker-gray; li { margin-bottom: 0.5rem; font-size: 0.875rem; line-height: 1.5rem; + color: $darker-gray; a { line-height: 1.5rem; @@ -302,6 +303,18 @@ export default { } } } + + .pricing-info { + display: block; + span { + &.price { + color: #075f35; + font-size: 0.875rem; + font-weight: $font-weight-bold; + display: inline-block; + } + } + } } } From e3114879d4dd6d66a67cbca0bc6010e8ac0a529d Mon Sep 17 00:00:00 2001 From: Katie Kroell Date: Tue, 10 Feb 2026 15:55:09 -0500 Subject: [PATCH 13/55] fix width --- src/layouts/service-packages/service-packages.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue index d5c959ec..fc735992 100644 --- a/src/layouts/service-packages/service-packages.vue +++ b/src/layouts/service-packages/service-packages.vue @@ -220,7 +220,7 @@ export default { .iss-heritage-container-width { .service-packages-container { @media (min-width: 992px) { - width: 85%; // REVISIT + width: 100%; } position: relative; min-height: 1px; From 706b3429f59d497095f319ac35cfeeeaa1504dd5 Mon Sep 17 00:00:00 2001 From: Katie Kroell Date: Wed, 11 Feb 2026 12:10:52 -0500 Subject: [PATCH 14/55] styling updates --- .../site-sub-header/site-sub-header.vue | 2 +- .../service-package-radio.vue | 24 +++++++------------ .../service-packages/service-packages.vue | 9 ++++--- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/iss-components/site-sub-header/site-sub-header.vue b/src/iss-components/site-sub-header/site-sub-header.vue index 1dc202c3..976cf8df 100644 --- a/src/iss-components/site-sub-header/site-sub-header.vue +++ b/src/iss-components/site-sub-header/site-sub-header.vue @@ -117,7 +117,7 @@ export default { alternateFormatting() { // override for service-packages unique style if (this.issContainingPage?.toLowerCase() === 'service-packages') { - return 'service-packages-subtext my-4'; + return 'service-packages-subtext'; } return this.darkGraySubText ? 'dark-gray' : 'light-gray'; } diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index 4e1cf8f1..efdf6d7b 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -144,10 +144,10 @@ export default { cursor: pointer; width: 100%; padding: 1rem; - border: 0.666667px solid $gray-300; + border: 0.666667px solid #dddddd; //box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); - border-radius: 0.5rem; + border-radius: 0.25rem; overflow: hidden; min-height: 60px; max-height: 100px; @@ -165,7 +165,7 @@ export default { top: 5px; margin-right: 1rem; border-radius: 50%; - border: 0.666667px solid $gray-500; + border: 0.666667px solid #767676; width: 16px; height: 16px; min-width: 16px; @@ -186,7 +186,7 @@ export default { &:hover { + .package-label { &:before { - border: 0.666667px solid #8e9292; + border: 0.666667px solid #767676; box-shadow: 0px 0px 0px 4px #9fcee6, 0px 1px 4px rgba(0, 0, 0, 0.2); } } @@ -215,24 +215,18 @@ export default { + .package-label { &:before { - box-shadow: 0px 0px 0px 1px $heritage-blue-secondary; + box-shadow: 0px 0px 0px 1px $heritage-blue-primary; + border: none; } } + .package-label { &:after { - background: $heritage-blue-secondary; + background: $heritage-blue-primary; } } } - &:focus { - + .package-label { - &:before { - border: 2px solid $heritage-blue-secondary; - } - } - } } .package-footer { @@ -267,12 +261,12 @@ export default { } ul { - margin:0.5rem 0 0 -.6rem; // .9375rem in fmg + margin:0.5rem 0 0 -.6rem; padding: 0; color: $darker-gray; li { - margin-bottom: 0.5rem; + margin-bottom: 0.25rem; font-size: 0.875rem; line-height: 1.5rem; color: $darker-gray; diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue index fc735992..b6ab110f 100644 --- a/src/layouts/service-packages/service-packages.vue +++ b/src/layouts/service-packages/service-packages.vue @@ -11,7 +11,7 @@
@@ -236,12 +236,15 @@ export default { text-align: center; margin-top: 0.75rem; padding: 0px; + p { + margin-bottom: 18px; + } } } } .disclaimer { margin: 0.5rem 0 1.5rem 0; - padding-left: 3rem; + //padding-left: 3rem; :deep(.external-text) { text-decoration: none; } @@ -253,6 +256,6 @@ export default { } .continue-button { - width: 60% + width: 50% } From 2cdf15cb951fc67ed0b9561b969ea2725e0d360b Mon Sep 17 00:00:00 2001 From: "US\\katie.kroell" Date: Wed, 11 Feb 2026 15:48:49 -0500 Subject: [PATCH 15/55] more styling fun --- .../service-package-radio.vue | 116 +++++++++++------- 1 file changed, 73 insertions(+), 43 deletions(-) diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index efdf6d7b..160826ab 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -7,15 +7,24 @@ :class="[buttonLabelSubCopy ? 'has-subheader' : '']" for="testradio">
-
-

- -

-

-

+
+
+

+ +

+

+

+
+
+ +
    @@ -50,13 +59,14 @@ + class="package-footer fw-bold caption mt-4 mr-3" + v-html="buttonFooterCopy"> +
-
+
@@ -123,6 +133,21 @@ export default { .package-wrapper { margin: 0.5rem 0; + .button-label { + display: flex; + &:before { + content: ""; + position: relative; + top: 5px; + margin-right: 1rem; + border-radius: 50%; + border: 0.666667px solid #767676; + width: 16px; + height: 16px; + min-width: 16px; + } + } + label { display: block; height: 100%; @@ -130,6 +155,8 @@ export default { .package-label { height: 100%; + display: flex; + flex-direction: column; } input[type="radio"] { @@ -139,11 +166,12 @@ export default { + .package-label { display: flex; + flex-direction: column; align-items: flex-start; position: relative; cursor: pointer; width: 100%; - padding: 1rem; + padding: 10px; border: 0.666667px solid #dddddd; //box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); @@ -159,23 +187,11 @@ export default { min-height: 86px; } - &:before { - content: ""; - position: relative; - top: 5px; - margin-right: 1rem; - border-radius: 50%; - border: 0.666667px solid #767676; - width: 16px; - height: 16px; - min-width: 16px; - } - &:after { content: ""; position: absolute; - left: 19px; - top: 24px; + left: 13px; + top: 18px; border-radius: 50%; width: 10px; height: 10px; @@ -184,7 +200,7 @@ export default { } &:hover { - + .package-label { + + .button-label { &:before { border: 0.666667px solid #767676; box-shadow: 0px 0px 0px 4px #9fcee6, 0px 1px 4px rgba(0, 0, 0, 0.2); @@ -213,7 +229,7 @@ export default { max-height: 500px; } - + .package-label { + + .button-label { &:before { box-shadow: 0px 0px 0px 1px $heritage-blue-primary; border: none; @@ -238,8 +254,8 @@ export default { } .package-specs { - display: flex; - flex-direction: column; + //display: flex; + flex-direction: row; width: 100%; max-height: 0; transition: all 1s ease; @@ -249,7 +265,7 @@ export default { p { color: #4d4e53; font-weight: 600; - display: flex; + //display: flex; justify-content: space-between; &.sub-label { @@ -261,7 +277,7 @@ export default { } ul { - margin:0.5rem 0 0 -.6rem; + margin: 0.5rem 0 0 1.25rem; padding: 0; color: $darker-gray; @@ -293,19 +309,33 @@ export default { display: none; @include media-breakpoint-up(md) { display: flex; - flex-direction: column; + flex-direction: row; } } } - .pricing-info { - display: block; - span { - &.price { - color: #075f35; - font-size: 0.875rem; - font-weight: $font-weight-bold; - display: inline-block; + .pricing-info-desktop { + @include media-breakpoint-down(md) { + display: none; + span { + &.price { + color: #075f35; + font-size: 0.875rem; + font-weight: $font-weight-bold; + } + } + } + } + + .pricing-info-mobile { + @include media-breakpoint-up(md) { + display: none; + span { + &.price { + color: #075f35; + font-size: 0.875rem; + font-weight: $font-weight-bold; + } } } } From f3ab2d9886225cdf7354bf8cb3f054ef0858ccfd Mon Sep 17 00:00:00 2001 From: "US\\katie.kroell" Date: Thu, 12 Feb 2026 10:12:04 -0500 Subject: [PATCH 16/55] fix price styling --- .../service-package-radio.vue | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index 160826ab..e2ccda45 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -254,8 +254,7 @@ export default { } .package-specs { - //display: flex; - flex-direction: row; + flex: 0; width: 100%; max-height: 0; transition: all 1s ease; @@ -265,7 +264,6 @@ export default { p { color: #4d4e53; font-weight: 600; - //display: flex; justify-content: space-between; &.sub-label { @@ -308,21 +306,24 @@ export default { .hide-when-closed { display: none; @include media-breakpoint-up(md) { - display: flex; - flex-direction: row; + display: block; } } } .pricing-info-desktop { + display: flex; + flex: 1; + align-items: flex-end; @include media-breakpoint-down(md) { display: none; - span { - &.price { - color: #075f35; - font-size: 0.875rem; - font-weight: $font-weight-bold; - } + } + span { + &.price { + color: #075f35; + font-size: 0.875rem; + font-weight: $font-weight-bold; + padding: 1rem 0 1rem 0; } } } @@ -330,12 +331,12 @@ export default { .pricing-info-mobile { @include media-breakpoint-up(md) { display: none; - span { - &.price { - color: #075f35; - font-size: 0.875rem; - font-weight: $font-weight-bold; - } + } + span { + &.price { + color: #075f35; + font-size: 0.875rem; + font-weight: $font-weight-bold; } } } From 3edca952cb60c9545d14530b060f6e9eb2a68eba Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Thu, 12 Feb 2026 11:38:30 -0600 Subject: [PATCH 17/55] Initial setup for migrating to latest eslint and updated to latest eslint config format. --- .eslintrc.js | 59 - eslint.config.js | 143 + package-lock.json | 8196 ++++++++++++++++++++------------------------- package.json | 16 +- 4 files changed, 3769 insertions(+), 4645 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 eslint.config.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 3ced6a6d..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,59 +0,0 @@ -module.exports = { - env: { - browser: true, - jest: true - }, - parserOptions: { - ecmaVersion: 'latest' - }, - extends: [ - 'eslint-config-airbnb-base', - 'plugin:vue/vue3-recommended', - 'plugin:jsdoc/recommended' - ], - rules: { - 'linebreak-style': 'off', - 'vue/component-definition-name-casing': ['warn', 'kebab-case'], - 'vue/require-default-prop': 'off', - 'vue/attribute-hyphenation': ['warn', 'never'], - 'vue/v-on-event-hyphenation': ['warn', 'never'], - 'object-curly-newline': ['error', { consistent: true }], - 'function-paren-newline': ['error', 'multiline'], - 'operator-linebreak': ['error', 'before', { overrides: { '=': 'after' } }], - 'implicit-arrow-linebreak': ['off'], - 'comma-dangle': ['error', 'never'], - indent: ['error', 4, { SwitchCase: 1 }], - 'max-len': ['error', { code: 160 }], - 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], - 'vue/html-indent': 'off', - 'vue/html-closing-bracket-newline': ['error', { - singleline: 'never', - multiline: 'never' - }], - 'jsdoc/check-tag-names': ['error', { - definedTags: ['store', 'endpoint', 'category', 'subcategory', 'remarks'] - }], - 'jsdoc/require-jsdoc': 0, - 'vue/html-self-closing': ['error', { - html: { - void: 'any', - normal: 'any', - component: 'any' - }, - svg: 'always', - math: 'always' - }], - 'import/extensions': ['error', 'always', { js: 'ignorePackages' }], - 'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['item'] }], - 'no-restricted-syntax': ['off', 'ForOfStatement'], - 'no-return-await': 'off' - }, - settings: { - 'import/resolver': { - alias: { - map: [['@', './src/']], - extensions: ['.js', '.vue'] - } - } - } -}; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..de884494 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,143 @@ +import { globalIgnores } from 'eslint/config'; +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'; +import pluginVue from 'eslint-plugin-vue'; +//import pluginVitest from '@vitest/eslint-plugin'; +import js from '@eslint/js'; +import globals from 'globals'; +import stylistic from '@stylistic/eslint-plugin'; +import jsdoc from 'eslint-plugin-jsdoc'; + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{vue,ts,js,mts,tsx}'], + }, + + js.configs.recommended, + jsdoc.configs['flat/recommended-mixed'], + + // Add @stylistic configuration + stylistic.configs.customize({ + indent: 4, + quotes: 'single', + semi: true, + // Add any other stylistic preferences here + }), + + ...pluginVue.configs['flat/essential'], + vueTsConfigs.recommended, + { + name: 'app/global-rules', + files: ['**/*.{js,ts,vue}'], + rules: { + 'eqeqeq': ['error', 'smart'], + 'no-nested-ternary': 'error', + 'no-param-reassign': 'error', + 'jsdoc/require-jsdoc': 'off', + '@stylistic/comma-dangle': 'off', + '@stylistic/max-len': ['error', { + code: 160, + tabWidth: 4, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true + }], + + 'vue/max-len': ['error', { + code: 160, + template: 160, + tabWidth: 4, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreHTMLTextContents: false + }], + } + }, + + { + name: 'app/custom-vue-rules', + files: ['**/*.vue'], + rules: { + 'vue/block-lang': ['error', { + script: { + lang: ['js', 'ts'], + allowNoLang: true // Still allow - \ No newline at end of file diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 01c855ef..13e9a767 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -415,11 +415,11 @@ export default { } #tpaSearchQuestionLabel { - margin-bottom: 10px; + margin-bottom: .625rem; } #map { - height: 435px; + height: 27.1875rem; } #providerSelectionForm { @@ -431,12 +431,12 @@ export default { } hr { - margin: 22px 0; + margin: 1.375rem 0; } .need-help-link-container { - margin-top:30px; - margin-bottom: 35px; + margin-top:1.875rem; + margin-bottom: 2.1875rem; } From dca9c93efb5383a721c12158ec99b959d6fbdf16 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Thu, 12 Feb 2026 14:49:44 -0500 Subject: [PATCH 21/55] Update unit tests for tpa-search --- src/layouts/tpa-search/tpa-search.spec.js | 2225 ++++++++------------- 1 file changed, 830 insertions(+), 1395 deletions(-) diff --git a/src/layouts/tpa-search/tpa-search.spec.js b/src/layouts/tpa-search/tpa-search.spec.js index 393938b8..06e6f8e0 100644 --- a/src/layouts/tpa-search/tpa-search.spec.js +++ b/src/layouts/tpa-search/tpa-search.spec.js @@ -1,1031 +1,511 @@ +/* eslint-disable max-len */ // Components -import { shallowMount } from '@vue/test-utils'; -import { createTestingPinia } from '@pinia/testing'; import tpaSearch from '@/layouts/tpa-search/tpa-search.vue'; // Supporting Files -import baseMixin from '@/mixins/base-mixin'; +import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; import { getMountOptions } from '@/helpers/unit-test-helper.js'; -import { useMainStore } from '@/store'; -import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; +import { createTestingPinia } from '@pinia/testing'; +import navigationScenarios from '@/router/router-constants/navigation-scenarios.js'; import settleAllPromises from '@/helpers/layout-helper.js'; +import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; +import { useMainStore } from '@/store'; +import { toTitleCase, toDisplayPhoneNumber } from '@/helpers/text-helper.js'; +import bailoutMessage from '@/constants/bailoutMessage'; import globalRules from '@/constants/global-rules.js'; -import widgetFields from '@/constants/cms-widget-fields.js'; -import navigationScenarios from '@/router/router-constants/navigation-scenarios'; -// Mock fetchCmsContentForPage -jest.mock('@/helpers/cms-content-helper', () => ({ - fetchCmsContentForPage: jest.fn(), - doesCopyContainRouterLink: jest.fn() -})); - -// Mock our module for promises. jest.mock('@/helpers/layout-helper.js', () => jest.fn()); -function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRunAfterInitializingStore = () => {}) { +jest.mock('@/helpers/cms-content-helper', () => ({ + fetchCmsContentForPage: jest.fn(), + setupModalLinks: jest.fn(), + processIfStatements: jest.fn() +})); + +jest.mock('@/helpers/text-helper', () => ({ + toTitleCase: jest.fn((text) => text?.toLowerCase()?.replace(/(^|\s|-)\S/g, (letter) => letter.toUpperCase()) ?? ''), + toDisplayPhoneNumber: jest.fn((phone) => phone ?? '') +})); + +const mockMixin = { + methods: { + getCmsContent: jest.fn().mockImplementation(() => ''), + setCmsContent: jest.fn(), + onSubmit: jest.fn(), + onInvalidSubmit: jest.fn(), + navigateBack: jest.fn(), + savePageDataToStore: jest.fn() + } +}; + +const footerStub = { + render: () => {}, + methods: { + updateButtonText: jest.fn() + } +}; + +const loaderStub = { + render: () => {}, + template: '
' +}; + +function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}) { const mountOptions = getMountOptions({ router: { navigate: jest.fn() + }, + route: { + query: { issPage: 'tpa-search' } } }); + mountOptions.global.stubs = { + ...mountOptions.global.stubs, + siteFooter: footerStub, + siteHeader: true, + textboxQuestion: true, + dropdownQuestion: true, + buttonQuestion: true, + textLink: true, + alert: true, + googleMap: true, + loader: loaderStub + }; + const testingPinia = createTestingPinia({ initialState: { - main: mainInitialState + main: { + order: { + customer: { + address: { + zipCode: '43215' + } + }, + serviceLocation: { + provider: { + providerNumber: '' + } + } + }, + ...mainInitialState + } } }); useMainStore(testingPinia); - methodToRunAfterInitializingStore(); + methodToRun(); mountOptions.global.plugins = [testingPinia]; - mountOptions.data = () => (initialData); + mountOptions.mixins = [mockMixin]; + mountOptions.data = () => ({ + dataLoaded: true, + ...initialData + }); - const apiResponses = { cmsContent: {} }; - - settleAllPromises.mockImplementation(() => apiResponses); + const apiResponses = { + cmsContent: {}, + providers: [] + }; + const apiPromise = Promise.resolve(apiResponses); + settleAllPromises.mockImplementation(() => apiPromise); fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); const wrapper = shallowMount(tpaSearch, mountOptions); - wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {}); - wrapper.vm.setCmsContent = jest.fn(); - wrapper.vm.$router.navigateWithSpinner = jest.fn(); - wrapper.vm.navigateBack = baseMixin.methods.navigateBack; - return { wrapper }; } -describe('TPA search page', () => { - test.skip('returns the initial data', () => { +describe('tpa-search.vue', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('returns the initial data', () => { // Arrange - const zipCode = '12663'; - const mainInitialState = { - order: { - customer: { - address: { zipCode } - } - } - }; - const { wrapper } = getMountedComponent(mainInitialState); + const { wrapper } = getMountedComponent(); // Assert expect(wrapper.vm.$data).toMatchSnapshot(); }); - describe('should render', () => { - test('site header', () => { + + describe('Rendering', () => { + test('Should render site header', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const expectedWidgetName = 'SiteHeaderWidget'; + const { wrapper } = getMountedComponent(); // Act const siteHeader = wrapper.findComponent({ ref: 'siteHeader' }); // Assert - expect(siteHeader.exists()).toBeTruthy(); - expect(siteHeader.props().cmsWidgetName).toBe(expectedWidgetName); + expect(siteHeader.exists()).toBe(true); }); - test('search providers form', () => { + + test('Should render site footer', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - - // Act - const searchProvidersForm = wrapper.findComponent('#searchProvidersForm'); - - // Assert - expect(searchProvidersForm.exists()).toBeTruthy(); - }); - test('search question label', () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - - // Act - const searchQuestionLabel = wrapper.find('#tpaSearchQuestionLabel'); - - // Assert - expect(searchQuestionLabel.exists()).toBeTruthy(); - expect(searchQuestionLabel.attributes().for).toBe('tpaSearchQuestionField'); - expect(searchQuestionLabel.classes()).toContain('text-center'); - expect(searchQuestionLabel.classes()).toContain('mt-5'); - expect(searchQuestionLabel.classes()).toContain('mb-0'); - expect(searchQuestionLabel.classes()).toContain('text-black'); - expect(searchQuestionLabel.classes()).toContain('w-100'); - expect(searchQuestionLabel.classes()).toContain('fs-5'); - }); - test('search instructions', () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - - // Act - const searchInstructions = wrapper.find('#searchInstructions'); - - // Assert - expect(searchInstructions.exists()).toBeTruthy(); - expect(searchInstructions.attributes().for).toBe('tpaSearchQuestionField'); - expect(searchInstructions.classes()).toContain('text-center'); - expect(searchInstructions.classes()).toContain('small'); - expect(searchInstructions.classes()).toContain('darker-gray'); - expect(searchInstructions.classes()).toContain('w-100'); - expect(searchInstructions.classes()).toContain('mb-4'); - }); - test('search question field', () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const expectedWidgetName = 'TPASearchQuestion'; - - // Act - const searchQuestionField = wrapper.findComponent('#tpaSearchQuestionField'); - - // Assert - expect(searchQuestionField.exists()).toBeTruthy(); - expect(searchQuestionField.props().inputId).toBe('tpaSearchQuestionFieldInput'); - expect(searchQuestionField.props().isRequired).toBeTruthy(); - expect(searchQuestionField.props().cmsWidgetName).toBe(expectedWidgetName); - expect(searchQuestionField.props().includeSearchIcon).toBeTruthy(); - expect(searchQuestionField.props().displayQuestionText).toBeFalsy(); - expect(searchQuestionField.props().validationRules) - .toBe(`${globalRules.ZIP_CODE_REQUIRED}|${globalRules.ZIP_CODE_SEARCH_FORMAT}`); - }); - test('provider selection form', () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - - // Act - const providerSelectionForm = wrapper.findComponent('#providerSelectionForm'); - - // Assert - expect(providerSelectionForm.exists()).toBeTruthy(); - }); - test('map', async () => { - // Arrange - const zipCode = '12345'; const { wrapper } = getMountedComponent(); - await wrapper.setData({ - providers: [{ - address: { - streetAddress: '123 Lane Ave', - city: 'New York', - state: 'NY', - zipCode: '99230' - } - }], - mapZipCode: zipCode - }); - const expectedAddresses = ['123 Lane Ave, New York, NY 99230']; // Act - const map = wrapper.findComponent('#map'); + const siteFooter = wrapper.findComponent({ ref: 'siteFooter' }); // Assert - expect(map.exists()).toBeTruthy(); - expect(map.classes()).toContain('mb-4'); - expect(map.props().markers.map((p) => p.fullAddress)).toEqual(expectedAddresses); - expect(map.props().zipCode).toBe(zipCode); + expect(siteFooter.exists()).toBe(true); }); - test('search radius filter', async () => { + + test('Should render tpa search question label', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const expectedWidgetName = 'FilterByQuestion'; + const { wrapper } = getMountedComponent(); // Act - const searchRadiusFilter = wrapper.findComponent('#searchRadiusFilter'); + const label = wrapper.find('#tpaSearchQuestionLabel'); // Assert - expect(searchRadiusFilter.exists()).toBeTruthy(); - expect(searchRadiusFilter.props().cmsWidgetName).toBe(expectedWidgetName); - expect(searchRadiusFilter.props().validationRules).toBe(globalRules.OPTION_REQUIRED); + expect(label.exists()).toBe(true); }); - test('providers loader when reloadingProviders is true', async () => { + + test('Should render search instructions label', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ reloadingProviders: true }); + const { wrapper } = getMountedComponent(); // Act - const providersLoader = wrapper.findComponent('#providersLoader'); + const label = wrapper.find('#searchInstructions'); // Assert - expect(providersLoader.exists()).toBeTruthy(); + expect(label.exists()).toBe(true); }); - test('select provider question when reloadingProviders are false', async () => { + + test('Should render textbox question for TPA search', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ - providers: [{ - companyName: 'Shop Auto Glass', - phoneNumber: '740-555-1234', - distanceInMiles: 1.2345, - providerNumber: '232209', - address: { - streetAddress: '123 Lane Ave', - city: 'New York', - state: 'NY', - zipCode: '99230' - } - }], + const { wrapper } = getMountedComponent(); + + // Act + const textbox = wrapper.find('#tpaSearchQuestionField'); + + // Assert + expect(textbox.exists()).toBe(true); + }); + + test('Should render dropdown for radius filter', () => { + // Arrange + const { wrapper } = getMountedComponent(); + + // Act + const dropdown = wrapper.find('#searchRadiusFilter'); + + // Assert + expect(dropdown.exists()).toBe(true); + }); + + test('Should render google map', () => { + // Arrange + const { wrapper } = getMountedComponent(); + + // Act + const map = wrapper.find('#map'); + + // Assert + expect(map.exists()).toBe(true); + }); + + test('Should show loader state when reloadingProviders is true', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { reloadingProviders: true }); + + // Assert + expect(wrapper.vm.reloadingProviders).toBe(true); + }); + + test('Should show provider selection when reloadingProviders is false', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { reloadingProviders: false }); + + // Assert + expect(wrapper.vm.reloadingProviders).toBe(false); + }); + + test('Should render no network shops alert when providers is empty', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { providers: [] }); + + // Act + const alert = wrapper.find('#alertNoNetworkProviders'); + + // Assert + expect(alert.exists()).toBe(true); + }); + + test('Should render preferred shop not listed link', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { reloadingProviders: false }); + + // Act + const link = wrapper.find('#preferredShopNotListedLink'); + + // Assert + expect(link.exists()).toBe(true); + }); + + test('Should have needHelpLinkText computed property', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { reloadingProviders: false }); - const expectedAnswers = [{ - buttonLabel: 'Shop Auto Glass', - buttonLabelSubCopy: '1.2 mi', - buttonBodyCopy: '123 Lane Ave, New York, NY 99230
740-555-1234', - value: '232209' - }]; - const expectedAdditionalButtonData = { - displayAvailabilityIndicators: false - }; - // Act - const selectProviderQuestion = wrapper.findComponent('#selectProviderQuestion'); - - // Assert - expect(selectProviderQuestion.exists()).toBeTruthy(); - expect(selectProviderQuestion.props().buttonTypeString).toBe('shopListButton'); - expect(selectProviderQuestion.classes()).toContain('radioQuestion'); - expect(selectProviderQuestion.props().groupName).toBe('chooseShop'); - expect(selectProviderQuestion.props().textPosition).toBe('text-start'); - expect(selectProviderQuestion.props().isRequired).toBeTruthy(); - expect(selectProviderQuestion.props().validationRules).toBe(globalRules.OPTION_REQUIRED); - expect(selectProviderQuestion.props().answers).toEqual(expectedAnswers); - expect(selectProviderQuestion.props().additionalButtonData).toEqual(expectedAdditionalButtonData); - }); - test.each([[], null, undefined])( - 'no network providers alert when providers length is 0, undefined, or null and reloadingProviders is false', - async (providers) => { - // Arrange - const initialData = { - providers - }; - const wrapper = shallowMount(tpaSearch, getMountOptions({}, initialData)); - await wrapper.setData({ reloadingProviders: false }); - - // Act - const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders'); - - // Assert - expect(noNetworkProvidersAlert.exists()).toBeTruthy(); - expect(noNetworkProvidersAlert.props().cmsWidgetName).toBeTruthy(); - expect(noNetworkProvidersAlert.props().alertClass).toBe('alert-warning'); - expect(noNetworkProvidersAlert.props().isDismissible).toBeFalsy(); - } - ); - test('preferred shop not listed link when reloadingProviders is false', async () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ reloadingProviders: false }); - - // Act - const preferredShopNotListedLink = wrapper.findComponent('#preferredShopNotListedLink'); - - // Assert - expect(preferredShopNotListedLink.exists()).toBeTruthy(); - expect(preferredShopNotListedLink.props().linkType).toBe('navigation'); - // eslint-disable-next-line no-script-url - expect(preferredShopNotListedLink.props().href).toBe('javascript:void(0)'); - }); - test('site footer', () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - - // Act - const footer = wrapper.findComponent({ ref: 'siteFooter' }); - - // Assert - expect(footer.exists()).toBeTruthy(); - expect(footer.props().cmsWidgetName).toBe('SiteFooterWidget'); + // Assert - needHelpLinkText computed exists and returns a value (empty string from mock) + expect(wrapper.vm.needHelpLinkText).toBeDefined(); }); }); - describe('should not render', () => { - test('select provider question when reloadingProviders is true', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - await wrapper.setData({ reloadingProviders: true }); - // Act - const selectProviderQuestion = wrapper.findComponent('#selectProviderQuestion'); - - // Assert - expect(selectProviderQuestion.exists()).toBeFalsy(); - }); - describe('no network providers alert', () => { - test('when providers length is not 0', async () => { + describe('Computed', () => { + describe('radiusFilterOptions', () => { + test('returns empty object when no options', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ providers: ['p1', 'p2'] }); + const { wrapper } = getMountedComponent(); // Act - const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders'); + const result = wrapper.vm.radiusFilterOptions; // Assert - expect(noNetworkProvidersAlert.exists()).toBeFalsy(); + expect(result).toEqual({}); }); - test('when reloadingProviders is true', async () => { + + test('returns formatted options when CMS content exists', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ providers: [], reloadingProviders: true }); + const mockOptions = [ + { Name: '10', Text: '10 miles' }, + { Name: '25', Text: '25 miles' }, + { Name: '50', Text: '50 miles' } + ]; + const mountOptions = getMountOptions({ router: { navigate: jest.fn() } }); + mountOptions.global.stubs = { siteFooter: footerStub, siteHeader: true, loader: loaderStub }; + mountOptions.mixins = [{ + methods: { + getCmsContent: jest.fn().mockImplementation(() => mockOptions), + setCmsContent: jest.fn(), + onSubmit: jest.fn(), + onInvalidSubmit: jest.fn(), + navigateBack: jest.fn(), + savePageDataToStore: jest.fn() + } + }]; + mountOptions.data = () => ({ dataLoaded: true }); + const wrapper = shallowMount(tpaSearch, mountOptions); // Act - const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders'); + const result = wrapper.vm.radiusFilterOptions; // Assert - expect(noNetworkProvidersAlert.exists()).toBeFalsy(); + expect(result).toEqual({ + '10': '10 miles', + '25': '25 miles', + '50': '50 miles' + }); }); }); - test('preferred shop not listed link when reloadingProviders is true', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - await wrapper.setData({ reloadingProviders: true }); - // Act - const preferredShopNotListedLink = wrapper.findComponent('#preferredShopNotListedLink'); - - // Assert - expect(preferredShopNotListedLink.exists()).toBeFalsy(); - }); - }); - describe('computed', () => { - describe('filterOptions', () => { - test.each([[], null, undefined])( - 'returns empty object when cms content is empty, null or undefined', - (cmsContent) => { - // Arrange - const mountOptions = getMountOptions(); - mountOptions.mixins = [{ - methods: { - getCmsContent: jest.fn().mockImplementation((widget, field) => - (widget === 'FilterByQuestion' && field === widgetFields.INPUT_QUESTION_WIDGET.ANSWERS - ? cmsContent - : '')) - } - }]; - const wrapper = shallowMount(tpaSearch, mountOptions); - - // Act - const result = wrapper.vm.filterOptions; - - // Assert - expect(result).toEqual({}); - } - ); - test('returns expected when cms content is some non empty iterable', () => { + describe('radiusFilteredAndCappedProviders', () => { + test('filters providers by radius and caps at 5', () => { // Arrange - const mountOptions = getMountOptions(); - const cmsContent = [{ Name: 'foo' }, { Name: 'bar' }]; - mountOptions.mixins = [{ - methods: { - getCmsContent: jest.fn().mockImplementation((widget, field) => - (widget === 'FilterByQuestion' && field === widgetFields.INPUT_QUESTION_WIDGET.ANSWERS - ? cmsContent - : '')) - } - }]; - const wrapper = shallowMount(tpaSearch, mountOptions); - const expected = { foo: 'foo', bar: 'bar' }; + const providers = [ + { providerNumber: '1', distanceInMiles: 5, companyName: 'Shop 1', address: {} }, + { providerNumber: '2', distanceInMiles: 15, companyName: 'Shop 2', address: {} }, + { providerNumber: '3', distanceInMiles: 20, companyName: 'Shop 3', address: {} }, + { providerNumber: '4', distanceInMiles: 30, companyName: 'Shop 4', address: {} }, + { providerNumber: '5', distanceInMiles: 35, companyName: 'Shop 5', address: {} }, + { providerNumber: '6', distanceInMiles: 40, companyName: 'Shop 6', address: {} }, + { providerNumber: '7', distanceInMiles: 45, companyName: 'Shop 7', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { providers, radiusFilter: 50 }); // Act - const result = wrapper.vm.filterOptions; + const result = wrapper.vm.radiusFilteredAndCappedProviders; // Assert - expect(result).toEqual(expected); + expect(result.length).toBe(5); + }); + + test('filters out providers beyond radius', () => { + // Arrange + const providers = [ + { providerNumber: '1', distanceInMiles: 5, companyName: 'Shop 1', address: {} }, + { providerNumber: '2', distanceInMiles: 15, companyName: 'Shop 2', address: {} }, + { providerNumber: '3', distanceInMiles: 30, companyName: 'Shop 3', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { providers, radiusFilter: 25 }); + + // Act + const result = wrapper.vm.radiusFilteredAndCappedProviders; + + // Assert + expect(result.length).toBe(2); + expect(result.map(p => p.providerNumber)).toEqual(['1', '2']); }); }); - test.each([null, undefined, '', 'non empty string'])( - 'tpaSearchQuestionLabel returns "%p" from getCmsContent', - (cmsContent) => { - // Arrange - const mountOptions = getMountOptions(); - mountOptions.mixins = [{ - methods: { - getCmsContent: jest.fn().mockImplementation((widget, field) => - (widget === 'TPASearchQuestion' && field === widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT - ? cmsContent - : '')) - } - }]; - const wrapper = shallowMount(tpaSearch, mountOptions); - // Act - const result = wrapper.vm.tpaSearchQuestionLabel; - - // Assert - expect(result).toEqual(cmsContent); - } - ); - test.each([null, undefined, '', 'non empty string'])( - 'searchInstructionsText returns "%p" from getCmsContent', - (cmsContent) => { - // Arrange - const mountOptions = getMountOptions(); - mountOptions.mixins = [{ - methods: { - getCmsContent: jest.fn().mockImplementation((widget, field) => - (widget === 'SearchInstructions' && field === widgetFields.TEXT_BLOCK_WIDGET.TEXT - ? cmsContent - : '')) - } - }]; - const wrapper = shallowMount(tpaSearch, mountOptions); - - // Act - const result = wrapper.vm.searchInstructionsText; - - // Assert - expect(result).toEqual(cmsContent); - } - ); - test.each([null, undefined, '', 'non empty string'])( - 'shopNotListedModalLink returns "%p" from getCmsContent', - (cmsContent) => { - // Arrange - const mountOptions = getMountOptions(); - mountOptions.mixins = [{ - methods: { - getCmsContent: jest.fn().mockImplementation((widget, field) => - (widget === 'ShopNotListedLink' && field === widgetFields.TEXT_BLOCK_WIDGET.TEXT - ? cmsContent - : '')) - } - }]; - const wrapper = shallowMount(tpaSearch, mountOptions); - - // Act - const result = wrapper.vm.shopNotListedModalLink; - - // Assert - expect(result).toEqual(cmsContent); - } - ); - describe('radiusInMiles', () => { - test.each([ - [15, '15 miles'], - [25, '25 miles'], - [50, '50 miles'], - [100, '100 miles'] - ])( - 'returns %p when filter is "%p"', - async (expectedRadius, filter) => { - // Arrange - const { wrapper } = getMountedComponent(); - await wrapper.setData({ filter }); - - // Act - const result = wrapper.vm.radiusInMiles; - - // Assert - expect(result).toEqual(expectedRadius); - } - ); - test.each([null, undefined, 'some random string'])( - 'returns 0 when filter is "%p"', - async (filter) => { - // Arrange - const { wrapper } = getMountedComponent(); - await wrapper.setData({ filter }); - - // Act - const result = wrapper.vm.radiusInMiles; - - // Assert - expect(result).toEqual(0); - } - ); - }); - describe('noNetworkShopsAlertHeaderText', () => { - test('returns exact cmsContent value when "{custom:radiusInMiles}" is not a substring', async () => { - // Arrange - const cmsContent = 'content returned from cms'; - const mountOptions = getMountOptions(); - mountOptions.mixins = [{ - methods: { - getCmsContent: jest.fn().mockImplementation((widget, field) => - (widget === 'NoNetworkShopsAlertWidget' && field === widgetFields.ALERT_WIDGET.HEADLINE_TEXT - ? cmsContent - : '')) - } - }]; - const wrapper = shallowMount(tpaSearch, mountOptions); - - // Act - const result = wrapper.vm.noNetworkShopsAlertHeaderText; - - // Assert - expect(result).toEqual(cmsContent); - }); - test('returns cmsContent value with all instances of "{custom:radiusInMiles}" replaced with radiusInMiles', () => { - // Arrange - const cmsContent = '{custom:radiusInMiles} ret{custom:radiusInMiles}urned {custom:radiusInMiles}cms{custom:radiusInMiles}'; - const mountOptions = getMountOptions(); - mountOptions.mixins = [{ - methods: { - getCmsContent: jest.fn().mockImplementation((widget, field) => - (widget === 'NoNetworkShopsAlertWidget' && field === widgetFields.ALERT_WIDGET.HEADLINE_TEXT - ? cmsContent - : '')) - } - }]; - const wrapper = shallowMount(tpaSearch, mountOptions); - const expected = '0 ret0urned 0cms0'; - - // Act - const result = wrapper.vm.noNetworkShopsAlertHeaderText; - - // Assert - expect(result).toEqual(expected); - }); - }); describe('providerAddresses', () => { - test.each([null, undefined, []])( - 'returns empty list when providers empty', - async (providers) => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ providers }); - - // Act - const result = wrapper.vm.providerAddresses; - - // Assert - expect(result).toEqual([]); - } - ); - test('returns expected when providers not empty', async () => { + test('maps providers to address objects for google map', () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ - providers: [ - { - companyName: 'Shop Auto Glass', - phoneNumber: '740-555-1234', - distanceInMiles: 1.2345, - providerNumber: '232209', - address: { - streetAddress: '123 Lane Ave', - city: 'New York', - state: 'NY', - zipCode: '99230' - } - }, - { - companyName: 'Safelite AutoGlass', - phoneNumber: '676-555-0099', - distanceInMiles: 123.9452, - providerNumber: '123456', - address: { - streetAddress: '2003 Coral Ln', - city: 'Birmingham', - state: 'AL', - zipCode: '43001' - } - } - ] - }); - const expectedResult = [ - '123 Lane Ave, New York, NY 99230', - '2003 Coral Ln, Birmingham, AL 43001' - ]; - - // Act - const result = wrapper.vm.providerAddresses.map((pa) => pa.fullAddress); - - // Assert - expect(result).toEqual(expectedResult); - }); - }); - describe('providerButtonData', () => { - test.each([null, undefined, []])( - 'returns empty list when providers equals "%p"', - async (providers) => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ providers }); - - // Act - const result = wrapper.vm.providerButtonData; - - // Assert - expect(result).toEqual([]); - } - ); - test('returns expected when providers not empty', async () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ - providers: [ - { - companyName: 'Shop Auto Glass', - phoneNumber: '740-555-1234', - distanceInMiles: 1.2345, - providerNumber: '232209', - address: { - streetAddress: '123 Lane Ave', - city: 'New York', - state: 'NY', - zipCode: '99230' - } - }, - { - companyName: 'Safelite AutoGlass', - phoneNumber: '676-555-0099', - distanceInMiles: 123.9452, - providerNumber: '123456', - address: { - streetAddress: '2003 Coral Ln', - city: 'Birmingham', - state: 'AL', - zipCode: '43001' - } - } - ] - }); - const expectedResult = [ + const providers = [ { - buttonLabel: 'Shop Auto Glass', - buttonLabelSubCopy: '1.2 mi', - buttonBodyCopy: '123 Lane Ave, New York, NY 99230
740-555-1234', - value: '232209' - }, - { - buttonLabel: 'Safelite AutoGlass', - buttonLabelSubCopy: '123.9 mi', - buttonBodyCopy: '2003 Coral Ln, Birmingham, AL 43001
676-555-0099', - value: '123456' + providerNumber: '1', + companyName: 'TEST SHOP', + distanceInMiles: 5, + address: { + streetAddress: '123 MAIN ST', + city: 'COLUMBUS', + state: 'OH', + zipCode: '43215' + } } ]; + const { wrapper } = getMountedComponent({}, { providers, radiusFilter: 25 }); + + // Act + const result = wrapper.vm.providerAddresses; + + // Assert + expect(result.length).toBe(1); + expect(result[0].title).toBe('test shop'); + expect(result[0].addressLines.length).toBe(2); + }); + + test('returns empty array when no providers', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { providers: [], radiusFilter: 25 }); + + // Act + const result = wrapper.vm.providerAddresses; + + // Assert + expect(result).toEqual([]); + }); + }); + + describe('providerButtonData', () => { + test('maps providers to button data format', () => { + // Arrange + const providers = [ + { + providerNumber: '12345', + companyName: 'AUTO GLASS SHOP', + distanceInMiles: 5.75, + phoneNumber: '6145551234', + address: { + streetAddress: '456 OAK AVE', + city: 'DUBLIN', + state: 'OH', + zipCode: '43017' + } + } + ]; + const { wrapper } = getMountedComponent({}, { providers, radiusFilter: 25 }); // Act const result = wrapper.vm.providerButtonData; // Assert - expect(result).toEqual(expectedResult); + expect(result.length).toBe(1); + expect(result[0].buttonLabel).toBe('auto glass shop'); + expect(result[0].buttonLabelSubCopy).toBe('5.8 mi'); + expect(result[0].value).toBe('12345'); }); }); + describe('selectedProviderIsSafeliteShop', () => { - test('no issue when provider in providers with no provider number', async () => { + test('returns true when selected provider is Safelite shop', () => { // Arrange - const selectedProviderNumber = '10099'; const providers = [ - { isSafeliteShop: false }, - { isSafeliteShop: true } + { providerNumber: '123', isSafeliteShop: true }, + { providerNumber: '456', isSafeliteShop: false } ]; - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ selectedProviderNumber, providers }); + const { wrapper } = getMountedComponent({}, { + providers, + selectedProviderNumber: '123', + radiusFilter: 25 + }); // Act const result = wrapper.vm.selectedProviderIsSafeliteShop; // Assert - expect(result).toEqual(false); + expect(result).toBe(true); }); - test.each([null, undefined, []])( - 'returns false when providers %s', - async (providers) => { - // Arrange - const selectedProviderNumber = '10099'; - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ selectedProviderNumber, providers }); - // Act - const result = wrapper.vm.selectedProviderIsSafeliteShop; - - // Assert - expect(result).toEqual(false); - } - ); - test.each([ - [true, true], - [false, false] - ])( - 'returns %p when first instance of multiple matching providerNumbers has isSafeliteShop value %p', - async (expectedOutput, isSafeliteShop) => { - // Arrange - const selectedProviderNumber = '29987'; - const providers = [ - { - providerNumber: selectedProviderNumber, - isSafeliteShop - }, - { - providerNumber: selectedProviderNumber, - isSafeliteShop: !isSafeliteShop - } - ]; - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ providers }); - await wrapper.setData({ selectedProviderNumber }); - - // Act - const result = wrapper.vm.selectedProviderIsSafeliteShop; - - // Assert - expect(result).toEqual(expectedOutput); - } - ); - test.each([[false, false], [true, true]])( - 'returns %p when providerNumber match has isSafeliteShop equal to %p', - async (expectedOutput, isSafeliteShop) => { - // Arrange - const selectedProviderNumber = '19274'; - const providers = [{ - providerNumber: selectedProviderNumber, - isSafeliteShop - }]; - const wrapper = shallowMount(tpaSearch, getMountOptions()); - await wrapper.setData({ selectedProviderNumber, providers }); - - // Act - const result = wrapper.vm.selectedProviderIsSafeliteShop; - - // Assert - expect(result).toEqual(expectedOutput); - } - ); - }); - }); - describe('watch', () => { - // TODO update/add to filter watch tests - test('on filter calls getTpaProviders and sets providers', async () => { - // Arrange - const zipCode = '78220'; - const initialMapZipCode = '88800'; - const { wrapper } = getMountedComponent({}, { - zipCode, - mapZipCode: initialMapZipCode, - dataLoaded: true - }); - const providers = { data: { shopProviders: ['some data', 'some more data'] } }; - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers)); - const newFilter = 'new filter'; - - // Act - await wrapper.vm.$options.watch.filter.call(wrapper.vm, newFilter); - - // Assert - expect(useMainStore().getTpaProviders).toBeCalled(); - expect(wrapper.vm.providers).toEqual(providers.data.shopProviders); - expect(wrapper.vm.mapZipCode).toBe(zipCode); - }); - describe('on filter', () => { - test('calls getTpaProviders once and sets providers when dataLoaded is true', async () => { + test('returns false when selected provider is not Safelite shop', () => { // Arrange - const zipCode = '78220'; - const initialMapZipCode = '88800'; + const providers = [ + { providerNumber: '123', isSafeliteShop: true }, + { providerNumber: '456', isSafeliteShop: false } + ]; const { wrapper } = getMountedComponent({}, { - zipCode, - mapZipCode: initialMapZipCode, - dataLoaded: true + providers, + selectedProviderNumber: '456', + radiusFilter: 25 }); - const providers = { data: { shopProviders: ['some data', 'some more data'] } }; - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers)); - const newFilter = 'new filter'; // Act - await wrapper.vm.$options.watch.filter.call(wrapper.vm, newFilter); + const result = wrapper.vm.selectedProviderIsSafeliteShop; // Assert - expect(useMainStore().getTpaProviders).toBeCalled(); - expect(wrapper.vm.providers).toEqual(providers.data.shopProviders); - expect(wrapper.vm.mapZipCode).toBe(zipCode); + expect(result).toBe(false); }); - test('never calls getTpaProviders and does not set providers when dataLoaded is false', async () => { + + test('returns false when no provider is selected', () => { // Arrange - const zipCode = '78220'; - const initialMapZipCode = '88800'; + const providers = [ + { providerNumber: '123', isSafeliteShop: true } + ]; const { wrapper } = getMountedComponent({}, { - zipCode, - mapZipCode: initialMapZipCode, - dataLoaded: false + providers, + selectedProviderNumber: '', + radiusFilter: 25 }); - const providers = { data: { shopProviders: ['some data', 'some more data'] } }; - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers)); - const newFilter = 'new filter'; // Act - await wrapper.vm.$options.watch.filter.call(wrapper.vm, newFilter); + const result = wrapper.vm.selectedProviderIsSafeliteShop; // Assert - expect(useMainStore().getTpaProviders).not.toBeCalled(); - expect(wrapper.vm.providers).not.toEqual(providers.data.shopProviders); - expect(wrapper.vm.mapZipCode).toBe(initialMapZipCode); + expect(result).toBe(false); }); }); - // TODO confirm tests appropriate. Also should this method be async? - describe('on providers', () => { - test('does not update provider number when dataLoaded false', () => { + + describe('noNetworkShopsAlertHeaderText', () => { + test('replaces radius placeholder with actual value', () => { // Arrange - const initialValue = 1234; - const { wrapper } = getMountedComponent({}, { - dataLoaded: false, - selectedProviderNumber: initialValue - }); - const value = 'some other value'; - const newProviders = [{ providerNumber: value }]; - - // Act - wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders); - - // Assert - expect(wrapper.vm.selectedProviderNumber).toBe(initialValue); - }); - test.each([null, undefined, []])( - 'sets selected provider number to "" when there are no providers', - (newProviders) => { - // Arrange - const { wrapper } = getMountedComponent({}, { - dataLoaded: true, - selectedProviderNumber: null - }); - - // Act - wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders); - - // Assert - expect(wrapper.vm.selectedProviderNumber).toBe(''); - } - ); - test('sets selected provider number to value of provider when there is one provider', () => { - // Arrange - const { wrapper } = getMountedComponent({}, { - dataLoaded: true, - selectedProviderNumber: null - }); - const value = 'some value'; - const newProviders = [{ providerNumber: value }]; - - // Act - wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders); - - // Assert - expect(wrapper.vm.selectedProviderNumber).toBe(value); - }); - test('sets selected provider number to "" when there are more than one provider', () => { - // Arrange - const { wrapper } = getMountedComponent({}, { - dataLoaded: true, - selectedProviderNumber: null - }); - const newProviders = [{ value: 'val1' }, { value: 'val2' }]; - - // Act - wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders); - - // Assert - expect(wrapper.vm.selectedProviderNumber).toBe(''); - }); - }); - describe('selectedProviderNumber', () => { - describe('does not call updateServiceLocation when', () => { - test('dataLoaded is false', () => { - // Arrange - const providerNumber = 38447; - const { wrapper } = getMountedComponent({}, { - providers: [{ - providerNumber, - address: { - streetAddress: '143 Average Lane', - city: 'Cambridge', - state: 'OH', - zipCode: '72983', - zipCodeCtu: '0390' - }, - companyName: "Sally's Auto", - phoneNumber: '1234567890' - }], - dataLoaded: false - }); - - // Act - wrapper.vm.$options.watch.selectedProviderNumber.call(wrapper.vm, providerNumber); - - // Assert - expect(wrapper.vm.mainStore.updateServiceLocation).not.toHaveBeenCalled(); - }); - test('providers list is null ', () => { - // Arrange - const { wrapper } = getMountedComponent({}, { - providers: null, - dataLoaded: true - }); - const newProviderNumber = 11235; - - // Act - wrapper.vm.$options.watch.selectedProviderNumber.call(wrapper.vm, newProviderNumber); - - // Assert - expect(wrapper.vm.mainStore.updateServiceLocation).not.toHaveBeenCalled(); - }); - test('providers list is empty ', () => { - // Arrange - const { wrapper } = getMountedComponent({}, { - providers: [], - dataLoaded: true - }); - const newProviderNumber = 11235; - - // Act - wrapper.vm.$options.watch.selectedProviderNumber.call(wrapper.vm, newProviderNumber); - - // Assert - expect(wrapper.vm.mainStore.updateServiceLocation).not.toHaveBeenCalled(); - }); - test('providers list does not contain match for provider number ', () => { - // Arrange - const { wrapper } = getMountedComponent({}, { - providers: [{ - providerNumber: 8374, - address: { - streetAddress: '143 Average Lane', - city: 'Cambridge', - state: 'OH', - zipCode: '72983', - zipCodeCtu: '0390' - }, - companyName: "Sally's Auto", - phoneNumber: '1234567890' - }], - dataLoaded: true - }); - const newProviderNumber = 11235; - - // Act - wrapper.vm.$options.watch.selectedProviderNumber.call(wrapper.vm, newProviderNumber); - - // Assert - expect(wrapper.vm.mainStore.updateServiceLocation).not.toHaveBeenCalled(); - }); - }); - test('Calls updateServiceLocation when matching provider found.', () => { - // Arrange - const newProviderNumber = 11235; - const streetAddress = '143 Average Lane'; - const city = 'Berlin'; - const state = 'MA'; - const zipCode = '12345'; - const zipCodeCtu = '0004'; - const companyName = "Sally's Auto"; - const phoneNumber = '3298479879'; - const provider = { - providerNumber: newProviderNumber, - address: { - streetAddress, - city, - state, - zipCode, - zipCodeCtu - }, - companyName, - phoneNumber - }; - const filter = '10 miles'; - const serviceZipCode = 10098; - const { wrapper } = getMountedComponent({}, { - providers: [ - provider, - { providerNumber: 328949832 } - ], - filter, - zipCode: serviceZipCode, - dataLoaded: true - }); - - // Act - wrapper.vm.$options.watch.selectedProviderNumber.call(wrapper.vm, newProviderNumber); - - // Assert - expect(wrapper.vm.mainStore.updateServiceLocation).toHaveBeenCalledTimes(1); - expect(wrapper.vm.mainStore.updateServiceLocation).toHaveBeenCalledWith({ - searchFilter: filter, - zipCode: serviceZipCode, - provider: { - providerNumber: newProviderNumber, - address: { - streetAddress, - city, - state, - zipCode, - zipCodeCtu - }, - companyName, - phoneNumber + const mountOptions = getMountOptions({ router: { navigate: jest.fn() } }); + mountOptions.global.stubs = { siteFooter: footerStub, siteHeader: true, loader: loaderStub }; + mountOptions.mixins = [{ + methods: { + getCmsContent: jest.fn().mockImplementation(() => 'No shops within {custom:radiusInMiles}'), + setCmsContent: jest.fn(), + onSubmit: jest.fn(), + onInvalidSubmit: jest.fn(), + navigateBack: jest.fn(), + savePageDataToStore: jest.fn() } - }); + }]; + mountOptions.data = () => ({ dataLoaded: true, radiusFilter: 25 }); + const wrapper = shallowMount(tpaSearch, mountOptions); + + // Act + const result = wrapper.vm.noNetworkShopsAlertHeaderText; + + // Assert + expect(result).toBe('No shops within 25 miles'); }); }); }); - describe('method', () => { - test.each([ - ['address', 'city', 'ST', 'zip', 'Address, City, ST zip'], - ['address', null, 'ST', 'zip', 'Address, ST zip'], - ['address', 'city', null, 'zip', 'Address, City zip'], - ['address', 'city', 'ST', null, 'Address, City, ST'], - [null, null, null, null, ''] - ])( - 'getFullProviderAddress returns expected', - (streetAddress, city, state, zipCode, expected) => { + + describe('Methods', () => { + describe('getFullProviderAddress', () => { + test('returns combined address lines', () => { // Arrange const { wrapper } = getMountedComponent(); const provider = { address: { - streetAddress, - city, - state, - zipCode + streetAddress: '123 MAIN ST', + city: 'COLUMBUS', + state: 'OH', + zipCode: '43215' } }; @@ -1033,26 +513,20 @@ describe('TPA search page', () => { const result = wrapper.vm.getFullProviderAddress(provider); // Assert - expect(result).toEqual(expected); - } - ); - test.each([ - ['address', 'city', 'ST', 'zip', 'Address'], - ['address', null, 'ST', 'zip', 'Address'], - ['address', 'city', null, 'zip', 'Address'], - ['address', 'city', 'ST', null, 'Address'], - [null, null, null, null, ''] - ])( - 'getProviderAddress returns expected', - (streetAddress, city, state, zipCode, expected) => { + expect(result).toContain('123 Main St'); + expect(result).toContain('Columbus'); + expect(result).toContain('OH'); + expect(result).toContain('43215'); + }); + }); + + describe('getProviderAddress', () => { + test('returns title cased street address', () => { // Arrange const { wrapper } = getMountedComponent(); const provider = { address: { - streetAddress, - city, - state, - zipCode + streetAddress: 'UPPERCASE STREET' } }; @@ -1060,26 +534,31 @@ describe('TPA search page', () => { const result = wrapper.vm.getProviderAddress(provider); // Assert - expect(result).toEqual(expected); - } - ); - test.each([ - ['address', 'city', 'ST', 'zip', 'City, ST zip'], - ['address', null, 'ST', 'zip', 'ST zip'], - ['address', 'city', null, 'zip', 'City zip'], - ['address', 'city', 'ST', null, 'City, ST'], - [null, null, null, null, ''] - ])( - 'getProviderCityZipState returns expected', - (streetAddress, city, state, zipCode, expected) => { + expect(toTitleCase).toHaveBeenCalledWith('UPPERCASE STREET'); + }); + + test('handles null address', () => { + // Arrange + const { wrapper } = getMountedComponent(); + const provider = { address: null }; + + // Act + const result = wrapper.vm.getProviderAddress(provider); + + // Assert + expect(result).toBe(''); + }); + }); + + describe('getProviderCityZipState', () => { + test('returns formatted city, state, zip', () => { // Arrange const { wrapper } = getMountedComponent(); const provider = { address: { - streetAddress, - city, - state, - zipCode + city: 'COLUMBUS', + state: 'OH', + zipCode: '43215' } }; @@ -1087,132 +566,174 @@ describe('TPA search page', () => { const result = wrapper.vm.getProviderCityZipState(provider); // Assert - expect(result).toEqual(expected); - } - ); - describe('getProviderButtonData', () => { - test.each([null, undefined, {}])( - 'returns empty list when getTpaProviders returns no data', - async (newProviders) => { - // Arrange - const { wrapper } = getMountedComponent(); - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders)); + expect(result).toContain('OH'); + expect(result).toContain('43215'); + }); - // Act - const result = await wrapper.vm.getProviderButtonData(); - - // Assert - expect(result).toEqual([]); - } - ); - test('returns expected when getTpaProviders returns data', async () => { + test('handles missing city', () => { // Arrange const { wrapper } = getMountedComponent(); - const providers = { - data: { - shopProviders: [{ companyName: 'foo' }] + const provider = { + address: { + state: 'OH', + zipCode: '43215' } }; - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers)); // Act - const result = await wrapper.vm.getProviderButtonData(); + const result = wrapper.vm.getProviderCityZipState(provider); // Assert - expect(result).toEqual(providers.data.shopProviders); + expect(result).toBe('OH 43215'); + }); + + test('handles missing state and zip', () => { + // Arrange + const { wrapper } = getMountedComponent(); + const provider = { + address: { + city: 'COLUMBUS' + } + }; + + // Act + const result = wrapper.vm.getProviderCityZipState(provider); + + // Assert + expect(result).toContain('Columbus'); }); }); - test('doNotSeeMyShopLinkClick invokes navigate method', () => { - // Arrange - const { wrapper } = getMountedComponent(); - // Act - wrapper.vm.doNotSeeMyShopLinkClick(); - - // Assert - expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); - }); - describe('searchClick', () => { - test.each([null, undefined, {}])( - 'sets providers to empty list when no data returned from getProviders', - async (newProviders) => { - // Arrange - const zipCode = '28331'; - const initialMapZipCode = '10022'; - const { wrapper } = getMountedComponent({}, { - zipCode, - mapZipCode: initialMapZipCode - }); - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders)); - - // Act - await wrapper.vm.searchClick(); - - // Assert - expect(wrapper.vm.providers).toEqual([]); - expect(wrapper.vm.mapZipCode).toEqual(zipCode); - } - ); - test('returns value from getProviders', async () => { + describe('getShopButtonDataFromProvider', () => { + test('returns formatted button data with distance', () => { // Arrange - const zipCode = '12990'; - const initialMapZipCode = '00981'; - const { wrapper } = getMountedComponent({}, { - zipCode, - mapZipCode: initialMapZipCode - }); - const shopProviders = [{ companyName: 'foo' }]; - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => ({ - data: { shopProviders } - })); + const { wrapper } = getMountedComponent(); + const provider = { + providerNumber: '12345', + companyName: 'TEST SHOP', + distanceInMiles: 5.789, + phoneNumber: '6145551234', + address: { + streetAddress: '123 MAIN ST', + city: 'COLUMBUS', + state: 'OH', + zipCode: '43215' + } + }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.buttonLabel).toBe('test shop'); + expect(result.buttonLabelSubCopy).toBe('5.8 mi'); + expect(result.value).toBe('12345'); + expect(result.buttonBodyCopy).toContain('
'); + }); + + test('returns empty distance when distanceInMiles is null', () => { + // Arrange + const { wrapper } = getMountedComponent(); + const provider = { + providerNumber: '12345', + companyName: 'TEST SHOP', + distanceInMiles: null, + phoneNumber: '6145551234', + address: {} + }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.buttonLabelSubCopy).toBe(''); + }); + + test('returns empty distance when distanceInMiles is NaN', () => { + // Arrange + const { wrapper } = getMountedComponent(); + const provider = { + providerNumber: '12345', + companyName: 'TEST SHOP', + distanceInMiles: NaN, + phoneNumber: '6145551234', + address: {} + }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.buttonLabelSubCopy).toBe(''); + }); + }); + + describe('searchClick', () => { + test('searches by shop name when input is not a zip code', async () => { + // Arrange + const { wrapper } = getMountedComponent(); + wrapper.vm.tpaSearchValue = 'Auto Glass Shop'; + wrapper.vm.getProviderButtonData = jest.fn().mockResolvedValue([]); // Act await wrapper.vm.searchClick(); // Assert - expect(wrapper.vm.providers).toEqual(shopProviders); - expect(wrapper.vm.mapZipCode).toEqual(zipCode); + expect(wrapper.vm.getProviderButtonData).toHaveBeenCalledWith('Auto Glass Shop'); }); - }); - test('backButtonAction invokes navigate method', () => { - // Arrange - const { wrapper } = getMountedComponent(); - // Act - wrapper.vm.navigateBack(); - - // Assert - expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalled(); - }); - describe('forwardButtonAction', () => { - test('invokes navigate method with safelite shop scenario when selectedProviderIsSafeliteShop true', async () => { + test('updates mapZipCode when input is a zip code', async () => { // Arrange const { wrapper } = getMountedComponent(); - const providerNumber = '1109'; - await wrapper.setData({ - selectedProviderNumber: providerNumber, - providers: [{ - providerNumber, - isSafeliteShop: true - }] + wrapper.vm.tpaSearchValue = '43215'; + wrapper.vm.mapZipCode = '12345'; + wrapper.vm.getProviderButtonData = jest.fn().mockResolvedValue([]); + + // Act + await wrapper.vm.searchClick(); + + // Assert + expect(wrapper.vm.mapZipCode).toBe('43215'); + expect(wrapper.vm.getProviderButtonData).toHaveBeenCalledWith(); + }); + }); + + describe('forwardButtonAction', () => { + test('navigates with SAFELITE_SHOP scenario when provider is Safelite', () => { + // Arrange + const providers = [ + { providerNumber: '123', isSafeliteShop: true, distanceInMiles: 5, companyName: 'Safelite', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { + providers, + selectedProviderNumber: '123', + radiusFilter: 25, + tpaSearchValue: 'test', + mapZipCode: '43215' }); // Act wrapper.vm.forwardButtonAction(); // Assert - expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP, undefined); + expect(wrapper.vm.mainStore.updateIsSafeliteProvider).toHaveBeenCalledWith(true); + expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( + navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP, + expect.anything() + ); }); - test('invokes navigate method with non safelite shop scenario when selectedProviderIsSafeliteShop false', async () => { + + test('navigates with NON_SAFELITE_SHOP scenario when provider is not Safelite', () => { // Arrange - const { wrapper } = getMountedComponent(); - const providerNumber = '928'; - await wrapper.setData({ - selectedProviderNumber: providerNumber, - providers: [{ - providerNumber, - isSafeliteShop: false - }] + const providers = [ + { providerNumber: '456', isSafeliteShop: false, distanceInMiles: 5, companyName: 'Other Shop', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { + providers, + selectedProviderNumber: '456', + radiusFilter: 25, + tpaSearchValue: 'test', + mapZipCode: '43215' }); // Act @@ -1221,421 +742,335 @@ describe('TPA search page', () => { // Assert expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP, - undefined + expect.anything() + ); + }); + + test('saves empty tpaSearchValue when it equals mapZipCode', () => { + // Arrange + const providers = [ + { providerNumber: '123', isSafeliteShop: false, distanceInMiles: 5, companyName: 'Test Shop', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { + providers, + selectedProviderNumber: '123', + radiusFilter: 25, + tpaSearchValue: '43215', + mapZipCode: '43215' + }); + const savePageDataSpy = jest.spyOn(wrapper.vm, 'savePageDataToStore'); + + // Act + wrapper.vm.forwardButtonAction(); + + // Assert + expect(savePageDataSpy).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ tpaSearchValue: '' }) + ); + }); + + test('saves tpaSearchValue when different from mapZipCode', () => { + // Arrange + const providers = [ + { providerNumber: '123', isSafeliteShop: false, distanceInMiles: 5, companyName: 'Auto Shop', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { + providers, + selectedProviderNumber: '123', + radiusFilter: 25, + tpaSearchValue: 'Auto Shop', + mapZipCode: '43215' + }); + const savePageDataSpy = jest.spyOn(wrapper.vm, 'savePageDataToStore'); + + // Act + wrapper.vm.forwardButtonAction(); + + // Assert + expect(savePageDataSpy).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ tpaSearchValue: 'Auto Shop' }) ); }); }); - describe('getCustomValueFromString', () => { - test('returns radius in miles value if "radiusInMiles"', () => { + + describe('needHelpLinkClick', () => { + test('sets bailout and navigates', () => { // Arrange const { wrapper } = getMountedComponent(); - const str = 'radiusInMiles'; // Act - const result = wrapper.vm.getCustomValueFromString(str); + wrapper.vm.needHelpLinkClick(); // Assert - expect(result).toBe(0); - }); - test('returns radius in miles value if not "radiusInMiles"', () => { - // Arrange - const { wrapper } = getMountedComponent(); - const str = 'some other string'; - - // Act - const result = wrapper.vm.getCustomValueFromString(str); - - // Assert - expect(result).toBeNull(); + expect(wrapper.vm.mainStore.setBailout).toHaveBeenCalled(); + expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( + navigationScenarios.CLICKED_NEED_HELP, + expect.anything() + ); }); }); - describe('getShopButtonDataFromProvider', () => { - test.each([null, undefined, {}])( - 'returns empty list given parameter with no provider', - (provider) => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const expectedResult = { - buttonLabel: '', - buttonLabelSubCopy: '', - buttonBodyCopy: '
', - value: '' - }; - // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); - - // Assert - expect(result).toEqual(expectedResult); - } - ); - test('returns expected given provider', () => { + describe('getProviderButtonData', () => { + test('sets reloadingProviders to true then false', async () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const provider = { - companyName: 'Shop Auto Glass', - phoneNumber: '740-555-1234', - distanceInMiles: 1.2345, - providerNumber: '232209', - address: { - streetAddress: '123 Lane Ave', - city: 'New York', - state: 'NY', - zipCode: '99230' - } - }; - const expectedResult = { - buttonLabel: 'Shop Auto Glass', - buttonLabelSubCopy: '1.2 mi', - buttonBodyCopy: '123 Lane Ave, New York, NY 99230
740-555-1234', - value: '232209' - }; + const { wrapper } = getMountedComponent(); + wrapper.vm.mainStore.getTpaAndSafeliteProviders = jest.fn().mockResolvedValue({ data: [] }); // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); + const promise = wrapper.vm.getProviderButtonData(); + expect(wrapper.vm.reloadingProviders).toBe(true); + await promise; // Assert - expect(result).toEqual(expectedResult); + expect(wrapper.vm.reloadingProviders).toBe(false); }); - describe('returns result with button label', () => { - test.each(['', undefined, null])( - 'equal to empty string when name is empty', - (companyName) => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const provider = { companyName }; - // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); - - // Assert - expect(result.buttonLabel).toEqual(''); - } - ); - test('returns provider name when exists', () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const companyName = 'Some Shop Name'; - const provider = { companyName }; - - // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); - - // Assert - expect(result.buttonLabel).toEqual(companyName); - }); - }); - describe('returns result with button label sub copy', () => { - test.each([null, undefined, 'random string'])( - 'as empty string when distanceInMiles is not a number', - (distanceInMiles) => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const provider = { distanceInMiles }; - - // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); - - // Assert - expect(result.buttonLabelSubCopy).toEqual(''); - } - ); - test.each([[0, 0], [0, 0.000], [-7.89, -7.9], [109, 109], [1.234, 1.2], [2.99, 3]])( - 'displays expected distance value', - (distanceInMiles, displayedDistance) => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const provider = { distanceInMiles }; - const expectedSubCopy = `${displayedDistance} mi`; - - // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); - - // Assert - expect(result.buttonLabelSubCopy).toEqual(expectedSubCopy); - } - ); - }); - test('returns result with button body copy showing expected address and phone number', () => { + test('calls store method with zipCode and shopName', async () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const provider = { - phoneNumber: '123-555-0099', - address: { - streetAddress: '434 Up Down Blvd', - city: 'San Francisco', - state: 'CA', - zipCode: '90887' - } - }; - const expected = '434 Up Down Blvd, San Francisco, CA 90887
123-555-0099'; + const { wrapper } = getMountedComponent(); + wrapper.vm.mapZipCode = '43215'; + wrapper.vm.mainStore.getTpaAndSafeliteProviders = jest.fn().mockResolvedValue({ data: [] }); // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); + await wrapper.vm.getProviderButtonData('Test Shop'); // Assert - expect(result.buttonBodyCopy).toEqual(expected); - }); - describe('returns result with value', () => { - test.each(['', undefined, null])( - 'equal to empty string when providerNumber is empty', - (providerNumber) => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const provider = { providerNumber }; - - // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); - - // Assert - expect(result.value).toEqual(''); - } - ); - test('returns provider number when exists', () => { - // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); - const providerNumber = '123391'; - const provider = { providerNumber }; - - // Act - const result = wrapper.vm.getShopButtonDataFromProvider(provider); - - // Assert - expect(result.value).toEqual(providerNumber); - }); + expect(wrapper.vm.mainStore.getTpaAndSafeliteProviders).toHaveBeenCalledWith('43215', 'Test Shop'); }); }); }); - describe('before route enter', () => { - test('when service location zipCode set in store, it is used', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - const customerZipCode = '18394'; - const serviceZipCode = '43982'; - useMainStore().order.customer.address.zipCode = customerZipCode; - useMainStore().order.serviceLocation.zipCode = serviceZipCode; - // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - - // Assert - expect(wrapper.vm.zipCode).toBe(serviceZipCode); - }); - test('when service location zipCode not set in store, customer zipCode used', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - const customerZipCode = '18394'; - useMainStore().order.customer.address.zipCode = customerZipCode; - useMainStore().order.serviceLocation.zipCode = null; - - // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - - // Assert - expect(wrapper.vm.zipCode).toBe(customerZipCode); - }); - test('when search filter set in store, expected filter and provider number returned', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - const customerZipCode = '18394'; - const searchFilter = '25 miles'; - const providerNumber = 749321; - useMainStore().order.customer.address.zipCode = customerZipCode; - useMainStore().order.serviceLocation.zipCode = null; - useMainStore().order.serviceLocation.searchFilter = searchFilter; - useMainStore().order.serviceLocation.provider.providerNumber = providerNumber; - const providers = [{ companyName: 'some provider' }]; - const expectedRadius = 25; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => - ({ data: { shopProviders: radius === expectedRadius ? providers : [] } })); - - // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - - // Assert - expect(wrapper.vm.filter).toBe(searchFilter); - expect(wrapper.vm.providers).toEqual(providers); - expect(wrapper.vm.selectedProviderNumber).toBe(providerNumber); - expect(useMainStore().getTpaProviders).toBeCalledTimes(1); - expect(useMainStore().getTpaProviders).toBeCalledWith(customerZipCode, expectedRadius); - }); - test('when providers exist at 15 mile radius, filter is set to "15 miles" and providers set to expected', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - const zipCode = '18394'; - useMainStore().order.customer.address.zipCode = '00001'; - useMainStore().order.serviceLocation.zipCode = zipCode; - useMainStore().order.serviceLocation.searchFilter = null; - const providers = [{ companyName: 'provider' }]; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => - ({ data: { shopProviders: radius === 15 ? providers : [] } })); - const expectedFilter = '15 miles'; - - // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - - // Assert - expect(wrapper.vm.zipCode).toBe(zipCode); - expect(wrapper.vm.filter).toBe(expectedFilter); - expect(wrapper.vm.providers).toEqual(providers); - expect(wrapper.vm.selectedProviderNumber).toBeNull(); - expect(useMainStore().getTpaProviders).toBeCalledTimes(1); - }); - test('when providers exist at 25 mile radius, filter is set to "25 miles" and providers set to expected', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - const zipCode = '18394'; - useMainStore().order.customer.address.zipCode = '00001'; - useMainStore().order.serviceLocation.zipCode = zipCode; - useMainStore().order.serviceLocation.searchFilter = null; - const providers = [{ companyName: 'provider' }]; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => - ({ data: { shopProviders: radius === 25 ? providers : [] } })); - const expectedFilter = '25 miles'; - - // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - - // Assert - expect(wrapper.vm.zipCode).toBe(zipCode); - expect(wrapper.vm.filter).toBe(expectedFilter); - expect(wrapper.vm.providers).toEqual(providers); - expect(wrapper.vm.selectedProviderNumber).toBeNull(); - expect(useMainStore().getTpaProviders).toBeCalledTimes(2); - }); - test('when providers exist at 50 mile radius but not 25, filter is set to "50 miles" and providers set to expected', async () => { - // Arrange - const { wrapper } = getMountedComponent(); - const zipCode = '18394'; - useMainStore().order.customer.address.zipCode = '00001'; - useMainStore().order.serviceLocation.zipCode = zipCode; - useMainStore().order.serviceLocation.searchFilter = null; - const providers = [{ companyName: 'provider' }]; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => - ({ data: { shopProviders: radius === 50 ? providers : [] } })); - const expectedFilter = '50 miles'; - - // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - - // Assert - expect(wrapper.vm.zipCode).toBe(zipCode); - expect(wrapper.vm.filter).toBe(expectedFilter); - expect(wrapper.vm.providers).toEqual(providers); - expect(wrapper.vm.selectedProviderNumber).toBeNull(); - expect(useMainStore().getTpaProviders).toBeCalledTimes(3); - }); - test( - 'when providers exist at 100 mile radius but not 50 or 25, filter is set to "100 miles" and providers set to expected', - async () => { + describe('Watchers', () => { + describe('providers watcher', () => { + test('auto-selects provider when only one provider exists', async () => { // Arrange - const { wrapper } = getMountedComponent(); - const zipCode = '18394'; - useMainStore().order.customer.address.zipCode = zipCode; - useMainStore().order.serviceLocation.zipCode = null; - useMainStore().order.serviceLocation.searchFilter = null; - const providers = [{ companyName: 'provider' }]; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => - ({ data: { shopProviders: radius === 100 ? providers : [] } })); - const expectedFilter = '100 miles'; + const { wrapper } = getMountedComponent({}, { dataLoaded: true }); + wrapper.vm.selectedProviderNumber = ''; // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); + wrapper.vm.providers = [{ providerNumber: '12345', distanceInMiles: 5, companyName: 'Test Shop', address: {} }]; + await nextTick(); // Assert - expect(wrapper.vm.zipCode).toBe(zipCode); - expect(wrapper.vm.filter).toBe(expectedFilter); - expect(wrapper.vm.providers).toEqual(providers); - expect(wrapper.vm.selectedProviderNumber).toBeNull(); - expect(useMainStore().getTpaProviders).toBeCalledTimes(4); - } - ); - test( - 'when no providers exist at 100, 50, or 25 mile radius, filter is set to "100 miles" and providers set to empty list', - async () => { + expect(wrapper.vm.selectedProviderNumber).toBe('12345'); + }); + + test('clears selection when multiple providers exist', async () => { // Arrange - const { wrapper } = getMountedComponent(); - const zipCode = '18394'; - useMainStore().order.customer.address.zipCode = '00001'; - useMainStore().order.serviceLocation.zipCode = zipCode; - useMainStore().order.serviceLocation.searchFilter = null; - useMainStore().getTpaProviders = jest.fn().mockImplementation(() => ([])); - const expectedFilter = '100 miles'; + const { wrapper } = getMountedComponent({}, { dataLoaded: true }); + wrapper.vm.selectedProviderNumber = '12345'; // Act - await tpaSearch.beforeRouteEnter.call( - wrapper.vm, - { query: { issPage: 'tpa-search' } }, - undefined, - (c) => c(wrapper.vm) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); + wrapper.vm.providers = [ + { providerNumber: '12345', distanceInMiles: 5, companyName: 'Shop 1', address: {} }, + { providerNumber: '67890', distanceInMiles: 10, companyName: 'Shop 2', address: {} } + ]; + await nextTick(); // Assert - expect(wrapper.vm.zipCode).toBe(zipCode); - expect(wrapper.vm.filter).toBe(expectedFilter); - expect(wrapper.vm.providers).toEqual([]); - expect(wrapper.vm.selectedProviderNumber).toBeNull(); - expect(useMainStore().getTpaProviders).toBeCalledTimes(4); - } - ); + expect(wrapper.vm.selectedProviderNumber).toBe(''); + }); + + test('does not auto-select when dataLoaded is false', async () => { + // Arrange + const { wrapper } = getMountedComponent({}, { dataLoaded: false }); + wrapper.vm.selectedProviderNumber = ''; + + // Act + wrapper.vm.providers = [{ providerNumber: '12345', distanceInMiles: 5, companyName: 'Test', address: {} }]; + await nextTick(); + + // Assert + expect(wrapper.vm.selectedProviderNumber).toBe(''); + }); + }); + + describe('selectedProviderNumber watcher', () => { + test('updates service location in store when provider is selected', async () => { + // Arrange + const providers = [ + { + providerNumber: '12345', + companyName: 'Test Shop', + phoneNumber: '6145551234', + address: { + streetAddress: '123 Main St', + city: 'Columbus', + state: 'OH', + zipCode: '43215', + zipCodeCtu: '01234' + }, + distanceInMiles: 5 + } + ]; + const { wrapper } = getMountedComponent({}, { dataLoaded: true, providers, radiusFilter: 25 }); + + // Act + wrapper.vm.selectedProviderNumber = '12345'; + await nextTick(); + + // Assert + expect(wrapper.vm.mainStore.updateServiceLocation).toHaveBeenCalledWith( + expect.objectContaining({ + provider: expect.objectContaining({ + providerNumber: '12345', + companyName: 'Test Shop' + }) + }) + ); + }); + + test('does not update store when provider not found', async () => { + // Arrange + const providers = [ + { providerNumber: '12345', distanceInMiles: 5, companyName: 'Test', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { dataLoaded: true, providers, radiusFilter: 25 }); + wrapper.vm.mainStore.updateServiceLocation.mockClear(); + + // Act + wrapper.vm.selectedProviderNumber = '99999'; + await nextTick(); + + // Assert + expect(wrapper.vm.mainStore.updateServiceLocation).not.toHaveBeenCalled(); + }); + + test('does not update store when dataLoaded is false', async () => { + // Arrange + const providers = [ + { providerNumber: '12345', distanceInMiles: 5, companyName: 'Test', address: {} } + ]; + const { wrapper } = getMountedComponent({}, { dataLoaded: false, providers, radiusFilter: 25 }); + wrapper.vm.mainStore.updateServiceLocation.mockClear(); + + // Act + wrapper.vm.selectedProviderNumber = '12345'; + await nextTick(); + + // Assert + expect(wrapper.vm.mainStore.updateServiceLocation).not.toHaveBeenCalled(); + }); + }); + }); + + describe('beforeRouteEnter', () => { + test('fetches CMS content and providers on route enter', async () => { + // Arrange + const mockProviders = [ + { providerNumber: '123', distanceInMiles: 5 } + ]; + const { wrapper } = getMountedComponent(); + settleAllPromises.mockImplementation(() => ({ + cmsContent: {}, + providers: mockProviders + })); + + const to = { query: { issPage: 'tpa-search' } }; + const next = jest.fn((callback) => { + if (typeof callback === 'function') { + callback(wrapper.vm); + } + }); + + // Act + await tpaSearch.beforeRouteEnter(to, undefined, next); + + // Assert + expect(next).toHaveBeenCalled(); + }); + + test('restores page data from store if available', async () => { + // Arrange + const pageData = { + tpaSearchValue: 'Test Shop', + radiusFilter: 50 + }; + const { wrapper } = getMountedComponent(); + wrapper.vm.mainStore.pageData = jest.fn().mockReturnValue(pageData); + settleAllPromises.mockImplementation(() => ({ + cmsContent: {}, + providers: [] + })); + + const to = { query: { issPage: 'tpa-search' } }; + const next = jest.fn((callback) => { + if (typeof callback === 'function') { + callback(wrapper.vm); + } + }); + + // Act + await tpaSearch.beforeRouteEnter(to, undefined, next); + + // Assert + expect(next).toHaveBeenCalled(); + }); + }); + + describe('Data initialization', () => { + test('widget names are correctly defined', () => { + // Arrange + const { wrapper } = getMountedComponent(); + + // Assert + expect(wrapper.vm.widget.siteHeader).toBe('SiteHeaderWidget'); + expect(wrapper.vm.widget.tpaSearchQuestion).toBe('TPASearchQuestion'); + expect(wrapper.vm.widget.filterByQuestion).toBe('FilterByQuestion'); + expect(wrapper.vm.widget.noNetworkShopsAlert).toBe('NoNetworkShopsAlertWidget'); + expect(wrapper.vm.widget.siteFooter).toBe('SiteFooterWidget'); + }); + + test('validation rules are correctly defined', () => { + // Arrange + const { wrapper } = getMountedComponent(); + + // Assert + expect(wrapper.vm.rules.tpaSearch).toBe(globalRules.TPA_SEARCH_FORMAT); + expect(wrapper.vm.rules.filter).toBe(globalRules.OPTION_REQUIRED); + expect(wrapper.vm.rules.provider).toBe(globalRules.OPTION_REQUIRED); + }); + + test('additionalButtonData is correctly initialized', () => { + // Arrange + const { wrapper } = getMountedComponent(); + + // Assert + expect(wrapper.vm.additionalButtonData).toEqual({ + displayAvailabilityIndicators: false + }); + }); + }); + + describe('dataLoaded flag', () => { + test('dataLoaded can be initialized as false', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { dataLoaded: false }); + + // Assert - verify initial state + expect(wrapper.vm.dataLoaded).toBe(false); + }); + + test('dataLoaded can be initialized as true', () => { + // Arrange + const { wrapper } = getMountedComponent({}, { dataLoaded: true }); + + // Assert + expect(wrapper.vm.dataLoaded).toBe(true); + }); + + test('dataLoaded affects provider watcher behavior', async () => { + // Arrange - with dataLoaded false, provider watcher should not auto-select + const { wrapper } = getMountedComponent({}, { dataLoaded: false, selectedProviderNumber: '' }); + + // Act - set providers (watcher should not auto-select because dataLoaded is false) + wrapper.vm.providers = [{ providerNumber: '12345', distanceInMiles: 5, companyName: 'Test', address: {} }]; + await nextTick(); + + // Assert - selectedProviderNumber should remain empty + expect(wrapper.vm.selectedProviderNumber).toBe(''); + }); }); }); From b76cbd8e15dc13b0edb099cb2c12117d8bd9d742 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Thu, 12 Feb 2026 14:51:05 -0500 Subject: [PATCH 22/55] Remove snapshot test --- src/layouts/tpa-search/tpa-search.spec.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/layouts/tpa-search/tpa-search.spec.js b/src/layouts/tpa-search/tpa-search.spec.js index 06e6f8e0..d2d5ad2e 100644 --- a/src/layouts/tpa-search/tpa-search.spec.js +++ b/src/layouts/tpa-search/tpa-search.spec.js @@ -120,14 +120,6 @@ describe('tpa-search.vue', () => { jest.clearAllMocks(); }); - test('returns the initial data', () => { - // Arrange - const { wrapper } = getMountedComponent(); - - // Assert - expect(wrapper.vm.$data).toMatchSnapshot(); - }); - describe('Rendering', () => { test('Should render site header', () => { // Arrange From 38175f5d013d406b081cc9b57d79689dec1ad3b2 Mon Sep 17 00:00:00 2001 From: "US\\katie.kroell" Date: Thu, 12 Feb 2026 15:08:12 -0500 Subject: [PATCH 23/55] final styling updates (hopefully) --- .../service-package-question.vue | 30 +++++++++---------- .../service-package-radio.vue | 9 ++++-- .../service-packages/service-packages.vue | 18 +++++++---- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/layouts/service-packages/service-package-question/service-package-question.vue b/src/layouts/service-packages/service-package-question/service-package-question.vue index 87f50898..8c62c893 100644 --- a/src/layouts/service-packages/service-package-question/service-package-question.vue +++ b/src/layouts/service-packages/service-package-question/service-package-question.vue @@ -22,7 +22,6 @@ import servicePackageRadio from '@/layouts/service-packages/service-package-ques import partTypeStrings from '@/constants/part-type-strings'; import { useMainStore } from '@/store'; import { getPriceOfLineItem } from '@/helpers/price-calculator'; -import { getHighestFullySatisfiedTier } from '@/helpers/service-package-helper.js'; const glassLocations = damageLocationsSelected; @@ -62,7 +61,6 @@ export default { if (this.availableLineItems?.lineItems) { return this.availableLineItems.lineItems; } - return this.availableLineItems ?? []; }, servicePackageAnswers() { @@ -146,9 +144,6 @@ export default { } }, watch: { - availableLineItems() { - this.selectDefaultPackage(); - }, selectedPackageName(newValue) { const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue); this.$emit('vapsItemsSelected', VapsProductsInSelectedPackage); @@ -217,17 +212,6 @@ export default { }); return vapsPrice; }, - selectDefaultPackage() { - const { glassToReplace, isRepair } = store.order.damage; - const { vaps } = store.order.lineItems; - const defaultTier = getHighestFullySatisfiedTier( - glassToReplace ?? [], - this.availableLineItems, - isRepair, - vaps ?? [] - ); - this.selectedPackageName = defaultTier; - }, getVapsLineItemsForSelectedPackage(packageName) { const vapsLineItemsForSelectedPackage = []; if (packageName === packageNames.TIER_TWO) { @@ -289,5 +273,19 @@ export default { .service-package-question-text { font-weight: 500; text-align: center; + margin-bottom: 0; +} + +@include media-breakpoint-up(md) { + :deep(.package-main) { + padding: 0 1.5rem 0 1.5rem; + .package-wrapper:nth-child(2) { + margin-left: 0.5rem; + } + .package-wrapper:nth-child(3) { + margin-right: 0; + margin-left: 0.5rem; + } + } } diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index e2ccda45..bfad975a 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -173,7 +173,6 @@ export default { width: 100%; padding: 10px; border: 0.666667px solid #dddddd; - //box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); border-radius: 0.25rem; overflow: hidden; @@ -250,7 +249,7 @@ export default { font-weight: $font-weight-bold; margin-top: 20px; font-size: 0.875rem; - line-height: 21px; + line-height: 24px; } .package-specs { @@ -323,12 +322,16 @@ export default { color: #075f35; font-size: 0.875rem; font-weight: $font-weight-bold; - padding: 1rem 0 1rem 0; + padding: 0.35rem 0 1rem 0; } } } .pricing-info-mobile { + display: flex; + flex: 1; + justify-content: flex-end; + margin-top: 0.25rem; @include media-breakpoint-up(md) { display: none; } diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue index b6ab110f..7fb7a81e 100644 --- a/src/layouts/service-packages/service-packages.vue +++ b/src/layouts/service-packages/service-packages.vue @@ -34,6 +34,7 @@
@@ -219,8 +220,16 @@ export default { From 6d10f3e64f9c9a81ee1086f6a4ca58ae9a6715ad Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Thu, 12 Feb 2026 15:33:22 -0500 Subject: [PATCH 24/55] Re-add new endpoint call --- src/store/index.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/store/index.js b/src/store/index.js index 9ea63b3e..3b86e15a 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1038,8 +1038,24 @@ export const useMainStore = defineStore({ return this.getProviders(zipCode, radius, true) }, - getTpaProviders(zipCode, radius) { - return this.getProviders(zipCode, radius, false) + getTpaAndSafeliteProviders(zipCode, provderNameSearchString = "") { + const { parentAccountNumber } = this.order; + const carId = this.order.vehicle.carId; + const damageType = this.damage.isRepair ? 'Repair' : 'Install'; + + const parts = getLineItemsFlattened(this.order.lineItems.glassParts); + const partNumbers = parts.map((part) => part.partNumber).join(','); + + let url = `${endpoints.GetTpaAndSafeliteProviders.url}/${zipCode}?accountNumber=${parentAccountNumber}&carId=${carId}&damageType=${damageType}&partNumbers=${partNumbers}`; + if (provderNameSearchString) { + url += `&providerNameSearchString=${provderNameSearchString}`; + } + return new Promise((resolve, reject) => { + globalMethods.callHttpClient({ + method: endpoints.GetTpaAndSafeliteProviders.method, + endpoint: url + }).then((response) => resolve(response), (error) => reject(error)); + }); }, getProviders(zipcode, radius, safeliteOnly) { From 555223d7cd113bd84d304fda783e8f158595a1e2 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Thu, 12 Feb 2026 15:34:20 -0500 Subject: [PATCH 25/55] Auto stash before merge of "feature/INSR-7771" and "origin/develop" --- src/global-methods.js | 5 ++- src/store/store.spec.js | 68 +++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/global-methods.js b/src/global-methods.js index 211cc83d..a21dc99c 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -54,7 +54,10 @@ export default { callHttpClient({ method, endpoint, payload, logApiCall = true }) { return new Promise((resolve, reject) => { const store = useMainStore(); - const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO; + let cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO; + if (endpoint.includes ("/location")) { + cfDistroUrl = "https://localhost:44370"; + } const payloadAndAnalyticsData = { ...payload, AppName: 'ISS' }; const sessionKey = getSessionKeyValue(); const headers = { diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 4f4173d8..f92fc95c 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -1966,53 +1966,65 @@ describe('Store', () => { }); }); - describe('getTpaProviders method', () => { + describe('getTpaAndSafeliteProviders method', () => { describe('successful method call', () => { - it.each([[true, 'Repair'], [false, 'Replace']])( - 'calls getProviders api endpoint', + it.each([[true, 'Repair'], [false, 'Install']])( + 'calls getProviders api endpoint with damageType %s', async (isRepair, damageType) => { // Arrange globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({})); const carId = getRandomString(10, 14); const parentAccountNumber = getRandomString(6, 6); + const partNumber = getRandomString(6, 6); store.order.parentAccountNumber = parentAccountNumber; store.order.damage.isRepair = isRepair; store.order.vehicle.carId = carId; - const zipCode = getRandomString(6, 6); - const radius = getRandomInt(5, 100); - const isBigTruck = false; - store.order.vehicle.isBigTruck = isBigTruck; - const expectedEndpoint = endpoints.GetProviders.url( - zipCode, - damageType, - radius, - parentAccountNumber, - 'false', - carId, - null, - isBigTruck - ); + store.order.lineItems.glassParts = [{ partNumber }]; + const zipCode = getRandomString(5, 5); + + const expectedEndpoint = `${endpoints.GetTpaAndSafeliteProviders.url}/${zipCode}?accountNumber=${parentAccountNumber}&carId=${carId}&damageType=${damageType}&partNumbers=${partNumber}`; // Act - await store.getTpaProviders(zipCode, radius); + await store.getTpaAndSafeliteProviders(zipCode); // Asserts expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({ - method: endpoints.GetProviders.method, + method: endpoints.GetTpaAndSafeliteProviders.method, endpoint: expectedEndpoint })); } ); + it('includes providerNameSearchString in endpoint when provided', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({})); + + const carId = getRandomString(10, 14); + const parentAccountNumber = getRandomString(6, 6); + const partNumber = getRandomString(6, 6); + store.order.parentAccountNumber = parentAccountNumber; + store.order.damage.isRepair = true; + store.order.vehicle.carId = carId; + store.order.lineItems.glassParts = [{ partNumber }]; + const zipCode = getRandomString(5, 5); + const providerNameSearchString = 'Test Shop'; + + // Act + await store.getTpaAndSafeliteProviders(zipCode, providerNameSearchString); + + // Asserts + expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({ + endpoint: expect.stringContaining(`&providerNameSearchString=${providerNameSearchString}`) + })); + }); it('Returns expected response object', async () => { // Arrange const response = { ReferralNumber: getRandomString(6, 6) }; globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(response)); - const zipCode = getRandomString(6, 6); - const radius = getRandomInt(5, 100); + const zipCode = getRandomString(5, 5); // Act - const result = store.getTpaProviders(zipCode, radius); + const result = store.getTpaAndSafeliteProviders(zipCode); // Asserts await expect(result).resolves.toBe(response); @@ -2020,11 +2032,10 @@ describe('Store', () => { it('null response by api => no exceptions thrown', async () => { // Arrange globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(null)); - const zipCode = getRandomString(6, 6); - const radius = getRandomInt(5, 100); + const zipCode = getRandomString(5, 5); // Act - await store.getTpaProviders(zipCode, radius); + await store.getTpaAndSafeliteProviders(zipCode); }); it('no providers returned by api => promise resolves to empty list', async () => { // Arrange @@ -2034,11 +2045,10 @@ describe('Store', () => { } }; globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(responseNoProviders)); - const zipCode = getRandomString(6, 6); - const radius = getRandomInt(5, 100); + const zipCode = getRandomString(5, 5); // Act - const result = await store.getTpaProviders(zipCode, radius); + const result = await store.getTpaAndSafeliteProviders(zipCode); // Asserts expect(result.data.shopProviders).toHaveLength(0); @@ -2060,7 +2070,7 @@ describe('Store', () => { globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(responseProviders)); // Act - const result = await store.getTpaProviders(); + const result = await store.getTpaAndSafeliteProviders(); // Asserts expect(result.data.shopProviders).toHaveLength(2); From 709b8cdc90f459600859be516baf92ed7314e8b1 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Thu, 12 Feb 2026 15:34:57 -0500 Subject: [PATCH 26/55] Remove local testing --- src/global-methods.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/global-methods.js b/src/global-methods.js index a21dc99c..211cc83d 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -54,10 +54,7 @@ export default { callHttpClient({ method, endpoint, payload, logApiCall = true }) { return new Promise((resolve, reject) => { const store = useMainStore(); - let cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO; - if (endpoint.includes ("/location")) { - cfDistroUrl = "https://localhost:44370"; - } + const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO; const payloadAndAnalyticsData = { ...payload, AppName: 'ISS' }; const sessionKey = getSessionKeyValue(); const headers = { From cb64e53daf45ca016b25ea821cf400da7cbe28a6 Mon Sep 17 00:00:00 2001 From: "US\\katie.kroell" Date: Thu, 12 Feb 2026 15:38:24 -0500 Subject: [PATCH 27/55] add to do --- .../service-package-radio/service-package-radio.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue index bfad975a..03a9f3db 100644 --- a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -123,6 +123,7 @@ export default { diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue index 5ceefd0d..6715e8f1 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.vue +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -11,19 +11,12 @@
-
- -
+ :key="i" + class="glass-question-container"> +
@@ -51,7 +52,7 @@ import glassPartQuestion from '@/layouts/vehicle-parts/glass-part-question/glass import siteHeader from '@/iss-components/site-header/site-header.vue'; import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue'; -import alert from '@/ux-components/alert/alert.vue'; +import textLink from '@/ux-components/text-link/text-link.vue'; // Supporting Files import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; @@ -61,6 +62,7 @@ import { Form } from 'vee-validate'; import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin'; import BaseFormMixin from '@/mixins/base-form-mixin.js'; import { useMainStore } from '@/store'; +import widgetFields from '@/constants/cms-widget-fields'; export default { name: 'vehicle-parts', @@ -71,7 +73,7 @@ export default { siteHeader, siteSubHeader, siteFooter, - alert + textLink }, mixins: [BaseFormMixin, vehicleQuestionsMixin], async beforeRouteEnter(to, from, next) { @@ -88,17 +90,6 @@ export default { // Call the "next" function to complete the transition to this page. next((vm) => { vm.setCmsContent(resultMap.cmsContent); - - // Glass Part Question dynamic component - Object.keys(vm.$refs) - .filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined) - .forEach((c) => - vm.$refs[c][0].initializeComponent({ - ColorQuestionWidget: - resultMap.cmsContent.ColorQuestionWidget, - FeatureQuestionWidget: - resultMap.cmsContent.FeatureQuestionWidget - })); }); }, data() { @@ -156,6 +147,9 @@ export default { RefPrefix() { return 'partQuestion'; + }, + needHelpLinkText() { + return this.getCmsContent('NeedHelpLinkWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT); } }, mounted() { @@ -220,23 +214,47 @@ export default { }); }); }); + }, + requestCallbackBailout() { + this.mainStore.setBailout(bailoutMessage.RequestCallback()); + this.$router.navigate(navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, this.$route); } } }; diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 2698360f..c3075d3f 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -248,6 +248,10 @@ const routingTable = () => [ { scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, destinationIssPageValue: issPageValues.COVERAGE_STATEMENT + }, + { + scenario: navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, + destinationIssPageValue: issPageValues.BAILOUT_PAGE } ] }, From 4a97b0bc131433747234bc66c63984bd9561223a Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Thu, 12 Feb 2026 16:09:32 -0500 Subject: [PATCH 30/55] Fix bad copy-paste for comment --- .../glass-part-question/glass-part-question.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js index b446332d..fd7afa32 100644 --- a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js +++ b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js @@ -41,6 +41,7 @@ function setupMocks({ glassNameProp, glassLocationProp, colorAnswersProp, modelV }; + // Mock store const partsOrQuestions = pageData ?? { partsOrQuestions: [{ glassName: 'Stationary', glassLocation: 'Rear', parts: [] }] }; useMainStore().pageData = jest.fn(); useMainStore().pageData.mockReturnValue(partsOrQuestions); @@ -48,7 +49,6 @@ function setupMocks({ glassNameProp, glassLocationProp, colorAnswersProp, modelV const wrapper = shallowMount(glassPartQuestion, mountOptions); - // Mock store return { wrapper }; } From 99aa82875399e9153cf1ceaff486253d31256c58 Mon Sep 17 00:00:00 2001 From: AHumphriesSL Date: Thu, 12 Feb 2026 16:18:35 -0500 Subject: [PATCH 31/55] double quotes -> single quotes Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../vehicle-parts/glass-part-question/glass-part-question.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue index f29a6af4..1d5c2115 100644 --- a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue +++ b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue @@ -158,7 +158,7 @@ export default { const exceptions = ['side', 'glass', 'dimming']; let temp = toTitleCase(text); exceptions.forEach((exception) => { - const regEx = new RegExp(exception, "ig"); + const regEx = new RegExp(exception, 'ig'); temp = temp.replace(regEx, exception); }); return temp; From ac0721d47013bf490878c425e0c8091e83acbb1c Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Thu, 12 Feb 2026 16:38:36 -0500 Subject: [PATCH 32/55] Margin tweak --- src/layouts/vehicle-parts/vehicle-parts.vue | 31 +++++++++++---------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue index 6715e8f1..4e75e55f 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.vue +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -13,21 +13,22 @@ ref="siteSubHeader" class="mt-4 subheader" cmsWidgetName="SiteSubHeaderWidget" /> -
- +
+
+ +
Date: Thu, 12 Feb 2026 16:57:59 -0500 Subject: [PATCH 33/55] INSR-7758: Tweak some spacing Includes a change to how the forward button is shows/hides so the size of the footer doesn't change between the states --- .../questions-page-layout/questions-page-layout.vue | 10 +++++++++- src/iss-components/site-footer/site-footer.vue | 12 +++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/iss-components/questions-page-layout/questions-page-layout.vue b/src/iss-components/questions-page-layout/questions-page-layout.vue index 43ce4251..9a20bb12 100644 --- a/src/iss-components/questions-page-layout/questions-page-layout.vue +++ b/src/iss-components/questions-page-layout/questions-page-layout.vue @@ -6,7 +6,7 @@
diff --git a/src/iss-components/site-footer/site-footer.vue b/src/iss-components/site-footer/site-footer.vue index 9499c3c2..a694015e 100644 --- a/src/iss-components/site-footer/site-footer.vue +++ b/src/iss-components/site-footer/site-footer.vue @@ -14,14 +14,13 @@ id="stacked" class="col button-col d-flex px-0"> From a87bcea7c3be95583f6e745da3819bfa22f30d4a Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Thu, 12 Feb 2026 17:08:10 -0500 Subject: [PATCH 34/55] Remove availability indicator logic --- .../tpa-shop-list-button.vue | 120 ------------------ 1 file changed, 120 deletions(-) diff --git a/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue b/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue index 9cbd5961..8fbe5146 100644 --- a/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue +++ b/src/iss-components/tpa-shop-list-button/tpa-shop-list-button.vue @@ -20,28 +20,6 @@ class="m-0 caption ms-2" :class="textPosition">{{ buttonLabelSubCopy }} -
- -
-
-
- {{ badgeText }} -
-
{ - this.availabilityRating = data; - }); - } - } }; @@ -165,68 +107,6 @@ export default { font-size: .75rem; line-height: 1.5rem; } - - .availability-indicator { - background-repeat: no-repeat; - display: flex; - align-items: center; - margin-left: auto; - padding: 0.125rem 0.5rem; - - .availability-badge { - display: inline-flex; - width: .8125rem; - height: .75rem; - background-position: center; - background-repeat: no-repeat; - margin: 0 0.25rem 0 0; - - &.green { - background-image: url($svg-shop-list-button-green-availability); - } - - &.orange { - background-image: url($svg-shop-list-button-orange-availability); - } - } - .button-auxillary-copy { - box-sizing: border-box; - justify-content: right; - line-height: 1.25rem; - font-weight: 500; - font-size: 0.75rem; - align-items: center; - } - - .loader { - padding: 0 0.25rem 0 0.25rem; - padding-top: 0.125rem; - padding-bottom: 0.125rem; - } - - .loader:after { - background-color: $gray-550; - height: 1rem; - width: 1rem; - } - - &.green { - color: $green-700; - background-color: $green-100; - } - - &.orange { - color: $orange-600; - background-color: $orange-100; - } - - &.gray { - color: $gray-600; - background-color: $gray-100; - padding-left: 0.125rem; - padding-right: 0.125rem; - } - } } .row-two { From 053cd44cbedbca711ca974b24effe3ad96ba40e0 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Thu, 12 Feb 2026 17:58:35 -0500 Subject: [PATCH 35/55] INSR-8434: Fix defects seen on contact-details --- src/assets/img/icons/select-disabled.png | Bin 0 -> 1326 bytes .../dropdown-question/dropdown-question.vue | 4 ++-- .../textbox-question/textbox-question.vue | 1 + .../contact-details/contact-details.vue | 18 ++++++++++++--- src/ux-components/alert/alert.vue | 21 +++++++++++++----- 5 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 src/assets/img/icons/select-disabled.png diff --git a/src/assets/img/icons/select-disabled.png b/src/assets/img/icons/select-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..03ac8316652b7385bfb886649bae252967a8243b GIT binary patch literal 1326 zcmV+}1=0G6P)Px(=t)FDR9Fe^SX+o3MHH>uJ+qscl?W=q2NFRD`XTufjS^XPcV}jM!JvtLD)9k< zpkKc5S46**KtMDof*`0wn4Z_J>*6blfBGW|ei$Kw7(roYoSo?@&*`yuYpT0)dni>8k3kt}0QNP^yi_5jD`Sg`ti-xlk^j9XE}P8{qrRc_o97wAMMsib!8+ z=RVIX)yBy+mTSdvq>mgc zEVY_RcyV#@+FrlkR7zi&nh8E~$W7;Lc(xQ?T3Wh%t=DgAt@dw(C0pc>%a~-w2D<4O zV@xLlty)U!yEa0GmMD3dmR9;1+lMFaQ&-d+#=INz{R^bF7*DMO(RY(X9g%nD=gZIW zM?01rH_BM%i21e>uGQ7We(gF{z#=hS;uGQd8lSCV>`*EsRxC27J z+dkG;R#vWBZFjyz_a3nQqMS!PuYAVDM_P=BX#t|In*`br{S_0$T$(63^pQg@>t}s=lP=di{X!%}?(-D=s*YoDi5f5AJ z`%5<~396{uqFv6zSw)#JaoPDX5A5&z(TO|3(K1mlX?-o1~qv zr;5eL*2P0DXtLnZce8v^wtExQpw@Qt?8fi*z-|xh_Q3zK2mXt>TEexK1ZVN3*vBsj zza(6;?&JVq1)5uJx8J~W_hEhsMY~6^$L(VUqV`dU4iIQQ!oQs=>!M7E22v9%5V_G5 zpm?=bi>(?=&Nw!6g)KFLsEHMb`ozG7rdH4Te*I(&9}@_3AeS9RO-C0dxf(ccJFyFV zwN`5$v+=BT#xh3&|2#Fr1^sYc-TMMh_Z1d#O_W>0`NoQcZ(7AU$+T7hH*ymu5o_%~ zl+NGjc{AUKYhD^kyWC=QOkx;_gBI=%EIhRuauE~8jmtr;-*a> zIe3DvHgVd_$=oJBB1{Ljlh^^o_OuW`u)5l=;bdpZOhB6$edO3a=3-78j|r229l(p2 z#ZKE~>;P`X$<7Cyi6S3uV)R8=bY%|ait6SyQJ94JO64S`w+~G+ZFFU+-R_)0>x93d ztx7hJv0;vwV+e$=Q;JV*cItyemsc1!(E90Ee1=n>O@*0|tN-p=?{l>7mY6^iAoAUw ziTvT2nVCP6+!3~=Fo`^^TkG}Cp_}(dfk{`4>CY4S{Gr*|*$YX26H6Uqlf*=>|wp=79izwqV36oeZmw#{_<-u&TZWWx~DvulsPpxJaW(FP$W9~kNId^@bK${qS ksVWJMOk-@v?ZgTFKQebCaIJlJh5!Hn07*qoM6N<$f-y3L(*OVf literal 0 HcmV?d00001 diff --git a/src/digital-components/dropdown-question/dropdown-question.vue b/src/digital-components/dropdown-question/dropdown-question.vue index 0a25d34b..733f33d9 100644 --- a/src/digital-components/dropdown-question/dropdown-question.vue +++ b/src/digital-components/dropdown-question/dropdown-question.vue @@ -156,8 +156,8 @@ export default { &:disabled, &.disabled { background-color: $gray-100; - color: $gray-500; - filter: grayscale(100%); + background-image: url(~@/assets/img/icons/select-disabled.png); + cursor: not-allowed; } } &.dropdown-compact { diff --git a/src/digital-components/textbox-question/textbox-question.vue b/src/digital-components/textbox-question/textbox-question.vue index 85bef043..2d441edb 100644 --- a/src/digital-components/textbox-question/textbox-question.vue +++ b/src/digital-components/textbox-question/textbox-question.vue @@ -394,6 +394,7 @@ input::-webkit-date-and-time-value { &:disabled, &.disabled { background-color: $gray-100; + cursor: not-allowed; } } p { diff --git a/src/layouts/contact-details/contact-details.vue b/src/layouts/contact-details/contact-details.vue index c28d0d3a..0963d216 100644 --- a/src/layouts/contact-details/contact-details.vue +++ b/src/layouts/contact-details/contact-details.vue @@ -57,9 +57,9 @@ ref="stateQuestion" v-model="state" inputId="state" - class="form-group" + class="form-group state-dropdown" :cmsWidgetName="widget.stateQuestion" - :options="states" + :options="getStates" isDisabled /> { + // eslint-disable-next-line no-param-reassign + acc[key] = states[key].toUpperCase(); + return acc; + }, {}); + }, }, watch: { isSameAsPolicyAddress(newValue) { @@ -301,6 +308,11 @@ export default { .form-group { margin-bottom: 1.25rem; } +.state-dropdown { + :deep(option) { + text-transform: uppercase; + } +} .same-address-checkbox { :deep(.form-check-input) { &:checked + label p, diff --git a/src/ux-components/alert/alert.vue b/src/ux-components/alert/alert.vue index 4d50bc1b..29aa5853 100644 --- a/src/ux-components/alert/alert.vue +++ b/src/ux-components/alert/alert.vue @@ -7,7 +7,12 @@ alertClass, cssClassNameForCmsWidget, ]"> -
+
+ type="button"> + class="alert-body collapse" + :class="{ 'show': !startCollapsed }">
Date: Fri, 13 Feb 2026 10:08:22 -0500 Subject: [PATCH 38/55] INSR-8434: remove unnecessary text-transform --- src/layouts/contact-details/contact-details.vue | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/layouts/contact-details/contact-details.vue b/src/layouts/contact-details/contact-details.vue index 0963d216..88e3edfb 100644 --- a/src/layouts/contact-details/contact-details.vue +++ b/src/layouts/contact-details/contact-details.vue @@ -57,7 +57,7 @@ ref="stateQuestion" v-model="state" inputId="state" - class="form-group state-dropdown" + class="form-group" :cmsWidgetName="widget.stateQuestion" :options="getStates" isDisabled /> @@ -308,11 +308,6 @@ export default { .form-group { margin-bottom: 1.25rem; } -.state-dropdown { - :deep(option) { - text-transform: uppercase; - } -} .same-address-checkbox { :deep(.form-check-input) { &:checked + label p, From ff6dca765022b379f43a1b281cb3929dfa3fbac0 Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Thu, 12 Feb 2026 15:53:06 -0500 Subject: [PATCH 39/55] drop off updates for schedule page Morning Drop off. --- .../date-picker/date-picker.vue | 228 +++++++++++++----- src/layouts/schedule-page/schedule-page.vue | 16 +- .../service-location/service-location.vue | 8 +- src/store/index.js | 5 +- 4 files changed, 182 insertions(+), 75 deletions(-) diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue index 09b6ab44..70519fe4 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -99,9 +99,9 @@
@@ -256,6 +261,10 @@ export default { appointmentEstimate() { return getDisplayTextForDurationLength(this.appointmentDurationMinutesMinimum, this.appointmentDurationMinutesMaximum); }, + calendarViewDirection() { + if (this.selectableDatesSetting === 'custom') return 'future'; + return 'past'; + }, daysToAdd() { return this.isMobileView ? this.daysInViewMobile : this.daysInViewStandard; }, @@ -284,10 +293,6 @@ export default { this.todayOverrideDateString || convertDateToDateString(new Date()) ); - }, - calendarViewDirection() { - if (this.selectableDatesSetting === 'custom') return 'future'; - return 'past'; } }, watch: { @@ -301,16 +306,16 @@ export default { this.findFirstAvailableDateInView(); } }, + selectedDate(newValue, oldValue) { + if (newValue !== oldValue) { + this.$emit('dateSelected', newValue); + } + }, selectedTimeSlot(newValue, oldValue) { if (newValue !== oldValue) { const testObj = this.getSelectedTimeSlotInfoObject(newValue); this.$emit('timeSlotSelected', testObj); } - }, - selectedDate(newValue, oldValue) { - if (newValue !== oldValue) { - this.$emit('dateSelected', newValue); - } } }, methods: { @@ -340,50 +345,44 @@ export default { addPremiumFlagToInput(routeCode) { return (`${routeCode}${PREMIUM_TIME_SLOT_ID_FLAG}`); }, - removePremiumFlagFromInput(routeCode) { - return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, ''); + checkIsSelectedDay(index, timeOfDayGrouping) { + const dateToShowString = this.getDateToShowString(index); + return this.selectedDate === dateToShowString && this.selectedTimeOfDayGrouping === timeOfDayGrouping; }, - getSelectedTimeSlotInfoObject(timeSlot) { - const routeCode = timeSlot?.id; - let routeCodeToUse = routeCode; - const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG); - if (routeCodeIncludesPremium) { - routeCodeToUse = this.removePremiumFlagFromInput(routeCode); + checkIsSelectedTimeSlot(timeSlot) { + if (!this.selectedTimeSlot) { + return false; } - - const fullTimeSlot = this.selectableTimeSlotsData?.find((ts) => ts.id === routeCodeToUse); - - if (fullTimeSlot) { - return { - timeSlot: { - date: this.selectedDate, - routeCode: fullTimeSlot.id, - startTime: fullTimeSlot.startTime, - endTime: fullTimeSlot.endTime, - jobMaxMinutes: this.appointmentDurationMinutesMaximum.toString(), - jobMinMinutes: this.appointmentDurationMinutesMinimum.toString() - }, - isPremiumAppointment: !!routeCodeIncludesPremium - }; + return (this.selectedTimeSlot.isDropoff === timeSlot.isDropoff + && this.selectedTimeSlot.startTime === timeSlot.startTime + && this.selectedTimeSlot.timeOfDay === timeSlot.timeOfDay); + }, + displayDropOffInformation(timeSlot) { + if (timeSlot.isDropoff) { + if (timeSlot.timeOfDay === 'morning') { + return this.getCmsContent( + 'DropOffTimeSlotModal', + 'BodyText' + ); + } + return this.getCmsContent( + 'OvernightDropOffTimeSlotModal', + 'BodyText' + ); } - - return { - timeSlot: { - date: null, - startTime: null, - endTime: null, - routeCode: null, - jobMaxMinutes: null, - jobMinMinutes: null - }, - isPremiumAppointment: null - }; + return ''; }, displayTimeSlotTime(timeSlot) { const appointmentType = this.activeAppointmentType || AppointmentTypeStrings.IN_SHOP; if (appointmentType === AppointmentTypeStrings.MOBILE || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) { return `${militaryToTwelveHourTime(timeSlot.startTime)} - ${militaryToTwelveHourTime(timeSlot.endTime)}`; } + if (timeSlot.isDropoff) { + if (timeSlot.timeOfDay === 'morning') { + return 'Drop & Go'; + } + return 'Overnight drop off'; + } return militaryToTwelveHourTime(timeSlot.startTime); }, findFirstAvailableDateInView() { @@ -436,8 +435,7 @@ export default { if (!initialTimeSlot || !initialTimeSlot.startTime) { return; } - const initialStartTime = initialTimeSlot.startTime; - const foundTimeSlot = timeSlots.find((ts) => ts.startTime === initialStartTime); + const foundTimeSlot = timeSlots.find((ts) => ts.startTime === initialTimeSlot.startTime && ts.endTime === initialTimeSlot.endTime); if (foundTimeSlot) { this.selectTimeSlotForDay(foundTimeSlot); } @@ -452,6 +450,44 @@ export default { const dateToShow = this.getDateToShow(index); return convertDateToDateString(dateToShow); }, + getSelectedTimeSlotInfoObject(timeSlot) { + const routeCode = timeSlot?.id; + let routeCodeToUse = routeCode; + const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG); + if (routeCodeIncludesPremium) { + routeCodeToUse = this.removePremiumFlagFromInput(routeCode); + } + + const fullTimeSlot = this.selectableTimeSlotsData?.find((ts) => ts.id === routeCodeToUse); + + if (fullTimeSlot) { + return { + timeSlot: { + date: this.selectedDate, + routeCode: fullTimeSlot.id, + startTime: fullTimeSlot.startTime, + endTime: fullTimeSlot.endTime, + isDropoff: fullTimeSlot.isDropoff, + jobMaxMinutes: this.appointmentDurationMinutesMaximum.toString(), + jobMinMinutes: this.appointmentDurationMinutesMinimum.toString() + }, + isPremiumAppointment: !!routeCodeIncludesPremium + }; + } + + return { + timeSlot: { + date: null, + routeCode: null, + startTime: null, + endTime: null, + isDropoff: null, + jobMaxMinutes: null, + jobMinMinutes: null + }, + isPremiumAppointment: null + }; + }, async gotoNextPage() { const maxPageIndex = Math.floor((this.daysLoaded - 1) / this.daysToAdd); if (this.activePageIndex >= maxPageIndex) { @@ -479,15 +515,15 @@ export default { this.selectedTimeSlot = null; this.findFirstAvailableDateInView(); }, - checkIsSelectedDay(index, timeOfDayGrouping) { - const dateToShowString = this.getDateToShowString(index); - return this.selectedDate === dateToShowString && this.selectedTimeOfDayGrouping === timeOfDayGrouping; + mapTimeSlot(timeSlot, timeOfDay) { + return { + ...timeSlot, + isDropoff: timeSlot.id.endsWith('DROP OFF'), + timeOfDay + }; }, - checkIsSelectedTimeSlot(timeSlot) { - if (!this.selectedTimeSlot) { - return false; - } - return this.selectedTimeSlot.startTime === timeSlot.startTime; + removePremiumFlagFromInput(routeCode) { + return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, ''); }, selectTimeSlotForDay(timeSlot) { if (timeSlot) { @@ -567,13 +603,28 @@ export default { morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); return timeHour < 12; - }), + }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'morning')), afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); return timeHour >= 12; - }), + }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'afternoon')), isSelected: false }; + + if (dateObjectToPush.morningTimeSlots.length > 0) { + const findIndex = dateObjectToPush.morningTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF')); + if (findIndex !== -1) { + const dropOffSlot = dateObjectToPush.morningTimeSlots.splice(findIndex, 1)[0]; + dateObjectToPush.morningTimeSlots.unshift(dropOffSlot); + } + } + if (dateObjectToPush.afternoonTimeSlots.length > 0) { + const findIndex = dateObjectToPush.afternoonTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF')); + if (findIndex !== -1) { + const dropOffSlot = dateObjectToPush.afternoonTimeSlots.splice(findIndex, 1)[0]; + dateObjectToPush.afternoonTimeSlots.push(dropOffSlot); + } + } this.selectableDatesData.push(dateObjectToPush); }); @@ -635,14 +686,28 @@ export default { morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); return timeHour < 12; - }), + }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'morning')), afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); return timeHour >= 12; - }), + }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'afternoon')), isSelected: false }; + if (dateObjectToPush.morningTimeSlots.length > 0) { + const findIndex = dateObjectToPush.morningTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF')); + if (findIndex !== -1) { + const dropOffSlot = dateObjectToPush.morningTimeSlots.splice(findIndex, 1)[0]; + dateObjectToPush.morningTimeSlots.unshift(dropOffSlot); + } + } + if (dateObjectToPush.afternoonTimeSlots.length > 0) { + const findIndex = dateObjectToPush.afternoonTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF')); + if (findIndex !== -1) { + const dropOffSlot = dateObjectToPush.afternoonTimeSlots.splice(findIndex, 1)[0]; + dateObjectToPush.afternoonTimeSlots.push(dropOffSlot); + } + } this.selectableDatesData.push(dateObjectToPush); } }); @@ -832,16 +897,55 @@ export default { text-align: center; cursor: pointer; + :deep(.dropoff-label) { + sup { + top: -0.375rem; + font-size: 0.75rem; + } + } + + & + .time-slot-drop-off-information { + max-height: 0; + overflow: hidden; + transition: max-height 0.75s ease-in-out; + font-weight: 400; + text-align: left; + color: $darker-gray; + + :deep(ul) { + margin: 0; + margin-bottom: 0.375rem; + + > li { + padding-left: 0.625rem; + margin-top: 0.625rem; + } + } + } + &.selected-time-slot { background-color: $background-color-selected; border: solid 1px #0070d1; color:#000; font-weight: 500; + + :deep(.dropoff-label) { + sup { + top: .25rem; + font-size: 1.625rem; + } + } + + & + .time-slot-drop-off-information { + max-height: 12rem; + transition: max-height 0.75s ease-in-out; + } } &.has-date-error { border: 1px solid #d93025; } + } } diff --git a/src/layouts/schedule-page/schedule-page.vue b/src/layouts/schedule-page/schedule-page.vue index 8ee70191..da3ef43a 100644 --- a/src/layouts/schedule-page/schedule-page.vue +++ b/src/layouts/schedule-page/schedule-page.vue @@ -6,7 +6,6 @@
@@ -176,9 +175,7 @@ const getAvailableDates = async ( storeAction.payload.endDate, storeAction.payload.shopAppointmentType, storeAction.payload.providerNumber - ).catch(() => { - console.warn('Error fetching shop time slots...'); - }); + ); } else if (storeAction.payload?.zipCodeOverride) { timeSlotsResponse = await useMainStore().getMobileTimeSlots( storeAction.payload.startDate, @@ -343,7 +340,7 @@ export default { && (((this.selectedAppointmentType === AppointmentTypeStrings.MOBILE || this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) && serviceLocationToUse.mobileProviderNumber) - || (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP + || ((this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP || this.selectedAppointmentType === AppointmentTypeStrings.DROP_OFF) && (this.selectedProvider?.providerNumber || serviceLocationToUse.provider?.providerNumber))); const damageInfo = useMainStore().order.damage.isRepair @@ -652,8 +649,13 @@ export default { return; } - // Save service location then schedule and navigate forward - await this.$refs.serviceLocation.forwardButtonAction(); + let appointmentTypeToUse = this.selectedAppointmentType; + if (appointmentTypeToUse === AppointmentTypeStrings.IN_SHOP && this.selectedTimeSlotInfo.timeSlot?.isDropoff === true) { + appointmentTypeToUse = AppointmentTypeStrings.DROP_OFF; + } + + // Save service location then save schedule then navigate forward + await this.$refs.serviceLocation.forwardButtonAction(appointmentTypeToUse); this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot); this.$router.navigate( this.navigationScenarios.CLICKED_FORWARD, diff --git a/src/layouts/schedule-page/service-location/service-location.vue b/src/layouts/schedule-page/service-location/service-location.vue index d664a8ba..cc96f7c2 100644 --- a/src/layouts/schedule-page/service-location/service-location.vue +++ b/src/layouts/schedule-page/service-location/service-location.vue @@ -308,7 +308,7 @@ export default { } }, selectedAppointmentType(newValue, oldValue) { - if (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP && this.selectedProvider && this.availabilityRating === null) { + if (this.isInshop && this.selectedProvider && this.availabilityRating === null) { this.refreshAvailabilityRating(); const appointmenTypeServiceLocationObj = { @@ -336,7 +336,7 @@ export default { }, selectedProvider(newProvider, oldProvider) { if (newProvider?.providerNumber !== oldProvider?.providerNumber || this.availabilityRating === null) { - const appointmentIsInshop = this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP; + const appointmentIsInshop = this.isInshop; const returnedProvider = { provider: newProvider, refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null @@ -358,7 +358,7 @@ export default { await this.setData(initialData); this.initializingData = false; }, - async forwardButtonAction() { + async forwardButtonAction(appointmentType) { let provider = this.selectedProvider; this.mainStore.updateMobileFee(null); if (this.isMobile) { @@ -382,7 +382,7 @@ export default { state: this.state, zipCode: this.zipCode, zipCodeCtu: this.zipCodeCtu, - appointmentType: this.selectedAppointmentType, + appointmentType: appointmentType || this.selectedAppointmentType, provider }); }, diff --git a/src/store/index.js b/src/store/index.js index e9b39fd0..74c3deb0 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -965,7 +965,8 @@ export const useMainStore = defineStore({ providerNumber, startDate, endDate, - shopAppointmentType, + shopAppointmentType: (shopAppointmentType === AppointmentTypeStrings.IN_SHOP + || shopAppointmentType === AppointmentTypeStrings.DROP_OFF) ? 'InShopOrDropoff' : shopAppointmentType, applicationName: applicationConfig.APPLICATION_NAME, billToAccountNumber: this.billToAccountNumber, parentAccountNumber: this.order.parentAccountNumber, // this.payment.parentAccountNumber, @@ -1605,7 +1606,7 @@ export const useMainStore = defineStore({ order.customer.firstName = data?.customer?.firstName; order.customer.lastName = data?.customer?.lastName; order.customer.emailAddress = data?.customer?.emailAddress; - + order.contactInfo.extension = data?.customer?.homePhone?.slice(10); order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10); order.contactInfo.servicePhone = data?.customer?.servicePhone; From 71ba9f7b8023fe2ea53531e6734f09ebf950ffb9 Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Fri, 13 Feb 2026 11:05:46 -0500 Subject: [PATCH 40/55] three of the 4 issues from card --- .../vin-location-information/vin-location-information.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/layouts/vin-lookup/vin-location-information/vin-location-information.vue b/src/layouts/vin-lookup/vin-location-information/vin-location-information.vue index 72e970ad..7bc12729 100644 --- a/src/layouts/vin-lookup/vin-location-information/vin-location-information.vue +++ b/src/layouts/vin-lookup/vin-location-information/vin-location-information.vue @@ -61,7 +61,6 @@ export default { .vin-toggle { &::after { content: ""; - transition: all 0.5s ease; background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.9' xml:space='preserve'%3e%3cpath d='M8 8.9c-.2 0-.5-.1-.6-.3L.3 1.5C.1 1.4 0 1.1 0 .9 0 .7.1.4.3.3.4.1.7 0 .9 0c.2 0 .5.1.6.3L8 6.7 14.5.2c.1-.1.4-.2.6-.2.2 0 .5.1.6.3s.3.4.3.6c0 .2-.1.5-.3.6L8.6 8.6c-.1.2-.4.3-.6.3z' fill='%231474a2'/%3e%3c/svg%3e"); background-repeat: no-repeat; margin-left: 0.5rem; @@ -72,9 +71,6 @@ export default { &.active::after { transform: rotate(180deg); } - a { - font-size: 0.875rem; - } } } #vin-location-detail-wrapper { From ed4c10af13ca53cfda9a38edbf1c86017a55e568 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Fri, 13 Feb 2026 12:11:20 -0500 Subject: [PATCH 41/55] All non styling changes --- .../address-vehicles-question.vue | 9 ++++--- .../address-vehicles/address-vehicles.vue | 26 ++++--------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue index ba04cf22..d64f3815 100644 --- a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue +++ b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue @@ -49,7 +49,7 @@ export default { vehicles: Array, vehicleSelected: Object, modelValue: String, - cmsWidgetName: String, + questionText: String, validationRules: String, isCarIdDifferent: Boolean, displayMatchedDifferentVehicleAlert: Boolean, @@ -92,9 +92,6 @@ export default { .replaceAll('{custom:vinYmmsFound}', vinYmmsFound) .replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected); }, - questionText() { - return this.getCmsContent('VehicleConfirmationQuestion', 'QuestionText'); - }, selectedVehicleVin: { get() { return this.modelValue; @@ -126,4 +123,8 @@ export default { #address-vehicles-question-alert p { margin-bottom: 0 !important; // Overrides extra margin-bottom on alert body text } + +:deep(div.list-button-content > span:first-child) { + text-transform: uppercase; +} diff --git a/src/layouts/address-vehicles/address-vehicles.vue b/src/layouts/address-vehicles/address-vehicles.vue index 3740b674..07100d77 100644 --- a/src/layouts/address-vehicles/address-vehicles.vue +++ b/src/layouts/address-vehicles/address-vehicles.vue @@ -13,15 +13,6 @@ - { const maskSymbol = '*'; - const vinStart = maskSymbol.repeat(v.vin.length - 6); + const vinStart = maskSymbol.repeat(6); const vinEnd = v.vin.substring(v.vin.length - 6); return { vin: v.vin, @@ -235,13 +226,6 @@ export default { } else { this.displayMatchedDifferentVehicleAlert = true; } - this.$refs.siteFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ` - + `${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model} ${this.forwardButtonCarStyle}`); - } else { - this.$refs.siteFooter.updateButtonText(this.getCmsContent( - 'SiteFooterWidget', - 'ForwardButtonText' - )); } }, deep: true From 88dff3fe650bba6163505dc90bc47e0c43fdff91 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Fri, 13 Feb 2026 12:18:50 -0500 Subject: [PATCH 42/55] Unit test fixes --- src/layouts/address-vehicles/address-vehicles.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/layouts/address-vehicles/address-vehicles.spec.js b/src/layouts/address-vehicles/address-vehicles.spec.js index 542e9a0d..0b83a100 100644 --- a/src/layouts/address-vehicles/address-vehicles.spec.js +++ b/src/layouts/address-vehicles/address-vehicles.spec.js @@ -100,6 +100,9 @@ function setupMocks({ if (contentName === 'ProvideVinAlert') { return 'ProvideVinAlertTestReturn'; } + if (contentName === 'VehicleConfirmationQuestion') { + return 'VehicleConfirmationQuestionTestReturn'; + } return null; }) }, From fd8e8ec3fcb54b5bee9c1922c1dc0a5c41576b09 Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Fri, 13 Feb 2026 11:56:35 -0600 Subject: [PATCH 43/55] INSR-7930 Implement Client Possessive logic --- src/helpers/text-helper.js | 17 +++++++++++++++++ src/layouts/entry-page/entry-page.spec.js | 2 ++ src/layouts/entry-page/entry-page.vue | 7 +++++++ src/store/index.js | 1 + 4 files changed, 27 insertions(+) diff --git a/src/helpers/text-helper.js b/src/helpers/text-helper.js index 1b54084f..379fa56e 100644 --- a/src/helpers/text-helper.js +++ b/src/helpers/text-helper.js @@ -133,3 +133,20 @@ export function formatDate(date, formatter) { } return ''; } + +/** + * @function toPossessive + * @param {string | null} input + * @returns {string} + */ +export function toPossessive(input) { + if (input == null || input.trim().length === 0) { + return input; + } + + if (input.toLowerCase().endsWith("s")) { + return `${input}'` + } + + return `${input}'s` +} \ No newline at end of file diff --git a/src/layouts/entry-page/entry-page.spec.js b/src/layouts/entry-page/entry-page.spec.js index afa28c8d..ee238737 100644 --- a/src/layouts/entry-page/entry-page.spec.js +++ b/src/layouts/entry-page/entry-page.spec.js @@ -144,6 +144,7 @@ describe('entry-page.vue', () => { TPAEnabled: true, ClientFullName: 'Full Name', ClientDisplayName: 'Display Name', + ClientPossessiveName: 'Display Name\'s', ClaimRegistrationRequired: true, EnableNoCompQuote: true }) @@ -153,6 +154,7 @@ describe('entry-page.vue', () => { expect(issConfig.clientName).toBe('TestClient'); expect(issConfig.clientFullName).toBe('Full Name'); expect(issConfig.clientDisplayName).toBe('Display Name'); + expect(issConfig.clientPossessiveName).toBe('Display Name\'s'); expect(issConfig.parentAccountNumber).toBe('12345'); expect(issConfig.styleSheet).toBe('test-style'); expect(issConfig.isCoverageEnabled).toBe(true); diff --git a/src/layouts/entry-page/entry-page.vue b/src/layouts/entry-page/entry-page.vue index 3918dc33..e9880117 100644 --- a/src/layouts/entry-page/entry-page.vue +++ b/src/layouts/entry-page/entry-page.vue @@ -14,6 +14,7 @@ import { getISSCookie, updateOrCreateISSCookie } from '@/helpers/cookie-helper.j import { useMainStore } from '@/store'; import showIssLoadingModal from '@/helpers/loading-modal-helper'; import applicationConfig from '@/constants/application-config'; +import { toPossessive } from '@/helpers/text-helper'; export default { name: 'entry-page', @@ -144,6 +145,7 @@ export default { this.mainStore.issConfig.clientName = data.accountName; this.mainStore.issConfig.clientFullName = data.accountName; // Defaults to use the client name. this.mainStore.issConfig.clientDisplayName = data.accountName; // Defaults to use the client name. + this.mainStore.issConfig.clientPossessiveName = toPossessive(data.accountName); // Defaults to use the client name. this.mainStore.issConfig.parentAccountNumber = data.parentAccountNumber; this.mainStore.issConfig.styleSheet = data.styleSheet; this.mainStore.issConfig.isCoverageEnabled = data.coverageEnabled; @@ -163,6 +165,11 @@ export default { if (clientFlags.ClientDisplayName != null) { this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName; + this.mainStore.issConfig.clientPossessiveName = toPossessive(clientFlags.ClientDisplayName); + } + + if (clientFlags.ClientPossessiveName != null) { + this.mainStore.issConfig.clientPossessiveName = clientFlags.ClientPossessiveName; } if (clientFlags.ClaimRegistrationRequired) { diff --git a/src/store/index.js b/src/store/index.js index 74c3deb0..676fadb7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -247,6 +247,7 @@ export const getDefaultState = () => ({ clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name clientFullName: 'Generic Insurance', // this is the default and will be overriden by the client's name or client's full name. clientDisplayName: 'Generic Insurance', // this is the default and will be overridden by the client's name or client display name. + clientPossessiveName: 'Generic Insurance\'s', // this is the default and will be overridden by the client's name or client display name converted to possessive or the clients possessive name. clientHeader: {}, styleSheet: '', // Stylesheet used by the client. parentAccountNumber: 0, // Parent account number used by the client. From 1f690985a0910e9ce7487ee52ecc61ad536300c4 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Fri, 13 Feb 2026 14:02:01 -0500 Subject: [PATCH 44/55] INSR-7752: Validation tweaks, alert tweak to fix collapse animation --- src/constants/error-messages.js | 4 +- .../contact-details/contact-details.vue | 5 +- src/ux-components/alert/alert.vue | 68 ++++++++++--------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index 3292660a..62e79340 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -68,7 +68,9 @@ const errorMessages = Object.freeze({ BAILOUT_EMAIL_ADDRESS_REQUIRED: 'Email address is required.', TPA_SEARCH_ZIP_FORMAT: 'Please enter a valid Zip code.', TPA_SEARCH_SHOP_FORMAT: 'Please enter a valid shop name.', - TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.' + TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.', + SERVICE_ADDRESS_REQUIRED: 'Service street address is required.', + SERVICE_CITY_REQUIRED: 'Service city is required.' }); export default errorMessages; diff --git a/src/layouts/contact-details/contact-details.vue b/src/layouts/contact-details/contact-details.vue index 021fabd0..5a84b8f3 100644 --- a/src/layouts/contact-details/contact-details.vue +++ b/src/layouts/contact-details/contact-details.vue @@ -135,8 +135,8 @@ import widgetFields from '@/constants/cms-widget-fields'; import states from '@/constants/states'; // DEFINE VALIDATION RULES -defineRule('street-address-required', required(errorMessages.STREET_ADDRESS_REQUIRED)); -defineRule('city-required', required(errorMessages.CITY_REQUIRED)); +defineRule('street-address-required', required(errorMessages.SERVICE_ADDRESS_REQUIRED)); +defineRule('city-required', required(errorMessages.SERVICE_CITY_REQUIRED)); export default { name: 'contact-details', @@ -221,7 +221,6 @@ export default { }, getStates() { return Object.keys(states).reduce((acc, key) => { - // eslint-disable-next-line no-param-reassign acc[key] = states[key].toUpperCase(); return acc; }, {}); diff --git a/src/ux-components/alert/alert.vue b/src/ux-components/alert/alert.vue index 6c0ba950..a6323194 100644 --- a/src/ux-components/alert/alert.vue +++ b/src/ux-components/alert/alert.vue @@ -77,42 +77,44 @@
- +
From 740c5e1498e243ed78014f320677a3da2dde4815 Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Fri, 13 Feb 2026 14:52:28 -0500 Subject: [PATCH 45/55] Big truck, button styling When selected but unfocused the border now matches --- src/iss-components/shop-list-button/shop-list-button.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/iss-components/shop-list-button/shop-list-button.vue b/src/iss-components/shop-list-button/shop-list-button.vue index 67091bde..b7ffff54 100644 --- a/src/iss-components/shop-list-button/shop-list-button.vue +++ b/src/iss-components/shop-list-button/shop-list-button.vue @@ -123,7 +123,6 @@ $heritage-checked-border-color: #0070d1; &:checked + .list-button-content { background: $background-color-selected; border-color: $heritage-checked-border-color; - box-shadow: 0 0 0 1px $blue; .button-label-copy { font-weight: 500; color: $black; From 78ae497f33f04732985b17e265600ca8468c4bf6 Mon Sep 17 00:00:00 2001 From: Jeremy-Z Date: Fri, 13 Feb 2026 15:34:37 -0500 Subject: [PATCH 46/55] Checking in updated package-lock.json file after all the package changes. --- package-lock.json | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9fa7874..3eaedf24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,6 @@ "concurrently": "^9.1.2", "dotenv-safe": "^9.1.0", "eslint": "^9.39.2", - "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsdoc": "^61.5.0", "eslint-plugin-vue": "^10.6.2", @@ -9377,19 +9376,6 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-import-resolver-alias": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", - "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - }, - "peerDependencies": { - "eslint-plugin-import": ">=1.4.0" - } - }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", From 01148a74ab2e9d0a24300fd49ddb3ee13edb3f33 Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Fri, 13 Feb 2026 16:35:48 -0500 Subject: [PATCH 47/55] INSR-8457: Adjust spacing to account for change to subheader. Update colors for alert-danger icons --- src/layouts/address-lookup/address-lookup.vue | 8 +++++--- src/styles/ux-variables.scss | 1 + src/ux-components/alert/alert.vue | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue index d3db4c1f..cf6e6e4c 100644 --- a/src/layouts/address-lookup/address-lookup.vue +++ b/src/layouts/address-lookup/address-lookup.vue @@ -14,9 +14,11 @@ ref="siteSubHeader" cmsWidgetName="SiteSubHeaderWidget" class="mt-4" /> - +
+ +
Date: Fri, 13 Feb 2026 17:12:23 -0500 Subject: [PATCH 48/55] INSR-7774: Skip vehicle-lookup if not windshield replacement --- src/layouts/vehicle-damage/vehicle-damage.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index 265ad56a..5fac4f61 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -216,6 +216,13 @@ export default { === damageLocationsSelected.REPAIR ); }, + isWindshieldReplace() { + return ( + this.isWindshieldDamageLocation + && this.selectedWindshieldOptions.selectedWindshieldDamageType + === damageLocationsSelected.REPLACE + ); + }, isDriverSideReplace() { if (!this.isSideDoorDamageLocation) return false; @@ -418,7 +425,7 @@ export default { this.navigationScenarios.CLICKED_FORWARD_WITH_REPAIR, this.$route ); - } else if (this.mainStore.order.vehicle.vin) { + } else if (this.mainStore.order.vehicle.vin || !this.isWindshieldReplace) { // If vin already exists, navigate directly to vin-lookup const partsOrQuestionsResponse = await this.getPartsOrQuestions(); From 916f56a50e08b7c5e50e11c2b6044cb90129cbed Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Fri, 13 Feb 2026 17:27:28 -0500 Subject: [PATCH 49/55] INSR-8455: Skip vehicle-lookup on back navigation when appropriate --- src/helpers/damage-helper.js | 9 +++++++++ src/mixins/vehicle-questions-mixin.js | 3 +++ src/router/router-constants/navigation-scenarios.js | 1 + src/router/router-constants/routing-table.js | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/src/helpers/damage-helper.js b/src/helpers/damage-helper.js index 7083d7c0..be5d5370 100644 --- a/src/helpers/damage-helper.js +++ b/src/helpers/damage-helper.js @@ -90,6 +90,15 @@ export function getGlassList(glassPieces) { return names.toLowerCase(); } +export function includesWindshieldReplacement() { + const mainStore = useMainStore(); + const windshieldMatches = + mainStore.damage.glassToReplace?.filter( + (glassToReplace) => glassToReplace.glassLocation === damageLocationsSelected.WINDSHIELD + ) ?? []; + return windshieldMatches.length > 0; +} + /** * Commented code are copied directly from DigitalConsumer.FixMyGlass * and have not been adjusted for ISS. diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index b92bb837..d5c492d8 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -1,3 +1,4 @@ +import { includesWindshieldReplacement } from '@/helpers/damage-helper'; import issPageValues from '@/router/router-constants/issPage-values'; import navigationScenarios from '@/router/router-constants/navigation-scenarios'; import { useMainStore } from '@/store'; @@ -457,6 +458,8 @@ export default { let backNavigationScenario = ''; if (self.mainStore.order.damage.isRepair) { backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_REPAIR; + } else if (!includesWindshieldReplacement()) { + backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS; } else { backNavigationScenario = self.mainStore.vehicle.vin ? navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js index 6f17494d..d6fba845 100644 --- a/src/router/router-constants/navigation-scenarios.js +++ b/src/router/router-constants/navigation-scenarios.js @@ -61,6 +61,7 @@ const navigationScenarios = Object.freeze({ CLICKED_BACK_WITH_MOLDING_QUESTIONS: 'CLICKED_BACK_WITH_MOLDING_QUESTIONS', CLICKED_BACK_WITH_CAPABILITY_QUESTIONS: 'CLICKED_BACK_WITH_CAPABILITY_QUESTIONS', CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: 'CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS', + CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS: 'CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS', CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: 'CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS', // Schedule diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index c3075d3f..eec16cf3 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -536,6 +536,10 @@ const routingTable = () => [ scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, 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, destinationIssPageValue: issPageValues.VEHICLE_LOOKUP From 00a8e89dbea878f98cfa0e9a524e35bc78fff3dd Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 16 Feb 2026 09:47:50 -0500 Subject: [PATCH 50/55] move alerts and update red --- .../license-plate-lookup.vue | 36 +++++++++---------- src/ux-components/alert/alert.vue | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue index c7eab0f8..ac6bb727 100644 --- a/src/layouts/license-plate-lookup/license-plate-lookup.vue +++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue @@ -14,6 +14,24 @@ cmsWidgetName="SiteSubHeaderWidget" class="mt-4" subHeaderMarginClasses="mt-1" /> + + - - Date: Mon, 16 Feb 2026 09:57:10 -0500 Subject: [PATCH 51/55] move alerts above continue button --- src/layouts/vin-lookup/vin-lookup.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 66bc41a9..3bb0d637 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -14,8 +14,6 @@ - + Date: Mon, 16 Feb 2026 10:14:44 -0500 Subject: [PATCH 52/55] INSR-8455: Add some tests, fix others --- .../vehicle-damage/vehicle-damage.spec.js | 50 ++++++++++- .../vehicle-parts/vehicle-parts.spec.js | 2 + src/mixins/vehicle-questions-mixin.spec.js | 89 ++++++++++++++++--- 3 files changed, 128 insertions(+), 13 deletions(-) diff --git a/src/layouts/vehicle-damage/vehicle-damage.spec.js b/src/layouts/vehicle-damage/vehicle-damage.spec.js index 42c4ed6b..db8ae50d 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.spec.js +++ b/src/layouts/vehicle-damage/vehicle-damage.spec.js @@ -5,6 +5,7 @@ import routerParams from '@/router/router-constants/router-params'; import { useMainStore } from '@/store'; import vehicleCategories from '@/constants/vehicle-categories'; import VehicleDamageComponent from '@/layouts/vehicle-damage/vehicle-damage.vue'; +import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin'; const mockRoute = { params: {} @@ -12,6 +13,16 @@ const mockRoute = { const mockRouter = { navigate: jest.fn() }; +jest.mock('@/mixins/vehicle-questions-mixin', () => ({ + methods: { + navigateForward: jest.fn(), + getPartsOrQuestions: jest.fn(() => Promise.resolve({ + data: { + partsOrQuestions: [] + } + })) + } +})); const mountOptions = { global: { mixins: [ @@ -91,6 +102,43 @@ describe('vehicle-damage.vue', () => { expect(mockRouter.navigate) .toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_REPAIR, mockRoute); }); + test('When damage selected is a replace but not windshield, navigate forward from vehicle-questions-mixin', async () => { + mountOptions.global.plugins = [createTestingPinia({ + initialState: { + main: { + order: { + damage: { + isRepair: false, + glassToReplace: [{ glassLocation: 'Rear', glassName: 'Stationary' }] + } + } + } + } + })]; + const wrapper = mount(VehicleDamageComponent, mountOptions); + const siteFooterWrapper = wrapper.getComponent({ ref: 'siteFooter' }); + + useMainStore().getSupportingItems = jest.fn().mockImplementation(() => Promise.resolve({ + data: { data: [ + { + description: null, + partNumber: 'SUPPLIES-REPAIR', + partType: 'REPAIR FEE' + }, + { + description: null, + partNumber: 'WSREPAIR', + partType: 'REPAIR FEE' + } + ] } + })); + + siteFooterWrapper.vm.$emit('forwardClicked'); + + await flushPromises(); + expect(vehicleQuestionsMixin.methods.getPartsOrQuestions).toHaveBeenCalledTimes(1); + expect(vehicleQuestionsMixin.methods.navigateForward).toHaveBeenCalledTimes(1); + }); test('Error in getPartsOrQuestions call => bailout true and navigate forward with CLICKED_FORWARD_WITH_BAILOUT scenario', async () => { mountOptions.global.plugins = [createTestingPinia({ initialState: { @@ -115,7 +163,7 @@ describe('vehicle-damage.vue', () => { const partsQuestionsErrorResponse = { error: 'Error getting parts' }; - useMainStore().getPartsOrQuestions = jest.fn().mockImplementation(() => ( + vehicleQuestionsMixin.methods.getPartsOrQuestions.mockImplementation(() => ( partsQuestionsErrorResponse )); siteFooterWrapper.vm.$emit('forwardClicked'); diff --git a/src/layouts/vehicle-parts/vehicle-parts.spec.js b/src/layouts/vehicle-parts/vehicle-parts.spec.js index e05f2298..909bf57c 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.spec.js +++ b/src/layouts/vehicle-parts/vehicle-parts.spec.js @@ -212,6 +212,7 @@ describe('vehicle-parts.vue', () => { test('User had part questions > navigateBack triggers a router.navigateWithoutSaving change with correct scenario', async () => { // Arrange + useMainStore().damage.glassToReplace = [{ glassLocation: 'Rear', glassName: 'Stationary' }, { glassLocation: 'Windshield', glassName: 'Single' }]; const { wrapper } = setupMocks({ mountOptionsMockData: { router: { @@ -244,6 +245,7 @@ describe('vehicle-parts.vue', () => { test('User did not have part questions > navigateBack triggers a router.navigate change with correct scenario', async () => { // Arrange + useMainStore().damage.glassToReplace = [{ glassLocation: 'Rear', glassName: 'Stationary' }, { glassLocation: 'Windshield', glassName: 'Single' }]; const { wrapper } = setupMocks({ mountOptionsMockData: { router: { diff --git a/src/mixins/vehicle-questions-mixin.spec.js b/src/mixins/vehicle-questions-mixin.spec.js index 5efde63c..a878e09f 100644 --- a/src/mixins/vehicle-questions-mixin.spec.js +++ b/src/mixins/vehicle-questions-mixin.spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import { setupMocksForJsFiles, getMountOptions } from '@/helpers/unit-test-helper.js'; import issPageValues from '@/router/router-constants/issPage-values'; import navigationScenarios from '@/router/router-constants/navigation-scenarios'; -import { useMainStore } from '@/store'; +import { useMainStore, getDefaultState } from '@/store'; /** @ignore */ function setupMocks({ issPage = issPageValues.VIN_LOOKUP }) { @@ -53,6 +53,7 @@ function setupMocks({ issPage = issPageValues.VIN_LOOKUP }) { describe('vehicle-questions-mixin', () => { afterEach(() => { jest.clearAllMocks(); + useMainStore().order = getDefaultState().order; }); describe('hasPartQuestions', () => { @@ -2181,19 +2182,83 @@ describe('vehicle-questions-mixin', () => { }); describe('navigateBackByVehicleQuestions', () => { - test('current page is coverage-statement and damage is repair => go to vehicle-damage', () => { - // Arrange - const { wrapper } = setupMocks({ issPage: issPageValues.COVERAGE_STATEMENT }); - useMainStore().order.damage.isRepair = true; + describe('current page is coverage-statement', () => { + test('damage is repair => go to vehicle-damage', () => { + // Arrange + const { wrapper } = setupMocks({ issPage: issPageValues.COVERAGE_STATEMENT }); + useMainStore().order.damage.isRepair = true; - // Act - wrapper.vm.navigateBackByVehicleQuestions(); + // Act + wrapper.vm.navigateBackByVehicleQuestions(); - // Assert - expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalledWith( - navigationScenarios.CLICKED_BACK_WITH_REPAIR, - { query: { issPage: issPageValues.COVERAGE_STATEMENT } } - ); + // Assert + expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalledWith( + navigationScenarios.CLICKED_BACK_WITH_REPAIR, + { query: { issPage: issPageValues.COVERAGE_STATEMENT } } + ); + }); + test('damage is replace but not windshield => go to vehicle-damage', () => { + // Arrange + const { wrapper } = setupMocks({ issPage: issPageValues.COVERAGE_STATEMENT }); + useMainStore().order.damage.isRepair = false; + useMainStore().order.damage.glassToReplace = [{ glassLocation: 'Rear', glassName: 'Stationary' }]; + + // Act + wrapper.vm.navigateBackByVehicleQuestions(); + + // Assert + expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalledWith( + navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN_AND_NO_MORE_QUESTIONS, + { query: { issPage: issPageValues.COVERAGE_STATEMENT } } + ); + }); + test('damage is replace and includes windshield, we have vin => go to vehicle-damage', () => { + // Arrange + const { wrapper } = setupMocks({ issPage: issPageValues.COVERAGE_STATEMENT }); + useMainStore().order.damage.isRepair = false; + useMainStore().order.damage.glassToReplace = [{ glassLocation: 'Rear', glassName: 'Stationary' }, { glassLocation: 'Windshield', glassName: 'Single' }]; + useMainStore().order.vehicle.vin = '5NMS3CADXLH233004'; + + // Act + wrapper.vm.navigateBackByVehicleQuestions(); + + // Assert + expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalledWith( + navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, + { query: { issPage: issPageValues.COVERAGE_STATEMENT } } + ); + }); + test('damage is replace and includes windshield => go to vehicle-lookup', () => { + // Arrange + const { wrapper } = setupMocks({ issPage: issPageValues.COVERAGE_STATEMENT }); + useMainStore().order.damage.isRepair = false; + useMainStore().order.damage.glassToReplace = [{ glassLocation: 'Rear', glassName: 'Stationary' }, { glassLocation: 'Windshield', glassName: 'Single' }]; + + // Act + wrapper.vm.navigateBackByVehicleQuestions(); + + // Assert + expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalledWith( + navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS, + { query: { issPage: issPageValues.COVERAGE_STATEMENT } } + ); + }); + test('damage is replace and includes windshield but there are parts to choose => go to vehicle-parts', () => { + // Arrange + const { wrapper } = setupMocks({ issPage: issPageValues.COVERAGE_STATEMENT }); + useMainStore().order.damage.isRepair = false; + useMainStore().order.damage.glassToReplace = [{ glassLocation: 'Rear', glassName: 'Stationary' }, { glassLocation: 'Windshield', glassName: 'Single' }]; + wrapper.vm.hasGlassLocationWithMultipleParts = jest.fn().mockReturnValue(true); + + // Act + wrapper.vm.navigateBackByVehicleQuestions(); + + // Assert + expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalledWith( + navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE, + { query: { issPage: issPageValues.COVERAGE_STATEMENT } } + ); + }); }); test( From b6b5212638be0a438ad58dd87f05bf5e28494931 Mon Sep 17 00:00:00 2001 From: AHumphriesSL Date: Mon, 16 Feb 2026 10:17:06 -0500 Subject: [PATCH 53/55] Update misleading comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/layouts/vehicle-damage/vehicle-damage.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index 5fac4f61..b486f90e 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -426,7 +426,7 @@ export default { this.$route ); } else if (this.mainStore.order.vehicle.vin || !this.isWindshieldReplace) { - // If vin already exists, navigate directly to vin-lookup + // If vin already exists or not replacing windshield, get parts/questions and navigate forward const partsOrQuestionsResponse = await this.getPartsOrQuestions(); if (partsOrQuestionsResponse.error) { From 6ad3e29869bed16c5fbfc20e442b612a2d0bf23c Mon Sep 17 00:00:00 2001 From: Josh Dassinger Date: Mon, 16 Feb 2026 10:55:37 -0600 Subject: [PATCH 54/55] Fixed assigning the default store state --- src/store/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/index.js b/src/store/index.js index 676fadb7..34d77533 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2457,7 +2457,7 @@ export const useMainStore = defineStore({ // populate initial state populateInitialState(forceReset) { if (!sessionStorage.getItem(storeId) || forceReset) { - this.$state = state; + this.$state = getDefaultState(); } }, From 6aae14975f5041d8d07ef29160a70e4f298d821b Mon Sep 17 00:00:00 2001 From: Alex Humphries Date: Mon, 16 Feb 2026 12:54:46 -0500 Subject: [PATCH 55/55] Fix alert icon red --- src/styles/ux-variables.scss | 1 - src/ux-components/alert/alert.vue | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss index cde2f976..e9455d7f 100644 --- a/src/styles/ux-variables.scss +++ b/src/styles/ux-variables.scss @@ -218,7 +218,6 @@ $alert-yellow-bg: #fef6e8; $alert-yellow-color: #e86421; $alert-red-bg: #fbe9e8; $alert-red-color: $red; -$alert-red-icon: #da291c; //Modal animation // This affects all [Bootstrap] modals diff --git a/src/ux-components/alert/alert.vue b/src/ux-components/alert/alert.vue index 34b9856d..0e0ea2be 100644 --- a/src/ux-components/alert/alert.vue +++ b/src/ux-components/alert/alert.vue @@ -355,7 +355,7 @@ export default { border-top-color: $alert-red-color; } svg { - fill: $alert-red-icon; + fill: $alert-red-color; width: 1rem; height: 1rem; }