Merge branch 'develop' into feature/INSR-8325

This commit is contained in:
Katie Kroell 2026-02-16 14:13:36 -05:00
commit 6362735849
125 changed files with 5905 additions and 7314 deletions

View file

@ -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']
}
}
}
};

149
eslint.config.js Normal file
View file

@ -0,0 +1,149 @@
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 <script> without a lang tag
},
style: {
lang: 'scss',
allowNoLang: false // Disallow <style> without lang="scss"
}
}]
}
},
{
name: 'app/custom-js-rules',
files: ['**/*.{js,ts}'],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
...globals.vitest,
...globals.vue,
...globals.jest
}
}
},
// Additional test globals (object) — applies only to test files
// {
// files: ['**/*.spec.*', '**/*.test.*', '**/unit-test-helper.js'],
// ...pluginVitest.configs.recommended,
// },
{
name: 'app/temp-rules',
rules: {
// Temp rules until we make a pass at fixing all of them.
'@stylistic/arrow-parens': 'off',
'@stylistic/brace-style': 'off',
'@stylistic/eol-last': 'off',
'@stylistic/indent': 'off',
'@stylistic/indent-binary-ops': 'off',
'@stylistic/max-len': 'off',
'@stylistic/max-statements-per-line': 'off',
'@stylistic/multiline-ternary': 'off',
'@stylistic/no-trailing-spaces': 'off',
'@stylistic/object-curly-spacing': 'off',
'@stylistic/operator-linebreak': 'off',
'@stylistic/quote-props': 'off',
'@stylistic/quotes': 'off',
'@stylistic/semi': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'jsdoc/reject-any-type': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/require-returns-type': 'off',
'jsdoc/ts-no-empty-object-type': 'off',
'no-case-declarations': 'off',
'no-constant-binary-expression': 'off',
'no-dupe-keys': 'off',
'no-import-assign': 'off',
'no-shadow': 'off',
'vitest/no-conditional-expect': 'off',
'vitest/no-identical-title': 'off',
'vitest/valid-expect': 'off',
'vitest/valid-expect-in-promise': 'off',
'vitest/valid-title': 'off',
'vue/max-len': 'off',
'vue/multi-word-component-names': 'off',
'vue/no-reserved-component-names': 'off',
'vue/no-side-effects-in-computed-properties': 'off',
'@typescript-eslint/no-require-imports': 'off',
'vitest/no-commented-out-tests': 'off',
'vitest/no-focused-tests': 'off',
'no-undef': 'off',
'vue/require-toggle-inside-transition': 'off',
'vue/no-dupe-keys': 'off',
'@typescript-eslint/no-this-alias': 'off',
'vitest/expect-expect': 'off',
'no-param-reassign': 'off',
}
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**', 'playwright-tests', '**/*.snap']),
);

8182
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,10 @@
"test:unit": "vue-cli-service test:unit --coverage --ci --colors",
"test:unit:coverage": "vue-cli-service test:unit --coverage --ci --colors",
"test:unit:lite": "vue-cli-service test:unit --ci",
"test:playwright": "playwright test --config=playwright-tests/playwright.config.ts"
"test:playwright": "playwright test --config=playwright-tests/playwright.config.ts",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"lint:inspect": "eslint --inspect-config"
},
"dependencies": {
"axios": "^1.13.5",
@ -31,11 +34,13 @@
"vue-router": "4.2.4"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@faker-js/faker": "^9.0.3",
"@pinia/testing": "0.1.2",
"@playwright/test": "^1.56.1",
"@rushstack/eslint-patch": "^1.3.2",
"@saucelabs/playwright-reporter": "^1.5.0",
"@stylistic/eslint-plugin": "^5.6.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/user-event": "14.4.3",
"@testing-library/vue": "6.6.1",
@ -48,6 +53,8 @@
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-unit-jest": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.1",
"@vue/vue3-jest": "^27.0.0-alpha.1",
"axe-core": "^4.10.2",
@ -57,8 +64,11 @@
"concurrently": "^9.1.2",
"dotenv-safe": "^9.1.0",
"eslint": "^9.39.2",
"eslint-plugin-vue": "^9.15.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsdoc": "^61.5.0",
"eslint-plugin-vue": "^10.6.2",
"form-data": "^4.0.4",
"globals": "^17.0.0",
"jest": "^27.0.5",
"jest-junit": "^13.0.0",
"jest-serializer-vue": "^3.1.0",
@ -66,13 +76,14 @@
"jsdom": "^22.1.0",
"luxon": "^3.5.0",
"ortoni-report": "^2.0.8",
"prettier": "^3.7.4",
"sass": "^1.77.8",
"sass-loader": "^12.0.0",
"saucectl": "^0.188.0",
"typescript-eslint": "^8.11.0",
"vite": "^6.4.1",
"vitest": "^3.2.4",
"volar-service-vetur": "latest",
"vue-eslint-parser": "^10.2.0",
"wait-on": "^8.0.2"
}
}

View file

@ -44,7 +44,7 @@ export default {
watch: {
shouldShowLoader(newVal) {
// prevent keyboard input when loader is shown, re-enable when hidden
if(newVal) {
if (newVal) {
document.onkeydown = () => false;
}
else {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -93,6 +93,10 @@ const endpoints = Object.freeze({
) => `${LOCATION_BASE_URL}/providers/${zipCode}/${damageType}/${radius}/${parentAccountNumber}/${safeliteOnly}/${carId}/${recalPartNumber != null ? `${recalPartNumber}/` : ''}${isBigTruck}`,
method: 'GET'
},
GetTpaAndSafeliteProviders: {
url: `${LOCATION_BASE_URL}/tpa-and-safelite-providers`,
method: 'GET'
},
GetCapabilityQuestions: {
url: `${PARTS_BASE_URL}/capability-questions`,
method: 'GET'
@ -118,7 +122,6 @@ const endpoints = Object.freeze({
zipCode,
applicationName,
referralSequenceNumber
// eslint-disable-next-line max-len
) => `${PARTS_BASE_URL}/recal-parts/${carId}/${partNumber}/${recalibrationType}/${parentAccountNumber}/${zipCode}/${applicationName}/${referralSequenceNumber}`,
method: 'GET'
},

View file

@ -31,9 +31,7 @@ const errorMessages = Object.freeze({
INVALID_ZIP: 'The ZIP you entered was invalid. Please enter a valid ZIP.',
ZIP_CODE_NOT_SERVICED_FOR_VEHICLE: 'We do not currently offer glass service for your vehicle in this ZIP code. Please try another ZIP code.',
VIN_REQUIRED: 'Please enter your VIN',
VIN_FORMAT:
// eslint-disable-next-line max-len
'Invalid VIN. Please make sure that you entered the correct 17-digit, alpha-numeric number. VINs do not contain the letters I, O, or Q',
VIN_FORMAT: 'Invalid VIN. Please make sure that you entered the correct 17-digit, alpha-numeric number. VINs do not contain the letters I, O, or Q',
OPTION_REQUIRED: 'Please select an option',
VEHICLE_REQUIRED: 'Please select a vehicle',
POLICY_NUMBER_REQUIRED: 'Policy number is required.',
@ -67,7 +65,12 @@ 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.'
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.',
SERVICE_ADDRESS_REQUIRED: 'Service street address is required.',
SERVICE_CITY_REQUIRED: 'Service city is required.'
});
export default errorMessages;

View file

@ -28,7 +28,8 @@ 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'
});
export default globalRules;

View file

@ -1,91 +0,0 @@
// TintMap with array keys by location, lowercase to avoid as much string mismatching as possible.
// Src assumes you have a @/assets/img/tints/, making the final value @/assets/img/tints/{Src} in the markup.
// See vehicle-parts for implementation example.
const tintMap = Object.freeze({
other: [
// Blue Shade
{ name: 'blue tint, blue shade', src: 'Glass-BlueShade-BlueTint.svg' },
{ name: 'brown tint, blue shade', src: 'Glass-BlueShade-BrownTint.svg' },
{ name: 'gray tint, blue shade', src: 'Glass-BlueShade-GrayTint.svg' },
{ name: 'green tint, blue shade', src: 'Glass-BlueShade-GreenTint.svg' },
{ name: 'clear, blue shade', src: 'Glass-BlueShade-NoTint.svg' },
// Brown Shade
{ name: 'brown tint, brown shade', src: 'Glass-BrownShade-BrownTint.svg' },
// Gray Shade
{ name: 'blue tint, gray shade', src: 'Glass-GrayShade-BlueTint.svg' },
{ name: 'brown tint, gray shade', src: 'Glass-GrayShade-BrownTint.svg' },
{ name: 'gray tint, gray shade', src: 'Glass-GrayShade-GrayTint.svg' },
{ name: 'green tint, gray shade', src: 'Glass-GrayShade-GreenTint.svg' },
// Green Shade
{ name: 'blue tint, green shade', src: 'Glass-GreenShade-BlueTint.svg' },
{ name: 'brown tint, green shade', src: 'Glass-GreenShade-BrownTint.svg' },
{ name: 'green tint, green shade', src: 'Glass-GreenShade-GreenTint.svg' },
// Tints Only
{ name: 'blue tint privacy', src: 'Glass-NoShade-BlueTint.svg' },
{ name: 'bronze tint', src: 'Glass-NoShade-BrownTint.svg' },
{ name: 'dark brown tint', src: 'Glass-NoShade-DarkBrownTint.svg' },
{ name: 'privacy, black frame', src: 'Glass-NoShade-Privacy.svg' },
{ name: 'gray tint', src: 'Glass-NoShade-GrayTint.svg' },
{ name: 'green tint', src: 'Glass-NoShade-GreenTint.svg' },
{ name: 'gray tint privacy', src: 'Glass-NoShade-GrayTint.svg' },
{ name: 'blue tint', src: 'Glass-NoShade-BlueTint.svg' },
{ name: 'green tint privacy', src: 'Glass-NoShade-GreenTint.svg' },
// No shade or tint
{ name: 'clear', src: 'Glass-NoShade-NoTint.svg' }
],
windshield: [
// Blue Shade
{ name: 'blue tint, blue shade', src: 'Windshield-BlueShade-BlueTint.svg' },
{ name: 'bronze tint, blue shade', src: 'Windshield-BlueShade-BrownTint.svg' },
{ name: 'gray tint, blue shade', src: 'Windshield-BlueShade-GrayTint.svg' },
{ name: 'green tint, blue shade', src: 'Windshield-BlueShade-GreenTint.svg' },
{ name: 'clear, blue shade', src: 'Windshield-BlueShade-NoTint.svg' },
// Brown Shade
{ name: 'bronze tint, bronze shade', src: 'Windshield-BrownShade-BrownTint.svg' },
// Gray Shade
{ name: 'blue tint, gray shade', src: 'Windshield-GrayShade-BlueTint.svg' },
{ name: 'brown tint, gray shade', src: 'Windshield-GrayShade-BrownTint.svg' },
{ name: 'gray tint, gray shade', src: 'Windshield-GrayShade-GrayTint.svg' },
{ name: 'green tint, gray shade', src: 'Windshield-GrayShade-GreenTint.svg' },
// Green Shade
{ name: 'blue tint, green shade', src: 'Windshield-GreenShade-BlueTint.svg' },
{ name: 'brown tint, green shade', src: 'Windshield-GreenShade-BrownTint.svg' },
{ name: 'green tint, green shade', src: 'Windshield-GreenShade-GreenTint.svg' },
// Tints Only
{ name: 'blue tint', src: 'Windshield-NoShade-BlueTint.svg' },
{ name: 'bronze tint', src: 'Windshield-NoShade-BrownTint.svg' },
{ name: 'dark brown tint', src: 'Windshield-NoShade-DarkBrownTint.svg' },
{ name: 'privacy, black frame', src: 'Windshield-NoShade-Privacy.svg' },
{ name: 'gray tint', src: 'Windshield-NoShade-GrayTint.svg' },
{ name: 'green tint', src: 'Windshield-NoShade-GreenTint.svg' },
{ name: 'gray tint privacy', src: 'Windshield-NoShade-GrayTint.svg' },
// No shade or tint
{ name: 'clear', src: 'Windshield-NoShade-NoTint.svg' }
]
});
// Gets the tint image source string given the glass location, and the tint description (like 'Green Tint')
// Use lowered strings here to try to avoid mismatch. Returns an empty string if array key doesn't exist.
// Returns undefined if no items are found.
const getTintImage = (glassLocation, colorString) => {
// Windshield glass has special images, all other glass uses the same though.
const glassLoc = (glassLocation.toLowerCase() !== 'windshield') ? 'other' : 'windshield';
if (tintMap[glassLoc] === undefined) {
return '';
}
return tintMap[glassLoc].find((item) => item.name.toLowerCase() === colorString.toLowerCase());
};
export default getTintImage;

View file

@ -119,7 +119,6 @@ export default {
this.handleClick(e);
break;
case this.eventTypes.CHANGE:
// eslint-disable-next-line no-unused-expressions
this.selectingInitiatesLoad
? this.handleSelectionChange(e)
: this.handleClick(e);

View file

@ -1,4 +1,3 @@
/* eslint-disable max-len */
import { shallowMount } from '@vue/test-utils';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import { getMountOptions } from '@/helpers/unit-test-helper.js';

View file

@ -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-grow-0 flex-shrink-0';
}
break;
case 'providerPrefRadio':
classes = 'option-wrapper';
@ -325,6 +332,12 @@ export default {
<style lang="scss" scoped>
.button-question {
:deep(.package-wrapper) {
@media (min-width: 768px) {
flex: 0 0 calc(33.333% - 0.67rem);
max-width: calc(33.333% - 0.67rem);
}
}
color: $black;
.radio-button-container {
@ -345,6 +358,11 @@ export default {
&.button-question-text-left {
text-align: left;
}
&.service-package-question-text {
font-weight: 500;
text-align: center;
margin-bottom: 0.5rem;
}
}
}

View file

@ -99,9 +99,9 @@
</div>
<template
v-for="timeSlot in selectableTimeSlotsData"
:key="timeSlot.startTime">
:key="`${timeSlot.startTime}-${timeSlot.isDropoff}`">
<input
:id="`timeslot${timeSlot.startTime}`"
:id="`timeslot${timeSlot.startTime}-${timeSlot.isDropoff}`"
v-model="selectedTime"
type="radio"
name="time-slot"
@ -114,9 +114,14 @@
class="time-slot-button"
:class="[checkIsSelectedTimeSlot(timeSlot) ? 'selected-time-slot' : ''
, {'has-date-error': showTimeSlotError}]"
@click="selectTimeSlotForDay(timeSlot)">
{{ displayTimeSlotTime(timeSlot) }}
@click="selectTimeSlotForDay(timeSlot)"
v-html="displayTimeSlotTime(timeSlot)">
</label>
<div
v-if="timeSlot.isDropoff"
class="time-slot-drop-off-information"
v-html="displayDropOffInformation(timeSlot)">
</div>
</template>
</div>
<div class="row form-test-error">
@ -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 '<span class="dropoff-label">Drop & Go<sup>&trade;</sup></span>';
}
return '<span class="dropoff-label">Overnight drop off</span>';
}
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);
});
@ -593,7 +644,6 @@ export default {
this.findFirstAvailableDateInView();
let attempts = 0;
while (!this.selectedDate && attempts < 5) {
// eslint-disable-next-line no-await-in-loop
await this.gotoNextPage();
attempts += 1;
}
@ -636,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);
}
});
@ -833,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;
}
}
}

View file

@ -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 {

View file

@ -63,7 +63,6 @@ import { useForm } from 'vee-validate';
import { modalPositions } from '@/constants/component-variants';
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'modal',
components: {
ButtonMain
@ -103,7 +102,6 @@ export default {
// TODO: Correct Duplicate key 'modalId' issue.
// Probably just rename the prop. -br
return {
// eslint-disable-next-line vue/no-dupe-keys
modalId,
meta,
validate,

View file

@ -157,7 +157,6 @@ export default {
initialValue
};
// eslint-disable-next-line no-shadow
const { errorMessage, handleBlur, handleChange, meta, validate, errors } =
useField(props.inputId, props.validationRules, fieldOptions);
@ -310,22 +309,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: 3rem;
}
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: 4.5rem 0 0 4.5rem;
height: 3rem;
&: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 4.5rem 4.5rem 0;
background-color: $blue-100;
width: 2.75rem;
height: 100%;
border: 1px solid $gray-500;
border: 1px solid #0070d1;
border-left: none;
display: flex;
}
@ -394,6 +402,7 @@ input::-webkit-date-and-time-value {
&:disabled,
&.disabled {
background-color: $gray-100;
cursor: not-allowed;
}
}
p {

View file

@ -96,7 +96,7 @@ export default {
);
}
if (error.response.status != '404') {
if (error.response.status !== 404) {
global.$logger.logError(
`${method}: ${endpoint}: ${error.message}`,
error.response

View file

@ -1,8 +1,3 @@
/* eslint-disable no-use-before-define */
/* eslint-disable no-param-reassign */
/* eslint-disable jsdoc/require-param-type */
/* eslint-disable jsdoc/require-param-description */
/* eslint-disable jsdoc/require-returns */
import dynamicStrings from '@/constants/dynamic-strings';
import { useMainStore } from '@/store';
@ -300,7 +295,6 @@ function mapStringToState(str) {
// Our final string value that will be built from the matches.
const stringBuilder = '';
// eslint-disable-next-line no-restricted-syntax
for (const match of globalStateMatches) {
// Reset store state for each match.
const valueFromStore = getStoreValueFromString(match[2]);
@ -331,12 +325,10 @@ function getStoreValueFromString(str) {
if (!str) return '';
let storeOrStateObject = useMainStore();
// eslint-disable-next-line no-restricted-syntax
for (const s of str.split('.')) {
if (s === 'getters') continue; // For backward compatibility
// TODO: I don't think this next line is doing what they think it's doing.
// eslint-disable-next-line eqeqeq, valid-typeof
if (typeof storeOrStateObject[s] != undefined) {
if (typeof storeOrStateObject[s] !== "undefined") {
storeOrStateObject = storeOrStateObject[s];
} else {
break;
@ -384,13 +376,11 @@ export function processIfStatements(str, ifConditionKeyword, replacePlaceholderC
*/
function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
let index = 0;
// eslint-disable-next-line no-restricted-syntax
for (const match of matches) {
if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) {
let interiorIndex = 0;
let nestedLevel = 0;
let elseStatementIndex = null;
// eslint-disable-next-line no-restricted-syntax
for (const interiorMatch of matches.slice(index + 1)) {
if (interiorMatch.groups.isIfStatement) {
if (interiorMatch.groups.ifConditionType === ifConditionKeyword) {
@ -539,7 +529,6 @@ export function doesCopyContainTextLink(copy) {
export function setupModalLinks(context) {
context.$nextTick(() => {
const elements = document.getElementsByClassName('modal-text');
// eslint-disable-next-line no-restricted-syntax
for (const element of elements) {
const target = element.getAttribute('modalTarget');
if (target) {
@ -626,7 +615,7 @@ export function getRouterLinkHtmlStringFromCopy(copy) {
/**
* Returns a phone link as an 'a' tag element
* @param copy
* @param phoneNumber
* @returns string
*/
export function getPhoneLinkHtmlStringFromPhoneNumber(phoneNumber) {

View file

@ -6,7 +6,6 @@ import { useMainStore } from '@/store';
* @function isLocalhost
*/
function isLocalhost() {
// eslint-disable-next-line no-restricted-globals
return location.hostname.includes('localhost');
}
@ -30,7 +29,6 @@ function getCookieValueByName(name) {
Gets current domain without the subdomain for cookie.
*/
function getDomainWithoutSubdomain() {
// eslint-disable-next-line no-restricted-globals
const url = location.hostname;
if (isLocalhost()) {
return 'localhost';

View file

@ -44,7 +44,6 @@ function hasMatchingReplacementOption(vehicleDamageOptions, selectedGlassToRepla
Rear: 'backGlassOptions'
};
// eslint-disable-next-line no-restricted-syntax
for (const glassToReplace of selectedGlassToReplace) {
const propName = optionsMap[glassToReplace.glassLocation];
const { availableReplacementOptions } = vehicleDamageOptions[propName];
@ -91,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.

View file

@ -134,7 +134,6 @@ export function getDateFormat(date, format) {
const hour = (`0${date.getHours()}`).slice(-2);
const minute = (`0${date.getMinutes()}`).slice(-2);
const second = (`0${date.getSeconds()}`).slice(-2);
// eslint-disable-next-line consistent-return
return format
.replace('yyyy', year)
.replace('MM', month)
@ -147,7 +146,6 @@ export function getDateFormat(date, format) {
export function padTo2Digits(time) {
// Use the built-in method toString() with a radix of 10 to convert the time value to a decimal string
// eslint-disable-next-line no-param-reassign
time = time.toString(10);
// Use the conditional operator to check if the length of the string is less than 2
return time.length < 2
@ -172,7 +170,6 @@ export function convertMsToTime(milliseconds) {
export function calculateDuration(startDate, endDate) {
if (startDate instanceof Date !== true) return;
if (endDate instanceof Date !== true) return;
// eslint-disable-next-line consistent-return
return convertMsToTime(endDate - startDate);
}

View file

@ -1,4 +1,3 @@
/* eslint-disable max-len */
import { experimentSettings } from '@/constants/experiments';
export function hasExperimentSetting(storeExperimentSettings, settingName) {

View file

@ -125,6 +125,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
@ -138,6 +160,6 @@ export default function defineGlobalRules() {
defineGlobalPolicyNumberRules();
defineGlobalPolicyZipCodeRules();
defineGlobalZipCodeRules();
defineGlobalTpaSearchRules();
defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED));
}

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line import/prefer-default-export
export function getLineItemsFlattened(lineItems) {
return lineItems?.flatMap((li) => [li, ...(getLineItemsFlattened(li.childParts))]) ?? [];
}

View file

@ -13,7 +13,6 @@ export function deepClone(object) {
}
const clone = { ...object };
// eslint-disable-next-line no-return-assign
Object.keys(clone).forEach((key) =>
(clone[key] = typeof object[key] === 'object' ? deepClone(object[key]) : object[key]));

View file

@ -6,7 +6,6 @@
export function getPriceOfLineItem(lineItem) {
let price = (lineItem.kitPrice ?? 0) + (lineItem.laborAmount ?? 0) + (lineItem.sellingPrice ?? 0);
if (lineItem.childParts && lineItem.childParts.length !== 0) {
// eslint-disable-next-line no-use-before-define
price += getPriceOfLineItems(lineItem.childParts);
}
return price;
@ -15,7 +14,6 @@ export function getPriceOfLineItem(lineItem) {
export function getSalesTaxOfLineItem(lineItem) {
let price = lineItem.salesTax ?? 0;
if (lineItem.childParts && lineItem.childParts.length !== 0) {
// eslint-disable-next-line no-use-before-define
price += getTaxOfLineItems(lineItem.childParts);
}
return price;

View file

@ -44,7 +44,6 @@ describe('querystring-helper', () => {
const result = getLineItemQueryString(lineItems, 'param');
// Assert
// eslint-disable-next-line max-len
expect(result).toBe('&param[0].partNumber=a&param[1].partNumber=b&param[2].partNumber=c&param[3].partNumber=d&param[4].partNumber=e');
});
});
@ -74,7 +73,6 @@ describe('querystring-helper', () => {
const result = getTaxLineItemQueryString(lineItems, 'param');
// Assert
// eslint-disable-next-line max-len
expect(result).toBe('&param[0].partNumber=a&param[0].laborAmount=0&param[0].sellingPrice=10&param[0].kitPrice=10&param[1].partNumber=b&param[1].laborAmount=10&param[1].sellingPrice=10&param[1].kitPrice=0&param[2].partNumber=c&param[2].laborAmount=5&param[2].sellingPrice=5&param[2].kitPrice=5&param[3].partNumber=d&param[3].laborAmount=10&param[3].sellingPrice=10&param[3].kitPrice=10');
});
});

View file

@ -46,7 +46,7 @@ export function createUnorderedListFromStringOfParagraphs(stringOfParagraphs) {
*/
export function toTitleCase(text) {
let temp = text?.toLowerCase() ?? '';
return temp.replace(/(^|\s|-)\S/g, (letter) => letter.toUpperCase());
return temp.replace(/(^|\s|-|\/)\S/g, (letter) => letter.toUpperCase());
}
/**
@ -114,7 +114,7 @@ const currencyFormatter = new Intl.NumberFormat('en-US', {
/**
* @function formatAmountInDollars
* @param {string, number} amount
* @param {string | number} amount
* @returns {string}
*/
export function formatAmountInDollars(amount) {
@ -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`
}

View file

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import { RouterLinkStub } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import navigationScenarios from '@/router/router-constants/navigation-scenarios.js';
@ -60,9 +59,7 @@ export function getMountOptions(mockData) {
// Heritage integration common methods
export const cookies = {
[cookieNames.ISS_SESSION_INFO]:
// eslint-disable-next-line max-len
'{"ReferralNumber":"1566818","ReferralDate":"2022-03-15T10:56:24.597","ReferralCorrelationId":"404d2b04-f86e-45c3-b373-127b6217b060","ShouldResetState":false}',
[cookieNames.ISS_SESSION_INFO]: '{"ReferralNumber":"1566818","ReferralDate":"2022-03-15T10:56:24.597","ReferralCorrelationId":"404d2b04-f86e-45c3-b373-127b6217b060","ShouldResetState":false}',
UNIQUE_SESSION_ID: '33756020-b58e-4ec7-b8b8-3f1576719c40',
anotherCookie: '{}',
someOtherCookie: '{}',

View file

@ -232,7 +232,6 @@ export default {
const self = this;
this.$nextTick(() => {
this.showAllFields = true;
// eslint-disable-next-line no-restricted-syntax
let processedStreetAddress = false;
let processedRoute = false;
for (const component of googlePlace.address_components) {

View file

@ -373,7 +373,7 @@ export default {
const label = this.cartOrder.damage.glassToReplace?.length > 0
? this.getCmsContent(this.widget.warrantyText, widgetFields.TEXT_BLOCK_WIDGET.TEXT)
: this.getCmsContent(this.widget.guaranteeText, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
return {
return {
name: label,
cartItemType: cartItemType.WARRANTY,
partType: partTypeStrings.WARRANTY,

View file

@ -90,7 +90,6 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, propsData
async function awaitingSetupTicks(wrapper) {
for (let i = 0; i < 11; i++) {
// eslint-disable-next-line no-await-in-loop
await wrapper.vm.$nextTick();
}
}

View file

@ -119,8 +119,8 @@ export default {
const map = await this.getMap(zipBounds?.getCenter());
await this.addMarkersToMap(map, markerPositions);
let positionsToDisplay = markerPositions.map((marker) => marker.position);
if(zipBounds) {
const positionsToDisplay = markerPositions.map((marker) => marker.position);
if (zipBounds) {
positionsToDisplay.push(zipBounds.getNorthEast());
positionsToDisplay.push(zipBounds.getSouthWest());
}

View file

@ -1,4 +1,3 @@
/* eslint-disable max-len */
// Components
import questionsPageLayout from '@/iss-components/questions-page-layout/questions-page-layout.vue';

View file

@ -6,7 +6,7 @@
<div class="iss-heritage-container-width">
<div class="questions-page-layout-container iss-heritage-content-container-width">
<siteSubHeader
class="mt-4"
class="mt-4 subheader"
cmsWidgetName="SiteSubHeaderWidget" />
<alert
v-if="alertFewMoreQuestionsHeader || alertFewMoreQuestionsCopy"
@ -129,6 +129,13 @@ export default {
padding-left: .9375rem;
padding-right: .9375rem;
}
.subheader {
:deep(strong) {
color: $black;
font-weight: 600;
text-transform: uppercase;
}
}
}
.questions-page {
@ -141,6 +148,7 @@ export default {
}
}
.need-help-link-container {
margin-top: 1.875rem;
margin-bottom: 2.25rem;
}
</style>

View file

@ -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;

View file

@ -14,14 +14,13 @@
id="stacked"
class="col button-col d-flex px-0">
<buttonMain
v-if="!isForwardButtonHidden"
ref="buttonMain"
variant="navigation"
:buttonText="buttonText"
:class="
(disableForwardAction || isForwardActionDisabled) &&
'form-test-invalid'
"
:class="{
'form-test-invalid': (disableForwardAction || isForwardActionDisabled),
'hide-button': isForwardButtonHidden
}"
:aria-disabled="disableForwardAction || isForwardActionDisabled"
:isDisabled="disableForwardAction || isForwardActionDisabled"
data-test-id="site-footer-main-button"
@ -149,5 +148,8 @@ export default {
margin-top: 1.25rem;
}
}
.hide-button {
visibility: hidden;
}
}
</style>

View file

@ -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';
}
@ -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;
}

View file

@ -0,0 +1,117 @@
<template>
<transition
name="fade"
mode="out-in">
<baseInputButton
v-bind="$props"
v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 label-margin-bottom no-hover">
<div
:aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
<div class="row-one">
<span
id="buttonLabelSpan"
class="m-0 button-label-copy"
:class="textPosition">{{ buttonLabel }}
</span>
<span
id="buttonLabelSubCopySpan"
class="m-0 caption ms-2"
:class="textPosition">{{ buttonLabelSubCopy }}
</span>
</div>
<div class="row-two">
<span
v-if="buttonBodyCopy"
id="buttonBodyCopy"
class="m-0 button-label-sub-copy"
v-html="buttonBodyCopy"></span>
</div>
<span
v-if="screenReaderOnlyText"
id="screenReaderOnlyTextSpan"
class="sr-only">
{{ screenReaderOnlyText }}
</span>
</div>
</baseInputButton>
</transition>
</template>
<script>
import baseInputButton from '@/digital-components/base-input-button/base-input-button.vue';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
export default {
name: 'tpa-shop-list-button',
components: {
baseInputButton
},
mixins: [inputButtonWrapperMixin],
};
</script>
<style lang="scss" scoped>
@import "@/styles/ux-variables-svg-strings.scss";
.list-button {
outline: none;
input[type="radio"],
input[type="checkbox"] {
position: static; //override bootstrap
&:checked + .list-button-content {
background: #e7f1f6;
border: 1px solid #0070d1;
}
}
}
.list-button-content {
color: $gray-600;
position: relative;
background: $white;
transition: all 150ms linear;
border-radius: $border-radius-lg;
border: 1px solid $gray-500;
width: 100%;
outline: none;
span {
&.small {
font-size: 0.75rem;
color: $gray-550;
}
}
}
.button-content {
row-gap: 0.25rem;
color: #4d4e53;
font-size: .9375rem;
font-weight: 400;
line-height: 1.625rem;
.row-one {
display: flex;
align-items: center;
line-height: 1.5rem;
.button-label-copy {
font-weight: 600;
line-height: 1.5rem;
text-transform: capitalize;
}
#buttonLabelSubCopySpan {
font-size: .75rem;
line-height: 1.5rem;
}
}
.row-two {
text-align: left;
}
}
div.col:not(:last-child) >.label-margin-bottom{
margin-bottom: 1rem;
}
</style>

View file

@ -167,9 +167,7 @@ describe('address-lookup.vue', () => {
expect(wrapper.findComponent({ ref: 'alertMatchedTwoIdenticalYMMVehicle' }).isVisible()).toBe(true);
});
// eslint-disable-next-line max-len
test(
'if the looking up VIN by address is not allowed in the state selected display the Vin Lookup By HomeAddress Not Allowed Alert',
test('if the looking up VIN by address is not allowed in the state selected display the Vin Lookup By HomeAddress Not Allowed Alert',
async () => {
// Arrange
const mockRegistrationAddress = {
@ -364,9 +362,7 @@ describe('address-lookup.vue', () => {
);
});
// eslint-disable-next-line max-len
test(
'if a different vehicle is found than the one entered and the selected glass is not available for that vehicle, navigate back to vehicle-damage page',
test('if a different vehicle is found than the one entered and the selected glass is not available for that vehicle, navigate back to vehicle-damage page',
async () => {
// Arrange
const mockRegistrationAddress = {

View file

@ -14,9 +14,11 @@
ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" />
<customerQuestions
ref="customerQuestions"
v-model="customerQuestions" />
<div class="mt-4">
<customerQuestions
ref="customerQuestions"
v-model="customerQuestions" />
</div>
<alert
v-if="displayVinNotFoundAlert"
ref="alertVinNotFound"
@ -107,7 +109,6 @@ export default {
siteSubHeader,
customerQuestions,
alert,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [baseFormMixin, vinPagesMixin],
@ -153,9 +154,7 @@ export default {
).replaceAll('{custom:damage}', getDamageString());
},
AlertMatchedDifferentVehicleBody() {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected = `${useMainStore().order.vehicle.year} ${
useMainStore().order.vehicle.make
} ${useMainStore().order.vehicle.model}`;
@ -175,12 +174,8 @@ export default {
).replaceAll('{custom:damage}', getDamageString());
},
AlertMatchedTwoIdenticalYMMVehicleBody() {
const vinYmmsFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model} ${this.customAlertData?.vehicleInfo?.style}`;
const vinYmmsExpected =
// eslint-disable-next-line max-len
`${useMainStore().order.vehicle.year} ${useMainStore().order.vehicle.make} ${useMainStore().order.vehicle.model} ${useMainStore().order.vehicle.style}`;
const vinYmmsFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model} ${this.customAlertData?.vehicleInfo?.style}`;
const vinYmmsExpected = `${useMainStore().order.vehicle.year} ${useMainStore().order.vehicle.make} ${useMainStore().order.vehicle.model} ${useMainStore().order.vehicle.style}`;
return this.getCmsContent(
'AlertMatchedTwoIdenticalYMMVehicleWidget',
@ -191,9 +186,7 @@ export default {
.replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected);
},
isTwoIdenticalYMMVehicleFound() {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected = `${useMainStore().order.vehicle.year} ${
useMainStore().order.vehicle.make
} ${useMainStore().order.vehicle.model}`;
@ -290,9 +283,7 @@ export default {
// Update button "Continue with..."
showIssLoadingModal(false);
return this.$refs.siteFooter
// eslint-disable-next-line max-len
.updateButtonText(`Continue with ${carFound.year} ${carFound.make} ${carFound.model} ${this.forwardButtonCarStyle}`);
return this.$refs.siteFooter.updateButtonText(`Continue with ${carFound.year} ${carFound.make} ${carFound.model} ${this.forwardButtonCarStyle}`);
}
// update data

View file

@ -49,7 +49,7 @@ export default {
vehicles: Array,
vehicleSelected: Object,
modelValue: String,
cmsWidgetName: String,
questionText: String,
validationRules: String,
isCarIdDifferent: Boolean,
displayMatchedDifferentVehicleAlert: Boolean,
@ -81,20 +81,14 @@ export default {
).replaceAll('{custom:damage}', getDamageString());
},
AlertMatchedTwoIdenticalYMMVehicleBody() {
const vinYmmsFound =
// eslint-disable-next-line max-len
`${this.selectedVehicle?.vehicle.year} ${this.selectedVehicle?.vehicle.make} ${this.selectedVehicle?.vehicle.model} ${this.selectedVehicle?.vehicle.style}`;
const vinYmmsExpected =
`${this.vehicleSelected?.year} ${this.vehicleSelected?.make} ${this.vehicleSelected?.model} ${this.vehicleSelected?.style}`;
const vinYmmsFound = `${this.selectedVehicle?.vehicle.year} ${this.selectedVehicle?.vehicle.make} ${this.selectedVehicle?.vehicle.model} ${this.selectedVehicle?.vehicle.style}`;
const vinYmmsExpected = `${this.vehicleSelected?.year} ${this.vehicleSelected?.make} ${this.vehicleSelected?.model} ${this.vehicleSelected?.style}`;
return this.getCmsContent('AlertMatchedTwoIdenticalYMMVehicleWidget', 'BodyText')
.replaceAll('{custom:damage}', getDamageString())
.replaceAll('{custom:vinYmmsFound}', vinYmmsFound)
.replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected);
},
questionText() {
return this.getCmsContent('VehicleConfirmationQuestion', 'QuestionText');
},
selectedVehicleVin: {
get() {
return this.modelValue;
@ -126,4 +120,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;
}
</style>

View file

@ -100,6 +100,9 @@ function setupMocks({
if (contentName === 'ProvideVinAlert') {
return 'ProvideVinAlertTestReturn';
}
if (contentName === 'VehicleConfirmationQuestion') {
return 'VehicleConfirmationQuestionTestReturn';
}
return null;
})
},
@ -190,9 +193,7 @@ describe('address-vehicles.vue', () => {
expect(wrapper.vm.forwardButtonAction).toReturn;
});
// eslint-disable-next-line max-len
test(
'Should navigate to CLICKED_FORWARD scenario if carId is different and selected glass not available for vehicle on navigateForward',
test('Should navigate to CLICKED_FORWARD scenario if carId is different and selected glass not available for vehicle on navigateForward',
async () => {
// Arrange
const { wrapper } = setupMocks({});

View file

@ -13,15 +13,6 @@
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" />
<alert
id="multiple-vehicles-alert"
ref="alertFoundMultipleVehicles"
cmsWidgetName="FoundMultipleVehicles"
class="my-5"
alertClass="alert-warning"
:manualHeadline="AlertFoundMultipleVehiclesHeader"
manualCopy=""
:isDismissible="false" />
<alert
v-if="displayNoServiceAlert"
ref="AlertNoService"
@ -32,7 +23,7 @@
<addressVehiclesQuestion
ref="addressVehiclesQuestion"
v-model="selectedVehicleVin"
cmsWidgetName="VehicleConfirmationQuestion"
:questionText="questionText"
:vehicles="VehiclesForQuestions"
:vehicleSelected="VehicleSelected"
validationRules="vehicle-required"
@ -118,7 +109,6 @@ export default {
siteFooter,
siteHeader,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert,
addressVehiclesQuestion
@ -165,10 +155,10 @@ export default {
vehicleCount() {
return this.VehiclesForQuestions.length;
},
AlertFoundMultipleVehiclesHeader() {
questionText() {
return this.getCmsContent(
'FoundMultipleVehicles',
'HeadlineText'
'VehicleConfirmationQuestion',
'QuestionText'
).replaceAll('{custom:vehicleCount}', this.vehicleCount);
},
isTwoIdenticalYMMVehicleFound() {
@ -187,7 +177,7 @@ export default {
// Map API result data, to address-vehicles data structure
const mappedData = this.VehiclesFromApi.map((v) => {
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 +225,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

View file

@ -108,7 +108,6 @@ export default {
siteSubHeader,
siteFooter,
textboxQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
textBlock
},

View file

@ -29,9 +29,7 @@ const baseStoreGettersPageData = () => ({
capabilityQuestions: [
{
questionSequence: 1,
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?',
questionText: 'Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?',
answers: [
{
answerResult1: 'DYNAMIC',
@ -67,17 +65,13 @@ const baseStoreGettersDamage = () => ({
result: 'FW04848',
answeredQuestions: [
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
questionText: 'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
selectedAnswer: '1|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 1
},
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
questionText: 'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswer: '2|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 2
@ -212,17 +206,13 @@ describe('capabilityQuestions.vue', () => {
answerResult: 'FW04848',
answeredQuestions: [
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
questionText: 'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
selectedAnswer: '1|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 1
},
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
questionText: 'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswer: '2|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 2

View file

@ -35,7 +35,6 @@ import questionsPageLayout from '@/iss-components/questions-page-layout/question
export default {
name: 'capability-questions',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
questionsPageLayout
},

View file

@ -59,7 +59,7 @@
inputId="state"
class="form-group"
:cmsWidgetName="widget.stateQuestion"
:options="states"
:options="getStates"
isDisabled />
<textboxQuestion
ref="zipCodeQuestion"
@ -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',
@ -147,7 +147,6 @@ export default {
checkbox,
textareaQuestion,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert,
buttonQuestion,
@ -219,7 +218,13 @@ export default {
},
vehicleProtectedQuestionAnswers() {
return this.getCmsContent(this.widget.vehicleProtectedQuestion, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS) || [];
}
},
getStates() {
return Object.keys(states).reduce((acc, key) => {
acc[key] = states[key].toUpperCase();
return acc;
}, {});
},
},
watch: {
isSameAsPolicyAddress(newValue) {

View file

@ -1,4 +1,3 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<transition
name="fade"

View file

@ -1,4 +1,3 @@
/* eslint-disable max-len */
// Components
import coverageStatement from '@/layouts/coverage-statement/coverage-statement.vue';
@ -368,7 +367,6 @@ describe('coverageStatement.vue', () => {
[false, coverageStatuses.NO_COVERAGE, coverageType.ITAC],
[false, coverageStatuses.NO_COVERAGE, coverageType.Deductible]
])('isQuoteDisplayed', (expected, status, type) => {
// eslint-disable-next-line max-len
test(`$returns ${expected} when coverageStatus is ${getEnumName(coverageStatuses, status)} and coverageType is ${getEnumName(coverageType, type)}`, () => {
// Arrange
const mainInitialState = {
@ -726,7 +724,6 @@ describe('coverageStatement.vue', () => {
// Act
coverageStatement.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next);
for (let i = 0; i < 7; i++) {
// eslint-disable-next-line no-await-in-loop
await nextTick();
}
@ -752,7 +749,6 @@ describe('coverageStatement.vue', () => {
// Act
coverageStatement.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next);
for (let i = 0; i < 7; i++) {
// eslint-disable-next-line no-await-in-loop
await nextTick();
}

View file

@ -1,8 +1,6 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="fade-on-route-transition coverage-statement">
@ -147,7 +145,6 @@ export default {
name: 'coverage-statement',
components: {
siteHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
contentGroupModal,
buttonMain,

View file

@ -426,7 +426,7 @@ describe('duplicateCheck.vue', () => {
expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED, undefined);
});
// eslint-disable-next-line max-len
test('coverageType deductible and loaded duplicate with policy vehicle => CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE', async () => {
// Arrange
const vin = getRandomString(17, 17);
@ -454,7 +454,7 @@ describe('duplicateCheck.vue', () => {
expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE, undefined);
});
// eslint-disable-next-line max-len
test('coverageType deductible and loaded duplicate with non policy vehicle => CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NON_POLICY_VEHICLE', async () => {
// Arrange
const vin = getRandomString(17, 17);
@ -482,7 +482,7 @@ describe('duplicateCheck.vue', () => {
expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NON_POLICY_VEHICLE, undefined);
});
// eslint-disable-next-line max-len
test('coverageType deductible and loaded duplicate with no policy vehicles => CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NO_POLICY_VEHICLES', async () => {
// Arrange
const { wrapper } = getMountedComponent({

View file

@ -72,7 +72,6 @@ export default {
siteSubHeader,
buttonQuestion,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
buttonMain
},

View file

@ -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);

View file

@ -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) {

View file

@ -256,7 +256,6 @@ describe('license-plate-lookup.vue', () => {
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
// eslint-disable-next-line max-len, function-paren-newline
test('if a different vehicle is found than the one entered and the selected glass is not available for that vehicle, navigate back to vehicle-damage page',
async () => {
// Arrange

View file

@ -14,6 +14,24 @@
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4"
subHeaderMarginClasses="mt-1" />
<textboxQuestion
id="license-plate-question-wrapper"
v-model="licensePlate"
cmsWidgetName="LicensePlateNumberQuestionWidget"
isRequired
disableAutoFill
inputId="license-plate-question"
class="mt-4"
validationRules="license-plate-required" />
<dropdownQuestion
ref="state"
v-model="licenseState"
cmsWidgetName="StateQuestionWidget"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="stateOptions"
disableAutoFill
validationRules="state-required"
class="mt-4" />
<alert
v-if="displayVinNotFoundAlert"
ref="alertVinNotFound"
@ -48,24 +66,6 @@
cmsWidgetName="AlertNoServiceWidget"
alertClass="alert-danger"
:isDismissable="false" />
<textboxQuestion
id="license-plate-question-wrapper"
v-model="licensePlate"
cmsWidgetName="LicensePlateNumberQuestionWidget"
isRequired
disableAutoFill
inputId="license-plate-question"
class="mt-4"
validationRules="license-plate-required" />
<dropdownQuestion
ref="state"
v-model="licenseState"
cmsWidgetName="StateQuestionWidget"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="stateOptions"
disableAutoFill
validationRules="state-required"
class="mt-4" />
<siteFooter
ref="siteFooter"
class="mt-5"
@ -115,7 +115,6 @@ defineRule('state-required', required(errorMessages.STATE_REQUIRED));
export default {
name: 'license-plate-lookup',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
siteFooter,
siteHeader,
@ -169,11 +168,8 @@ export default {
).replaceAll('{custom:damage}', getDamageString());
},
AlertMatchedDifferentVehicleBody() {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make}
${this.mainStore.order.vehicle.model}`;
const vinYmmFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return this.getCmsContent(
'AlertMatchedDifferentVehicleWidget',
@ -190,12 +186,8 @@ export default {
).replaceAll('{custom:damage}', getDamageString());
},
AlertMatchedTwoIdenticalYMMVehicleBody() {
const vinYmmsFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model} ${this.customAlertData?.vehicleInfo?.style}`;
const vinYmmsExpected =
// eslint-disable-next-line max-len
`${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model} ${this.mainStore.order.vehicle.style}`;
const vinYmmsFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model} ${this.customAlertData?.vehicleInfo?.style}`;
const vinYmmsExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model} ${this.mainStore.order.vehicle.style}`;
return this.getCmsContent(
'AlertMatchedTwoIdenticalYMMVehicleWidget',
@ -206,9 +198,7 @@ export default {
.replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected);
},
isTwoIdenticalYMMVehicleFound() {
const vinYmmFound =
// eslint-disable-next-line max-len
`${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make}
${this.mainStore.order.vehicle.model}`;
return vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase();
@ -300,9 +290,7 @@ export default {
showIssLoadingModal(false);
// Update button "Continue with..."
return this.$refs.siteFooter
// eslint-disable-next-line max-len
.updateButtonText(`Continue with ${vehicleFromLookup.year} ${vehicleFromLookup.make} ${vehicleFromLookup.model} ${this.forwardButtonCarStyle}`);
return this.$refs.siteFooter.updateButtonText(`Continue with ${vehicleFromLookup.year} ${vehicleFromLookup.make} ${vehicleFromLookup.model} ${this.forwardButtonCarStyle}`);
}
// Save vehicle, license plate, and registration information

View file

@ -132,17 +132,13 @@ const baseStoreGettersDamage = () => ({
result: 'FW04848',
answeredQuestions: [
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
questionText: 'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
selectedAnswer: '1|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 1
},
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
questionText: 'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswer: '2|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 2
@ -219,17 +215,13 @@ describe('moldingQuestions.vue', () => {
answerResult: 'FW04848',
answeredQuestions: [
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
questionText: 'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
selectedAnswer: '1|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 1
},
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
questionText: 'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswer: '2|nextQuestion|3|Yes',
selectedAnswerText: 'Yes',
questionNum: 2

View file

@ -39,7 +39,6 @@ import questionsPageLayout from '@/iss-components/questions-page-layout/question
export default {
name: 'molding-questions',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
questionsPageLayout
},
@ -150,7 +149,6 @@ export default {
// get parts from the questionAnswers
const partsOrQuestions = this.partsOrQuestionsData;
// eslint-disable-next-line no-restricted-syntax
for (const answer of questionAnswersArray) {
partsOrQuestions.find((partOrQuestion) =>
partOrQuestion.glassLocation === answer.glassLocation

View file

@ -138,7 +138,6 @@ import coverageType from '@/constants/coverage-type';
export default {
name: 'order-confirmation',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
siteHeader,
siteFooter,
@ -239,7 +238,7 @@ export default {
},
orderConfirmationUpdateAppointmentText() {
let content = '';
if(this.isNoComp) {
if (this.isNoComp) {
content = this.getCmsContentWithCustomValues(
this.widgets.emailConfirmationNoComp,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT

View file

@ -90,9 +90,7 @@ const baseStoreGettersPageData = () => ({
partQuestions: [
{
questionSequence: 1,
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
questionText: 'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
answers: [
{
answerResult: '',
@ -122,16 +120,12 @@ const baseStoreGettersDamage = () => ({
result: 'FW04848',
answeredQuestions: [
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
questionText: 'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
selectedAnswerText: 'Yes',
questionNum: 1
},
{
questionText:
// eslint-disable-next-line max-len
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
questionText: 'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswerText: 'Yes',
questionNum: 2
}

View file

@ -36,7 +36,6 @@ import navigationScenarios from '@/router/router-constants/navigation-scenarios'
export default {
name: 'part-questions',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
questionsPageLayout,
},

View file

@ -1,7 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
// eslint-disable-next-line max-len
import paymentMethodListButton from '@/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.vue';
const testConstants = {

View file

@ -90,7 +90,6 @@ import { supportsApplePay } from '@/helpers/browser-helper';
export default {
name: 'payment-method',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
siteHeader,
siteSubHeader,

View file

@ -415,7 +415,6 @@ export default {
cartDropdown,
siteHeader,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert
},

View file

@ -21,7 +21,6 @@ import showIssLoadingModal from '@/helpers/loading-modal-helper';
export default {
name: 'payment-return',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],

View file

@ -79,7 +79,6 @@ export default {
siteSubHeader,
siteFooter,
buttonQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],

View file

@ -108,7 +108,6 @@ export default {
textboxQuestion,
addressQuestions,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],

View file

@ -93,10 +93,7 @@ describe('policy-vehicles.vue', () => {
});
describe('forwardButtonAction', () => {
// eslint-disable-next-line max-len
test(
// eslint-disable-next-line max-len
'Selected VIN matches vehicle listed in system => update vehicle and navigate forward with CLICKED_FORWARD_LISTED_VEHICLE scenario.',
test('Selected VIN matches vehicle listed in system => update vehicle and navigate forward with CLICKED_FORWARD_LISTED_VEHICLE scenario.',
async () => {
// Arrange
const { wrapper } = setupMocks({});
@ -150,9 +147,7 @@ describe('policy-vehicles.vue', () => {
}
);
test(
// eslint-disable-next-line max-len
'Selected VIN matches vehicle listed in system and policy vehicle has Educator endorsement => update vehicle and navigate forward with CLICKED_FORWARD_WITH_ENDORSEMENTS scenario.',
test('Selected VIN matches vehicle listed in system and policy vehicle has Educator endorsement => update vehicle and navigate forward with CLICKED_FORWARD_WITH_ENDORSEMENTS scenario.',
async () => {
// Arrange
const { wrapper } = setupMocks({});
@ -203,9 +198,7 @@ describe('policy-vehicles.vue', () => {
}
);
test(
// eslint-disable-next-line max-len
'Selected VIN matches vehicle listed in system and policy vehicle has Parking Guard endorsement => update vehicle and navigate forward with CLICKED_FORWARD_WITH_ENDORSEMENTS scenario.',
test('Selected VIN matches vehicle listed in system and policy vehicle has Parking Guard endorsement => update vehicle and navigate forward with CLICKED_FORWARD_WITH_ENDORSEMENTS scenario.',
async () => {
// Arrange
const { wrapper } = setupMocks({});
@ -285,7 +278,6 @@ describe('policy-vehicles.vue', () => {
await wrapper.vm.forwardButtonAction();
// Assert
// eslint-disable-next-line max-len
expect(wrapper.vm.mainStore.setBailout).toHaveBeenCalledWith(bailoutMessage.vehicleVinLookupError(vin, lookupReturnValue.data));
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
@ -296,9 +288,7 @@ describe('policy-vehicles.vue', () => {
}
);
test(
// eslint-disable-next-line max-len
'Vehicle not found in lookupVehicleByVin call => policyVinFound false and navigate forward with CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND scenario.',
test('Vehicle not found in lookupVehicleByVin call => policyVinFound false and navigate forward with CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND scenario.',
async () => {
// Arrange
const { wrapper } = setupMocks({});

View file

@ -81,7 +81,6 @@ export default {
siteHeader,
siteFooter,
policyVehiclesQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert
},

View file

@ -88,9 +88,7 @@ export default {
siteFooter,
siteHeader,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
buttonQuestion,
buttonMain,
steeringModal,
tpaRecalModal,
@ -111,7 +109,7 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
// open steering modal if it has body text, the state is defined in the CMS content and doesn't
// populate if the state is not listed in the CMS content
if (!!vm.$refs[STEERING_MODAL_REF_NAME].ModalBodyText) {
if (vm.$refs[STEERING_MODAL_REF_NAME].ModalBodyText) {
vm.openStateSteeringModal();
}
});

View file

@ -136,7 +136,7 @@ export default {
},
footerButtonClick() {
if (this.tpaRecalAnswer && (!this.showAcknowledgementCheckbox || this.acknowledged )) {
if (this.tpaRecalAnswer && (!this.showAcknowledgementCheckbox || this.acknowledged)) {
this.$refs[this.ModalName]?.closeModal();
this.$emit('buttonClick', this.tpaRecalAnswer);
} else if (!this.tpaRecalAnswer) {
@ -182,7 +182,6 @@ export default {
}
}
.form-test-error {
p {
font-weight: 500;

View file

@ -1,4 +1,3 @@
/* eslint-env jest */
import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import issPageValues from '@/router/router-constants/issPage-values';

View file

@ -6,7 +6,6 @@
<div class="fade-on-route-transition">
<div class="justify-content-center">
<siteHeader
class="header"
cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width">
@ -176,17 +175,13 @@ const getAvailableDates = async (
storeAction.payload.endDate,
storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber
).catch(() => {
// eslint-disable-next-line no-console
console.warn('Error fetching shop time slots...');
});
);
} else if (storeAction.payload?.zipCodeOverride) {
timeSlotsResponse = await useMainStore().getMobileTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.zipCodeOverride
).catch(() => {
// eslint-disable-next-line no-console
console.warn('Error fetching mobile time slots...');
});
}
@ -220,7 +215,6 @@ export default {
datePicker,
serviceLocation,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],
@ -346,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
@ -655,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,

View file

@ -91,7 +91,6 @@ import {
getServiceabilityDetails
} from '@/helpers/service-location-helper';
import { deepClone } from '@/helpers/object-helper.js';
// eslint-disable-next-line max-len
import vehicleProtectedQuestion from '@/layouts/schedule-page/service-location/mobile-location-modal-question/vehicle-protected-question/vehicle-protected-question.vue';
export default {
@ -195,7 +194,6 @@ export default {
&& this.addressModel.zipCode !== ''
&& this.internalModel.isVehicleProtected !== null
) {
// eslint-disable-next-line max-len
return `${this.addressModel.streetAddress}\n${this.addressModel.city}, ${this.addressModel.state} ${this.addressModel.zipCode}`;
}
return this.getCmsContent(this.linkWidgetName, 'BodyText');

View file

@ -26,7 +26,6 @@ export default {
buttonQuestion
},
props: {
// eslint-disable-next-line vue/require-prop-types
modelValue: {
isVehicleProtected: Boolean
},

View file

@ -1,4 +1,3 @@
/* eslint-env jest */
import baseMixin from '@/mixins/base-mixin';
import { mount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';

View file

@ -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
});
},
@ -489,7 +489,6 @@ export default {
this.setMobileProviderNumber(initialData.providers.mobileProviderNumber);
let foundMatch = false;
if (this.selectedProvider && this.selectedProvider.providerNumber) {
// eslint-disable-next-line max-len
const matchedProvider = initialData.providers.shopProviders.find((provider) => provider.providerNumber === this.selectedProvider.providerNumber);
if (matchedProvider) {
foundMatch = true;
@ -497,7 +496,6 @@ export default {
}
}
if (initialData.providers.shopProviders.length > 0 && !foundMatch) {
// eslint-disable-next-line prefer-destructuring
this.selectedProvider = initialData.providers.shopProviders[0];
} else if (initialData.providers.shopProviders.length === 0) {
this.selectedProvider = null;
@ -563,7 +561,6 @@ export default {
if (providers) {
this.setMobileProviderNumber(providers.mobileProviderNumber);
if (providers.shopProviders.length > 0) {
// eslint-disable-next-line prefer-destructuring
this.selectedProvider = providers.shopProviders[0];
} else {
this.selectedProvider = null;

View file

@ -177,7 +177,6 @@ export default {
return toTitleCase(this.modelValue.companyName);
},
modalHeaderText() {
// eslint-disable-next-line max-len
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT).replaceAll('{custom:serviceZipcode}', this.internalZipcode);
},
modalFooterText() {
@ -262,14 +261,12 @@ export default {
}
},
methods: {
// eslint-disable-next-line consistent-return
async updateShops(autoExpand = false) {
this.errorMessage = '';
let result = await this.getNearbyShops(this.searchRadiusInMiles);
let currentSearchIndex = this.searchRadiusArray.findIndex((option) => option.Name === this.searchRadiusInMiles);
while (autoExpand && result.length === 0 && currentSearchIndex < this.searchRadiusArray.length - 1) {
const newSearchRadius = this.searchRadiusArray[currentSearchIndex + 1].Name;
// eslint-disable-next-line no-await-in-loop
result = await this.getNearbyShops(newSearchRadius);
currentSearchIndex += 1;
}

View file

@ -2,12 +2,16 @@
<buttonQuestion
ref="buttonQuestion"
v-model="selectedPackageName"
class="service-package-question"
:answers="servicePackageAnswers"
:groupName="groupName"
:questionText="questionText"
:questionTextClasses="['service-package-question-text']"
buttonTypeString="servicePackageRadio"
:buttonTypeObject="servicePackageRadio"
:validationRules="validationRules"
:isRequired="isRequired" />
:isRequired="isRequired"
:isHorizontalLayout="true" />
</template>
<script>
@ -17,9 +21,7 @@ import damageLocationsSelected from '@/constants/damage-locations-selected';
import servicePackageRadio from '@/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue';
import partTypeStrings from '@/constants/part-type-strings';
import { useMainStore } from '@/store';
import allGlassPartsAndItemsHavePrices from '@/layouts/service-packages/service-package-helper/service-package-helper';
import { getPriceOfLineItem } from '@/helpers/price-calculator';
import { getHighestFullySatisfiedTier } from '@/helpers/service-package-helper.js';
const glassLocations = damageLocationsSelected;
@ -50,7 +52,8 @@ export default {
data() {
return {
servicePackageRadio,
selectedPackageName: ''
selectedPackageName: '',
questionText: 'Select an option:'
};
},
computed: {
@ -58,7 +61,6 @@ export default {
if (this.availableLineItems?.lineItems) {
return this.availableLineItems.lineItems;
}
return this.availableLineItems ?? [];
},
servicePackageAnswers() {
@ -142,9 +144,6 @@ export default {
}
},
watch: {
availableLineItems() {
this.selectDefaultPackage();
},
selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit('vapsItemsSelected', VapsProductsInSelectedPackage);
@ -213,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) {
@ -280,3 +268,24 @@ export default {
}
};
</script>
<style lang="scss" scoped>
.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;
}
}
}
</style>

View file

@ -7,18 +7,24 @@
:class="[buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs">
<div>
<p class="m-0">
<span v-html="buttonLabel"></span>
<div class="button-label">
<div class="label-wrapper">
<p class="m-0">
<span
class="mt-1"
v-html="buttonLabel"></span>
</p>
<p
v-if="buttonLabelSubCopy"
class="sub-label m-0"
v-html="buttonLabelSubCopy">
</p>
</div>
<div class="pricing-info-mobile">
<span
class="pricing-info"
class="price"
v-html="buttonAuxiliaryCopy"></span>
</p>
<p
v-if="buttonLabelSubCopy"
class="sub-label m-0"
v-html="buttonLabelSubCopy">
</p>
</div>
</div>
<div class="hide-when-closed">
<ul>
@ -53,10 +59,16 @@
<!--ms-n6-->
<div
v-if="buttonFooterCopy"
class="package-footer fw-bold caption mt-4 ml-n4 mr-3"
v-html="buttonFooterCopy"></div>
class="package-footer fw-bold caption mt-4 mr-3"
v-html="buttonFooterCopy">
</div>
</div>
</div>
<div class="pricing-info-desktop">
<span
class="price"
v-html="buttonAuxiliaryCopy"></span>
</div>
</div>
</baseInputButton>
</template>
@ -111,6 +123,7 @@ export default {
</script>
<style lang="scss" scoped>
// TO DO: look into why the font renders differently than Heritage, even with the same specs
.ml-n4 {
margin-left: -$spacer * 2;
}
@ -121,8 +134,30 @@ 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%;
}
.package-label {
height: 100%;
display: flex;
flex-direction: column;
}
input[type="radio"] {
@ -132,14 +167,15 @@ export default {
+ .package-label {
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
cursor: pointer;
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);
border-radius: 0.5rem;
padding: 10px;
border: 0.666667px solid #dddddd;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
border-radius: 0.25rem;
overflow: hidden;
min-height: 60px;
max-height: 100px;
@ -151,23 +187,11 @@ export default {
min-height: 86px;
}
&:before {
content: "";
position: relative;
top: 5px;
margin-right: 1rem;
border-radius: 50%;
border: 1px solid $gray-500;
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;
@ -176,9 +200,9 @@ export default {
}
&:hover {
+ .package-label {
+ .button-label {
&:before {
border: 1px solid #8e9292;
border: 0.666667px solid #767676;
box-shadow: 0px 0px 0px 4px #9fcee6, 0px 1px 4px rgba(0, 0, 0, 0.2);
}
}
@ -200,42 +224,37 @@ 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 {
+ .button-label {
&:before {
box-shadow: 0px 0px 0px 1px $blue;
box-shadow: 0px 0px 0px 1px $heritage-blue-primary;
border: none;
}
}
+ .package-label {
&:after {
background: $blue;
background: $heritage-blue-primary;
}
}
}
&:focus {
+ .package-label {
&:before {
border: 2px solid $blue;
}
}
}
}
.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: 24px;
}
.package-specs {
display: flex;
flex-direction: column;
flex: 0;
width: 100%;
max-height: 0;
transition: all 1s ease;
@ -243,32 +262,28 @@ export default {
max-height: 500px;
}
p {
font-weight: $font-weight-bold; // 600 in fmg
display: flex;
color: #4d4e53;
font-weight: 600;
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 1.25rem;
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;
a {
line-height: 1.5rem;
@ -291,8 +306,41 @@ export default {
.hide-when-closed {
display: none;
@include media-breakpoint-up(md) {
display: flex;
flex-direction: column;
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;
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;
}
span {
&.price {
color: #075f35;
font-size: 0.875rem;
font-weight: $font-weight-bold;
}
}
}

View file

@ -9,11 +9,10 @@
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width">
<div class="service-packages-container iss-heritage-content-container-width">
<div class="service-packages-container">
<siteSubHeader
class="mt-4"
subHeaderClasses="mt-5"
justification="left"
class="subheader mt-5"
justification="center"
cmsWidgetName="SiteSubHeaderWidget"
issContainingPage="service-packages" />
<servicePackageQuestion
@ -25,15 +24,24 @@
isRequired
@vapsItemsSelected="vapsItemsSelectedAction"
@link-event="openModalAction" />
<p
class="caption disclaimer"
v-html="PriceDisclaimerText"></p>
<div class="d-flex justify-content-center mt-8 mb-4">
<buttonMain
ref="buttonMain"
class="continue-button"
variant="navigation"
:buttonText="'Continue'"
@clickEvent="forwardButtonAction" />
</div>
<siteFooter
cmsWidgetName="SiteFooterWidget"
class="back-link"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBack"
@ForwardClicked="forwardButtonAction" />
:isForwardButtonHidden="true"
@backClicked="navigateBack" />
</div>
<p
class="caption disclaimer"
v-html="PriceDisclaimerText"></p>
</div>
<loadingModal
ref="loadingModal"
@ -72,6 +80,7 @@ import globalRules from '@/constants/global-rules';
import servicePackageQuestion from '@/layouts/service-packages/service-package-question/service-package-question.vue';
import issPageValues from '@/router/router-constants/issPage-values';
import bailoutMessage from '@/constants/bailoutMessage';
import buttonMain from '@/ux-components/button-main/button-main.vue';
const store = useMainStore();
@ -81,11 +90,11 @@ export default {
siteHeader,
siteFooter,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
servicePackageQuestion,
loadingModal,
contentGroupModal
contentGroupModal,
buttonMain
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -210,30 +219,50 @@ export default {
<style lang="scss" scoped>
.iss-heritage-container-width {
.service-packages-container {
@include media-breakpoint-up(md) {
width: 100%;
.continue-button {
width: 50%
}
}
@include media-breakpoint-down(md) {
.continue-button {
width: 90%
}
}
position: relative;
min-height: 1px;
padding-left: .9375rem;
padding-right: .9375rem;
display: block;
margin: 0 auto;
:deep(.subheader-primary) {
display: block;
}
:deep(.subheader-secondary) {
display: block;
text-align: center;
margin-top: 0.75rem;
padding: 0px;
p {
margin-bottom: 18px;
}
}
}
}
.subheader-secondary {
margin-top: 0.5rem;
padding: 0px;
}
.disclaimer {
margin: 0.5rem 0 1.5rem 0;
a {
color: #4d5151;
font-weight: 400;
margin: 0.5rem 1rem 1.5rem 1.5rem;
:deep(.external-text) {
text-decoration: none;
}
}
.service-packages {
:deep(strong) {
font-weight: $font-weight-bold;
font-weight: 400;
}
}
.back-link {
margin-left: 1rem;
}
</style>

View file

@ -69,7 +69,6 @@ export default {
siteHeader,
siteFooter,
textBlock,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],

View file

@ -1,214 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TPA search page returns the initial data 1`] = `
Object {
"additionalButtonData": Object {
"displayAvailabilityIndicators": false,
},
"dataLoaded": false,
"filter": "",
"mapZipCode": null,
"providers": Array [],
"reloadingProviders": false,
"rules": Object {
"filter": "option-required",
"provider": "option-required",
"zipCode": "zip-code-required|zip-code-search-format",
},
"selectedProviderNumber": "",
"shopListButton": Object {
"beforeMount": [Function],
"components": Object {
"baseInputButton": Object {
"computed": Object {
"buttonId": [Function],
"eventTypes": [Function],
"inputType": [Function],
"isChecked": [Function],
"isValueSelectedOnClick": [Function],
},
"data": [Function],
"emits": Array [
"update:modelValue",
],
"methods": Object {
"handleBlur": [Function],
"handleClick": [Function],
"handleEventAction": [Function],
"handleFocus": [Function],
"handleSelectionChange": [Function],
},
"mounted": [Function],
"name": "base-input-button",
"props": Object {
"buttonWrapperClasses": Array [
[Function],
[Function],
[Function],
],
"groupName": Object {
"required": true,
"type": [Function],
},
"inputClasses": Array [
[Function],
[Function],
[Function],
],
"isMultiSelect": [Function],
"isRequired": Object {
"default": true,
"type": [Function],
},
"lastValuePushedToGa": Array [
[Function],
[Function],
],
"modelValue": Object {
"required": true,
"validator": [Function],
},
"selectingInitiatesLoad": Object {
"default": false,
"type": [Function],
},
"setLastValuePushedToGa": [Function],
"suppressError": [Function],
"validationRules": Object {
"default": "",
"type": [Function],
},
"value": Object {
"required": true,
"type": Array [
[Function],
[Function],
],
},
"valueToLogType": [Function],
},
"render": [Function],
"setup": [Function],
},
"loader": Object {
"computed": Object {
"cssProps": [Function],
},
"methods": Object {
"captureClick": [Function],
},
"name": "loader",
"props": Object {
"allowPageInteraction": Object {
"default": false
"type": [Function],
},
"height": Object {
"default": 1,
"type": [Function],
},
"loaderColor": Object {
"type": [Function],
},
"loaderPosition": Object {
"type": [Function],
},
"width": Object {
"default": 1,
"type": [Function],
},
},
"render": [Function],
},
},
"computed": Object {
"availabilityRatingClass": [Function],
"badgeText": [Function],
"isLoaderDisplayed": [Function],
},
"data": [Function],
"mixins": Array [
Object {
"computed": Object {
"selectedValue": Object {
"get": [Function],
"set": [Function],
},
},
"model": Object {
"event": "change",
"prop": "modelValue",
},
"props": Object {
"additionalButtonData": [Function],
"additionalButtonStyling": [Function],
"altText": Object {
"default": "",
"type": [Function],
},
"buttonAuxiliaryCopy": [Function],
"buttonBodyCopy": [Function],
"buttonFooterCopy": [Function],
"buttonImage": [Function],
"buttonImageId": [Function],
"buttonLabel": Array [
[Function],
[Function],
],
"buttonLabelSubCopy": [Function],
"groupName": Object {
"required": true,
"type": [Function],
},
"isMultiSelect": [Function],
"isRequired": Object {
"default": true,
"type": [Function],
},
"isWide": [Function],
"lastValuePushedToGa": Array [
[Function],
[Function],
],
"modelValue": Object {
"required": true,
"validator": [Function],
},
"screenReaderOnlyText": [Function],
"selectingInitiatesLoad": Object {
"default": false,
"type": [Function],
},
"setLastValuePushedToGa": [Function],
"suppressError": [Function],
"textPosition": [Function],
"validationRules": Object {
"default": "",
"type": [Function],
},
"value": Object {
"required": true,
"type": Array [
[Function],
[Function],
],
},
"valueToLogType": [Function],
},
},
],
"name": "shop-list-button",
"render": [Function],
},
"widget": Object {
"filterByQuestion": "FilterByQuestion",
"noNetworkShopsAlert": "NoNetworkShopsAlertWidget",
"searchInstructions": "SearchInstructions",
"shopNotListedLink": "ShopNotListedLink",
"siteFooter": "SiteFooterWidget",
"siteHeader": "SiteHeaderWidget",
"tpaSearchQuestion": "TPASearchQuestion",
},
"zipCode": null,
}
`;

File diff suppressed because it is too large Load diff

View file

@ -15,26 +15,26 @@
<label
id="tpaSearchQuestionLabel"
for="tpaSearchQuestionField"
class="text-center fs-5 mt-5 mb-0 text-black w-100">
class="fs-5 mt-5 text-black w-100">
{{ tpaSearchQuestionLabel }}
</label>
<label
id="searchInstructions"
for="tpaSearchQuestionField"
class="text-center small darker-gray w-100 mb-4">
class="darker-gray w-100 mb-4">
{{ searchInstructionsText }}
</label>
<div class="mb-5">
<textboxQuestion
id="tpaSearchQuestionField"
v-model="zipCode"
v-model="tpaSearchValue"
inputId="tpaSearchQuestionFieldInput"
:cmsWidgetName="widget.tpaSearchQuestion"
:includeSearchIcon="true"
:displayQuestionText="false"
isRequired
:isDisabled="reloadingProviders"
:validationRules="rules.zipCode"
:validationRules="rules.tpaSearch"
@clickEvent="searchClick" />
</div>
</Form>
@ -52,10 +52,10 @@
@invalidSubmit="onInvalidSubmit">
<dropdownQuestion
id="searchRadiusFilter"
v-model="filter"
v-model="radiusFilter"
:cmsWidgetName="widget.filterByQuestion"
inputId="searchRadiusFilterInput"
:options="filterOptions"
:options="radiusFilterOptions"
disableAutoFill
:isDisabled="reloadingProviders"
:validationRules="rules.filter" />
@ -73,7 +73,7 @@
id="selectProviderQuestion"
v-model="selectedProviderNumber"
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
:buttonTypeObject="tpaShopListButton"
class="radioQuestion"
:answers="providerButtonData"
groupName="chooseShop"
@ -99,12 +99,23 @@
:text="shopNotListedModalLink"
@clickEvent="doNotSeeMyShopLinkClick" />
</div>
<hr>
<siteFooter
class="mt-6"
ref="siteFooter"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
<div class="need-help-link-container">
<textLink
id="needHelpLink"
v-if="needHelpLinkText"
linkType="navigation"
:text="needHelpLinkText"
href="#"
@clickEvent="needHelpLinkClick" />
</div>
</div>
</Form>
</div>
@ -123,7 +134,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
@ -135,53 +146,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';
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 };
}
import settleAllPromises from '@/helpers/layout-helper';
import issPageValues from '@/router/router-constants/issPage-values';
export default {
name: 'tpa-search',
@ -195,28 +161,44 @@ export default {
loader,
googleMap,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
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 pageData = useMainStore().pageData(to.query.issPage);
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const providersPromise = useMainStore().getTpaAndSafeliteProviders(zipCode, pageData?.tpaSearchValue ?? '');
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.mapZipCode = zipCode;
vm.providers = resultMap.providers;
if (pageData) {
vm.tpaSearchValue = pageData?.tpaSearchValue ?? vm.mapZipCode;
vm.radiusFilter = pageData?.radiusFilter ?? 25;
}
});
},
data() {
return {
dataLoaded: false,
zipCode: null,
pageData: null,
tpaSearchValue: null,
mapZipCode: null,
filter: '',
radiusFilter: null,
providers: [],
selectedProviderNumber: '',
selectedProviderNumber: useMainStore().order.serviceLocation.provider.providerNumber,
reloadingProviders: false,
additionalButtonData: {
displayAvailabilityIndicators: false
@ -228,28 +210,28 @@ export default {
filterByQuestion: 'FilterByQuestion',
noNetworkShopsAlert: 'NoNetworkShopsAlertWidget',
shopNotListedLink: 'ShopNotListedLink',
siteFooter: 'SiteFooterWidget'
siteFooter: 'SiteFooterWidget',
needHelpLink: 'NeedHelpLink'
},
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
},
shopListButton: shallowRef(shopListButton)
tpaShopListButton: shallowRef(tpaShopListButton)
};
},
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(
@ -257,6 +239,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,
@ -269,25 +254,25 @@ 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);
)?.replaceAll('{custom:radiusInMiles}', this.radiusFilter + " miles");
},
radiusFilteredAndCappedProviders() {
return this.providers.filter((provider) => provider.distanceInMiles <= this.radiusFilter).slice(0, 5);
},
providerAddresses() {
return this.providers?.map((provider) => ({
title: provider.companyName,
return this.radiusFilteredAndCappedProviders?.map((provider) => ({
title: provider.companyName.toLowerCase(),
fullAddress: this.getFullProviderAddress(provider),
addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)]
})) ?? [];
},
providerButtonData() {
return (
this.providers?.map((provider) =>
this.radiusFilteredAndCappedProviders?.map((provider) =>
this.getShopButtonDataFromProvider(provider)) ?? []
);
},
@ -298,14 +283,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 +295,6 @@ export default {
const provider = this.providers?.find((p) => p.providerNumber === newNumber);
if (provider && this.dataLoaded) {
useMainStore().updateServiceLocation({
searchFilter: this.filter,
zipCode: this.zipCode,
provider: {
providerNumber: provider?.providerNumber,
@ -342,13 +318,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,16 +348,16 @@ 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() {
needHelpLinkClick() {
this.mainStore.setBailout(bailoutMessage.RequestCallback());
this.$router.navigate(
this.navigationScenarios.CLICKED_NEED_HELP,
@ -396,8 +365,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) {
@ -407,16 +380,12 @@ 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);
},
getCustomValueFromString(str) {
switch (str) {
case 'radiusInMiles':
return this.radiusInMiles;
default:
return null;
}
},
getShopButtonDataFromProvider(provider) {
const cellNumber = toDisplayPhoneNumber(provider?.phoneNumber);
const distance =
@ -426,7 +395,7 @@ export default {
: null;
return {
buttonLabel: provider?.companyName ?? '',
buttonLabel: provider?.companyName.toLowerCase() ?? '',
buttonLabelSubCopy: distance === null ? '' : `${distance} mi`,
buttonBodyCopy: `${this.getFullProviderAddress(provider)}<br>${
cellNumber ?? ''
@ -442,4 +411,30 @@ export default {
.darker-gray {
color: map-get($colors, 'darker-gray');
}
#tpaSearchQuestionLabel {
margin-bottom: .625rem;
}
#map {
height: 27.1875rem;
}
#providerSelectionForm {
#alertNoNetworkProviders {
:deep(.alert) {
margin-bottom: 0px;
}
}
}
hr {
margin: 1.375rem 0;
}
.need-help-link-container {
margin-top:1.875rem;
margin-bottom: 2.1875rem;
}
</style>

View file

@ -131,12 +131,10 @@ export default {
components: {
siteHeader,
textBlock,
buttonMain,
reviewBlock,
deductibleBox,
siteFooter,
contactDetailsDrawer,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert,
modal
@ -300,7 +298,6 @@ export default {
this.getEditShopLinkText,
() => this.navigate(this.navigationScenarios.EDIT_PREFERRED_SHOP)
),
// eslint-disable-next-line max-len
this.getSection(
this.getContactInfoTitle,
this.getContactInfoLines,

View file

@ -87,7 +87,6 @@ export default {
watch: {
isAvailable(val) {
// CHECK TO UPDATE SELECTED VALUES WHEN ISAVAILABLE IS TRUE
// eslint-disable-next-line no-unused-expressions
val && this.updateSelectedValues();
},
shouldDisplayReplaceOptionsQuestion(shouldDisplayReplaceOptionsQuestion) {

View file

@ -1,4 +1,3 @@
/* eslint-env jest */
import { mount, flushPromises } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
@ -6,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: {}
@ -13,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: [
@ -92,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: {
@ -116,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');

View file

@ -132,7 +132,6 @@ export default {
damageLocationQuestion,
windshieldOptions,
replaceOptionsQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert
},
@ -217,6 +216,13 @@ export default {
=== damageLocationsSelected.REPAIR
);
},
isWindshieldReplace() {
return (
this.isWindshieldDamageLocation
&& this.selectedWindshieldOptions.selectedWindshieldDamageType
=== damageLocationsSelected.REPLACE
);
},
isDriverSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
@ -419,8 +425,8 @@ export default {
this.navigationScenarios.CLICKED_FORWARD_WITH_REPAIR,
this.$route
);
} else if (this.mainStore.order.vehicle.vin) {
// If vin already exists, navigate directly to vin-lookup
} else if (this.mainStore.order.vehicle.vin || !this.isWindshieldReplace) {
// If vin already exists or not replacing windshield, get parts/questions and navigate forward
const partsOrQuestionsResponse = await this.getPartsOrQuestions();
if (partsOrQuestionsResponse.error) {

View file

@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
// eslint-disable-next-line max-len
import windshieldChipCountQuestion from '@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.vue';
import { getMountOptions } from '@/helpers/unit-test-helper.js';

View file

@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
// eslint-disable-next-line max-len
import windshieldDamageTypeQuestion from '@/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue';
import { getMountOptions } from '@/helpers/unit-test-helper.js';

View file

@ -1,4 +1,3 @@
/* eslint-env jest */
import { mount } from '@vue/test-utils';
import baseMixin from '@/mixins/base-mixin';
import navigationScenarios from '@/router/router-constants/navigation-scenarios';

View file

@ -51,7 +51,6 @@ import TextBlock from '@/digital-components/text-block/text-block.vue';
export default {
name: 'vehicle-lookup',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
SiteFooter,
siteHeader,

View file

@ -41,13 +41,14 @@ function setupMocks({ glassNameProp, glassLocationProp, colorAnswersProp, modelV
};
const wrapper = shallowMount(glassPartQuestion, mountOptions);
// Mock store
const partsOrQuestions = pageData ?? { partsOrQuestions: [{ glassName: 'Stationary', glassLocation: 'Rear', parts: [] }] };
useMainStore().pageData = jest.fn();
useMainStore().pageData.mockReturnValue(partsOrQuestions);
document.querySelector = jest.fn().mockReturnValue({ clicked: false, click: jest.fn() });
const wrapper = shallowMount(glassPartQuestion, mountOptions);
return { wrapper };
}
@ -62,36 +63,23 @@ describe('glass-part-question.vue', () => {
window.console.log(wrapper.vm.featureListData['Green Tint'][0].Text);
// Assert
expect(Object.keys(wrapper.vm.featureListData).length).toBe(2);
expect(wrapper.vm.featureListData['Green Tint'][0].Text).toBe('heated glass, solar, 1 hole');
expect(wrapper.vm.featureListData['Green Tint'][0].Text).toBe('Heated glass, Solar, 1 Hole');
expect(wrapper.vm.featureListData['Green Tint'][0].Name).toBe('DB12209GTYN');
expect(wrapper.vm.featureListData['Gray Tint Privacy'][0].Text).toBe('heated glass, solar, 1 hole');
expect(wrapper.vm.featureListData['Gray Tint Privacy'][0].Text).toBe('Heated glass, Solar, 1 Hole');
expect(wrapper.vm.featureListData['Gray Tint Privacy'][0].Name).toBe('DB12209YPYN');
});
test('Tint mapper, should get tint image by glassLocation and tintColor', async () => {
test('Tint mapper, should get return css class based on tint color', async () => {
// Arrange
const { wrapper } = setupMocks(featureListData);
// Act
await wrapper.vm.$nextTick();
const tintSourceImage = wrapper.vm.getTintSourceImage('Rear', 'Green Tint');
const tintClass = wrapper.vm.convertTintToCSSClass('Green Tint');
// Assert
expect(tintSourceImage).toBe('Glass-NoShade-GreenTint.svg');
});
test('Tint mapper, should return empty string if no tint map found', async () => {
// Arrange
const { wrapper } = setupMocks(featureListData);
// Act
await wrapper.vm.$nextTick();
const tintSourceImage = wrapper.vm.getTintSourceImage('Rear', 'Crazy Rainbow Tint');
// Assert
expect(tintSourceImage).toBe('');
expect(tintClass).toBe('green-tint');
});
test('Should emit updateModelValue, and have correct attributes', async () => {
@ -122,7 +110,7 @@ describe('glass-part-question.vue', () => {
{ partNumber: 'DB12209GTYN', color: 'Green Tint' }
]);
expect(listCard.attributes('groupname')).toBe('Rear-Stationary');
expect(listCard.attributes('validationrules')).toBe('Rear-Stationary-tint-required');
expect(listCard.attributes('validationrules')).toBe('Rear-Stationary-part-required');
});
test('default is selected if only one option', async () => {
@ -136,58 +124,42 @@ describe('glass-part-question.vue', () => {
}
]
};
const updateSpy = jest.spyOn(glassPartQuestion.methods, 'updateSelectedPartNumber');
const { wrapper } = setupMocks(featureListData);
// Act
await wrapper.vm.$nextTick();
await wrapper.setData({ selectedTint: 'Green Tint' });
// take emitted value, pass down as modelValue
// yes, yes, it's not ideal
await wrapper.setProps({ modelValue: wrapper.emitted()['update:modelValue'][0][0] });
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.selectedPartNumber).toBe('DB12209GTYN');
expect(updateSpy).toHaveBeenCalledWith('DB12209GTYN');
});
const partsForSelectedTintTestCases = [
const partsForLocationTestCases = [
[
'Rear',
'Stationary',
'Green Tint',
[
{ partNumber: 'Glass1', color: 'Green Tint' },
{ partNumber: 'Glass2', color: 'Blue Tint' },
{ partNumber: 'Glass3', color: 'Green Tint' },
{ partNumber: 'Glass4', color: 'Green Tint' },
{ partNumber: 'Glass6', color: 'Green Tint' }
{ partNumber: 'Glass5', color: 'Blue Tint' },
{ partNumber: 'Glass6', color: 'Green Tint' },
{ partNumber: 'Glass7', color: 'Red Tint' }
]
],
[
'Rear',
'Stationary',
'Blue Tint',
[
{ partNumber: 'Glass2', color: 'Blue Tint' },
{ partNumber: 'Glass5', color: 'Blue Tint' }
]
],
['Rear', 'Stationary', 'Red Tint', [{ partNumber: 'Glass7', color: 'Red Tint' }]],
[
'Windshield',
'Single',
'Green Tint',
[
{ partNumber: 'Windshield1', color: 'Green Tint' },
{ partNumber: 'Windshield2', color: 'Green Tint' }
]
],
['Windshield', 'Single', 'Blue Tint', []],
['Driver', 'Quarter', 'Green Tint', []]
['Driver', 'Quarter', []]
];
test.each(partsForSelectedTintTestCases)(
'partsForSelectedTint returns correct parts',
async (glassLocation, glassName, selectedTint, expectedResults) => {
test.each(partsForLocationTestCases)(
'partsForLocation returns correct parts',
async (glassLocation, glassName, expectedResults) => {
// Arrange
const pageData = { partsOrQuestions: [
{
@ -219,11 +191,8 @@ describe('glass-part-question.vue', () => {
pageData
});
// Act
await wrapper.setData({ selectedTint });
// Assert
expect(expectedResults).toEqual(wrapper.vm.partsForSelectedTint);
expect(wrapper.vm.partsForLocation).toEqual(expectedResults);
}
);
});

View file

@ -1,44 +1,27 @@
<template>
<div class="row">
<p class="mb-0 color-question-text">
{{ colorQuestionText }}
</p>
<span class="glass-location-text">
{{ partType }}
</span>
</div>
<div class="nested-radio">
<div class="row my-2">
<div class="col">
<buttonQuestion
v-model="selectedTint"
:answers="tintSelectionOptions"
buttonTypeString="listCard"
:isWide="true"
altText=""
isRequired
:groupName="replaceAllSpaceWithDash(`${glassLocation}-${glassName}`)"
:validationRules="tintValidationRules">
<div
class="row my-2"
aria-live="polite">
<div class="col">
<buttonQuestion
id="glass-part-question"
v-model="selectedPartNumber"
buttonTypeString="radio"
class="radioQuestion"
:questionText="glassFeatureQuestion"
:answers="featureListData[selectedTint]"
textPosition="text-start"
:loaderEnabled="false"
isRequired
isSmallQuestionText
:groupName="replaceAllSpaceWithDash(`${glassLocation}-${glassName}-${selectedTint}`)"
:validationRules="partValidationRules" />
</div>
</div>
</buttonQuestion>
</div>
<template
v-for="(tint, index) in tintSelectionOptions"
:key="index">
<div class="tint-option">
<span>{{ `${partType} - ${tint}` }}</span>
<span :class="`tint-image ${convertTintToCSSClass(tint)}`"></span>
</div>
</div>
<buttonQuestion
id="glass-part-question"
v-model="selectedPartNumber"
buttonTypeString="list-button"
class="radioQuestion"
:answers="featureListData[tint]"
:loaderEnabled="false"
isRequired
:groupName="replaceAllSpaceWithDash(`${glassLocation}-${glassName}`)"
:validationRules="partValidationRules" />
</template>
</template>
<script>
@ -46,12 +29,10 @@
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
// Supporting files
import getTintImage from '@/constants/tint-mapper';
import getCustomTransformValue from '@/constants/dynamictext-mapper';
import { defineRule } from 'vee-validate';
import { required } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages';
import { useMainStore } from '@/store';
import { toTitleCase } from '@/helpers/text-helper';
export default {
name: 'glass-part-question',
@ -72,42 +53,21 @@ export default {
emits: ['update:modelValue'],
data() {
return {
glassColorQuestion: '',
glassFeatureQuestion: '',
selectedTint: ''
};
},
computed: {
tintValidationRules() {
const validationRuleName = `${this.glassLocation}-${this.glassName}-tint-required`;
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
return validationRuleName;
},
partValidationRules() {
const validationRuleName = `${this.glassLocation}-${this.glassName}-part-required`;
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
return validationRuleName;
},
colorQuestionText() {
return getCustomTransformValue(
this.glassColorQuestion,
`${this.glassLocation} ${this.glassName}`
);
},
tintSelectionOptions() {
const tintOptions = [];
Object.keys(this.featureListData).forEach((tintOption) => {
const buttonImage = this.getTintSourceImage(this.glassLocation, tintOption);
tintOptions.push({
value: tintOption,
buttonLabel: tintOption,
buttonImage: buttonImage ? require(`@/assets/img/tints/${buttonImage}`) : null
});
tintOptions.push(tintOption);
});
return tintOptions;
@ -115,21 +75,21 @@ export default {
selectedPartNumber: {
get() {
return this.modelValue?.partNumber;
return this.modelValue?.partNumber ?? '';
},
set(newValue) {
const part = this.partsForSelectedTint
.filter((partItem) => partItem.partNumber === newValue?.value)[0];
const part = this.partsForLocation
.filter((partItem) => partItem.partNumber === newValue)[0];
this.$emit('update:modelValue', part);
}
},
partsForSelectedTint() {
partsForLocation() {
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter((dataForGlassLocationAndName) =>
dataForGlassLocationAndName.glassName === this.glassName
&& dataForGlassLocationAndName.glassLocation === this.glassLocation);
const matchingGlassParts = matchingGlass?.length === 1 ? matchingGlass[0].parts : [];
return matchingGlassParts.filter((part) => part.color === this.selectedTint) ?? [];
return matchingGlassParts;
},
// Creates a map of the feature list data in the correct Name/Value
@ -142,7 +102,7 @@ export default {
// Map the data to Name/Text object for ButtonQuestion
const mappedItem = item.FeatureAnswers.reduce((featureArr, it) => {
featureArr.Text = it.FeatureAnswerText; // Display to User
featureArr.Text = this.toTitleCaseWithExceptions(it.FeatureAnswerText); // Display to User
featureArr.Name = it.PartNumber; // Backing Value
return featureArr;
@ -158,72 +118,19 @@ export default {
PartDataFromApi() {
return this.mainStore.pageData(this.$route.query.issPage) ?? {};
}
},
watch: {
selectedTint() {
this.AutoSelectIfSinglePart();
},
partType() {
const type = this.partsForLocation?.[0]?.partType ?? '';
return toTitleCase(type);
}
},
mounted() {
this.LoadPreselectedValues();
this.AutoSelectIfSinglePart();
},
methods: {
// Initialize the component data
initializeComponent(cmsContent) {
this.glassColorQuestion = cmsContent.ColorQuestionWidget.QuestionText;
this.glassFeatureQuestion = cmsContent.FeatureQuestionWidget.QuestionText;
},
// Gets tint images based on the glass type, and tint name.
// Returns an empty string if the src or object is undefined.
getTintSourceImage(glassLocation, tintColor) {
const tintSourceObject = getTintImage(glassLocation, tintColor);
if (tintSourceObject === undefined || !tintSourceObject.src) {
return '';
}
return tintSourceObject.src;
},
AutoSelectIfSinglePart() {
if (this.partsForSelectedTint?.length > 0) {
// Check if only a single part is present for the tint and set the v-model if it is.
if (this.partsForSelectedTint?.length === 1) {
// select element with matching partNumber
this.updateSelectedPartNumber(this.partsForSelectedTint[0].partNumber);
} else {
this.selectedPartNumber = null;
}
}
},
// Loads the preselected values from the store.
LoadPreselectedValues() {
this.$nextTick(() => {
// Populate button-question model-value if parts data already exists in store
if (this.modelValue !== undefined) {
this.selectedTint = this.modelValue.color;
const { glassParts } = useMainStore().order.lineItems;
if (glassParts) {
for (const tintPart of this.partsForSelectedTint) {
for (const glassPart of glassParts) {
if (tintPart.partNumber === glassPart.partNumber) {
this.$nextTick(() => {
this.updateSelectedPartNumber(tintPart.partNumber);
});
return;
}
}
}
}
}
});
},
updateSelectedPartNumber(partNumber) {
this.selectedPartNumber = { value: partNumber };
this.selectedPartNumber = partNumber;
this.$nextTick(() => {
const radioInput = document.querySelector(`input[value=${this.selectedPartNumber}]`);
// Fire a click event on the input so the field is updated
@ -231,37 +138,128 @@ export default {
});
},
AutoSelectIfSinglePart() {
if (this.partsForLocation?.length > 0) {
// Check if only a single part is present for the tint and set the v-model if it is.
if (this.partsForLocation?.length === 1) {
// select element with matching partNumber
this.updateSelectedPartNumber(this.partsForLocation[0].partNumber);
}
}
},
replaceAllSpaceWithDash(str) {
return String(str).replaceAll(' ', '-');
},
convertTintToCSSClass(tint) {
return tint.toLowerCase().replaceAll(' ', '-').replaceAll(',', '');
},
toTitleCaseWithExceptions(text) {
const exceptions = ['side', 'glass', 'dimming'];
let temp = toTitleCase(text);
exceptions.forEach((exception) => {
const regEx = new RegExp(exception, 'ig');
temp = temp.replace(regEx, exception);
});
return temp;
}
}
};
</script>
<style lang="scss" scoped>
.nested-radio {
:deep(.ui-radio) {
flex-direction: column;
margin: 0.25rem 0;
}
p {
font-size: 0.875rem;
}
}
.color-question-text {
color: $black;
.glass-location-text {
font-weight: $font-weight-bold;
color: $black;
}
.tint-option {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 2rem;
margin-top: .5rem;
margin-bottom: .25rem;
font-weight: 600;
color: $black;
#glass-part-question {
span.fw-bold.w-100 {
margin-top: 0.5rem;
margin-bottom: 0;
@include media-breakpoint-up(md) {
justify-content: flex-start;
}
div.col.radio-button-container {
padding-bottom: 0;
}
.radioQuestion {
:deep(.list-button) {
margin-bottom: 1rem;
}
}
.tint-image {
width: 2rem;
height: 2rem;
margin-left: 1rem;
margin-top: -.25rem;
background-repeat: no-repeat;
background-image: url(~@/assets/img/tints/GlassTint.png);
}
.blue-tint-green-shade {
background-position: -53px -53px;
}
.blue-tint-blue-shade {
background-position: -160px -53px;
}
.gray-tint-gray-shade {
background-position: -267px -53px;
}
.blue-tint {
background-position: -373px -53px;
}
.gray-tint {
background-position: -480px -53px;
}
.green-tint-green-shade {
background-position: -53px -160px;
}
.green-tint-blue-shade {
background-position: -160px -160px;
}
.green-tint-gray-shade {
background-position: -267px -160px;
}
.green-tint {
background-position: -373px -160px;
}
.clear {
background-position: -480px -160px;
}
.bronze-tint-green-shade {
background-position: -53px -266px;
}
.bronze-tint-blue-shade {
background-position: -160px -266px;
}
.bronze-tint-gray-shade {
background-position: -267px -266px;
}
.bronze-tint-bronze-shade {
background-position: -373px -266px;
}
.bronze-tint {
background-position: -480px -266px;
}
.bronze-tint-privacy {
background-position: -587px -266px;
}
.clear-blue-shade {
background-position: -53px -373px;
}
.gray-tint-blue-shade {
background-position: -160px -373px;
}
.blue-tint-gray-shade {
background-position: -267px -373px;
}
.gray-tint-privacy {
background-position: -373px -373px;
}
.tinted {
background-position: -480px -373px;
}
</style>

View file

@ -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: {

Some files were not shown because too many files have changed in this diff Show more