Merge pull request #1256 from Safelite/rlsmerge/2023.07.27-to-submit-cash
Rlsmerge/2023.07.27 to submit cash
This commit is contained in:
commit
50a7e39c6d
11 changed files with 149 additions and 80 deletions
|
|
@ -56,15 +56,19 @@
|
||||||
:class="[!isLoading ? 'date-picker-hidden' : '']"
|
:class="[!isLoading ? 'date-picker-hidden' : '']"
|
||||||
loaderColor="blue"
|
loaderColor="blue"
|
||||||
loaderPosition="center" />
|
loaderPosition="center" />
|
||||||
|
<div class="row form-test-error">
|
||||||
|
<ErrorMessage name="date-of-month" class="small mt-1"></ErrorMessage>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="calendarViewDirection === 'future' && !disableViewMoreDatesButton"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-link"
|
||||||
|
id="viewMoreDates"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="showAnotherMonth">
|
||||||
|
View more dates
|
||||||
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<button
|
|
||||||
v-if="calendarViewDirection === 'future' && !disableViewMoreDatesButton"
|
|
||||||
type="button"
|
|
||||||
class="btn btn-link"
|
|
||||||
:disabled="isLoading"
|
|
||||||
@click="showAnotherMonth">
|
|
||||||
View more dates
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -78,6 +82,7 @@ import {
|
||||||
convertDateToDateString,
|
convertDateToDateString,
|
||||||
convertDateStringToDate,
|
convertDateStringToDate,
|
||||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||||
|
import { useField, ErrorMessage } from "vee-validate";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "datePicker",
|
name: "datePicker",
|
||||||
|
|
@ -112,6 +117,32 @@ export default {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
validationRules: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const propsClone = Object.assign({}, props);
|
||||||
|
const modelValue = propsClone.modelValue;
|
||||||
|
|
||||||
|
const fieldOptions = {
|
||||||
|
value: modelValue,
|
||||||
|
initialValue: modelValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { errorMessage, handleBlur, handleChange, meta, validate, errors, resetField } =
|
||||||
|
useField("date-of-month", props.validationRules, fieldOptions);
|
||||||
|
|
||||||
|
return {
|
||||||
|
errorMessage,
|
||||||
|
handleBlur,
|
||||||
|
handleChange,
|
||||||
|
validate,
|
||||||
|
meta,
|
||||||
|
errors,
|
||||||
|
resetField,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
todayString() {
|
todayString() {
|
||||||
|
|
@ -419,7 +450,7 @@ export default {
|
||||||
monthsAfterToLoadOffset (number),
|
monthsAfterToLoadOffset (number),
|
||||||
hideSecondMonth (boolean),
|
hideSecondMonth (boolean),
|
||||||
preSelectedDate (string)
|
preSelectedDate (string)
|
||||||
|
|
||||||
data used:
|
data used:
|
||||||
todayDate (date object)
|
todayDate (date object)
|
||||||
this.hideSomeDaysForInitialView (string)
|
this.hideSomeDaysForInitialView (string)
|
||||||
|
|
@ -615,41 +646,39 @@ export default {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
scrollToElement(elementId, speed, easing) {
|
scrollToElement(elementId, duration = 800, easing = "ease-in-out") {
|
||||||
// TODO - needs to be cleaned up & refactored
|
const wrapper = document.querySelector(".page-container-grouped-styles");
|
||||||
function scrollTopSmooth(wrapper, target, duration = 300, timingName = "linear") {
|
const target = document.getElementById(elementId);
|
||||||
const initY = wrapper.scrollTop;
|
const initY = wrapper.scrollTop;
|
||||||
const wrapperRect = wrapper.getBoundingClientRect();
|
const wrapperRect = wrapper.getBoundingClientRect();
|
||||||
const targetRect = target.getBoundingClientRect();
|
const targetRect = target.getBoundingClientRect();
|
||||||
const targetY = targetRect.top - wrapperRect.top - BUFFER_OFFSET;
|
const targetY = targetRect.top - wrapperRect.top - BUFFER_OFFSET;
|
||||||
const timingFunc = TIMINGFUNC_MAP[timingName];
|
const timingFunc = TIMINGFUNC_MAP[easing];
|
||||||
let start = null;
|
let start = null;
|
||||||
|
const step = (timestamp) => {
|
||||||
const step = (timestamp) => {
|
start = start || timestamp;
|
||||||
start = start || timestamp;
|
const time = Math.min(1, (timestamp - start) / duration);
|
||||||
const progress = timestamp - start,
|
const percentageNew = timingFunc(time);
|
||||||
// Growing from 0 to 1
|
const distanceToGo = targetY;
|
||||||
time = Math.min(1, (timestamp - start) / duration);
|
const thisDistance = percentageNew * distanceToGo;
|
||||||
const percentageNew = timingFunc(time);
|
wrapper.scrollTo(0, initY + thisDistance);
|
||||||
const distanceToGo = targetY;
|
if (percentageNew < 1) {
|
||||||
const thisDistance = percentageNew * distanceToGo;
|
window.requestAnimationFrame(step);
|
||||||
|
}
|
||||||
wrapper.scrollTo(0, initY + thisDistance);
|
};
|
||||||
if (percentageNew < 1) {
|
window.requestAnimationFrame(step);
|
||||||
window.requestAnimationFrame(step);
|
},
|
||||||
}
|
},
|
||||||
};
|
watch: {
|
||||||
window.requestAnimationFrame(step);
|
modelValue(newValue, oldValue) {
|
||||||
}
|
this.resetField({
|
||||||
|
value: newValue,
|
||||||
const wrapper = document.getElementById("date-picker-fieldset");
|
});
|
||||||
const targetMonth = document.getElementById(elementId);
|
|
||||||
|
|
||||||
scrollTopSmooth(wrapper, targetMonth, 800, "ease-in-out");
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
loader,
|
loader,
|
||||||
|
ErrorMessage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -660,27 +689,22 @@ export default {
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
}
|
}
|
||||||
.date-picker {
|
.date-picker {
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
overflow-y: auto;
|
|
||||||
flex-grow: 1;
|
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;
|
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
width: 2rem;
|
width: calc(100% - 1.5rem);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
width: 100%;
|
width: 1.5rem;
|
||||||
height: 100%;
|
height: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.calendar-grid-container {
|
.calendar-grid-container {
|
||||||
|
|
@ -813,9 +837,9 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 3rem;
|
min-width: 2.5rem;
|
||||||
|
width: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
min-width: 3rem;
|
|
||||||
|
|
||||||
span {
|
span {
|
||||||
&.small {
|
&.small {
|
||||||
|
|
@ -916,16 +940,15 @@ export default {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
&.last-available-month:not(&.month-hidden) {
|
|
||||||
margin-bottom: 14rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-link {
|
.btn-link {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-underline-offset: 4px;
|
text-underline-offset: 4px;
|
||||||
position: absolute;
|
flex-grow: 0;
|
||||||
bottom: 0;
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.past {
|
.past {
|
||||||
|
|
@ -997,4 +1020,10 @@ export default {
|
||||||
text-underline-offset: 4px;
|
text-underline-offset: 4px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-test-error {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 414px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="funnel-sub-header">
|
<div class="funnel-sub-header">
|
||||||
<div
|
<div
|
||||||
class="d-flex align-items-center justify-content-center container-fluid overflow-hidden">
|
class="d-flex align-items-center justify-content-center container-fluid overflow-hidden">
|
||||||
<h5 class="text-center fw-normal mb-2" :class="headerColor">
|
<h5 class="text-center fw-normal mb-0" :class="headerColor">
|
||||||
<span>
|
<span>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,11 @@ describe("quote.vue", () => {
|
||||||
},
|
},
|
||||||
referralNumber: "1234567",
|
referralNumber: "1234567",
|
||||||
},
|
},
|
||||||
|
payment: {
|
||||||
|
insuranceCoverage: {
|
||||||
|
isVerified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
@ -185,6 +190,11 @@ describe("quote.vue", () => {
|
||||||
glassParts: ["item", "item2"],
|
glassParts: ["item", "item2"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
payment: {
|
||||||
|
insuranceCoverage: {
|
||||||
|
isVerified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ export default {
|
||||||
(store.getters.order.damage.isRepair ||
|
(store.getters.order.damage.isRepair ||
|
||||||
(store.getters.order.lineItems?.glassParts != null &&
|
(store.getters.order.lineItems?.glassParts != null &&
|
||||||
store.getters.order.lineItems.glassParts.length > 0)) &&
|
store.getters.order.lineItems.glassParts.length > 0)) &&
|
||||||
store.getters.order.referralNumber?.length !== 6
|
!store.getters.payment.insuranceCoverage.isVerified
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getDefaultIsInsuranceSelectedValue(availableLineItems) {
|
getDefaultIsInsuranceSelectedValue(availableLineItems) {
|
||||||
|
|
@ -166,7 +166,7 @@ export default {
|
||||||
return availableLineItems
|
return availableLineItems
|
||||||
? baseMixin.methods.getTierOnePackagePrice(
|
? baseMixin.methods.getTierOnePackagePrice(
|
||||||
baseMixin.methods.filterOutFees(availableLineItems)
|
baseMixin.methods.filterOutFees(availableLineItems)
|
||||||
) > 500
|
) > 300
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
class="text-link-small"
|
class="text-link-small"
|
||||||
:customSelectableDatesCallback="getAvailableDatesMethod"
|
:customSelectableDatesCallback="getAvailableDatesMethod"
|
||||||
|
validationRules="date-required"
|
||||||
@date-clicked="openInshopTimeSlotsModal" />
|
@date-clicked="openInshopTimeSlotsModal" />
|
||||||
<time-slot-modal-question
|
<time-slot-modal-question
|
||||||
ref="timeSlotModalQuestion"
|
ref="timeSlotModalQuestion"
|
||||||
|
|
@ -93,7 +94,7 @@ const getAvailableDates = async (
|
||||||
const timeSlotsData = {};
|
const timeSlotsData = {};
|
||||||
timeSlotsData.days = [];
|
timeSlotsData.days = [];
|
||||||
let apiStartDate = startDateString;
|
let apiStartDate = startDateString;
|
||||||
let apiEndDate = apiEndDateLimit;
|
let apiEndDate = endDateString;
|
||||||
|
|
||||||
for (let i = 1; i <= apiCallsCount; i++) {
|
for (let i = 1; i <= apiCallsCount; i++) {
|
||||||
let storeActionConfig;
|
let storeActionConfig;
|
||||||
|
|
@ -105,6 +106,10 @@ const getAvailableDates = async (
|
||||||
if (i === apiCallsCount) {
|
if (i === apiCallsCount) {
|
||||||
apiEndDate = endDateString;
|
apiEndDate = endDateString;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (apiEndDate > apiEndDateLimit) {
|
||||||
|
apiEndDate = apiEndDateLimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appointmentType === AppointmentTypeStrings.MOBILE) {
|
if (appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
|
|
@ -126,13 +131,13 @@ const getAvailableDates = async (
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
storeActionConfigs.push(storeActionConfig);
|
if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASYNC METHOD
|
|
||||||
const timeSlotsResponsesData = {
|
const timeSlotsResponsesData = {
|
||||||
days: [],
|
days: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
function compareDayStrings(a, b) {
|
function compareDayStrings(a, b) {
|
||||||
if (a.date < b.date) return -1;
|
if (a.date < b.date) return -1;
|
||||||
if (a.date > b.date) return 1;
|
if (a.date > b.date) return 1;
|
||||||
|
|
|
||||||
|
|
@ -319,15 +319,19 @@ export default {
|
||||||
return `${hours}:${minutes} ${meridianNotation}`;
|
return `${hours}:${minutes} ${meridianNotation}`;
|
||||||
},
|
},
|
||||||
getDisplayTextForDurationLength(durationMinimum, durationMaximum) {
|
getDisplayTextForDurationLength(durationMinimum, durationMaximum) {
|
||||||
let displayTextForDurationLength;
|
const isLongAppointment = durationMaximum >= 120;
|
||||||
if (durationMaximum >= 120) {
|
const isDurationRange = durationMinimum !== durationMaximum;
|
||||||
displayTextForDurationLength = `${durationMinimum / 60} - ${
|
|
||||||
durationMaximum / 60
|
const adjustedMinimum = isLongAppointment ? durationMinimum / 60 : durationMinimum;
|
||||||
} hours`;
|
const adjustedMaximum = isLongAppointment ? durationMaximum / 60 : durationMaximum;
|
||||||
} else {
|
|
||||||
displayTextForDurationLength = `${durationMinimum} - ${durationMaximum} minutes`;
|
const durationText = isDurationRange
|
||||||
}
|
? `${adjustedMinimum} - ${adjustedMaximum}`
|
||||||
return displayTextForDurationLength;
|
: adjustedMinimum;
|
||||||
|
|
||||||
|
const unitText = isLongAppointment ? "hours" : "minutes";
|
||||||
|
|
||||||
|
return `${durationText} ${unitText}`;
|
||||||
},
|
},
|
||||||
getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
|
getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
|
||||||
selectedTimeSlotId,
|
selectedTimeSlotId,
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,10 @@
|
||||||
<span v-if="!isLoaderDisplayed" class="m-0 button-auxillary-copy">{{
|
<span v-if="!isLoaderDisplayed" class="m-0 button-auxillary-copy">{{
|
||||||
badgeText
|
badgeText
|
||||||
}}</span>
|
}}</span>
|
||||||
<loader v-if="isLoaderDisplayed" :class="['left', 'no-block']" />
|
<loader
|
||||||
<!-- no-block allows the user to click between shops while availability indicators are loading -->
|
v-if="isLoaderDisplayed"
|
||||||
|
:loaderPosition="left"
|
||||||
|
:allowPageInteraction="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-two">
|
<div class="row-two">
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,12 @@
|
||||||
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
|
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
|
||||||
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
|
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
|
||||||
v-model="customAlertData"
|
v-model="customAlertData"
|
||||||
v-if="vinPopulatedOnPageLoad && isInsuranceVerified && !displayInvalidZipAlert"
|
v-if="
|
||||||
|
vinPopulatedOnPageLoad &&
|
||||||
|
isInsuranceVerified &&
|
||||||
|
!displayInvalidZipAlert &&
|
||||||
|
!displayNonServiceableZipAlert
|
||||||
|
"
|
||||||
alertClass="alert-success" />
|
alertClass="alert-success" />
|
||||||
<alert
|
<alert
|
||||||
class="my-4"
|
class="my-4"
|
||||||
|
|
@ -106,7 +111,12 @@
|
||||||
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
|
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
|
||||||
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
|
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
|
||||||
v-model="customAlertData"
|
v-model="customAlertData"
|
||||||
v-if="vinPopulatedOnPageLoad && !isInsuranceVerified && !displayInvalidZipAlert"
|
v-if="
|
||||||
|
vinPopulatedOnPageLoad &&
|
||||||
|
!isInsuranceVerified &&
|
||||||
|
!displayInvalidZipAlert &&
|
||||||
|
!displayNonServiceableZipAlert
|
||||||
|
"
|
||||||
alertClass="alert-success" />
|
alertClass="alert-success" />
|
||||||
<funnelFooter
|
<funnelFooter
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
|
|
||||||
|
|
@ -446,9 +446,7 @@ export default {
|
||||||
|
|
||||||
const payment = store.getters.payment;
|
const payment = store.getters.payment;
|
||||||
|
|
||||||
if (store.getters.order.referralNumber?.length === 6) {
|
if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
|
||||||
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
|
|
||||||
} else if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
|
|
||||||
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
|
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
|
||||||
} else {
|
} else {
|
||||||
self.$router.navigateWithSaving(
|
self.$router.navigateWithSaving(
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ export default {
|
||||||
outline: none;
|
outline: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@
|
||||||
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]"></div>
|
v-bind:class="[
|
||||||
|
this.loaderColor,
|
||||||
|
this.loaderPosition,
|
||||||
|
this.allowPageInteraction ? 'allow-ui-interaction' : '',
|
||||||
|
]"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -19,6 +23,12 @@ export default {
|
||||||
loaderPosition: {
|
loaderPosition: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
/* Set to true for loaders where UI interaction must be allowed to continue while the loader is active
|
||||||
|
(See example shop-list-button) */
|
||||||
|
allowPageInteraction: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -83,8 +93,8 @@ 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
|
&.allow-ui-interaction::before {
|
||||||
&.no-block::before {
|
//no-block to enable clicking on certain buttons with loaders while the loader is actives
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue