Merge pull request #3044 from Safelite/feature/CASH-1790-refactor-fixes

Feature/cash 1790 refactor fixes
This commit is contained in:
AdamCaouetteSafelite 2026-02-04 09:17:31 -05:00 committed by GitHub
commit 7eef34f6a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 154 additions and 2842 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,18 @@
<template>
<div
class="date-picker text-center"
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`">
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass} ${loadingStatus === 'reinitial' ? 'loading-reinitial' : ''}`">
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
<legend class="sr-only">Select a day and time</legend>
<div
v-for="month in months"
v-for="(month, i) in months"
:key="`${month.monthLabel}-${month.yearNum?.toString()}`"
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
class="calendar-grid-container"
class="calendar-grid-container month"
:class="[
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
month.monthClass,
i === 0 ? 'first-month-shown' : '',
]">
<div class="month-year body-small d-flex align-items-center">
{{ month.monthLabel }} {{ month.yearNum?.toString() }}
@ -114,7 +115,8 @@
</div>
</div>
<loader
:class="[!isLoading ? 'date-picker-hidden' : '']"
class="date-picker-loader"
:class="[loadingStatus !== 'none' ? 'show-loader' : '']"
loaderColor="blue"
loaderPosition="center" />
<div class="row form-test-error" id="date-of-month-error">
@ -125,7 +127,7 @@
type="button"
class="btn btn-link view-more-dates"
id="viewMoreDates"
:disabled="isLoading"
:disabled="loadingStatus !== 'none'"
@click="showAnotherMonth">
View more dates
</button>
@ -160,7 +162,6 @@ export default {
name: "datePicker",
data() {
return {
isLoading: true,
months: null,
disableViewMoreDatesButton: false,
hideSomeDaysForInitialView: null,
@ -169,7 +170,10 @@ export default {
};
},
props: {
customComponentId: String,
customComponentId: {
type: String,
default: "",
},
selectableDatesSetting: {
type: String,
validator(value) {
@ -200,6 +204,10 @@ export default {
pricingByDayUpcharge: Number,
isPricingByDayExperiment: Boolean,
isMobileSelected: Boolean,
loadingStatus: {
type: String,
default: "none",
},
},
setup(props) {
const uuid = uuidv4();
@ -494,7 +502,6 @@ export default {
});
},
async setCalendarData(config = {}) {
this.isLoading = true;
this.selectableDatesInshop = [];
this.selectableDatesMobile = [];
this.months = [];
@ -546,7 +553,6 @@ export default {
// }
}
this.months = months;
this.isLoading = false;
if (config.preSelectedDate) {
this.$nextTick(() => {
@ -734,7 +740,6 @@ export default {
},
async showAnotherMonth() {
if (!this.months) return;
this.isLoading = true;
let monthToShow;
let monthStartDate;
@ -779,7 +784,6 @@ export default {
monthToShowDates[monthToShowDates.length - 1].inputValue
);
this.isLoading = false;
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
this.scrollToElement(monthToShow.monthString);
@ -792,7 +796,6 @@ export default {
},
async updateSelectableDates(monthStart, monthEnd) {
const moreSelectableDates = await this.getMoreDatesCallback(monthStart, monthEnd);
moreSelectableDates.inshopTimeSlotsData.days.forEach((selectableDate) => {
const index = this.selectableDatesInshop.findIndex(
(dateObj) => dateObj.date === selectableDate.date
@ -851,12 +854,6 @@ export default {
},
},
watch: {
isLoading(newValue) {
// if done loading dates, then set to first available date
if (newValue === false && this.firstAvailableSelectableDate) {
this.selectedDate = this.firstAvailableSelectableDate;
}
},
modelValue(newValue) {
this.resetField({
value: newValue,
@ -877,9 +874,14 @@ export default {
</script>
<style lang="scss" scoped>
.date-picker-hidden {
.date-picker-loader {
opacity: 0;
max-height: 0;
&.show-loader {
opacity: 1;
max-height: none;
}
}
.date-picker {
position: relative;
@ -908,12 +910,12 @@ export default {
height: 1.5rem;
}
}
.calendar-grid-container {
.month {
margin: 0 auto 1rem auto;
position: relative;
transition:
height ease 2s,
opacity ease 2s;
height ease 1s,
max-height ease 1s;
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-content: center;
@ -1162,7 +1164,7 @@ export default {
}
.past {
.calendar-grid-container {
.month {
.month-year {
grid-area: 1 / 1 / 2 / 6;
}
@ -1219,7 +1221,7 @@ export default {
}
// new calendar styling overrides
&.calendar-2025 {
.calendar-grid-container .grid-item {
.month .grid-item {
margin: 0 0 0.15rem 0;
}
.month-year {
@ -1321,6 +1323,33 @@ export default {
}
}
}
&.loading-reinitial {
.month {
opacity: 0;
max-height: 0;
}
.month.first-month-shown {
opacity: 1;
max-height: none;
.grid-item.radio-wrapper.selectable-day {
input[type="radio"]:checked + label,
label {
background-color: $white;
border: none;
color: initial;
cursor: default;
&:hover {
border: none;
}
span {
font-family: UrbanistRegular;
}
}
}
}
}
}
:deep(.timeslots .button-inner-wrapper) {
display: flex;

View file

@ -72,10 +72,10 @@ export function isDropOffRouteCode(routeCode) {
);
}
export function getTodayDate(routeCode) {
export function getTodayDate() {
return new Date();
}
export function getTodayDateString(routeCode) {
export function getTodayDateString() {
return convertDateToDateString(getTodayDate());
}

View file

@ -149,7 +149,8 @@
:pricingByDayBasePrice="pricingByDayBasePrice"
:pricingByDayUpcharge="pricingByDayUpcharge"
:showPricingByDay="showPricingByDay"
:isPricingByDayExperiment="isPricingByDayExperiment" />
:isPricingByDayExperiment="isPricingByDayExperiment"
:loadingStatus="calendarLoadingStatus" />
<timeSlotQuestion
ref="timeSlotModalQuestion"
v-if="showTimeSlotQuestion"
@ -168,7 +169,8 @@
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:isSameDay="isSameDay"
:selectedRouteCodeData="selectedRouteCodeData"
@waitListRequested="handleWaitListRequested" />
@waitListRequested="handleWaitListRequested"
:lazyLoadAmountToLoad="10" />
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
@ -439,15 +441,14 @@ export default {
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
mobilePremiumAppointmentFee: null,
waitListRequested: null,
displayWaitList: null,
pricingByDayUpchargeLineItem: null,
includePricingByDayUpcharge: null,
isPricingByDayExperiment: null,
pricingByDayBasePrice: null,
pricingByDayUpcharge: null,
showPricingByDay: null,
selectableDatesInshop: [],
selectableDatesMobile: [],
selectableDatesInshop: {},
selectableDatesMobile: {},
preSelectedDate: null,
streetAddress: this.getServiceAddressFromStore(),
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
@ -480,6 +481,7 @@ export default {
experimentSettings.SHOW_MOBILE_FIRST_APPT,
"true"
),
calendarLoadingStatus: "none",
};
},
async beforeRouteEnter(to, from, next) {
@ -590,6 +592,8 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.showLoadingModal();
vm.calendarLoadingStatus = "initial";
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.mobilePremiumAppointmentFee = pricedPremiumFee;
@ -610,6 +614,9 @@ export default {
);
vm.initializeDatePicker().then(() => {
vm.showMobileFirstModal();
vm.hideLoadingModal();
vm.calendarLoadingStatus = "none";
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
});
});
},
@ -896,6 +903,53 @@ export default {
? this.$refs.mobileFirstModal?.getIsModalOpen()
: false;
},
displayWaitList() {
if (!this.appointmentType) return false;
const firstMobileDateString =
Array.isArray(this.selectableDatesMobile.days) &&
this.selectableDatesMobile.days.length > 0
? this.selectableDatesMobile.days[0].date
: null;
const firstInshopDateString =
Array.isArray(this.selectableDatesInshop.days) &&
this.selectableDatesInshop.days.length > 0
? this.selectableDatesInshop.days[0].date
: null;
const dateString =
this.isMobileSelected && firstMobileDateString
? firstMobileDateString
: firstInshopDateString;
if (
experimentMixin.methods.hasSettingEqualTo(
experimentSettings.DISPLAY_WAITLIST,
"true"
) &&
dateString
) {
const [year, month, day] = dateString.split("-").map(Number);
const targetDate = new Date(year, month - 1, day);
const currentDate = getTodayDate();
const futureDate = new Date(currentDate);
const experimentThresholdDays = experimentMixin.methods.hasSetting(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
? parseInt(
experimentMixin.methods.getSettingValue(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
)
: 0;
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
if (targetDate >= futureDate) {
return true;
} else {
return false;
}
}
return false;
},
hasSelectableDatesLoaded() {
if (this.isMobileSelected) {
return this.selectableDatesMobile?.days?.length > 0;
@ -1164,9 +1218,10 @@ export default {
);
}
},
async onShopSelected(shopQuestionPopUpData) {
onShopSelected(shopQuestionPopUpData) {
this.resetWaitlist();
this.preSelectedDate = null;
this.calendarLoadingStatus = "reinitial";
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
const oldProvider = this.selectedProvider;
const selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
@ -1186,11 +1241,16 @@ export default {
this.updateSelectedProvider(selectedProvider);
}
}
await this.initializeDatePicker();
this.setSelectedDateToFirstAvailable();
this.initializeDatePicker().then(() => {
this.showMobileFirstModal();
this.hideLoadingModal();
this.calendarLoadingStatus = "none";
this.setSelectedDateToFirstAvailable();
});
},
async getMoreScheduleData(startDate, endDate) {
// called only when "View more dates" is clicked; not on initial page load
this.calendarLoadingStatus = "more";
const moreShopTimeSlots = await getScheduleApiResponse({
startDateString: startDate,
endDateString: endDate,
@ -1202,6 +1262,7 @@ export default {
includeMobileTimeSlots: this.isServiceableMobile,
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
});
this.calendarLoadingStatus = "none";
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
@ -1240,7 +1301,6 @@ export default {
await datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
this.selectableDatesMobile =
await datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
this.setDisplayWaitList();
if (this.preSelectedDate) {
this.selectedDate = this.preSelectedDate;
@ -1252,11 +1312,8 @@ export default {
) {
await this.$nextTick();
await this.$refs.datePicker.showAnotherMonth();
this.setDisplayWaitList();
}
}
this.isShowMobileFirstAppt && this.hideLoadingModal();
},
getIsRecalAcknowledgedForSchedulingFromStore() {
return store.getters.order.isRecalAcknowledgedForScheduling;
@ -1526,39 +1583,6 @@ export default {
}
this.showLoadingModal();
},
setDisplayWaitList() {
const dateString = this.isMobileSelected
? this.selectableDatesMobile?.days[0]?.date
: this.selectableDatesInshop?.days[0]?.date;
if (
experimentMixin.methods.hasSettingEqualTo(
experimentSettings.DISPLAY_WAITLIST,
"true"
) &&
dateString
) {
const [year, month, day] = dateString.split("-").map(Number);
const targetDate = new Date(year, month - 1, day);
const currentDate = getTodayDate();
const futureDate = new Date(currentDate);
const experimentThresholdDays = experimentMixin.methods.hasSetting(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
? parseInt(
experimentMixin.methods.getSettingValue(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
)
: 0;
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
if (targetDate >= futureDate) {
this.displayWaitList = true;
} else {
this.displayWaitList = false;
}
}
},
updateSupportingItems() {
const supportingItems = this.getSupportingItems();
@ -1754,6 +1778,7 @@ export default {
}
this.resetMobileLocation();
this.calendarLoadingStatus = "initial";
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
this.shopProviderData = result.data;
@ -1764,6 +1789,9 @@ export default {
this.selectedDate = null;
this.initializeDatePicker().then(() => {
this.showMobileFirstModal();
this.hideLoadingModal();
this.calendarLoadingStatus = "none";
this.setSelectedDateToFirstAvailable();
});
});
},

View file

@ -36,7 +36,10 @@
isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '',
]"
:answers="availableTimeSlots"
:lazyLoad="{ isLazyLoad: !isMobileAppointment, amountToLoad: 10 }"
:lazyLoad="{
isLazyLoad: isLazyLoad,
amountToLoad: lazyLoadAmountToLoad || 10,
}"
groupName="chooseTimeSlot"
textPosition="text-center"
questionText="Available times:"
@ -134,6 +137,7 @@ export default {
isSameDay: Boolean,
displayWaitList: Boolean,
selectedRouteCodeData: Object,
lazyLoadAmountToLoad: Number,
},
data() {
return {
@ -396,6 +400,26 @@ export default {
!this.isDropOffAppointmentAvailable
);
},
isLazyLoad() {
if (this.availableTimeSlots?.length < this.lazyLoadAmountToLoad + 1) return false;
if (this.isMobileAppointment) return false;
if (this.selectedAnswerForTimeSlots && !this.isSelectedInshopTimeSlotShowing)
return false;
return true;
},
availableTimeSlotsForInshop() {
return this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate?.timeSlots);
},
isSelectedInshopTimeSlotShowing() {
if (!this.selectedAnswerForTimeSlots) return false;
const idx = this.availableTimeSlotsForInshop?.findIndex((timeSlot) => {
return timeSlot.value === this.selectedAnswerForTimeSlots;
});
if (idx > -1 && idx < this.lazyLoadAmountToLoad) {
return true;
}
return false;
},
},
methods: {
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
@ -453,6 +477,7 @@ export default {
// }
// },
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
if (!timeSlotsForSelectedDate) return;
return timeSlotsForSelectedDate
.filter((timeSlot) => {
return !isDropOffRouteCode(timeSlot.id);