diff --git a/src/constants/query-strings.js b/src/constants/query-strings.js
index 40f3a2242..5c056c934 100644
--- a/src/constants/query-strings.js
+++ b/src/constants/query-strings.js
@@ -19,6 +19,7 @@ const queryStrings = {
AUTH_CODE: "auth_code",
TRANSACTION_ID: "transaction_id",
TRANS_REFERENCE_NUMBER: "auth_trans_ref_no",
+ DISPLAY_PIA_ALERT: "displayPiaAlert",
};
export { queryStrings };
diff --git a/src/digital-components/textarea-question/textarea-question.vue b/src/digital-components/textarea-question/textarea-question.vue
index aa81c8286..eda3a6f9c 100644
--- a/src/digital-components/textarea-question/textarea-question.vue
+++ b/src/digital-components/textarea-question/textarea-question.vue
@@ -35,7 +35,7 @@ export default {
isRequired: Boolean,
maxLength: {
type: Number,
- default: 250,
+ default: 150,
},
modelValue: String,
},
@@ -103,7 +103,7 @@ export default {
}
}
p {
- color: $gray-500;
+ color: $gray-600;
&.urgent-countdown {
color: $red;
}
diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue
index c1a9608a8..1180b3152 100644
--- a/src/fmg-components/cart/cart.vue
+++ b/src/fmg-components/cart/cart.vue
@@ -5,7 +5,10 @@
class="row vin-toggle flex align-items-center pt-4"
:class="[isExpanded ? 'expanded' : '']"
@click="toggleIsExpanded()">
-
+
{{ amountDueText }}
{{ getFormattedAmount("", amountDue) }}
@@ -13,7 +16,8 @@
-
+
+ {{ screenReaderTotalAmountDueText }}
{{ getFormattedAmount("", packagePrice) }}
@@ -27,7 +31,11 @@
linkType="text"
:text="removeLinkText"
href="#!"
- @click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
+ @click-event="removeItem(cartItem.cartItemType, cartItem.category)">
+
+ {{ cartItem.name }}
+
+
@@ -56,25 +64,40 @@
{{ recycleFeeCartItem.name }}
- {{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}
+ {{ screenReaderRecycleFeeText }}{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}
{{ subtotalText }}{{ getFormattedAmount("", subTotal) }}
+ >{{ screenReaderSubTotalText }}{{ getFormattedAmount("", subTotal) }}
{{ salesTaxText }}{{ getFormattedAmount("", salesTax) }}
+ >{{ screenReaderSalesTaxText }}{{ getFormattedAmount("", salesTax) }}
{{ amountPaidText }}{{ getFormattedAmount("", amountPaid) }}
+ >{{ screenReaderAmountPaidText }}{{ getFormattedAmount("", amountPaid) }}
{{ amountDueText }}{{ getFormattedAmount("", amountDue) }}
+ >{{ screenReaderTotalAmountDueText }}{{ getFormattedAmount("", amountDue) }}
@@ -187,6 +210,24 @@ export default {
},
},
computed: {
+ screenReaderTotalAmountDueText() {
+ return this.getCmsContent("ScreenReaderTotalAmountDueWidget", "Text");
+ },
+ screenReaderRemoveVapsText() {
+ return this.getCmsContent("ScreenReaderRemoveVapsWidget", "Text");
+ },
+ screenReaderRecycleFeeText() {
+ return this.getCmsContent("ScreenReaderRecycleFeeWidget", "Text");
+ },
+ screenReaderSubTotalText() {
+ return this.getCmsContent("ScreenReaderSubTotalWidget", "Text");
+ },
+ screenReaderAmountPaidText() {
+ return this.getCmsContent("ScreenReaderAmountPaidWidget", "Text");
+ },
+ screenReaderSalesTaxText() {
+ return this.getCmsContent("ScreenReaderSalesTaxWidget", "Text");
+ },
availableLineItems() {
if (!this.lineItems || this.lineItems.length < 1) {
return [];
diff --git a/src/helpers/promotions-helper.js b/src/helpers/promotions-helper.js
index 567304de6..3968248d6 100644
--- a/src/helpers/promotions-helper.js
+++ b/src/helpers/promotions-helper.js
@@ -132,8 +132,19 @@ export async function revalidatePromosAndValidateQueryStringPromo(
pageNameToLog,
false
);
- if (!excludeFromInactivePromoErrorCodes.includes(validatePromoResponse.errorCode)) {
- // Save any valid (applied or not) query string promoCode to inactivePromos
+ if (validatePromoResponse.errorCode == null) {
+ // Save promo to store
+ const activePromos = store.getters.order.lineItems.promos ?? [];
+ activePromos.push(...validatePromoResponse.orderPromos);
+ baseMixin.methods.dispatchStoreAction(
+ storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
+ {
+ activePromos: activePromos,
+ },
+ false
+ );
+ } else if (!excludeFromInactivePromoErrorCodes.includes(validatePromoResponse.errorCode)) {
+ // Save any valid query string promoCode (not applied) 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)) {
@@ -202,16 +213,33 @@ export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
}
export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
+ const matchingPromoCodes = [];
const matchingPromos = [];
+ const consolidatedPromosWithIds = {};
+ // Combine bundle promos to make sure each part of the bundle is satisfied
promos.forEach((promo) => {
+ const cleanPromoCode = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
+ if (consolidatedPromosWithIds[cleanPromoCode]) {
+ consolidatedPromosWithIds[cleanPromoCode].push(...promo.discountedLineItemIds);
+ } else {
+ consolidatedPromosWithIds[cleanPromoCode] = promo.discountedLineItemIds.slice(0);
+ }
+ });
+ Object.keys(consolidatedPromosWithIds).forEach((promoCode) => {
let allIdsMatch = true;
- promo.discountedLineItemIds.forEach((id) => {
+ consolidatedPromosWithIds[promoCode].forEach((id) => {
if (lineItemsOnOrder.filter((x) => x.id === id).length === 0) {
allIdsMatch = false;
return;
}
});
if (allIdsMatch) {
+ matchingPromoCodes.push(promoCode);
+ }
+ });
+ promos.forEach((promo) => {
+ const cleanPromoCode = getPromoCodeWithoutBundleIdentifier(promo.promoCode);
+ if (matchingPromoCodes.includes(cleanPromoCode)) {
matchingPromos.push(promo);
}
});
diff --git a/src/layouts/customer-details/customer-details.vue b/src/layouts/customer-details/customer-details.vue
index 668bd0495..08d547f96 100644
--- a/src/layouts/customer-details/customer-details.vue
+++ b/src/layouts/customer-details/customer-details.vue
@@ -48,7 +48,7 @@
class="mb-4"
v-model="techNotes"
cmsWidgetName="TextAreaContentWidget"
- maxLength="250" />
+ maxLength="150" />
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 cbbb51ceb..b8aa2dd1a 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
@@ -62,6 +62,7 @@ import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
import { processIfStatements } from "@/helpers/cms-content-helper";
+import { deepClone } from "@/helpers/object-helper";
// DEFINE VALIDATION RULES
defineRule("checkbox-required", required(errorMessages.OPTION_REQUIRED));
@@ -198,6 +199,12 @@ export default {
return null;
}
},
+ getAnswerFromCms(name, answers) {
+ const answerIndex = answers.findIndex((answer) => {
+ return answer.Name === name;
+ });
+ return answers[answerIndex];
+ },
},
computed: {
currentCartItems() {
@@ -280,21 +287,17 @@ export default {
}
return result;
},
+ wipersModalDataFromCms() {
+ return this.getAnswerFromCms("WipersModal", this.answersCmsData);
+ },
+ rainDefenseModalDataFromCms() {
+ return this.getAnswerFromCms("RainDefenseModal", this.answersCmsData);
+ },
buttonsToDisplay() {
const buttons = [];
if (!this.answersCmsData) return buttons;
- const getAnswer = (name, answers) => {
- const answerIndex = answers.findIndex((answer) => {
- return answer.Name === name;
- });
- return answers[answerIndex];
- };
-
- const modalData = getAnswer("WipersModal", this.answersCmsData);
-
- // remove custom var for now
- modalData.Text = modalData.Text?.replaceAll("{custom:WIPERTYPE}", "");
+ const wipersModalData = deepClone(this.wipersModalDataFromCms);
if (this.wipersOffered === wipersOfferedStrings.BOTH) {
// NO REAR WIPERS or FRONT WIPERS IN CART; SHOW COMBO BUTTON
@@ -303,27 +306,50 @@ export default {
this.wipersModal?.totalRearPrice,
];
prices.sort((a, b) => a - b);
- modalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1];
- buttons.push(modalData);
+ wipersModalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1];
+ wipersModalData.Text = wipersModalData.Text?.replaceAll("{custom:WIPERTYPE}", "");
+ buttons.push(wipersModalData);
}
if (this.wipersOffered === wipersOfferedStrings.FRONT) {
// NO FRONT WIPERS IN CART; SHOW ONLY FRONT WIPERS BUTTON
- modalData.SubText = "+$" + this.wipersModal?.totalFrontPrice;
- buttons.push(modalData);
+ wipersModalData.SubText = "+$" + this.wipersModal?.totalFrontPrice;
+ if (this.rearWipersInCart) {
+ wipersModalData.Text = wipersModalData.Text?.replaceAll(
+ "{custom:WIPERTYPE}",
+ wipersOfferedStrings.FRONT.toLowerCase() + " "
+ );
+ } else {
+ wipersModalData.Text = wipersModalData.Text?.replaceAll(
+ "{custom:WIPERTYPE}",
+ ""
+ );
+ }
+ buttons.push(wipersModalData);
}
if (this.wipersOffered === wipersOfferedStrings.REAR) {
// NO REAR WIPERS IN CART; SHOW ONLY REAR WIPERS BUTTON
- modalData.SubText = "+$" + this.wipersModal?.totalRearPrice;
- buttons.push(modalData);
+ wipersModalData.SubText = "+$" + this.wipersModal?.totalRearPrice;
+ if (this.frontWipersInCart) {
+ wipersModalData.Text = wipersModalData.Text?.replaceAll(
+ "{custom:WIPERTYPE}",
+ wipersOfferedStrings.REAR.toLowerCase() + " "
+ );
+ } else {
+ wipersModalData.Text = wipersModalData.Text?.replaceAll(
+ "{custom:WIPERTYPE}",
+ ""
+ );
+ }
+ buttons.push(wipersModalData);
}
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 rainDefenseModalData = deepClone(this.rainDefenseModalDataFromCms);
+ rainDefenseModalData.SubText = "+$" + this.rainDefenseModal?.listPrice;
+ buttons.push(rainDefenseModalData);
}
return buttons;
@@ -417,6 +443,7 @@ export default {
p.price {
text-align: center;
color: $green;
+ font-weight: 500;
}
p:last-child {
margin-bottom: 0;
@@ -438,6 +465,7 @@ export default {
.additional-button-data {
font-size: 0.875rem;
+ font-weight: 500;
line-height: 1.75;
padding-left: 0.5rem;
color: $green;
diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue
index d7e8c465b..b81ed9d5a 100644
--- a/src/layouts/payment-method/payment-method.vue
+++ b/src/layouts/payment-method/payment-method.vue
@@ -103,6 +103,7 @@ import {
getNewlyInactivatedPromos,
} from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings";
+import { routerParams } from "@/router/router-constants/router-params";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper";
@@ -440,7 +441,7 @@ export default {
} catch (error) {
console.log("error: response from pia submit work order:" + error.message);
this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
- this.$route.params[this.routerParams.DISPLAY_PIA_ALERT] = true;
+ this.$route.params[routerParams.DISPLAY_PIA_ALERT] = true;
return;
}
}
@@ -488,7 +489,10 @@ export default {
return this.paymentMethodInternalModel;
},
shouldDisplayPiaAlert() {
- return this.$route.params[this.routerParams.DISPLAY_PIA_ALERT];
+ return (
+ this.$route.query[queryStrings.DISPLAY_PIA_ALERT] ||
+ this.$route.params[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.
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 94de72765..16d170e82 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
@@ -11,6 +11,7 @@ jest.mock("@/digital-components/modal/modal", () => ({
methods: {
closeModal: jest.fn(),
resetButtonStyle: jest.fn(),
+ resetForm: jest.fn(),
},
}));
@@ -49,7 +50,9 @@ describe("promo-modal-question.vue", () => {
// Assert
expect(wrapper.vm.displayInvalidPromoAlert).toBe(false);
- expect(wrapper.vm.displayInvalidOnOrderPromoAlert).toBe(false);
+ expect(wrapper.vm.displayStackingPromoAlert).toBe(false);
+ expect(wrapper.vm.displayInShopPromoAlert).toBe(false);
+ expect(wrapper.vm.displaySimilarPromoAlert).toBe(false);
});
it("Should emit update:modelValue on Modal closed", async () => {
// Arrange
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 cde033066..7c3a95151 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
@@ -22,19 +22,34 @@
+ v-bind:isDismissible="false" />
+ v-bind:isDismissible="false" />
+
+
Promo code "{{ promoCode }}" applied
1) {
+ if (additionalInfo?.length > 2) conflictingCodes += ",";
+ for (let i = 1; i < additionalInfo?.length - 1; i++) {
+ conflictingCodes += ` "${additionalInfo[i]},`;
+ }
+ conflictingCodes += `" and "${additionalInfo?.slice(-1)[0]}`;
+ }
+ return conflictingCodes;
},
onInputIdAssigned(inputId) {
this.promoTextInputId = inputId;
@@ -146,6 +169,7 @@ export default {
this.resetAlerts();
this.$emit("update:modelValue", this.lineItems);
this.promoCode = "";
+ this.modal.resetForm();
},
focusOnPromoInput() {
const input = document.getElementById(this.promoTextInputId);
@@ -171,6 +195,7 @@ export default {
isValid: !("errorCode" in promoValidationResponse),
promoCode: promoValidationResponse?.orderPromos,
errorCode: promoValidationResponse?.errorCode,
+ additionalInfo: promoValidationResponse?.additionalInfo,
};
},
removeItem(promo) {
@@ -181,6 +206,43 @@ export default {
getPromoCodeWithoutBundleIdentifier(lineItemsToKeep.promoCode) != promo
);
},
+ getErrorMessage(error, additionalInfo) {
+ switch (error) {
+ case promoErrorCodes.PROMO_STACKING_NOT_ALLOWED:
+ case promoErrorCodes.SIMILAR_PROMO_ALREADY_ON_ORDER:
+ return additionalInfo.length === 1 &&
+ additionalInfo[0] === this.promoCode.toUpperCase()
+ ? (this.displaySimilarPromoAlert = true)
+ : ((this.errorMessage = this.StackPromoText?.replace(
+ /\{custom:(NEW|OLD)PROMOCODE\}/g,
+ (match, group) =>
+ group === "NEW"
+ ? this.promoCode.toUpperCase()
+ : this.getConflictingPromoCode(additionalInfo)
+ )),
+ (this.displayStackingPromoAlert = true));
+ case promoErrorCodes.INVALID_PROMO_ON_ORDER:
+ if (additionalInfo.some((x) => x.toUpperCase() === "APPOINTMENT_TYPE")) {
+ this.errorMessage = this.InShopPromoText?.replaceAll(
+ "{custom:INSHOPPROMOCODE}",
+ this.promoCode.toUpperCase()
+ );
+ return (this.displayInShopPromoAlert = true);
+ } else {
+ this.errorMessage = this.PromoText?.replaceAll(
+ "{custom:PROMOCODE}",
+ this.promoCode.toUpperCase()
+ );
+ return (this.displayInvalidPromoAlert = true);
+ }
+ default:
+ this.errorMessage = this.PromoText?.replaceAll(
+ "{custom:PROMOCODE}",
+ this.promoCode.toUpperCase()
+ );
+ return (this.displayInvalidPromoAlert = true);
+ }
+ },
async addPromoCode() {
if (this.promoCode) {
this.resetAlerts();
@@ -227,11 +289,7 @@ export default {
this.lineItems.vaps?.push(...getVaps);
this.closeModal();
} else {
- if (promoCodeData.errorCode == promoErrorCodes.PROMO_STACKING_NOT_ALLOWED) {
- this.displayInvalidOnOrderPromoAlert = true;
- } else {
- this.displayInvalidPromoAlert = true;
- }
+ this.getErrorMessage(promoCodeData.errorCode, promoCodeData.additionalInfo);
this.focusOnPromoInput();
this.resetModalButtonStyle();
}
@@ -241,7 +299,9 @@ export default {
watch: {
promoCode() {
this.displayInvalidPromoAlert = false;
- this.displayInvalidOnOrderPromoAlert = false;
+ this.displayStackingPromoAlert = false;
+ this.displaySimilarPromoAlert = false;
+ this.displayInShopPromoAlert = false;
},
modelValue: {
handler(newValue) {
diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue
index 8088cbb06..d12e45705 100644
--- a/src/layouts/payment-pia-return/payment-pia-return.vue
+++ b/src/layouts/payment-pia-return/payment-pia-return.vue
@@ -24,12 +24,9 @@ export default {
if (piaError) {
console.log("Error during payment: " + piaError);
- this.$router.navigateWithoutSaving(
- this.navigationScenarios.PIA_ERROR,
- this.$route,
- {},
- { [routerParams.DISPLAY_PIA_ALERT]: true }
- );
+ this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route, {
+ [queryStrings.DISPLAY_PIA_ALERT]: true,
+ });
} else {
switch (store.getters.order.payment.piaType) {
case paymentMethods.CREDIT_CARD:
diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue
index 95f427b8f..d86859668 100644
--- a/src/layouts/payment/payment.vue
+++ b/src/layouts/payment/payment.vue
@@ -350,8 +350,8 @@ export default {
const taxedVaps = mapTaxedLineItemsToStoreFormat(taxedLineItems, availableVaps);
// Add items that were not in the store yet but added via query string promo validation
- lineItems.promos = lineItems.promos ?? [];
- lineItems.promos.push(...newValidatedPromos);
+ //newValidatedPromos contains both validated and revalidated promos.
+ lineItems.promos = Array.from(newValidatedPromos);
const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos(
newValidatedPromos,
taxedVaps,
diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue
index 1357e5acc..e48e8fff8 100644
--- a/src/layouts/quote/quote.vue
+++ b/src/layouts/quote/quote.vue
@@ -178,7 +178,6 @@ export default {
vm.supportingItems = resultMap.supportingItems;
vm.availableLineItems = pricingResults;
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
- vm.newValidatedPromos = validatePromoResponse?.orderPromos;
if (revalidatePromoResponse) {
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
@@ -207,16 +206,11 @@ export default {
availableLineItems: null,
supportingItems: null,
pricedGlassParts: null,
- newValidatedPromos: null,
};
},
computed: {
allActivePromos() {
- const allActivePromos = [];
- if (this.newValidatedPromos) allActivePromos.push(...this.newValidatedPromos);
- if (this.$store.getters.order.lineItems.promos)
- allActivePromos.push(...this.$store.getters.order.lineItems.promos);
- return allActivePromos;
+ return this.$store.getters.order.lineItems.promos ?? [];
},
},
methods: {
@@ -294,11 +288,6 @@ export default {
}
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
- this.dispatchStoreAction(
- this.storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
- { activePromos: this.allActivePromos },
- false
- );
const payment = this.$store.getters.payment;
if (payment.isInsurance) {
diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue
index 3455de18e..ea33f7642 100644
--- a/src/layouts/vehicle-parts/vehicle-parts.vue
+++ b/src/layouts/vehicle-parts/vehicle-parts.vue
@@ -249,5 +249,8 @@ export default {
height: auto;
}
}
+ .two-list-card-width {
+ width: 100%;
+ }
}
diff --git a/src/store/index.js b/src/store/index.js
index 332b60cf6..efaf5b428 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -2130,6 +2130,8 @@ export const actions = {
},
// Manage promo saving to ensure a promoCode never ends up in both active and inactive
saveActiveAndOrInactivePromos(context, { activePromos = null, inactivePromos = null }) {
+ activePromos = activePromos?.slice(0);
+ inactivePromos = inactivePromos?.slice(0);
let activePromosToSave;
let inactivePromosToSave;
if (!activePromos && !inactivePromos) {