Merge branch 'develop' into feature/CASH-1947
This commit is contained in:
commit
f86a06a466
27 changed files with 1202 additions and 90 deletions
9
jsconfig.json
Normal file
9
jsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
5
src/assets/img/icons/info-circle-blue.svg
Normal file
5
src/assets/img/icons/info-circle-blue.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8 0C6.41775 0 4.87104 0.469192 3.55544 1.34824C2.23985 2.22729 1.21447 3.47672 0.608967 4.93853C0.00346626 6.40034 -0.15496 8.00887 0.153721 9.56072C0.462403 11.1126 1.22433 12.538 2.34315 13.6569C3.46197 14.7757 4.88743 15.5376 6.43928 15.8463C7.99113 16.155 9.59966 15.9965 11.0615 15.391C12.5233 14.7855 13.7727 13.7602 14.6518 12.4446C15.5308 11.129 16 9.58225 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34315C12.1566 0.842855 10.1217 0 8 0V0ZM8.4672 11.4C8.4672 11.5485 8.4082 11.691 8.30318 11.796C8.19816 11.901 8.05572 11.96 7.9072 11.96C7.75868 11.96 7.61624 11.901 7.51122 11.796C7.4062 11.691 7.3472 11.5485 7.3472 11.4V7.08C7.3472 6.93148 7.4062 6.78904 7.51122 6.68402C7.61624 6.579 7.75868 6.52 7.9072 6.52C8.05572 6.52 8.19816 6.579 8.30318 6.68402C8.4082 6.78904 8.4672 6.93148 8.4672 7.08V11.4ZM7.9072 5.4C7.79645 5.4 7.68818 5.36716 7.59608 5.30562C7.50399 5.24409 7.43222 5.15663 7.38983 5.0543C7.34745 4.95198 7.33636 4.83938 7.35796 4.73075C7.37957 4.62212 7.43291 4.52234 7.51122 4.44402C7.58954 4.3657 7.68932 4.31237 7.79795 4.29076C7.90658 4.26915 8.01918 4.28024 8.12151 4.32263C8.22383 4.36501 8.31129 4.43679 8.37283 4.52888C8.43436 4.62097 8.4672 4.72924 8.4672 4.84C8.46741 4.9136 8.45307 4.98651 8.42501 5.05455C8.39694 5.12259 8.3557 5.18441 8.30365 5.23645C8.25161 5.28849 8.18979 5.32973 8.12176 5.3578C8.05372 5.38587 7.9808 5.40021 7.9072 5.4Z" fill="#0070D1"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -15,6 +15,8 @@ const GaCategories = {
|
||||||
APPOINTMENT: "Appointment",
|
APPOINTMENT: "Appointment",
|
||||||
SERVICE_LOCATION: "service-location",
|
SERVICE_LOCATION: "service-location",
|
||||||
CONFIRMATION_CLICKED: "confirmation clicked",
|
CONFIRMATION_CLICKED: "confirmation clicked",
|
||||||
|
INSHOP_CONFIRMATION_CLICKED: "inshop confirmation clicked",
|
||||||
|
MOBILE_CONFIRMATION_CLICKED: "mobile confirmation clicked",
|
||||||
RECAL_DISCLAIMER: "Recalibration disclaimer",
|
RECAL_DISCLAIMER: "Recalibration disclaimer",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const experimentUniverses = {
|
||||||
IGQ_SkipQuote: "NextGen_IGQSkipToInsurance",
|
IGQ_SkipQuote: "NextGen_IGQSkipToInsurance",
|
||||||
AFTERPAY_BREAKOUT_DISPLAY: "AfterpayBreakoutDisplay",
|
AFTERPAY_BREAKOUT_DISPLAY: "AfterpayBreakoutDisplay",
|
||||||
MOBILE_FIRST_APPOINTMENT: "MobileFirstAppointment",
|
MOBILE_FIRST_APPOINTMENT: "MobileFirstAppointment",
|
||||||
|
MULTI_LOCATION_POPUP: "MultiLocationPopup",
|
||||||
};
|
};
|
||||||
|
|
||||||
const experimentSettings = {
|
const experimentSettings = {
|
||||||
|
|
@ -40,7 +41,13 @@ const experimentSettings = {
|
||||||
SHOW_MOBILE_FIRST_POPUP_REPLACE_WITHOUT_MSR: "Show_Mobile_First_Popup_Replace_Without_MSR",
|
SHOW_MOBILE_FIRST_POPUP_REPLACE_WITHOUT_MSR: "Show_Mobile_First_Popup_Replace_Without_MSR",
|
||||||
SHOW_MOBILE_FIRST_POPUP_REPLACE_WITH_MSR: "Show_Mobile_First_Popup_Replace_With_MSR",
|
SHOW_MOBILE_FIRST_POPUP_REPLACE_WITH_MSR: "Show_Mobile_First_Popup_Replace_With_MSR",
|
||||||
OFFER_OEM: "OfferOem",
|
OFFER_OEM: "OfferOem",
|
||||||
|
SHOW_MULTI_LOCATION_APPT: "ShowMultiLocationAppt",
|
||||||
|
SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION: "Show_NoAvailableDaysMultiLocation",
|
||||||
|
SHOW_NO_PM_DAYS_MULTI_LOCATION: "Show_NoPMDaysMultiLocation",
|
||||||
|
SHOW_PM_DAYS_MULTI_LOCATION: "Show_PMDaysMultiLocation",
|
||||||
USE_ADYEN_PAYMENT: "UseAdyenPayment",
|
USE_ADYEN_PAYMENT: "UseAdyenPayment",
|
||||||
|
SHOW_UNVERIFIED_DEDUCT_ENTRY: "ShowUnverifiedDeducEntry",
|
||||||
|
SERVICE_PACKAGE_ORDER: "ServicePackageOrder",
|
||||||
};
|
};
|
||||||
|
|
||||||
const experimentTriggers = {
|
const experimentTriggers = {
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ export default {
|
||||||
let classes;
|
let classes;
|
||||||
switch (this.buttonTypeString) {
|
switch (this.buttonTypeString) {
|
||||||
case "timeSlotModalListButton":
|
case "timeSlotModalListButton":
|
||||||
|
case "multiLocationRadioButton":
|
||||||
case "listButton":
|
case "listButton":
|
||||||
classes = "w-100";
|
classes = "w-100";
|
||||||
break;
|
break;
|
||||||
|
|
@ -265,6 +266,8 @@ export default {
|
||||||
classes += " radio-button-container";
|
classes += " radio-button-container";
|
||||||
} else if (this.buttonTypeString == "servicePackageRadio") {
|
} else if (this.buttonTypeString == "servicePackageRadio") {
|
||||||
classes = "package-wrapper col";
|
classes = "package-wrapper col";
|
||||||
|
} else if (this.buttonTypeString == "multiLocationRadioButton") {
|
||||||
|
classes = "multi-location-wrapper col";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length > 2) {
|
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length > 2) {
|
||||||
|
|
|
||||||
|
|
@ -31,23 +31,15 @@
|
||||||
<div class="donation-slider my-4">
|
<div class="donation-slider my-4">
|
||||||
<input
|
<input
|
||||||
name="amount"
|
name="amount"
|
||||||
id="one"
|
id="zero"
|
||||||
type="radio"
|
type="radio"
|
||||||
:value="donationValues[0]"
|
:value="donationValues[0]"
|
||||||
checked />
|
checked />
|
||||||
<label for="one" class="label">$1</label>
|
<label for="zero" class="label">${{ donationValues[0] }}</label>
|
||||||
<input
|
<input name="amount" id="one" type="radio" :value="donationValues[1]" />
|
||||||
name="amount"
|
<label for="one" class="label">${{ donationValues[1] }}</label>
|
||||||
id="three"
|
<input name="amount" id="two" type="radio" :value="donationValues[2]" />
|
||||||
type="radio"
|
<label for="two" class="label">${{ donationValues[2] }}</label>
|
||||||
:value="donationValues[1]" />
|
|
||||||
<label for="three" class="label">$3</label>
|
|
||||||
<input
|
|
||||||
name="amount"
|
|
||||||
id="five"
|
|
||||||
type="radio"
|
|
||||||
:value="donationValues[2]" />
|
|
||||||
<label for="five" class="label">$5</label>
|
|
||||||
<div class="indicator-bar"></div>
|
<div class="indicator-bar"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -264,17 +256,17 @@ export default {
|
||||||
background: $blue;
|
background: $blue;
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
&#one:checked {
|
&#zero:checked {
|
||||||
~ .indicator-bar {
|
~ .indicator-bar {
|
||||||
transform: translateX(2rem);
|
transform: translateX(2rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&#three:checked {
|
&#one:checked {
|
||||||
~ .indicator-bar {
|
~ .indicator-bar {
|
||||||
transform: translateX(calc(100% + 2rem));
|
transform: translateX(calc(100% + 2rem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&#five:checked {
|
&#two:checked {
|
||||||
~ .indicator-bar {
|
~ .indicator-bar {
|
||||||
transform: translateX(calc(200% + 2rem));
|
transform: translateX(calc(200% + 2rem));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,10 @@ export default {
|
||||||
serviceLocationZipCode:
|
serviceLocationZipCode:
|
||||||
store.getters.order.serviceLocation.zipCode,
|
store.getters.order.serviceLocation.zipCode,
|
||||||
pricedLineItems: pricedLineItemsToTax,
|
pricedLineItems: pricedLineItemsToTax,
|
||||||
|
serviceLocationAddressLine1:
|
||||||
|
store.getters.order.serviceLocation?.address,
|
||||||
|
serviceLocationAddressLine2:
|
||||||
|
store.getters.order.serviceLocation?.address2,
|
||||||
},
|
},
|
||||||
"payment-method",
|
"payment-method",
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ export async function loadSessionIfPresent(isConceptInsurance, pageNameToLog) {
|
||||||
// Do nothing if there is no cookie or session to use for loading.
|
// Do nothing if there is no cookie or session to use for loading.
|
||||||
if (
|
if (
|
||||||
funnelCookie == null ||
|
funnelCookie == null ||
|
||||||
funnelCookie.SavedSessionId == null ||
|
|
||||||
funnelCookie.ReferralCorrelationId == null ||
|
funnelCookie.ReferralCorrelationId == null ||
|
||||||
!funnelCookie.ReferralNumber
|
!funnelCookie.ReferralNumber
|
||||||
) {
|
) {
|
||||||
|
|
@ -128,7 +127,7 @@ async function loadSession(
|
||||||
const response = await baseMixin.methods.dispatchStoreActionWithLogging(
|
const response = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
storeActions.LOAD_SESSION,
|
storeActions.LOAD_SESSION,
|
||||||
{
|
{
|
||||||
savedSessionId: savedSessionId?.toString(),
|
savedSessionId: savedSessionId != null ? savedSessionId.toString() : null,
|
||||||
referralNumber,
|
referralNumber,
|
||||||
referralDate,
|
referralDate,
|
||||||
referralCorrelationId,
|
referralCorrelationId,
|
||||||
|
|
|
||||||
11
src/helpers/vehicle-helper.js
Normal file
11
src/helpers/vehicle-helper.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
|
export function hasVehicleInfo() {
|
||||||
|
return (
|
||||||
|
store.getters.order?.vehicle.year &&
|
||||||
|
store.getters.order?.vehicle?.make &&
|
||||||
|
store.getters.order?.vehicle?.model &&
|
||||||
|
store.getters.order?.vehicle?.style &&
|
||||||
|
store.getters.order?.vehicle?.carId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -470,7 +470,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
donationValues() {
|
donationValues() {
|
||||||
return [1, 3, 5];
|
var donationAmounts = this.getCmsContent("DonationAmountsWidget", "Text");
|
||||||
|
return donationAmounts.split(",").map(Number);
|
||||||
},
|
},
|
||||||
lineItemsWithoutDonation() {
|
lineItemsWithoutDonation() {
|
||||||
const lineItemsClone = deepClone(this.lineItems);
|
const lineItemsClone = deepClone(this.lineItems);
|
||||||
|
|
@ -531,10 +532,11 @@ export default {
|
||||||
window.location.assign(location.protocol + "//" + location.host);
|
window.location.assign(location.protocol + "//" + location.host);
|
||||||
},
|
},
|
||||||
async addDonationToOrder() {
|
async addDonationToOrder() {
|
||||||
|
const selectedItemNumber = this.donationValues.indexOf(Number(this.donationAmount));
|
||||||
const donationResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
const donationResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
storeActions.SUBMIT_DONATION_PART_TO_SV2,
|
storeActions.SUBMIT_DONATION_PART_TO_SV2,
|
||||||
{
|
{
|
||||||
donationAmount: parseInt(this.donationAmount),
|
selectedItemNumber: selectedItemNumber,
|
||||||
submittedOrder: this.submittedOrder,
|
submittedOrder: this.submittedOrder,
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,9 @@ function setupMocks() {
|
||||||
vehicle: { year: 2020, make: "Toyota", model: "Camry", carId: "12345" },
|
vehicle: { year: 2020, make: "Toyota", model: "Camry", carId: "12345" },
|
||||||
damage: { isRepair: false },
|
damage: { isRepair: false },
|
||||||
},
|
},
|
||||||
|
applicationUser: {
|
||||||
|
experiments: [],
|
||||||
|
},
|
||||||
policy: {
|
policy: {
|
||||||
currentDeductible: 123,
|
currentDeductible: 123,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,17 @@
|
||||||
v-bind:isDismissible="false" />
|
v-bind:isDismissible="false" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<alert
|
||||||
|
ref="UnverifiedInsuranceAlert"
|
||||||
|
class="mt-5 mb-5"
|
||||||
|
cmsWidgetName="UnverifiedInsuranceAlertWidget"
|
||||||
|
v-if="shouldDisplayUnverifiedInsuranceAlert"
|
||||||
|
:showInfoIcon="true"
|
||||||
|
:showHorizontalRow="true"
|
||||||
|
alertClass="alert-info" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="isRecalibrationOnOrder && shouldHideRecalibration"
|
v-if="isRecalibrationOnOrder && shouldHideRecalibration"
|
||||||
class="questions-about-service mt-5">
|
class="questions-about-service mt-5">
|
||||||
|
|
@ -311,6 +322,8 @@ export default {
|
||||||
serviceLocationState: store.getters.order.serviceLocation.state,
|
serviceLocationState: store.getters.order.serviceLocation.state,
|
||||||
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
||||||
pricedLineItems: lineItemsForCart,
|
pricedLineItems: lineItemsForCart,
|
||||||
|
serviceLocationAddressLine1: store.getters.order.serviceLocation?.address,
|
||||||
|
serviceLocationAddressLine2: store.getters.order.serviceLocation?.address2,
|
||||||
},
|
},
|
||||||
"payment-method",
|
"payment-method",
|
||||||
false
|
false
|
||||||
|
|
@ -515,6 +528,10 @@ export default {
|
||||||
serviceLocationState: this.$store.getters.order.serviceLocation.state,
|
serviceLocationState: this.$store.getters.order.serviceLocation.state,
|
||||||
serviceLocationZipCode: this.$store.getters.order.serviceLocation.zipCode,
|
serviceLocationZipCode: this.$store.getters.order.serviceLocation.zipCode,
|
||||||
pricedLineItems: this.lineItems,
|
pricedLineItems: this.lineItems,
|
||||||
|
serviceLocationAddressLine1:
|
||||||
|
this.$store.getters.order.serviceLocation?.address,
|
||||||
|
serviceLocationAddressLine2:
|
||||||
|
this.$store.getters.order.serviceLocation?.address2,
|
||||||
},
|
},
|
||||||
"payment-method",
|
"payment-method",
|
||||||
false
|
false
|
||||||
|
|
@ -639,6 +656,8 @@ export default {
|
||||||
serviceLocationState: store.getters.order.serviceLocation.state,
|
serviceLocationState: store.getters.order.serviceLocation.state,
|
||||||
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
||||||
pricedLineItems: this.lineItems,
|
pricedLineItems: this.lineItems,
|
||||||
|
serviceLocationAddressLine1: store.getters.order.serviceLocation?.address,
|
||||||
|
serviceLocationAddressLine2: store.getters.order.serviceLocation?.address2,
|
||||||
},
|
},
|
||||||
"payment-method",
|
"payment-method",
|
||||||
false
|
false
|
||||||
|
|
@ -865,6 +884,13 @@ export default {
|
||||||
shouldDisplayPiaAlert() {
|
shouldDisplayPiaAlert() {
|
||||||
return getBoolFromString(this.$route?.query?.piaError);
|
return getBoolFromString(this.$route?.query?.piaError);
|
||||||
},
|
},
|
||||||
|
shouldDisplayUnverifiedInsuranceAlert() {
|
||||||
|
const shouldDisplayUnverifiedDeduct = experimentMixin.methods.hasSettingEqualTo(
|
||||||
|
experimentSettings.SHOW_UNVERIFIED_DEDUCT_ENTRY,
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
return shouldDisplayUnverifiedDeduct;
|
||||||
|
},
|
||||||
// Necessary to make the watcher of lineItems work
|
// Necessary to make the watcher of lineItems work
|
||||||
// JavaScript does not keep a record of the old value, only a reference to it's location in the memory.
|
// JavaScript does not keep a record of the old value, only a reference to it's location in the memory.
|
||||||
// This creates a separate location for oldValue/newValue so they can be compared
|
// This creates a separate location for oldValue/newValue so they can be compared
|
||||||
|
|
@ -1033,7 +1059,15 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert.alert-info.widget-name-UnverifiedInsuranceAlertWidget {
|
||||||
|
:deep(.alert-heading) {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(span.label),
|
:deep(span.label),
|
||||||
:deep(span) {
|
:deep(span) {
|
||||||
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ jest.mock("@/mixins/experiment-mixin.js", () => ({
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
hasSetting(settingName) {
|
||||||
|
if (settingName === mockExperimentSettings.PROMO_ON_PACKAGE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
hasSettingEqualTo(settingName, settingValue) {
|
hasSettingEqualTo(settingName, settingValue) {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,16 @@ export default {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
|
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
|
||||||
|
|
||||||
|
if (this.experimentPackageOrder()) {
|
||||||
|
const orderedKeys = this.experimentPackageOrder()
|
||||||
|
.split(",")
|
||||||
|
.map((key) => key.trim());
|
||||||
|
modifiedAnswers.sort(
|
||||||
|
(a, b) => orderedKeys.indexOf(a.value) - orderedKeys.indexOf(b.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return modifiedAnswers;
|
return modifiedAnswers;
|
||||||
},
|
},
|
||||||
isDiscountOnOrder() {
|
isDiscountOnOrder() {
|
||||||
|
|
@ -234,6 +244,11 @@ export default {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
experimentPackageOrder() {
|
||||||
|
return experimentMixin.methods.hasSetting(experimentSettings.SERVICE_PACKAGE_ORDER)
|
||||||
|
? experimentMixin.methods.getSettingValue(experimentSettings.SERVICE_PACKAGE_ORDER)
|
||||||
|
: false;
|
||||||
|
},
|
||||||
getButtonTypeObject() {
|
getButtonTypeObject() {
|
||||||
if (this.isAfterpayBreakoutDisplay()) {
|
if (this.isAfterpayBreakoutDisplay()) {
|
||||||
return this.servicePackageRadioAfterpayExperiment;
|
return this.servicePackageRadioAfterpayExperiment;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,345 @@
|
||||||
|
<template>
|
||||||
|
<div id="multi-location-modal-container">
|
||||||
|
<modal
|
||||||
|
:ref="modalName"
|
||||||
|
:headerText="modalHeaderText"
|
||||||
|
suppressPageScroll
|
||||||
|
:onModalClosedCallback="onModalClosed"
|
||||||
|
:footerButtonText="modalFooterText"
|
||||||
|
:isFooterButtonPrimary="true"
|
||||||
|
@footer-button-event="confirmAppointment"
|
||||||
|
@isModalOpened="setIsModalOpen">
|
||||||
|
<buttonQuestion
|
||||||
|
ref="buttonQuestion"
|
||||||
|
buttonTypeString="multiLocationRadioButton"
|
||||||
|
:buttonTypeObject="multiLocationRadioButton"
|
||||||
|
class="mt-5"
|
||||||
|
:answers="availableAppointments"
|
||||||
|
groupName="multiLocationQuestion"
|
||||||
|
textPosition="text-center"
|
||||||
|
v-model="selectedValue"
|
||||||
|
isRequired
|
||||||
|
validationRules="time-slot-required" />
|
||||||
|
|
||||||
|
<template v-slot:modal-header-slot>
|
||||||
|
<span>{{ modalSubHeaderText }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:modal-footer-slot>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-link"
|
||||||
|
id="see-more-options"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="closeModal">
|
||||||
|
{{ buttonText }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Supporting files
|
||||||
|
import store from "@/store";
|
||||||
|
import modal from "@/digital-components/modal/modal";
|
||||||
|
import { splitCopyOnCMSPlaceHolder } from "@/helpers/cms-content-helper";
|
||||||
|
import { get12HourTimeFormat } from "@/helpers/date-helper";
|
||||||
|
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
||||||
|
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||||
|
import multiLocationRadioButton from "./multi-location-radio/multi-location-radio";
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
import { defineRule, useField } from "vee-validate";
|
||||||
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
|
||||||
|
const NULL_APPOINTMENT = {
|
||||||
|
estimatedServiceMinutes: {
|
||||||
|
minimum: null,
|
||||||
|
maximum: null,
|
||||||
|
},
|
||||||
|
timeSlot: null,
|
||||||
|
date: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validation for the modal button
|
||||||
|
defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "multi-location-modal",
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: String,
|
||||||
|
modalWidgetName: String,
|
||||||
|
buttonWidgetName: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isModalOpen: false,
|
||||||
|
mobileAppointmentOption: NULL_APPOINTMENT,
|
||||||
|
inshopAppointmentOption: NULL_APPOINTMENT,
|
||||||
|
confirmedAppointment: false,
|
||||||
|
multiLocationRadioButton: multiLocationRadioButton,
|
||||||
|
selectedValue: null,
|
||||||
|
experimentSettings: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
get12HourTimeFormat,
|
||||||
|
openModal() {
|
||||||
|
this.modal.openModal();
|
||||||
|
},
|
||||||
|
onModalClosed() {
|
||||||
|
this.pushEvent();
|
||||||
|
this.confirmedAppointment = false;
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.modal.closeModal();
|
||||||
|
},
|
||||||
|
confirmAppointment() {
|
||||||
|
if (!this.selectedAppointment.timeSlot.id) return;
|
||||||
|
|
||||||
|
const selectedTimeSlot = {
|
||||||
|
timeSlot: this.selectedAppointment.timeSlot,
|
||||||
|
routeCode: this.selectedAppointment.timeSlot.id,
|
||||||
|
date: this.selectedAppointment.date,
|
||||||
|
};
|
||||||
|
this.confirmedAppointment = true;
|
||||||
|
this.$emit("confirm-appointment", {
|
||||||
|
selectedTimeSlot: selectedTimeSlot,
|
||||||
|
appointmentType: this.selectedValue,
|
||||||
|
});
|
||||||
|
this.modal.closeModal();
|
||||||
|
},
|
||||||
|
setPromotedMobileAppointment(appointment) {
|
||||||
|
this.mobileAppointmentOption = appointment;
|
||||||
|
},
|
||||||
|
setPromotedInshopAppointment(appointment) {
|
||||||
|
this.inshopAppointmentOption = appointment;
|
||||||
|
},
|
||||||
|
getIsModalOpen() {
|
||||||
|
return this.isModalOpen;
|
||||||
|
},
|
||||||
|
setIsModalOpen(isOpen) {
|
||||||
|
this.isModalOpen = isOpen;
|
||||||
|
},
|
||||||
|
captureExperimentSettings(settingsString) {
|
||||||
|
this.experimentSettings = settingsString;
|
||||||
|
},
|
||||||
|
pushEvent() {
|
||||||
|
let gaAction = this.confirmedAppointment ? this.GaActions.YES : this.GaActions.NO;
|
||||||
|
if (
|
||||||
|
this.selectedValue?.toLowerCase() === "mobile" &&
|
||||||
|
this.mobileAppointmentOption?.timeSlot
|
||||||
|
) {
|
||||||
|
this.pushEventToGA(
|
||||||
|
this.GaCategories.MOBILE_CONFIRMATION_CLICKED,
|
||||||
|
gaAction,
|
||||||
|
this.mobileAppointmentOption.timeSlot.startTime +
|
||||||
|
"-" +
|
||||||
|
this.mobileAppointmentOption.timeSlot.endTime +
|
||||||
|
" " +
|
||||||
|
this.mobileAppointmentOption.date,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
this.experimentSettings
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
this.selectedValue?.toLowerCase() === "inshop" &&
|
||||||
|
this.inshopAppointmentOption?.timeSlot
|
||||||
|
) {
|
||||||
|
this.pushEventToGA(
|
||||||
|
this.GaCategories.INSHOP_CONFIRMATION_CLICKED,
|
||||||
|
gaAction,
|
||||||
|
this.inshopAppointmentOption.timeSlot.startTime +
|
||||||
|
"-" +
|
||||||
|
this.inshopAppointmentOption.date,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
this.experimentSettings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedAppointment() {
|
||||||
|
if (this.selectedValue?.toLowerCase() === "mobile") {
|
||||||
|
return this.mobileAppointmentOption;
|
||||||
|
} else if (this.selectedValue?.toLowerCase() === "inshop") {
|
||||||
|
return this.inshopAppointmentOption;
|
||||||
|
} else {
|
||||||
|
return NULL_APPOINTMENT;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modalName() {
|
||||||
|
return this.modalWidgetName;
|
||||||
|
},
|
||||||
|
modal() {
|
||||||
|
return this.$refs[this.modalName];
|
||||||
|
},
|
||||||
|
modalSubHeaderText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "SubheaderText");
|
||||||
|
},
|
||||||
|
modalHeaderText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
modalFooterText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
||||||
|
},
|
||||||
|
buttonText() {
|
||||||
|
return this.getCmsContent(this.modalWidgetName, "FooterText2");
|
||||||
|
},
|
||||||
|
mobileButtonLabel() {
|
||||||
|
return this.getCmsContent(this.buttonWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
inshopButtonLabel() {
|
||||||
|
return this.getCmsContent(this.buttonWidgetName, "SubheaderText");
|
||||||
|
},
|
||||||
|
mobileButtonDate() {
|
||||||
|
if (!this.mobileAppointmentOption.date) return "";
|
||||||
|
return new Date(this.mobileAppointmentOption.date).toLocaleDateString("en-US", {
|
||||||
|
timeZone: "UTC",
|
||||||
|
weekday: "short",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
inshopButtonDate() {
|
||||||
|
if (!this.inshopAppointmentOption.date) return "";
|
||||||
|
return new Date(this.inshopAppointmentOption.date).toLocaleDateString("en-US", {
|
||||||
|
timeZone: "UTC",
|
||||||
|
weekday: "short",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
mobileButtonTime() {
|
||||||
|
const timeslot =
|
||||||
|
this.get12HourTimeFormat(this.mobileAppointmentOption.timeSlot?.startTime) +
|
||||||
|
" - " +
|
||||||
|
this.get12HourTimeFormat(this.mobileAppointmentOption.timeSlot?.endTime);
|
||||||
|
return timeslot;
|
||||||
|
},
|
||||||
|
inshopButtonTime() {
|
||||||
|
const timeslot = this.get12HourTimeFormat(
|
||||||
|
this.inshopAppointmentOption.timeSlot?.startTime
|
||||||
|
);
|
||||||
|
return timeslot;
|
||||||
|
},
|
||||||
|
mobileLocationLabel() {
|
||||||
|
return this.getCmsContent(this.buttonWidgetName, "BodyText");
|
||||||
|
},
|
||||||
|
inshopLocationLabel() {
|
||||||
|
return this.inshopAppointmentOption?.addressCopy;
|
||||||
|
},
|
||||||
|
mobileServiceDuration() {
|
||||||
|
const durationTime = getDisplayTextForDurationLength(
|
||||||
|
this.mobileAppointmentOption.estimatedServiceMinutes.minimum,
|
||||||
|
this.mobileAppointmentOption.estimatedServiceMinutes.maximum
|
||||||
|
);
|
||||||
|
const footerText = this.getCmsContent(this.buttonWidgetName, "FooterText").replaceAll(
|
||||||
|
"{custom:serviceLength}",
|
||||||
|
durationTime
|
||||||
|
);
|
||||||
|
return footerText;
|
||||||
|
},
|
||||||
|
inshopServiceDuration() {
|
||||||
|
const durationTime = getDisplayTextForDurationLength(
|
||||||
|
this.inshopAppointmentOption.estimatedServiceMinutes.minimum,
|
||||||
|
this.inshopAppointmentOption.estimatedServiceMinutes.maximum
|
||||||
|
);
|
||||||
|
const footerText = this.getCmsContent(this.buttonWidgetName, "FooterText").replaceAll(
|
||||||
|
"{custom:serviceLength}",
|
||||||
|
durationTime
|
||||||
|
);
|
||||||
|
return footerText;
|
||||||
|
},
|
||||||
|
availableAppointments() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: "mobile",
|
||||||
|
buttonLabel: this.mobileButtonDate,
|
||||||
|
buttonLabelSubCopy: this.mobileButtonTime,
|
||||||
|
buttonBodyCopy: this.mobileLocationLabel,
|
||||||
|
buttonAuxillaryCopy: this.mobileButtonLabel,
|
||||||
|
buttonFooterCopy: this.mobileServiceDuration,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "inshop",
|
||||||
|
buttonLabel: this.inshopButtonDate,
|
||||||
|
buttonLabelSubCopy: this.inshopButtonTime,
|
||||||
|
buttonBodyCopy: this.inshopLocationLabel,
|
||||||
|
buttonAuxillaryCopy: this.inshopButtonLabel,
|
||||||
|
buttonFooterCopy: this.inshopServiceDuration,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { modal, buttonQuestion },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
#multi-location-modal-container {
|
||||||
|
.modal-component {
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
.modal-dialog {
|
||||||
|
left: 0;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 22.063rem;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-dialog {
|
||||||
|
.modal-content {
|
||||||
|
.modal-header {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
color: $red;
|
||||||
|
font-weight: $font-weight-600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: $font-family-sans-serif-semibold;
|
||||||
|
}
|
||||||
|
.modal-title {
|
||||||
|
font-size: $font-size-20;
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-body {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
|
.button-question {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body-inner {
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
font-size: $font-size-14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
flex-flow: wrap-reverse;
|
||||||
|
|
||||||
|
#see-more-options {
|
||||||
|
width: 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
font-weight: $font-weight-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { mount } from "@vue/test-utils";
|
||||||
|
import multiLocationRadio from "./multi-location-radio";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
|
describe("multi-location-radio.vue", () => {
|
||||||
|
it("Should include buttonLabel in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should include buttonLabelSubCopy in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"]));
|
||||||
|
});
|
||||||
|
it("Should include buttonFooterCopy in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonFooterCopy"]));
|
||||||
|
});
|
||||||
|
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a <ul><li>...</li>(x5)</ul> buttonBodyCopy", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Act
|
||||||
|
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(results.length).toBe(5);
|
||||||
|
});
|
||||||
|
it("Should get strings from getArrayOfListItemsFromRawCmsCopy without <ul> or <li> tags when provided with a <ul><li>...</li></ul> buttonBodyCopy", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const fileredResults = results.filter((result) => {
|
||||||
|
return (
|
||||||
|
result.includes("<ul>") ||
|
||||||
|
result.includes("</ul>") ||
|
||||||
|
result.includes("<li>") ||
|
||||||
|
result.includes("</li>")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(fileredResults.length).toBe(0);
|
||||||
|
});
|
||||||
|
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when buttonBodyCopy has 6 total <li>...</li>, but one is empty ", async () => {
|
||||||
|
// Arrange
|
||||||
|
const moddedProps = mockProps;
|
||||||
|
moddedProps["buttonBodyCopy"] =
|
||||||
|
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li><li></li></ul>";
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: moddedProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(results.length).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockProps = {
|
||||||
|
groupName: "group-name",
|
||||||
|
buttonLabel: "buttonLabel test copy",
|
||||||
|
buttonLabelAuxillaryCopy: "buttonLabelAuxillaryCopy test copy",
|
||||||
|
buttonLabelSubCopy: "buttonLabelSubCopy test copy",
|
||||||
|
buttonBodyCopy:
|
||||||
|
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>",
|
||||||
|
buttonFooterCopy: "buttonFooterCopy test copy",
|
||||||
|
buttonAuxillaryCopy: "buttonAuxillaryCopy test copy",
|
||||||
|
};
|
||||||
|
|
||||||
|
function setupMocks({ mountOptionsMockData = {} }) {
|
||||||
|
const wrapper = mount(multiLocationRadio, {
|
||||||
|
...mountOptionsMockData,
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,311 @@
|
||||||
|
<template>
|
||||||
|
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
|
||||||
|
<div class="radio-label" for="testradio">
|
||||||
|
<div class="location-specs">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col md-6">
|
||||||
|
<p class="m-0">
|
||||||
|
<span v-html="this.buttonLabel"></span>
|
||||||
|
</p>
|
||||||
|
<span class="service-tag" v-html="this.buttonAuxillaryCopy"></span>
|
||||||
|
<p
|
||||||
|
class="sub-label m-0"
|
||||||
|
v-if="this.buttonLabelSubCopy"
|
||||||
|
v-html="this.buttonLabelSubCopy"></p>
|
||||||
|
</div>
|
||||||
|
<div class="col md-6 location-info">
|
||||||
|
<div
|
||||||
|
class="location-info-label"
|
||||||
|
v-if="this.buttonBodyCopy && this.value === 'mobile'"
|
||||||
|
v-html="this.buttonBodyCopy"></div>
|
||||||
|
<div
|
||||||
|
class="location-info-label"
|
||||||
|
v-if="this.buttonBodyCopy && this.value === 'inshop'">
|
||||||
|
<textLink
|
||||||
|
linkType="newWindowLink"
|
||||||
|
:text="this.buttonBodyCopy"
|
||||||
|
:href="inshopHref"
|
||||||
|
target="_blank" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col md-6 time-info" v-html="this.buttonFooterCopy"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</baseInputButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
|
||||||
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
import {
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
} from "@/helpers/cms-content-helper";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "multiLocationRadioButton",
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
components: {
|
||||||
|
baseInputButton,
|
||||||
|
textLink,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
arrayOfListItemsFromBodyText() {
|
||||||
|
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
|
||||||
|
},
|
||||||
|
shouldDisplayStrikeThroughPrice() {
|
||||||
|
const displayPrice = this.buttonAuxillaryCopy?.substring(
|
||||||
|
this.buttonAuxillaryCopy.indexOf("$")
|
||||||
|
);
|
||||||
|
return displayPrice != this.additionalButtonData?.strikeThroughPrice;
|
||||||
|
},
|
||||||
|
isAppleBrowser() {
|
||||||
|
return baseMixin.methods.isAppleBrowser();
|
||||||
|
},
|
||||||
|
inshopHref() {
|
||||||
|
if (this.isAppleBrowser) {
|
||||||
|
return "https://maps.apple.com/?q=" + encodeURIComponent(this.buttonBodyCopy) + '"';
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
"https://www.google.com/maps/search/?api=1&query=" +
|
||||||
|
encodeURIComponent(this.buttonBodyCopy) +
|
||||||
|
'"'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
stripUlTagFromCopy(copy) {
|
||||||
|
const regex = /(?:<ul(?:.*?)>)|(?:<\/ul>)/g;
|
||||||
|
return copy?.replace(regex, "");
|
||||||
|
},
|
||||||
|
getArrayOfListItemsFromRawCmsCopy(copy) {
|
||||||
|
const withoutUlTags = this.stripUlTagFromCopy(copy);
|
||||||
|
return withoutUlTags
|
||||||
|
?.split(/(?:<li(?:.*?)>)|(?:<\/li>)/g)
|
||||||
|
?.filter((lineItem) => lineItem);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.multi-location-wrapper {
|
||||||
|
margin: 1rem 0;
|
||||||
|
&:first-child {
|
||||||
|
margin: 1rem 0 0 0;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin: 1rem 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: -9999px;
|
||||||
|
|
||||||
|
+ .radio-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid $gray-300;
|
||||||
|
box-shadow:
|
||||||
|
0px 4px 8px -4px rgba(0, 0, 0, 0.15),
|
||||||
|
0px 4px 24px -8px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 52px;
|
||||||
|
max-height: 126px;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
}
|
||||||
|
transition: max-height 0.25s ease-in;
|
||||||
|
|
||||||
|
&.has-subheader {
|
||||||
|
min-height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
position: relative;
|
||||||
|
top: 5px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
min-width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
min-width: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
+ .radio-label {
|
||||||
|
&:before {
|
||||||
|
border: 1px solid #8e9292;
|
||||||
|
box-shadow:
|
||||||
|
0px 0px 0px 4px #9fcee6,
|
||||||
|
0px 1px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
+ .radio-label {
|
||||||
|
.location-specs {
|
||||||
|
.hide-when-closed {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.service-tag {
|
||||||
|
background: $blue;
|
||||||
|
color: $blue-150;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .radio-label {
|
||||||
|
border: 1px solid $blue;
|
||||||
|
background-color: $blue-100;
|
||||||
|
}
|
||||||
|
+ .radio-label {
|
||||||
|
&:before {
|
||||||
|
box-shadow: 0px 0px 0px 1px $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .radio-label {
|
||||||
|
&:after {
|
||||||
|
background: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
+ .radio-label {
|
||||||
|
&:before {
|
||||||
|
border: 2px solid $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.col {
|
||||||
|
flex: 1 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-tag {
|
||||||
|
font-size: $font-size-12;
|
||||||
|
font-family: $font-family-sans-serif-semibold;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
background: $blue-150;
|
||||||
|
color: $blue;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: $font-size-14;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
color: $black;
|
||||||
|
font-family: $font-family-sans-serif-semibold;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sub-label {
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
border-bottom: 1px solid $gray-1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-info-label,
|
||||||
|
.time-info {
|
||||||
|
font-family: $font-family-sans-serif;
|
||||||
|
font-size: $font-size-12;
|
||||||
|
}
|
||||||
|
.location-info-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
margin-right: 0.375rem;
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 1rem;
|
||||||
|
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 50%;
|
||||||
|
background-size: 0.75rem auto;
|
||||||
|
padding: 0 0.75rem 0 0;
|
||||||
|
@include blue-filter();
|
||||||
|
}
|
||||||
|
& > a.new-window-link {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-family: $font-family-sans-serif;
|
||||||
|
font-size: $font-size-12;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideaway {
|
||||||
|
from {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(40px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-when-closed {
|
||||||
|
display: none;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -5,7 +5,12 @@
|
||||||
<mobileFirstModal
|
<mobileFirstModal
|
||||||
modalWidgetName="MobileFirstModalWidget"
|
modalWidgetName="MobileFirstModalWidget"
|
||||||
ref="mobileFirstModal"
|
ref="mobileFirstModal"
|
||||||
@confirm-appointment="updateTimeSlotandNavigateForward" />
|
@confirm-appointment="updateMobileFirstTimeSlotandNavigateForward" />
|
||||||
|
<multiLocationModal
|
||||||
|
modalWidgetName="MultiLocationModalWidget"
|
||||||
|
buttonWidgetName="MultiLocationRadioButtonWidget"
|
||||||
|
ref="multiLocationModal"
|
||||||
|
@confirm-appointment="updateMultiLocationTimeSlotandNavigateForward" />
|
||||||
<recalAckModal
|
<recalAckModal
|
||||||
modalWidgetName="RecalAckModalWidget"
|
modalWidgetName="RecalAckModalWidget"
|
||||||
agreementWidgetName="RecalAgreementQuestionWidget"
|
agreementWidgetName="RecalAgreementQuestionWidget"
|
||||||
|
|
@ -14,7 +19,9 @@
|
||||||
ref="recalAckModal" />
|
ref="recalAckModal" />
|
||||||
<div
|
<div
|
||||||
class="container page-container-grouped-styles"
|
class="container page-container-grouped-styles"
|
||||||
:class="[isMobileFirstModalOpen ? 'hidden-background' : '']">
|
:class="[
|
||||||
|
isMobileFirstModalOpen || isMultiLocationModalOpen ? 'hidden-background' : '',
|
||||||
|
]">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||||
<mobileFeeWaiverAlert
|
<mobileFeeWaiverAlert
|
||||||
|
|
@ -28,7 +35,6 @@
|
||||||
cmsWidgetName="ScheduleYourServiceWidget"
|
cmsWidgetName="ScheduleYourServiceWidget"
|
||||||
id="schedule-your-service" />
|
id="schedule-your-service" />
|
||||||
<appointmentTypeQuestion
|
<appointmentTypeQuestion
|
||||||
v-if="hasSelectableDatesLoaded"
|
|
||||||
v-model="appointmentTypeFromAppointmentTypeQuestion"
|
v-model="appointmentTypeFromAppointmentTypeQuestion"
|
||||||
v-show="isAppointmentTypeDisplayed"
|
v-show="isAppointmentTypeDisplayed"
|
||||||
:isServiceableMobile="isServiceableMobile"
|
:isServiceableMobile="isServiceableMobile"
|
||||||
|
|
@ -36,7 +42,6 @@
|
||||||
:isDisplayed="isAppointmentTypeDisplayed"
|
:isDisplayed="isAppointmentTypeDisplayed"
|
||||||
:mobileFeeApplies="mobileFeeApplies"
|
:mobileFeeApplies="mobileFeeApplies"
|
||||||
:zipCode="zipCode"
|
:zipCode="zipCode"
|
||||||
ref="appointmentTypeQuestion"
|
|
||||||
groupName="appointmentTypeQuestion"
|
groupName="appointmentTypeQuestion"
|
||||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||||
validationRules="option-required"
|
validationRules="option-required"
|
||||||
|
|
@ -193,6 +198,7 @@ import shopLocation from "@/layouts/schedule/shop-location/shop-location";
|
||||||
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
|
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
|
||||||
import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue";
|
import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue";
|
||||||
import mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-modal.vue";
|
import mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-modal.vue";
|
||||||
|
import multiLocationModal from "@/layouts/schedule/multi-location-modal/multi-location-modal.vue";
|
||||||
import recalAckModal from "@/layouts/schedule/recal-ack-modal/recal-ack-modal.vue";
|
import recalAckModal from "@/layouts/schedule/recal-ack-modal/recal-ack-modal.vue";
|
||||||
|
|
||||||
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
||||||
|
|
@ -479,10 +485,7 @@ export default {
|
||||||
shopListButton: shopListButton,
|
shopListButton: shopListButton,
|
||||||
lastSelectedInshopOrDropoffProvider: null,
|
lastSelectedInshopOrDropoffProvider: null,
|
||||||
selectedMobileFirstAppointment: false,
|
selectedMobileFirstAppointment: false,
|
||||||
isShowMobileFirstAppt: experimentMixin.methods.hasSettingEqualTo(
|
selectedMultiLocationAppointment: false,
|
||||||
experimentSettings.SHOW_MOBILE_FIRST_APPT,
|
|
||||||
"true"
|
|
||||||
),
|
|
||||||
calendarLoadingStatus: "none",
|
calendarLoadingStatus: "none",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -615,7 +618,8 @@ export default {
|
||||||
shopProviderData.data
|
shopProviderData.data
|
||||||
);
|
);
|
||||||
vm.initializeDatePicker().then(() => {
|
vm.initializeDatePicker().then(() => {
|
||||||
vm.showMobileFirstModal();
|
// vm.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST
|
||||||
|
vm.showMultiLocationModal();
|
||||||
vm.hideLoadingModal();
|
vm.hideLoadingModal();
|
||||||
vm.calendarLoadingStatus = "none";
|
vm.calendarLoadingStatus = "none";
|
||||||
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
|
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
|
||||||
|
|
@ -904,6 +908,11 @@ export default {
|
||||||
? this.$refs.mobileFirstModal?.getIsModalOpen()
|
? this.$refs.mobileFirstModal?.getIsModalOpen()
|
||||||
: false;
|
: false;
|
||||||
},
|
},
|
||||||
|
isMultiLocationModalOpen() {
|
||||||
|
return this.selectableDatesMobile.days && this.selectableDatesInshop.days
|
||||||
|
? this.$refs.multiLocationModal?.getIsModalOpen()
|
||||||
|
: false;
|
||||||
|
},
|
||||||
displayWaitList() {
|
displayWaitList() {
|
||||||
if (!this.appointmentType) return false;
|
if (!this.appointmentType) return false;
|
||||||
const firstMobileDateString =
|
const firstMobileDateString =
|
||||||
|
|
@ -952,12 +961,24 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
hasSelectableDatesLoaded() {
|
hasSelectableDatesLoaded() {
|
||||||
if (this.isMobileSelected) {
|
if (this.isServiceableMobile) {
|
||||||
return this.selectableDatesMobile?.days?.length > 0;
|
return this.selectableDatesMobile?.days?.length > 0;
|
||||||
} else {
|
} else {
|
||||||
return this.selectableDatesInshop?.days?.length > 0;
|
return this.selectableDatesInshop?.days?.length > 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isShowMobileFirstAppt() {
|
||||||
|
return experimentMixin.methods.hasSettingEqualTo(
|
||||||
|
experimentSettings.SHOW_MOBILE_FIRST_APPT,
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isShowMultiLocationPopup() {
|
||||||
|
return experimentMixin.methods.hasSettingEqualTo(
|
||||||
|
experimentSettings.SHOW_MULTI_LOCATION_APPT,
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
splitCopyOnCMSPlaceHolder,
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
|
@ -1243,7 +1264,8 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.initializeDatePicker().then(() => {
|
this.initializeDatePicker().then(() => {
|
||||||
this.showMobileFirstModal();
|
// this.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST
|
||||||
|
this.showMultiLocationModal();
|
||||||
this.hideLoadingModal();
|
this.hideLoadingModal();
|
||||||
this.calendarLoadingStatus = "none";
|
this.calendarLoadingStatus = "none";
|
||||||
this.setSelectedDateToFirstAvailable();
|
this.setSelectedDateToFirstAvailable();
|
||||||
|
|
@ -1277,7 +1299,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async initializeDatePicker() {
|
async initializeDatePicker() {
|
||||||
this.isShowMobileFirstAppt && this.showLoadingModal();
|
this.isShowMultiLocationPopup && this.showLoadingModal();
|
||||||
|
|
||||||
const includeMobileTimeSlots = this.isServiceableMobile;
|
const includeMobileTimeSlots = this.isServiceableMobile;
|
||||||
const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff;
|
const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff;
|
||||||
|
|
@ -1802,7 +1824,8 @@ export default {
|
||||||
this.zipCodeCtu = newZipCode.zipCodeCtu;
|
this.zipCodeCtu = newZipCode.zipCodeCtu;
|
||||||
this.selectedDate = null;
|
this.selectedDate = null;
|
||||||
this.initializeDatePicker().then(() => {
|
this.initializeDatePicker().then(() => {
|
||||||
this.showMobileFirstModal();
|
// this.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST
|
||||||
|
this.showMultiLocationModal();
|
||||||
this.hideLoadingModal();
|
this.hideLoadingModal();
|
||||||
this.calendarLoadingStatus = "none";
|
this.calendarLoadingStatus = "none";
|
||||||
this.setSelectedDateToFirstAvailable();
|
this.setSelectedDateToFirstAvailable();
|
||||||
|
|
@ -1857,10 +1880,13 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.appointmentType = null;
|
this.appointmentType = null;
|
||||||
}
|
}
|
||||||
this.setSelectedDateToFirstAvailable(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
|
this.setSelectedDateToFirstAvailable();
|
||||||
},
|
},
|
||||||
setSelectedDateToFirstAvailable() {
|
setSelectedDateToFirstAvailable() {
|
||||||
this.selectedDate = this.getFirstAvailableDate();
|
if (this.hasSelectableDatesLoaded) {
|
||||||
|
// ensure dates have been loaded by now
|
||||||
|
this.selectedDate = this.getFirstAvailableDate();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showRecalAcknowledgementModal() {
|
showRecalAcknowledgementModal() {
|
||||||
if (this.shouldShowRecalAckModal()) {
|
if (this.shouldShowRecalAckModal()) {
|
||||||
|
|
@ -1914,10 +1940,10 @@ export default {
|
||||||
const order = this.$store.getters.order;
|
const order = this.$store.getters.order;
|
||||||
const isRepair = order.damage.isRepair;
|
const isRepair = order.damage.isRepair;
|
||||||
|
|
||||||
let preSelectedMobileAppointment = null;
|
let promotedMobileAppointment = null;
|
||||||
const todaysDate = getTodayDate();
|
const todaysDate = getTodayDate();
|
||||||
let firstMobileAMAppt = await this.getFirstAvailableMobileApptByTOD("AM");
|
let firstMobileAMAppt = await this.getFirstAvailableApptByTOD("AM", "mobile");
|
||||||
let firstMobilePMAppt = await this.getFirstAvailableMobileApptByTOD("PM");
|
let firstMobilePMAppt = await this.getFirstAvailableApptByTOD("PM", "mobile");
|
||||||
if (!firstMobileAMAppt && !firstMobilePMAppt) {
|
if (!firstMobileAMAppt && !firstMobilePMAppt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -2012,7 +2038,7 @@ export default {
|
||||||
if (this.isShowMobileFirstAppt) {
|
if (this.isShowMobileFirstAppt) {
|
||||||
if (pmMobileDays == 0) {
|
if (pmMobileDays == 0) {
|
||||||
// Selects the first available time slot
|
// Selects the first available time slot
|
||||||
preSelectedMobileAppointment =
|
promotedMobileAppointment =
|
||||||
numberOfDaysToFirstMobileAMDate <= numberOfDaysToFirstMobilePMDate
|
numberOfDaysToFirstMobileAMDate <= numberOfDaysToFirstMobilePMDate
|
||||||
? firstMobileAMAppt
|
? firstMobileAMAppt
|
||||||
: firstMobilePMAppt;
|
: firstMobilePMAppt;
|
||||||
|
|
@ -2021,20 +2047,20 @@ export default {
|
||||||
numberOfDaysToFirstMobilePMDate <= pmMobileDays
|
numberOfDaysToFirstMobilePMDate <= pmMobileDays
|
||||||
) {
|
) {
|
||||||
// Selects the first available PM time slot
|
// Selects the first available PM time slot
|
||||||
preSelectedMobileAppointment = firstMobilePMAppt;
|
promotedMobileAppointment = firstMobilePMAppt;
|
||||||
} else if (
|
} else if (
|
||||||
firstMobilePMDate &&
|
firstMobilePMDate &&
|
||||||
numberOfDaysToFirstMobilePMDate >= noPMMobileDays
|
numberOfDaysToFirstMobilePMDate >= noPMMobileDays
|
||||||
) {
|
) {
|
||||||
// Selects the first available AM time slot. If none, selects the first available PM time slot
|
// Selects the first available AM time slot. If none, selects the first available PM time slot
|
||||||
preSelectedMobileAppointment = firstMobileAMAppt
|
promotedMobileAppointment = firstMobileAMAppt
|
||||||
? firstMobileAMAppt
|
? firstMobileAMAppt
|
||||||
: firstMobilePMAppt;
|
: firstMobilePMAppt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preSelectedMobileAppointment) {
|
if (promotedMobileAppointment) {
|
||||||
this.$refs.mobileFirstModal.setSelectedAppointment(
|
this.$refs.mobileFirstModal.setSelectedAppointment(
|
||||||
preSelectedMobileAppointment
|
promotedMobileAppointment
|
||||||
);
|
);
|
||||||
this.$refs.mobileFirstModal.openModal();
|
this.$refs.mobileFirstModal.openModal();
|
||||||
}
|
}
|
||||||
|
|
@ -2042,11 +2068,176 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getFirstAvailableMobileApptByTOD(timeOfDay) {
|
async showMultiLocationModal() {
|
||||||
if (!this.selectableDatesMobile?.days?.length) {
|
if (!this.selectedRouteCodeData?.routeCode) {
|
||||||
|
let maxDayRangeToShowPmTimeslot = experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.SHOW_PM_DAYS_MULTI_LOCATION
|
||||||
|
);
|
||||||
|
let maxDayRangeToShowNoPmTimeslot = experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.SHOW_NO_PM_DAYS_MULTI_LOCATION
|
||||||
|
);
|
||||||
|
let maxDayRangeToShowAnyTimeslot = experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION
|
||||||
|
);
|
||||||
|
let showMultiLocationAppointment = experimentMixin.methods.getSettingValue(
|
||||||
|
experimentSettings.SHOW_MULTI_LOCATION_APPT
|
||||||
|
);
|
||||||
|
let experimentSettingsString =
|
||||||
|
"ShowPm:" +
|
||||||
|
maxDayRangeToShowPmTimeslot +
|
||||||
|
"|" +
|
||||||
|
"NoPm:" +
|
||||||
|
maxDayRangeToShowNoPmTimeslot +
|
||||||
|
"|" +
|
||||||
|
"NoAvail:" +
|
||||||
|
maxDayRangeToShowAnyTimeslot;
|
||||||
|
let promotedMobileAppointment = null;
|
||||||
|
let promotedInshopAppointment = null;
|
||||||
|
const todaysDate = getTodayDate();
|
||||||
|
|
||||||
|
// Helper function to calculate days until appointment (handles null/undefined)
|
||||||
|
const calculateDaysUntilDate = (appointmentObj) => {
|
||||||
|
const dateString = appointmentObj?.date;
|
||||||
|
return dateString
|
||||||
|
? (new Date(dateString) - todaysDate) / (1000 * 60 * 60 * 24)
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch all appointments
|
||||||
|
const [firstMobileAMAppt, firstMobilePMAppt, firstInshopAMAppt, firstInshopPMAppt] =
|
||||||
|
await Promise.all([
|
||||||
|
this.getFirstAvailableApptByTOD("AM", "mobile"),
|
||||||
|
this.getFirstAvailableApptByTOD("PM", "mobile"),
|
||||||
|
this.getFirstAvailableApptByTOD("AM", "inshop"),
|
||||||
|
this.getFirstAvailableApptByTOD("PM", "inshop"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Extract dates
|
||||||
|
const firstMobileAMDate = firstMobileAMAppt?.date;
|
||||||
|
const firstMobilePMDate = firstMobilePMAppt?.date;
|
||||||
|
const firstInshopAMDate = firstInshopAMAppt?.date;
|
||||||
|
const firstInshopPMDate = firstInshopPMAppt?.date;
|
||||||
|
|
||||||
|
// Calculate days until appointments
|
||||||
|
const numberOfDaysToFirstMobileAMDate = calculateDaysUntilDate(firstMobileAMAppt);
|
||||||
|
const numberOfDaysToFirstMobilePMDate = calculateDaysUntilDate(firstMobilePMAppt);
|
||||||
|
const numberOfDaysToFirstInshopAMDate = calculateDaysUntilDate(firstInshopAMAppt);
|
||||||
|
const numberOfDaysToFirstInshopPMDate = calculateDaysUntilDate(firstInshopPMAppt);
|
||||||
|
|
||||||
|
const shouldExposeMultiLocationModal = () => {
|
||||||
|
const isMobileAppointmentInDateRange =
|
||||||
|
(firstMobileAMDate &&
|
||||||
|
numberOfDaysToFirstMobileAMDate <= maxDayRangeToShowAnyTimeslot) ||
|
||||||
|
(firstMobilePMDate &&
|
||||||
|
numberOfDaysToFirstMobilePMDate <= maxDayRangeToShowAnyTimeslot);
|
||||||
|
const isInshopAppointmentInDateRange =
|
||||||
|
(firstInshopAMDate &&
|
||||||
|
numberOfDaysToFirstInshopAMDate <= maxDayRangeToShowAnyTimeslot) ||
|
||||||
|
(firstInshopPMDate &&
|
||||||
|
numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowAnyTimeslot);
|
||||||
|
|
||||||
|
if (showMultiLocationAppointment?.toLowerCase() === "true") {
|
||||||
|
return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shouldExposeMultiLocationModal()) {
|
||||||
|
const multiLocationPopupExperiment =
|
||||||
|
store.getters.applicationUser.experiments.find(
|
||||||
|
(e) => e.universeName === experimentUniverses.MULTI_LOCATION_POPUP
|
||||||
|
);
|
||||||
|
const hasExposedMultiLocationPopup = multiLocationPopupExperiment?.isExposed;
|
||||||
|
|
||||||
|
if (!hasExposedMultiLocationPopup && multiLocationPopupExperiment) {
|
||||||
|
baseMixin.methods.dispatchStoreActionWithLogging(
|
||||||
|
storeActions.LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE,
|
||||||
|
{
|
||||||
|
userId: getUserIdValue(),
|
||||||
|
deviceId: getDeviceIdValue(),
|
||||||
|
sessionKey: getSessionKeyValue(),
|
||||||
|
pageName: "schedule",
|
||||||
|
experiment: multiLocationPopupExperiment,
|
||||||
|
},
|
||||||
|
"schedule",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.isShowMultiLocationPopup) {
|
||||||
|
if (maxDayRangeToShowPmTimeslot == 0) {
|
||||||
|
// Selects the first available mobile time slot
|
||||||
|
promotedMobileAppointment =
|
||||||
|
numberOfDaysToFirstMobileAMDate <= numberOfDaysToFirstMobilePMDate
|
||||||
|
? firstMobileAMAppt
|
||||||
|
: firstMobilePMAppt;
|
||||||
|
// Selects the first available inshop time slot
|
||||||
|
promotedInshopAppointment =
|
||||||
|
numberOfDaysToFirstInshopAMDate <= numberOfDaysToFirstInshopPMDate
|
||||||
|
? firstInshopAMAppt
|
||||||
|
: firstInshopPMAppt;
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
firstMobilePMDate &&
|
||||||
|
numberOfDaysToFirstMobilePMDate <= maxDayRangeToShowPmTimeslot
|
||||||
|
) {
|
||||||
|
// Selects the first available PM time slot
|
||||||
|
promotedMobileAppointment = firstMobilePMAppt;
|
||||||
|
} else if (
|
||||||
|
firstMobilePMDate &&
|
||||||
|
numberOfDaysToFirstMobilePMDate >= maxDayRangeToShowNoPmTimeslot
|
||||||
|
) {
|
||||||
|
// Selects the first available AM time slot. If none, selects the first available PM time slot
|
||||||
|
promotedMobileAppointment = firstMobileAMAppt
|
||||||
|
? firstMobileAMAppt
|
||||||
|
: firstMobilePMAppt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
firstInshopPMDate &&
|
||||||
|
numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowPmTimeslot
|
||||||
|
) {
|
||||||
|
// Selects the first available PM time slot
|
||||||
|
promotedInshopAppointment = firstInshopPMAppt;
|
||||||
|
} else if (
|
||||||
|
firstInshopPMDate &&
|
||||||
|
numberOfDaysToFirstInshopPMDate >= maxDayRangeToShowNoPmTimeslot
|
||||||
|
) {
|
||||||
|
// Selects the first available AM time slot. If none, selects the first available PM time slot
|
||||||
|
promotedInshopAppointment = firstInshopAMAppt
|
||||||
|
? firstInshopAMAppt
|
||||||
|
: firstInshopPMAppt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promotedMobileAppointment && promotedInshopAppointment) {
|
||||||
|
this.$refs.multiLocationModal.setPromotedMobileAppointment(
|
||||||
|
promotedMobileAppointment
|
||||||
|
);
|
||||||
|
this.$refs.multiLocationModal.setPromotedInshopAppointment(
|
||||||
|
promotedInshopAppointment
|
||||||
|
);
|
||||||
|
this.$refs.multiLocationModal.openModal();
|
||||||
|
this.$refs.multiLocationModal.captureExperimentSettings(
|
||||||
|
experimentSettingsString
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getFirstAvailableApptByTOD(timeOfDay, appointmentType) {
|
||||||
|
let selectableDays;
|
||||||
|
if (appointmentType?.toLowerCase() == "mobile") {
|
||||||
|
selectableDays = this.selectableDatesMobile?.days;
|
||||||
|
} else if (appointmentType?.toLowerCase() == "inshop") {
|
||||||
|
selectableDays = this.selectableDatesInshop?.days;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!selectableDays?.length) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
for (const dateObj of this.selectableDatesMobile.days) {
|
for (const dateObj of selectableDays) {
|
||||||
let matchingTimeSlot = {
|
let matchingTimeSlot = {
|
||||||
estimatedServiceMinutes: {
|
estimatedServiceMinutes: {
|
||||||
minimum: this.estimatedServiceMinutesMinimum,
|
minimum: this.estimatedServiceMinutesMinimum,
|
||||||
|
|
@ -2054,6 +2245,7 @@ export default {
|
||||||
},
|
},
|
||||||
timeSlot: null,
|
timeSlot: null,
|
||||||
date: null,
|
date: null,
|
||||||
|
addressCopy: null,
|
||||||
};
|
};
|
||||||
const isMatchingApptDay = dateObj.timeSlots.some(
|
const isMatchingApptDay = dateObj.timeSlots.some(
|
||||||
(slot) =>
|
(slot) =>
|
||||||
|
|
@ -2061,6 +2253,16 @@ export default {
|
||||||
(matchingTimeSlot.timeSlot = slot) &&
|
(matchingTimeSlot.timeSlot = slot) &&
|
||||||
(matchingTimeSlot.date = dateObj.date)
|
(matchingTimeSlot.date = dateObj.date)
|
||||||
);
|
);
|
||||||
|
if (
|
||||||
|
appointmentType?.toLowerCase() == "inshop" &&
|
||||||
|
this.selectedShopAnswer?.address1 &&
|
||||||
|
this.selectedShopAnswer?.address2
|
||||||
|
) {
|
||||||
|
matchingTimeSlot.addressCopy =
|
||||||
|
this.selectedShopAnswer?.address1 +
|
||||||
|
", " +
|
||||||
|
this.selectedShopAnswer?.address2;
|
||||||
|
}
|
||||||
if (isMatchingApptDay && matchingTimeSlot) {
|
if (isMatchingApptDay && matchingTimeSlot) {
|
||||||
return matchingTimeSlot;
|
return matchingTimeSlot;
|
||||||
}
|
}
|
||||||
|
|
@ -2068,7 +2270,7 @@ export default {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateTimeSlotandNavigateForward(timeSlotObj) {
|
updateMobileFirstTimeSlotandNavigateForward(timeSlotObj) {
|
||||||
this.selectedMobileFirstAppointment = true;
|
this.selectedMobileFirstAppointment = true;
|
||||||
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||||
this.selectedDate = timeSlotObj.date;
|
this.selectedDate = timeSlotObj.date;
|
||||||
|
|
@ -2076,6 +2278,18 @@ export default {
|
||||||
this.updateTimeSlot(timeSlotObj);
|
this.updateTimeSlot(timeSlotObj);
|
||||||
this.forwardButtonAction();
|
this.forwardButtonAction();
|
||||||
},
|
},
|
||||||
|
updateMultiLocationTimeSlotandNavigateForward({ selectedTimeSlot, appointmentType }) {
|
||||||
|
if (appointmentType?.toLowerCase() == "mobile") {
|
||||||
|
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||||
|
} else if (appointmentType?.toLowerCase() == "inshop") {
|
||||||
|
this.appointmentType = AppointmentTypeStrings.IN_SHOP;
|
||||||
|
}
|
||||||
|
this.selectedMultiLocationAppointment = true;
|
||||||
|
this.selectedDate = selectedTimeSlot.date;
|
||||||
|
this.updateSelectedProvider();
|
||||||
|
this.updateTimeSlot(selectedTimeSlot);
|
||||||
|
this.forwardButtonAction();
|
||||||
|
},
|
||||||
updateRecalAcknowledgedAndNavigateForward(isAcknowledged) {
|
updateRecalAcknowledgedAndNavigateForward(isAcknowledged) {
|
||||||
this.isRecalAcknowledgedForScheduling = isAcknowledged;
|
this.isRecalAcknowledgedForScheduling = isAcknowledged;
|
||||||
this.forwardButtonAction();
|
this.forwardButtonAction();
|
||||||
|
|
@ -2084,7 +2298,7 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
appointmentTypeFromAppointmentTypeQuestion: {
|
appointmentTypeFromAppointmentTypeQuestion: {
|
||||||
handler(newValue, oldValue) {
|
handler(newValue, oldValue) {
|
||||||
this.handleAppointmentTypeChange(newValue); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
|
this.handleAppointmentTypeChange(newValue);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -2099,6 +2313,7 @@ export default {
|
||||||
textBlock,
|
textBlock,
|
||||||
timeSlotQuestion,
|
timeSlotQuestion,
|
||||||
mobileFirstModal,
|
mobileFirstModal,
|
||||||
|
multiLocationModal,
|
||||||
recalAckModal,
|
recalAckModal,
|
||||||
alert,
|
alert,
|
||||||
serviceZipModalQuestion,
|
serviceZipModalQuestion,
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,13 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
baseMixin.methods.ResetExternalParamsAndHideModal();
|
||||||
|
|
||||||
|
//clear any validation errors for external parameter flow
|
||||||
|
const form = vm.$refs.theForm;
|
||||||
|
if (form) {
|
||||||
|
form.setFieldError("DamageLocationQuestion", undefined);
|
||||||
|
form.setFieldTouched("DamageLocationQuestion", false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
baseMixin.methods.ResetExternalParamsAndHideModal();
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,7 @@ export default {
|
||||||
sessionData.isPia = order?.payment?.isPia ?? false;
|
sessionData.isPia = order?.payment?.isPia ?? false;
|
||||||
sessionData.piaType = order?.payment?.piaType;
|
sessionData.piaType = order?.payment?.piaType;
|
||||||
sessionData.parentAccountNumber = order?.payment?.parentAccountNumber;
|
sessionData.parentAccountNumber = order?.payment?.parentAccountNumber;
|
||||||
|
sessionData.billToAccountNumber = order?.payment?.billToAccountNumber;
|
||||||
sessionData.settledTenderAmount = order?.settledTenderAmount;
|
sessionData.settledTenderAmount = order?.settledTenderAmount;
|
||||||
sessionData.recalRequired = containsRecalParts(order?.lineItems);
|
sessionData.recalRequired = containsRecalParts(order?.lineItems);
|
||||||
sessionData.recalType = getRecalPartNumbers(order?.lineItems?.glassParts);
|
sessionData.recalType = getRecalPartNumbers(order?.lineItems?.glassParts);
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,15 @@ export default {
|
||||||
userAgent.includes("webOS")
|
userAgent.includes("webOS")
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
isAppleBrowser() {
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
return (
|
||||||
|
userAgent.includes("iPod") ||
|
||||||
|
userAgent.includes("iPad") ||
|
||||||
|
userAgent.includes("iPhone") ||
|
||||||
|
userAgent.includes("Mac")
|
||||||
|
);
|
||||||
|
},
|
||||||
getSubmittedOrder() {
|
getSubmittedOrder() {
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
|
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,14 @@ export async function consumeReferralQuerystrings() {
|
||||||
// If no referral number in querystring, check if heritage funnel updated last.
|
// If no referral number in querystring, check if heritage funnel updated last.
|
||||||
// This would indicate a customer that went to heritage but did not return through the
|
// This would indicate a customer that went to heritage but did not return through the
|
||||||
// normal route. ie: left the site in search of a discount and returned without querystrings.
|
// normal route. ie: left the site in search of a discount and returned without querystrings.
|
||||||
|
|
||||||
|
//or if a referral number is in query string check if heritage funnel updated last
|
||||||
|
// This would indicate a customer that returned through normal route like from sfa email
|
||||||
const didHeritageFunnelUpdateLast = getFunnelCookie()?.DidHeritageFunnelUpdateLast;
|
const didHeritageFunnelUpdateLast = getFunnelCookie()?.DidHeritageFunnelUpdateLast;
|
||||||
if (didHeritageFunnelUpdateLast && !referralNumber) {
|
if (didHeritageFunnelUpdateLast) {
|
||||||
referralNumber = getFunnelCookie().ReferralNumber;
|
referralNumber = referralNumber ?? getFunnelCookie().ReferralNumber;
|
||||||
parentAccount = getFunnelCookie().ReferralParentAccountNumber;
|
parentAccount = parentAccount ?? getFunnelCookie().ReferralParentAccountNumber;
|
||||||
correlationId = getFunnelCookie().ReferralCorrelationId;
|
correlationId = correlationId ?? getFunnelCookie().ReferralCorrelationId;
|
||||||
referralDate = getFunnelCookie().ReferralDate;
|
referralDate = getFunnelCookie().ReferralDate;
|
||||||
savedSessionId = getFunnelCookie().SavedSessionId;
|
savedSessionId = getFunnelCookie().SavedSessionId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { getBoolFromString } from "@/helpers/boolean-helper";
|
||||||
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||||
import { routeData } from "@/router/constants/routes";
|
import { routeData } from "@/router/constants/routes";
|
||||||
import { isVerifiedInsurance } from "@/router/methods/helpers/requires-verified-redirect";
|
import { isVerifiedInsurance } from "@/router/methods/helpers/requires-verified-redirect";
|
||||||
|
import { hasVehicleInfo } from "@/helpers/vehicle-helper";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import baseMixin from "@/mixins/base-mixin";
|
import baseMixin from "@/mixins/base-mixin";
|
||||||
|
|
@ -33,7 +34,7 @@ export async function vehicleBeforeEnter(to, from) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVerifiedInsurance()) {
|
if (isVerifiedInsurance() && hasVehicleInfo()) {
|
||||||
return {
|
return {
|
||||||
name: routeData.VEHICLE_DAMAGE.name,
|
name: routeData.VEHICLE_DAMAGE.name,
|
||||||
replace: true,
|
replace: true,
|
||||||
|
|
|
||||||
|
|
@ -1645,6 +1645,7 @@ export const actions = {
|
||||||
isPia,
|
isPia,
|
||||||
piaType,
|
piaType,
|
||||||
parentAccountNumber,
|
parentAccountNumber,
|
||||||
|
billToAccountNumber,
|
||||||
settledTenderAmount,
|
settledTenderAmount,
|
||||||
recalRequired,
|
recalRequired,
|
||||||
recalType,
|
recalType,
|
||||||
|
|
@ -1718,6 +1719,7 @@ export const actions = {
|
||||||
totalPrice: totalPrice,
|
totalPrice: totalPrice,
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
cashPriceSubTotal: cashPriceSubTotal,
|
cashPriceSubTotal: cashPriceSubTotal,
|
||||||
|
billToAccountNumber: billToAccountNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
|
|
@ -2012,52 +2014,27 @@ export const actions = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||||
const escapeRecalibrationType = (rt) => rt.split("&").join("%26");
|
const flattenedLineItemsWithChildParts = getFlattenedArrayOfLineItemsWithChildParts(
|
||||||
|
context.getters.order.lineItems.glassParts
|
||||||
const partialLineItemsObjects = context.getters.order.lineItems.glassParts?.map(
|
|
||||||
(lineItem) => ({
|
|
||||||
partNumber: lineItem.partNumber,
|
|
||||||
recalibrationType:
|
|
||||||
lineItem.recalibrationType && isRecalPartOrHasChildRecalPart(lineItem)
|
|
||||||
? escapeRecalibrationType(lineItem.recalibrationType)
|
|
||||||
: undefined,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var lineItems = null;
|
const lineItemsList = flattenedLineItemsWithChildParts
|
||||||
if (partialLineItemsObjects) {
|
.map((item) => item.partNumber)
|
||||||
lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
|
.join(",");
|
||||||
partialLineItemsObjects,
|
|
||||||
"lineItems"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const vehicle = context.getters.vehicle;
|
const vehicle = context.getters.vehicle;
|
||||||
const carId = vehicle.carId;
|
const carId = vehicle.carId;
|
||||||
const damage = context.getters.damage;
|
const damage = context.getters.damage;
|
||||||
const glassArray = convertGlassPieceNamingForApi(damage.glassToReplace);
|
|
||||||
|
|
||||||
const glassPieces = buildQueryStringParameterFromArrayOfComplexObjects(
|
|
||||||
glassArray,
|
|
||||||
"glassPieces"
|
|
||||||
);
|
|
||||||
|
|
||||||
const parentAccountNumber =
|
const parentAccountNumber =
|
||||||
context.getters.order.payment.parentAccountNumber ??
|
context.getters.order.payment.parentAccountNumber ??
|
||||||
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
|
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
|
||||||
const referralSequenceNumber = context.getters.order.referralSequenceNumber;
|
|
||||||
|
|
||||||
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&parentAccountNumber=${parentAccountNumber}&referralSequenceNumber=${referralSequenceNumber}&applicationName=${applicationConfig.ANALYTICS_APPLICATION_NAME}`;
|
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&parentAccountNumber=${parentAccountNumber}&isRepair=${damage.isRepair}`;
|
||||||
if (lineItems) {
|
if (lineItemsList) {
|
||||||
endPoint += `&${lineItems}`;
|
endPoint += `&lineItems=${lineItemsList}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glassPieces) {
|
|
||||||
endPoint += `&${glassPieces}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
endPoint += `&isHeavyTruckVehicle=${context.getters.order.vehicle.isBigTruck}`;
|
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetServiceabilityDetails.method,
|
method: endpoints.GetServiceabilityDetails.method,
|
||||||
endpoint: endPoint,
|
endpoint: endPoint,
|
||||||
|
|
@ -2126,14 +2103,14 @@ export const actions = {
|
||||||
},
|
},
|
||||||
async submitDonationPartToSV2(
|
async submitDonationPartToSV2(
|
||||||
context,
|
context,
|
||||||
{ payload: { donationAmount, submittedOrder }, pageNameToLog }
|
{ payload: { selectedItemNumber, submittedOrder }, pageNameToLog }
|
||||||
) {
|
) {
|
||||||
const state = JSON.parse(
|
const state = JSON.parse(
|
||||||
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
|
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
|
||||||
);
|
);
|
||||||
const order = state.order;
|
const order = state.order;
|
||||||
const payload = {
|
const payload = {
|
||||||
donationAmount: parseInt(donationAmount),
|
selectedItemNumber: selectedItemNumber,
|
||||||
savedSessionId: state.applicationUser.savedSessionId,
|
savedSessionId: state.applicationUser.savedSessionId,
|
||||||
paymentInfo: {
|
paymentInfo: {
|
||||||
parentAccountNumber: order.payment.parentAccountNumber,
|
parentAccountNumber: order.payment.parentAccountNumber,
|
||||||
|
|
@ -3138,6 +3115,8 @@ export const actions = {
|
||||||
serviceLocationState,
|
serviceLocationState,
|
||||||
serviceLocationZipCode,
|
serviceLocationZipCode,
|
||||||
pricedLineItems,
|
pricedLineItems,
|
||||||
|
serviceLocationAddressLine1,
|
||||||
|
serviceLocationAddressLine2,
|
||||||
},
|
},
|
||||||
pageNameToLog,
|
pageNameToLog,
|
||||||
}
|
}
|
||||||
|
|
@ -3171,6 +3150,8 @@ export const actions = {
|
||||||
appointmentType: appointmentType,
|
appointmentType: appointmentType,
|
||||||
pricedLineItems: lineItemsWithOnlyPriceInfo,
|
pricedLineItems: lineItemsWithOnlyPriceInfo,
|
||||||
serviceLocation: {
|
serviceLocation: {
|
||||||
|
addressLine1: appointmentType == "Mobile" ? serviceLocationAddressLine1 : null,
|
||||||
|
addressLine2: appointmentType == "Mobile" ? serviceLocationAddressLine2 : null,
|
||||||
city: appointmentType == "Mobile" ? serviceLocationCity : null,
|
city: appointmentType == "Mobile" ? serviceLocationCity : null,
|
||||||
state: appointmentType == "Mobile" ? serviceLocationState : null,
|
state: appointmentType == "Mobile" ? serviceLocationState : null,
|
||||||
zipCode: appointmentType == "Mobile" ? serviceLocationZipCode : null,
|
zipCode: appointmentType == "Mobile" ? serviceLocationZipCode : null,
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,9 @@
|
||||||
}
|
}
|
||||||
// Usage: @include responsive-font-size-md(mobile-font-size-in-rem, desktop-font-size-in-rem);
|
// Usage: @include responsive-font-size-md(mobile-font-size-in-rem, desktop-font-size-in-rem);
|
||||||
// Example: @include responsive-font-size-md(0.875rem, 1rem);
|
// Example: @include responsive-font-size-md(0.875rem, 1rem);
|
||||||
|
|
||||||
|
// Filter to use on SVG elements that are background
|
||||||
|
@mixin blue-filter {
|
||||||
|
filter: brightness(0) saturate(100%) invert(31%) sepia(68%) saturate(1676%) hue-rotate(186deg)
|
||||||
|
brightness(93%) contrast(110%);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,12 @@ $body-color: $gray-600;
|
||||||
$font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif;
|
$font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif;
|
||||||
$font-family-monospace:
|
$font-family-monospace:
|
||||||
UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
$font-family-sans-serif-semibold: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||||
|
$font-family-sans-serif-bold: UrbanistBold, Arial, Helvetica, sans-serif;
|
||||||
|
$font-family-sans-serif-extrabold: UrbanistExtraBold, Arial, Helvetica, sans-serif;
|
||||||
|
$font-family-sans-serif-medium-italic: UrbanistMedium_Italic, Arial, Helvetica, sans-serif;
|
||||||
|
$font-family-sans-serif-bold-italic: UrbanistBold_Italic, Arial, Helvetica, sans-serif;
|
||||||
|
|
||||||
// stylelint-enable value-keyword-case
|
// stylelint-enable value-keyword-case
|
||||||
$font-family-base: $font-family-sans-serif;
|
$font-family-base: $font-family-sans-serif;
|
||||||
$font-family-code: $font-family-monospace;
|
$font-family-code: $font-family-monospace;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@
|
||||||
this.alertClass,
|
this.alertClass,
|
||||||
this.cssClassNameForCmsWidget,
|
this.cssClassNameForCmsWidget,
|
||||||
]">
|
]">
|
||||||
<p class="mx-6 my-0 fw-bold alert-heading">{{ alertHeadline }}</p>
|
<p class="mx-6 my-0 fw-bold alert-heading">
|
||||||
|
<img v-if="showInfoIcon" :src="infoIcon" alt="Info Icon" class="info-icon" />
|
||||||
|
{{ alertHeadline }}
|
||||||
|
</p>
|
||||||
|
<hr v-if="showHorizontalRow" />
|
||||||
<template v-for="paragraph in splitAlertCopyForParagraphTag" :key="paragraph">
|
<template v-for="paragraph in splitAlertCopyForParagraphTag" :key="paragraph">
|
||||||
<p
|
<p
|
||||||
class="mx-4 mt-1 mb-0 text-body small"
|
class="mx-4 mt-1 mb-0 text-body small"
|
||||||
|
|
@ -58,6 +62,8 @@ import textBlock from "@/digital-components/text-block/text-block";
|
||||||
export default {
|
export default {
|
||||||
name: "alert",
|
name: "alert",
|
||||||
props: {
|
props: {
|
||||||
|
showInfoIcon: Boolean,
|
||||||
|
showHorizontalRow: Boolean,
|
||||||
isDismissible: Boolean,
|
isDismissible: Boolean,
|
||||||
alertClass: String,
|
alertClass: String,
|
||||||
/*
|
/*
|
||||||
|
|
@ -87,6 +93,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
infoIcon() {
|
||||||
|
return require(`@/assets/img/icons/info-circle-blue.svg`);
|
||||||
|
},
|
||||||
pageQueryString() {
|
pageQueryString() {
|
||||||
return applicationConfig.PAGE_QUERYSTRING;
|
return applicationConfig.PAGE_QUERYSTRING;
|
||||||
},
|
},
|
||||||
|
|
@ -148,6 +157,12 @@ export default {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
border-width: 0px;
|
border-width: 0px;
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid $blue;
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue