Merge pull request #2426 from Safelite/CASH-499-calendar-ui-updates

Cash 499 calendar UI updates
This commit is contained in:
bmauger 2025-04-16 09:30:09 -04:00 committed by GitHub
commit f06da57711
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 184 additions and 1259 deletions

View file

@ -1,5 +1,7 @@
<template> <template>
<div class="date-picker text-center" :class="calendarViewDirection"> <div
class="date-picker text-center"
:class="`${calendarViewDirection} ${isPricingByDayClass} ${showPricingByDayClass}`">
<fieldset id="date-picker-fieldset" ref="datePickerFieldset"> <fieldset id="date-picker-fieldset" ref="datePickerFieldset">
<legend class="sr-only">Select a day and time</legend> <legend class="sr-only">Select a day and time</legend>
<div <div
@ -60,18 +62,24 @@
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '', date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
date.dayClasses, date.dayClasses,
date.isSelectable ? 'selectable-day' : '', date.isSelectable ? 'selectable-day' : '',
date.isPricingByDayUpchargeDay ? 'upch-day' : '',
]"> ]">
<input <input
:disabled="!date.isSelectable" :disabled="!date.isSelectable"
type="radio" type="radio"
name="day-of-month" name="day-of-month"
v-model="selectedDate" v-model="selectedDate"
@click="fireDateSelectedEvent" @click="fireDateSelectedEvent($event, date)"
@keypress.enter="fireDateSelectedEvent" @keypress.enter="fireDateSelectedEvent($event, date)"
:value="date.inputValue" :value="date.inputValue"
:id="`${month.monthLabel}-${date.dateNum.toString()}`" /> :id="`${month.monthLabel}-${date.dateNum.toString()}`" />
<label :for="`${month.monthLabel}-${date.dateNum.toString()}`"> <label :for="`${month.monthLabel}-${date.dateNum.toString()}`">
<span>{{ date.dateNum.toString() }}</span> <span>{{ date.dateNum.toString() }}</span>
<span
v-if="isPricingByDayExperiment && showPricingByDay && date.isSelectable"
class="price">
{{ date.priceString }}
</span>
</label> </label>
</div> </div>
</div> </div>
@ -99,8 +107,17 @@
// Supporting files // Supporting files
import loader from "@/ux-components/loader/loader"; 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 {
import { selectableDaysOptions, requiredParameter, forceTwoDigitString } from "./mixins/helpers"; TIMINGFUNC_MAP,
BUFFER_OFFSET,
MONTHS_OF_YEAR,
DAYS_OF_WEEK,
} from "@/digital-components/date-picker/mixins/constants";
import {
selectableDaysOptions,
requiredParameter,
forceTwoDigitString,
} from "@/digital-components/date-picker/mixins/helpers";
import { import {
convertDateToDateString, convertDateToDateString,
convertDateStringToDate, convertDateStringToDate,
@ -148,6 +165,10 @@ export default {
type: String, type: String,
default: "", default: "",
}, },
showPricingByDay: Boolean,
pricingByDayBasePrice: Number,
pricingByDayUpcharge: Number,
isPricingByDayExperiment: Boolean,
}, },
setup(props) { setup(props) {
const uuid = uuidv4(); const uuid = uuidv4();
@ -199,6 +220,12 @@ export default {
if (this.selectableDatesSetting === "custom") return "future"; if (this.selectableDatesSetting === "custom") return "future";
return "past"; return "past";
}, },
isPricingByDayClass() {
return this.isPricingByDayExperiment ? "pricing-by-day" : "";
},
showPricingByDayClass() {
return this.showPricingByDay ? "show-pricing-by-day" : "";
},
selectedDate: { selectedDate: {
get() { get() {
return this.modelValue; return this.modelValue;
@ -212,12 +239,12 @@ export default {
initializeComponent(initialData) { initializeComponent(initialData) {
this.setCalendarData(initialData); this.setCalendarData(initialData);
}, },
fireDateSelectedEvent(event) { fireDateSelectedEvent(event, date) {
// Ignore if arrow key selected radioButton // Ignore if arrow key selected radioButton
if (event.screenX === 0 && event.screenY === 0) { if (event.screenX === 0 && event.screenY === 0) {
return; return;
} }
this.$emit("date-clicked"); this.$emit("date-clicked", date);
}, },
getWeekStartDate(dateString) { getWeekStartDate(dateString) {
const date = convertDateStringToDate(dateString); const date = convertDateStringToDate(dateString);
@ -440,6 +467,8 @@ export default {
initialViewEndDate: config.initialViewEndDate, initialViewEndDate: config.initialViewEndDate,
hideSecondMonth: hideSecondMonth, hideSecondMonth: hideSecondMonth,
preSelectedDate: config.preSelectedDate, preSelectedDate: config.preSelectedDate,
pricingByDayBasePrice: config.pricingByDayBasePrice,
pricingByDayUpcharge: config.pricingByDayUpcharge,
}; };
if (direction === "future") { if (direction === "future") {
// first 0, then 1 // first 0, then 1
@ -573,6 +602,17 @@ export default {
("0" + monthNum).slice(-2) + ("0" + monthNum).slice(-2) +
"-" + "-" +
("0" + i).slice(-2); ("0" + i).slice(-2);
const dayIndex = convertDateStringToDate(dateString).getDay();
const dayObject = DAYS_OF_WEEK[dayIndex];
const isSelectable =
this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
? true
: false;
const isPricingByDayUpchargeDay = dayObject.isPricingByDayUpchargeDay;
const displayPrice = isPricingByDayUpchargeDay
? options.pricingByDayBasePrice + options.pricingByDayUpcharge
: options.pricingByDayBasePrice;
const priceString = "$" + displayPrice;
if (offset === 0 && i === this.todayDateNum) { if (offset === 0 && i === this.todayDateNum) {
dayClasses += " current-day"; dayClasses += " current-day";
@ -583,8 +623,8 @@ export default {
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) { if (dayIndex === 0) {
dayClasses += " sunday"; dayClasses += " " + dayObject.cssClass;
} }
if ( if (
this.hideSomeDaysForInitialView && this.hideSomeDaysForInitialView &&
@ -598,10 +638,9 @@ export default {
dateNum: i, dateNum: i,
dayClasses: dayClasses, dayClasses: dayClasses,
inputValue: dateString, inputValue: dateString,
isSelectable: priceString: priceString,
this.selectableDatesData.findIndex((date) => date.date === dateString) > -1 isSelectable: isSelectable,
? true isPricingByDayUpchargeDay: isPricingByDayUpchargeDay,
: false,
}; };
dates.push(dateObject); dates.push(dateObject);
} }
@ -897,7 +936,6 @@ export default {
} }
} }
&:hover,
&:checked { &:checked {
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
box-shadow: 0 0 0 4px transparent; box-shadow: 0 0 0 4px transparent;
@ -924,9 +962,7 @@ export default {
&.selectable-day { &.selectable-day {
label { label {
color: $blue;
background-color: $blue-100; background-color: $blue-100;
border: 1px solid $blue;
min-width: 2.5rem; min-width: 2.5rem;
width: 2.5rem; width: 2.5rem;
border-radius: 50%; border-radius: 50%;
@ -934,12 +970,11 @@ export default {
} }
&.unavailable-day { &.unavailable-day {
label { label {
color: $gray-500;
background-color: $gray-100;
border: none; border: none;
pointer-events: none; pointer-events: none;
} }
} }
//NEEDED?
&.unavailable-day:not(&.sunday) { &.unavailable-day:not(&.sunday) {
label { label {
&::before { &::before {
@ -949,7 +984,6 @@ export default {
left: -1rem; left: -1rem;
width: 1rem; width: 1rem;
height: 2.5rem; height: 2.5rem;
background: $gray-100;
} }
} }
} }
@ -1056,7 +1090,107 @@ export default {
} }
} }
} }
// pricing by day override styles
&.pricing-by-day {
.calendar-grid-container .grid-item {
margin: 0 0 0.15rem 0;
}
.month-year {
grid-area: 1 / 1 / 2 / 8;
}
.legend {
display: none;
}
.radio-wrapper {
align-items: flex-start;
width: 100%;
height: 2.5rem;
color: $black;
input[type="radio"] {
&:focus-visible + label {
box-shadow: none;
}
&:focus + label,
&:checked:focus + label {
box-shadow: none;
background-color: $white;
color: $black;
}
&:checked + label {
background: $blue-100;
border: 2px solid $blue;
border-radius: 0.25rem;
color: $black;
span {
font-family: AvertaSemibold;
font-weight: 400;
}
}
}
label {
flex-direction: column;
height: 2.5rem;
width: 100%;
font-size: 0.75rem;
line-height: 1.2;
justify-content: center;
padding: 0.25rem;
}
&.selectable-day {
label {
color: $black;
background-color: $blue-100;
min-width: 2.5rem;
width: 2.5rem;
border-radius: 0.25rem;
}
}
&.current-day {
label:after {
margin-top: 0.2rem;
position: relative;
top: 0;
}
}
}
&.show-pricing-by-day {
.radio-wrapper {
&.selectable-day label span {
text-decoration: none;
color: $black;
font-weight: 400;
font-family: "AvertaSemibold";
&.price {
font-family: "AvertaRegular";
font-weight: 400;
}
}
&.upch-day label span.price {
color: $gray-600;
font-weight: 400;
font-family: $font-family-sans-serif;
}
input[type="radio"] {
&:focus + label,
&:checked:focus + label {
color: $gray-600;
}
&:checked + label {
color: $blue;
span {
font-weight: 400;
font-family: "AvertaSemibold";
}
}
}
}
}
}
} }
.btn-link { .btn-link {
display: block; display: block;
position: relative; position: relative;

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@
</div> </div>
</div> </div>
<div class="footer-inner-wrapper"> <div class="footer-inner-wrapper">
<p class="mx-3 my-0 pb-1">&copy; 2025 Safelite Group</p>
<textLink <textLink
linkType="newWindowLink" linkType="newWindowLink"
text="Terms of service" text="Terms of service"
@ -32,11 +33,6 @@
linkType="navigation" linkType="navigation"
text="Cookie preferences" text="Cookie preferences"
href="javascript:OneTrust.ToggleInfoDisplay();" /> href="javascript:OneTrust.ToggleInfoDisplay();" />
<textLink
linkType="newWindowLink"
text="Warranty"
href="//www.safelite.com/national-lifetime-warranty"
target="_blank" />
<textLink <textLink
linkType="newWindowLink" linkType="newWindowLink"
text="Notice at collection" text="Notice at collection"
@ -83,6 +79,14 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
p {
font-size: 0.875rem;
color: $black;
@include media-breakpoint-up(md) {
font-size: 1rem;
}
}
:deep(a) { :deep(a) {
text-decoration: none; text-decoration: none;
&.new-window-link { &.new-window-link {

View file

@ -56,7 +56,10 @@
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" /> <textBlock
class="mb-5"
cmsWidgetName="DisclaimerCopyWidget"
typeStyle="caption" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -203,3 +203,9 @@ export default {
}, },
}; };
</script> </script>
<style lang="scss">
.ui-autocomplete {
z-index: 3;
}
</style>

View file

@ -275,8 +275,8 @@ export default {
const emailFromStore = store.getters.order.customer.emailAddress; const emailFromStore = store.getters.order.customer.emailAddress;
const isEmailInStoreOnPageLoad = emailFromStore?.length > 0; const isEmailInStoreOnPageLoad = emailFromStore?.length > 0;
const isPopupSkipped = const isPopupSkipped =
store.getters.pageData(fmgPageValues.QUOTE).saveProgressPopupSkipped === true; store.getters.pageData(fmgPageValues.QUOTE)?.saveProgressPopupSkipped === true;
const showSaveProgressPopup = isEmailInStoreOnPageLoad || isPopupSkipped ? false : true; const showSaveProgressPopup = !(isEmailInStoreOnPageLoad || isPopupSkipped);
const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true; const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true;
const shouldSkipToInsurance = const shouldSkipToInsurance =
getBoolFromString( getBoolFromString(

View file

@ -18,7 +18,7 @@
marginTopSizeOverride="1" /> marginTopSizeOverride="1" />
</template> </template>
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" /> <locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
<datePickerForPricingByDay <datePicker
customComponentId="dateQuestion" customComponentId="dateQuestion"
selectableDatesSetting="custom" selectableDatesSetting="custom"
ref="datePicker" ref="datePicker"
@ -77,7 +77,7 @@ import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form, defineRule } from "vee-validate"; import { Form, defineRule } from "vee-validate";
import datePickerForPricingByDay from "@/experiment-components/date-picker-for-pricing-by-day"; import datePicker from "@/digital-components/date-picker/date-picker";
import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts"; import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts";
import timeSlotModalQuestion from "./time-slot-modal-question/time-slot-modal-question"; import timeSlotModalQuestion from "./time-slot-modal-question/time-slot-modal-question";
import textBlock from "@/digital-components/text-block/text-block"; import textBlock from "@/digital-components/text-block/text-block";
@ -298,14 +298,13 @@ export default {
); );
// While Pricing By Day Experiment is active, using the updated datePicker // While Pricing By Day Experiment is active, using the updated datePicker
const datePickerInitialDataPromise = const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
await datePickerForPricingByDay.methods.loadInitialData({ // setup config options for date-picker
// setup config options for date-picker selectableDatesSetting: "custom",
selectableDatesSetting: "custom", initialViewRowsToShow: 5,
initialViewRowsToShow: 5, customSelectableDatesCallback: getAvailableDates,
customSelectableDatesCallback: getAvailableDates, preSelectedDate: preSelectedDate,
preSelectedDate: preSelectedDate, });
});
// Get pricingByDayUpcharge needed for Pricing By Day // Get pricingByDayUpcharge needed for Pricing By Day
const pricingByDayUpchargePartPromise = showPricingByDay const pricingByDayUpchargePartPromise = showPricingByDay
@ -735,7 +734,7 @@ export default {
funnelSubHeader, funnelSubHeader,
Form, Form,
loadingModal, loadingModal,
datePickerForPricingByDay, datePicker,
locationAlerts, locationAlerts,
timeSlotModalQuestion, timeSlotModalQuestion,
textBlock, textBlock,