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 percent: 85
}, },
'payment-method': { 'payment-method': {
percent: 90 percent: 95
}, },
'payment-page': { 'payment-page': {
percent: 95 // No progress bar on this page
percent: 0
}, },
'tpa-search': { 'tpa-search': {
percent: 80 percent: 80

View file

@ -142,3 +142,18 @@ export function getSalesTax(order) {
export function getCartTotal(order) { export function getCartTotal(order) {
return Math.round((getSubtotal(order) + getSalesTax(order)) * 100) / 100; 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> <template>
<div class="cart-table"> <div
v-if="!isDeductibleOnly"
class="cart-table">
<div class="cart-item-list"> <div class="cart-item-list">
<div <div
id="cart-deductible-or-base-price" v-if="showDeductibleCartItem"
class="cart-item"> class="cart-item">
<div class="price-row"> <div class="price-row">
<template <span id="deductible-label">{{ deductibleLabel }}</span>
v-if="showDeductibleCartItem"> <span id="deductible-value">{{ getDisplayed(deductible) }}</span>
<span id="deductible-label">{{ deductibleLabel }}</span> </div>
<span id="deductible-value">{{ getDisplayed(deductible) }}</span> </div>
</template> <div
<template v-else
v-else> class="cart-item">
<span id="base-price-label">{{ basePriceLabel }}</span> <div class="price-row">
<span id="base-price-value">{{ getDisplayed(servicePrice) }}</span> <span id="base-price-label">{{ basePriceLabel }}</span>
</template> <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>
</div> </div>
@ -24,16 +34,7 @@
:key="i" :key="i"
class="cart-item non-packaged"> class="cart-item non-packaged">
<div class="price-row"> <div class="price-row">
<span <span>{{ item?.name ?? '' }}</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> <span>{{ formatAmountInDollars(item?.subTotal ?? 0) }}</span>
</div> </div>
<textLink <textLink
@ -103,11 +104,10 @@
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span> <span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
</div> </div>
</div> </div>
<contentGroupModal </div>
:ref="RECYCLING_MODAL_REF_NAME" <div v-else class="deductible-box">
:cmsWidgetName="recyclingModalCmsWidgetName"> <span class="deductible-label">{{ deductibleLabel }}</span>
<textBlock cmsWidgetName="RecycleTextBlock" /> <span class="deductible-value">{{ getDisplayed(deductible) }}</span>
</contentGroupModal>
</div> </div>
</template> </template>
@ -128,6 +128,7 @@ import {
getCartTotal, getCartTotal,
getDeductible, getDeductible,
getLineItems, getMobileFeeLineItem, getLineItems, getMobileFeeLineItem,
getRecalibrationTotal,
getRecycleFeeLineItem, getSalesTax, getRecycleFeeLineItem, getSalesTax,
getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp, getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp,
isOrderUnverified isOrderUnverified
@ -136,7 +137,6 @@ import { getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculat
import { processIfStatements } from '@/helpers/cms-content-helper'; import { processIfStatements } from '@/helpers/cms-content-helper';
const VERIFYING_COVERAGE = 'Verifying coverage'; const VERIFYING_COVERAGE = 'Verifying coverage';
const RECYCLING_MODAL_REF_NAME = 'RecycleModal';
export default { export default {
name: 'confirmation-cart', name: 'confirmation-cart',
@ -148,7 +148,6 @@ export default {
props: { props: {
readOnly: Boolean, readOnly: Boolean,
showAsPaid: Boolean, showAsPaid: Boolean,
recyclingModalCmsWidgetName: String,
submittedOrder: Object submittedOrder: Object
}, },
data() { data() {
@ -158,6 +157,7 @@ export default {
amountPaid: 'AmountPaidTextWidget', amountPaid: 'AmountPaidTextWidget',
deductible: 'DeductibleWidget', deductible: 'DeductibleWidget',
basePrice: 'BasePriceWidget', basePrice: 'BasePriceWidget',
recalibration: 'RecalibrationWidget',
subtotal: 'SubtotalWidget', subtotal: 'SubtotalWidget',
salesTax: 'SalesTaxWidget', salesTax: 'SalesTaxWidget',
recycleFee: 'RecycleFeeWidget', recycleFee: 'RecycleFeeWidget',
@ -167,8 +167,7 @@ export default {
warrantyText: 'WarrantyCartItemTextWidget', warrantyText: 'WarrantyCartItemTextWidget',
guaranteeText: 'GuaranteeCartItemTextWidget' guaranteeText: 'GuaranteeCartItemTextWidget'
}, },
cartItemType, cartItemType
RECYCLING_MODAL_REF_NAME
}; };
}, },
computed: { computed: {
@ -181,9 +180,18 @@ export default {
deductible() { deductible() {
return getDeductible(this.cartOrder); return getDeductible(this.cartOrder);
}, },
isDeductibleOnly() {
return this.deductible > 0 && this.subTotal === this.deductible;
},
showDeductibleCartItem() { showDeductibleCartItem() {
return this.isUnverified || (!this.isNoComp && !this.isITAC); return this.isUnverified || (!this.isNoComp && !this.isITAC);
}, },
showRecalibrationCartItem() {
return this.recalibrationPrice > 0;
},
recalibrationPrice() {
return getRecalibrationTotal(this.cartOrder);
},
lineItems() { lineItems() {
return getLineItems(this.cartOrder); return getLineItems(this.cartOrder);
}, },
@ -191,7 +199,8 @@ export default {
return getRecycleFeeLineItem(this.cartOrder); return getRecycleFeeLineItem(this.cartOrder);
}, },
servicePrice() { servicePrice() {
return getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0; const price = getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0;
return price - this.recalibrationPrice;
}, },
isUnverified() { isUnverified() {
return isOrderUnverified(this.cartOrder); return isOrderUnverified(this.cartOrder);
@ -327,6 +336,9 @@ export default {
this.getCustomValueFromString this.getCustomValueFromString
); );
}, },
recalibrationLabel() {
return this.getCmsContent(this.widget.recalibration, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
subtotalLabel() { subtotalLabel() {
return this.getCmsContent(this.widget.subtotal, widgetFields.TEXT_BLOCK_WIDGET.TEXT); return this.getCmsContent(this.widget.subtotal, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
}, },
@ -452,8 +464,6 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@/styles/ux-variables-svg-strings.scss"; @import "@/styles/ux-variables-svg-strings.scss";
$RECYCLING_MODAL_REF_NAME: 'RecycleModal';
.cart-table { .cart-table {
max-height: 50rem; max-height: 50rem;
transition: all 150ms ease-in; transition: all 150ms ease-in;
@ -507,23 +517,20 @@ $RECYCLING_MODAL_REF_NAME: 'RecycleModal';
background-color: $green; background-color: $green;
padding: .5rem 1rem; padding: .5rem 1rem;
} }
}
:deep(.modal-content a.external-text) { .deductible-box {
text-underline-offset: 0.25rem; display: flex;
line-height: 2; align-items: center;
padding: 0 0 0.25rem 0; gap: .25rem;
font-weight: 500; }
max-width: -webkit-fit-content; .deductible-label {
max-width: -moz-fit-content; color: $black;
max-width: fit-content; font-weight: 600;
} }
.deductible-value {
:deep(##{$RECYCLING_MODAL_REF_NAME} h5) { color: $green;
text-align: center; font-weight: 500;
} font-size: 1.625rem;
}
:deep(#recycle-fee-label a){
line-height: initial;
} }
</style> </style>

View file

@ -2,32 +2,30 @@
<baseInputButton <baseInputButton
v-bind="$props" v-bind="$props"
v-model="selectedValue" v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2"> 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 py-3 px-4"> <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 <img
v-if="shouldDisplaySideImage" v-if="shouldDisplaySideImage"
:id="buttonImageId" :id="buttonImageId"
class="ms-auto order-3" class="ms-auto"
:src="buttonImage" :src="buttonImage"
:alt="altText" /> :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> </div>
</baseInputButton> </baseInputButton>
</template> </template>
@ -78,48 +76,26 @@ export default {
outline: none; outline: none;
input[type="radio"], input[type="radio"],
input[type="checkbox"] { 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 { &:checked + .list-button-content {
background: $background-color-selected;
border-color: $heritage-blue-primary;
font-weight: 600;
color: $black; 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 { .list-button-content {
color: $gray-600; color: $darker-gray;
font-weight: 400;
position: relative; position: relative;
background: $white; background: $white;
transition: all 150ms linear; transition: all 150ms linear;
border-radius: $border-radius-lg; border-radius: 3.75rem;
border: 1px solid $gray-500; border: 1px solid #CACBCC;
width: 100%; width: 100%;
outline: none; outline: none;
padding: .625rem 2rem;
span { height: 2.875rem;
&.small { margin-bottom: .75rem;
font-size: $font-size-xsm;
color: $gray-550;
}
}
} }
</style> </style>

View file

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

View file

@ -12,21 +12,24 @@
</div> </div>
<div class="iss-heritage-container-width"> <div class="iss-heritage-container-width">
<div class="payment-method-container iss-heritage-content-container-width"> <div class="payment-method-container iss-heritage-content-container-width">
<alert
class=""
alertClass="alert-warning"
cmsWidgetName="AlertIncompleteWidget" />
<siteSubHeader <siteSubHeader
class="mb-5 mt-4 px-3" class="subheader"
cmsWidgetName="SiteSubHeaderWidget" cmsWidgetName="SiteSubHeaderWidget" />
secondaryTextClasses="text-center"
subHeaderClasses="mt-4" />
<div class="main-content-container"> <div class="main-content-container">
<hr class="my-0" /> <hr class="section-divider" />
<reviewDropdown ref="reviewDropdown" /> <reviewDropdown ref="reviewDropdown" />
<hr class="my-0" /> <hr class="section-divider" />
<textBlock
cmsWidgetName="CartHeaderWidget" />
<cartDropdown <cartDropdown
:showAsPaid="false" :showAsPaid="false"
:readOnly="false" :readOnly="false"
recyclingModalCmsWidgetName="RecycleModal" recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" /> servicePackageTitleWidgetName="ServicePackageTitle" />
<hr class="mt-0 mb-5" />
<alert <alert
v-if="displayPayInAdvanceAlert" v-if="displayPayInAdvanceAlert"
name="payInAdvanceErrorAlert" name="payInAdvanceErrorAlert"
@ -34,6 +37,9 @@
cmsWidgetName="PayInAdvanceErrorAlertWidget" cmsWidgetName="PayInAdvanceErrorAlertWidget"
alertClass="alert-danger" alertClass="alert-danger"
:isDismissible="false" /> :isDismissible="false" />
<hr
v-if="!isPayInAdvanceDisabled"
class="pia-divider" />
<paymentMethodQuestion <paymentMethodQuestion
v-if="!isPayInAdvanceDisabled" v-if="!isPayInAdvanceDisabled"
v-model="paymentMethod" 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 cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
import paymentMethodQuestion from '@/layouts/payment-method/payment-method-question/payment-method-question.vue'; import paymentMethodQuestion from '@/layouts/payment-method/payment-method-question/payment-method-question.vue';
import alert from '@/ux-components/alert/alert.vue'; import alert from '@/ux-components/alert/alert.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
// Supporting Items // Supporting Items
import settleAllPromises from '@/helpers/layout-helper'; import settleAllPromises from '@/helpers/layout-helper';
@ -97,7 +104,8 @@ export default {
reviewDropdown, reviewDropdown,
cartDropdown, cartDropdown,
paymentMethodQuestion, paymentMethodQuestion,
alert alert,
textBlock
}, },
mixins: [baseFormMixin], mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -137,9 +145,8 @@ export default {
customCallToActionButtonCopy() { customCallToActionButtonCopy() {
switch (this.paymentMethod) { switch (this.paymentMethod) {
case paymentMethods.PayNow: case paymentMethods.PayNow:
return 'Continue to checkout';
case paymentMethods.AFTERPAY: case paymentMethods.AFTERPAY:
return 'Continue to Afterpay'; return 'Continue to checkout';
default: default:
return null; return null;
} }
@ -304,20 +311,49 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
$page-side-padding: 1.5rem; $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 { .payment-method-container {
position: relative; position: relative;
min-height: 1px; min-height: 1px;
padding-left: .9375rem; padding-left: .9375rem;
padding-right: .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> </style>

View file

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