INSR-7757: Update order confirmation page and cart

This commit is contained in:
Alex Humphries 2026-02-10 16:46:39 -05:00
parent 7e56c0d33d
commit b5ecac8b5a
8 changed files with 2444 additions and 99 deletions

View file

@ -125,6 +125,10 @@ const endpoints = Object.freeze({
url: `${PARTS_BASE_URL}/mobile-fee`,
method: 'GET'
},
GetSV2GlassTypes: {
url: `${PARTS_BASE_URL}/map-to-sv2-glass-types`,
method: 'POST'
},
GetServiceabilityDetails: {
url: `${LOCATION_BASE_URL}/serviceability-details`,
method: 'GET'

View file

@ -7,7 +7,8 @@
RECALIBRATION: 'RECALIBRATION',
REPLACE_FEE: 'REPLACE FEE',
MOBILE_FEE: 'MOBILE FEE',
REPAIR_FEE: 'REPAIR FEE'
REPAIR_FEE: 'REPAIR FEE',
WARRANTY: 'WARRANTY'
});
export default partTypeStrings;

View file

@ -71,6 +71,26 @@ export async function isGlassAvailableForCarId(carId) {
}
}
export function getGlassList(glassPieces) {
let names = '';
switch (glassPieces.length)
{
case 0:
break;
case 1:
names = glassPieces[0];
break;
case 2:
names = glassPieces.join(' and ');
break;
default:
names = glassPieces.slice(0, -1).join(', ') + ', and ' + glassPieces.slice(-1);
break;
}
return names.toLowerCase();
}
/**
* Commented code are copied directly from DigitalConsumer.FixMyGlass
* and have not been adjusted for ISS.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,478 @@
<template>
<div class="cart-table">
<div class="cart-item-list">
<div
id="cart-deductible-or-base-price"
class="cart-item">
<template
v-if="showDeductibleCartItem">
<span id="deductible-label">{{ deductibleLabel }}</span>
<span id="deductible-value">{{ getDisplayed(deductible) }}</span>
</template>
<template
v-else>
<span id="base-price-label">{{ basePriceLabel }}</span>
<span id="base-price-value">{{ getDisplayed(servicePrice) }}</span>
</template>
</div>
<!-- NonPackaged Cart Items -->
<div
v-for="(item, i) in nonServicePackageCartItems"
:key="i"
class="cart-item">
<span
v-if="item.cartItemType === cartItemType.RECYCLE_FEE"
id="recycle-fee-label">
<textLink
linkType="text"
:text="item?.name ?? ''"
href="javascript:void(0)"
@clickEvent="openModal(RECYCLING_MODAL_REF_NAME)" />
</span>
<span v-else>{{ item?.name ?? '' }}</span>
<span>{{ formatAmountInDollars(item?.subTotal ?? 0) }}</span>
</div>
<!-- Packaged Cart Items -->
<div
v-for="(item, i) in servicePackageCartItems"
:key="i"
class="cart-item">
<span>{{ item?.name ?? '' }}</span>
<span>{{ formatAmountInDollars(item?.subTotal ?? 0) }}</span>
</div>
</div>
<div
id="cart-footer"
class="cart-footer pb-5">
<div
id="cart-subtotal-and-tax"
class="subtotal-and-tax">
<div
id="cart-subtotal"
class="py-2 col d-flex justify-content-between">
<span id="subtotal-label">{{ subtotalLabel }}</span>
<span id="subtotal-value">{{ getDisplayed(subTotal) }}</span>
</div>
<div
id="cart-sales-tax"
class="py-2 col d-flex justify-content-between">
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
<span id="sales-tax-value">{{ formatAmountInDollars(salesTax) }}</span>
</div>
<div
v-if="showAsPaid"
id="cart-amount-paid"
class="py-2 col d-flex justify-content-between">
<span id="amount-paid-label">{{ amountPaidLabel }}</span>
<span if="amount-paid-value">{{ getDisplayed(amountPaid) }}</span>
</div>
</div>
<div
id="bottom-amount-due"
class="bottom-amount-due col d-flex justify-content-between">
<span id="bottom-amount-due-label">{{ amountDueLabel }}</span>
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
</div>
</div>
<contentGroupModal
:ref="RECYCLING_MODAL_REF_NAME"
:cmsWidgetName="recyclingModalCmsWidgetName">
<textBlock cmsWidgetName="RecycleTextBlock" />
</contentGroupModal>
</div>
</template>
<script>
import { useMainStore } from '@/store';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
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';
import partTypeStrings from '@/constants/part-type-strings.js';
import cartItemType from '@/constants/cart-item-type.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import {
getCartTotal,
getDeductible,
getLineItems, getMobileFeeLineItem,
getRecycleFeeLineItem, getSalesTax,
getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp,
isOrderUnverified
} from '@/helpers/cart-helper';
import { getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculator';
const VERIFYING_COVERAGE = 'Verifying coverage';
const RECYCLING_MODAL_REF_NAME = 'RecycleModal';
export default {
name: 'confirmation-cart',
components: {
contentGroupModal,
textBlock,
textLink
},
props: {
showAsPaid: Boolean,
recyclingModalCmsWidgetName: String,
submittedOrder: Object
},
data() {
return {
widget: {
amountDue: 'AmountDueTextWidget',
amountPaid: 'AmountPaidTextWidget',
deductible: 'DeductibleWidget',
basePrice: 'BasePriceWidget',
subtotal: 'SubtotalWidget',
salesTax: 'SalesTaxWidget',
recycleFee: 'RecycleFeeWidget',
mobileFee: 'MobileServiceWidget',
servicePackage: 'ServicePackageTitle',
vapsItemDescriptions: 'VapsItemDescriptions',
warrantyText: 'WarrantyCartItemTextWidget',
guaranteeText: 'GuaranteeCartItemTextWidget'
},
cartItemType,
RECYCLING_MODAL_REF_NAME
};
},
computed: {
cartOrder() {
return this.submittedOrder ?? useMainStore().order;
},
availableVaps() {
return this.cartOrder.availableVaps;
},
deductible() {
return getDeductible(this.cartOrder);
},
showDeductibleCartItem() {
return this.isUnverified || (!this.isNoComp && !this.isITAC);
},
lineItems() {
return getLineItems(this.cartOrder);
},
recycleFeeLineItem() {
return getRecycleFeeLineItem(this.cartOrder);
},
servicePrice() {
return getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0;
},
isUnverified() {
return isOrderUnverified(this.cartOrder);
},
isITAC() {
return isOrderITAC(this.cartOrder);
},
isNoComp() {
return isOrderNoComp(this.cartOrder);
},
// TODO fix rounding
subTotal() {
return getSubtotal(this.cartOrder);
},
salesTax() {
return getSalesTax(this.cartOrder);
},
total() {
return getCartTotal(this.cartOrder);
},
amountDue() {
return this.showAsPaid ? 0 : this.total;
},
amountPaid() {
return !this.showAsPaid ? 0 : this.total;
},
availableLineItems() {
const { supportingItems, glassParts, otherParts, feeItems } = this.lineItems;
const result = [
...(supportingItems ?? []),
...(glassParts ?? []),
...(otherParts ?? []),
...(this.availableVaps ?? []),
...(feeItems ?? [])
];
return result;
},
vehicleDamage() {
return this.submittedOrder ? this.submittedOrder.damage : useMainStore().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() {
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.recycleFeeCartItem && !isRecycleFeeHidden) {
items.push(this.recycleFeeCartItem);
}
if (this.mobileFeeCartItem && !isMobileFeeHidden) {
items.push(this.mobileFeeCartItem);
}
if (this.isITAC || this.isNoComp) {
items.push(this.warrantyCartItem);
}
return items;
},
frontWipersCartItem() {
return this.getCartItemForVapsPart(partTypeStrings.FRONT_WIPER);
},
rearWipersCartItem() {
return this.getCartItemForVapsPart(partTypeStrings.REAR_WIPER);
},
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);
},
amountPaidLabel() {
return this.getCmsContent(this.widget.amountPaid, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
deductibleLabel() {
return this.getCmsContent(this.widget.deductible, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
basePriceLabel() {
return this.getCmsContent(this.widget.basePrice, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
subtotalLabel() {
return this.getCmsContent(this.widget.subtotal, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
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(
this.getCmsContent(this.widget.recycleFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
[this.recycleFeeLineItem],
cartItemType.RECYCLE_FEE,
partTypeStrings.REPLACE_FEE
)
: null;
},
mobileFeeCartItem() {
const mobileFeeLineItem = getMobileFeeLineItem(this.cartOrder);
return mobileFeeLineItem
? this.getCartItem(
this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
[mobileFeeLineItem],
cartItemType.MOBILE_FEE,
partTypeStrings.MOBILE_FEE
)
: null;
},
warrantyCartItem() {
const label = this.submittedOrder.damage.glassToReplace?.length > 0
? this.getCmsContent(this.widget.warrantyText, widgetFields.TEXT_BLOCK_WIDGET.TEXT)
: this.getCmsContent(this.widget.guaranteeText, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
return {
name: label,
cartItemType: cartItemType.VAP,
partType: partTypeStrings.WARRANTY,
subTotal: 0,
salesTax: 0
}
}
},
methods: {
openModal(modalName) {
this.$refs[modalName].openModal();
},
getDisplayed(amount) {
return this.isUnverified
? VERIFYING_COVERAGE
: formatAmountInDollars(amount);
},
getCartItem(label, lineItems, cartItemTypeString, partType) {
let cartItem = null;
if (lineItems && lineItems.length > 0) {
cartItem = {
name: label,
cartItemType: cartItemTypeString,
partType,
subTotal: getPriceOfLineItems(lineItems),
salesTax: getTaxOfLineItems(lineItems)
};
}
return cartItem;
},
getCartItemForVapsPart(partType) {
const label = this.getCmsContentForVapsType(partType);
const lineItems = this.lineItems.vaps
?.filter((vapsLineItem) => vapsLineItem.partType === partType) ?? [];
return this.getCartItem(label, lineItems, cartItemType.VAP, partType);
},
removeVap(partType) {
const newVaps = useMainStore().lineItems.vaps?.filter((vap) => vap?.partType !== partType) ?? [];
useMainStore().updateVaps(newVaps);
},
getCmsContentForVapsType(vapsPartType) {
const vapsItemDescriptions = this.getCmsContent(
this.widget.vapsItemDescriptions,
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
);
if (!vapsItemDescriptions) {
return '';
}
const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType);
return vapsTypeDescription?.Text ?? '';
},
formatAmountInDollars
}
};
</script>
<style lang="scss" scoped>
@import "@/styles/ux-variables-svg-strings.scss";
$RECYCLING_MODAL_REF_NAME: 'RecycleModal';
.cart-table {
max-height: 50rem;
transition: all 150ms ease-in;
min-width: fit-content;
:deep(a) {
padding-bottom: 0 !important;
line-height: 1.625rem !important;
text-underline-offset: auto !important;
}
.cart-item {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: .5rem 0rem;
width: 100%;
min-width: fit-content;
&:nth-child(odd) {
background-color: #f4f4f4;
}
&:last-child {
border-bottom: 1px solid #d4d6d8;
}
span {
white-space: nowrap;
padding: 0rem 1rem;
}
}
.cart-footer {
border-top: 1px solid $green;
}
.subtotal-and-tax {
background-color: $green-150;
font-weight: $font-weight-bold;
color: $black;
>div {
padding: .5rem 1rem;
}
}
.bottom-amount-due {
color: $white;
font-weight: 600;
background-color: $green;
padding: .5rem 1rem;
}
}
:deep(.modal-content a.external-text) {
text-underline-offset: 0.25rem;
line-height: 2;
padding: 0 0 0.25rem 0;
font-weight: 500;
max-width: -webkit-fit-content;
max-width: -moz-fit-content;
max-width: fit-content;
}
:deep(##{$RECYCLING_MODAL_REF_NAME} h5) {
text-align: center;
}
:deep(#recycle-fee-label a){
line-height: initial;
}
</style>

View file

@ -8,21 +8,34 @@
<div class="justify-content-center">
<siteHeader :cmsWidgetName="widgets.siteHeader" />
</div>
<div class="iss-heritage-container-width">
<div class="order-confirmation-container iss-heritage-content-container-width">
<div class="text-center text-color--black pt-5 pb-5 fs-5">
<div class="iss-heritage-container-width order-confirmation-container row">
<div class="appointment-info-container col-lg-7">
<div class="header">
<img :src="orderConfirmationImage" />
<span
class="ms-2"
class="header-text"
v-html="orderConfirmationHeaderText"></span>
</div>
<div class="appointment-details">
<div class="appointment-date-time text-center mt-4">
<p>{{ formatDate(schedule.date) }}</p>
<p class="mt-2">
{{ appointmentTimeText }}
</p>
</div>
<div class="subheader">
<span
class="subheader-text"
v-html="orderConfirmationSubheaderText"></span>
<span
class="update-appointment-text"
v-html="orderConfirmationUpdateAppointmentText"></span>
</div>
<div class="service-description confirmation-section">
<span>{{ serviceDescriptionText }}</span>
</div>
<div class="work-order-details confirmation-section">
<span class="work-order-title">{{ workOrderTitle }}</span>
<span class="work-order-description">{{ workOrderNumber }}</span>
</div>
<div class="appointment-details confirmation-section">
<span class="appointment-title">{{ appointmentWordingText }}</span>
<span>{{ formatDate(schedule.date) }}</span>
<span>{{ appointmentTimeText }}</span>
<span class="appointment-location">{{ appointmentLocation }}</span>
<addToCalendar
mobileWidgetName="AddToCalendar_Mobile"
inShopWidgetName="AddToCalendar_InShop"
@ -40,29 +53,32 @@
:hasRecalibrationPart="hasRecalibrationPart"
:uniqueId="referralNumber"
:isRepair="isRepair" />
<div
class="appointment-text text-center lh-base"
v-html="appointmentWordingText"></div>
<div
class="appointment-text text-center lh-base mt-2"
v-html="appointmentWordingText2"></div>
</div>
<hr class="mb-0" />
<div>
<cartDropdown
v-if="showCart"
:showAsPaid="payment.isPayInAdvance"
:readOnly="true"
:isInitiallyExpanded="false"
:showDropdownHeader="true"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle"
:submittedOrder="submittedOrder" />
<div
v-if="displayWipers"
class="wiper-details confirmation-section">
<span class="wipers-title">{{ wipersTitle }}</span>
<span
v-for="(description, index) in wipersBody"
:key="index"
class="wipers-body">{{ description }}</span>
</div>
<div
v-if="displayRainRepel"
class="rain-repel-details confirmation-section">
<span class="rain-repel-title">{{ rainRepelTitle }}</span>
<span class="rain-repel-body">{{ rainRepelBody }}</span>
</div>
<hr class="mt-0 mb-5" />
<div
class="email-confirmation-text"
v-html="confirmationEmailText" />
v-if="displaySMSPhone"
class="sms-phone-details confirmation-section">
<span class="sms-phone-title">{{ smsTitle }}</span>
<span class="sms-phone-body">{{ contactInfo.servicePhone }}</span>
</div>
<div class="contact-details confirmation-section">
<span class="contact-title">{{ contactDetailsTitle }}</span>
<span class="contact-body">{{ contactDetailsBody }}</span>
</div>
<siteFooter
v-if="carrierUrl"
ref="siteFooter"
@ -72,6 +88,17 @@
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
<div class="cart-container col-lg-4 offset-lg-1 col-md-5">
<div class="cart-header">
{{ cartHeaderText }}
</div>
<confirmationCart
v-if="showCart"
:showAsPaid="payment.isPayInAdvance"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle"
:submittedOrder="submittedOrder" />
</div>
</div>
</div>
</Form>
@ -81,7 +108,7 @@
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import addToCalendar from '@/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue';
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
import confirmationCart from '@/iss-components/confirmation-cart/confirmation-cart.vue';
// Supporting files
import {
@ -103,6 +130,9 @@ import { toTitleCase } from '@/helpers/text-helper.js';
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
import applicationConfig from '@/constants/application-config';
import widgetFields from '@/constants/cms-widget-fields.js';
import { getGlassList } from '@/helpers/damage-helper';
import partTypeStrings from '@/constants/part-type-strings';
import coverageType from '@/constants/coverage-type';
export default {
name: 'order-confirmation',
@ -112,18 +142,23 @@ export default {
siteHeader,
siteFooter,
addToCalendar,
cartDropdown
confirmationCart
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const glassTypesPromise = useMainStore().getSV2GlassTypes(useMainStore().getSubmittedOrder().damage.glassToReplace);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
},
{
resultKey: 'glassTypes',
promise: glassTypesPromise
}
];
// use resultMap to populate layout content.
@ -131,6 +166,9 @@ export default {
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.setData({
glassTypes: resultMap.glassTypes
})
});
},
setup() {
@ -144,18 +182,21 @@ export default {
serviceLocation,
schedule,
customer,
contactInfo,
payment,
customerPortalLoginToken,
damage,
referralNumber
referralNumber,
hasRecalibrationPart
} = this.submittedOrder;
const { issConfig, hasRecalibrationPart } = this.mainStore;
const { issConfig } = this.mainStore;
return {
vehicle,
customerEmail: customer?.emailAddress,
payment,
schedule,
serviceLocation,
contactInfo,
appointmentType: serviceLocation.appointmentType,
providerAddress: serviceLocation?.provider?.address,
customerPortalLoginToken,
@ -164,12 +205,24 @@ export default {
isRepair: damage.isRepair,
hasRecalibrationPart,
referralNumber: referralNumber?.toString(),
glassTypes: [],
isNoComp: this.submittedOrder.insuranceCoverage.coverageType === coverageType.NO_COMP,
widgets: {
siteHeader: 'SiteHeaderWidget',
emailConfirmation: 'EmailConfirmationWordingWidget',
emailConfirmationNoComp: 'EmailConfirmationNoCompWordingWidget',
orderConfirmation: 'OrderConfirmationContent',
serviceDescription: 'ServiceDescriptionTextWidget',
workOrderNumberTitle: 'WorkOrderNumberTextWidget',
mobile: 'MobileWordingWidget',
dropOffAndInShop: 'DropOffAndInShopWordingWidget',
wipersText: 'WipersTextWidget',
rainRepel: 'RainRepelWidget',
smsUpdates: 'SmsUpdatesWidget',
contactDetails: 'ContactDetailsWidget',
vapsItemDescriptions: 'VapsItemDescriptions',
payAtAppointment: 'PayAtAppointmentTextWidget',
orderDetails: 'OrderDetailsTextWidget',
siteFooter: 'SiteFooterWidget'
}
};
@ -182,18 +235,29 @@ export default {
vehicleYear: this.vehicle?.year,
vehicleMake: this.vehicle?.make,
vehicleModel: this.vehicle?.model,
serviceType: this.serviceTypeText,
address: this.appointmentLocation,
inShopDuration: this.inShopAppointmentDuration,
email: this.customerEmail,
phone: this.contactInfo.servicePhone,
workOrderNumber: this.workOrderNumber,
CUSTOMER_PORTAL_URL: applicationConfig.CUSTOMER_PORTAL_URL,
CUSTOMER_PORTAL_LOGIN_TOKEN: this.customerPortalLoginToken
};
},
confirmationEmailText() {
const content = this.getCmsContentWithCustomValues(
this.widgets.emailConfirmation,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
orderConfirmationUpdateAppointmentText() {
let content = '';
if(this.isNoComp) {
content = this.getCmsContentWithCustomValues(
this.widgets.emailConfirmationNoComp,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
} else {
content = this.getCmsContentWithCustomValues(
this.widgets.emailConfirmation,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
}
return content
?.replaceAll('&lt;', '<')
?.replaceAll('&gt;', '>');
@ -210,10 +274,50 @@ export default {
widgetFields.CONTENT_GROUP_WIDGET.IMAGE
);
},
orderConfirmationSubheaderText() {
return this.getCmsContent(
this.widgets.orderConfirmation,
widgetFields.CONTENT_GROUP_WIDGET.SUBHEADER_TEXT
);
},
serviceDescriptionText() {
const content = this.getCmsContentWithCustomValues(
this.widgets.serviceDescription,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
return content;
},
serviceTypeText() {
let glassList = getGlassList(this.glassTypes);
let workType = '';
if (this.isRepair) {
workType = 'repair';
} else {
workType = 'replacement';
if (this.hasRecalibrationPart) {
if (glassList === 'windshield') {
workType = 'replacement and recalibration';
}
else {
glassList = glassList.replace('windshield', 'windshield replacement, recalibration');
}
}
}
return `${glassList} ${workType}`;
},
workOrderTitle() {
return this.getCmsContent(
this.widgets.workOrderNumberTitle,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
workOrderNumber() {
return this.submittedOrder.workOrderNumber;
},
mobileWordingText() {
return this.getCmsContentWithCustomValues(
this.widgets.mobile,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
);
},
mobileWordingText2() {
@ -225,7 +329,7 @@ export default {
nonMobileWordingText() {
return this.getCmsContentWithCustomValues(
this.widgets.dropOffAndInShop,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
);
},
nonMobileWordingText2() {
@ -241,12 +345,12 @@ export default {
}
if (this.isInShopAppointment) {
const formattedStartTime = get12HourTimeFormat(startTime);
return `at ${formattedStartTime}`;
return `Your appointment is at ${formattedStartTime}`;
}
if (this.isMobileAppointment) {
const mobileStartTime = get12HourTimeMobileFormat(startTime);
const mobileEndTime = get12HourTimeMobileFormat(endTime);
return `Between ${mobileStartTime} - ${mobileEndTime}`;
return `Your appointment is at ${mobileStartTime} - ${mobileEndTime}`;
}
return null;
},
@ -261,12 +365,12 @@ export default {
},
serviceLocationFullAddress() {
const { address, address2, city, state, zipCode } = this.serviceLocation;
return `${address ?? ''}, ${address2 ? `${address2},` : ''}<br/> ${city ?? ''}, ${state ?? ''} ${zipCode ?? ''}`;
return `${address ?? ''}, ${address2 ? `${address2},` : ''} ${city ?? ''}, ${state ?? ''} ${zipCode ?? ''}`;
},
providerFullAddress() {
const { streetAddress, city, state, zipCode } = this.providerAddress;
return streetAddress
? `${toTitleCase(streetAddress)},<br/> ${toTitleCase(city)}, ${state ?? ''} ${zipCode ?? ''}`
? `${toTitleCase(streetAddress)}, ${toTitleCase(city)}, ${state ?? ''} ${zipCode ?? ''}`
: '';
},
appointmentWordingText() {
@ -317,7 +421,85 @@ export default {
// settleTenderAmount always shows 0 via localhost or dev.
// Temporarily set return true to see cart in localhost or dev environment
return false;
return true;
},
displayWipers() {
const hasWiperPart = this.submittedOrder.lineItems.vaps.some((part) => part.partType.toLowerCase().includes('wiper'));
return hasWiperPart;
},
wipersTitle() {
return this.getCmsContent(
this.widgets.wipersText,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
wipersBody() {
const wiperTypesOnOrder = [];
const hasFrontWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.FRONT_WIPER);
const hasRearWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.REAR_WIPER);
if (hasFrontWiper) {
wiperTypesOnOrder.push(partTypeStrings.FRONT_WIPER);
}
if (hasRearWiper) {
wiperTypesOnOrder.push(partTypeStrings.REAR_WIPER);
}
const wiperDescriptions = wiperTypesOnOrder.map((type) => {
return this.getCmsContentForVapsType(type);
});
return wiperDescriptions;
},
displayRainRepel() {
return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.RAIN_DEFENSE);
},
rainRepelTitle() {
return this.getCmsContent(
this.widgets.rainRepel,
widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
);
},
rainRepelBody() {
return this.getCmsContent(
this.widgets.rainRepel,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
},
displaySMSPhone() {
return this.contactInfo.requestTextUpdates && this.contactInfo.servicePhone;
},
smsTitle() {
return this.getCmsContent(
this.widgets.smsUpdates,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
contactDetailsTitle() {
return this.getCmsContent(
this.widgets.contactDetails,
widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
);
},
contactDetailsBody() {
return this.getCmsContentWithCustomValues(
this.widgets.contactDetails,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
},
cartHeaderText() {
if (this.payment.isPayInAdvance) {
return this.getCmsContent(
this.widgets.orderDetails,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
} else {
return this.getCmsContent(
this.widgets.payAtAppointment,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
}
}
},
mounted() {
@ -411,68 +593,135 @@ export default {
return dateObject.toLocaleDateString('en-us', {
weekday: 'long',
month: 'long',
day: 'numeric'
day: 'numeric',
year: 'numeric'
});
},
setData({ glassTypes }) {
this.glassTypes = glassTypes;
},
getCmsContentForVapsType(vapsPartType) {
const vapsItemDescriptions = this.getCmsContent(
this.widgets.vapsItemDescriptions,
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
);
if (!vapsItemDescriptions) {
return '';
}
const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType);
return vapsTypeDescription?.Text ?? '';
}
}
};
</script>
<style lang="scss" scoped>
.iss-heritage-container-width {
.order-confirmation-container {
position: relative;
min-height: 1px;
padding-left: .9375rem;
padding-right: .9375rem;
.order-confirmation-container {
.appointment-info-container
{
padding: 0 .9375rem;
margin-bottom: 1.875rem;
}
}
.page-container-grouped-styles {
overflow: auto;
}
.text-color--black {
color: $black;
}
.appointment-details {
margin-bottom: 1.5rem;
padding: 1rem 1.5rem 1.5rem;
box-shadow: 0 0.188rem 0.625rem rgb(0 0 0 / 0.2);
border-radius: 0.313rem;
}
.appointment-date-time P {
color: $black;
font-size: $h5-font-size;
line-height: map-get($spacers, 6);
margin-bottom: 0;
+ p {
font-size: map-get($spacers, 4);
line-height: 1.625rem;
font-weight: $font-weight-bold;
.cart-container {
padding: 0 .9375rem;
}
}
.appointment-text {
:deep(p) {
margin: 0;
}
:deep(strong) {
font-weight: $font-weight-bold;
.header {
flex-direction: row;
margin-top: 1.25rem;
margin-bottom: .625rem;
font-size: 1.25rem;
color: $black;
}
}
.email-confirmation-text {
:deep(a) {
font-weight: $font-weight-bold;
text-decoration: underline;
img {
width: 2.25rem;
height: 2.25rem;
margin-right: 1rem;
}
}
:deep(strong) {
font-weight: $font-weight-bold;
.subheader {
display: flex;
flex-direction: column;
margin-top: 1.25rem;
.subheader-text {
margin-bottom: .625rem;
font-size: 1rem;
font-weight: 500;
color: $black;
}
.update-appointment-text {
:deep(.text-primary) {
color: $heritage-blue-primary;
}
:deep(a) {
font-weight: $font-weight-bold;
color: $heritage-blue-primary;
text-decoration: none;
&:hover {
color: $heritage-blue-secondary;
text-decoration: underline;
}
}
}
}
.confirmation-section {
display: flex;
flex-direction: column;
padding: 1.875rem 0;
border-bottom: 1px solid lightgray;
>:first-child {
font-weight: 600;
color: $black;
}
}
.work-order-title {
margin-bottom: .625rem;
}
.appointment-details {
padding-bottom: 2.0625rem;
.appointment-title {
margin-bottom: .625rem;
}
.appointment-location {
margin-top: .625rem;
}
}
.rain-repel-details {
.rain-repel-title {
color: #4d4e53;
}
}
.sms-phone-details {
.sms-phone-title {
color: #4d4e53;
}
}
.contact-details {
.contact-title {
margin-bottom: .625rem;
}
}
.cart-header {
margin-top: 1.25rem;
margin-bottom: .625rem;
color: $black;
font-weight: $font-weight-bold;
}
}
</style>

View file

@ -24,8 +24,7 @@
<cartDropdown
:showAsPaid="false"
:readOnly="false"
:showDropdownHeader="true"
:isInitiallyExpanded="false"
:showAmountInHeader="true"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" />
<hr class="mt-0 mb-5" />

View file

@ -2874,6 +2874,19 @@ export const useMainStore = defineStore({
});
},
async getSV2GlassTypes(glassPieces) {
const glassArray = convertGlassPieceNamingForApi(glassPieces);
const response = await globalMethods.callHttpClient({
method: endpoints.GetSV2GlassTypes.method,
endpoint: endpoints.GetSV2GlassTypes.url,
payload: {
glassPieces: glassArray,
formatWithSpaces: true
}
});
return response.data;
},
hasSubmittedOrder() {
return window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER) !== null;
},
@ -2889,12 +2902,13 @@ export const useMainStore = defineStore({
await this.getCarrierAccountInfo();
const submittedOrder = this.order;
const { experiments } = this.applicationUser;
const { issConfig } = this;
const { issConfig, hasRecalibrationPart } = this;
submittedOrder.isUnverified = this.isUnverified;
submittedOrder.isVerified = this.isVerified;
submittedOrder.submitType = submitType;
submittedOrder.payment.isPayInAdvance = this.isPayInAdvance;
submittedOrder.hasRecalibrationPart = hasRecalibrationPart;
// set to local storage
window.sessionStorage.setItem(webStorageConstants.SUBMITTED_ORDER, JSON.stringify(submittedOrder));