Merge branch 'release/2026.01.22' into rlsmerge/2026.01.22-to-2026.01.29
This commit is contained in:
commit
3b3903709b
9 changed files with 307 additions and 1 deletions
|
|
@ -15,6 +15,7 @@ const GaCategories = {
|
|||
APPOINTMENT: "Appointment",
|
||||
SERVICE_LOCATION: "service-location",
|
||||
CONFIRMATION_CLICKED: "confirmation clicked",
|
||||
RECAL_DISCLAIMER: "Recalibration disclaimer",
|
||||
};
|
||||
|
||||
const GaActions = {
|
||||
|
|
@ -29,6 +30,7 @@ const GaActions = {
|
|||
MORE_LOCATIONS_CLICKED: "more_locations_clicked",
|
||||
YES: "yes",
|
||||
NO: "no",
|
||||
ACKNOWLEDGED: "Acknowledged",
|
||||
};
|
||||
|
||||
const GaLabels = {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";
|
|||
|
||||
const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN";
|
||||
|
||||
const RECAL_ACK_YES = "Yes";
|
||||
|
||||
const RouteCodeFlags = {
|
||||
ALL_DAY_DROP_OFF: "ALL DAY DROP OFF",
|
||||
OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF",
|
||||
|
|
@ -31,4 +33,5 @@ export {
|
|||
PRICING_BY_DAY_PART_TYPE,
|
||||
RouteCodeFlags,
|
||||
cmsWidgetFieldMappings,
|
||||
RECAL_ACK_YES,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ const storeActions = {
|
|||
SAVE_PAYPAL_TOKEN: "savePaypalToken",
|
||||
SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount",
|
||||
SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn",
|
||||
SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling",
|
||||
SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected",
|
||||
SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable",
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const storeMutations = {
|
|||
LOCK_TOKEN: "updateLockToken",
|
||||
UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount",
|
||||
UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn",
|
||||
UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "updateIsRecalAcknowledgedForScheduling",
|
||||
UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected",
|
||||
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
|
||||
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ import { partTypeStrings } from "@/constants/part-type-strings";
|
|||
|
||||
const recalPartTypes = [partTypeStrings.RECALIBRATION, partTypeStrings.ADAS_RECALIBRATION];
|
||||
|
||||
export function anyPartWithRequiresRecalFlag(lineItems) {
|
||||
if (!lineItems) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lineItems.glassParts?.some((li) => li.requiresRecalibration === true);
|
||||
}
|
||||
|
||||
export function isRecalPartOrHasChildRecalPart(lineItem) {
|
||||
if (lineItem.childParts && lineItem.childParts.length > 0) {
|
||||
return isRecalPart(lineItem) || containsRecalParts(lineItem.childParts);
|
||||
|
|
|
|||
223
src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue
Normal file
223
src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
// This modal is used to acknowledge the recal disclaimer during scheduling. // It will trigger when
|
||||
there is a glass part with requiresRecalibration = true // but there is no recalibration part in
|
||||
lineItems. This mostly occurs when // a zip code that does mobile only is selected but the vehicle
|
||||
requires // recalibration.
|
||||
<template>
|
||||
<div id="recal-ack-modal-container">
|
||||
<modal
|
||||
:ref="modalName"
|
||||
suppressPageScroll
|
||||
:onModalClosedCallback="onModalClosed"
|
||||
:footerButtonText="modalFooterText"
|
||||
:isFooterButtonPrimary="true"
|
||||
@footer-button-event="ackRecalDisclaimer">
|
||||
<p class="modal-body-inner" v-html="modalBodyText"></p>
|
||||
<div>
|
||||
<div class="learn-more">
|
||||
<a href="javascript:;" @click="learnMore = !learnMore">{{ learnMoreLabel }}</a>
|
||||
<div v-if="learnMore">{{ learnMoreText }}</div>
|
||||
</div>
|
||||
<buttonQuestion
|
||||
:questionText="questionText"
|
||||
:answers="answersFromCms"
|
||||
:groupName="groupName"
|
||||
buttonTypeString="listButtonHorizontal"
|
||||
v-model="selectedValues"
|
||||
isRequired />
|
||||
</div>
|
||||
<template v-slot:modal-header-slot>
|
||||
<span>{{ modalHeaderText }}</span>
|
||||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modal from "@/digital-components/modal/modal";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import { RECAL_ACK_YES } from "@/constants/schedule-constants";
|
||||
|
||||
export default {
|
||||
name: "recal-ack-modal",
|
||||
props: {
|
||||
agreementWidgetName: String,
|
||||
modalWidgetName: String,
|
||||
groupName: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
learnMore: false,
|
||||
isModalOpen: true,
|
||||
isRecalAcknowledged: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.modal.openModal();
|
||||
},
|
||||
onModalClosed() {
|
||||
this.$emit("recal-ack-modal");
|
||||
},
|
||||
closeModal() {
|
||||
this.modal.closeModal();
|
||||
},
|
||||
setIsModalOpen(isOpen) {
|
||||
this.isModalOpen = isOpen;
|
||||
},
|
||||
ackRecalDisclaimer() {
|
||||
this.modal.closeModal();
|
||||
|
||||
if (this.isRecalAcknowledged === RECAL_ACK_YES) {
|
||||
this.pushEventToGA(
|
||||
this.GaCategories.RECAL_DISCLAIMER,
|
||||
this.GaActions.ACKNOWLEDGED,
|
||||
this.isRecalAcknowledged,
|
||||
true
|
||||
);
|
||||
|
||||
this.$emit("recalAcknowledged", this.isRecalAcknowledged);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
questionText() {
|
||||
return this.getCmsContent(this.agreementWidgetName, "QuestionText");
|
||||
},
|
||||
answersFromCms() {
|
||||
return this.getCmsContent(this.agreementWidgetName, "Answers");
|
||||
},
|
||||
learnMoreLabel() {
|
||||
return this.getCmsContent(this.modalWidgetName, "SubheaderText");
|
||||
},
|
||||
learnMoreText() {
|
||||
return this.getCmsContent(this.modalWidgetName, "FooterText2");
|
||||
},
|
||||
modalName() {
|
||||
return this.modalWidgetName;
|
||||
},
|
||||
modal() {
|
||||
return this.$refs[this.modalName];
|
||||
},
|
||||
modalHeaderText() {
|
||||
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||
},
|
||||
modalBodyText() {
|
||||
return this.getCmsContent(this.modalWidgetName, "BodyText");
|
||||
},
|
||||
modalFooterText() {
|
||||
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
||||
},
|
||||
selectedValues: {
|
||||
get: function () {
|
||||
return this.isRecalAcknowledged;
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.isRecalAcknowledged = this.answersFromCms.find(
|
||||
(a) => a.Name === newValue
|
||||
)?.Text;
|
||||
},
|
||||
},
|
||||
},
|
||||
components: { modal, buttonQuestion },
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
#recal-ack-modal-container {
|
||||
.modal-component {
|
||||
@include media-breakpoint-up(md) {
|
||||
.modal-dialog {
|
||||
left: 0;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
width: 34.875rem;
|
||||
transform: translate(0, 0);
|
||||
|
||||
.modal-content {
|
||||
border-radius: $border-radius-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal-dialog {
|
||||
.modal-content {
|
||||
.btn-close {
|
||||
display: none;
|
||||
}
|
||||
.modal-header {
|
||||
width: 75%;
|
||||
padding: 0 1.25rem 0 1.25rem;
|
||||
flex-direction: column;
|
||||
& > span {
|
||||
color: $black;
|
||||
font-size: 1.25rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: $font-size-20;
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
}
|
||||
.modal-body {
|
||||
padding: 0.875rem 1.25rem 1.875rem 1.25rem;
|
||||
|
||||
.modal-body-inner {
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 1rem;
|
||||
background-color: #f4f4f4;
|
||||
font-size: $font-size-14;
|
||||
}
|
||||
p {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.button-inner-wrapper > .col:first-child .list-button-horizontal {
|
||||
border-bottom-left-radius: 50rem;
|
||||
border-top-left-radius: 50rem;
|
||||
}
|
||||
.col:first-of-type .list-button-horizontal .list-button-horizontal-content,
|
||||
.list-group:first-of-type
|
||||
.list-button-horizontal
|
||||
.list-button-horizontal-content {
|
||||
border-bottom-left-radius: 50rem;
|
||||
border-top-left-radius: 50rem;
|
||||
}
|
||||
|
||||
.button-inner-wrapper > .col:last-child .list-button-horizontal {
|
||||
border-bottom-right-radius: 50rem;
|
||||
border-top-right-radius: 50rem;
|
||||
}
|
||||
.col:last-of-type .list-button-horizontal .list-button-horizontal-content,
|
||||
.list-group:first-of-type
|
||||
.list-button-horizontal
|
||||
.list-button-horizontal-content {
|
||||
border-bottom-right-radius: 50rem;
|
||||
border-top-right-radius: 50rem;
|
||||
}
|
||||
|
||||
.learn-more {
|
||||
padding-bottom: 0;
|
||||
font-size: 0.875rem;
|
||||
a {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.question-text {
|
||||
margin-top: 0.875rem;
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
flex-flow: wrap-reverse;
|
||||
padding-top: 0;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
.modal-footer .modal-footer-button:hover {
|
||||
background-color: $red-1000;
|
||||
border-color: $red-1100;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,6 +6,12 @@
|
|||
modalWidgetName="MobileFirstModalWidget"
|
||||
ref="mobileFirstModal"
|
||||
@confirm-appointment="updateTimeSlotandNavigateForward" />
|
||||
<recalAckModal
|
||||
modalWidgetName="RecalAckModalWidget"
|
||||
agreementWidgetName="RecalAgreementQuestionWidget"
|
||||
groupName="recalAckGroup"
|
||||
@recalAcknowledged="updateRecalAcknowledgedAndNavigateForward"
|
||||
ref="recalAckModal" />
|
||||
<div
|
||||
class="container page-container-grouped-styles"
|
||||
:class="[isMobileFirstModalOpen ? 'hidden-background' : '']">
|
||||
|
|
@ -190,6 +196,7 @@ import shopLocation from "@/layouts/schedule/shop-location/shop-location";
|
|||
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 mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-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 shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
|
||||
|
|
@ -216,6 +223,7 @@ import {
|
|||
RouteCodeFlags,
|
||||
PREMIUM_FEE_PART_TYPE,
|
||||
PRICING_BY_DAY_PART_TYPE,
|
||||
RECAL_ACK_YES,
|
||||
} from "@/constants/schedule-constants";
|
||||
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
|
|
@ -242,6 +250,7 @@ import {
|
|||
isDropOffRouteCode,
|
||||
getTodayDate,
|
||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||
import { containsRecalParts, anyPartWithRequiresRecalFlag } from "@/helpers/recal-helper";
|
||||
|
||||
import { DAYS_OF_WEEK } from "@/digital-components/date-picker/mixins/constants";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
|
|
@ -430,6 +439,7 @@ export default {
|
|||
name: "schedule",
|
||||
data() {
|
||||
return {
|
||||
isRecalAcknowledgedForScheduling: this.getIsRecalAcknowledgedForSchedulingFromStore(),
|
||||
selectedDate: this.getSelectedDateFromStore(),
|
||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
mobilePremiumAppointmentFee: null,
|
||||
|
|
@ -1233,6 +1243,9 @@ export default {
|
|||
|
||||
this.isShowMobileFirstAppt && this.hideLoadingModal();
|
||||
},
|
||||
getIsRecalAcknowledgedForSchedulingFromStore() {
|
||||
return store.getters.order.isRecalAcknowledgedForScheduling;
|
||||
},
|
||||
getScheduleApiResponse,
|
||||
getServiceZipCtuCodeFromStore() {
|
||||
return store.getters.order.serviceLocation.zipCodeCtu;
|
||||
|
|
@ -1351,7 +1364,25 @@ export default {
|
|||
hideLoadingModal() {
|
||||
this.$refs.loadingModal.hideModal();
|
||||
},
|
||||
async closeRecalAcknowledgementPopup() {
|
||||
this.showRecalAcknowledgementPopup = false;
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const showRecalAcknowledgementModal = await this.showRecalAcknowledgementModal();
|
||||
if (
|
||||
showRecalAcknowledgementModal &&
|
||||
this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES
|
||||
) {
|
||||
this.showRecalAcknowledgementPopup = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
storeActions.SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING,
|
||||
this.isRecalAcknowledgedForScheduling,
|
||||
false
|
||||
);
|
||||
|
||||
this.navigatingForward = true;
|
||||
var ctu = this.zipCodeCtu;
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
|
|
@ -1752,6 +1783,26 @@ export default {
|
|||
setSelectedDateToFirstAvailable() {
|
||||
this.selectedDate = this.getFirstAvailableDate();
|
||||
},
|
||||
async showRecalAcknowledgementModal() {
|
||||
const order = this.$store.getters.order;
|
||||
const containsRecalPart = containsRecalParts(order.lineItems);
|
||||
const hasPartWithRecalRequirement = anyPartWithRequiresRecalFlag(order.lineItems);
|
||||
|
||||
// if any of the glass parts contain an item with the requiresRecalibration flag set to true
|
||||
// and the order does not contain a recalibration part, show the recalibration acknowledgement modal
|
||||
if (
|
||||
!containsRecalPart &&
|
||||
hasPartWithRecalRequirement &&
|
||||
this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES
|
||||
) {
|
||||
this.$refs.navbar.removeLoader();
|
||||
this.$refs.recalAckModal.openModal();
|
||||
return true;
|
||||
}
|
||||
|
||||
this.closeRecalAcknowledgementPopup();
|
||||
return false;
|
||||
},
|
||||
async showMobileFirstModal() {
|
||||
if (!this.selectedRouteCodeData?.routeCode) {
|
||||
const pmMobileDays = experimentMixin.methods.getSettingValue(
|
||||
|
|
@ -1875,6 +1926,10 @@ export default {
|
|||
this.updateTimeSlot(timeSlotObj);
|
||||
this.forwardButtonAction();
|
||||
},
|
||||
updateRecalAcknowledgedAndNavigateForward(isAcknowledged) {
|
||||
this.isRecalAcknowledgedForScheduling = isAcknowledged;
|
||||
this.forwardButtonAction();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
appointmentTypeFromAppointmentTypeQuestion: {
|
||||
|
|
@ -1920,7 +1975,7 @@ export default {
|
|||
textBlock,
|
||||
timeSlotQuestion,
|
||||
mobileFirstModal,
|
||||
|
||||
recalAckModal,
|
||||
alert,
|
||||
serviceZipModalQuestion,
|
||||
appointmentTypeQuestion,
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ const getDefaultState = () => {
|
|||
lockToken: null,
|
||||
settledTenderAmount: 0,
|
||||
isRecalAckOptIn: false,
|
||||
isRecalAcknowledgedForScheduling: "",
|
||||
isMSRFeeApplicable: false,
|
||||
},
|
||||
applicationUser: {
|
||||
|
|
@ -343,6 +344,9 @@ export const mutations = {
|
|||
updateIsRecalAckOptIn(state, isRecalAckOptIn) {
|
||||
state.order.isRecalAckOptIn = isRecalAckOptIn;
|
||||
},
|
||||
updateIsRecalAcknowledgedForScheduling(state, isRecalAcknowledgedForScheduling) {
|
||||
state.order.isRecalAcknowledgedForScheduling = isRecalAcknowledgedForScheduling;
|
||||
},
|
||||
updateIsOemGlassSelected(state, isOemGlassSelected) {
|
||||
state.order.damage.installOemGlass = isOemGlassSelected;
|
||||
},
|
||||
|
|
@ -2965,6 +2969,13 @@ export const actions = {
|
|||
context.commit(storeMutations.UPDATE_IS_RECAL_ACK_OPT_IN, isRecalAckOptIn);
|
||||
},
|
||||
|
||||
saveIsRecalAcknowledgedForScheduling(context, isRecalAcknowledgedForScheduling) {
|
||||
context.commit(
|
||||
storeMutations.UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING,
|
||||
isRecalAcknowledgedForScheduling
|
||||
);
|
||||
},
|
||||
|
||||
saveIsOemGlassSelected(context, isOemGlassSelected) {
|
||||
context.commit(storeMutations.UPDATE_IS_OEM_GLASS_SELECTED, isOemGlassSelected);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ $red-600: #ac160b;
|
|||
$red-700: #840900;
|
||||
$red-800: #5b0600;
|
||||
$red-900: #330300;
|
||||
$red-1000: #a80019;
|
||||
$red-1100: #791710;
|
||||
|
||||
// Greens
|
||||
$green-100: #e3f2ea;
|
||||
|
|
|
|||
Loading…
Reference in a new issue