Merge branch 'develop' into feature/jnou/fix-iss-regression

This commit is contained in:
JennyNou 2026-02-13 15:23:35 -05:00
commit 1385f9ed94
17 changed files with 288 additions and 148 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -68,7 +68,9 @@ const errorMessages = Object.freeze({
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_ZIP_FORMAT: 'Please enter a valid Zip code.',
TPA_SEARCH_SHOP_FORMAT: 'Please enter a valid shop name.', TPA_SEARCH_SHOP_FORMAT: 'Please enter a valid shop name.',
TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.' TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.',
SERVICE_ADDRESS_REQUIRED: 'Service street address is required.',
SERVICE_CITY_REQUIRED: 'Service city is required.'
}); });
export default errorMessages; export default errorMessages;

View file

@ -99,9 +99,9 @@
</div> </div>
<template <template
v-for="timeSlot in selectableTimeSlotsData" v-for="timeSlot in selectableTimeSlotsData"
:key="timeSlot.startTime"> :key="`${timeSlot.startTime}-${timeSlot.isDropoff}`">
<input <input
:id="`timeslot${timeSlot.startTime}`" :id="`timeslot${timeSlot.startTime}-${timeSlot.isDropoff}`"
v-model="selectedTime" v-model="selectedTime"
type="radio" type="radio"
name="time-slot" name="time-slot"
@ -114,9 +114,14 @@
class="time-slot-button" class="time-slot-button"
:class="[checkIsSelectedTimeSlot(timeSlot) ? 'selected-time-slot' : '' :class="[checkIsSelectedTimeSlot(timeSlot) ? 'selected-time-slot' : ''
, {'has-date-error': showTimeSlotError}]" , {'has-date-error': showTimeSlotError}]"
@click="selectTimeSlotForDay(timeSlot)"> @click="selectTimeSlotForDay(timeSlot)"
{{ displayTimeSlotTime(timeSlot) }} v-html="displayTimeSlotTime(timeSlot)">
</label> </label>
<div
v-if="timeSlot.isDropoff"
class="time-slot-drop-off-information"
v-html="displayDropOffInformation(timeSlot)">
</div>
</template> </template>
</div> </div>
<div class="row form-test-error"> <div class="row form-test-error">
@ -256,6 +261,10 @@ export default {
appointmentEstimate() { appointmentEstimate() {
return getDisplayTextForDurationLength(this.appointmentDurationMinutesMinimum, this.appointmentDurationMinutesMaximum); return getDisplayTextForDurationLength(this.appointmentDurationMinutesMinimum, this.appointmentDurationMinutesMaximum);
}, },
calendarViewDirection() {
if (this.selectableDatesSetting === 'custom') return 'future';
return 'past';
},
daysToAdd() { daysToAdd() {
return this.isMobileView ? this.daysInViewMobile : this.daysInViewStandard; return this.isMobileView ? this.daysInViewMobile : this.daysInViewStandard;
}, },
@ -284,10 +293,6 @@ export default {
this.todayOverrideDateString this.todayOverrideDateString
|| convertDateToDateString(new Date()) || convertDateToDateString(new Date())
); );
},
calendarViewDirection() {
if (this.selectableDatesSetting === 'custom') return 'future';
return 'past';
} }
}, },
watch: { watch: {
@ -301,16 +306,16 @@ export default {
this.findFirstAvailableDateInView(); this.findFirstAvailableDateInView();
} }
}, },
selectedDate(newValue, oldValue) {
if (newValue !== oldValue) {
this.$emit('dateSelected', newValue);
}
},
selectedTimeSlot(newValue, oldValue) { selectedTimeSlot(newValue, oldValue) {
if (newValue !== oldValue) { if (newValue !== oldValue) {
const testObj = this.getSelectedTimeSlotInfoObject(newValue); const testObj = this.getSelectedTimeSlotInfoObject(newValue);
this.$emit('timeSlotSelected', testObj); this.$emit('timeSlotSelected', testObj);
} }
},
selectedDate(newValue, oldValue) {
if (newValue !== oldValue) {
this.$emit('dateSelected', newValue);
}
} }
}, },
methods: { methods: {
@ -340,50 +345,44 @@ export default {
addPremiumFlagToInput(routeCode) { addPremiumFlagToInput(routeCode) {
return (`${routeCode}${PREMIUM_TIME_SLOT_ID_FLAG}`); return (`${routeCode}${PREMIUM_TIME_SLOT_ID_FLAG}`);
}, },
removePremiumFlagFromInput(routeCode) { checkIsSelectedDay(index, timeOfDayGrouping) {
return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, ''); const dateToShowString = this.getDateToShowString(index);
return this.selectedDate === dateToShowString && this.selectedTimeOfDayGrouping === timeOfDayGrouping;
}, },
getSelectedTimeSlotInfoObject(timeSlot) { checkIsSelectedTimeSlot(timeSlot) {
const routeCode = timeSlot?.id; if (!this.selectedTimeSlot) {
let routeCodeToUse = routeCode; return false;
const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG);
if (routeCodeIncludesPremium) {
routeCodeToUse = this.removePremiumFlagFromInput(routeCode);
} }
return (this.selectedTimeSlot.isDropoff === timeSlot.isDropoff
const fullTimeSlot = this.selectableTimeSlotsData?.find((ts) => ts.id === routeCodeToUse); && this.selectedTimeSlot.startTime === timeSlot.startTime
&& this.selectedTimeSlot.timeOfDay === timeSlot.timeOfDay);
if (fullTimeSlot) { },
return { displayDropOffInformation(timeSlot) {
timeSlot: { if (timeSlot.isDropoff) {
date: this.selectedDate, if (timeSlot.timeOfDay === 'morning') {
routeCode: fullTimeSlot.id, return this.getCmsContent(
startTime: fullTimeSlot.startTime, 'DropOffTimeSlotModal',
endTime: fullTimeSlot.endTime, 'BodyText'
jobMaxMinutes: this.appointmentDurationMinutesMaximum.toString(), );
jobMinMinutes: this.appointmentDurationMinutesMinimum.toString() }
}, return this.getCmsContent(
isPremiumAppointment: !!routeCodeIncludesPremium 'OvernightDropOffTimeSlotModal',
}; 'BodyText'
);
} }
return '';
return {
timeSlot: {
date: null,
startTime: null,
endTime: null,
routeCode: null,
jobMaxMinutes: null,
jobMinMinutes: null
},
isPremiumAppointment: null
};
}, },
displayTimeSlotTime(timeSlot) { displayTimeSlotTime(timeSlot) {
const appointmentType = this.activeAppointmentType || AppointmentTypeStrings.IN_SHOP; const appointmentType = this.activeAppointmentType || AppointmentTypeStrings.IN_SHOP;
if (appointmentType === AppointmentTypeStrings.MOBILE || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) { if (appointmentType === AppointmentTypeStrings.MOBILE || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
return `${militaryToTwelveHourTime(timeSlot.startTime)} - ${militaryToTwelveHourTime(timeSlot.endTime)}`; 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); return militaryToTwelveHourTime(timeSlot.startTime);
}, },
findFirstAvailableDateInView() { findFirstAvailableDateInView() {
@ -436,8 +435,7 @@ export default {
if (!initialTimeSlot || !initialTimeSlot.startTime) { if (!initialTimeSlot || !initialTimeSlot.startTime) {
return; return;
} }
const initialStartTime = initialTimeSlot.startTime; const foundTimeSlot = timeSlots.find((ts) => ts.startTime === initialTimeSlot.startTime && ts.endTime === initialTimeSlot.endTime);
const foundTimeSlot = timeSlots.find((ts) => ts.startTime === initialStartTime);
if (foundTimeSlot) { if (foundTimeSlot) {
this.selectTimeSlotForDay(foundTimeSlot); this.selectTimeSlotForDay(foundTimeSlot);
} }
@ -452,6 +450,44 @@ export default {
const dateToShow = this.getDateToShow(index); const dateToShow = this.getDateToShow(index);
return convertDateToDateString(dateToShow); 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() { async gotoNextPage() {
const maxPageIndex = Math.floor((this.daysLoaded - 1) / this.daysToAdd); const maxPageIndex = Math.floor((this.daysLoaded - 1) / this.daysToAdd);
if (this.activePageIndex >= maxPageIndex) { if (this.activePageIndex >= maxPageIndex) {
@ -479,15 +515,15 @@ export default {
this.selectedTimeSlot = null; this.selectedTimeSlot = null;
this.findFirstAvailableDateInView(); this.findFirstAvailableDateInView();
}, },
checkIsSelectedDay(index, timeOfDayGrouping) { mapTimeSlot(timeSlot, timeOfDay) {
const dateToShowString = this.getDateToShowString(index); return {
return this.selectedDate === dateToShowString && this.selectedTimeOfDayGrouping === timeOfDayGrouping; ...timeSlot,
isDropoff: timeSlot.id.endsWith('DROP OFF'),
timeOfDay
};
}, },
checkIsSelectedTimeSlot(timeSlot) { removePremiumFlagFromInput(routeCode) {
if (!this.selectedTimeSlot) { return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, '');
return false;
}
return this.selectedTimeSlot.startTime === timeSlot.startTime;
}, },
selectTimeSlotForDay(timeSlot) { selectTimeSlotForDay(timeSlot) {
if (timeSlot) { if (timeSlot) {
@ -567,13 +603,28 @@ export default {
morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour < 12; return timeHour < 12;
}), }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'morning')),
afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour >= 12; return timeHour >= 12;
}), }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'afternoon')),
isSelected: false 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); this.selectableDatesData.push(dateObjectToPush);
}); });
@ -635,14 +686,28 @@ export default {
morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour < 12; return timeHour < 12;
}), }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'morning')),
afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => { afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10); const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour >= 12; return timeHour >= 12;
}), }).map((timeSlot) => this.mapTimeSlot(timeSlot, 'afternoon')),
isSelected: false 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); this.selectableDatesData.push(dateObjectToPush);
} }
}); });
@ -832,16 +897,55 @@ export default {
text-align: center; text-align: center;
cursor: pointer; 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 { &.selected-time-slot {
background-color: $background-color-selected; background-color: $background-color-selected;
border: solid 1px #0070d1; border: solid 1px #0070d1;
color:#000; color:#000;
font-weight: 500; 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 { &.has-date-error {
border: 1px solid #d93025; border: 1px solid #d93025;
} }
} }
} }

View file

@ -156,8 +156,8 @@ export default {
&:disabled, &:disabled,
&.disabled { &.disabled {
background-color: $gray-100; background-color: $gray-100;
color: $gray-500; background-image: url(~@/assets/img/icons/select-disabled.png);
filter: grayscale(100%); cursor: not-allowed;
} }
} }
&.dropdown-compact { &.dropdown-compact {

View file

@ -402,6 +402,7 @@ input::-webkit-date-and-time-value {
&:disabled, &:disabled,
&.disabled { &.disabled {
background-color: $gray-100; background-color: $gray-100;
cursor: not-allowed;
} }
} }
p { p {

View file

@ -133,3 +133,20 @@ export function formatDate(date, formatter) {
} }
return ''; 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

@ -49,7 +49,7 @@ export default {
vehicles: Array, vehicles: Array,
vehicleSelected: Object, vehicleSelected: Object,
modelValue: String, modelValue: String,
cmsWidgetName: String, questionText: String,
validationRules: String, validationRules: String,
isCarIdDifferent: Boolean, isCarIdDifferent: Boolean,
displayMatchedDifferentVehicleAlert: Boolean, displayMatchedDifferentVehicleAlert: Boolean,
@ -89,9 +89,6 @@ export default {
.replaceAll('{custom:vinYmmsFound}', vinYmmsFound) .replaceAll('{custom:vinYmmsFound}', vinYmmsFound)
.replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected); .replaceAll('{custom:vinYmmsExpected}', vinYmmsExpected);
}, },
questionText() {
return this.getCmsContent('VehicleConfirmationQuestion', 'QuestionText');
},
selectedVehicleVin: { selectedVehicleVin: {
get() { get() {
return this.modelValue; return this.modelValue;
@ -123,4 +120,8 @@ export default {
#address-vehicles-question-alert p { #address-vehicles-question-alert p {
margin-bottom: 0 !important; // Overrides extra margin-bottom on alert body text margin-bottom: 0 !important; // Overrides extra margin-bottom on alert body text
} }
:deep(div.list-button-content > span:first-child) {
text-transform: uppercase;
}
</style> </style>

View file

@ -100,6 +100,9 @@ function setupMocks({
if (contentName === 'ProvideVinAlert') { if (contentName === 'ProvideVinAlert') {
return 'ProvideVinAlertTestReturn'; return 'ProvideVinAlertTestReturn';
} }
if (contentName === 'VehicleConfirmationQuestion') {
return 'VehicleConfirmationQuestionTestReturn';
}
return null; return null;
}) })
}, },

View file

@ -13,15 +13,6 @@
<siteSubHeader <siteSubHeader
cmsWidgetName="SiteSubHeaderWidget" cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" /> class="mt-4" />
<alert
id="multiple-vehicles-alert"
ref="alertFoundMultipleVehicles"
cmsWidgetName="FoundMultipleVehicles"
class="my-5"
alertClass="alert-warning"
:manualHeadline="AlertFoundMultipleVehiclesHeader"
manualCopy=""
:isDismissible="false" />
<alert <alert
v-if="displayNoServiceAlert" v-if="displayNoServiceAlert"
ref="AlertNoService" ref="AlertNoService"
@ -32,7 +23,7 @@
<addressVehiclesQuestion <addressVehiclesQuestion
ref="addressVehiclesQuestion" ref="addressVehiclesQuestion"
v-model="selectedVehicleVin" v-model="selectedVehicleVin"
cmsWidgetName="VehicleConfirmationQuestion" :questionText="questionText"
:vehicles="VehiclesForQuestions" :vehicles="VehiclesForQuestions"
:vehicleSelected="VehicleSelected" :vehicleSelected="VehicleSelected"
validationRules="vehicle-required" validationRules="vehicle-required"
@ -164,10 +155,10 @@ export default {
vehicleCount() { vehicleCount() {
return this.VehiclesForQuestions.length; return this.VehiclesForQuestions.length;
}, },
AlertFoundMultipleVehiclesHeader() { questionText() {
return this.getCmsContent( return this.getCmsContent(
'FoundMultipleVehicles', 'VehicleConfirmationQuestion',
'HeadlineText' 'QuestionText'
).replaceAll('{custom:vehicleCount}', this.vehicleCount); ).replaceAll('{custom:vehicleCount}', this.vehicleCount);
}, },
isTwoIdenticalYMMVehicleFound() { isTwoIdenticalYMMVehicleFound() {
@ -186,7 +177,7 @@ export default {
// Map API result data, to address-vehicles data structure // Map API result data, to address-vehicles data structure
const mappedData = this.VehiclesFromApi.map((v) => { const mappedData = this.VehiclesFromApi.map((v) => {
const maskSymbol = '*'; const maskSymbol = '*';
const vinStart = maskSymbol.repeat(v.vin.length - 6); const vinStart = maskSymbol.repeat(6);
const vinEnd = v.vin.substring(v.vin.length - 6); const vinEnd = v.vin.substring(v.vin.length - 6);
return { return {
vin: v.vin, vin: v.vin,
@ -234,13 +225,6 @@ export default {
} else { } else {
this.displayMatchedDifferentVehicleAlert = true; 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 deep: true

View file

@ -59,7 +59,7 @@
inputId="state" inputId="state"
class="form-group" class="form-group"
:cmsWidgetName="widget.stateQuestion" :cmsWidgetName="widget.stateQuestion"
:options="states" :options="getStates"
isDisabled /> isDisabled />
<textboxQuestion <textboxQuestion
ref="zipCodeQuestion" ref="zipCodeQuestion"
@ -135,8 +135,8 @@ import widgetFields from '@/constants/cms-widget-fields';
import states from '@/constants/states'; import states from '@/constants/states';
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule('street-address-required', required(errorMessages.STREET_ADDRESS_REQUIRED)); defineRule('street-address-required', required(errorMessages.SERVICE_ADDRESS_REQUIRED));
defineRule('city-required', required(errorMessages.CITY_REQUIRED)); defineRule('city-required', required(errorMessages.SERVICE_CITY_REQUIRED));
export default { export default {
name: 'contact-details', name: 'contact-details',
@ -218,7 +218,13 @@ export default {
}, },
vehicleProtectedQuestionAnswers() { vehicleProtectedQuestionAnswers() {
return this.getCmsContent(this.widget.vehicleProtectedQuestion, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS) || []; 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: { watch: {
isSameAsPolicyAddress(newValue) { isSameAsPolicyAddress(newValue) {

View file

@ -144,6 +144,7 @@ describe('entry-page.vue', () => {
TPAEnabled: true, TPAEnabled: true,
ClientFullName: 'Full Name', ClientFullName: 'Full Name',
ClientDisplayName: 'Display Name', ClientDisplayName: 'Display Name',
ClientPossessiveName: 'Display Name\'s',
ClaimRegistrationRequired: true, ClaimRegistrationRequired: true,
EnableNoCompQuote: true EnableNoCompQuote: true
}) })
@ -153,6 +154,7 @@ describe('entry-page.vue', () => {
expect(issConfig.clientName).toBe('TestClient'); expect(issConfig.clientName).toBe('TestClient');
expect(issConfig.clientFullName).toBe('Full Name'); expect(issConfig.clientFullName).toBe('Full Name');
expect(issConfig.clientDisplayName).toBe('Display Name'); expect(issConfig.clientDisplayName).toBe('Display Name');
expect(issConfig.clientPossessiveName).toBe('Display Name\'s');
expect(issConfig.parentAccountNumber).toBe('12345'); expect(issConfig.parentAccountNumber).toBe('12345');
expect(issConfig.styleSheet).toBe('test-style'); expect(issConfig.styleSheet).toBe('test-style');
expect(issConfig.isCoverageEnabled).toBe(true); expect(issConfig.isCoverageEnabled).toBe(true);

View file

@ -14,6 +14,7 @@ import { getISSCookie, updateOrCreateISSCookie } from '@/helpers/cookie-helper.j
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import showIssLoadingModal from '@/helpers/loading-modal-helper'; import showIssLoadingModal from '@/helpers/loading-modal-helper';
import applicationConfig from '@/constants/application-config'; import applicationConfig from '@/constants/application-config';
import { toPossessive } from '@/helpers/text-helper';
export default { export default {
name: 'entry-page', name: 'entry-page',
@ -144,6 +145,7 @@ export default {
this.mainStore.issConfig.clientName = data.accountName; this.mainStore.issConfig.clientName = data.accountName;
this.mainStore.issConfig.clientFullName = data.accountName; // Defaults to use the client name. 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.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.parentAccountNumber = data.parentAccountNumber;
this.mainStore.issConfig.styleSheet = data.styleSheet; this.mainStore.issConfig.styleSheet = data.styleSheet;
this.mainStore.issConfig.isCoverageEnabled = data.coverageEnabled; this.mainStore.issConfig.isCoverageEnabled = data.coverageEnabled;
@ -163,6 +165,11 @@ export default {
if (clientFlags.ClientDisplayName != null) { if (clientFlags.ClientDisplayName != null) {
this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName; 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) { if (clientFlags.ClaimRegistrationRequired) {

View file

@ -6,7 +6,6 @@
<div class="fade-on-route-transition"> <div class="fade-on-route-transition">
<div class="justify-content-center"> <div class="justify-content-center">
<siteHeader <siteHeader
class="header"
cmsWidgetName="SiteHeaderWidget" /> cmsWidgetName="SiteHeaderWidget" />
</div> </div>
<div class="iss-heritage-container-width"> <div class="iss-heritage-container-width">
@ -176,9 +175,7 @@ const getAvailableDates = async (
storeAction.payload.endDate, storeAction.payload.endDate,
storeAction.payload.shopAppointmentType, storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber storeAction.payload.providerNumber
).catch(() => { );
console.warn('Error fetching shop time slots...');
});
} else if (storeAction.payload?.zipCodeOverride) { } else if (storeAction.payload?.zipCodeOverride) {
timeSlotsResponse = await useMainStore().getMobileTimeSlots( timeSlotsResponse = await useMainStore().getMobileTimeSlots(
storeAction.payload.startDate, storeAction.payload.startDate,
@ -343,7 +340,7 @@ export default {
&& (((this.selectedAppointmentType === AppointmentTypeStrings.MOBILE && (((this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) || this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)
&& serviceLocationToUse.mobileProviderNumber) && serviceLocationToUse.mobileProviderNumber)
|| (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP || ((this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP || this.selectedAppointmentType === AppointmentTypeStrings.DROP_OFF)
&& (this.selectedProvider?.providerNumber || serviceLocationToUse.provider?.providerNumber))); && (this.selectedProvider?.providerNumber || serviceLocationToUse.provider?.providerNumber)));
const damageInfo = useMainStore().order.damage.isRepair const damageInfo = useMainStore().order.damage.isRepair
@ -652,8 +649,13 @@ export default {
return; return;
} }
// Save service location then schedule and navigate forward let appointmentTypeToUse = this.selectedAppointmentType;
await this.$refs.serviceLocation.forwardButtonAction(); 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.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
this.$router.navigate( this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD, this.navigationScenarios.CLICKED_FORWARD,

View file

@ -308,7 +308,7 @@ export default {
} }
}, },
selectedAppointmentType(newValue, oldValue) { selectedAppointmentType(newValue, oldValue) {
if (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP && this.selectedProvider && this.availabilityRating === null) { if (this.isInshop && this.selectedProvider && this.availabilityRating === null) {
this.refreshAvailabilityRating(); this.refreshAvailabilityRating();
const appointmenTypeServiceLocationObj = { const appointmenTypeServiceLocationObj = {
@ -336,7 +336,7 @@ export default {
}, },
selectedProvider(newProvider, oldProvider) { selectedProvider(newProvider, oldProvider) {
if (newProvider?.providerNumber !== oldProvider?.providerNumber || this.availabilityRating === null) { if (newProvider?.providerNumber !== oldProvider?.providerNumber || this.availabilityRating === null) {
const appointmentIsInshop = this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP; const appointmentIsInshop = this.isInshop;
const returnedProvider = { const returnedProvider = {
provider: newProvider, provider: newProvider,
refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null
@ -358,7 +358,7 @@ export default {
await this.setData(initialData); await this.setData(initialData);
this.initializingData = false; this.initializingData = false;
}, },
async forwardButtonAction() { async forwardButtonAction(appointmentType) {
let provider = this.selectedProvider; let provider = this.selectedProvider;
this.mainStore.updateMobileFee(null); this.mainStore.updateMobileFee(null);
if (this.isMobile) { if (this.isMobile) {
@ -382,7 +382,7 @@ export default {
state: this.state, state: this.state,
zipCode: this.zipCode, zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu, zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType, appointmentType: appointmentType || this.selectedAppointmentType,
provider provider
}); });
}, },

View file

@ -61,7 +61,6 @@ export default {
.vin-toggle { .vin-toggle {
&::after { &::after {
content: ""; content: "";
transition: all 0.5s ease;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.9' xml:space='preserve'%3e%3cpath d='M8 8.9c-.2 0-.5-.1-.6-.3L.3 1.5C.1 1.4 0 1.1 0 .9 0 .7.1.4.3.3.4.1.7 0 .9 0c.2 0 .5.1.6.3L8 6.7 14.5.2c.1-.1.4-.2.6-.2.2 0 .5.1.6.3s.3.4.3.6c0 .2-.1.5-.3.6L8.6 8.6c-.1.2-.4.3-.6.3z' fill='%231474a2'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.9' xml:space='preserve'%3e%3cpath d='M8 8.9c-.2 0-.5-.1-.6-.3L.3 1.5C.1 1.4 0 1.1 0 .9 0 .7.1.4.3.3.4.1.7 0 .9 0c.2 0 .5.1.6.3L8 6.7 14.5.2c.1-.1.4-.2.6-.2.2 0 .5.1.6.3s.3.4.3.6c0 .2-.1.5-.3.6L8.6 8.6c-.1.2-.4.3-.6.3z' fill='%231474a2'/%3e%3c/svg%3e");
background-repeat: no-repeat; background-repeat: no-repeat;
margin-left: 0.5rem; margin-left: 0.5rem;
@ -72,9 +71,6 @@ export default {
&.active::after { &.active::after {
transform: rotate(180deg); transform: rotate(180deg);
} }
a {
font-size: 0.875rem;
}
} }
} }
#vin-location-detail-wrapper { #vin-location-detail-wrapper {

View file

@ -247,6 +247,7 @@ export const getDefaultState = () => ({
clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name
clientFullName: 'Generic Insurance', // this is the default and will be overriden by the client's name or client's full name. clientFullName: 'Generic Insurance', // this is the default and will be overriden by the client's name or client's full name.
clientDisplayName: 'Generic Insurance', // this is the default and will be overridden by the client's name or client display name. clientDisplayName: 'Generic Insurance', // this is the default and will be overridden by the client's name or client display name.
clientPossessiveName: 'Generic Insurance\'s', // this is the default and will be overridden by the client's name or client display name converted to possessive or the clients possessive name.
clientHeader: {}, clientHeader: {},
styleSheet: '', // Stylesheet used by the client. styleSheet: '', // Stylesheet used by the client.
parentAccountNumber: 0, // Parent account number used by the client. parentAccountNumber: 0, // Parent account number used by the client.
@ -965,7 +966,8 @@ export const useMainStore = defineStore({
providerNumber, providerNumber,
startDate, startDate,
endDate, endDate,
shopAppointmentType, shopAppointmentType: (shopAppointmentType === AppointmentTypeStrings.IN_SHOP
|| shopAppointmentType === AppointmentTypeStrings.DROP_OFF) ? 'InShopOrDropoff' : shopAppointmentType,
applicationName: applicationConfig.APPLICATION_NAME, applicationName: applicationConfig.APPLICATION_NAME,
billToAccountNumber: this.billToAccountNumber, billToAccountNumber: this.billToAccountNumber,
parentAccountNumber: this.order.parentAccountNumber, // this.payment.parentAccountNumber, parentAccountNumber: this.order.parentAccountNumber, // this.payment.parentAccountNumber,
@ -1605,7 +1607,7 @@ export const useMainStore = defineStore({
order.customer.firstName = data?.customer?.firstName; order.customer.firstName = data?.customer?.firstName;
order.customer.lastName = data?.customer?.lastName; order.customer.lastName = data?.customer?.lastName;
order.customer.emailAddress = data?.customer?.emailAddress; order.customer.emailAddress = data?.customer?.emailAddress;
order.contactInfo.extension = data?.customer?.homePhone?.slice(10); order.contactInfo.extension = data?.customer?.homePhone?.slice(10);
order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10); order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10);
order.contactInfo.servicePhone = data?.customer?.servicePhone; order.contactInfo.servicePhone = data?.customer?.servicePhone;

View file

@ -7,7 +7,12 @@
alertClass, alertClass,
cssClassNameForCmsWidget, cssClassNameForCmsWidget,
]"> ]">
<div class="alert-header"> <div
class="alert-header"
:class="{
'collapsible-header': isCollapsible
}"
@click="toggleCollapse">
<div class="alert-icon-container"> <div class="alert-icon-container">
<svg <svg
class="alert-icon" class="alert-icon"
@ -35,8 +40,7 @@
<button <button
v-if="isCollapsible" v-if="isCollapsible"
class="btn-collapse" class="btn-collapse"
type="button" type="button">
@click="toggleCollapse">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="15" width="15"
@ -73,41 +77,44 @@
<div <div
v-show="alertCopy" v-show="alertCopy"
:id="collapseId" :id="collapseId"
class="alert-body show"> class="collapse"
<template :class="{ 'show': !startCollapsed }">
v-for="paragraph in splitAlertCopyForParagraphTag" <div class="alert-body">
:key="paragraph"> <template
<p v-for="paragraph in splitAlertCopyForParagraphTag"
v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)" :key="paragraph">
class="m-0 text-body" <p
v-html="paragraph"></p> v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)"
<p class="m-0 text-body"
v-else v-html="paragraph"></p>
class="m-0 text-body"> <p
<template v-if="doesCopyContainRouterLink(paragraph)">
<textBlock
:customText="paragraph"
class="mb-1"
:marginTopSizeOverride="0" />
</template>
<template
v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)"
v-else v-else
:key="copy"> class="m-0 text-body">
<span v-if="doesCopyContainTextLink(copy)"> <template v-if="doesCopyContainRouterLink(paragraph)">
<textLink <textBlock
linkType="text" :customText="paragraph"
:text="getRouterLinkDisplayTextFromCopy(copy)" class="mb-1"
href="#!" :marginTopSizeOverride="0" />
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)" </template>
@clickEvent="$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))" /> <template
</span> v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)"
<span
v-else v-else
v-html="copy"></span> :key="copy">
</template> <span v-if="doesCopyContainTextLink(copy)">
</p> <textLink
</template> linkType="text"
:text="getRouterLinkDisplayTextFromCopy(copy)"
href="#!"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
@clickEvent="$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))" />
</span>
<span
v-else
v-html="copy"></span>
</template>
</p>
</template>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -208,7 +215,7 @@ export default {
mounted() { mounted() {
this.ensureAlertIsInViewPort(); this.ensureAlertIsInViewPort();
if (this.isCollapsible) { if (this.isCollapsible) {
this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId), { toggle: this.startCollapsed }); this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId), { toggle: false });
} }
}, },
methods: { methods: {
@ -236,6 +243,9 @@ export default {
); );
}, },
toggleCollapse() { toggleCollapse() {
if (!this.isCollapsible) {
return;
}
this.collapsed = !this.collapsed; this.collapsed = !this.collapsed;
if (this.collapsed) { if (this.collapsed) {
this.collapseElement.hide(); this.collapseElement.hide();
@ -268,6 +278,9 @@ export default {
padding: .75rem 1rem .75rem 1rem; padding: .75rem 1rem .75rem 1rem;
font-weight: 600; font-weight: 600;
color: $black; color: $black;
&.collapsible-header {
cursor: pointer;
}
} }
.alert-icon-container { .alert-icon-container {
display: flex; display: flex;