Merge pull request #2426 from Safelite/CASH-499-calendar-ui-updates
Cash 499 calendar UI updates
This commit is contained in:
commit
f06da57711
7 changed files with 184 additions and 1259 deletions
|
|
@ -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
|
|
@ -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">© 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 {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -203,3 +203,9 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ui-autocomplete {
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue