Merge branch 'develop' into feature/CSR-1388-tech-review-changes

This commit is contained in:
Leah Schumann 2023-11-16 09:12:10 -05:00
commit a15cdcea59
13 changed files with 141 additions and 86 deletions

View file

@ -53,6 +53,9 @@
:text="recycleFeeCartItem.name"
href="#!"
@click-event="openModal(recyclingModalCmsWidgetName)" />
<span v-else-if="cartItem == recycleFeeCartItem">
{{ recycleFeeCartItem.name }}
</span>
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
</div>

View file

@ -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

View file

@ -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;
}

View file

@ -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() {

View file

@ -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() {

View file

@ -33,7 +33,6 @@
class="pb-2"
v-model="lineItems"
:availableVaps="availableVaps"
linkWidgetName="PromoLinkWidget"
modalWidgetName="PromoModalWidget" />
<hr class="mb-5" />
@ -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();
}
},

View file

@ -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,

View file

@ -10,12 +10,14 @@
:ref="modalName"
:headerText="modalHeaderText"
:onModalClosedCallback="onModalClosed"
:onModalOpenedCallback="focusOnPromoInput"
:footerButtonText="modalFooterText"
@footer-button-event="addPromoCode">
<promoQuestion
ref="promoQuestion"
customInputId="promoCode"
v-model="promoCode"
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }"
cmsWidgetName="PromoQuestionWidget" />
<alert
ref="alertInvalidPromo"
@ -74,13 +76,12 @@ export default {
},
props: {
modelValue: Object,
linkWidgetName: String,
modalWidgetName: String,
availableVaps: Object,
},
computed: {
promoLinkText() {
return this.getCmsContent(this.linkWidgetName, "BodyText");
return this.getCmsContent(this.modalWidgetName, "HeaderText");
},
modalHeaderText() {
return this.getCmsContent("PromoQuestionWidget", "QuestionText");
@ -127,6 +128,9 @@ export default {
getPromoCode() {
return this.lineItems.promos?.[0].promoCode;
},
onInputIdAssigned(inputId) {
this.promoTextInputId = inputId;
},
openModal() {
this.modal.openModal();
},
@ -217,13 +221,11 @@ export default {
} else {
if (promoCodeData.errorCode == promoErrorCodes.PROMO_STACKING_NOT_ALLOWED) {
this.displayInvalidOnOrderPromoAlert = true;
this.focusOnPromoInput();
this.resetModalButtonStyle();
} else {
this.displayInvalidPromoAlert = true;
this.focusOnPromoInput();
this.resetModalButtonStyle();
}
this.focusOnPromoInput();
this.resetModalButtonStyle();
}
}
},

View file

@ -8,6 +8,7 @@
cornerStyle="rounded"
:displayQuestionText="false"
isRequired
@input="validateAlphanumeric"
validationRules="promo-required" />
</template>
@ -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,
},

View file

@ -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,

View file

@ -9,10 +9,6 @@
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="siteSubHeader" />
<div
cmsWidgetName="FindYourVehicleCopyWidget"
v-html="this.findYourVehicleCopy"
class="mt-2 mb-4 small"></div>
<vehicleQuestion
ref="vehicleYearQuestion"
v-model="selectedYear"
@ -298,9 +294,6 @@ export default {
else if (this.imageUrl == null && this.carId !== null) return false;
else return true;
},
findYourVehicleCopy() {
return this.getCmsContent("FindYourVehicleCopyWidget", "QuestionText");
},
},
methods: {
getVehicle(year, make, model, style) {

View file

@ -1317,7 +1317,6 @@ export const actions = {
});
syncLineItemIds(response.data, context.getters.order.lineItems.vaps);
return response;
},
@ -2349,6 +2348,7 @@ export const actions = {
activePromosToUse = activePromosToUse ?? order.lineItems.promos;
lineItemsToUse = lineItemsToUse ? deepClone(lineItemsToUse) : deepClone(order.lineItems);
addGuidToLineItemsIfNotAlreadyThere(lineItemsToUse.vaps);
lineItemsToUse.promos = activePromosToUse;
inactivePromosToUse = inactivePromosToUse ?? order.payment.inactivePromos;

View file

@ -3,6 +3,7 @@
class="loader"
role="alert"
aria-label="Loading new page"
@click="captureClick"
v-bind:class="[
this.loaderColor,
this.loaderPosition,
@ -30,6 +31,13 @@ export default {
default: false,
},
},
methods: {
captureClick(event) {
if (!this.allowPageInteraction) {
event.stopPropagation();
}
},
},
};
</script>
@ -95,7 +103,7 @@ export default {
}
&.allow-ui-interaction::before {
//no-block to enable clicking on certain buttons with loaders while the loader is actives
z-index: -1;
pointer-events: none;
}
}
</style>