Merge branch 'develop' into feature/CSR-1388-tech-review-changes
This commit is contained in:
commit
a15cdcea59
13 changed files with 141 additions and 86 deletions
|
|
@ -53,6 +53,9 @@
|
|||
:text="recycleFeeCartItem.name"
|
||||
href="#!"
|
||||
@click-event="openModal(recyclingModalCmsWidgetName)" />
|
||||
<span v-else-if="cartItem == recycleFeeCartItem">
|
||||
{{ recycleFeeCartItem.name }}
|
||||
</span>
|
||||
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export function getDateDifferenceInDays(startDate, endDate) {
|
|||
// Use the built-in method to get the difference in milliseconds
|
||||
var difference = date1 - date2;
|
||||
// Convert milliseconds to days and return the result
|
||||
return Math.floor(difference / (1000 * 3600 * 24));
|
||||
return Math.round(difference / (1000 * 3600 * 24));
|
||||
}
|
||||
export function get12HourTimeFormat(time) {
|
||||
// Check correct time format and split into components
|
||||
|
|
|
|||
|
|
@ -173,13 +173,10 @@ export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
|
|||
alerts.push(createPromoSuccessAlert(newlyActivatedPromoCode));
|
||||
});
|
||||
|
||||
const inactivePromoCodesFromResponse = getPromoCodesFromPromoObjectsWithoutDuplicates(
|
||||
const newlyInactivatedPromos = getNewlyInactivatedPromos(
|
||||
oldInactivePromos,
|
||||
promoResponse.errors
|
||||
);
|
||||
const newlyInactivatedPromos = inactivePromoCodesFromResponse.filter(
|
||||
(errorPromoCode) => !oldInactivePromos.includes(errorPromoCode)
|
||||
);
|
||||
|
||||
newlyInactivatedPromos.forEach((newlyInactivatedPromo) => {
|
||||
alerts.push(createPromoErrorAlert(newlyInactivatedPromo));
|
||||
});
|
||||
|
|
@ -269,6 +266,45 @@ export function shouldStripPromoQueryString(fmgPageQueryValue) {
|
|||
return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue);
|
||||
}
|
||||
|
||||
export function getNewlyInactivatedPromos(oldInactivePromos, newInactivePromos) {
|
||||
const inactivePromoCodesFromResponse =
|
||||
getPromoCodesFromPromoObjectsWithoutDuplicates(newInactivePromos);
|
||||
return inactivePromoCodesFromResponse.filter(
|
||||
(errorPromoCode) => !oldInactivePromos.includes(errorPromoCode)
|
||||
);
|
||||
}
|
||||
|
||||
export function createPromoSuccessAlert(promoCode) {
|
||||
return {
|
||||
messageHeadline: "Promo applied!",
|
||||
messageCopy: `Promo "${getPromoCodeWithoutBundleIdentifier(
|
||||
promoCode.toUpperCase()
|
||||
)}" was successfully applied to your cart.`,
|
||||
type: "alert-success",
|
||||
isDismissible: true,
|
||||
shouldAutoFade: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPromoErrorAlert(promoCode, errorCode = null, additionalInfo) {
|
||||
const alert = {
|
||||
messageHeadline: "Promo error",
|
||||
type: "alert-danger",
|
||||
shouldAutoFade: false,
|
||||
isDismissible: true,
|
||||
};
|
||||
if (stackingPromoErrorCodes.includes(errorCode)) {
|
||||
alert.messageCopy = `Sorry, promo code "${getPromoCodeWithoutBundleIdentifier(
|
||||
promoCode.toUpperCase()
|
||||
)}" cannot be combined with "${additionalInfo[0].toUpperCase()}"`;
|
||||
} else {
|
||||
alert.messageCopy = `Sorry, promo code "${getPromoCodeWithoutBundleIdentifier(
|
||||
promoCode.toUpperCase()
|
||||
)}" is not valid`;
|
||||
}
|
||||
return alert;
|
||||
}
|
||||
|
||||
// private methods
|
||||
function findLineItemsWithPartType(typeToFind, itemsToSearch) {
|
||||
const partTypeMatches = itemsToSearch?.filter(
|
||||
|
|
@ -290,28 +326,3 @@ function getPromoCodesFromPromoObjectsWithoutDuplicates(promos) {
|
|||
|
||||
return promoCodes;
|
||||
}
|
||||
|
||||
function createPromoSuccessAlert(promoCode) {
|
||||
return {
|
||||
messageHeadline: "Promo applied!",
|
||||
messageCopy: `Promo "${promoCode.toUpperCase()}" was successfully applied to your cart.`,
|
||||
type: "alert-success",
|
||||
isDismissible: true,
|
||||
shouldAutoFade: true,
|
||||
};
|
||||
}
|
||||
|
||||
function createPromoErrorAlert(promoCode, errorCode = null, additionalInfo) {
|
||||
const alert = {
|
||||
messageHeadline: "Promo error",
|
||||
type: "alert-danger",
|
||||
shouldAutoFade: false,
|
||||
isDismissible: true,
|
||||
};
|
||||
if (stackingPromoErrorCodes.includes(errorCode)) {
|
||||
alert.messageCopy = `Sorry, promo code "${promoCode.toUpperCase()}" cannot be combined with "${additionalInfo[0].toUpperCase()}"`;
|
||||
} else {
|
||||
alert.messageCopy = `Sorry, promo code "${promoCode.toUpperCase()}" is not valid`;
|
||||
}
|
||||
return alert;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,13 +98,13 @@ export default {
|
|||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
const lineItemsFromStore = deepClone(store.getters.order.lineItems);
|
||||
const lineItemsFromSubmittedOrder = deepClone(store.getters.submittedOrder.lineItems);
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.lineItems = lineItemsFromStore;
|
||||
vm.vaps = lineItemsFromStore.vaps;
|
||||
vm.lineItems = lineItemsFromSubmittedOrder;
|
||||
vm.vaps = lineItemsFromSubmittedOrder.vaps;
|
||||
});
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -287,13 +287,6 @@ export default {
|
|||
return answers[answerIndex];
|
||||
};
|
||||
|
||||
if (!this.rainDefenseInCart) {
|
||||
// NO RAIN DEFENSE IN CART; SHOW RAIN DEFENSE BUTTON
|
||||
const modalData = getAnswer("RainDefenseModal", this.answersCmsData);
|
||||
modalData.SubText = "+$" + this.rainDefenseModal?.listPrice;
|
||||
buttons.push(modalData);
|
||||
}
|
||||
|
||||
const modalData = getAnswer("WipersModal", this.answersCmsData);
|
||||
if (this.wipersOffered === wipersOfferedStrings.BOTH) {
|
||||
// NO REAR WIPERS or FRONT WIPERS IN CART; SHOW COMBO BUTTON
|
||||
|
|
@ -318,6 +311,13 @@ export default {
|
|||
buttons.push(modalData);
|
||||
}
|
||||
|
||||
if (!this.rainDefenseInCart) {
|
||||
// NO RAIN DEFENSE IN CART; SHOW RAIN DEFENSE BUTTON
|
||||
const modalData = getAnswer("RainDefenseModal", this.answersCmsData);
|
||||
modalData.SubText = "+$" + this.rainDefenseModal?.listPrice;
|
||||
buttons.push(modalData);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
},
|
||||
wipersOfferedStrings() {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
class="pb-2"
|
||||
v-model="lineItems"
|
||||
:availableVaps="availableVaps"
|
||||
linkWidgetName="PromoLinkWidget"
|
||||
modalWidgetName="PromoModalWidget" />
|
||||
|
||||
<hr class="mb-5" />
|
||||
|
|
@ -97,6 +96,11 @@ import {
|
|||
revalidatePromosAndValidateQueryStringPromo,
|
||||
buildToastMessagesFromRevalidateOrValidatePromoResponse,
|
||||
getVapsThatNeedToBeAddedToSatisfyPromos,
|
||||
getPromoCodeWithoutBundleIdentifier,
|
||||
removeCurrentlyActivePromoCodesFromInactivePromos,
|
||||
createPromoSuccessAlert,
|
||||
createPromoErrorAlert,
|
||||
getNewlyInactivatedPromos,
|
||||
} from "@/helpers/promotions-helper";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
|
|
@ -220,6 +224,10 @@ export default {
|
|||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.availableVaps = taxedVaps;
|
||||
vm.lineItems = lineItems;
|
||||
vm.inactivePromos = removeCurrentlyActivePromoCodesFromInactivePromos(
|
||||
vm.lineItems.promos,
|
||||
vm.inactivePromos
|
||||
);
|
||||
vm.updateFooterButtonText(vm.customCtaCopy);
|
||||
|
||||
if (revalidatePromoResponse) {
|
||||
|
|
@ -335,17 +343,20 @@ export default {
|
|||
"payment-method",
|
||||
false
|
||||
);
|
||||
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
|
||||
revalidatePromoResponse,
|
||||
this.lineItems.promos,
|
||||
this.inactivePromos
|
||||
);
|
||||
revalidateAlerts.forEach((alert) => {
|
||||
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
||||
});
|
||||
|
||||
// Handle error alerts here, success alerts are handled in the watcher
|
||||
if (revalidatePromoResponse.errors.length) {
|
||||
getNewlyInactivatedPromos(
|
||||
this.inactivePromos,
|
||||
revalidatePromoResponse.errors
|
||||
).forEach((promoCode) => {
|
||||
const errorAlert = createPromoErrorAlert(promoCode);
|
||||
this.$refs.funnelHeader.pushGlobalAlert(errorAlert, errorAlert.shouldAutoFade);
|
||||
});
|
||||
}
|
||||
this.lineItems.promos = revalidatePromoResponse.promoLineItems;
|
||||
this.inactivePromos = revalidatePromoResponse.errors.map((x) => x.promoCode);
|
||||
this.inactivePromos = revalidatePromoResponse.errors.map((x) =>
|
||||
getPromoCodeWithoutBundleIdentifier(x.promoCode)
|
||||
);
|
||||
this.$refs.loadingModal.hideModal();
|
||||
},
|
||||
backButtonAction() {
|
||||
|
|
@ -479,7 +490,19 @@ export default {
|
|||
) {
|
||||
return;
|
||||
}
|
||||
if (oldValue.vaps.length != newValue.vaps.length) {
|
||||
if (oldValue.promos.length < newValue.promos.length) {
|
||||
const oldPromoCodes = oldValue.promos.map(
|
||||
(promoObject) => promoObject.promoCode
|
||||
);
|
||||
const newlyActivatedPromoCodes = newValue.promos.filter(
|
||||
(newPromo) => !oldPromoCodes.includes(newPromo.promoCode)
|
||||
);
|
||||
const alert = createPromoSuccessAlert(newlyActivatedPromoCodes[0].promoCode);
|
||||
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
||||
} else if (
|
||||
oldValue.promos.length == newValue.promos.length &&
|
||||
oldValue.vaps.length != newValue.vaps.length
|
||||
) {
|
||||
this.revalidatePromos();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,13 +14,8 @@ jest.mock("@/digital-components/modal/modal", () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const linkWidgetName = "linkWidgetName";
|
||||
const modalWidgetName = "modalWidgetName";
|
||||
|
||||
const mockLinkCmsContent = {
|
||||
BodyText: "Sample link body text here.",
|
||||
};
|
||||
|
||||
const mockModalCmsContent = {
|
||||
FooterText: "Sample modal footer text here.",
|
||||
};
|
||||
|
|
@ -28,10 +23,6 @@ const mockModalCmsContent = {
|
|||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
if (widgetName === linkWidgetName) {
|
||||
return mockLinkCmsContent[cmsFieldName];
|
||||
}
|
||||
|
||||
if (widgetName === modalWidgetName) {
|
||||
return mockModalCmsContent[cmsFieldName];
|
||||
}
|
||||
|
|
@ -48,7 +39,6 @@ describe("promo-modal-question.vue", () => {
|
|||
const wrapper = mount(promoModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
linkWidgetName: linkWidgetName,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
|
|
@ -74,7 +64,6 @@ describe("promo-modal-question.vue", () => {
|
|||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: lineItems,
|
||||
linkWidgetName: linkWidgetName,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@
|
|||
:ref="modalName"
|
||||
:headerText="modalHeaderText"
|
||||
:onModalClosedCallback="onModalClosed"
|
||||
:onModalOpenedCallback="focusOnPromoInput"
|
||||
:footerButtonText="modalFooterText"
|
||||
@footer-button-event="addPromoCode">
|
||||
<promoQuestion
|
||||
ref="promoQuestion"
|
||||
customInputId="promoCode"
|
||||
v-model="promoCode"
|
||||
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }"
|
||||
cmsWidgetName="PromoQuestionWidget" />
|
||||
<alert
|
||||
ref="alertInvalidPromo"
|
||||
|
|
@ -74,13 +76,12 @@ export default {
|
|||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
linkWidgetName: String,
|
||||
modalWidgetName: String,
|
||||
availableVaps: Object,
|
||||
},
|
||||
computed: {
|
||||
promoLinkText() {
|
||||
return this.getCmsContent(this.linkWidgetName, "BodyText");
|
||||
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||
},
|
||||
modalHeaderText() {
|
||||
return this.getCmsContent("PromoQuestionWidget", "QuestionText");
|
||||
|
|
@ -127,6 +128,9 @@ export default {
|
|||
getPromoCode() {
|
||||
return this.lineItems.promos?.[0].promoCode;
|
||||
},
|
||||
onInputIdAssigned(inputId) {
|
||||
this.promoTextInputId = inputId;
|
||||
},
|
||||
openModal() {
|
||||
this.modal.openModal();
|
||||
},
|
||||
|
|
@ -217,13 +221,11 @@ export default {
|
|||
} else {
|
||||
if (promoCodeData.errorCode == promoErrorCodes.PROMO_STACKING_NOT_ALLOWED) {
|
||||
this.displayInvalidOnOrderPromoAlert = true;
|
||||
this.focusOnPromoInput();
|
||||
this.resetModalButtonStyle();
|
||||
} else {
|
||||
this.displayInvalidPromoAlert = true;
|
||||
this.focusOnPromoInput();
|
||||
this.resetModalButtonStyle();
|
||||
}
|
||||
this.focusOnPromoInput();
|
||||
this.resetModalButtonStyle();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
cornerStyle="rounded"
|
||||
:displayQuestionText="false"
|
||||
isRequired
|
||||
@input="validateAlphanumeric"
|
||||
validationRules="promo-required" />
|
||||
</template>
|
||||
|
||||
|
|
@ -22,6 +23,8 @@ import { errorMessages } from "@/constants/error-messages";
|
|||
// Define Validation Rules
|
||||
defineRule("promo-required", required(errorMessages.PROMO_REQUIRED));
|
||||
|
||||
const ALPHANUMERIC_REGEX = /[^a-zA-Z0-9]/g;
|
||||
|
||||
export default {
|
||||
name: "promo-question",
|
||||
props: {
|
||||
|
|
@ -38,6 +41,11 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
validateAlphanumeric() {
|
||||
this.value = this.value.replace(ALPHANUMERIC_REGEX, "");
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textboxQuestion,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -267,8 +267,6 @@ export default {
|
|||
"payment"
|
||||
);
|
||||
|
||||
// TODO: Data fetching Promise declarations
|
||||
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
|
|
@ -360,6 +358,7 @@ export default {
|
|||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.fetchSignatureInfo(resultMap.signature);
|
||||
vm.setIFrameListener();
|
||||
|
||||
vm.availableVaps = taxedVaps;
|
||||
vm.lineItems = lineItems;
|
||||
|
|
@ -508,17 +507,26 @@ export default {
|
|||
}
|
||||
},
|
||||
getPiaLineItems() {
|
||||
let itemsForPia = "";
|
||||
const itemMap = new Map();
|
||||
const glassParts = store.getters.order.lineItems.glassParts;
|
||||
const supportingItems = store.getters.order.lineItems.supportingItems;
|
||||
const vaps = store.getters.order.lineItems.vaps;
|
||||
const items = [...(glassParts ?? []), ...(supportingItems ?? []), ...(vaps ?? [])];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const amount = items[i].laborAmount + items[i].sellingPrice;
|
||||
itemsForPia += items[i].partType + "|" + amount + "|1|";
|
||||
for (const item of items) {
|
||||
const amount = item.laborAmount + item.sellingPrice;
|
||||
const key = `${item.partType}|${amount}`;
|
||||
|
||||
if (itemMap.has(key)) {
|
||||
itemMap.get(key).quantity++;
|
||||
} else {
|
||||
itemMap.set(key, { partType: item.partType, amount, quantity: 1 });
|
||||
}
|
||||
}
|
||||
return itemsForPia;
|
||||
|
||||
return Array.from(itemMap.values())
|
||||
.map((item) => `${item.partType}|${item.amount}|${item.quantity}`)
|
||||
.join("||");
|
||||
},
|
||||
getAmountDue() {
|
||||
return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
|
||||
|
|
@ -577,6 +585,16 @@ export default {
|
|||
|
||||
this.submitHopForm();
|
||||
},
|
||||
handleIFrameContentWindwMessage(event) {
|
||||
if (event.data.indexOf("afterpayClosed") > -1) {
|
||||
this.backButtonAction();
|
||||
}
|
||||
},
|
||||
setIFrameListener() {
|
||||
window.addEventListener("message", (event) =>
|
||||
this.handleIFrameContentWindwMessage(event)
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@
|
|||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="siteSubHeader" />
|
||||
<div
|
||||
cmsWidgetName="FindYourVehicleCopyWidget"
|
||||
v-html="this.findYourVehicleCopy"
|
||||
class="mt-2 mb-4 small"></div>
|
||||
<vehicleQuestion
|
||||
ref="vehicleYearQuestion"
|
||||
v-model="selectedYear"
|
||||
|
|
@ -298,9 +294,6 @@ export default {
|
|||
else if (this.imageUrl == null && this.carId !== null) return false;
|
||||
else return true;
|
||||
},
|
||||
findYourVehicleCopy() {
|
||||
return this.getCmsContent("FindYourVehicleCopyWidget", "QuestionText");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getVehicle(year, make, model, style) {
|
||||
|
|
|
|||
|
|
@ -1317,7 +1317,6 @@ export const actions = {
|
|||
});
|
||||
|
||||
syncLineItemIds(response.data, context.getters.order.lineItems.vaps);
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
|
|
@ -2349,6 +2348,7 @@ export const actions = {
|
|||
|
||||
activePromosToUse = activePromosToUse ?? order.lineItems.promos;
|
||||
lineItemsToUse = lineItemsToUse ? deepClone(lineItemsToUse) : deepClone(order.lineItems);
|
||||
addGuidToLineItemsIfNotAlreadyThere(lineItemsToUse.vaps);
|
||||
lineItemsToUse.promos = activePromosToUse;
|
||||
|
||||
inactivePromosToUse = inactivePromosToUse ?? order.payment.inactivePromos;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
class="loader"
|
||||
role="alert"
|
||||
aria-label="Loading new page"
|
||||
@click="captureClick"
|
||||
v-bind:class="[
|
||||
this.loaderColor,
|
||||
this.loaderPosition,
|
||||
|
|
@ -30,6 +31,13 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
captureClick(event) {
|
||||
if (!this.allowPageInteraction) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -95,7 +103,7 @@ export default {
|
|||
}
|
||||
&.allow-ui-interaction::before {
|
||||
//no-block to enable clicking on certain buttons with loaders while the loader is actives
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue