INSR-7759: More cart updates, preliminary SMS opt-in work
This commit is contained in:
parent
de460d4730
commit
b4977cd63c
4 changed files with 160 additions and 141 deletions
|
|
@ -70,7 +70,9 @@ const errorMessages = Object.freeze({
|
|||
TPA_SEARCH_SHOP_FORMAT: 'Please enter a valid shop name.',
|
||||
TPA_SEARCH_REQUIRED: 'Please enter a shop name or ZIP code.',
|
||||
SERVICE_ADDRESS_REQUIRED: 'Service street address is required.',
|
||||
SERVICE_CITY_REQUIRED: 'Service city is required.'
|
||||
SERVICE_CITY_REQUIRED: 'Service city is required.',
|
||||
|
||||
PAYMENT_METHOD_PAGE_OPTION_REQUIRED: 'Please select an Option',
|
||||
});
|
||||
|
||||
export default errorMessages;
|
||||
|
|
|
|||
|
|
@ -19,11 +19,12 @@ export function getLineItems(order) {
|
|||
* @returns {[]} array of service line items
|
||||
*/
|
||||
export function getServiceLineItems(order) {
|
||||
const { supportingItems, glassParts, otherParts } = getLineItems(order);
|
||||
const { supportingItems, glassParts, otherParts, feeItems } = getLineItems(order);
|
||||
return [
|
||||
...(supportingItems ?? []),
|
||||
...(glassParts ?? []),
|
||||
...(otherParts ?? [])
|
||||
...(otherParts ?? []),
|
||||
...(feeItems ?? [])
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -33,10 +34,9 @@ export function getServiceLineItems(order) {
|
|||
* @returns {[]} array of non-service line items
|
||||
*/
|
||||
export function getNonServiceLineItems(order) {
|
||||
const { vaps, feeItems } = getLineItems(order);
|
||||
const { vaps } = getLineItems(order);
|
||||
return [
|
||||
...(vaps ?? []),
|
||||
...(feeItems ?? [])
|
||||
...(vaps ?? [])
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -157,3 +157,11 @@ export function getRecalibrationTotal(order) {
|
|||
|
||||
return getPriceOfLineItems(recalibrationLineItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the order only contains a deductible line item and no other line items
|
||||
* @param {object} order order object
|
||||
* @returns {boolean} whether the order is deductible only
|
||||
*/
|
||||
export function isDeductibleOnly(order) {
|
||||
}
|
||||
|
|
@ -24,15 +24,14 @@
|
|||
class="cart-item">
|
||||
<div class="price-row">
|
||||
<span id="recalibration-label">{{ recalibrationLabel }}</span>
|
||||
<span id="recalibration-value">{{ getDisplayed(recalibrationPrice) }}</span>
|
||||
<span id="recalibration-value">{{ formatAmountInDollars(recalibrationPrice) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NonPackaged Cart Items -->
|
||||
<div
|
||||
v-for="(item, i) in nonServicePackageCartItems"
|
||||
v-for="(item, i) in cartItems"
|
||||
:key="i"
|
||||
class="cart-item non-packaged">
|
||||
class="cart-item packaged">
|
||||
<div class="price-row">
|
||||
<span>{{ item?.name ?? '' }}</span>
|
||||
<span>{{ formatAmountInDollars(item?.subTotal ?? 0) }}</span>
|
||||
|
|
@ -49,32 +48,12 @@
|
|||
</template>
|
||||
</textLink>
|
||||
</div>
|
||||
|
||||
<!-- Packaged Cart Items -->
|
||||
<div
|
||||
v-for="(item, i) in servicePackageCartItems"
|
||||
:key="i"
|
||||
class="cart-item packaged">
|
||||
<div class="price-row">
|
||||
<span>{{ item?.name ?? '' }}</span>
|
||||
<span>{{ formatAmountInDollars(item?.subTotal ?? 0) }}</span>
|
||||
</div>
|
||||
<textLink
|
||||
v-if="!readOnly"
|
||||
linkType="text"
|
||||
text="Remove"
|
||||
href="javascript:void(0)"
|
||||
@clickEvent="removeVap(item.partType)">
|
||||
<template #after-text>
|
||||
<span class="sr-only">{{ item?.name ?? '' }}</span>
|
||||
</template>
|
||||
</textLink>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="cart-footer"
|
||||
class="cart-footer pb-5">
|
||||
class="cart-footer">
|
||||
<div
|
||||
v-if="!isUnverified"
|
||||
id="cart-subtotal-and-tax"
|
||||
class="subtotal-and-tax">
|
||||
<div
|
||||
|
|
@ -106,7 +85,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-else class="deductible-box">
|
||||
<span class="deductible-label">{{ deductibleLabel }}</span>
|
||||
<span class="deductible-label">{{ deductibleLabel }}:</span>
|
||||
<span class="deductible-value">{{ getDisplayed(deductible) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -117,7 +96,6 @@ import contentGroupModal from '@/iss-components/content-group-modal/content-grou
|
|||
import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||
import { formatAmountInDollars } from '@/helpers/text-helper.js';
|
||||
import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/service-package-helper.js';
|
||||
|
||||
// Constants
|
||||
import { experimentSettings } from '@/constants/experiments';
|
||||
|
|
@ -181,7 +159,7 @@ export default {
|
|||
return getDeductible(this.cartOrder);
|
||||
},
|
||||
isDeductibleOnly() {
|
||||
return this.deductible > 0 && this.subTotal === this.deductible;
|
||||
return this.cartItems.length === 0;
|
||||
},
|
||||
showDeductibleCartItem() {
|
||||
return this.isUnverified || (!this.isNoComp && !this.isITAC);
|
||||
|
|
@ -238,59 +216,8 @@ export default {
|
|||
];
|
||||
return result;
|
||||
},
|
||||
vehicleDamage() {
|
||||
return this.cartOrder.damage;
|
||||
},
|
||||
servicePackageTier() {
|
||||
const { glassToReplace, isRepair } = this.vehicleDamage;
|
||||
const { vaps } = this.lineItems;
|
||||
return getHighestFullySatisfiedTier(
|
||||
glassToReplace ?? [],
|
||||
this.availableLineItems,
|
||||
isRepair,
|
||||
vaps ?? []
|
||||
);
|
||||
},
|
||||
partTypesInServicePackage() {
|
||||
const { glassToReplace, isRepair } = this.vehicleDamage;
|
||||
return getPackageContents(
|
||||
glassToReplace ?? [],
|
||||
this.availableLineItems,
|
||||
isRepair,
|
||||
this.servicePackageTier
|
||||
) ?? [];
|
||||
},
|
||||
servicePackageCartItems() {
|
||||
cartItems() {
|
||||
const items = [];
|
||||
if (this.partTypesInServicePackage.includes(partTypeStrings.FRONT_WIPER) ?? this.frontWipersCartItem) {
|
||||
items.push(this.frontWipersCartItem);
|
||||
}
|
||||
if (this.partTypesInServicePackage.includes(partTypeStrings.REAR_WIPER) ?? this.rearWipersCartItem) {
|
||||
items.push(this.rearWipersCartItem);
|
||||
}
|
||||
if (this.partTypesInServicePackage.includes(partTypeStrings.RAIN_DEFENSE) ?? this.rainDefenseCartItem) {
|
||||
items.push(this.rainDefenseCartItem);
|
||||
}
|
||||
return items;
|
||||
},
|
||||
nonServicePackageCartItems() {
|
||||
const vapPartTypesInOrder = Array.from(new Set(this.lineItems.vaps?.map((vap) => vap.partType) ?? []));
|
||||
const vapPartTypesInOrderButNotPackage = vapPartTypesInOrder
|
||||
.filter((partType) => !this.partTypesInServicePackage.includes(partType))
|
||||
?? [];
|
||||
const vapsCartItemsNotInPackage = vapPartTypesInOrderButNotPackage.map((partType) => {
|
||||
switch (partType) {
|
||||
case partTypeStrings.FRONT_WIPER:
|
||||
return this.frontWipersCartItem;
|
||||
case partTypeStrings.REAR_WIPER:
|
||||
return this.rearWipersCartItem;
|
||||
case partTypeStrings.RAIN_DEFENSE:
|
||||
return this.rainDefenseCartItem;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const items = vapsCartItemsNotInPackage.filter((item) => item != null);
|
||||
const isRecycleFeeHidden = this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN) === 'true';
|
||||
const isMobileFeeHidden = this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN) === 'true';
|
||||
if (this.isITAC || this.isNoComp) {
|
||||
|
|
@ -302,6 +229,20 @@ export default {
|
|||
if (this.mobileFeeCartItem && !isMobileFeeHidden) {
|
||||
items.push(this.mobileFeeCartItem);
|
||||
}
|
||||
const vapPartTypesInOrder = Array.from(new Set(this.lineItems.vaps?.map((vap) => vap.partType) ?? []));
|
||||
const vapsCartItems = vapPartTypesInOrder.map((partType) => {
|
||||
switch (partType) {
|
||||
case partTypeStrings.FRONT_WIPER:
|
||||
return this.frontWipersCartItem;
|
||||
case partTypeStrings.REAR_WIPER:
|
||||
return this.rearWipersCartItem;
|
||||
case partTypeStrings.RAIN_DEFENSE:
|
||||
return this.rainDefenseCartItem;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
items.push(...vapsCartItems.filter((item) => item != null));
|
||||
return items;
|
||||
},
|
||||
frontWipersCartItem() {
|
||||
|
|
@ -313,12 +254,6 @@ export default {
|
|||
rainDefenseCartItem() {
|
||||
return this.getCartItemForVapsPart(partTypeStrings.RAIN_DEFENSE);
|
||||
},
|
||||
allCartItems() {
|
||||
return [
|
||||
...this.servicePackageCartItems,
|
||||
...this.nonServicePackageCartItems
|
||||
];
|
||||
},
|
||||
amountDueLabel() {
|
||||
return this.getCmsContent(this.widget.amountDue, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||
},
|
||||
|
|
@ -345,21 +280,6 @@ export default {
|
|||
salesTaxLabel() {
|
||||
return this.getCmsContent(this.widget.salesTax, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||
},
|
||||
servicePackageNames() {
|
||||
return this.getCmsContent(
|
||||
this.widget.servicePackage,
|
||||
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
|
||||
);
|
||||
},
|
||||
servicePackageLabelWidget() {
|
||||
if (!this.servicePackageNames) {
|
||||
return '';
|
||||
}
|
||||
const currentPackage = this.servicePackageNames
|
||||
?.find((entry) => entry?.Name === this.servicePackageTier);
|
||||
|
||||
return currentPackage?.SubWidgetName ?? '';
|
||||
},
|
||||
recycleFeeCartItem() {
|
||||
return this.recycleFeeLineItem
|
||||
? this.getCartItem(
|
||||
|
|
@ -517,20 +437,19 @@ export default {
|
|||
background-color: $green;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.deductible-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
}
|
||||
.deductible-label {
|
||||
color: $black;
|
||||
font-weight: 600;
|
||||
}
|
||||
.deductible-value {
|
||||
color: $green;
|
||||
font-weight: 500;
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
}
|
||||
.deductible-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
}
|
||||
.deductible-label {
|
||||
color: $black;
|
||||
font-weight: 600;
|
||||
}
|
||||
.deductible-value {
|
||||
color: $green;
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -23,13 +23,17 @@
|
|||
<hr class="section-divider" />
|
||||
<reviewDropdown ref="reviewDropdown" />
|
||||
<hr class="section-divider" />
|
||||
<textBlock
|
||||
cmsWidgetName="CartHeaderWidget" />
|
||||
<cartDropdown
|
||||
:showAsPaid="false"
|
||||
:readOnly="false"
|
||||
recyclingModalCmsWidgetName="RecycleModal"
|
||||
servicePackageTitleWidgetName="ServicePackageTitle" />
|
||||
<div class="cart-header">
|
||||
<span class="cart-label">{{ cartHeaderText }}</span>
|
||||
<span v-if="showCartTotal" class="cart-value">{{ cartTotal }}</span>
|
||||
</div>
|
||||
<div class="cart-dropdown-container">
|
||||
<cartDropdown
|
||||
:showAsPaid="false"
|
||||
:readOnly="false"
|
||||
recyclingModalCmsWidgetName="RecycleModal"
|
||||
servicePackageTitleWidgetName="ServicePackageTitle" />
|
||||
</div>
|
||||
<alert
|
||||
v-if="displayPayInAdvanceAlert"
|
||||
name="payInAdvanceErrorAlert"
|
||||
|
|
@ -45,12 +49,20 @@
|
|||
v-model="paymentMethod"
|
||||
:cmsWidgetName="paymentMethodWidgetName"
|
||||
:validationRules="rules.optionRequired" />
|
||||
<alert
|
||||
v-if="isPayInAdvanceDisabled"
|
||||
:isDismissible="false"
|
||||
alertClass="alert-info"
|
||||
cmsWidgetName="NoPayInAdvanceDisclaimerWidget"
|
||||
:shouldScrollToOnMount="false" />
|
||||
<div
|
||||
v-if="!isOptedInSMSPreviously"
|
||||
class="sms-opt-in-section">
|
||||
<hr class="sms-divider" />
|
||||
<span class="sms-opt-in-header">{{ smsOptInHeaderText }}</span>
|
||||
<buttonQuestion
|
||||
v-model="smsOptIn"
|
||||
groupName="sms-opt-in"
|
||||
buttonTypeString="listButtonHorizontal"
|
||||
:questionText="smsOptinQuestionText"
|
||||
:answers="smsOptinQuestionAnswers"
|
||||
isRequired
|
||||
:validationRules="rules.optionRequired" />
|
||||
</div>
|
||||
<siteFooter
|
||||
ref="siteFooter"
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
|
|
@ -76,6 +88,7 @@ import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
|
|||
import paymentMethodQuestion from '@/layouts/payment-method/payment-method-question/payment-method-question.vue';
|
||||
import alert from '@/ux-components/alert/alert.vue';
|
||||
import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||
|
||||
// Supporting Items
|
||||
import settleAllPromises from '@/helpers/layout-helper';
|
||||
|
|
@ -84,7 +97,7 @@ import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
|||
import { paymentMethods } from '@/constants/payment-method-constants';
|
||||
import queryStrings from '@/constants/query-strings';
|
||||
import globalRules from '@/constants/global-rules';
|
||||
import { Form } from 'vee-validate';
|
||||
import { defineRule, Form } from 'vee-validate';
|
||||
import issPageValues from '@/router/router-constants/issPage-values';
|
||||
import bailoutMessage from '@/constants/bailoutMessage';
|
||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
||||
|
|
@ -93,6 +106,13 @@ import { experimentSettings } from '@/constants/experiments';
|
|||
import submitType from '@/constants/submit-type';
|
||||
import routerParams from '@/router/router-constants/router-params';
|
||||
import { supportsApplePay } from '@/helpers/browser-helper';
|
||||
import { getCartTotal } from '@/helpers/cart-helper';
|
||||
import { formatAmountInDollars } from '@/helpers/text-helper';
|
||||
import widgetFields from '@/constants/cms-widget-fields';
|
||||
import errorMessages from '@/constants/error-messages';
|
||||
import { required } from '@/helpers/validation-rules';
|
||||
|
||||
defineRule('option-required', required(errorMessages.PAYMENT_METHOD_PAGE_OPTION_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: 'payment-method',
|
||||
|
|
@ -105,7 +125,8 @@ export default {
|
|||
cartDropdown,
|
||||
paymentMethodQuestion,
|
||||
alert,
|
||||
textBlock
|
||||
textBlock,
|
||||
buttonQuestion
|
||||
},
|
||||
mixins: [baseFormMixin],
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -134,11 +155,17 @@ export default {
|
|||
paymentMethod: null,
|
||||
widget: {
|
||||
paymentMethod: 'PaymentMethodWidget',
|
||||
paymentMethodApplePay: 'PaymentMethodWidgetApplePay'
|
||||
paymentMethodApplePay: 'PaymentMethodWidgetApplePay',
|
||||
amountDue: 'AmountDueCartHeaderTextWidget',
|
||||
payAtAppointment: 'PayAtAppointmentTextWidget',
|
||||
smsOptInHeader: 'SMSOptInHeaderWidget',
|
||||
smsOptInQuestion: 'SMSOptInQuestionWidget'
|
||||
},
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
},
|
||||
smsOptIn: null,
|
||||
smsPhoneNumber: this.mainStore.contactInfo?.alternativePhone
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -163,6 +190,41 @@ export default {
|
|||
},
|
||||
paymentMethodWidgetName() {
|
||||
return supportsApplePay() ? this.widget.paymentMethodApplePay : this.widget.paymentMethod;
|
||||
},
|
||||
showCartTotal() {
|
||||
return this.mainStore.isITAC || this.mainStore.isNoComp
|
||||
|| this.mainStore.order.lineItems?.vaps?.length > 0;
|
||||
},
|
||||
cartHeaderText() {
|
||||
if (this.isPayInAdvanceDisabled) {
|
||||
return this.getCmsContent(
|
||||
this.widget.payAtAppointment,
|
||||
widgetFields.TEXT_BLOCK_WIDGET.TEXT
|
||||
);
|
||||
} else {
|
||||
return this.getCmsContent(
|
||||
this.widget.amountDue,
|
||||
widgetFields.TEXT_BLOCK_WIDGET.TEXT
|
||||
);
|
||||
}
|
||||
},
|
||||
cartTotal() {
|
||||
return formatAmountInDollars(getCartTotal(this.mainStore.order));
|
||||
},
|
||||
isOptedInSMSPreviously() {
|
||||
return false;
|
||||
},
|
||||
smsOptInHeaderText() {
|
||||
return this.getCmsContent(
|
||||
this.widget.smsOptInHeader,
|
||||
widgetFields.TEXT_BLOCK_WIDGET.TEXT
|
||||
);
|
||||
},
|
||||
smsOptinQuestionText() {
|
||||
return this.getCmsContent(this.widget.smsOptInQuestion, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
|
||||
},
|
||||
smsOptinQuestionAnswers() {
|
||||
return this.getCmsContent(this.widget.smsOptInQuestion, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS) || [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -266,6 +328,11 @@ export default {
|
|||
},
|
||||
async forwardButtonAction() {
|
||||
useMainStore().savePaymentMethodChoice(this.paymentMethod);
|
||||
if (this.smsOptIn !== null) {
|
||||
useMainStore().updateContactInfo({
|
||||
requestTextUpdates: this.smsOptIn === 'Yes'
|
||||
});
|
||||
}
|
||||
if (this.paymentMethod === paymentMethods.PAY_AT_TIME_OF_SERVICE) {
|
||||
try {
|
||||
await submitWorkOrder({ submitType: submitType.SAFELITE });
|
||||
|
|
@ -350,9 +417,32 @@ $page-side-padding: 1.5rem;
|
|||
margin-bottom: 1.875rem;
|
||||
}
|
||||
|
||||
.cart-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.cart-label {
|
||||
color: $black;
|
||||
font-weight: 600;
|
||||
line-height: 1.625rem;
|
||||
}
|
||||
|
||||
.cart-value {
|
||||
color: $green;
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-dropdown-container {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.pia-divider {
|
||||
margin-top: .5rem;
|
||||
margin-bottom: .75rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue