Merge branch 'develop' into feature/jnou/fix-iss-regression
This commit is contained in:
commit
d23fa89fdd
11 changed files with 566 additions and 30 deletions
|
|
@ -11,22 +11,29 @@ const GaEvents = Object.freeze({
|
|||
|
||||
const GaCategories = Object.freeze({
|
||||
API_RESPONSE: 'Api_Response',
|
||||
CONFIRMATION_CLICKED: "confirmation clicked",
|
||||
CONFIRMATION_CLICKED_INSHOP: "inshop confirmation clicked",
|
||||
CONFIRMATION_CLICKED_MOBILE: "mobile confirmation clicked",
|
||||
EVOX: 'Evox'
|
||||
});
|
||||
|
||||
const GaActions = Object.freeze({
|
||||
RESULT: 'Result',
|
||||
CLICKED: 'Clicked',
|
||||
NO: "no",
|
||||
RESULT: 'Result',
|
||||
SUBMITTED: 'Submitted',
|
||||
VIF: 'vif',
|
||||
SUBMITTED: 'Submitted'
|
||||
YES: "yes",
|
||||
});
|
||||
|
||||
const GaLabels = Object.freeze({
|
||||
SUCCESS: 'Success',
|
||||
ADDRESS_LOOKUP: 'Address_Look_up',
|
||||
ERROR: 'Error',
|
||||
LICENSE_PLATE_LOOKUP: 'License_Plate_Look_Up',
|
||||
NO: "no",
|
||||
SUCCESS: 'Success',
|
||||
VIN_LOOKUP: 'Vin_Look_Up',
|
||||
ADDRESS_LOOKUP: 'Address_Look_up'
|
||||
YES: "yes",
|
||||
});
|
||||
|
||||
const ValueToLogTypes = Object.freeze({
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const experimentUniverses = Object.freeze({
|
|||
ISS_FUNNEL: 'ISSFunnel',
|
||||
ISS_FEATURETOGGLE_AREFEES_HIDDEN: 'NextGenISS_FeatureToggle_AreFeesHidden',
|
||||
ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN: 'NextGenISS_FeatureToggle_AreFeesOverridden',
|
||||
ISS_MOBILE_FIRST_APPOINTMENTS: 'ISS_Mobile_First_Appointments',
|
||||
ADYEN_PAYMENT_TEST: 'NextGenAdyenPaymentTest'
|
||||
});
|
||||
|
||||
|
|
@ -12,6 +13,9 @@ const experimentSettings = Object.freeze({
|
|||
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_OVERRIDDEN: 'OverrideMobileFee',
|
||||
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN: 'HideRecycleFee',
|
||||
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_OVERRIDDEN: 'OverrideRecycleFee',
|
||||
ISS_MOBILE_FIRST_MAX_MOBILE_DAYS: 'MaxMobileDays',
|
||||
ISS_MOBILE_FIRST_MAX_PM_MOBILE_DAYS: 'MaxPmMobileDays',
|
||||
ISS_MOBILE_FIRST_SHOW_FIRST_MOBILE_APPOINTMENT: 'ShowMobileFirstAppointment',
|
||||
ISS_ENABLE_ADYEN_V1: 'ISS_Enable_Adyen_V1'
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ export function mapAdyenToIssPaymentMethod(adyenMethod) {
|
|||
const paymentMethodMap = {
|
||||
["scheme"]: paymentMethods.CREDIT_CARD,
|
||||
["afterpaytouch_US"]: paymentMethods.AFTERPAY,
|
||||
["afterpaytouch"]: paymentMethods.AFTERPAY,
|
||||
["paypal"]: paymentMethods.PAYPAL,
|
||||
["applepay"]: paymentMethods.APPLEPAY,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,8 +15,23 @@ function processZipCodeResults(request) {
|
|||
}));
|
||||
}
|
||||
|
||||
export async function getZipCodeData(zipCode) {
|
||||
return await processZipCodeResults(useMainStore().validateZip({ zip: zipCode }));
|
||||
export async function getAvailabilityRating(
|
||||
startDate,
|
||||
endDate,
|
||||
shopAppointmentType,
|
||||
providerNumber
|
||||
) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await useMainStore().getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber);
|
||||
|
||||
const numberOfDaysToEvaluate = 2;
|
||||
const isGoodAvailability =
|
||||
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length
|
||||
>= numberOfDaysToEvaluate;
|
||||
|
||||
const shopStatus = isGoodAvailability ? 'high' : 'low';
|
||||
|
||||
return Promise.resolve(shopStatus);
|
||||
}
|
||||
|
||||
export async function getMobileZipCodeData(zipCode) {
|
||||
|
|
@ -42,25 +57,6 @@ export async function getPricedMobileFeePart(serviceZipCode) {
|
|||
return Promise.resolve(pricingResults[0]);
|
||||
}
|
||||
|
||||
export async function getAvailabilityRating(
|
||||
startDate,
|
||||
endDate,
|
||||
shopAppointmentType,
|
||||
providerNumber
|
||||
) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await useMainStore().getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber);
|
||||
|
||||
const numberOfDaysToEvaluate = 2;
|
||||
const isGoodAvailability =
|
||||
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length
|
||||
>= numberOfDaysToEvaluate;
|
||||
|
||||
const shopStatus = isGoodAvailability ? 'high' : 'low';
|
||||
|
||||
return Promise.resolve(shopStatus);
|
||||
}
|
||||
|
||||
export function getProviderData() {
|
||||
const mainStore = useMainStore();
|
||||
const storeServiceLocation = mainStore.order.serviceLocation;
|
||||
|
|
@ -73,6 +69,18 @@ export function getProviderData() {
|
|||
return { storeServiceLocation, isMobileAppointment, storeSelectedProvider, serviceZipCode }
|
||||
}
|
||||
|
||||
export async function getZipCodeData(zipCode) {
|
||||
return await processZipCodeResults(useMainStore().validateZip({ zip: zipCode }));
|
||||
}
|
||||
|
||||
export function mapTimeSlot(timeSlot, timeOfDay) {
|
||||
return {
|
||||
...timeSlot,
|
||||
isDropoff: timeSlot.id.endsWith('DROP OFF'),
|
||||
timeOfDay
|
||||
};
|
||||
}
|
||||
|
||||
export async function onProviderChanged() {
|
||||
const { isMobileAppointment, storeSelectedProvider, serviceZipCode } = getProviderData();
|
||||
if (!storeSelectedProvider) {
|
||||
|
|
@ -101,4 +109,4 @@ export async function onProviderChanged() {
|
|||
}
|
||||
|
||||
await settleAllPromises(promiseResultMap);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.pushEventToGA("existing_claim", "pop_up_displayed", this.mainStore.accountNameForEvents, true);
|
||||
this.$refs.continueModal.openModal();
|
||||
},
|
||||
setModalStatus(isOpened) {
|
||||
|
|
@ -77,9 +78,11 @@ export default {
|
|||
this.$refs.continueModal.closeModal();
|
||||
},
|
||||
continueReferral() {
|
||||
this.pushEventToGA("existing_claim", "finish_claim", this.mainStore.accountNameForEvents, true);
|
||||
this.$emit('continue-previous-referral');
|
||||
},
|
||||
startNewReferral() {
|
||||
this.pushEventToGA("existing_claim", "start_new_claim", this.mainStore.accountNameForEvents, true);
|
||||
this.$emit('start-new-referral');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,11 +230,15 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
isSameAsPolicyAddress(newValue) {
|
||||
this.pushEventToGA("mobile_location", "same_as_policy_address", newValue ? "checked" : "unchecked", true);
|
||||
if (newValue) {
|
||||
this.copyPolicyAddressToServiceLocation();
|
||||
} else {
|
||||
this.clearAddressFields();
|
||||
}
|
||||
},
|
||||
isVehicleProtected(newValue) {
|
||||
this.pushEventToGA("mobile_location", "is_vehicle_protected", newValue, true);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<suggestTimeslotModal
|
||||
:ref="SUGGEST_TIMESLOT_MODAL_REF_NAME"
|
||||
cmsWidgetName="SuggestTimeslotModal"
|
||||
@confirmAppointmentClicked="autoSelectTimeslotConfirmed" />
|
||||
</Form>
|
||||
</template>
|
||||
<script>
|
||||
|
|
@ -70,8 +74,10 @@ import locationAlerts from '@/layouts/schedule-page/location-alerts/location-ale
|
|||
import datePicker from '@/digital-components/date-picker/date-picker.vue';
|
||||
import serviceLocation from '@/layouts/schedule-page/service-location/service-location.vue';
|
||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||
import suggestTimeslotModal from '@/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue';
|
||||
|
||||
// Supporting files
|
||||
import { experimentSettings, experimentUniverses } from '@/constants/experiments';
|
||||
import {
|
||||
AppointmentTypeStrings,
|
||||
GET_MOBILE_TIME_SLOTS,
|
||||
|
|
@ -91,13 +97,16 @@ import settleAllPromises from '@/helpers/layout-helper';
|
|||
import showIssLoadingModal from '@/helpers/loading-modal-helper.js';
|
||||
import {
|
||||
getPricedMobileFeePart,
|
||||
getZipCodeData
|
||||
getMobileZipCodeData,
|
||||
getZipCodeData,
|
||||
mapTimeSlot
|
||||
} from '@/helpers/service-location-helper';
|
||||
import { Form } from 'vee-validate';
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
// Define constants
|
||||
const SUGGEST_TIMESLOT_MODAL_REF_NAME = 'SuggestTimeslotModal';
|
||||
const TIME_SLOTS_CALL_DAYS_LIMIT = 15;
|
||||
|
||||
const getAvailableDates = async (
|
||||
|
|
@ -219,6 +228,7 @@ export default {
|
|||
datePicker,
|
||||
serviceLocation,
|
||||
siteFooter,
|
||||
suggestTimeslotModal,
|
||||
Form
|
||||
},
|
||||
mixins: [BaseFormMixin],
|
||||
|
|
@ -323,9 +333,16 @@ export default {
|
|||
zipCodeCtu: resultMap.zipCodeData?.zipCodeCtu
|
||||
};
|
||||
|
||||
const mobileFirstObject = {
|
||||
mobileProviderNumber: resultMap.providers?.mobileProviderNumber,
|
||||
serviceabilityDetails: resultMap.serviceabilityDetails,
|
||||
zipCode: initialServiceLocationObj.zipCode
|
||||
}
|
||||
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.$refs.serviceLocation.initializeComponent(serviceLocationData);
|
||||
await vm.setData(resultMap.premiumFeeWithPrice, initialServiceLocationObj);
|
||||
await vm.checkMobileFirstExperience(mobileFirstObject);
|
||||
showIssLoadingModal(false);
|
||||
});
|
||||
},
|
||||
|
|
@ -351,7 +368,8 @@ export default {
|
|||
selectedServiceLocation: this.getServiceLocation(),
|
||||
selectedServiceLocationCity: this.getServiceLocationCity(),
|
||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
showDatePickerError: false
|
||||
showDatePickerError: false,
|
||||
SUGGEST_TIMESLOT_MODAL_REF_NAME
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -432,6 +450,185 @@ export default {
|
|||
arePagePrerequisitesValid() {
|
||||
return useMainStore().order.serviceLocation.zipCode !== null;
|
||||
},
|
||||
async autoSelectTimeslotConfirmed(emittedValue) {
|
||||
if (this.mainStore.isNoComp || this.mainStore.isITAC) {
|
||||
console.log('User is No Comp or ITAC, skipping mobile fee update on auto select timeslot confirmed');
|
||||
// this.mainStore.updateMobileFee(this.mobileFeePart);
|
||||
}
|
||||
|
||||
const providerToUse = {
|
||||
providerNumber: emittedValue.mobileProviderNumber,
|
||||
address: {
|
||||
streetAddress: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null
|
||||
}
|
||||
};
|
||||
|
||||
this.mainStore.updateServiceLocation({
|
||||
state: emittedValue.zipCodeData.state,
|
||||
zipCode: emittedValue.zipCodeData.zipCode,
|
||||
zipCodeCtu: emittedValue.zipCodeData.zipCodeCtu,
|
||||
appointmentType: AppointmentTypeStrings.MOBILE,
|
||||
provider: providerToUse
|
||||
});
|
||||
|
||||
const timeSlotToUse = {
|
||||
date: emittedValue.date,
|
||||
routeCode: emittedValue.timeSlot.id,
|
||||
startTime: emittedValue.timeSlot.startTime,
|
||||
endTime: emittedValue.timeSlot.endTime,
|
||||
jobMaxMinutes: emittedValue.estimatedServiceMinutesMax.toString(),
|
||||
jobMinMinutes: emittedValue.estimatedServiceMinutesMin.toString(),
|
||||
};
|
||||
|
||||
this.mainStore.saveSchedule(timeSlotToUse);
|
||||
showIssLoadingModal(true);
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
async checkMobileFirstExperience(mobileFirstObject = {}) {
|
||||
const showMobileFirst = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_SHOW_FIRST_MOBILE_APPOINTMENT);
|
||||
if (showMobileFirst) {
|
||||
const pmMobileDaysLimit = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_MAX_PM_MOBILE_DAYS);
|
||||
const mobileDaysLimit = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_MAX_MOBILE_DAYS);
|
||||
const hasSelectedDate = !!this.getSelectedDate();
|
||||
const isServicableMobile = mobileFirstObject.serviceabilityDetails?.isGlassServiceableMobile;
|
||||
if (!hasSelectedDate && isServicableMobile && mobileFirstObject.mobileProviderNumber && mobileFirstObject.zipCode) {
|
||||
const todayDateObject = new Date();
|
||||
const todayDateString = convertDateToDateString(todayDateObject);
|
||||
const calendarViewDirection = 'future';
|
||||
const initialDays = 15;
|
||||
|
||||
const initialViewStartDate = todayDateString;
|
||||
const initialEndDate = new Date();
|
||||
initialEndDate.setDate(initialEndDate.getDate() + (initialDays - 1));
|
||||
const initialViewEndDate = convertDateToDateString(initialEndDate);
|
||||
const preSelectedDate = this.selectedDate;
|
||||
|
||||
const initialData = await getAvailableDates(
|
||||
initialViewStartDate,
|
||||
initialViewEndDate,
|
||||
AppointmentTypeStrings.MOBILE,
|
||||
mobileFirstObject.mobileProviderNumber,
|
||||
mobileFirstObject.zipCode
|
||||
).then((response) => ({
|
||||
todayDate: todayDateString,
|
||||
initialViewStartDate,
|
||||
initialViewEndDate,
|
||||
calendarViewDirection,
|
||||
initialShopTimeSlotsResponse: response,
|
||||
preSelectedDate,
|
||||
initialDaysLoaded: initialDays,
|
||||
daysFromStart: null
|
||||
}))
|
||||
.catch(() => {
|
||||
return {
|
||||
todayDate: todayDateString,
|
||||
initialViewStartDate,
|
||||
initialViewEndDate,
|
||||
calendarViewDirection,
|
||||
initialShopTimeSlotsResponse: { days: [] },
|
||||
preSelectedDate,
|
||||
initialDaysLoaded: initialDays,
|
||||
daysFromStart: null
|
||||
};
|
||||
});
|
||||
|
||||
this.mobileDatesData = initialData;
|
||||
|
||||
const estimatedServiceMinutesMax = initialData.initialShopTimeSlotsResponse?.estimatedServiceMinutesMaximum || 120;
|
||||
const estimatedServiceMinutesMin = initialData.initialShopTimeSlotsResponse?.estimatedServiceMinutesMinimum || 90;
|
||||
|
||||
const retTimeSlotsResponse = [];
|
||||
initialData.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
|
||||
const dateObjectToPush = {
|
||||
date: selectableDate.date,
|
||||
timeSlots: selectableDate.timeSlots,
|
||||
morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
|
||||
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
|
||||
return timeHour < 12;
|
||||
}).map((timeSlot) => mapTimeSlot(timeSlot, 'morning')),
|
||||
afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
|
||||
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
|
||||
return timeHour >= 12;
|
||||
}).map((timeSlot) => mapTimeSlot(timeSlot, 'afternoon')),
|
||||
isSelected: false
|
||||
};
|
||||
|
||||
if (dateObjectToPush.morningTimeSlots.length > 0) {
|
||||
const findIndex = dateObjectToPush.morningTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF'));
|
||||
if (findIndex !== -1) {
|
||||
const dropOffSlot = dateObjectToPush.morningTimeSlots.splice(findIndex, 1)[0];
|
||||
dateObjectToPush.morningTimeSlots.unshift(dropOffSlot);
|
||||
}
|
||||
}
|
||||
if (dateObjectToPush.afternoonTimeSlots.length > 0) {
|
||||
const findIndex = dateObjectToPush.afternoonTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF'));
|
||||
if (findIndex !== -1) {
|
||||
const dropOffSlot = dateObjectToPush.afternoonTimeSlots.splice(findIndex, 1)[0];
|
||||
dateObjectToPush.afternoonTimeSlots.push(dropOffSlot);
|
||||
}
|
||||
}
|
||||
retTimeSlotsResponse.push(dateObjectToPush);
|
||||
});
|
||||
|
||||
let dateDifferenceForPmMobile = null;
|
||||
const firstPmMobileDateWithTimeSlots = retTimeSlotsResponse.find((date) => {
|
||||
const hasPmMobileTimeSlots = date.afternoonTimeSlots?.length > 0;
|
||||
if (hasPmMobileTimeSlots && pmMobileDaysLimit) {
|
||||
dateDifferenceForPmMobile = calcDaysBetweenDates(todayDateString, date.date);
|
||||
return dateDifferenceForPmMobile <= pmMobileDaysLimit;
|
||||
}
|
||||
return hasPmMobileTimeSlots;
|
||||
});
|
||||
|
||||
let dateDifferenceForMobile = null;
|
||||
const firstMobileDateWithTimeSlots = retTimeSlotsResponse.find((date) => {
|
||||
const hasMobileTimeSlots = date.morningTimeSlots?.length > 0;
|
||||
const hasPmMobileTimeSlots = date.afternoonTimeSlots?.length > 0;
|
||||
if (hasMobileTimeSlots || hasPmMobileTimeSlots) {
|
||||
if (mobileDaysLimit) {
|
||||
dateDifferenceForMobile = calcDaysBetweenDates(todayDateString, date.date);
|
||||
return mobileDaysLimit && dateDifferenceForMobile <= mobileDaysLimit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return hasPmMobileTimeSlots;
|
||||
});
|
||||
|
||||
const zipCodeData = await getMobileZipCodeData(mobileFirstObject.zipCode);
|
||||
if ((firstPmMobileDateWithTimeSlots || firstMobileDateWithTimeSlots) && zipCodeData?.isValid) {
|
||||
zipCodeData.zipCode = mobileFirstObject.zipCode;
|
||||
const timeSlotForAutoSelectionMobile = {
|
||||
date: firstPmMobileDateWithTimeSlots ? firstPmMobileDateWithTimeSlots.date : firstMobileDateWithTimeSlots.date,
|
||||
estimatedServiceMinutesMax,
|
||||
estimatedServiceMinutesMin,
|
||||
isRepair: this.mainStore.order.damage.isRepair,
|
||||
mobileProviderNumber: mobileFirstObject.mobileProviderNumber,
|
||||
timeSlot: firstPmMobileDateWithTimeSlots ? firstPmMobileDateWithTimeSlots.afternoonTimeSlots[0]
|
||||
: (firstMobileDateWithTimeSlots.morningTimeSlots[0] || firstMobileDateWithTimeSlots.afternoonTimeSlots[0]),
|
||||
zipCodeData
|
||||
};
|
||||
|
||||
const dateObjectToUse = new Date(`${timeSlotForAutoSelectionMobile.date}T${timeSlotForAutoSelectionMobile.timeSlot.startTime}:00`);
|
||||
timeSlotForAutoSelectionMobile.dateObject = dateObjectToUse;
|
||||
timeSlotForAutoSelectionMobile.dayOfWeek = dateObjectToUse.toLocaleDateString('en-US', { weekday: 'long' });
|
||||
|
||||
const experiment = this.getExperimentByName(experimentUniverses.ISS_MOBILE_FIRST_APPOINTMENTS);
|
||||
if (experiment && !experiment.isExposed) {
|
||||
this.mainStore.logMobileFirstExperimentExposure();
|
||||
}
|
||||
|
||||
this.$refs[SUGGEST_TIMESLOT_MODAL_REF_NAME].openModal(timeSlotForAutoSelectionMobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async setData(premiumFeeWithPriceResponse, initialServiceLocationObj) {
|
||||
this.selectedServiceLocation = initialServiceLocationObj;
|
||||
this.mobilePremiumAppointmentFee = premiumFeeWithPriceResponse
|
||||
|
|
|
|||
|
|
@ -0,0 +1,259 @@
|
|||
<template>
|
||||
<div class="suggest-timeslot-modal-container">
|
||||
<modal
|
||||
ref="suggestTimeslotModal"
|
||||
:onModalClosedCallback="onModalClosed">
|
||||
<div>
|
||||
<div class="header">{{ modalHeaderText }}</div>
|
||||
<div class="subheader">{{ modalHeaderSubText }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="message"
|
||||
v-html="modalBodyText">
|
||||
</div>
|
||||
<div class="appointment-info"
|
||||
v-html="modalAppointmentInformationText">
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-container">
|
||||
<div>
|
||||
<button type="button" aria-live="polite" aria-label="Confirm Appointment" class="btn btn-danger confirm-appointment"
|
||||
@click="confirmAppointmentClicked">
|
||||
{{ modalCloseButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" aria-live="polite" aria-label="See more options" class="btn btn-link see-more-options"
|
||||
@click="seeMoreOptionsClicked">
|
||||
{{ modalFooterSubText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modal from '@/digital-components/modal/modal.vue';
|
||||
import {
|
||||
convertDateToTwoDigitDay,
|
||||
getDisplayTextForDurationLength,
|
||||
militaryToTwelveHourTime
|
||||
} from '@/helpers/date-helper';
|
||||
|
||||
export default {
|
||||
name: 'suggest-timeslot-modal',
|
||||
components: {
|
||||
modal
|
||||
},
|
||||
emits: ['confirm-appointment-clicked'],
|
||||
data() {
|
||||
return {
|
||||
confirmedAppointment: false,
|
||||
timeSlotForAutoSelectionMobile: {}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
cmsWidgetName: {
|
||||
type: String,
|
||||
default: 'SuggestTimeslotModal'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customAppointmentEstimate() {
|
||||
return getDisplayTextForDurationLength(this.timeSlotForAutoSelectionMobile.estimatedServiceMinutesMin, this.timeSlotForAutoSelectionMobile.estimatedServiceMinutesMax)
|
||||
},
|
||||
customAppointmentWindow() {
|
||||
return `${militaryToTwelveHourTime(this.timeSlotForAutoSelectionMobile?.timeSlot?.startTime)} - ${militaryToTwelveHourTime(this.timeSlotForAutoSelectionMobile?.timeSlot?.endTime)}`;
|
||||
},
|
||||
customDayOfWeek() {
|
||||
return this.timeSlotForAutoSelectionMobile.dayOfWeek ? this.timeSlotForAutoSelectionMobile.dayOfWeek : '';
|
||||
},
|
||||
customDayOfWeekMonthDayYear() {
|
||||
if (!this.timeSlotForAutoSelectionMobile?.date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const dayOfWeekToUse = this.customDayOfWeek;
|
||||
const year = this.timeSlotForAutoSelectionMobile.dateObject.getFullYear();
|
||||
const month = this.timeSlotForAutoSelectionMobile.dateObject.toLocaleString('en-US', { month: 'long' });
|
||||
const day = convertDateToTwoDigitDay(this.timeSlotForAutoSelectionMobile.dateObject);
|
||||
return `${dayOfWeekToUse}, ${month} ${day}, ${year}`;
|
||||
},
|
||||
modalAppointmentInformationText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'BodyText2')
|
||||
.replaceAll('{custom:dowMonthDayYear}', this.customDayOfWeekMonthDayYear)
|
||||
.replaceAll('{custom:appointmentEstimate}', this.customAppointmentEstimate)
|
||||
.replaceAll('{custom:appointmentWindow}', this.customAppointmentWindow);
|
||||
},
|
||||
modalBodyText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'BodyText')
|
||||
.replaceAll('{custom:damageType}', this.timeSlotForAutoSelectionMobile.isRepair ? 'repair' : 'replace',)
|
||||
.replaceAll('{custom:dayOfWeekLong}', this.customDayOfWeek);
|
||||
},
|
||||
modalCloseButtonText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
|
||||
},
|
||||
modalFooterSubText() {
|
||||
return 'See more options';
|
||||
},
|
||||
modalHeaderSubText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'SubheaderText');
|
||||
},
|
||||
modalHeaderText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
confirmAppointmentClicked() {
|
||||
this.confirmedAppointment = true;
|
||||
this.$emit('confirm-appointment-clicked', this.timeSlotForAutoSelectionMobile);
|
||||
this.$refs.suggestTimeslotModal.closeModal();
|
||||
},
|
||||
onModalClosed() {
|
||||
this.pushEvent();
|
||||
this.confirmedAppointment = false;
|
||||
this.timeSlotForAutoSelectionMobile = {};
|
||||
},
|
||||
openModal(timeSlotForAutoSelectionMobile) {
|
||||
this.timeSlotForAutoSelectionMobile = timeSlotForAutoSelectionMobile;
|
||||
this.$refs.suggestTimeslotModal.openModal();
|
||||
},
|
||||
pushEvent() {
|
||||
const gaAction = this.confirmedAppointment ? this.GaActions.YES : this.GaActions.NO;
|
||||
if (this.timeSlotForAutoSelectionMobile && this.timeSlotForAutoSelectionMobile.timeSlot) {
|
||||
this.pushEventToGA(
|
||||
this.GaCategories.CONFIRMATION_CLICKED,
|
||||
gaAction,
|
||||
this.timeSlotForAutoSelectionMobile.timeSlot.startTime +
|
||||
"-" +
|
||||
this.timeSlotForAutoSelectionMobile.timeSlot.endTime +
|
||||
" " +
|
||||
this.timeSlotForAutoSelectionMobile.date,
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
seeMoreOptionsClicked() {
|
||||
this.$refs.suggestTimeslotModal.closeModal();
|
||||
},
|
||||
footerButtonClick() {
|
||||
this.$refs.suggestTimeslotModal.closeModal();
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.suggest-timeslot-modal-container {
|
||||
.modal {
|
||||
padding-right: 0;
|
||||
}
|
||||
:deep(.modal) {
|
||||
.modal-dialog {
|
||||
@include media-breakpoint-up(xs) {
|
||||
width: auto;
|
||||
max-width: unset;
|
||||
}
|
||||
@include media-breakpoint-up(xl) {
|
||||
width: 37.5rem;
|
||||
max-width: 37.5rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 22rem;
|
||||
max-width: 22rem;
|
||||
padding: 1.5rem 0;
|
||||
border-radius: 0.5rem;
|
||||
line-height: 1.5rem;
|
||||
|
||||
.modal-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-top: 1rem;
|
||||
color: #db0020;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.subheader {
|
||||
color: black;
|
||||
text-align: center;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 1.5rem;
|
||||
color: #525656;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1.625rem;
|
||||
|
||||
.emphasize {
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.appointment-info {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: #f4f4f4;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
|
||||
.emphasize {
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
margin-top: 1.5rem;
|
||||
padding: 0 1.5rem;
|
||||
border: none;
|
||||
|
||||
.confirm-appointment {
|
||||
width: 100%;
|
||||
padding: .75rem 0;
|
||||
border-radius: 4.5rem;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.see-more-options {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 1.5rem;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 1.625rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -68,6 +68,7 @@
|
|||
:options="DamageCauseOptions"
|
||||
disableAutoFill
|
||||
:validationRules="rules.damageOption"
|
||||
isRequired
|
||||
placeHolderText="Select an option"
|
||||
class="form-group" />
|
||||
<textboxQuestion
|
||||
|
|
@ -252,6 +253,35 @@ export default {
|
|||
}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.mainStore.applicationUser.firstHit) {
|
||||
const isInfoPrefilled = !!(this.mainStore.order.policy.policyNumber
|
||||
&& this.mainStore.order.policy.dateOfLoss
|
||||
&& this.mainStore.order.policy.policyZipCode);
|
||||
|
||||
this.pushEventToGA("policy_info", "info_prefilled", isInfoPrefilled.toString(), true);
|
||||
|
||||
this.$nextTick(() => {
|
||||
const formRoot = this.$el;
|
||||
const requiredFieldCount = formRoot.querySelectorAll('[aria-required="true"]').length;
|
||||
this.pushEventToGA("policy_info", "required_fields", requiredFieldCount.toString(), true);
|
||||
});
|
||||
|
||||
this.mainStore.applicationUser.firstHit = false;
|
||||
}
|
||||
|
||||
// TODO: Check if we're coming from SFA
|
||||
// eslint-disable-next-line
|
||||
if (false) {
|
||||
this.pushEventToGA("co_branded", "welcome_clicked_cta", "yes_clicked", 0);
|
||||
this.pushEventToGA("visitor_info_welcome", "referring_site", "SFA", null);
|
||||
}
|
||||
else {
|
||||
this.pushEventToGA("visitor_info_welcome", "referring_site", "ClientSite", null);
|
||||
}
|
||||
|
||||
this.pushEventToGA("visitor_info_welcome", "client_name", this.mainStore.accountNameForEvents, null);
|
||||
},
|
||||
computed: {
|
||||
DamageCauseOptions() {
|
||||
const damageCauseAnswers = this.getCmsContent(
|
||||
|
|
@ -460,6 +490,20 @@ export default {
|
|||
this.answeredContinueModal = true;
|
||||
this.$refs.continueModal.closeModal();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'welcomePageModel.policyZipCode': {
|
||||
handler: async function (newZip) {
|
||||
if (newZip.length === 5) {
|
||||
this.pushEventToGA("policy_info", "policy_zip", newZip, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
'welcomePageModel.damageCause': function (newCause) {
|
||||
if (newCause) {
|
||||
this.pushEventToGA("policy_info", "cause_of_loss", newCause, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ export default {
|
|||
hasSetting(settingName) {
|
||||
return Object.hasOwn(useMainStore().experimentSettings, settingName);
|
||||
},
|
||||
getExperimentByName(experimentName) {
|
||||
const experiment = useMainStore().applicationUser.experiments.find((e) => e.universeName === experimentName);
|
||||
return experiment ? experiment : null;
|
||||
},
|
||||
getSettingValue(settingName) {
|
||||
return this.hasSetting(settingName)
|
||||
? useMainStore().experimentSettings[settingName]
|
||||
|
|
|
|||
|
|
@ -239,7 +239,8 @@ export const getDefaultState = () => ({
|
|||
triggeredSiteEntry: false, // TODO not in save session
|
||||
duplicateOrders: [],
|
||||
hasSentSaveQuoteEmail: null,
|
||||
coverageLookupAttempts: 0
|
||||
coverageLookupAttempts: 0,
|
||||
firstHit: true
|
||||
},
|
||||
issConfig: {
|
||||
clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name
|
||||
|
|
@ -445,7 +446,8 @@ export const useMainStore = defineStore({
|
|||
.map((x) => x.settings)
|
||||
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
|
||||
originalDeductible: (state) => (state.order.damage.isRepair ? state.order.originalDeductible.repair : state.order.originalDeductible.replace),
|
||||
currentDeductible: (state) => (state.order.damage.isRepair ? state.order.currentDeductible.repair : state.order.currentDeductible.replace)
|
||||
currentDeductible: (state) => (state.order.damage.isRepair ? state.order.currentDeductible.repair : state.order.currentDeductible.replace),
|
||||
accountNameForEvents: (state) => state.issConfig.clientName.replaceAll(" ", "").replaceAll("&", "amp")
|
||||
},
|
||||
actions:
|
||||
{
|
||||
|
|
@ -2587,6 +2589,9 @@ export const useMainStore = defineStore({
|
|||
}
|
||||
);
|
||||
},
|
||||
logMobileFirstExperimentExposure() {
|
||||
this.logExperimentIfExists(issPageValues.SCHEDULE_PAGE, experimentUniverses.ISS_MOBILE_FIRST_APPOINTMENTS);
|
||||
},
|
||||
logWelcomePageExperiments(issPage) {
|
||||
this.logExperimentIfExists(issPage, experimentUniverses.ISS_FEATURETOGGLE_AREFEES_HIDDEN);
|
||||
this.logExperimentIfExists(issPage, experimentUniverses.ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN);
|
||||
|
|
|
|||
Loading…
Reference in a new issue