From 2cbae603bdb0ef5986e2bdcb31d8157184247b67 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Thu, 9 Nov 2023 09:11:19 -0500 Subject: [PATCH 1/5] CSR-1452 | Toast messages built and displayed For payment-method and quote, only for initial page load promo logic --- src/helpers/promotions-helper.js | 76 +++++++++++++++++++ src/layouts/payment-method/payment-method.vue | 19 ++++- src/layouts/quote/quote.vue | 18 ++++- src/store/index.js | 1 + 4 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/helpers/promotions-helper.js b/src/helpers/promotions-helper.js index 1689d9550..40ef6cafd 100644 --- a/src/helpers/promotions-helper.js +++ b/src/helpers/promotions-helper.js @@ -16,6 +16,10 @@ export const addableVapsPromoPartNumbers = [ promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER, ]; +const promoErrorCodes = { + CANNOT_COMBINE: "SimilarPromoAlreadyOnOrder" +}; + export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"]; export function getPromosWithAddableVaps(promoList) { @@ -110,6 +114,73 @@ export async function revalidatePromosAndValidateNewPromo( return { validatePromoResponse, revalidatePromoResponse }; } +export function buildToastMessagesFromRevalidateOrValidatePromoResponse(promoResponse, oldActivePromos = [], oldInactivePromos = []) { + console.log('oldActivePromos', oldActivePromos); + console.log('oldInactivePromos', oldInactivePromos); + console.log('promoResponse', promoResponse); + const alerts = []; + // Revalidate always has an "errors" array + if (promoResponse.errors) { + const oldPromoCodes = oldActivePromos.map(promo => getPromoCodeWithoutBundleIdentifier(promo.promoCode)); + const activePromoCodesFromResponse = []; + // consolidate bundle promos to avoid displaying multiple toast messages for one bundle + promoResponse.promoLineItems.forEach(newPromo => { + const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(newPromo.promoCode); + if (!activePromoCodesFromResponse.includes(promoCodeToDisplay)) { + activePromoCodesFromResponse.push(promoCodeToDisplay); + } + }); + console.log('oldPromoCodes', oldPromoCodes); + console.log('activePromoCodesFromResponse', activePromoCodesFromResponse); + const newlyActivatedPromoCodes = activePromoCodesFromResponse.filter(newPromoCode => !oldPromoCodes.includes(newPromoCode)); + const newlyInactivatedPromos = promoResponse.errors.filter(error => !oldInactivePromos.includes(error.promoCode)); + newlyActivatedPromoCodes.forEach(newlyActivatedPromoCode => { + alerts.push({ + messageHeadline: "Promo applied!", + messageCopy: `Promo "${newlyActivatedPromoCode}" was successfully applied to your cart.`, + type: "alert-success", + isDismissible: true, + shouldAutoFade: true, + }); + }); + newlyInactivatedPromos.forEach(newlyInactivatedPromo => { + console.log(newlyInactivatedPromo); + alerts.push({ + messageHeadline: "Promo error", + messageCopy: `Sorry, promo code "${newlyInactivatedPromo}" is not valid`, + type: "alert-danger", + isDismissible: true, + shouldAutoFade: false, + }); + }); + } else { + if (promoResponse.orderPromos) { + let promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(promoResponse.orderPromos[0].promoCode); + alerts.push({ + messageHeadline: "Promo applied!", + messageCopy: `Promo "${promoCodeToDisplay}" was successfully applied to your cart.`, + type: "alert-success", + isDismissible: true, + shouldAutoFade: true, + }); + } else { + const alertToPush = { + messageHeadline: "Promo error", + type: "alert-danger", + shouldAutoFade: false, + isDismissible: true, + } + if (promoResponse.errorCode == promoErrorCodes.CANNOT_COMBINE) { + alertToPush.messageCopy = `Sorry, promo code "${promoResponse.promoCode}" cannot be combined with ${promoResponse.additionalInfo[0]}`; + } else { + alertToPush.messageCopy = `Sorry, promo code "${promoResponse.promoCode}" is not valid`; + } + alerts.push(alertToPush); + } + } + return alerts; +} + export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) { const matchingPromos = []; promos.forEach((promo) => { @@ -152,6 +223,11 @@ export function getVapsThatNeedToBeAddedToSatisfyPromos( } } +// bundle promos look like: "bundlePromo/###"" for each promo, this returns "bundlePromo" only +export function getPromoCodeWithoutBundleIdentifier(promoCode) { + return promoCode.substring(0, promoCode.indexOf("/")); +} + export function shouldStripPromoQueryString(fmgPageQueryValue) { return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue); } diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 6e87235b0..33b338a6e 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -88,7 +88,7 @@ import { paymentMethods } from "@/constants/payment-method-constants"; import { experimentSettings } from "@/constants/experiments"; import { revalidatePromosAndValidateNewPromo, - getPromosWithAddableVaps, + buildToastMessagesFromRevalidateOrValidatePromoResponse, getVapsThatNeedToBeAddedToSatisfyPromos, } from "@/helpers/promotions-helper"; import { queryStrings } from "@/constants/query-strings"; @@ -169,8 +169,11 @@ export default { ); // Promo logic - const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO); + // Populate the previous state of promos for toast message usage in "next()" + const oldActivePromos = store.getters.lineItems.promos?.slice(0); + const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0); + const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO); const { validatePromoResponse, revalidatePromoResponse } = await revalidatePromosAndValidateNewPromo( promoCodeFromQueryString, @@ -219,8 +222,18 @@ export default { vm.setCmsContent(resultMap.cmsContent); vm.availableLineItems = taxedAvailableLineItems; vm.lineItems = lineItems; - vm.updateFooterButtonText(vm.customCtaCopy); + + if (revalidatePromoResponse) { + const validateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(revalidatePromoResponse, oldActivePromos, oldInactivePromos); + if (validateAlerts.length) { + vm.$refs.funnelHeader.triggerGlobalAlert(validateAlerts[0], validateAlerts[0].shouldAutoFade); + } + } + if (validatePromoResponse) { + const validateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(validatePromoResponse); + vm.$refs.funnelHeader.triggerGlobalAlert(validateAlerts[0], validateAlerts[0].shouldAutoFade); + } }); }, data() { diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index e6f34101f..46f36b330 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -80,7 +80,8 @@ import { Form, defineRule } from "vee-validate"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { applicationConfig } from "@/constants/application-config"; import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states"; -import { revalidatePromosAndValidateNewPromo } from "@/helpers/promotions-helper"; +import { revalidatePromosAndValidateNewPromo, + buildToastMessagesFromRevalidateOrValidatePromoResponse } from "@/helpers/promotions-helper"; import { queryStrings } from "@/constants/query-strings"; import { getQuerystringParameter } from "@/helpers/querystring-helper"; @@ -151,6 +152,10 @@ export default { ); // Promo logic + // Populate the previous state of promos for toast message usage in "next()" + const oldActivePromos = store.getters.lineItems.promos?.slice(0); + const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0); + const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO); const { validatePromoResponse, revalidatePromoResponse } = @@ -169,6 +174,17 @@ export default { vm.availableLineItems = pricingResults; vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems); vm.newValidatedPromos = validatePromoResponse?.orderPromos; + + if (revalidatePromoResponse) { + const validateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(revalidatePromoResponse, oldActivePromos, oldInactivePromos); + if (validateAlerts.length) { + vm.$refs.funnelHeader.triggerGlobalAlert(validateAlerts[0], validateAlerts[0].shouldAutoFade); + } + } + if (validatePromoResponse) { + const validateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(validatePromoResponse); + vm.$refs.funnelHeader.triggerGlobalAlert(validateAlerts[0], validateAlerts[0].shouldAutoFade); + } }); }, data() { diff --git a/src/store/index.js b/src/store/index.js index e2915a1af..d0a570b50 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2104,6 +2104,7 @@ export const actions = { addGuidToLineItemsIfNotAlreadyThere(vaps); context.commit(storeMutations.UPDATE_VAPS, vaps); }, + // COMBINE THESE savePromos(context, promos) { context.commit(storeMutations.UPDATE_PROMOS, promos); }, From 7eea3baa13b002c003cd9212d597fc5cb30064cf Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Thu, 9 Nov 2023 14:52:18 -0500 Subject: [PATCH 2/5] CSR-1452 | Alerts for promos and alert.vue refactoring --- src/fmg-components/cart/cart.vue | 3 +- .../funnel-header/funnel-header.vue | 56 +++++++++---------- src/helpers/promotions-helper.js | 18 +++--- src/layouts/payment-method/payment-method.vue | 17 ++++-- src/layouts/quote/quote.vue | 10 ++-- 5 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 7390495f2..a44ef3c07 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -221,7 +221,7 @@ export default { let cartItem = null; cartItem = { - name: "Some Kind of Promo", // get from CMS? + name: promoLineItem.promoCode, category: cartItemCategories.PROMOS, cartItemType: promoLineItem.partType, isDisplayed: true, @@ -230,6 +230,7 @@ export default { (promoLineItem.laborAmount ?? 0) + (promoLineItem.sellingPrice ?? 0), salesTax: promoLineItem.salesTax, + lineItems: [], }; promoLineItem.cartItemType = cartItem.cartItemType; diff --git a/src/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue index d9c598bb4..26a9652d4 100644 --- a/src/fmg-components/funnel-header/funnel-header.vue +++ b/src/fmg-components/funnel-header/funnel-header.vue @@ -5,18 +5,18 @@ Safelite logo - - - - +