Watch lineItems to automatically run revalidate when the cart changes Add in auto add of addable vaps in beforeRouteEnter Create adding of addableVaps to order in promo helper
476 lines
17 KiB
Vue
476 lines
17 KiB
Vue
<template>
|
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
|
<loadingModal notFullScreen ref="loadingModal" />
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col">
|
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
|
</div>
|
|
</div>
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-6 col-xl-4">
|
|
<vehicleBanner
|
|
cmsWidgetName="VehicleBannerWidget"
|
|
:displayGenericVehicleImage="false" />
|
|
|
|
<funnelSubHeader class="mb-5 mt-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
|
|
|
<p v-if="isPiaDisabled" class="fw-normal no-pia-disclaimer">
|
|
<span>{{ noPiaDisclaimer }}</span>
|
|
</p>
|
|
|
|
<hr />
|
|
|
|
<cart
|
|
:damage="damageInfo"
|
|
:availableLineItems="availableLineItems"
|
|
:allowItemRemoval="true"
|
|
v-model="lineItems"
|
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
|
@cart-remove="cartRemove"
|
|
recyclingModalCmsWidgetName="RecycleModal" />
|
|
|
|
<hr />
|
|
|
|
<div>
|
|
<alert
|
|
ref="piaErrorAlert"
|
|
class="my-4"
|
|
v-if="shouldDisplayPiaAlert"
|
|
cmsWidgetName="PIAErrorAlertWidget"
|
|
alertClass="alert-danger"
|
|
v-bind:isDismissible="false" />
|
|
</div>
|
|
|
|
<add-vaps-modal-buttons
|
|
v-model="lineItems"
|
|
:availableLineItems="availableLineItems"
|
|
vapsTilesCmsName="VapsProductTiles"
|
|
@added-to-cart="showAlert" />
|
|
|
|
<paymentMethodQuestion
|
|
v-if="!isPiaDisabled"
|
|
v-model="paymentMethodInternalModel"
|
|
validationRules="option-required" />
|
|
|
|
<navbar
|
|
cmsWidgetName="FunnelFooterWidget"
|
|
ref="navbar"
|
|
:isForwardActionDisabled="!meta.valid"
|
|
:isBackButtonHidden="shouldHideBackButton"
|
|
@back-clicked="backButtonAction"
|
|
@ForwardClicked="forwardButtonAction" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Form>
|
|
</template>
|
|
|
|
<script>
|
|
// Components
|
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
|
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
|
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
|
import paymentMethodQuestion from "@/layouts/payment-method/payment-method-question/payment-method-question";
|
|
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
|
import cart from "@/fmg-components/cart/cart";
|
|
import addVapsModalButtons from "./add-vaps-modal-buttons/add-vaps-modal-buttons";
|
|
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
|
import alert from "@/ux-components/alert/alert";
|
|
|
|
// Supporting Items
|
|
import baseMixin from "@/mixins/base-mixin.js";
|
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
|
import { storeActions } from "@/constants/store-actions";
|
|
import store from "@/store";
|
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|
import { paymentMethods } from "@/constants/payment-method-constants";
|
|
import { experimentSettings } from "@/constants/experiments";
|
|
import {
|
|
revalidatePromosAndValidateNewPromo,
|
|
getPromosWithAddableVaps,
|
|
getVapsThatNeedToBeAddedToSatisfyPromos,
|
|
} from "@/helpers/promotions-helper";
|
|
import { queryStrings } from "@/constants/query-strings";
|
|
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
|
|
|
import { Form } from "vee-validate";
|
|
import { defineRule } from "vee-validate";
|
|
import { required } from "@/helpers/validation-rules";
|
|
import { errorMessages } from "@/constants/error-messages";
|
|
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
|
import { mapTaxedAvailableLineItemsToStoreFormat } from "../../store";
|
|
|
|
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
|
|
|
export default {
|
|
name: "paymentMethod",
|
|
async beforeRouteEnter(to, from, next) {
|
|
// Call APIs
|
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
|
|
|
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.GET_WIPERS,
|
|
null,
|
|
"payment-method"
|
|
);
|
|
|
|
const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.GET_RAIN_DEFENSE,
|
|
null,
|
|
"payment-method"
|
|
);
|
|
|
|
const supportingItemsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.GET_SUPPORTING_ITEMS,
|
|
null,
|
|
"payment-method"
|
|
);
|
|
|
|
const promiseResultMap = [
|
|
{
|
|
resultKey: "cmsContent",
|
|
promise: cmsContentPromise,
|
|
},
|
|
{
|
|
resultKey: "wipers",
|
|
promise: wipersPromise,
|
|
},
|
|
{
|
|
resultKey: "rainDefense",
|
|
promise: rainDefensePromise,
|
|
},
|
|
{
|
|
resultKey: "supportingItems",
|
|
promise: supportingItemsPromise,
|
|
},
|
|
];
|
|
|
|
const resultMap = await settleAllPromises(promiseResultMap);
|
|
|
|
const glassParts = store.getters.order.lineItems.glassParts ?? [];
|
|
|
|
let availableLineItems = [
|
|
resultMap.rainDefense,
|
|
...resultMap.supportingItems,
|
|
...resultMap.wipers,
|
|
...glassParts,
|
|
];
|
|
|
|
const lineItemsFromStore = store.getters.order.lineItems;
|
|
|
|
await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
|
{
|
|
availableLineItems: availableLineItems,
|
|
},
|
|
"payment-method",
|
|
false
|
|
);
|
|
|
|
// Promo logic
|
|
const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
|
|
|
|
const { validatePromoResponse, revalidatePromoResponse } =
|
|
await revalidatePromosAndValidateNewPromo(
|
|
promoCodeFromQueryString,
|
|
availableLineItems,
|
|
"payment-method"
|
|
);
|
|
|
|
const newValidatedPromos = validatePromoResponse?.orderPromos ?? [];
|
|
|
|
availableLineItems.push(...newValidatedPromos);
|
|
// End of promo logic
|
|
|
|
const taxedAvailableLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
|
{
|
|
billToAccountNumber: "87291",
|
|
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
|
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
|
serviceLocationCity: store.getters.order.serviceLocation.city,
|
|
serviceLocationState: store.getters.order.serviceLocation.state,
|
|
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
|
pricedAvailableLineItems: availableLineItems,
|
|
},
|
|
"payment-method",
|
|
false
|
|
);
|
|
|
|
// Match all available line items to the line items as they are in the store
|
|
// and rebuild the original structure.
|
|
const lineItems = mapTaxedAvailableLineItemsToStoreFormat(
|
|
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) => {
|
|
vm.setCmsContent(resultMap.cmsContent);
|
|
vm.availableLineItems = taxedAvailableLineItems;
|
|
vm.lineItems = lineItems;
|
|
|
|
vm.updateFooterButtonText(vm.customCtaCopy);
|
|
});
|
|
},
|
|
data() {
|
|
return {
|
|
lineItems: [],
|
|
availableLineItems: [],
|
|
paymentMethodInternalModel: this.getPaymentMethodFromStore(),
|
|
inactivePromos: this.getInactivePromosFromStore(),
|
|
};
|
|
},
|
|
methods: {
|
|
arePagePrerequisitesValid() {
|
|
// Line Items
|
|
const packageReqs = !!store.getters.order.lineItems.supportingItems;
|
|
|
|
// Service Location
|
|
const serviceLocation = store.getters.order.serviceLocation;
|
|
const mobileReqs = !!(
|
|
serviceLocation.address &&
|
|
serviceLocation.city &&
|
|
serviceLocation.state &&
|
|
serviceLocation.zipCode
|
|
);
|
|
|
|
const providerLocation = serviceLocation.provider.address;
|
|
const dropOffInshopReqs = !!(
|
|
providerLocation.streetAddress &&
|
|
providerLocation.city &&
|
|
providerLocation.state &&
|
|
providerLocation.zipCode
|
|
);
|
|
|
|
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
|
|
|
|
const serviceLocationReqs =
|
|
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
|
|
|
// Insurance
|
|
const isInsuranceSet = store.getters.order.payment.isInsurance !== null;
|
|
|
|
// Schedule
|
|
const schedule = store.getters.order.schedule;
|
|
const scheduleReqs = !!(
|
|
schedule.date &&
|
|
schedule.startTime &&
|
|
(!isMobile || schedule.endTime) &&
|
|
schedule.jobMaxMinutes &&
|
|
schedule.jobMinMinutes
|
|
);
|
|
|
|
// Customer
|
|
const customer = store.getters.order.customer;
|
|
const customerReqs = !!(
|
|
customer.firstName &&
|
|
customer.lastName &&
|
|
customer.phoneNumber &&
|
|
customer.emailAddress
|
|
);
|
|
|
|
return (
|
|
packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs
|
|
);
|
|
},
|
|
getPaymentMethodFromStore() {
|
|
const isPia = store.getters.order.payment.isPia;
|
|
|
|
if (isPia === null || isPia === undefined) {
|
|
return null;
|
|
}
|
|
|
|
if (isPia) {
|
|
return store.getters.order.payment.piaType;
|
|
} else {
|
|
return paymentMethods.LATER;
|
|
}
|
|
},
|
|
updateFooterButtonText(newValue) {
|
|
this.$refs.navbar.updateButtonText(newValue);
|
|
},
|
|
getInactivePromosFromStore() {
|
|
return this.$store.getters.order.payment.inactivePromos;
|
|
},
|
|
async revalidatePromos() {
|
|
const revalidatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA,
|
|
{
|
|
activePromosToUse: this.lineItems.promos,
|
|
inactivePromosToUse: this.inactivePromos,
|
|
lineItemsToUse: this.lineItems,
|
|
},
|
|
"payment-method",
|
|
false
|
|
);
|
|
|
|
this.lineItems.promos = revalidatePromoResponse.promoLineItems;
|
|
this.inactivePromos = revalidatePromoResponse.errors.map((x) => x.promoCode);
|
|
},
|
|
backButtonAction() {
|
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
|
},
|
|
async forwardButtonAction() {
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
|
|
this.paymentMethod,
|
|
false
|
|
);
|
|
|
|
// save lineitems as they now have salestax added
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
|
|
this.lineItems.glassParts,
|
|
false
|
|
);
|
|
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
|
this.lineItems.supportingItems,
|
|
false
|
|
);
|
|
await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false);
|
|
await this.dispatchStoreAction(storeActions.SAVE_PROMOS, this.lineItems.promos, false);
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_INACTIVE_PROMOS,
|
|
this.inactivePromos,
|
|
false
|
|
);
|
|
|
|
if (this.paymentMethod == paymentMethods.LATER) {
|
|
// this creates the final work order
|
|
await submitWorkOrder({ pageNameToLog: "payment-method", submitAfterSave: true });
|
|
this.$router.navigateWithoutSaving(
|
|
this.navigationScenarios.CLICKED_FORWARD,
|
|
this.$route
|
|
);
|
|
} else {
|
|
this.setupPia();
|
|
}
|
|
},
|
|
async setupPia() {
|
|
this.$refs.loadingModal.showModal();
|
|
|
|
// if we don't have a work order in delete substatus, set the flag and submit.
|
|
// we need a work order number for pia so we can pass it to safeliteHop.
|
|
if (!store.getters.order.workOrderNumber) {
|
|
try {
|
|
await submitWorkOrder({
|
|
pageNameToLog: "payment-method",
|
|
submitAfterSave: false,
|
|
createDeleteStatusWorkOrderForPia: true,
|
|
});
|
|
} 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;
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
|
this.$router.navigateWithoutSaving(
|
|
this.navigationScenarios.CLICKED_PAY_NOW,
|
|
this.$route
|
|
);
|
|
},
|
|
showAlert(alert) {
|
|
this.$refs.funnelHeader.triggerGlobalAlert(alert, true);
|
|
},
|
|
},
|
|
computed: {
|
|
damageInfo() {
|
|
return this.$store.getters.damage;
|
|
},
|
|
noPiaDisclaimer() {
|
|
return this.getCmsContent("NoPiaDisclaimerWidget", "Text");
|
|
},
|
|
customCtaCopy() {
|
|
switch (this.paymentMethod) {
|
|
case paymentMethods.CREDIT_CARD:
|
|
return "Continue to checkout";
|
|
case paymentMethods.PAYPAL:
|
|
return "Continue to Paypal";
|
|
case paymentMethods.AFTERPAY:
|
|
return "Continue to Afterpay";
|
|
default:
|
|
return null;
|
|
}
|
|
},
|
|
isPiaDisabled() {
|
|
const piaExperience =
|
|
this.getSettingValue(experimentSettings.PIA_EXPERIENCE) === "PIA Optional";
|
|
|
|
return !piaExperience;
|
|
},
|
|
paymentMethod() {
|
|
if (this.isPiaDisabled) {
|
|
return paymentMethods.LATER;
|
|
}
|
|
|
|
return this.paymentMethodInternalModel;
|
|
},
|
|
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,
|
|
navbar,
|
|
funnelSubHeader,
|
|
Form,
|
|
loadingModal,
|
|
cart,
|
|
alert,
|
|
addVapsModalButtons,
|
|
paymentMethodQuestion,
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.no-pia-disclaimer {
|
|
color: $gray-550;
|
|
margin: 0;
|
|
}
|
|
</style>
|