diff --git a/src/helpers/promotions-helper.js b/src/helpers/promotions-helper.js index a5d94789b..1689d9550 100644 --- a/src/helpers/promotions-helper.js +++ b/src/helpers/promotions-helper.js @@ -1,6 +1,7 @@ import store from "@/store"; import { storeActions } from "@/constants/store-actions"; import { partTypeStrings } from "@/constants/part-type-strings"; +import { deepClone } from "@/helpers/object-helper"; import baseMixin from "@/mixins/base-mixin.js"; export const promoPartNumberStrings = { @@ -126,6 +127,31 @@ export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) { return matchingPromos; } +// Returns the vaps that the provided requires to be on the order to satisfy +// Items that are already in will not be returned +export function getVapsThatNeedToBeAddedToSatisfyPromos( + promos, + availableLineItems, + lineItemsOnOrder +) { + const clonedVaps = deepClone(lineItemsOnOrder.vaps ?? []); + const promosWithAddableVaps = getPromosWithAddableVaps(promos); + if (promosWithAddableVaps.length) { + const matchingLineItems = getLineItemsThatMatchPromos( + promosWithAddableVaps, + availableLineItems + ); + // Check if matching items are already in the order + const idsOfVapsAlreadyInOrder = clonedVaps.map((lineItem) => lineItem.id); + const vapsToAddToCart = matchingLineItems.filter( + (lineItem) => !idsOfVapsAlreadyInOrder.includes(lineItem.id) + ); + return vapsToAddToCart; + } else { + return []; + } +} + export function shouldStripPromoQueryString(fmgPageQueryValue) { return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue); } 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 bb378c619..b4fa7a3b0 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 @@ -97,7 +97,11 @@ export default { this.$refs[modalName].closeModal(); }, addRainDefenseToCart(modalName, part) { - this.currentCartItems.vaps.push(part); + // Clone vaps array, then replace this.currentCartItems.vaps with it at the end + // in order to successfully trigger parent's watcher + const clonedVaps = this.currentCartItems.vaps.slice(0); + clonedVaps.push(part); + this.currentCartItems.vaps = clonedVaps; this.closeModal(modalName); const message = this.getRainDefenseAddedMessage(); this.$emit("added-to-cart", { @@ -133,19 +137,23 @@ export default { this.selectedWipers = ["REAR"]; } } + // Clone vaps array, then replace this.currentCartItems.vaps with it at the end + // in order to successfully trigger parent's watcher + const clonedVaps = this.currentCartItems.vaps.slice(0); itemsForCart.forEach((type) => { if (type === wipersOfferedStrings.FRONT) { part.frontWiperLineItems.forEach((part) => { - this.currentCartItems.vaps.push(part); + clonedVaps.push(part); }); } if (type === wipersOfferedStrings.REAR) { part.rearWiperLineItems.forEach((part) => { - this.currentCartItems.vaps.push(part); + clonedVaps.push(part); }); } }); + this.currentCartItems.vaps = clonedVaps; this.closeModal(modalName); diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 8a5f3a21d..6e87235b0 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -88,7 +88,8 @@ import { paymentMethods } from "@/constants/payment-method-constants"; import { experimentSettings } from "@/constants/experiments"; import { revalidatePromosAndValidateNewPromo, - getAddableVapsFromAvailableLineItems, + getPromosWithAddableVaps, + getVapsThatNeedToBeAddedToSatisfyPromos, } from "@/helpers/promotions-helper"; import { queryStrings } from "@/constants/query-strings"; import { getQuerystringParameter } from "@/helpers/querystring-helper"; @@ -177,7 +178,9 @@ export default { "payment-method" ); - availableLineItems.push(...(validatePromoResponse?.orderPromos ?? [])); + const newValidatedPromos = validatePromoResponse?.orderPromos ?? []; + + availableLineItems.push(...newValidatedPromos); // End of promo logic const taxedAvailableLineItems = await baseMixin.methods.dispatchStoreActionWithLogging( @@ -201,6 +204,15 @@ export default { availableLineItems, lineItemsFromStore ); + // Add items that were not in the store yet but added via query string promo validation + lineItems.promos = lineItems.promos ?? []; + lineItems.promos.push(...newValidatedPromos); + const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos( + newValidatedPromos, + availableLineItems, + lineItems + ); + lineItems.vaps.push(...vapsToAddToCart); // Call the "next" function to complete the transition to this page. next((vm) => { @@ -412,11 +424,35 @@ export default { shouldDisplayPiaAlert() { return this.$route.params[this.routerParams.DISPLAY_PIA_ALERT]; }, + // 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. + // This creates a separate location for oldValue/newValue so they can be compared + // See Vue github issue #2164 + lineItemsCloneForWatcher() { + return Object.assign({}, this.lineItems); + }, }, watch: { customCtaCopy(newValue) { this.updateFooterButtonText(newValue); }, + lineItemsCloneForWatcher: { + handler(newValue, oldValue) { + if ( + !oldValue || + oldValue.length == 0 || + !oldValue.vaps || + !newValue || + newValue.length == 0 + ) { + return; + } + if (oldValue.vaps.length != newValue.vaps.length) { + this.revalidatePromos(); + } + }, + deep: true, + }, }, components: { funnelHeader,