Merge branch 'release/2026.02.12' into rlsmerge/2026.02.12-to-develop
This commit is contained in:
commit
a0673fac85
21 changed files with 594 additions and 2935 deletions
|
|
@ -32,11 +32,7 @@ module.exports = {
|
|||
"!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development
|
||||
"!src/layouts/insurance/*.vue", // Temp test exclusion while in development
|
||||
"!src/layouts/insurance-company/*.vue", // Temp test exclusion while in development
|
||||
"!src/layouts/schedule/**/*.vue", // Temp test exclusion while in development
|
||||
"!src/digital-components/date-picker/**/*.vue", // Temp test exclusion while in development
|
||||
|
||||
"!src/**/*-june-2025.vue", // Exclude these temporary files for CASH-845 project
|
||||
|
||||
"!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development
|
||||
"!src/experiment-components/*.vue",
|
||||
// END
|
||||
|
|
@ -44,7 +40,7 @@ module.exports = {
|
|||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 64,
|
||||
statements: 68,
|
||||
},
|
||||
},
|
||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ export const pageProgressMapper = {
|
|||
percent: 30,
|
||||
step: 1,
|
||||
},
|
||||
"policy-info": {
|
||||
percent: 55,
|
||||
step: 2,
|
||||
},
|
||||
quote: {
|
||||
percent: 60,
|
||||
step: 2,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,18 @@
|
|||
<template>
|
||||
<div
|
||||
class="date-picker text-center"
|
||||
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`">
|
||||
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass} ${loadingStatus === 'reinitial' ? 'loading-reinitial' : ''}`">
|
||||
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
||||
<legend class="sr-only">Select a day and time</legend>
|
||||
<div
|
||||
v-for="month in months"
|
||||
v-for="(month, i) in months"
|
||||
:key="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||
class="calendar-grid-container"
|
||||
class="calendar-grid-container month"
|
||||
:class="[
|
||||
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
|
||||
month.monthClass,
|
||||
i === 0 ? 'first-month-shown' : '',
|
||||
]">
|
||||
<div class="month-year body-small d-flex align-items-center">
|
||||
{{ month.monthLabel }} {{ month.yearNum?.toString() }}
|
||||
|
|
@ -114,7 +115,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<loader
|
||||
:class="[!isLoading ? 'date-picker-hidden' : '']"
|
||||
class="date-picker-loader"
|
||||
:class="[loadingStatus !== 'none' ? 'show-loader' : '']"
|
||||
loaderColor="blue"
|
||||
loaderPosition="center" />
|
||||
<div class="row form-test-error" id="date-of-month-error">
|
||||
|
|
@ -125,7 +127,7 @@
|
|||
type="button"
|
||||
class="btn btn-link view-more-dates"
|
||||
id="viewMoreDates"
|
||||
:disabled="isLoading"
|
||||
:disabled="loadingStatus !== 'none'"
|
||||
@click="showAnotherMonth">
|
||||
View more dates
|
||||
</button>
|
||||
|
|
@ -160,7 +162,6 @@ export default {
|
|||
name: "datePicker",
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
months: null,
|
||||
disableViewMoreDatesButton: false,
|
||||
hideSomeDaysForInitialView: null,
|
||||
|
|
@ -169,7 +170,10 @@ export default {
|
|||
};
|
||||
},
|
||||
props: {
|
||||
customComponentId: String,
|
||||
customComponentId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
selectableDatesSetting: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
|
|
@ -200,6 +204,10 @@ export default {
|
|||
pricingByDayUpcharge: Number,
|
||||
isPricingByDayExperiment: Boolean,
|
||||
isMobileSelected: Boolean,
|
||||
loadingStatus: {
|
||||
type: String,
|
||||
default: "none",
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const uuid = uuidv4();
|
||||
|
|
@ -324,9 +332,9 @@ export default {
|
|||
let preSelectedDateMonthEnd = this.getMonthEnd(
|
||||
preSelectedDate.replace("-mobile", "")
|
||||
);
|
||||
let weekIncludesPreSelectedMonthEnd = false;
|
||||
let monthEndsByThisWeek = false;
|
||||
let i = 0;
|
||||
while (!weekIncludesPreSelectedMonthEnd) {
|
||||
while (!monthEndsByThisWeek && i < 52) {
|
||||
if (i > 0) {
|
||||
weekStartDate = this.getNextWeekSunday(weekEndDate);
|
||||
weekEndDate = this.getWeekEndDate(weekStartDate);
|
||||
|
|
@ -338,7 +346,12 @@ export default {
|
|||
preSelectedDateMonthEnd === weekEndDate
|
||||
) {
|
||||
weekEndDate = preSelectedDateMonthEnd;
|
||||
weekIncludesPreSelectedMonthEnd = true;
|
||||
monthEndsByThisWeek = true;
|
||||
} else if (preSelectedDateMonthEnd < weekEndDate) {
|
||||
// Additional case: if month ends before this week (but not necessarily during it),
|
||||
// still cut the loop here, but don't truncate the week.
|
||||
|
||||
monthEndsByThisWeek = true;
|
||||
}
|
||||
}
|
||||
weeks.push({
|
||||
|
|
@ -489,7 +502,6 @@ export default {
|
|||
});
|
||||
},
|
||||
async setCalendarData(config = {}) {
|
||||
this.isLoading = true;
|
||||
this.selectableDatesInshop = [];
|
||||
this.selectableDatesMobile = [];
|
||||
this.months = [];
|
||||
|
|
@ -541,7 +553,6 @@ export default {
|
|||
// }
|
||||
}
|
||||
this.months = months;
|
||||
this.isLoading = false;
|
||||
|
||||
if (config.preSelectedDate) {
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -729,7 +740,6 @@ export default {
|
|||
},
|
||||
async showAnotherMonth() {
|
||||
if (!this.months) return;
|
||||
this.isLoading = true;
|
||||
let monthToShow;
|
||||
let monthStartDate;
|
||||
|
||||
|
|
@ -774,7 +784,6 @@ export default {
|
|||
monthToShowDates[monthToShowDates.length - 1].inputValue
|
||||
);
|
||||
|
||||
this.isLoading = false;
|
||||
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
|
||||
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
||||
this.scrollToElement(monthToShow.monthString);
|
||||
|
|
@ -787,7 +796,6 @@ export default {
|
|||
},
|
||||
async updateSelectableDates(monthStart, monthEnd) {
|
||||
const moreSelectableDates = await this.getMoreDatesCallback(monthStart, monthEnd);
|
||||
|
||||
moreSelectableDates.inshopTimeSlotsData.days.forEach((selectableDate) => {
|
||||
const index = this.selectableDatesInshop.findIndex(
|
||||
(dateObj) => dateObj.date === selectableDate.date
|
||||
|
|
@ -846,12 +854,6 @@ export default {
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
isLoading(newValue) {
|
||||
// if done loading dates, then set to first available date
|
||||
if (newValue === false && this.firstAvailableSelectableDate) {
|
||||
this.selectedDate = this.firstAvailableSelectableDate;
|
||||
}
|
||||
},
|
||||
modelValue(newValue) {
|
||||
this.resetField({
|
||||
value: newValue,
|
||||
|
|
@ -872,9 +874,14 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.date-picker-hidden {
|
||||
.date-picker-loader {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
|
||||
&.show-loader {
|
||||
opacity: 1;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
.date-picker {
|
||||
position: relative;
|
||||
|
|
@ -903,12 +910,12 @@ export default {
|
|||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
.calendar-grid-container {
|
||||
.month {
|
||||
margin: 0 auto 1rem auto;
|
||||
position: relative;
|
||||
transition:
|
||||
height ease 2s,
|
||||
opacity ease 2s;
|
||||
height ease 1s,
|
||||
max-height ease 1s;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
justify-content: center;
|
||||
|
|
@ -1157,7 +1164,7 @@ export default {
|
|||
}
|
||||
|
||||
.past {
|
||||
.calendar-grid-container {
|
||||
.month {
|
||||
.month-year {
|
||||
grid-area: 1 / 1 / 2 / 6;
|
||||
}
|
||||
|
|
@ -1214,7 +1221,7 @@ export default {
|
|||
}
|
||||
// new calendar styling overrides
|
||||
&.calendar-2025 {
|
||||
.calendar-grid-container .grid-item {
|
||||
.month .grid-item {
|
||||
margin: 0 0 0.15rem 0;
|
||||
}
|
||||
.month-year {
|
||||
|
|
@ -1316,6 +1323,33 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.loading-reinitial {
|
||||
.month {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
.month.first-month-shown {
|
||||
opacity: 1;
|
||||
max-height: none;
|
||||
|
||||
.grid-item.radio-wrapper.selectable-day {
|
||||
input[type="radio"]:checked + label,
|
||||
label {
|
||||
background-color: $white;
|
||||
border: none;
|
||||
color: initial;
|
||||
cursor: default;
|
||||
&:hover {
|
||||
border: none;
|
||||
}
|
||||
span {
|
||||
font-family: UrbanistRegular;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.timeslots .button-inner-wrapper) {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -41,9 +41,6 @@ export default {
|
|||
beforeUnmount() {
|
||||
// Clean up event listeners and observers
|
||||
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
|
||||
if (this._salesforceChatOpenObserver) {
|
||||
this._salesforceChatOpenObserver.disconnect();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeSalesforceWebchat() {
|
||||
|
|
@ -133,6 +130,9 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.embedded-messaging > .embeddedMessagingFrame[class~="isMaximized"] {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
.dockableContainer {
|
||||
&.showDockableContainer {
|
||||
color: $gray-600;
|
||||
|
|
|
|||
|
|
@ -59,14 +59,6 @@ describe("sierraWebchat.vue", () => {
|
|||
expect(window.sierraChat.closeChatModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("dispatches sierra-chat-closed on handleSierraOnClose", () => {
|
||||
const eventSpy = jest.spyOn(window, "dispatchEvent");
|
||||
wrapper.vm.createSierraConfig().onClose();
|
||||
expect(eventSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: "sierra-chat-closed" })
|
||||
);
|
||||
});
|
||||
|
||||
it("dispatches sierra-chat-transfer on handleSierraOnTransfer", () => {
|
||||
const eventSpy = jest.spyOn(window, "dispatchEvent");
|
||||
const transfer = {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,20 @@ import analyticsMixin from "@/mixins/analytics-mixin";
|
|||
import store from "@/store";
|
||||
import { routeData } from "@/router/constants/routes";
|
||||
import { deepClone } from "@/helpers/object-helper";
|
||||
import { webchatHelper } from "@/helpers/webchat-helper";
|
||||
|
||||
export default {
|
||||
name: "sierraWebchat",
|
||||
mixins: [analyticsMixin],
|
||||
data() {
|
||||
return {
|
||||
transferInProgress: false,
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
const { webchatGlobalNonpersistedState, launchWebchat } = webchatHelper();
|
||||
return { webchatGlobalNonpersistedState, launchWebchat };
|
||||
},
|
||||
methods: {
|
||||
openSierraChatModal() {
|
||||
const sierra = window.sierraChat || window.sierra || window.SierraChat;
|
||||
|
|
@ -35,11 +45,18 @@ export default {
|
|||
display: "corner",
|
||||
onLoad: () => this.openSierraChatModal(),
|
||||
onOpen: () => {},
|
||||
onClose: () => window.dispatchEvent(new CustomEvent("sierra-chat-closed")),
|
||||
onTransfer: (transfer) =>
|
||||
onClose: () => {
|
||||
if (!this.transferInProgress) {
|
||||
this.webchatGlobalNonpersistedState.showWebchatButton = true;
|
||||
this.transferInProgress = false;
|
||||
}
|
||||
},
|
||||
onTransfer: (transfer) => {
|
||||
this.transferInProgress = true;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("sierra-chat-transfer", { detail: transfer })
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
getSierraContext() {
|
||||
|
|
@ -138,10 +155,7 @@ export default {
|
|||
}
|
||||
// Pass data to Salesforce chat if available
|
||||
const transfer = event.detail;
|
||||
if (
|
||||
window.embeddedservice_bootstrap &&
|
||||
typeof window.embeddedservice_bootstrap.bootstrapEmbeddedService === "function"
|
||||
) {
|
||||
if (window.embeddedservice_bootstrap && window.embeddedservice_bootstrap.prechatAPI) {
|
||||
if (
|
||||
transfer &&
|
||||
transfer.data &&
|
||||
|
|
@ -155,37 +169,16 @@ export default {
|
|||
LastName: transfer.data.last_name,
|
||||
Email: transfer.data.email,
|
||||
});
|
||||
if (
|
||||
window.embeddedservice_bootstrap.utilAPI &&
|
||||
typeof window.embeddedservice_bootstrap.utilAPI.launchChat === "function"
|
||||
) {
|
||||
window.embeddedservice_bootstrap.utilAPI.launchChat();
|
||||
return;
|
||||
}
|
||||
}
|
||||
window.embeddedservice_bootstrap.utilAPI.launchChat();
|
||||
this.webchatGlobalNonpersistedState.shouldLaunchSierra = false;
|
||||
this.launchWebchat();
|
||||
}
|
||||
};
|
||||
window.addEventListener("sierra-chat-transfer", this._handleSierraTransfer);
|
||||
|
||||
// Notify funnel-header about Sierra chat open/close state
|
||||
this._sierraChatContainerObserver = new MutationObserver(() => {
|
||||
const isOpen = !!document.querySelector("[data-sierra-chat-container]");
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("sierra-chat-visibility", { detail: { open: isOpen } })
|
||||
);
|
||||
});
|
||||
this._sierraChatContainerObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("launch-sierra-webchat", this.triggerSierraChat);
|
||||
window.removeEventListener("sierra-chat-transfer", this._handleSierraTransfer);
|
||||
if (this._sierraChatContainerObserver) {
|
||||
this._sierraChatContainerObserver.disconnect();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -79,16 +79,7 @@ export default {
|
|||
return this.getCmsContent(this.cmsWidgetName, "LogoImage");
|
||||
},
|
||||
shouldShowWebchatButton() {
|
||||
// Hide the chat icon if Sierra chat is open
|
||||
if (this.sierraChatOpen) {
|
||||
return false;
|
||||
}
|
||||
// Show if either Sierra is enabled or Salesforce button is available and not hidden by page
|
||||
return (
|
||||
(this.webchatGlobalNonpersistedState.shouldLaunchSierra ||
|
||||
this.webchatGlobalNonpersistedState.showWebchatButton) &&
|
||||
!this.shouldHideWebchatButtonOnPage
|
||||
);
|
||||
return this.webchatGlobalNonpersistedState.showWebchatButton;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -116,10 +107,6 @@ export default {
|
|||
webchatClicked(event) {
|
||||
event.preventDefault();
|
||||
this.launchWebchat();
|
||||
// Only set sierraChatOpen if Sierra experiment is active
|
||||
if (this.webchatGlobalNonpersistedState.shouldLaunchSierra) {
|
||||
this.sierraChatOpen = true;
|
||||
}
|
||||
},
|
||||
syncSierraExperimentFlag() {
|
||||
const experiments = store.getters.applicationUser.experiments;
|
||||
|
|
@ -159,11 +146,6 @@ export default {
|
|||
unknownAlertEvent.displayAlert = true;
|
||||
this.globalAlertMessages.push(unknownAlertEvent);
|
||||
}
|
||||
// Listen for Sierra chat open/close events from sierra-webchat
|
||||
this._handleSierraChatVisibility = (event) => {
|
||||
this.sierraChatOpen = !!(event.detail && event.detail.open);
|
||||
};
|
||||
window.addEventListener("sierra-chat-visibility", this._handleSierraChatVisibility);
|
||||
|
||||
// Add scroll listener for header shadow effect
|
||||
this._handleScroll = () => {
|
||||
|
|
@ -172,8 +154,6 @@ export default {
|
|||
window.addEventListener("scroll", this._handleScroll);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("sierra-chat-closed", this._handleSierraChatClosed);
|
||||
window.removeEventListener("sierra-chat-visibility", this._handleSierraChatVisibility);
|
||||
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
|
||||
window.removeEventListener("scroll", this._handleScroll);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const webchatGlobalNonpersistedState = reactive({
|
|||
|
||||
export const webchatHelper = () => {
|
||||
function launchWebchat() {
|
||||
webchatGlobalNonpersistedState.showWebchatButton = false;
|
||||
if (webchatGlobalNonpersistedState.shouldLaunchSierra) {
|
||||
window.dispatchEvent(new CustomEvent("launch-sierra-webchat")); // launches the sierra chat
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ export default {
|
|||
console.log(`Price = ${this.amountDue}`);
|
||||
console.log(`Adyen Price = ${this.adyenPriceTotal}`);
|
||||
|
||||
const requestBody = this.adyenInitRequestInfo;
|
||||
const requestBody = this.getAdyenInitRequestInfo();
|
||||
|
||||
console.log(`Calling with:`);
|
||||
console.log(requestBody);
|
||||
|
|
@ -294,6 +294,15 @@ export default {
|
|||
|
||||
console.log(checkout);
|
||||
|
||||
const expiryTime = new Date(checkout.options.expiresAt);
|
||||
const expiryInterval = expiryTime.getTime() - new Date().getTime();
|
||||
|
||||
const handleTimeout = () => {
|
||||
this.resetAdyenDropin();
|
||||
};
|
||||
|
||||
const timeout = setTimeout(handleTimeout, expiryInterval);
|
||||
|
||||
const configuration = {
|
||||
paymentMethodsConfiguration: {
|
||||
ideal: {
|
||||
|
|
@ -505,14 +514,26 @@ export default {
|
|||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
piaType() {
|
||||
return this.$store.getters.order.payment.piaType;
|
||||
async resetAdyenDropin() {
|
||||
this?.dropinComponent?.unmount();
|
||||
await this.initializeAdyen();
|
||||
},
|
||||
|
||||
adyenInitRequestInfo() {
|
||||
// Now a Method so it is always freshly called and not cached.
|
||||
getIdempotencyKey() {
|
||||
const currentDateTime = new Date();
|
||||
const currentHour = currentDateTime.getUTCHours();
|
||||
const currentDate = currentDateTime.getUTCDate();
|
||||
const id = this?.$store?.getters?.order?.referralCorrelationId;
|
||||
const system = this.sourceSystem;
|
||||
const total = this.adyenPriceTotal;
|
||||
|
||||
return `${id}-${system}-${currentDate}-${currentHour}-${total}`;
|
||||
},
|
||||
|
||||
// Now a Method so it is always freshly called and not cached.
|
||||
getAdyenInitRequestInfo() {
|
||||
return {
|
||||
sourceSystem: this.sourceSystem,
|
||||
referralSequenceNumber: this.$store.getters.order.referralSequenceNumber,
|
||||
|
|
@ -529,9 +550,15 @@ export default {
|
|||
IP: "127.0.0.1", // TODO
|
||||
firstName: this.$store.getters.order.customer.firstName,
|
||||
lastName: this.$store.getters.order.customer.lastName,
|
||||
idempotencyKey: this.idempotencyKey,
|
||||
idempotencyKey: this.getIdempotencyKey(),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
piaType() {
|
||||
return this.$store.getters.order.payment.piaType;
|
||||
},
|
||||
|
||||
workOrderNumberLastSixDigits() {
|
||||
const workOrderNumber = this.$store.getters.order.workOrderNumber ?? "";
|
||||
|
|
@ -588,10 +615,6 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
idempotencyKey() {
|
||||
return `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`;
|
||||
},
|
||||
|
||||
sourceSystem() {
|
||||
return "FMG-2.0";
|
||||
},
|
||||
|
|
@ -601,7 +624,7 @@ export default {
|
|||
},
|
||||
|
||||
adyenPriceTotal() {
|
||||
return this.amountDue * 100;
|
||||
return Math.round(this.amountDue * 100);
|
||||
},
|
||||
|
||||
// Cart info
|
||||
|
|
|
|||
|
|
@ -745,10 +745,16 @@ export default {
|
|||
}
|
||||
},
|
||||
updatePaymentMethod(paymentMethod) {
|
||||
// if the payment method is not set yet, then set it based on what HOP sends us
|
||||
debugLog(
|
||||
`updatePaymentMethod(${paymentMethod}) and existing store.getters.order.payment.piaType = ${store?.getters?.order?.payment?.piaType}`
|
||||
);
|
||||
|
||||
// if the payment method is not set yet, then set it based on what HOP sends
|
||||
// or if its not already set to AfterPay, always set it based on what HOP sends
|
||||
if (
|
||||
store.getters.order.payment.piaType == paymentMethods.PAY_NOW ||
|
||||
store.getters.order.payment.piaType == paymentMethods.NONE
|
||||
store.getters.order.payment.piaType == paymentMethods.NONE ||
|
||||
store.getters.order.payment.piaType != paymentMethods.AFTERPAY
|
||||
) {
|
||||
console.log(new Date() + " UPDATE PaymentMethod from HOP:" + paymentMethod);
|
||||
this.dispatchStoreAction(
|
||||
|
|
|
|||
70
src/layouts/policy-info/policy-info.spec.js
Normal file
70
src/layouts/policy-info/policy-info.spec.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import policyInfo from "@/layouts/policy-info/policy-info.vue";
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
fetchCmsContentForPage: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("policy-info.vue", () => {
|
||||
describe("component rendering", () => {
|
||||
test("renders funnelHeader component", () => {
|
||||
const { wrapper } = setupMocks();
|
||||
expect(wrapper.findComponent({ name: "funnelHeader" }).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test("renders funnelSubHeader component", () => {
|
||||
const { wrapper } = setupMocks();
|
||||
expect(wrapper.findComponent({ name: "funnelSubHeader" }).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test("renders navbar component", () => {
|
||||
const { wrapper } = setupMocks();
|
||||
expect(wrapper.findComponent({ name: "navbar" }).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test("renders Form component", () => {
|
||||
const { wrapper } = setupMocks();
|
||||
expect(wrapper.findComponent({ name: "Form" }).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("methods", () => {
|
||||
test("arePagePrerequisitesValid returns true", () => {
|
||||
const { wrapper } = setupMocks();
|
||||
expect(wrapper.vm.arePagePrerequisitesValid()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks() {
|
||||
const mockCmsContent = {
|
||||
FunnelHeaderWidget: { Text: "Header" },
|
||||
FunnelSubHeaderWidget: { Text: "Subheader" },
|
||||
FunnelFooterWidget: { Text: "Footer" },
|
||||
};
|
||||
|
||||
fetchCmsContentForPage.mockResolvedValue(mockCmsContent);
|
||||
settleAllPromises.mockResolvedValue({ cmsContent: mockCmsContent });
|
||||
|
||||
const mountOptions = getMountOptions({
|
||||
route: { name: "policy-info", query: {}, params: {} },
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = shallowMount(policyInfo, mountOptions);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
76
src/layouts/policy-info/policy-info.vue
Normal file
76
src/layouts/policy-info/policy-info.vue
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<!--
|
||||
... Page code goes here.
|
||||
-->
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
|
||||
export default {
|
||||
name: "policy-info",
|
||||
mixins: [],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
components: {
|
||||
Form,
|
||||
funnelHeader,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
},
|
||||
// Ensure CMS content is fetched during route navigation without depending on `to`/`from`
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
const pageName = to.name;
|
||||
const cmsContentPromise = fetchCmsContentForPage(pageName);
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
];
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
async backButtonAction() {
|
||||
// Stub, for navigating back via nav-bar.
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
// Stub, for navigating forward via nav-bar
|
||||
},
|
||||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -72,10 +72,10 @@ export function isDropOffRouteCode(routeCode) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getTodayDate(routeCode) {
|
||||
export function getTodayDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
export function getTodayDateString(routeCode) {
|
||||
export function getTodayDateString() {
|
||||
return convertDateToDateString(getTodayDate());
|
||||
}
|
||||
|
|
|
|||
183
src/layouts/schedule/schedule.spec.js
Normal file
183
src/layouts/schedule/schedule.spec.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// Components
|
||||
import schedule from "@/layouts/schedule/schedule.vue";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { routeData } from "@/router/constants/routes";
|
||||
import store from "@/store";
|
||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
|
||||
const storeMocked = {
|
||||
getters: {
|
||||
payment: { isInsurance: false },
|
||||
order: {
|
||||
payment: { isInsurance: false, insuranceCoverage: { isVerified: false } },
|
||||
referralNumber: "",
|
||||
serviceLocation: {
|
||||
address: "",
|
||||
address2: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zipCode: "00000",
|
||||
zipCodeCtu: "000",
|
||||
appointmentType: null,
|
||||
provider: {},
|
||||
isVehicleProtected: false,
|
||||
},
|
||||
schedule: {
|
||||
date: "",
|
||||
routeCode: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
jobMinMinutes: null,
|
||||
jobMaxMinutes: null,
|
||||
},
|
||||
policy: {
|
||||
isItac: false,
|
||||
isNoComp: false,
|
||||
},
|
||||
damage: { isRepair: false },
|
||||
lineItems: { glassParts: [] },
|
||||
},
|
||||
vehicle: {
|
||||
cardId: "",
|
||||
},
|
||||
lineItems: {
|
||||
supportingItems: [],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
isMobileAppointment: false,
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
|
||||
navigateToHeritageFunnel: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("schedule.vue", () => {
|
||||
describe("navigation", () => {
|
||||
test("backButtonAction => non-insurance: navigateWithoutSaving called", () => {
|
||||
// Arrange
|
||||
const { wrapper, mocks } = setupMocks();
|
||||
|
||||
// Act
|
||||
wrapper.vm.backButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
|
||||
mocks.navigationScenarios.CLICKED_BACK,
|
||||
routeData.SCHEDULE.name
|
||||
);
|
||||
});
|
||||
|
||||
test("backButtonAction => insurance verified: navigateToHeritageFunnel called", () => {
|
||||
// Arrange
|
||||
store.getters.order.payment.isInsurance = true;
|
||||
store.getters.order.payment.insuranceCoverage = { isVerified: true };
|
||||
store.getters.order.referralNumber = "";
|
||||
const { wrapper } = setupMocks();
|
||||
|
||||
// Act
|
||||
wrapper.vm.backButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(navigateToHeritageFunnel).toHaveBeenCalledWith({
|
||||
shouldSaveSession: false,
|
||||
pageNameToLog: "schedule",
|
||||
navType: "back",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("computed: isForwardActionDisabled", () => {
|
||||
test("should be true when routeCode missing or appointmentType not set", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
await wrapper.setData({
|
||||
selectedTimeSlotInfo: { timeSlot: { routeCode: null } },
|
||||
appointmentType: null,
|
||||
});
|
||||
expect(wrapper.vm.isForwardActionDisabled).toBe(true);
|
||||
});
|
||||
|
||||
test("should be false when routeCode present and appointmentType set", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
await wrapper.setData({
|
||||
selectedTimeSlotInfo: { timeSlot: { routeCode: "RC123" } },
|
||||
appointmentType: "SomeType",
|
||||
});
|
||||
expect(wrapper.vm.isForwardActionDisabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("computed: selectedRouteCodeData", () => {
|
||||
test("should return null when routeCode not set", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
await wrapper.setData({
|
||||
selectedTimeSlotInfo: { timeSlot: { routeCode: null } },
|
||||
});
|
||||
expect(wrapper.vm.selectedRouteCodeData).toBeNull();
|
||||
});
|
||||
|
||||
test("should return routeCode and premium flag when set", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
await wrapper.setData({
|
||||
selectedTimeSlotInfo: {
|
||||
timeSlot: { routeCode: "RC999" },
|
||||
isPremiumAppointment: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.vm.selectedRouteCodeData).toEqual({
|
||||
routeCode: "RC999",
|
||||
isPremiumAppointment: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("forwardButtonAction (early exit path)", () => {
|
||||
test("shows recal acknowledgement popup and does not navigate when required", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
// Force the recal-ack modal path
|
||||
wrapper.vm.showRecalAcknowledgementModal = jest.fn(() => true);
|
||||
await wrapper.setData({ isRecalAcknowledgedForScheduling: null });
|
||||
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
expect(wrapper.vm.showRecalAcknowledgementPopup).toBe(true);
|
||||
expect(wrapper.vm.$router.navigateWithSaving).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks(mountOptionsMockData = {}) {
|
||||
const route = { name: "schedule" };
|
||||
const defaultMountOptions = {
|
||||
route: route,
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
store: { ...storeMocked },
|
||||
};
|
||||
const mockBaseMixin = {
|
||||
methods: {
|
||||
getTierOnePackagePrice: jest.fn(),
|
||||
filterOutFees: jest.fn(),
|
||||
getCmsContent: jest.fn(),
|
||||
},
|
||||
};
|
||||
const baseMountOptions = getMountOptions({
|
||||
...defaultMountOptions,
|
||||
...mountOptionsMockData,
|
||||
});
|
||||
|
||||
baseMountOptions.global.mixins = [mockBaseMixin];
|
||||
baseMountOptions.global.mocks.pageName = route.name;
|
||||
const wrapper = shallowMount(schedule, baseMountOptions);
|
||||
wrapper.getCmsContent = jest.fn();
|
||||
|
||||
return { wrapper, mocks: baseMountOptions.global?.mocks || {} };
|
||||
}
|
||||
|
|
@ -98,15 +98,11 @@
|
|||
linkWidgetName="ServiceZipLinkWidget"
|
||||
modalWidgetName="ServiceZipModalWidget" />
|
||||
</div>
|
||||
<div
|
||||
class="row your-shop-location"
|
||||
v-if="appointmentType && appointmentType !== appointmentTypeStrings.MOBILE">
|
||||
<div class="row your-shop-location" v-if="showShopLocationComponent">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<div>
|
||||
<shopLocation
|
||||
v-if="
|
||||
appointmentType && appointmentType !== appointmentTypeStrings.MOBILE
|
||||
"
|
||||
v-if="showShopLocationComponent"
|
||||
:selectedShop="selectedShopAnswer"
|
||||
:selectedProviderNumberFromParent="selectedProvider?.providerNumber"
|
||||
:shopProviderDataFromParent="shopProviderData"
|
||||
|
|
@ -153,7 +149,8 @@
|
|||
:pricingByDayBasePrice="pricingByDayBasePrice"
|
||||
:pricingByDayUpcharge="pricingByDayUpcharge"
|
||||
:showPricingByDay="showPricingByDay"
|
||||
:isPricingByDayExperiment="isPricingByDayExperiment" />
|
||||
:isPricingByDayExperiment="isPricingByDayExperiment"
|
||||
:loadingStatus="calendarLoadingStatus" />
|
||||
<timeSlotQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
v-if="showTimeSlotQuestion"
|
||||
|
|
@ -172,7 +169,8 @@
|
|||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:isSameDay="isSameDay"
|
||||
:selectedRouteCodeData="selectedRouteCodeData"
|
||||
@waitListRequested="handleWaitListRequested" />
|
||||
@waitListRequested="handleWaitListRequested"
|
||||
:lazyLoadAmountToLoad="10" />
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
|
|
@ -443,15 +441,14 @@ export default {
|
|||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
mobilePremiumAppointmentFee: null,
|
||||
waitListRequested: null,
|
||||
displayWaitList: null,
|
||||
pricingByDayUpchargeLineItem: null,
|
||||
includePricingByDayUpcharge: null,
|
||||
isPricingByDayExperiment: null,
|
||||
pricingByDayBasePrice: null,
|
||||
pricingByDayUpcharge: null,
|
||||
showPricingByDay: null,
|
||||
selectableDatesInshop: [],
|
||||
selectableDatesMobile: [],
|
||||
selectableDatesInshop: {},
|
||||
selectableDatesMobile: {},
|
||||
preSelectedDate: null,
|
||||
streetAddress: this.getServiceAddressFromStore(),
|
||||
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
|
||||
|
|
@ -484,6 +481,7 @@ export default {
|
|||
experimentSettings.SHOW_MOBILE_FIRST_APPT,
|
||||
"true"
|
||||
),
|
||||
calendarLoadingStatus: "none",
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -594,6 +592,8 @@ export default {
|
|||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.showLoadingModal();
|
||||
vm.calendarLoadingStatus = "initial";
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
|
||||
vm.mobilePremiumAppointmentFee = pricedPremiumFee;
|
||||
|
|
@ -614,6 +614,9 @@ export default {
|
|||
);
|
||||
vm.initializeDatePicker().then(() => {
|
||||
vm.showMobileFirstModal();
|
||||
vm.hideLoadingModal();
|
||||
vm.calendarLoadingStatus = "none";
|
||||
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
@ -630,6 +633,20 @@ export default {
|
|||
this.handleZipCodeChange(newValue);
|
||||
},
|
||||
},
|
||||
showShopLocationComponent() {
|
||||
// If we get no inshop or mobile data, then show the shoplocation component so the user is not
|
||||
// looking at a half blank screen with no way to try another zip/location.
|
||||
if (
|
||||
(this.selectableDatesInshop === undefined &&
|
||||
this.selectableDatesMobile === undefined) ||
|
||||
(this.selectableDatesInshop?.days?.length === 0 &&
|
||||
this.selectableDatesMobile?.days?.length === 0) ||
|
||||
(this.appointmentType && this.appointmentType !== AppointmentTypeStrings.MOBILE)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isMobileSelected() {
|
||||
return (
|
||||
this.appointmentType == AppointmentTypeStrings.MOBILE ||
|
||||
|
|
@ -885,6 +902,53 @@ export default {
|
|||
? this.$refs.mobileFirstModal?.getIsModalOpen()
|
||||
: false;
|
||||
},
|
||||
displayWaitList() {
|
||||
if (!this.appointmentType) return false;
|
||||
const firstMobileDateString =
|
||||
Array.isArray(this.selectableDatesMobile.days) &&
|
||||
this.selectableDatesMobile.days.length > 0
|
||||
? this.selectableDatesMobile.days[0].date
|
||||
: null;
|
||||
const firstInshopDateString =
|
||||
Array.isArray(this.selectableDatesInshop.days) &&
|
||||
this.selectableDatesInshop.days.length > 0
|
||||
? this.selectableDatesInshop.days[0].date
|
||||
: null;
|
||||
const dateString =
|
||||
this.isMobileSelected && firstMobileDateString
|
||||
? firstMobileDateString
|
||||
: firstInshopDateString;
|
||||
|
||||
if (
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.DISPLAY_WAITLIST,
|
||||
"true"
|
||||
) &&
|
||||
dateString
|
||||
) {
|
||||
const [year, month, day] = dateString.split("-").map(Number);
|
||||
const targetDate = new Date(year, month - 1, day);
|
||||
const currentDate = getTodayDate();
|
||||
const futureDate = new Date(currentDate);
|
||||
const experimentThresholdDays = experimentMixin.methods.hasSetting(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
? parseInt(
|
||||
experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
)
|
||||
: 0;
|
||||
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
|
||||
|
||||
if (targetDate >= futureDate) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
hasSelectableDatesLoaded() {
|
||||
if (this.isMobileSelected) {
|
||||
return this.selectableDatesMobile?.days?.length > 0;
|
||||
|
|
@ -1153,9 +1217,10 @@ export default {
|
|||
);
|
||||
}
|
||||
},
|
||||
async onShopSelected(shopQuestionPopUpData) {
|
||||
onShopSelected(shopQuestionPopUpData) {
|
||||
this.resetWaitlist();
|
||||
this.preSelectedDate = null;
|
||||
this.calendarLoadingStatus = "reinitial";
|
||||
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
|
||||
const oldProvider = this.selectedProvider;
|
||||
const selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
|
||||
|
|
@ -1175,11 +1240,16 @@ export default {
|
|||
this.updateSelectedProvider(selectedProvider);
|
||||
}
|
||||
}
|
||||
await this.initializeDatePicker();
|
||||
this.setSelectedDateToFirstAvailable();
|
||||
this.initializeDatePicker().then(() => {
|
||||
this.showMobileFirstModal();
|
||||
this.hideLoadingModal();
|
||||
this.calendarLoadingStatus = "none";
|
||||
this.setSelectedDateToFirstAvailable();
|
||||
});
|
||||
},
|
||||
async getMoreScheduleData(startDate, endDate) {
|
||||
// called only when "View more dates" is clicked; not on initial page load
|
||||
this.calendarLoadingStatus = "more";
|
||||
const moreShopTimeSlots = await getScheduleApiResponse({
|
||||
startDateString: startDate,
|
||||
endDateString: endDate,
|
||||
|
|
@ -1191,6 +1261,7 @@ export default {
|
|||
includeMobileTimeSlots: this.isServiceableMobile,
|
||||
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
|
||||
});
|
||||
this.calendarLoadingStatus = "none";
|
||||
|
||||
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||
this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
|
||||
|
|
@ -1229,7 +1300,6 @@ export default {
|
|||
await datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
|
||||
this.selectableDatesMobile =
|
||||
await datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
|
||||
this.setDisplayWaitList();
|
||||
|
||||
if (this.preSelectedDate) {
|
||||
this.selectedDate = this.preSelectedDate;
|
||||
|
|
@ -1241,11 +1311,8 @@ export default {
|
|||
) {
|
||||
await this.$nextTick();
|
||||
await this.$refs.datePicker.showAnotherMonth();
|
||||
this.setDisplayWaitList();
|
||||
}
|
||||
}
|
||||
|
||||
this.isShowMobileFirstAppt && this.hideLoadingModal();
|
||||
},
|
||||
getIsRecalAcknowledgedForSchedulingFromStore() {
|
||||
return store.getters.order.isRecalAcknowledgedForScheduling;
|
||||
|
|
@ -1259,8 +1326,8 @@ export default {
|
|||
if (isMobileSelected === undefined) {
|
||||
isMobileSelected = this.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||
}
|
||||
let storedDate = store.getters.order.schedule.date;
|
||||
if (isMobileSelected) storedDate += "-mobile";
|
||||
let storedDate = store.getters.order.schedule?.date;
|
||||
if (isMobileSelected && storedDate) storedDate += "-mobile";
|
||||
return storedDate;
|
||||
},
|
||||
getSelectedTimeSlotInfo() {
|
||||
|
|
@ -1515,39 +1582,6 @@ export default {
|
|||
}
|
||||
this.showLoadingModal();
|
||||
},
|
||||
setDisplayWaitList() {
|
||||
const dateString = this.isMobileSelected
|
||||
? this.selectableDatesMobile?.days[0]?.date
|
||||
: this.selectableDatesInshop?.days[0]?.date;
|
||||
if (
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.DISPLAY_WAITLIST,
|
||||
"true"
|
||||
) &&
|
||||
dateString
|
||||
) {
|
||||
const [year, month, day] = dateString.split("-").map(Number);
|
||||
const targetDate = new Date(year, month - 1, day);
|
||||
const currentDate = getTodayDate();
|
||||
const futureDate = new Date(currentDate);
|
||||
const experimentThresholdDays = experimentMixin.methods.hasSetting(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
? parseInt(
|
||||
experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
)
|
||||
: 0;
|
||||
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
|
||||
|
||||
if (targetDate >= futureDate) {
|
||||
this.displayWaitList = true;
|
||||
} else {
|
||||
this.displayWaitList = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
updateSupportingItems() {
|
||||
const supportingItems = this.getSupportingItems();
|
||||
|
||||
|
|
@ -1743,6 +1777,7 @@ export default {
|
|||
}
|
||||
|
||||
this.resetMobileLocation();
|
||||
this.calendarLoadingStatus = "initial";
|
||||
|
||||
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
|
||||
this.shopProviderData = result.data;
|
||||
|
|
@ -1753,6 +1788,9 @@ export default {
|
|||
this.selectedDate = null;
|
||||
this.initializeDatePicker().then(() => {
|
||||
this.showMobileFirstModal();
|
||||
this.hideLoadingModal();
|
||||
this.calendarLoadingStatus = "none";
|
||||
this.setSelectedDateToFirstAvailable();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
@ -1805,7 +1843,6 @@ export default {
|
|||
this.appointmentType = null;
|
||||
}
|
||||
this.setSelectedDateToFirstAvailable(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
|
||||
this.setDisplayWaitList(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
|
||||
},
|
||||
setSelectedDateToFirstAvailable() {
|
||||
this.selectedDate = this.getFirstAvailableDate();
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@
|
|||
isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '',
|
||||
]"
|
||||
:answers="availableTimeSlots"
|
||||
:lazyLoad="{ isLazyLoad: !isMobileAppointment, amountToLoad: 10 }"
|
||||
:lazyLoad="{
|
||||
isLazyLoad: isLazyLoad,
|
||||
amountToLoad: lazyLoadAmountToLoad || 10,
|
||||
}"
|
||||
groupName="chooseTimeSlot"
|
||||
textPosition="text-center"
|
||||
questionText="Available times:"
|
||||
|
|
@ -134,6 +137,7 @@ export default {
|
|||
isSameDay: Boolean,
|
||||
displayWaitList: Boolean,
|
||||
selectedRouteCodeData: Object,
|
||||
lazyLoadAmountToLoad: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -396,6 +400,26 @@ export default {
|
|||
!this.isDropOffAppointmentAvailable
|
||||
);
|
||||
},
|
||||
isLazyLoad() {
|
||||
if (this.availableTimeSlots?.length < this.lazyLoadAmountToLoad + 1) return false;
|
||||
if (this.isMobileAppointment) return false;
|
||||
if (this.selectedAnswerForTimeSlots && !this.isSelectedInshopTimeSlotShowing)
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
availableTimeSlotsForInshop() {
|
||||
return this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate?.timeSlots);
|
||||
},
|
||||
isSelectedInshopTimeSlotShowing() {
|
||||
if (!this.selectedAnswerForTimeSlots) return false;
|
||||
const idx = this.availableTimeSlotsForInshop?.findIndex((timeSlot) => {
|
||||
return timeSlot.value === this.selectedAnswerForTimeSlots;
|
||||
});
|
||||
if (idx > -1 && idx < this.lazyLoadAmountToLoad) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
|
||||
|
|
@ -453,6 +477,7 @@ export default {
|
|||
// }
|
||||
// },
|
||||
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
|
||||
if (!timeSlotsForSelectedDate) return;
|
||||
return timeSlotsForSelectedDate
|
||||
.filter((timeSlot) => {
|
||||
return !isDropOffRouteCode(timeSlot.id);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ export const routeData = {
|
|||
name: "quote",
|
||||
path: "/quote",
|
||||
},
|
||||
POLICY_INFO: {
|
||||
name: "policy-info",
|
||||
path: "/policy-info",
|
||||
},
|
||||
INSURANCE_COMPANY: {
|
||||
name: "insurance-company",
|
||||
path: "/insurance-company",
|
||||
|
|
|
|||
|
|
@ -429,6 +429,10 @@ const routingTable = function () {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
pageName: routeData.POLICY_INFO.name,
|
||||
maps: [],
|
||||
},
|
||||
{
|
||||
pageName: routeData.INSURANCE_COMPANY.name,
|
||||
maps: [
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export const routes = [
|
|||
createRoute(routeData.CONFIRMATION),
|
||||
createRoute(routeData.RETURN_USER),
|
||||
createRoute(routeData.MOBILE_DETAILS),
|
||||
createRoute(routeData.POLICY_INFO),
|
||||
|
||||
// Virtual pages (resolve to a non-virtual page.)
|
||||
createVirtualRoute(routeData.LANDING, landingBeforeEnter),
|
||||
|
|
|
|||
Loading…
Reference in a new issue