Merge pull request #1512 from Safelite/feature/skiener/CSR-1452

CSR-1452 | Promo alerts
This commit is contained in:
scottkiener 2023-11-10 11:01:03 -05:00 committed by GitHub
commit 90b499424e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 323 additions and 62 deletions

View file

@ -86,8 +86,7 @@ const storeActions = {
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING: SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
"saveSupportingItemsSuppressingStateResetting", "saveSupportingItemsSuppressingStateResetting",
SAVE_VAPS: "saveVaps", SAVE_VAPS: "saveVaps",
SAVE_PROMOS: "savePromos", SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS: "saveActiveAndOrInactivePromos",
SAVE_INACTIVE_PROMOS: "saveInactivePromos",
SAVE_CUSTOMER_DETAILS: "saveCustomerDetails", SAVE_CUSTOMER_DETAILS: "saveCustomerDetails",
SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes", SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes",
SAVE_CCTOKEN: "saveCCToken", SAVE_CCTOKEN: "saveCCToken",

View file

@ -221,7 +221,7 @@ export default {
let cartItem = null; let cartItem = null;
cartItem = { cartItem = {
name: "Some Kind of Promo", // get from CMS? name: promoLineItem.promoCode,
category: cartItemCategories.PROMOS, category: cartItemCategories.PROMOS,
cartItemType: promoLineItem.partType, cartItemType: promoLineItem.partType,
isDisplayed: true, isDisplayed: true,
@ -230,6 +230,7 @@ export default {
(promoLineItem.laborAmount ?? 0) + (promoLineItem.laborAmount ?? 0) +
(promoLineItem.sellingPrice ?? 0), (promoLineItem.sellingPrice ?? 0),
salesTax: promoLineItem.salesTax, salesTax: promoLineItem.salesTax,
lineItems: [],
}; };
promoLineItem.cartItemType = cartItem.cartItemType; promoLineItem.cartItemType = cartItem.cartItemType;

View file

@ -5,18 +5,18 @@
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" /> <img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
<menuModal /> <menuModal />
</div> </div>
<template v-for="globalAlert in globalAlertMessages" :key="globalAlert.id">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<alert <alert
v-if="displayGlobalAlert" v-if="globalAlert.displayAlert"
class="rounded-0 w-100 border-0 shadow-sm" class="rounded-0 w-100 border-0 shadow-sm"
cmsWidgetName="GlobalAlert" cmsWidgetName="GlobalAlert"
:manualHeadline="globalAlertMessage.messageHeadline" :manualHeadline="globalAlert.messageHeadline"
:manualCopy="globalAlertMessage.messageCopy" :manualCopy="globalAlert.messageCopy"
:alertClass="globalAlertMessage.type" :alertClass="globalAlert.type"
:onAlertCloseCallback="onAlertClose" v-bind:isDismissible="globalAlert.isDismissible" />
v-bind:isDismissible="globalAlertMessage.isDismissible" /> </transition>
</transition> </template>
</template> </template>
<script> <script>
@ -32,14 +32,8 @@ export default {
name: "funnel-header", name: "funnel-header",
data() { data() {
return { return {
displayGlobalAlert: false, alertIdCounter: 0,
globalAlertMessage: { globalAlertMessages: [],
isDismissible: false,
messageCopy: "",
messageHeadline: "",
type: "",
},
countdownTimer: () => {},
}; };
}, },
props: { props: {
@ -51,18 +45,18 @@ export default {
}, },
}, },
methods: { methods: {
triggerGlobalAlert(alertEvent, isAutoDismissing) { pushGlobalAlert(alertToPush, isAutoDismissing) {
clearTimeout(this.countdownTimer); alertToPush.displayAlert = true;
this.displayGlobalAlert = true; alertToPush.id = this.alertIdCounter;
this.globalAlertMessage = alertEvent; this.alertIdCounter++;
if (isAutoDismissing) { if (isAutoDismissing) {
this.countdownTimer = setTimeout(() => { setTimeout(() => {
this.displayGlobalAlert = false; alertToPush.displayAlert = false;
// force a re-evaluation of the globalAlertMessages to detect the v-if change
this.globalAlertMessages = this.globalAlertMessages.splice(0);
}, ALERT_DURATION); }, ALERT_DURATION);
} }
}, this.globalAlertMessages.push(alertToPush);
onAlertClose() {
this.displayGlobalAlert = false;
}, },
}, },
components: { components: {
@ -77,8 +71,8 @@ export default {
); );
// If alert event is on the bus, then display the alert // If alert event is on the bus, then display the alert
if (alertEvent !== undefined) { if (alertEvent !== undefined) {
this.displayGlobalAlert = true; alertEvent.displayAlert = true;
this.globalAlertMessage = alertEvent; this.globalAlertMessages.push(alertEvent);
} }
}, },
}; };

View file

@ -16,6 +16,32 @@ export const addableVapsPromoPartNumbers = [
promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER, promoPartNumberStrings.WIPER_DISCOUNT_PART_NUMBER,
]; ];
const promoErrorCodes = {
UNKNOWN: "Unknown",
PROMO_NOT_YET_IN_USE: "PromoNotYetInUse",
PROMO_USAGE_COUNT_EXCEEDED: "PromoUsageCountExceeded",
PROMO_EXPIRED: "PromoExpired",
PROMO_DOES_NOT_EXIST: "PromoDoesNotExist",
PROMO_STACKING_NOT_ALLOWED: "PromoStackingNotAllowed",
SIMILAR_PROMO_ALREADY_ON_ORDER: "SimilarPromoAlreadyOnOrder",
INVALID_PROMO_ON_ORDER: "InvalidPromoOnOrder",
PROMO_VALIDATION_ERROR: "PromoValidationError",
NO_SUITABLE_ITEM_FOR_PROMO: "NoSuitableItemForPromo",
};
const stackingPromoErrorCodes = [
promoErrorCodes.PROMO_STACKING_NOT_ALLOWED,
promoErrorCodes.SIMILAR_PROMO_ALREADY_ON_ORDER,
];
const excludeFromInactivePromoErrorCodes = [
promoErrorCodes.UNKNOWN,
promoErrorCodes.PROMO_NOT_YET_IN_USE,
promoErrorCodes.PROMO_USAGE_COUNT_EXCEEDED,
promoErrorCodes.PROMO_EXPIRED,
promoErrorCodes.PROMO_DOES_NOT_EXIST,
];
export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"]; export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"];
export function getPromosWithAddableVaps(promoList) { export function getPromosWithAddableVaps(promoList) {
@ -63,35 +89,36 @@ export function removeVapsPromosFromPromoArray(promoArray) {
return filteredPromoArray; return filteredPromoArray;
} }
export async function revalidatePromosAndValidateNewPromo( export async function revalidatePromosAndValidateQueryStringPromo(
newPromo, newPromo,
pricedLineItems, pricedLineItems,
pageNameToLog pageNameToLog
) { ) {
const hasActivePromos = store.getters.order.lineItems.promos; const hasActivePromos = store.getters.order.lineItems.promos;
const hasHasInactivePromos = store.getters.order.lineItems.inactivePromos; const hasInactivePromos = store.getters.payment.inactivePromos;
const hasNewPromo = !!newPromo; const hasNewPromo = !!newPromo;
const addableVaps = getAddableVapsFromAvailableLineItems(pricedLineItems); const addableVaps = getAddableVapsFromAvailableLineItems(pricedLineItems);
let validatePromoResponse = null; let validatePromoResponse = null;
let revalidatePromoResponse = null; let revalidatePromoResponse = null;
if (hasActivePromos || hasHasInactivePromos) { if (
(hasActivePromos && hasActivePromos.length) ||
(hasInactivePromos && hasInactivePromos.length)
) {
revalidatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging( revalidatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA, storeActions.REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA,
{}, {},
pageNameToLog, pageNameToLog,
false false
); );
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_PROMOS,
revalidatePromoResponse.promoLineItems,
false
);
const revalidationErrorPromoCodes = revalidatePromoResponse.errors.map((x) => x.promoCode); const revalidationErrorPromoCodes = revalidatePromoResponse.errors.map((x) => x.promoCode);
baseMixin.methods.dispatchStoreAction( baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_INACTIVE_PROMOS, storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
revalidationErrorPromoCodes, {
activePromos: revalidatePromoResponse.promoLineItems,
inactivePromos: revalidationErrorPromoCodes,
},
false false
); );
} }
@ -105,11 +132,78 @@ export async function revalidatePromosAndValidateNewPromo(
pageNameToLog, pageNameToLog,
false false
); );
if (!excludeFromInactivePromoErrorCodes.includes(validatePromoResponse.errorCode)) {
// Save any valid (applied or not) query string promoCode to inactivePromos
// in order to not lose the query string promoCode if back button is pressed
const inactivePromoCodes = store.getters.payment.inactivePromos ?? [];
if (!inactivePromoCodes.includes(newPromo)) {
inactivePromoCodes.push(newPromo);
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
{
inactivePromos: inactivePromoCodes,
},
false
);
}
}
} }
return { validatePromoResponse, revalidatePromoResponse }; return { validatePromoResponse, revalidatePromoResponse };
} }
export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
promoResponse,
oldActivePromos = [],
oldInactivePromos = []
) {
const alerts = [];
// Revalidate always has an "errors" array
if (promoResponse.errors) {
const oldPromoCodes = oldActivePromos.map((promo) =>
getPromoCodeWithoutBundleIdentifier(promo.promoCode)
);
const activePromoCodesFromResponse = getPromoCodesFromPromoObjectsWithoutDuplicates(
promoResponse.promoLineItems
);
const newlyActivatedPromoCodes = activePromoCodesFromResponse.filter(
(newPromoCode) => !oldPromoCodes.includes(newPromoCode)
);
newlyActivatedPromoCodes.forEach((newlyActivatedPromoCode) => {
alerts.push(createPromoSuccessAlert(newlyActivatedPromoCode));
});
const inactivePromoCodesFromResponse = getPromoCodesFromPromoObjectsWithoutDuplicates(
promoResponse.errors
);
const newlyInactivatedPromos = inactivePromoCodesFromResponse.filter(
(errorPromoCode) => !oldInactivePromos.includes(errorPromoCode)
);
newlyInactivatedPromos.forEach((newlyInactivatedPromo) => {
alerts.push(createPromoErrorAlert(newlyInactivatedPromo));
});
}
// validate response
else {
if (promoResponse.orderPromos) {
const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(
promoResponse.orderPromos[0].promoCode
);
alerts.push(createPromoSuccessAlert(promoCodeToDisplay));
} else {
alerts.push(
createPromoErrorAlert(
promoResponse.promoCode,
promoResponse.errorCode,
promoResponse.additionalInfo
)
);
}
}
return alerts;
}
export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) { export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
const matchingPromos = []; const matchingPromos = [];
promos.forEach((promo) => { promos.forEach((promo) => {
@ -152,6 +246,11 @@ export function getVapsThatNeedToBeAddedToSatisfyPromos(
} }
} }
// bundle promos look like: "bundlePromo/###"" for each promo, this returns "bundlePromo" only
export function getPromoCodeWithoutBundleIdentifier(promoCode) {
return promoCode.split("/")[0];
}
export function shouldStripPromoQueryString(fmgPageQueryValue) { export function shouldStripPromoQueryString(fmgPageQueryValue) {
return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue); return pagesToStripPromoQueryStringFrom.includes(fmgPageQueryValue);
} }
@ -163,3 +262,42 @@ function findLineItemsWithPartType(typeToFind, itemsToSearch) {
); );
return partTypeMatches; return partTypeMatches;
} }
// returns a list of promo codes (string) only without duplicates or bundle identifiers
// <promos> parameter must be an array of objects with a 'promoCode' field
function getPromoCodesFromPromoObjectsWithoutDuplicates(promos) {
const promoCodes = [];
promos.forEach((promo) => {
const promoCodeToDisplay = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
if (!promoCodes.includes(promoCodeToDisplay)) {
promoCodes.push(promoCodeToDisplay);
}
});
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;
}

View file

@ -86,8 +86,8 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { paymentMethods } from "@/constants/payment-method-constants"; import { paymentMethods } from "@/constants/payment-method-constants";
import { experimentSettings } from "@/constants/experiments"; import { experimentSettings } from "@/constants/experiments";
import { import {
revalidatePromosAndValidateNewPromo, revalidatePromosAndValidateQueryStringPromo,
getPromosWithAddableVaps, buildToastMessagesFromRevalidateOrValidatePromoResponse,
getVapsThatNeedToBeAddedToSatisfyPromos, getVapsThatNeedToBeAddedToSatisfyPromos,
} from "@/helpers/promotions-helper"; } from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
@ -159,10 +159,13 @@ export default {
); );
// Promo logic // 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 } = const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateNewPromo( await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString, promoCodeFromQueryString,
pricedLineItemsToTax, pricedLineItemsToTax,
"payment-method" "payment-method"
@ -210,6 +213,26 @@ export default {
vm.availableVaps = taxedVaps; vm.availableVaps = taxedVaps;
vm.lineItems = lineItems; vm.lineItems = lineItems;
vm.updateFooterButtonText(vm.customCtaCopy); vm.updateFooterButtonText(vm.customCtaCopy);
if (revalidatePromoResponse) {
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
revalidatePromoResponse,
oldActivePromos,
oldInactivePromos
);
revalidateAlerts.forEach((alert) => {
vm.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
});
}
if (validatePromoResponse) {
const validateAlerts =
buildToastMessagesFromRevalidateOrValidatePromoResponse(validatePromoResponse);
vm.$refs.funnelHeader.pushGlobalAlert(
validateAlerts[0],
validateAlerts[0].shouldAutoFade
);
}
}); });
}, },
data() { data() {
@ -303,6 +326,14 @@ export default {
"payment-method", "payment-method",
false false
); );
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
revalidatePromoResponse,
this.lineItems.promos,
this.inactivePromos
);
revalidateAlerts.forEach((alert) => {
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
});
this.lineItems.promos = revalidatePromoResponse.promoLineItems; this.lineItems.promos = revalidatePromoResponse.promoLineItems;
this.inactivePromos = revalidatePromoResponse.errors.map((x) => x.promoCode); this.inactivePromos = revalidatePromoResponse.errors.map((x) => x.promoCode);
@ -330,10 +361,12 @@ export default {
false false
); );
await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false); await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false);
await this.dispatchStoreAction(storeActions.SAVE_PROMOS, this.lineItems.promos, false); this.dispatchStoreAction(
await this.dispatchStoreAction( storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
storeActions.SAVE_INACTIVE_PROMOS, {
this.inactivePromos, activePromos: this.lineItems.promos,
inactivePromos: this.inactivePromos,
},
false false
); );
@ -375,7 +408,7 @@ export default {
); );
}, },
showAlert(alert) { showAlert(alert) {
this.$refs.funnelHeader.triggerGlobalAlert(alert, true); this.$refs.funnelHeader.pushGlobalAlert(alert, true);
}, },
}, },
computed: { computed: {

View file

@ -190,7 +190,7 @@ import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { mapTaxedLineItemsToStoreFormat } from "../../store"; import { mapTaxedLineItemsToStoreFormat } from "../../store";
import { import {
revalidatePromosAndValidateNewPromo, revalidatePromosAndValidateQueryStringPromo,
getAddableVapsFromAvailableLineItems, getAddableVapsFromAvailableLineItems,
getVapsThatNeedToBeAddedToSatisfyPromos, getVapsThatNeedToBeAddedToSatisfyPromos,
} from "@/helpers/promotions-helper"; } from "@/helpers/promotions-helper";
@ -324,7 +324,7 @@ export default {
const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO); const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
const { validatePromoResponse, revalidatePromoResponse } = const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateNewPromo( await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString, promoCodeFromQueryString,
pricedLineItemsToTax, pricedLineItemsToTax,
"payment" "payment"

View file

@ -22,7 +22,7 @@ jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
})); }));
jest.mock("@/helpers/promotions-helper", () => ({ jest.mock("@/helpers/promotions-helper", () => ({
revalidatePromosAndValidateNewPromo: jest.fn(() => { revalidatePromosAndValidateQueryStringPromo: jest.fn(() => {
return { validatePromoResponse: null, revalidatePromoResponse: null }; return { validatePromoResponse: null, revalidatePromoResponse: null };
}), }),
})); }));
@ -258,10 +258,14 @@ describe("quote.vue", () => {
test("should have non-null values for necessary data members after 'beforeRouteEnter'", async () => { test("should have non-null values for necessary data members after 'beforeRouteEnter'", async () => {
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
payment: {},
}, },
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -286,6 +290,9 @@ describe("quote.vue", () => {
test("should default to insurance if query param 'isInsurance' is true", async () => { test("should default to insurance if query param 'isInsurance' is true", async () => {
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -315,6 +322,9 @@ describe("quote.vue", () => {
test("should default to cash if query param 'isInsurance' is false", async () => { test("should default to cash if query param 'isInsurance' is false", async () => {
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -345,6 +355,9 @@ describe("quote.vue", () => {
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -375,6 +388,9 @@ describe("quote.vue", () => {
test("should default to cash if cash selection is saved to store", async () => { test("should default to cash if cash selection is saved to store", async () => {
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -406,6 +422,9 @@ describe("quote.vue", () => {
// Also needs no query parameter or previous selection in store to be present // Also needs no query parameter or previous selection in store to be present
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -439,6 +458,9 @@ describe("quote.vue", () => {
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -467,10 +489,13 @@ describe("quote.vue", () => {
//Assert //Assert
expect(wrapper.vm.isInsuranceSelected).toBe(true); expect(wrapper.vm.isInsuranceSelected).toBe(true);
}); });
test("Should default to insurence if user Service zip is from certain States", async () => { test("Should default to insurance if user Service zip is from certain States", async () => {
//Arrange //Arrange
store.getters = { store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],

View file

@ -80,7 +80,10 @@ import { Form, defineRule } from "vee-validate";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { applicationConfig } from "@/constants/application-config"; import { applicationConfig } from "@/constants/application-config";
import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states"; import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states";
import { revalidatePromosAndValidateNewPromo } from "@/helpers/promotions-helper"; import {
revalidatePromosAndValidateQueryStringPromo,
buildToastMessagesFromRevalidateOrValidatePromoResponse,
} from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
@ -151,10 +154,14 @@ export default {
); );
// Promo logic // 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 promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
const { validatePromoResponse, revalidatePromoResponse } = const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateNewPromo( await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString, promoCodeFromQueryString,
pricingResults, pricingResults,
"quote" "quote"
@ -169,6 +176,25 @@ export default {
vm.availableLineItems = pricingResults; vm.availableLineItems = pricingResults;
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems); vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
vm.newValidatedPromos = validatePromoResponse?.orderPromos; vm.newValidatedPromos = validatePromoResponse?.orderPromos;
if (revalidatePromoResponse) {
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
revalidatePromoResponse,
oldActivePromos,
oldInactivePromos
);
revalidateAlerts.forEach((alert) => {
vm.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
});
}
if (validatePromoResponse) {
const validateAlerts =
buildToastMessagesFromRevalidateOrValidatePromoResponse(validatePromoResponse);
vm.$refs.funnelHeader.pushGlobalAlert(
validateAlerts[0],
validateAlerts[0].shouldAutoFade
);
}
}); });
}, },
data() { data() {
@ -265,7 +291,11 @@ export default {
} }
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false); this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
this.dispatchStoreAction(this.storeActions.SAVE_PROMOS, this.allActivePromos, false); this.dispatchStoreAction(
this.storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
{ activePromos: this.allActivePromos },
false
);
const payment = this.$store.getters.payment; const payment = this.$store.getters.payment;
if (payment.isInsurance) { if (payment.isInsurance) {

View file

@ -23,7 +23,7 @@ import {
getDisplayTextForDurationLength, getDisplayTextForDurationLength,
} from "@/layouts/schedule/helpers/schedule-helper"; } from "@/layouts/schedule/helpers/schedule-helper";
import { paymentMethods } from "@/constants/payment-method-constants"; import { paymentMethods } from "@/constants/payment-method-constants";
import { getPromoCodeWithoutBundleIdentifier } from "@/helpers/promotions-helper";
import { getDateDifferenceInDays } from "@/helpers/date-helper"; import { getDateDifferenceInDays } from "@/helpers/date-helper";
// Export State // Export State
const getDefaultState = () => { const getDefaultState = () => {
@ -2120,12 +2120,53 @@ export const actions = {
addGuidToLineItemsIfNotAlreadyThere(vaps); addGuidToLineItemsIfNotAlreadyThere(vaps);
context.commit(storeMutations.UPDATE_VAPS, vaps); context.commit(storeMutations.UPDATE_VAPS, vaps);
}, },
// COMBINE THESE
savePromos(context, promos) { savePromos(context, promos) {
context.commit(storeMutations.UPDATE_PROMOS, promos); context.commit(storeMutations.UPDATE_PROMOS, promos);
}, },
saveInactivePromos(context, inactivePromos) { saveInactivePromos(context, inactivePromos) {
context.commit(storeMutations.UPDATE_INACTIVE_PROMOS, inactivePromos); context.commit(storeMutations.UPDATE_INACTIVE_PROMOS, inactivePromos);
}, },
// Manage promo saving to ensure a promoCode never ends up in both active and inactive
saveActiveAndOrInactivePromos(context, { activePromos = null, inactivePromos = null }) {
let activePromosToSave;
let inactivePromosToSave;
if (!activePromos && !inactivePromos) {
// Allow double null to save empty arrays to store
activePromosToSave = [];
inactivePromosToSave = [];
} else if (activePromos && inactivePromos) {
// Prioritize active promos when both inactive and active are supplied
activePromosToSave = activePromos;
const activePromoCodes = activePromos.map((promoObject) =>
getPromoCodeWithoutBundleIdentifier(promoObject.promoCode)
);
inactivePromosToSave = inactivePromos.filter((inactivePromo) => {
return !activePromoCodes.includes(inactivePromo);
});
} else if (!activePromos) {
// Only inactivePromos supplied
inactivePromosToSave = inactivePromos;
activePromosToSave = (context.getters.lineItems.promos ?? []).filter((activePromo) => {
return !inactivePromosToSave.includes(
getPromoCodeWithoutBundleIdentifier(activePromo.promoCode)
);
});
} else if (!inactivePromos) {
// Only activePromos supplied
activePromosToSave = activePromos;
const activePromoCodes = activePromos.map((promoObject) =>
getPromoCodeWithoutBundleIdentifier(promoObject.promoCode)
);
inactivePromosToSave = (context.getters.payment.inactivePromos ?? []).filter(
(inactivePromo) => {
return !activePromoCodes.includes(inactivePromo);
}
);
}
context.commit(storeMutations.UPDATE_PROMOS, activePromosToSave);
context.commit(storeMutations.UPDATE_INACTIVE_PROMOS, inactivePromosToSave);
},
// Price order actions // Price order actions
async priceOrderItemsAndSaveServerData( async priceOrderItemsAndSaveServerData(