Merge pull request #1237 from Safelite/rlsmerge/develop-to-submit-cash-17-July

Rlsmerge/develop to submit cash 17 july
This commit is contained in:
hiteshkumar87 2023-07-17 13:10:44 +05:30 committed by GitHub
commit cbb4946130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 649 additions and 415 deletions

View file

@ -74,7 +74,7 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
</div> </div>
<div class="row form-test-error"> <div class="row form-test-error">
<error-message <error-message
class="small" class="small mt-1"
:name="formatString(groupName)" :name="formatString(groupName)"
v-if="!suppressError"></error-message> v-if="!suppressError"></error-message>
</div> </div>

View file

@ -6,7 +6,7 @@
v-for="month in months" v-for="month in months"
:key="`${month.monthLabel}-${month.yearNum?.toString()}`" :key="`${month.monthLabel}-${month.yearNum?.toString()}`"
:id="`${month.monthLabel}-${month.yearNum?.toString()}`" :id="`${month.monthLabel}-${month.yearNum?.toString()}`"
class="calendar-grid-container position-relative" class="calendar-grid-container"
:class="[ :class="[
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '', hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
month.monthClass, month.monthClass,
@ -74,6 +74,10 @@ import loader from "@/ux-components/loader/loader";
import store from "@/store"; import store from "@/store";
import { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR } from "./mixins/constants"; import { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR } from "./mixins/constants";
import { selectableDaysOptions, requiredParameter, forceTwoDigitString } from "./mixins/helpers"; import { selectableDaysOptions, requiredParameter, forceTwoDigitString } from "./mixins/helpers";
import {
convertDateToDateString,
convertDateStringToDate,
} from "@/layouts/schedule/helpers/schedule-helper";
export default { export default {
name: "datePicker", name: "datePicker",
@ -110,23 +114,14 @@ export default {
}, },
}, },
computed: { computed: {
today() { todayString() {
if (this.todayOverrideDateString) { return this.todayOverrideDateString || convertDateToDateString(new Date());
return new Date(this.todayOverrideDateString + "T00:00:00");
}
return new Date();
},
todayMonthIndex() {
return this.today.getMonth() + 1;
},
todayYearNum() {
return this.today.getFullYear();
}, },
todayDayIndex() { todayDayIndex() {
return this.today.getDay(); return convertDateStringToDate(this.todayString).getDay();
}, },
todayDateNum() { todayDateNum() {
return this.today.getDate(); return convertDateStringToDate(this.todayString).getDate();
}, },
currentWeekStartDateNum() { currentWeekStartDateNum() {
return this.todayDayIndex >= this.todayDateNum return this.todayDayIndex >= this.todayDateNum
@ -157,154 +152,187 @@ export default {
fireDateClickedEvent() { fireDateClickedEvent() {
this.$emit("date-clicked"); this.$emit("date-clicked");
}, },
getWeekStartDate(date) { getWeekStartDate(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay(); const dayOfWeek = date.getDay();
// Subtract the day of the week from date to get the date of Sunday // Subtract the day of the week from date to get the date of Sunday
const sunday = new Date(date); const sunday = new Date(date);
sunday.setDate(sunday.getDate() - dayOfWeek); sunday.setDate(sunday.getDate() - dayOfWeek);
return sunday; return convertDateToDateString(sunday);
}, },
getWeekEndDate(date) { getWeekEndDate(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay(); const dayOfWeek = date.getDay();
const daysUntilSaturday = 6 - dayOfWeek; // Calculate the number of days until Saturday const daysUntilSaturday = 6 - dayOfWeek; // Calculate the number of days until Saturday
// Clone the given date and add the remaining days until Saturday // Clone the given date and add the remaining days until Saturday
const saturday = new Date(date); const saturday = new Date(date);
saturday.setDate(date.getDate() + daysUntilSaturday); saturday.setDate(date.getDate() + daysUntilSaturday);
return saturday; return convertDateToDateString(saturday);
}, },
getNextWeekSunday(date) { getNextWeekSunday(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay(); const dayOfWeek = date.getDay();
const daysUntilNextSunday = 7 - dayOfWeek; // Calculate the number of days until the next Sunday const daysUntilNextSunday = 7 - dayOfWeek; // Calculate the number of days until the next Sunday
// Clone the given date and add the remaining days until Sunday // Clone the given date and add the remaining days until Sunday
const nextSunday = new Date(date); const nextSunday = new Date(date);
nextSunday.setDate(date.getDate() + daysUntilNextSunday); nextSunday.setDate(date.getDate() + daysUntilNextSunday);
return nextSunday; return convertDateToDateString(nextSunday);
}, },
getInitialViewWeeks(today, initialViewRowsToShow, preSelectedDateString) { getMonthEnd(dateStr) {
// convert string to date, do date calc, then return a string back
const date = new Date(dateStr.split("-")[0], parseInt(dateStr.split("-")[1]), 0);
return convertDateToDateString(date);
},
getInitialViewWeeks(todayString, initialViewRowsToShow, preSelectedDateString) {
// TODO: this only is for future direction; need to create logic for past direction // TODO: this only is for future direction; need to create logic for past direction
const weeks = []; const weeks = [];
let weekStartDate = this.getWeekStartDate(todayString);
let weekEndDate = this.getWeekEndDate(todayString);
if (preSelectedDateString) initialViewRowsToShow = 26; if (preSelectedDateString) {
let preSelectedDateMonthEnd = this.getMonthEnd(preSelectedDateString);
let weekIncludesPreSelectedMonthEnd = false;
let i = 0;
while (!weekIncludesPreSelectedMonthEnd) {
if (i > 0) {
weekStartDate = this.getNextWeekSunday(weekEndDate);
weekEndDate = this.getWeekEndDate(weekStartDate);
let weekStartDate = this.getWeekStartDate(today); if (
let weekEndDate = this.getWeekEndDate(today); (preSelectedDateMonthEnd > weekStartDate &&
for (let i = 0; i < initialViewRowsToShow; i++) { preSelectedDateMonthEnd < weekEndDate) ||
if (i > 0) { preSelectedDateMonthEnd === weekStartDate ||
weekStartDate = this.getNextWeekSunday(weekEndDate); preSelectedDateMonthEnd === weekEndDate
weekEndDate = this.getWeekEndDate(weekStartDate); ) {
} weekEndDate = preSelectedDateMonthEnd;
weeks.push({ weekIncludesPreSelectedMonthEnd = true;
weekNum: i + 1,
weekStartDate: weekStartDate,
weekEndDate: weekEndDate,
});
if (
preSelectedDateString &&
new Date(preSelectedDateString + "T00:00:00") < weekEndDate
) {
break;
}
}
// are any of these weeks split between two months?
// NOTE: a week split between two months counts as 2 weeks
const hasSplitWeek = (week) => {
return week.weekStartDate.getMonth() !== week.weekEndDate.getMonth() ? true : false;
};
const splitWeekIndex = weeks.findIndex(hasSplitWeek);
if (!preSelectedDateString && splitWeekIndex > -1) {
// a preSelectedDateString precludes split week logic
const week1 = [];
const week2 = [];
let switchToWeek2 = false;
for (let j = 0; j < 7; j++) {
const newDate = new Date(weeks[splitWeekIndex].weekStartDate);
newDate.setDate(newDate.getDate() + j);
if (newDate.getDate() === 1) switchToWeek2 = true;
if (switchToWeek2) {
week2.push(newDate);
} else {
week1.push(newDate);
}
}
const week1EndDate = week1[week1.length - 1];
const week2StartDate = week2[0];
if (week1EndDate < today) {
// replace week 1 with week 2
weeks[splitWeekIndex].weekStartDate = week2StartDate;
} else {
const newWeek = {
weekNum: weeks[splitWeekIndex].weekNum,
weekStartDate: week2StartDate,
weekEndDate: weeks[splitWeekIndex].weekEndDate,
};
weeks[splitWeekIndex].weekEndDate = week1EndDate;
weeks.splice(splitWeekIndex + 1, 0, newWeek);
weeks.pop();
weeks.forEach((item, index) => {
if (index > splitWeekIndex) {
item.weekNum = item.weekNum + 1;
} }
}
weeks.push({
weekNum: i + 1,
weekStartDate: weekStartDate,
weekEndDate: weekEndDate,
}); });
i++;
}
} else {
for (let i = 0; i < initialViewRowsToShow; i++) {
if (i > 0) {
weekStartDate = this.getNextWeekSunday(weekEndDate);
weekEndDate = this.getWeekEndDate(weekStartDate);
}
weeks.push({
weekNum: i + 1,
weekStartDate: weekStartDate,
weekEndDate: weekEndDate,
});
}
// If any of these weeks is split between two months, then make them 2 separate "weeks"
// (a week split between two months is considered 2 weeks per business requirements)
const hasSplitWeek = (week) => {
return week.weekStartDate.split("-")[1] !== week.weekEndDate.split("-")[1]
? true
: false;
};
const splitWeekIndex = weeks.findIndex(hasSplitWeek);
if (splitWeekIndex > -1) {
const week1 = [];
const week2 = [];
let switchToWeek2 = false;
for (let j = 0; j < 7; j++) {
const newDate = convertDateStringToDate(
weeks[splitWeekIndex].weekStartDate
);
newDate.setDate(newDate.getDate() + j);
if (newDate.getDate() === 1) switchToWeek2 = true;
if (switchToWeek2) {
week2.push(convertDateToDateString(newDate));
} else {
week1.push(convertDateToDateString(newDate));
}
}
const week1EndDate = week1[week1.length - 1];
const week2StartDate = week2[0];
if (week1EndDate < todayString) {
// replace week 1 with week 2
weeks[splitWeekIndex].weekStartDate = week2StartDate;
} else {
const newWeek = {
weekNum: weeks[splitWeekIndex].weekNum,
weekStartDate: week2StartDate,
weekEndDate: weeks[splitWeekIndex].weekEndDate,
};
weeks[splitWeekIndex].weekEndDate = week1EndDate;
weeks.splice(splitWeekIndex + 1, 0, newWeek);
weeks.pop();
weeks.forEach((item, index) => {
if (index > splitWeekIndex) {
item.weekNum = item.weekNum + 1;
}
});
}
} }
} }
return weeks; return weeks;
}, },
async loadInitialData(config) { async loadInitialData(config) {
let todayDate; /*
if (this.today) { ** NOTE: this _could_ be called by a parent before fully loaded, so data or computeds might not be available
todayDate = this.today; */
} else if (config.todayOverrideDateString) { let todayDateString;
todayDate = new Date(config.todayOverrideDateString + "T00:00:00");
} else {
todayDate = new Date();
}
const todayMonthIndex = todayDate.getMonth() + 1;
const todayYearNum = todayDate.getFullYear();
// TODO - set up currentMonthStart if direction is PAST:
// let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
const currentMonthEnd = new Date(todayYearNum, todayMonthIndex, 0);
let calendarViewDirection = "none"; let calendarViewDirection = "none";
if (config.selectableDatesSetting === "past") calendarViewDirection = "past";
if (config.selectableDatesSetting === "custom") calendarViewDirection = "future";
const initialViewWeeks = this.getInitialViewWeeks(
todayDate,
config.initialViewRowsToShow,
config.preSelectedDate
);
const initialViewStartDate = todayDate;
const initialViewEndDate = initialViewWeeks[initialViewWeeks.length - 1].weekEndDate;
const firstSaturdayMonth = initialViewWeeks[0].weekEndDate.getMonth();
const lastSundayMonth =
initialViewWeeks[initialViewWeeks.length - 1].weekStartDate.getMonth();
let hideSomeDaysForInitialView = false; let hideSomeDaysForInitialView = false;
let hideSecondMonth = false; let hideSecondMonth = false;
// TODO - NEED TO CREATE OPPOSITE LOGIC FOR "past" DIRECTION LIKE BELOW v v v if (this.todayString) {
if (calendarViewDirection === "future" && !config.preSelectedDate) { todayDateString = this.todayString;
if (firstSaturdayMonth !== lastSundayMonth) { } else if (config.todayOverrideDateString) {
hideSomeDaysForInitialView = true; todayDateString = config.todayOverrideDateString;
} } else {
if (initialViewStartDate.getMonth() === lastSundayMonth) { todayDateString = convertDateToDateString(new Date());
hideSecondMonth = true; }
if (currentMonthEnd > initialViewEndDate) { if (config.selectableDatesSetting === "past") calendarViewDirection = "past";
// should part of 1st month be hidden? if (config.selectableDatesSetting === "custom") calendarViewDirection = "future";
const currentMonthEnd = this.getMonthEnd(todayDateString);
// TODO - set up currentMonthStart if direction is PAST:
// let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
const initialViewWeeks = this.getInitialViewWeeks(
todayDateString,
config.initialViewRowsToShow,
config.preSelectedDate
);
const initialViewStartDate = todayDateString;
const initialViewEndDate = initialViewWeeks[initialViewWeeks.length - 1].weekEndDate;
if (!config.preSelectedDate) {
const firstSaturdayMonth = initialViewWeeks[0].weekEndDate.split("-")[1];
const lastSundayMonth =
initialViewWeeks[initialViewWeeks.length - 1].weekStartDate.split("-")[1];
if (calendarViewDirection === "future") {
if (firstSaturdayMonth !== lastSundayMonth) {
hideSomeDaysForInitialView = true; hideSomeDaysForInitialView = true;
} }
if (initialViewStartDate.split("-")[1] === lastSundayMonth) {
hideSecondMonth = true;
if (currentMonthEnd > initialViewEndDate) {
// should part of 1st month be hidden?
hideSomeDaysForInitialView = true;
}
}
} }
// TODO - NEED TO CREATE OPPOSITE LOGIC FOR "past" DIRECTION LIKE THE ABOVE ^ ^ ^
} }
const loadInitialDataPromise = new Promise((resolve, reject) => { const loadInitialDataPromise = new Promise((resolve, reject) => {
const response = config.customSelectableDatesCallback( const response = config.customSelectableDatesCallback(
initialViewStartDate.toISOString().split("T")[0], initialViewStartDate,
initialViewEndDate.toISOString().split("T")[0], initialViewEndDate,
store.getters.order.serviceLocation.appointmentType, store.getters.order.serviceLocation.appointmentType,
store.getters.order.serviceLocation.provider.providerNumber store.getters.order.serviceLocation.provider.providerNumber
); );
@ -313,7 +341,7 @@ export default {
return loadInitialDataPromise.then((response) => { return loadInitialDataPromise.then((response) => {
const initialData = { const initialData = {
todayDate: todayDate, todayDate: todayDateString,
initialViewStartDate: initialViewStartDate, initialViewStartDate: initialViewStartDate,
initialViewEndDate: initialViewEndDate, initialViewEndDate: initialViewEndDate,
calendarViewDirection: calendarViewDirection, calendarViewDirection: calendarViewDirection,
@ -329,7 +357,7 @@ export default {
this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView; this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView;
const hideSecondMonth = config.hideSecondMonth; const hideSecondMonth = config.hideSecondMonth;
const direction = config.calendarViewDirection; const direction = config.calendarViewDirection;
const monthsAfterToLoadOffset = 12; const monthsAfterToLoadOffset = 6;
const monthsBeforeToLoadOffset = 36; const monthsBeforeToLoadOffset = 36;
config.initialShopTimeSlotsResponse.days.forEach((selectableDate) => { config.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
this.selectableDatesData.push(selectableDate); this.selectableDatesData.push(selectableDate);
@ -371,6 +399,13 @@ export default {
const monthToShow = this.months.find((month) => const monthToShow = this.months.find((month) =>
month.monthClass.includes("month-preselected") month.monthClass.includes("month-preselected")
); );
if (
monthToShow.monthClass.includes("month-preselected") &&
monthToShow.monthClass.includes("last-available-month")
) {
// disable View More dates button if the preSelectedDate in the last available month
this.disableViewMoreDatesButton = true;
}
this.scrollToElement(monthToShow.monthString); this.scrollToElement(monthToShow.monthString);
}); });
} }
@ -390,32 +425,37 @@ export default {
this.hideSomeDaysForInitialView (string) this.hideSomeDaysForInitialView (string)
*/ */
let monthIndex = this.todayMonthIndex + offset; // mutable let monthNum = convertDateStringToDate(this.todayString).getMonth() + offset + 1;
let yearNum = this.todayYearNum; // mutable let yearNum = convertDateStringToDate(this.todayString).getFullYear();
const calendarViewDirection = options.calendarViewDirection; const calendarViewDirection = options.calendarViewDirection;
const initialViewStartDate = options.initialViewStartDate;
const initialViewEndDate = options.initialViewEndDate;
const hideSecondMonth = options.hideSecondMonth;
const preSelectedDateObj = options.preSelectedDate
? new Date(options.preSelectedDate + "T00:00:00")
: null;
const dates = [];
let monthClass = "";
let isMonthThatHidesSomeDaysForInitialView;
if (calendarViewDirection === "future" && offset > 0) { if (calendarViewDirection === "future" && offset > 0) {
while (monthIndex > 12) { while (monthNum > 12) {
monthIndex = monthIndex - 12; monthNum = monthNum - 12;
yearNum++; yearNum++;
} }
} else if (calendarViewDirection === "past" && offset < 0) { } else if (calendarViewDirection === "past" && offset < 0) {
while (monthIndex < 1) { while (monthNum < 1) {
monthIndex = 12 + monthIndex; monthNum = 12 + monthNum;
yearNum--; yearNum--;
} }
} }
const initialViewEndDate = options.initialViewEndDate;
// const initialViewStartDate = options.initialViewStartDate; // TODO: to be used for past calendarViewDirection
const hideSecondMonth = options.hideSecondMonth;
const preSelectedDateObj = options.preSelectedDate
? convertDateStringToDate(options.preSelectedDate)
: null;
const dates = [];
const monthEndDate = new Date(yearNum, monthNum, 0);
const monthStartDateNum =
offset === 0 && calendarViewDirection === "future"
? this.currentWeekStartDateNum
: 1;
const monthStartDate = new Date(yearNum, monthNum - 1, monthStartDateNum);
const startDateDayIndex = monthStartDate.getDay();
const monthEndDate = new Date(yearNum, monthIndex, 0); let monthClass = "";
let isMonthThatHidesSomeDaysForInitialView;
let monthEndDateNum = monthEndDate.getDate(); let monthEndDateNum = monthEndDate.getDate();
if ( if (
@ -426,16 +466,7 @@ export default {
monthEndDateNum = this.currentWeekEndDateNum; monthEndDateNum = this.currentWeekEndDateNum;
} }
const monthStartDateNum = if (options.preSelectedDate) {
offset === 0 && calendarViewDirection === "future"
? this.currentWeekStartDateNum
: 1;
const monthStartDate = new Date(yearNum, monthIndex - 1, monthStartDateNum);
const startDateDayIndex = monthStartDate.getDay();
const endDateDayIndex = monthEndDate.getDay();
if (preSelectedDateObj) {
if ( if (
monthStartDate.getFullYear() === preSelectedDateObj.getFullYear() && monthStartDate.getFullYear() === preSelectedDateObj.getFullYear() &&
monthStartDate.getMonth() === preSelectedDateObj.getMonth() monthStartDate.getMonth() === preSelectedDateObj.getMonth()
@ -472,25 +503,28 @@ export default {
const dateString = const dateString =
yearNum.toString() + yearNum.toString() +
"-" + "-" +
forceTwoDigitString(monthIndex) + ("0" + monthNum).slice(-2) +
"-" + "-" +
forceTwoDigitString(i); ("0" + i).slice(-2);
if (offset === 0 && i === this.todayDateNum) { if (offset === 0 && i === this.todayDateNum) {
dayClasses += "current-day"; dayClasses += " current-day";
} }
if (offset === 0 && i < this.todayDateNum && calendarViewDirection === "future") { if (offset === 0 && i < this.todayDateNum && calendarViewDirection === "future") {
dayClasses += "unavailable-day"; dayClasses += " unavailable-day";
} }
if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") { if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") {
dayClasses += "unavailable-day"; dayClasses += " unavailable-day";
}
if (convertDateStringToDate(dateString).getDay() === 0) {
dayClasses += " sunday";
} }
if ( if (
this.hideSomeDaysForInitialView && this.hideSomeDaysForInitialView &&
initialViewEndDate.getMonth() + 1 === monthIndex && convertDateStringToDate(initialViewEndDate).getMonth() + 1 === monthNum &&
initialViewEndDate.getDate() < i convertDateStringToDate(initialViewEndDate).getDate() < i
) { ) {
dayClasses += "day-hidden"; dayClasses += " day-hidden";
isMonthThatHidesSomeDaysForInitialView = true; isMonthThatHidesSomeDaysForInitialView = true;
} }
const dateObject = { const dateObject = {
@ -506,9 +540,9 @@ export default {
} }
const monthToAdd = { const monthToAdd = {
monthLabel: MONTHS_OF_YEAR[monthIndex - 1], monthLabel: MONTHS_OF_YEAR[monthNum - 1],
monthIndex: monthIndex, monthIndex: monthNum,
monthString: MONTHS_OF_YEAR[monthIndex - 1] + "-" + yearNum?.toString(), monthString: MONTHS_OF_YEAR[monthNum - 1] + "-" + yearNum?.toString(),
yearNum: yearNum, yearNum: yearNum,
dates: dates, dates: dates,
startDateDayIndex: startDateDayIndex, startDateDayIndex: startDateDayIndex,
@ -628,12 +662,16 @@ export default {
.date-picker { .date-picker {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
height: 100%; flex-grow: 1;
display: flex;
flex-direction: column;
fieldset { fieldset {
overflow-y: auto; overflow-y: auto;
height: 88%; flex-grow: 1;
position: relative; position: relative;
padding-bottom: 6rem; // arbitrary amount to provide enough spacing for scroll animation
max-height: 21.25rem; // 340px... arbitrary amount to not push View More link out of view on iphones
} }
.loader { .loader {
position: absolute; position: absolute;
@ -648,6 +686,7 @@ export default {
.calendar-grid-container { .calendar-grid-container {
margin: 0 auto 2rem auto; margin: 0 auto 2rem auto;
max-width: 414px; max-width: 414px;
position: relative;
transition: height ease 2s, opacity ease 2s; transition: height ease 2s, opacity ease 2s;
display: grid; display: grid;
grid-template-columns: repeat(7, 1fr); grid-template-columns: repeat(7, 1fr);
@ -659,6 +698,8 @@ export default {
.grid-item { .grid-item {
text-align: center; text-align: center;
margin: 10px 3px; margin: 10px 3px;
font-size: 0.875rem;
line-height: 1.5;
&.first-day-, &.first-day-,
&.first-day-0 { &.first-day-0 {
@ -687,7 +728,7 @@ export default {
.separator-line { .separator-line {
grid-area: 2/1/2/8; grid-area: 2/1/2/8;
border-top: 1px solid $gray-500; border-top: 1px solid $gray-500;
margin: 0.75rem 0; margin: 0.5rem 0;
} }
.month-year { .month-year {
@ -717,7 +758,7 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
outline: none; outline: none;
height: 1.35rem; height: 1.5rem;
opacity: 1; opacity: 1;
transition: height ease 250ms, opacity ease 250ms; transition: height ease 250ms, opacity ease 250ms;
@ -735,12 +776,6 @@ export default {
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1; box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1;
background-color: $blue; background-color: $blue;
color: $white; color: $white;
&.past-day {
box-shadow: none;
background-color: transparent;
color: $gray-500;
font-weight: normal;
}
&.current-day { &.current-day {
&:after { &:after {
background-color: $white; background-color: $white;
@ -754,7 +789,6 @@ export default {
&:checked + label { &:checked + label {
color: $white; color: $white;
background: $blue; background: $blue;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue;
&:after { &:after {
background-color: $white; background-color: $white;
} }
@ -779,10 +813,9 @@ export default {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 36px; width: 3rem;
height: 36px; height: 2.5rem;
min-width: 36px; min-width: 3rem;
border-radius: 50%;
span { span {
&.small { &.small {
@ -821,26 +854,43 @@ export default {
color: $blue; color: $blue;
background-color: $blue-100; background-color: $blue-100;
border: 1px solid $blue; border: 1px solid $blue;
min-width: 2.5rem;
width: 2.5rem;
border-radius: 50%;
} }
} }
&.past-day,
&.future-day,
&.unavailable-day { &.unavailable-day {
label { label {
color: $gray-500; color: $gray-500;
background-color: transparent; background-color: $gray-100;
border: 1px solid transparent; border: none;
pointer-events: none; pointer-events: none;
} }
} }
&.unavailable-day:not(&.sunday) {
label {
&::before {
content: "";
display: inline-block;
position: absolute;
left: -1rem;
width: 1rem;
height: 2.5rem;
background: $gray-100;
}
}
}
&.current-day { &.current-day {
label { label {
font-weight: 500;
color: $black;
&:after { &:after {
content: ""; content: "";
width: 0.25rem; width: 0.25rem;
height: 0.25rem; height: 0.25rem;
border-radius: 50%; border-radius: 50%;
background-color: $blue; background-color: $black;
position: absolute; position: absolute;
top: 28px; top: 28px;
} }
@ -864,15 +914,12 @@ export default {
opacity: 0; opacity: 0;
max-height: 0; max-height: 0;
margin-bottom: 0; margin-bottom: 0;
overflow: hidden;
} }
&.last-available-month:not(&.month-hidden) { &.last-available-month:not(&.month-hidden) {
margin-bottom: 14rem; margin-bottom: 14rem;
} }
} }
#bottom-spacer {
height: 20rem;
background: lightblue;
}
.btn-link { .btn-link {
font-weight: 500; font-weight: 500;

View file

@ -7,9 +7,4 @@ const requiredParameter = () => {
throw new Error("parameter is required"); throw new Error("parameter is required");
}; };
const forceTwoDigitString = (monthNum) => { export { selectableDaysOptions, requiredParameter };
const newString = monthNum.toString();
return newString.length === 1 ? "0" + newString : newString;
};
export { selectableDaysOptions, requiredParameter, forceTwoDigitString };

View file

@ -139,6 +139,13 @@ export default {
color: rgb(0, 0, 0); color: rgb(0, 0, 0);
} }
} }
.modal-footer {
position: sticky;
width: 100%;
bottom: 0;
z-index: 5;
background-color: $gray-100;
}
&.modal-component { &.modal-component {
.modal-dialog { .modal-dialog {
max-width: 576px; max-width: 576px;

View file

@ -65,9 +65,9 @@
</template> </template>
</template> </template>
</div> </div>
<div v-show="errorMessage" class="row my-1 form-test-error"> <div v-show="errorMessage" class="row form-test-error">
<span <span
class="d-inline-flex small mt-0" class="d-inline-flex small my-1"
role="alert" role="alert"
:class="[centerErrorMessage ? 'center-error-message' : '']" :class="[centerErrorMessage ? 'center-error-message' : '']"
>{{ errorMessage }}</span >{{ errorMessage }}</span

View file

@ -39,9 +39,11 @@ export default {
} }
const pageName = analyticsMixIn.methods.getPageName(); const pageName = analyticsMixIn.methods.getPageName();
const nextPageName = router.lastNavigationPage || pageName; const nextPageName = router.lastNavigationPage || pageName;
const endpointWithoutParams =
analyticsMixIn.methods.removeParamsFromEndpoint(endpoint);
analyticsMixIn.methods.pushEventToGA( analyticsMixIn.methods.pushEventToGA(
GaCategories.API_RESPONSE, GaCategories.API_RESPONSE,
`${nextPageName}_${endpoint}`, `${nextPageName}_${endpointWithoutParams}`,
`${GaLabels.SUCCESS}${additionalEventData}`, `${GaLabels.SUCCESS}${additionalEventData}`,
true true
); );

View file

@ -296,7 +296,7 @@ export function doesCopyContainTextLink(copy) {
* @returns array of strings * @returns array of strings
*/ */
export function splitCopyOnCMSPlaceHolder(copy) { export function splitCopyOnCMSPlaceHolder(copy) {
return copy.split(/{(.*?)}/g); return copy.split(/{(.*?)}/g).filter((str) => str.length > 0);
} }
/** /**

View file

@ -27,7 +27,7 @@
v-model="customerModel.emailAddress" v-model="customerModel.emailAddress"
ref="emailAddress" ref="emailAddress"
customInputId="emailAddress" customInputId="emailAddress"
validationRules="email-address-format" /> validationRules="email-address-required|email-address-format" />
</div> </div>
</div> </div>
<div class="row mb-0"> <div class="row mb-0">
@ -49,6 +49,7 @@ import textBlock from "@/digital-components/text-block/text-block";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED)); defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
defineRule("last-name-required", required(errorMessages.LAST_NAME_REQUIRED)); defineRule("last-name-required", required(errorMessages.LAST_NAME_REQUIRED));
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
defineRule( defineRule(
"email-address-format", "email-address-format",
regex( regex(

View file

@ -151,6 +151,7 @@ export default {
if ( if (
store.getters.order.vehicle.carId && store.getters.order.vehicle.carId &&
store.getters.order.serviceLocation.zipCode && store.getters.order.serviceLocation.zipCode &&
store.getters.order.customer.emailAddress &&
store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES) store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES)
) { ) {
return true; return true;

View file

@ -44,8 +44,9 @@
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"
v-model="emailAddress" v-model="emailAddress"
inputId="emailAddress" inputId="emailAddress"
isRequired
disableAutoFill disableAutoFill
validationRules="email-address-format" /> validationRules="email-address-required|email-address-format" />
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
@ -117,6 +118,7 @@ import { queryStrings } from "@/constants/query-strings";
// Define Validation Rules // Define Validation Rules
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT)); defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
defineRule( defineRule(
"email-address-format", "email-address-format",
regex( regex(

View file

@ -33,7 +33,7 @@
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"
v-model="email" v-model="email"
customInputId="email" customInputId="email"
validationRules="email-address-format" /> validationRules="email-address-required|email-address-format" />
</div> </div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
@ -115,6 +115,7 @@ import vinPagesMixin from "@/mixins/vin-pages-mixin";
defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED)); defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED));
defineRule("registration-zip-required", required(errorMessages.REGISTRATION_ZIP_REQUIRED)); defineRule("registration-zip-required", required(errorMessages.REGISTRATION_ZIP_REQUIRED));
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT)); defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
defineRule( defineRule(
"email-address-format", "email-address-format",
regex( regex(

View file

@ -18,3 +18,30 @@ export function calcDaysBetweenDates(dateString1, dateString2) {
const timeDifference = Math.abs(date2 - date1); // Calculate the time difference in milliseconds const timeDifference = Math.abs(date2 - date1); // Calculate the time difference in milliseconds
return Math.ceil(timeDifference / (1000 * 60 * 60 * 24)); // Convert milliseconds to days return Math.ceil(timeDifference / (1000 * 60 * 60 * 24)); // Convert milliseconds to days
} }
export function convertDateToDateString(date) {
// returns YYYY-MM-DD format
if (date instanceof Date !== true) return;
return (
date.getFullYear() +
"-" +
("0" + (date.getMonth() + 1)).slice(-2) +
"-" +
("0" + date.getDate()).slice(-2)
);
}
export function convertDateStringToDate(dateString) {
// dateString must be YYYY-MM-DD format
if (typeof dateString !== "string") return;
const dateParts = dateString.split("-");
return new Date(dateParts[0], parseInt(dateParts[1]) - 1, dateParts[2]);
}
export function sumDateString(dateString, daysToAdd) {
// dateString must be YYYY-MM-DD format
if (typeof dateString !== "string") return;
const date = convertDateStringToDate(dateString);
date.setDate(date.getDate() + daysToAdd);
return convertDateToDateString(date);
}

View file

@ -8,7 +8,7 @@
<textBlock <textBlock
cmsWidgetName="ChangeShopLink" cmsWidgetName="ChangeShopLink"
justifyText="center" justifyText="center"
class="mb-3" class="mb-3 text-link-small"
marginTopSizeOverride="1" /> marginTopSizeOverride="1" />
</template> </template>
<location-alerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" /> <location-alerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
@ -16,6 +16,7 @@
selectableDatesSetting="custom" selectableDatesSetting="custom"
ref="datePicker" ref="datePicker"
v-model="selectedDate" v-model="selectedDate"
class="text-link-small"
:customSelectableDatesCallback="getAvailableDatesMethod" :customSelectableDatesCallback="getAvailableDatesMethod"
@date-clicked="openInshopTimeSlotsModal" /> @date-clicked="openInshopTimeSlotsModal" />
<time-slot-modal-question <time-slot-modal-question
@ -63,7 +64,11 @@ import { storeActions } from "@/constants/store-actions";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { splitCopyOnCMSPlaceHolder } from "@/helpers/cms-content-helper"; import { splitCopyOnCMSPlaceHolder } from "@/helpers/cms-content-helper";
import { calcDaysBetweenDates } from "@/layouts/schedule/helpers/schedule-helper"; import {
calcDaysBetweenDates,
convertDateStringToDate,
sumDateString,
} from "@/layouts/schedule/helpers/schedule-helper";
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants"; import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
@ -81,46 +86,41 @@ const getAvailableDates = async (
appointmentType, appointmentType,
providerNumber providerNumber
) => { ) => {
const apiEndDateLimit = new Date(startDateString + "T00:00:00"); const apiEndDateLimit = sumDateString(startDateString, TIME_SLOTS_CALL_DAYS_LIMIT);
const endDate = new Date(endDateString + "T00:00:00");
apiEndDateLimit.setDate(apiEndDateLimit.getDate() + TIME_SLOTS_CALL_DAYS_LIMIT);
// how many days are between startDate and endDate?
const difference = calcDaysBetweenDates(startDateString, endDateString); const difference = calcDaysBetweenDates(startDateString, endDateString);
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT); const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
const storeActionConfigs = []; const storeActionConfigs = [];
const timeSlotsData = {}; const timeSlotsData = {};
let apiStartDate = new Date(startDateString + "T00:00:00");
let apiEndDate = apiEndDateLimit;
timeSlotsData.days = []; timeSlotsData.days = [];
let apiStartDate = startDateString;
let apiEndDate = apiEndDateLimit;
for (let i = 1; i <= apiCallsCount; i++) { for (let i = 1; i <= apiCallsCount; i++) {
let storeActionConfig; let storeActionConfig;
if (i > 1) { if (i > 1) {
apiStartDate = new Date(apiEndDate); apiStartDate = sumDateString(apiEndDate, 1);
apiStartDate.setDate(apiStartDate.getDate() + 1); apiEndDate = sumDateString(apiStartDate, TIME_SLOTS_CALL_DAYS_LIMIT);
apiEndDate = new Date(apiStartDate);
apiEndDate.setDate(apiEndDate.getDate() + TIME_SLOTS_CALL_DAYS_LIMIT); if (i === apiCallsCount) {
} apiEndDate = endDateString;
if (i === apiCallsCount) { }
apiEndDate = new Date(endDateString + "T00:00:00");
} }
if (appointmentType === AppointmentTypeStrings.MOBILE) { if (appointmentType === AppointmentTypeStrings.MOBILE) {
storeActionConfig = { storeActionConfig = {
storeAction: storeActions.GET_MOBILE_TIME_SLOTS, storeAction: storeActions.GET_MOBILE_TIME_SLOTS,
payload: { payload: {
startDate: apiStartDate.toISOString().split("T")[0], startDate: apiStartDate,
endDate: apiEndDate.toISOString().split("T")[0], endDate: apiEndDate,
}, },
}; };
} else { } else {
storeActionConfig = { storeActionConfig = {
storeAction: storeActions.GET_SHOP_TIME_SLOTS, storeAction: storeActions.GET_SHOP_TIME_SLOTS,
payload: { payload: {
startDate: apiStartDate.toISOString().split("T")[0], startDate: apiStartDate,
endDate: apiEndDate.toISOString().split("T")[0], endDate: apiEndDate,
shopAppointmentType: appointmentType, shopAppointmentType: appointmentType,
providerNumber: providerNumber, providerNumber: providerNumber,
}, },
@ -381,12 +381,11 @@ export default {
); );
} }
} }
this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText); this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText);
}, },
convertSelectedDateToShortMonthAndDay(selectedDate) { convertSelectedDateToShortMonthAndDay(selectedDate) {
// This conversion ensures we don't get get GMT induced date changes // This conversion ensures we don't get get GMT induced date changes
const dateObject = new Date(`${selectedDate}T00:00:00`); const dateObject = convertDateStringToDate(selectedDate);
// Ex: April 25 // Ex: April 25
return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" }); return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" });
}, },
@ -493,3 +492,18 @@ export default {
}, },
}; };
</script> </script>
<style lang="scss">
.text-link-small {
a,
.btn-link {
font-size: 0.875rem;
line-height: 1.5;
}
}
.funnel-sub-header {
h5.dark-header {
margin-bottom: 0.25rem;
}
}
</style>

View file

@ -41,6 +41,7 @@
import modal from "@/digital-components/modal/modal"; import modal from "@/digital-components/modal/modal";
import textBlock from "@/digital-components/text-block/text-block"; import textBlock from "@/digital-components/text-block/text-block";
import buttonQuestion from "@/digital-components/button-question/button-question"; import buttonQuestion from "@/digital-components/button-question/button-question";
import { convertDateStringToDate } from "@/layouts/schedule/helpers/schedule-helper";
import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button"; import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button";
// TODO: Move this somewhere more global // TODO: Move this somewhere more global
@ -55,6 +56,13 @@ import {
PREMIUM_FEE_PART_TYPE, PREMIUM_FEE_PART_TYPE,
RouteCodeFlags, RouteCodeFlags,
} from "@/constants/schedule-constants"; } from "@/constants/schedule-constants";
const cmsWidgetFieldMappings = {
MODAL_CLOSE_BUTTON: "FooterText",
SUPPLEMENTAL_INFORMATION: "BodyText",
TIME_SLOT_BUTTON: "HeaderText",
DISCLAIMER: "FooterText",
DURATION: "SubheaderText",
};
// Validation for the modal button // Validation for the modal button
defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED)); defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
@ -124,32 +132,64 @@ export default {
); );
} }
} }
return this.getCmsContent(appointmentTypeCmsWidgetName, "BodyText"); return this.getCmsContent(
appointmentTypeCmsWidgetName,
cmsWidgetFieldMappings.SUPPLEMENTAL_INFORMATION
);
}, },
footerCloseButtonText() { footerCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText"); return this.getCmsContent(
this.cmsWidgetName,
cmsWidgetFieldMappings.MODAL_CLOSE_BUTTON
);
}, },
premiumAppointmentButtonText() { premiumAppointmentButtonText() {
return this.getCmsContent(this.mobilePremiumCmsWidgetName, "HeaderText"); return this.getCmsContent(
this.mobilePremiumCmsWidgetName,
cmsWidgetFieldMappings.TIME_SLOT_BUTTON
);
}, },
dropoffButtonText() { dropoffButtonText() {
return this.getCmsContent(this.dropoffCmsWidgetName, "HeaderText"); return this.getCmsContent(
this.dropoffCmsWidgetName,
cmsWidgetFieldMappings.TIME_SLOT_BUTTON
);
},
sameDayDropoffButtonText() {
return this.getCmsContent(
this.sameDayDropOffCmsWidgetName,
cmsWidgetFieldMappings.TIME_SLOT_BUTTON
);
}, },
overnightDropoffButtonText() { overnightDropoffButtonText() {
return this.getCmsContent(this.overnightDropOffCmsWidgetName, "HeaderText"); return this.getCmsContent(
this.overnightDropOffCmsWidgetName,
cmsWidgetFieldMappings.TIME_SLOT_BUTTON
);
}, },
dropoffDisclaimerText() { dropoffDisclaimerText() {
return this.getCmsContent(this.dropoffCmsWidgetName, "FooterText"); return this.getCmsContent(this.dropoffCmsWidgetName, cmsWidgetFieldMappings.DISCLAIMER);
},
sameDayDropOffDisclaimerText() {
return this.getCmsContent(
this.sameDayDropOffCmsWidgetName,
cmsWidgetFieldMappings.DISCLAIMER
);
}, },
overnightDropOffDisclaimerText() { overnightDropOffDisclaimerText() {
return this.getCmsContent(this.overnightDropOffCmsWidgetName, "FooterText"); return this.getCmsContent(
this.overnightDropOffCmsWidgetName,
cmsWidgetFieldMappings.DISCLAIMER
);
}, },
disclaimerTextBlockCopy() { disclaimerTextBlockCopy() {
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) { if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
if (this.isSameDay) { if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
return null; if (this.isSameDay) {
} else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) { return this.sameDayDropOffDisclaimerText;
return this.dropoffDisclaimerText; } else {
return this.dropoffDisclaimerText;
}
} else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { } else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
return this.overnightDropOffDisclaimerText; return this.overnightDropOffDisclaimerText;
} else { } else {
@ -160,15 +200,24 @@ export default {
} }
}, },
dropOffDurationText() { dropOffDurationText() {
return this.getCmsContent(this.dropoffCmsWidgetName, "SubheaderText"); return this.getCmsContent(this.dropoffCmsWidgetName, cmsWidgetFieldMappings.DURATION);
},
sameDayDropoffDurationText() {
return this.getCmsContent(
this.sameDayDropOffCmsWidgetName,
cmsWidgetFieldMappings.DURATION
);
}, },
overnightDropoffDurationText() { overnightDropoffDurationText() {
return this.getCmsContent(this.overnightDropOffCmsWidgetName, "SubheaderText"); return this.getCmsContent(
this.overnightDropOffCmsWidgetName,
cmsWidgetFieldMappings.DURATION
);
}, },
inshopDurationText() { inshopDurationText() {
const inshopDurationTextWithoutTime = this.getCmsContent( const inshopDurationTextWithoutTime = this.getCmsContent(
this.cmsWidgetName, this.cmsWidgetName,
"SubheaderText" cmsWidgetFieldMappings.DURATION
); );
const inshopDurationTime = this.getDisplayTextForDurationLength( const inshopDurationTime = this.getDisplayTextForDurationLength(
this.estimatedServiceMinutesMinimum, this.estimatedServiceMinutesMinimum,
@ -185,15 +234,16 @@ export default {
if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) { if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
return this.overnightDropoffDurationText; return this.overnightDropoffDurationText;
} else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) { } else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
return this.dropOffDurationText; if (this.isSameDay) {
return this.sameDayDropoffDurationText;
} else {
return this.dropOffDurationText;
}
} else { } else {
return null; return null;
} }
} }
}, },
shouldShowDropoffDisclaimerText() {
return this.appointmentType === AppointmentTypeStrings.DROP_OFF && !this.isSameDay;
},
isSameDay() { isSameDay() {
if (!this.dateAndTimeSlotData) { if (!this.dateAndTimeSlotData) {
return false; return false;
@ -202,14 +252,13 @@ export default {
const todaysDate = new Date().toISOString().split("T")[0]; const todaysDate = new Date().toISOString().split("T")[0];
return selectedDate === todaysDate; return selectedDate === todaysDate;
}, },
dateSelectedReadableDate() { dateSelectedReadableDate() {
if (!this.dateAndTimeSlotData) { if (!this.dateAndTimeSlotData) {
return null; return null;
} }
// This conversion ensures we don't get get GMT induced date changes // This conversion ensures we don't get get GMT induced date changes
const dateObject = new Date(`${this.dateAndTimeSlotData.date}T00:00:00`); const dateObject = convertDateStringToDate(this.dateAndTimeSlotData.date);
// Ex: Tuesday, April 22 // Ex: Tuesday, April 22
return dateObject.toLocaleDateString("en-us", { return dateObject.toLocaleDateString("en-us", {
weekday: "long", weekday: "long",
@ -303,9 +352,14 @@ export default {
}, },
getAvailableTimeSlotsForDropOff(timeSlotsForSelectedDate) { getAvailableTimeSlotsForDropOff(timeSlotsForSelectedDate) {
const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => { const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => {
const buttonLabelValue = timeSlot.id.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF) let buttonLabelValue;
? this.overnightDropoffButtonText if (timeSlot.id.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
: this.dropoffButtonText; buttonLabelValue = this.overnightDropoffButtonText;
} else if (this.isSameDay) {
buttonLabelValue = this.sameDayDropoffButtonText;
} else {
buttonLabelValue = this.dropoffButtonText;
}
return { return {
value: timeSlot.id, value: timeSlot.id,
buttonLabel: buttonLabelValue, buttonLabel: buttonLabelValue,

View file

@ -1,6 +1,13 @@
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import appointmentTypeQuestion from "./appointment-type-question"; import appointmentTypeQuestion from "./appointment-type-question";
import store from "@/store";
store.getters = {
damage: {
isRepair: false,
},
};
const mockCmsContent = { const mockCmsContent = {
QuestionText: "Choose a service option:", QuestionText: "Choose a service option:",
@ -42,6 +49,15 @@ const mockMixin = {
}, },
}; };
afterEach(() => {
// reset store after each test
store.getters = {
damage: {
isRepair: false,
},
};
});
describe("appointment-type-question.vue", () => { describe("appointment-type-question.vue", () => {
it("Should display all options if both in-shop and mobile are available", async () => { it("Should display all options if both in-shop and mobile are available", async () => {
// Arrange/Act // Arrange/Act
@ -141,6 +157,45 @@ describe("appointment-type-question.vue", () => {
}, },
]); ]);
}); });
it("Should not display Drop off answer when it is repair order", async () => {
// Arrange/Act
store.getters = {
damage: {
isRepair: true,
},
};
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
cmsWidgetName: cmsWidgetName,
isServiceableInshop: true,
isServiceableMobile: true,
},
mountOptions: {
attachTo: document.body,
},
});
// Assert
expect(wrapper.vm.answersToDisplay).toEqual([
{
AnswerImageUrl: "",
Name: "Mobile",
SubText: "",
SubWidgetName: "",
Text: "Mobile",
},
{
AnswerImageUrl: "",
Name: "Inshop",
SubText: "",
SubWidgetName: "",
Text: "In-shop",
},
]);
});
it("Should display no answers if neither in-shop nor mobile service are available", async () => { it("Should display no answers if neither in-shop nor mobile service are available", async () => {
// Arrange/Act // Arrange/Act
@ -169,6 +224,8 @@ function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) {
if (props) resultingMountOptions.propsData = props; if (props) resultingMountOptions.propsData = props;
resultingMountOptions.global.mocks["$store"] = store;
const wrapper = isShallowMount const wrapper = isShallowMount
? shallowMount(appointmentTypeQuestion, resultingMountOptions) ? shallowMount(appointmentTypeQuestion, resultingMountOptions)
: mount(appointmentTypeQuestion, resultingMountOptions); : mount(appointmentTypeQuestion, resultingMountOptions);

View file

@ -18,6 +18,7 @@
<script> <script>
import buttonQuestion from "@/digital-components/button-question/button-question"; import buttonQuestion from "@/digital-components/button-question/button-question";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
export default { export default {
name: "appointment-type-question", name: "appointment-type-question",
@ -40,20 +41,19 @@ export default {
return this.getCmsContent(this.cmsWidgetName, "Answers"); return this.getCmsContent(this.cmsWidgetName, "Answers");
}, },
answersToDisplay() { answersToDisplay() {
let filteredAnswers; const shouldShowMobile = this.isServiceableMobile;
if (this.isServiceableMobile && this.isServiceableInshop) { const shouldShowInshop = this.isServiceableInshop;
filteredAnswers = this.answersFromCms; const shouldShowDropoff =
} else if (this.isServiceableMobile) { this.isServiceableInshop && !this.$store.getters.damage.isRepair;
filteredAnswers = this.answersFromCms.filter((answer) => answer.Name == "Mobile"); return this.answersFromCms
} else if (this.isServiceableInshop) { ? this.answersFromCms.filter((answer) => {
filteredAnswers = this.answersFromCms.filter( return (
(answer) => answer.Name == "Inshop" || answer.Name == "Dropoff" (answer.Name == AppointmentTypeStrings.IN_SHOP && shouldShowInshop) ||
); (answer.Name == AppointmentTypeStrings.MOBILE && shouldShowMobile) ||
} else { (answer.Name == AppointmentTypeStrings.DROP_OFF && shouldShowDropoff)
filteredAnswers = []; );
} })
: [];
return filteredAnswers;
}, },
selectedValues: { selectedValues: {
get: function () { get: function () {

View file

@ -62,27 +62,10 @@ export async function getAvailabilityRating(
false false
); );
// Rate the availability for the shop
let numberOfAppointmentsPerDay = [];
for (let i = 0; i < shopTimeSlots.data.days.length; i++) {
numberOfAppointmentsPerDay.push(shopTimeSlots.data.days[i].timeSlots.length);
}
const dateRange = 7;
const minimumNumberOfAppointmentsPerDay = 1;
const numberOfDaysToEvaluate = 2; const numberOfDaysToEvaluate = 2;
const isGoodAvailability =
let daysWithMinimalAppointmentsCount = 0; shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length >=
for (let i = 0; i < dateRange; i++) { numberOfDaysToEvaluate;
if (numberOfAppointmentsPerDay[i] >= minimumNumberOfAppointmentsPerDay) {
daysWithMinimalAppointmentsCount++;
if (daysWithMinimalAppointmentsCount >= numberOfDaysToEvaluate) {
break;
}
}
}
const isGoodAvailability = daysWithMinimalAppointmentsCount >= numberOfDaysToEvaluate;
const shopStatus = isGoodAvailability ? "high" : "low"; const shopStatus = isGoodAvailability ? "high" : "low";

View file

@ -93,94 +93,54 @@ const mockStoreActionGetServiceabilityDetails = storeActions.GET_SERVICEABILITY_
const mockStoreActionGetShopTimeSlots = storeActions.GET_SHOP_TIME_SLOTS; const mockStoreActionGetShopTimeSlots = storeActions.GET_SHOP_TIME_SLOTS;
const mockGetShopTimeSlotsGoodAvailability = { const mockGetShopTimeSlotsGoodAvailability = {
estimatedServiceMinutesMinimum: 0, data: {
estimatedServiceMinutesMaximimum: 0, estimatedServiceMinutesMinimum: 0,
days: [ estimatedServiceMinutesMaximimum: 0,
{ days: [
date: "string", {
timeSlots: [ date: "string",
{ timeSlots: [
id: "string", {
startTime: "", id: "string",
endTime: "", startTime: "",
offerPremium: true, endTime: "",
}, offerPremium: true,
], },
}, ],
{ },
date: "string", {
timeSlots: [], date: "string",
}, timeSlots: [
{ {
date: "string", id: "string",
timeSlots: [], startTime: "",
}, endTime: "",
{ offerPremium: true,
date: "string", },
timeSlots: [], ],
}, },
{ ],
date: "string", },
timeSlots: [],
},
{
date: "string",
timeSlots: [],
},
{
date: "string",
timeSlots: [
{
id: "string",
startTime: "",
endTime: "",
offerPremium: true,
},
],
},
],
}; };
const mockGetShopTimeSlotsLowAvailability = { const mockGetShopTimeSlotsLowAvailability = {
estimatedServiceMinutesMinimum: 0, data: {
estimatedServiceMinutesMaximimum: 0, estimatedServiceMinutesMinimum: 0,
days: [ estimatedServiceMinutesMaximimum: 0,
{ days: [
date: "string", {
timeSlots: [], date: "string",
}, timeSlots: [
{ {
date: "string", id: "string",
timeSlots: [], startTime: "",
}, endTime: "",
{ offerPremium: true,
date: "string", },
timeSlots: [], ],
}, },
{ ],
date: "string", },
timeSlots: [],
},
{
date: "string",
timeSlots: [],
},
{
date: "string",
timeSlots: [],
},
{
date: "string",
timeSlots: [
{
id: "string",
startTime: "",
endTime: "",
offerPremium: true,
},
],
},
],
}; };
jest.mock("@/mixins/base-mixin.js", () => ({ jest.mock("@/mixins/base-mixin.js", () => ({
@ -311,23 +271,36 @@ describe("service-location-helper.js", () => {
}); });
describe("getAvailabilityRating", () => { describe("getAvailabilityRating", () => {
// it("Should return a 'Good' rating", async () => { it("Should return a 'high' rating", async () => {
// // Arrange // Arrange
// const providerNumber = "0000001"; const providerNumber = "0000001";
// const expected = "Good"; const expected = "high";
// // Act // Act
// const result = await getAvailabilityRating(providerNumber); const result = await getAvailabilityRating(
// // Assert "2023-06-30",
// expect(result).toEqual(expected); "2023-07-06",
// }); "Inshop",
// it("Should return a 'Low' rating", async () => { providerNumber
// // Arrange );
// const providerNumber = "0000000";
// const expected = "Low"; // Assert
// // Act expect(result).toEqual(expected);
// const result = await getAvailabilityRating(providerNumber); });
// // Assert
// expect(result).toEqual(expected); it("Should return a 'low' rating", async () => {
// }); // Arrange
const providerNumber = "0000000";
const expected = "low";
// Act
const result = await getAvailabilityRating(
"2023-06-30",
"2023-07-06",
"Inshop",
providerNumber
);
// Assert
expect(result).toEqual(expected);
});
}); });
}); });

View file

@ -26,6 +26,7 @@
badgeText badgeText
}}</span> }}</span>
<loader v-if="isLoaderDisplayed" :class="['left', 'no-block']" /> <loader v-if="isLoaderDisplayed" :class="['left', 'no-block']" />
<!-- no-block allows the user to click between shops while availability indicators are loading -->
</div> </div>
</div> </div>
<div class="row-two"> <div class="row-two">

View file

@ -108,7 +108,7 @@ export default {
additionalButtonData() { additionalButtonData() {
const startDate = new Date(); const startDate = new Date();
const endDate = new Date(); const endDate = new Date();
endDate.setDate(startDate.getDate() + 7); endDate.setDate(startDate.getDate() + 6);
const formattedStartDate = startDate.toISOString().split("T")[0]; const formattedStartDate = startDate.toISOString().split("T")[0];
const formattedEndDate = endDate.toISOString().split("T")[0]; const formattedEndDate = endDate.toISOString().split("T")[0];

View file

@ -59,7 +59,7 @@
v-model="emailAddress" v-model="emailAddress"
customInputId="emailAddress" customInputId="emailAddress"
isRequired isRequired
validationRules="email-address-format" /> validationRules="email-address-required|email-address-format" />
</div> </div>
</div> </div>
<div class="row mb-0"> <div class="row mb-0">
@ -79,7 +79,7 @@
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader" :manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody" :manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
v-model="customAlertData" v-model="customAlertData"
v-if="vinPopulatedOnPageLoad && isInsuranceVerified" v-if="vinPopulatedOnPageLoad && isInsuranceVerified && !displayInvalidZipAlert"
alertClass="alert-success" /> alertClass="alert-success" />
<alert <alert
class="my-4" class="my-4"
@ -106,7 +106,7 @@
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader" :manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody" :manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
v-model="customAlertData" v-model="customAlertData"
v-if="vinPopulatedOnPageLoad && !isInsuranceVerified" v-if="vinPopulatedOnPageLoad && !isInsuranceVerified && !displayInvalidZipAlert"
alertClass="alert-success" /> alertClass="alert-success" />
<funnelFooter <funnelFooter
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
@ -152,6 +152,7 @@ import vinPagesMixin from "@/mixins/vin-pages-mixin";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT)); defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
defineRule( defineRule(
"email-address-format", "email-address-format",
regex( regex(

View file

@ -176,6 +176,18 @@ export default {
getSessionIdValue() === "00000000-0000-0000-0000-000000000000" getSessionIdValue() === "00000000-0000-0000-0000-000000000000"
); );
}, },
removeParamsFromEndpoint(endpoint) {
const endpointWithoutParams = endpoint.split("?")[0];
const numSlashesBeforeParams = 6;
let splitString = endpointWithoutParams.split("/");
if (splitString.length > numSlashesBeforeParams) {
splitString = splitString.slice(0, numSlashesBeforeParams);
return splitString.join("/");
} else {
return endpointWithoutParams;
}
},
}, },
computed: { computed: {
analyticsPageEvents() { analyticsPageEvents() {

View file

@ -533,6 +533,9 @@ export const getters = {
); );
return !!nonWindshieldItems?.length; return !!nonWindshieldItems?.length;
}, },
isMobileAppointment: (state) => {
return state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
},
lineItems: (state) => state.order.lineItems, lineItems: (state) => state.order.lineItems,
pageData: (state) => (page) => { pageData: (state) => (page) => {
return state.applicationUser.pageData[page]; return state.applicationUser.pageData[page];
@ -606,6 +609,10 @@ function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
return (array ?? []).map((x) => x[propertyName]).filter((x) => x); return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
} }
function provisionalTriggersToString(provisionalTriggers) {
return "ProvisionalTriggers:" + provisionalTriggers.join(",");
}
// Export Actions // Export Actions
export const actions = { export const actions = {
// Vehicle API Actions // Vehicle API Actions
@ -1230,13 +1237,16 @@ export const actions = {
const order = context.state.order; const order = context.state.order;
const vehicle = context.state.order.vehicle; const vehicle = context.state.order.vehicle;
let partNumbers = [ let lineItems = [
...(order.lineItems.supportingItems ?? []), ...(order.lineItems.supportingItems ?? []),
...(order.lineItems.vaps ?? []), ...(order.lineItems.vaps ?? []),
...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts), ...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts),
]; ];
partNumbers = partNumbers.map((lineItem) => { lineItems = lineItems.map((lineItem) => {
return lineItem.partNumber; return {
partNumber: lineItem.partNumber,
partType: lineItem.partType,
};
}); });
const glassPieces = order.damage.glassToReplace const glassPieces = order.damage.glassToReplace
? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace) ? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace)
@ -1249,7 +1259,7 @@ export const actions = {
applicationName: applicationConfig.APPLICATION_NAME, applicationName: applicationConfig.APPLICATION_NAME,
parentAccountNumber: context.getters.payment.parentAccountNumber, parentAccountNumber: context.getters.payment.parentAccountNumber,
carId: vehicle.carId, carId: vehicle.carId,
partNumbers: partNumbers, lineItems: lineItems,
glassPieces: glassPieces, glassPieces: glassPieces,
eon: order.eon, eon: order.eon,
coverage: { coverage: {
@ -1258,11 +1268,12 @@ export const actions = {
additionalAuthFlag: "", additionalAuthFlag: "",
}, },
partSelection: { partSelection: {
// TODO: Provisional booking will utilize these fields hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
hasAnsweredPartQuestions: false, hasAnsweredMoldingQuestions: !!order.damage.moldingQuestionAnswers?.length,
hasAnsweredMoldingQuestions: false, hasAnsweredCapabilityQuestions: !!order.damage.capabilityQuestionAnswers?.length,
hasAnsweredCapabilityQuestions: false, hasManuallySelectedParts:
hasManuallySelectedParts: false, !!context.state.applicationUser.pageData["vehicle-parts"]?.partsOrQuestions
.length,
}, },
vehicle: { vehicle: {
year: vehicle.year, year: vehicle.year,
@ -1277,19 +1288,24 @@ export const actions = {
method: endpoints.GetShopTimeSlots.method, method: endpoints.GetShopTimeSlots.method,
endpoint: endpoints.GetShopTimeSlots.url, endpoint: endpoints.GetShopTimeSlots.url,
payload: payload, payload: payload,
additionalSuccessEventDataHandler: (response) =>
provisionalTriggersToString(response.data.provisionalTriggers),
}); });
}, },
getMobileTimeSlots(context, { startDate, endDate }) { getMobileTimeSlots(context, { startDate, endDate }) {
const order = context.state.order; const order = context.state.order;
const vehicle = context.state.order.vehicle; const vehicle = context.state.order.vehicle;
let partNumbers = [ let lineItems = [
...(order.lineItems.supportingItems ?? []), ...(order.lineItems.supportingItems ?? []),
...(order.lineItems.vaps ?? []), ...(order.lineItems.vaps ?? []),
...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts), ...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts),
]; ];
partNumbers = partNumbers.map((lineItem) => { lineItems = lineItems.map((lineItem) => {
return lineItem.partNumber; return {
partNumber: lineItem.partNumber,
partType: lineItem.partType,
};
}); });
const glassPieces = order.damage.glassToReplace const glassPieces = order.damage.glassToReplace
? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace) ? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace)
@ -1300,7 +1316,7 @@ export const actions = {
applicationName: applicationConfig.APPLICATION_NAME, applicationName: applicationConfig.APPLICATION_NAME,
parentAccountNumber: context.getters.payment.parentAccountNumber, parentAccountNumber: context.getters.payment.parentAccountNumber,
carId: vehicle.carId, carId: vehicle.carId,
partNumbers: partNumbers, lineItems: lineItems,
glassPieces: glassPieces, glassPieces: glassPieces,
eon: order.eon, eon: order.eon,
coverage: { coverage: {
@ -1309,11 +1325,12 @@ export const actions = {
additionalAuthFlag: "", additionalAuthFlag: "",
}, },
partSelection: { partSelection: {
// TODO: Provisional booking will utilize these fields hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length,
hasAnsweredPartQuestions: false, hasAnsweredMoldingQuestions: !!order.damage.moldingQuestionAnswers?.length,
hasAnsweredMoldingQuestions: false, hasAnsweredCapabilityQuestions: !!order.damage.capabilityQuestionAnswers?.length,
hasAnsweredCapabilityQuestions: false, hasManuallySelectedParts:
hasManuallySelectedParts: false, !!context.state.applicationUser.pageData["vehicle-parts"]?.partsOrQuestions
.length,
}, },
vehicle: { vehicle: {
year: vehicle.year, year: vehicle.year,
@ -1328,6 +1345,8 @@ export const actions = {
method: endpoints.GetMobileTimeSlots.method, method: endpoints.GetMobileTimeSlots.method,
endpoint: endpoints.GetMobileTimeSlots.url, endpoint: endpoints.GetMobileTimeSlots.url,
payload: payload, payload: payload,
additionalSuccessEventDataHandler: (response) =>
provisionalTriggersToString(response.data.provisionalTriggers),
}); });
}, },
@ -1382,7 +1401,7 @@ export const actions = {
}, },
}, },
customer: { customer: {
emailAddress: order.customer.emailAddress || null, emailAddress: order.customer.emailAddress,
}, },
damage: { damage: {
numberOfChips: damage.numberOfChips, numberOfChips: damage.numberOfChips,
@ -1951,7 +1970,7 @@ export const actions = {
}, },
saveEmail(context, email) { saveEmail(context, email) {
context.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, email); context.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, email === "" ? null : email);
}, },
saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) { saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) {
@ -2190,6 +2209,7 @@ async function resetScheduleIfUnavailable(context, order) {
// If you put any kind of time stamp on the date string with dashes, then it IS parsed as local time. // If you put any kind of time stamp on the date string with dashes, then it IS parsed as local time.
var aptDate = new Date(order.schedule.date + "T00:00:00"); var aptDate = new Date(order.schedule.date + "T00:00:00");
var curDate = new Date(); var curDate = new Date();
curDate.setHours(0, 0, 0, 0);
// if appointment date is in the past, clear schedule // if appointment date is in the past, clear schedule
if (aptDate.getTime() < curDate.getTime()) { if (aptDate.getTime() < curDate.getTime()) {

View file

@ -4,6 +4,7 @@ import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { experimentTriggers } from "@/constants/experiments"; import { experimentTriggers } from "@/constants/experiments";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
// Mock global method // Mock global method
globalMethods.callHttpClient = jest.fn(); globalMethods.callHttpClient = jest.fn();
@ -2869,6 +2870,45 @@ describe("Getters", () => {
expect(getters.payment(storeState).insuranceCoverage.isVerified).toEqual(true); expect(getters.payment(storeState).insuranceCoverage.isVerified).toEqual(true);
}); });
describe("isMobileAppointment", () => {
it("Should return true for mobile appointments", () => {
// Arrange
const storeState = state;
// Act
mutations.updateServiceLocation(storeState, {
appointmentType: AppointmentTypeStrings.MOBILE,
});
// Assert
expect(getters.isMobileAppointment(storeState)).toBe(true);
});
it("Should return false for non-mobile appointments", () => {
// Arrange
const storeState = state;
// Act
mutations.updateServiceLocation(storeState, {
appointmentType: AppointmentTypeStrings.IN_SHOP,
});
// Assert
expect(getters.isMobileAppointment(storeState)).toBe(false);
});
it("Should return false for null appointments", () => {
// Arrange
const storeState = state;
// Act
mutations.updateServiceLocation(storeState, { appointmentType: null });
// Assert
expect(getters.isMobileAppointment(storeState)).toBe(false);
});
});
describe("experimentOrder", () => { describe("experimentOrder", () => {
test("glassToReplace, glassParts, and otherParts are null > return correct experimentOrder values", () => { test("glassToReplace, glassParts, and otherParts are null > return correct experimentOrder values", () => {
// Arrange // Arrange

View file

@ -100,7 +100,7 @@ export default {
color: $white; color: $white;
background: $blue-700; background: $blue-700;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700; box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
pointer-events: none; pointer-events: auto;
} }
&.delay { &.delay {
// fixes flicker while transitioning between states // fixes flicker while transitioning between states

View file

@ -3,7 +3,7 @@
class="loader" class="loader"
role="alert" role="alert"
aria-label="Loading new page" aria-label="Loading new page"
v-bind:class="[this.loaderColor, this.loaderPosition, this.blockUi]"></div> v-bind:class="[this.loaderColor, this.loaderPosition]"></div>
</template> </template>
<script> <script>
@ -19,11 +19,6 @@ export default {
loaderPosition: { loaderPosition: {
type: String, type: String,
}, },
/* Controls whether we block the UI when the loader is active or now, defaults to true*/
blockUi: {
type: Boolean,
default: true,
},
}, },
}; };
</script> </script>
@ -31,6 +26,7 @@ export default {
<style lang="scss"> <style lang="scss">
.loader { .loader {
display: flex; display: flex;
pointer-events: all;
//Open an overlay to prevent page interaction //Open an overlay to prevent page interaction
&:before { &:before {
@ -87,7 +83,7 @@ export default {
&.black:after { &.black:after {
background-color: $black; background-color: $black;
} }
//no-block to enable clicking on certain buttons with loaders while the loader is actives
&.no-block::before { &.no-block::before {
z-index: -1; z-index: -1;
} }