INSR-7759: Some payment-method parity updates

- Payment method question button now matches Heritage
- Recalibration is now its own line item
- Some other general updates to structure and formatting of page
This commit is contained in:
Alex Humphries 2026-02-16 17:11:56 -05:00
parent 9bc40980d8
commit c821fc7d83
8 changed files with 174 additions and 137 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -60,10 +60,11 @@ export const pageProgressMapper = {
percent: 85
},
'payment-method': {
percent: 90
percent: 95
},
'payment-page': {
percent: 95
// No progress bar on this page
percent: 0
},
'tpa-search': {
percent: 80

View file

@ -142,3 +142,18 @@ export function getSalesTax(order) {
export function getCartTotal(order) {
return Math.round((getSubtotal(order) + getSalesTax(order)) * 100) / 100;
}
/**
* Returns the total price of all recal child parts on the order
* @param {object} order order object
* @returns {number} total price of all recal child parts on the order
*/
export function getRecalibrationTotal(order) {
const itemsWithRecalibration = getServiceLineItems(order)
.filter((item) => item.requiresRecalibration);
const recalibrationLineItems = itemsWithRecalibration.map((item) => {
return item.childParts?.find((child) => child.partType === partTypeStrings.RECALIBRATION || child.partType === partTypeStrings.ADAS_RECALIBRATION) ?? {};
});
return getPriceOfLineItems(recalibrationLineItems);
}

View file

@ -1,20 +1,30 @@
<template>
<div class="cart-table">
<div
v-if="!isDeductibleOnly"
class="cart-table">
<div class="cart-item-list">
<div
id="cart-deductible-or-base-price"
v-if="showDeductibleCartItem"
class="cart-item">
<div class="price-row">
<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>
<span id="deductible-label">{{ deductibleLabel }}</span>
<span id="deductible-value">{{ getDisplayed(deductible) }}</span>
</div>
</div>
<div
v-else
class="cart-item">
<div class="price-row">
<span id="base-price-label">{{ basePriceLabel }}</span>
<span id="base-price-value">{{ getDisplayed(servicePrice) }}</span>
</div>
</div>
<div
v-if="showRecalibrationCartItem"
class="cart-item">
<div class="price-row">
<span id="recalibration-label">{{ recalibrationLabel }}</span>
<span id="recalibration-value">{{ getDisplayed(recalibrationPrice) }}</span>
</div>
</div>
@ -24,16 +34,7 @@
:key="i"
class="cart-item non-packaged">
<div class="price-row">
<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>{{ item?.name ?? '' }}</span>
<span>{{ formatAmountInDollars(item?.subTotal ?? 0) }}</span>
</div>
<textLink
@ -103,11 +104,10 @@
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
</div>
</div>
<contentGroupModal
:ref="RECYCLING_MODAL_REF_NAME"
:cmsWidgetName="recyclingModalCmsWidgetName">
<textBlock cmsWidgetName="RecycleTextBlock" />
</contentGroupModal>
</div>
<div v-else class="deductible-box">
<span class="deductible-label">{{ deductibleLabel }}</span>
<span class="deductible-value">{{ getDisplayed(deductible) }}</span>
</div>
</template>
@ -128,6 +128,7 @@ import {
getCartTotal,
getDeductible,
getLineItems, getMobileFeeLineItem,
getRecalibrationTotal,
getRecycleFeeLineItem, getSalesTax,
getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp,
isOrderUnverified
@ -136,7 +137,6 @@ import { getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculat
import { processIfStatements } from '@/helpers/cms-content-helper';
const VERIFYING_COVERAGE = 'Verifying coverage';
const RECYCLING_MODAL_REF_NAME = 'RecycleModal';
export default {
name: 'confirmation-cart',
@ -148,7 +148,6 @@ export default {
props: {
readOnly: Boolean,
showAsPaid: Boolean,
recyclingModalCmsWidgetName: String,
submittedOrder: Object
},
data() {
@ -158,6 +157,7 @@ export default {
amountPaid: 'AmountPaidTextWidget',
deductible: 'DeductibleWidget',
basePrice: 'BasePriceWidget',
recalibration: 'RecalibrationWidget',
subtotal: 'SubtotalWidget',
salesTax: 'SalesTaxWidget',
recycleFee: 'RecycleFeeWidget',
@ -167,8 +167,7 @@ export default {
warrantyText: 'WarrantyCartItemTextWidget',
guaranteeText: 'GuaranteeCartItemTextWidget'
},
cartItemType,
RECYCLING_MODAL_REF_NAME
cartItemType
};
},
computed: {
@ -181,9 +180,18 @@ export default {
deductible() {
return getDeductible(this.cartOrder);
},
isDeductibleOnly() {
return this.deductible > 0 && this.subTotal === this.deductible;
},
showDeductibleCartItem() {
return this.isUnverified || (!this.isNoComp && !this.isITAC);
},
showRecalibrationCartItem() {
return this.recalibrationPrice > 0;
},
recalibrationPrice() {
return getRecalibrationTotal(this.cartOrder);
},
lineItems() {
return getLineItems(this.cartOrder);
},
@ -191,7 +199,8 @@ export default {
return getRecycleFeeLineItem(this.cartOrder);
},
servicePrice() {
return getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0;
const price = getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0;
return price - this.recalibrationPrice;
},
isUnverified() {
return isOrderUnverified(this.cartOrder);
@ -327,6 +336,9 @@ export default {
this.getCustomValueFromString
);
},
recalibrationLabel() {
return this.getCmsContent(this.widget.recalibration, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
subtotalLabel() {
return this.getCmsContent(this.widget.subtotal, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
@ -452,8 +464,6 @@ export default {
<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;
@ -507,23 +517,20 @@ $RECYCLING_MODAL_REF_NAME: 'RecycleModal';
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;
.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;
}
}
</style>

View file

@ -2,32 +2,30 @@
<baseInputButton
v-bind="$props"
v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2">
<div class="button-content list-button-content d-flex flex-row py-3 px-4">
buttonWrapperClasses="list-group base-input-button list-button d-flex flex-column w-100 no-hover">
<div class="button-content list-button-content d-flex flex-row">
<div>
<span
v-for="token in tokens"
:key="token">
<span v-if="isInlineImageToken(token)">
<img
v-if="hasImage"
:src="buttonImage"
:alt="getInlineAltText(token)" />
<span v-else> {{ getInlineAltText(token) }}</span>
</span>
<span v-else>
{{ token }}
</span>
</span>
</div>
<img
v-if="shouldDisplaySideImage"
:id="buttonImageId"
class="ms-auto order-3"
class="ms-auto"
:src="buttonImage"
:alt="altText" />
<div class="order-2">
<p class="m-0">
<span
v-for="token in tokens"
:key="token">
<span v-if="isInlineImageToken(token)">
<img
v-if="hasImage"
:src="buttonImage"
:alt="getInlineAltText(token)" />
<span v-else> {{ getInlineAltText(token) }}</span>
</span>
<span v-else>
{{ token }}
</span>
</span>
</p>
</div>
</div>
</baseInputButton>
</template>
@ -78,48 +76,26 @@ export default {
outline: none;
input[type="radio"],
input[type="checkbox"] {
position: static; //override bootstrap
&:focus-visible + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content {
background: $background-color-selected;
border-color: $heritage-blue-primary;
font-weight: 600;
color: $black;
font-weight: 500;
background: $blue-100;
box-shadow: 0 0 0 1px $blue;
}
&:checked:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content p,
&:checked + .list-button-content span {
font-weight: 500;
}
&:checked + .list-button-content span:nth-child(2) {
font-weight: 400;
color: $gray-600;
}
}
}
.list-button-content {
color: $gray-600;
color: $darker-gray;
font-weight: 400;
position: relative;
background: $white;
transition: all 150ms linear;
border-radius: $border-radius-lg;
border: 1px solid $gray-500;
border-radius: 3.75rem;
border: 1px solid #CACBCC;
width: 100%;
outline: none;
span {
&.small {
font-size: $font-size-xsm;
color: $gray-550;
}
}
padding: .625rem 2rem;
height: 2.875rem;
margin-bottom: .75rem;
}
</style>

View file

@ -19,6 +19,7 @@
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import widgetFields from '@/constants/cms-widget-fields.js';
import paymentMethodListButton from './payment-method-list-button/payment-method-list-button.vue';
import { markRaw } from 'vue';
export default {
name: 'payment-method-question',
@ -33,7 +34,7 @@ export default {
emits: ['update:modelValue'],
data() {
return {
paymentMethodListButton
paymentMethodListButton: markRaw(paymentMethodListButton)
};
},
computed: {

View file

@ -12,21 +12,24 @@
</div>
<div class="iss-heritage-container-width">
<div class="payment-method-container iss-heritage-content-container-width">
<alert
class=""
alertClass="alert-warning"
cmsWidgetName="AlertIncompleteWidget" />
<siteSubHeader
class="mb-5 mt-4 px-3"
cmsWidgetName="SiteSubHeaderWidget"
secondaryTextClasses="text-center"
subHeaderClasses="mt-4" />
class="subheader"
cmsWidgetName="SiteSubHeaderWidget" />
<div class="main-content-container">
<hr class="my-0" />
<hr class="section-divider" />
<reviewDropdown ref="reviewDropdown" />
<hr class="my-0" />
<hr class="section-divider" />
<textBlock
cmsWidgetName="CartHeaderWidget" />
<cartDropdown
:showAsPaid="false"
:readOnly="false"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" />
<hr class="mt-0 mb-5" />
<alert
v-if="displayPayInAdvanceAlert"
name="payInAdvanceErrorAlert"
@ -34,6 +37,9 @@
cmsWidgetName="PayInAdvanceErrorAlertWidget"
alertClass="alert-danger"
:isDismissible="false" />
<hr
v-if="!isPayInAdvanceDisabled"
class="pia-divider" />
<paymentMethodQuestion
v-if="!isPayInAdvanceDisabled"
v-model="paymentMethod"
@ -69,6 +75,7 @@ import reviewDropdown from '@/layouts/payment-method/review-dropdown/review-drop
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';
// Supporting Items
import settleAllPromises from '@/helpers/layout-helper';
@ -97,7 +104,8 @@ export default {
reviewDropdown,
cartDropdown,
paymentMethodQuestion,
alert
alert,
textBlock
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -137,9 +145,8 @@ export default {
customCallToActionButtonCopy() {
switch (this.paymentMethod) {
case paymentMethods.PayNow:
return 'Continue to checkout';
case paymentMethods.AFTERPAY:
return 'Continue to Afterpay';
return 'Continue to checkout';
default:
return null;
}
@ -304,20 +311,49 @@ export default {
<style lang="scss" scoped>
$page-side-padding: 1.5rem;
.iss-heritage-container-width {
#app .iss-heritage-container-width {
.iss-heritage-content-container-width {
@include media-breakpoint-up(md) {
width: 58.33333333%;
}
}
.payment-method-container {
position: relative;
min-height: 1px;
padding-left: .9375rem;
padding-right: .9375rem;
.alert-warning {
margin-top: 1.25rem;
margin-bottom: 0rem;
}
.subheader {
margin-top: 1.25rem;
:deep(strong) {
color: $black;
font-weight: 600;
}
:deep(.subheader-secondary) {
margin-top: .625rem;
p {
margin-bottom: 0;
}
}
}
.section-divider {
margin-top: 1.5rem;
margin-bottom: 1.875rem;
}
.pia-divider {
margin-top: .5rem;
margin-bottom: .75rem;
}
}
}
.main-content-container {
padding: 0;
}
.page-container-grouped-styles {
overflow: auto;
}
</style>

View file

@ -1,7 +1,7 @@
<template>
<div :class="[isExpanded ? 'pb-3' : 'pb-4']">
<div>
<div
class="row review-toggle flex align-items-center pt-4"
class="row review-toggle flex align-items-center"
:class="[isExpanded ? 'expanded' : '']"
@click="toggleIsExpanded">
<a
@ -15,12 +15,12 @@
<vehicleReview
cmsWidgetName="VehicleReviewWidget"
:vehicle="vehicleInfo" />
<hr class="my-0" />
<hr class="review-divider" />
<damageReview
cmsWidgetName="DamageReviewWidget"
damageLocationsWidgetName="DamageLocationsWidget"
:damage="damageInfo" />
<hr class="my-0" />
<hr class="review-divider" />
<servicePackageReview
ref="servicePackageReview"
servicePackageOptionsCmsName="ServicePackageTitle"
@ -28,15 +28,15 @@
vapsItemsCmsName="VapsItemDescriptions"
:damage="damageInfo"
:lineItems="lineItems" />
<hr class="my-0" />
<hr class="review-divider" />
<serviceLocationReview
cmsWidgetName="ServiceLocationTitleWidget"
:serviceLocation="serviceLocationInfo" />
<hr class="my-0" />
<hr class="review-divider" />
<scheduleReview
cmsWidgetName="ScheduleWidget"
:appointmentType="appointmentType" />
<hr class="my-0" />
<hr class="review-divider" />
<customerReview
cmsWidgetName="CustomerReviewWidget"
:customer="customerInfo" />
@ -121,12 +121,13 @@ export default {
.review-toggle {
&:after {
content: "";
transition: all 0.5s ease;
background-image: url($svg-payment-method-review-toggle);
transition: transform none;
background-image: url(~@/assets/img/icons/chevron-circle_review_dropdown.png);
background-repeat: no-repeat;
background-position: right center;
width: 16px;
height: 9px;
background-size: 1.25rem 1.25rem;
width: 1.25rem;
height: 1.25rem;
display: inline-flex;
position: relative;
right: 0.75rem;