diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue
index 76269bbf2..14b756f02 100644
--- a/src/fmg-components/cart/cart.vue
+++ b/src/fmg-components/cart/cart.vue
@@ -53,6 +53,9 @@
:text="recycleFeeCartItem.name"
href="#!"
@click-event="openModal(recyclingModalCmsWidgetName)" />
+
+ {{ recycleFeeCartItem.name }}
+
{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}
diff --git a/src/helpers/date-helper.js b/src/helpers/date-helper.js
index 482440431..b5afc535c 100644
--- a/src/helpers/date-helper.js
+++ b/src/helpers/date-helper.js
@@ -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
diff --git a/src/helpers/promotions-helper.js b/src/helpers/promotions-helper.js
index f91a7a91b..b8b8f2b4e 100644
--- a/src/helpers/promotions-helper.js
+++ b/src/helpers/promotions-helper.js
@@ -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;
-}
diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue
index bd8ee8acd..8f34feb23 100644
--- a/src/layouts/confirmation/confirmation.vue
+++ b/src/layouts/confirmation/confirmation.vue
@@ -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() {
diff --git a/src/layouts/payment-method/add-vaps-modal-buttons/add-vaps-modal-buttons.vue b/src/layouts/payment-method/add-vaps-modal-buttons/add-vaps-modal-buttons.vue
index c822fa318..0dac2d1db 100644
--- a/src/layouts/payment-method/add-vaps-modal-buttons/add-vaps-modal-buttons.vue
+++ b/src/layouts/payment-method/add-vaps-modal-buttons/add-vaps-modal-buttons.vue
@@ -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() {
diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue
index ac111fcdb..4b9b1e761 100644
--- a/src/layouts/payment-method/payment-method.vue
+++ b/src/layouts/payment-method/payment-method.vue
@@ -33,7 +33,6 @@
class="pb-2"
v-model="lineItems"
:availableVaps="availableVaps"
- linkWidgetName="PromoLinkWidget"
modalWidgetName="PromoModalWidget" />
@@ -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();
}
},
diff --git a/src/layouts/payment-method/promo-modal-question/promo-modal-question.spec.js b/src/layouts/payment-method/promo-modal-question/promo-modal-question.spec.js
index 3846a6bfd..94de72765 100644
--- a/src/layouts/payment-method/promo-modal-question/promo-modal-question.spec.js
+++ b/src/layouts/payment-method/promo-modal-question/promo-modal-question.spec.js
@@ -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,
diff --git a/src/layouts/payment-method/promo-modal-question/promo-modal-question.vue b/src/layouts/payment-method/promo-modal-question/promo-modal-question.vue
index ac42fce2e..1f9fc4a9a 100644
--- a/src/layouts/payment-method/promo-modal-question/promo-modal-question.vue
+++ b/src/layouts/payment-method/promo-modal-question/promo-modal-question.vue
@@ -10,12 +10,14 @@
:ref="modalName"
:headerText="modalHeaderText"
:onModalClosedCallback="onModalClosed"
+ :onModalOpenedCallback="focusOnPromoInput"
:footerButtonText="modalFooterText"
@footer-button-event="addPromoCode">
@@ -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,
},
diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue
index 3cd7e3f0b..0cd632dc6 100644
--- a/src/layouts/payment/payment.vue
+++ b/src/layouts/payment/payment.vue
@@ -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,
diff --git a/src/layouts/vehicle/vehicle.vue b/src/layouts/vehicle/vehicle.vue
index ea63eefcc..282133743 100644
--- a/src/layouts/vehicle/vehicle.vue
+++ b/src/layouts/vehicle/vehicle.vue
@@ -9,10 +9,6 @@