Merge pull request #1097 from Safelite/feature/kroell/INSR-8105

INSR-8105: Afterpay modal on coverage-statement page
This commit is contained in:
katiekroell 2026-02-19 12:37:47 -05:00 committed by GitHub
commit 0834143cdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 231 additions and 1 deletions

View file

@ -0,0 +1,116 @@
<template>
<div class="afterpay-modal-banner" role="alert">
<component
src="https://js.squarecdn.com/square-marketplace.js"
async
:is="'script'">
</component>
<div class="afterpay-content">
<span class="afterpay-copy" v-html="afterpayCopy"></span>
<span class="afterpay-copy">{{ afterpayPaymentAmount }}</span>
<span class="afterpay-copy" v-html="afterpayCopyTwo"></span>
<img :src="imageUrl" class="image" alt="Afterpay logo"/>
<span class="afterpay-copy">. &nbsp;</span>
<a
id="afterpay-learnmore"
href="#"
data-afterpay-modal="en_US"
data-bind="click:afterpayLearnMore"
class="afterpay-learn-more"
@click.prevent>
<span>Learn more</span>
</a>
</div>
</div>
</template>
<script>
import { setupModalLinks } from "@/helpers/cms-content-helper";
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import { useMainStore } from '@/store/index.js';
export default {
name: "afterpay-banner",
components: {
},
props: {
cmsWidgetName: String,
totalServicePrice: Number
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {};
},
computed: {
imageUrl() {
return this.getCmsContent(this.cmsWidgetName, widgetFields.CONTENT_GROUP_WIDGET.IMAGE);
},
afterpayCopy() {
return this.getCmsContent(this.cmsWidgetName, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
},
afterpayCopyTwo() {
return this.getCmsContent(this.cmsWidgetName, widgetFields.CONTENT_GROUP_WIDGET.SUBHEADER_TEXT);
},
deductibleValue() {
return this.mainStore.currentDeductible;
},
afterpayPaymentAmount() {
return this.calculateAfterpayPaymentAmount;
},
isDeductibleScenario() {
return this.mainStore.isVerified && this.mainStore.isDeductible;
},
calculateAfterpayPaymentAmount() {
const totalAmount = this.isDeductibleScenario ? this.deductibleValue : this.totalServicePrice;
const afterpayAmount = (totalAmount / 4).toFixed(2);
return formatAmountInDollars(afterpayAmount);
}
}
};
</script>
<style lang="scss" scoped>
.afterpay-modal-banner {
background-color: $blue-100;
justify-content: center;
align-items: center;
padding: 0.5rem 1rem 0.5rem 1rem;
border-radius: 0.25rem;
margin-top: 20px;
}
.afterpay-content {
display: block;
text-align: center;
}
.afterpay-copy {
font-size: 0.875rem;
color: $blue-700;
font-weight: $font-weight-bold;
line-height: 1.5rem;
}
.image-and-link {
justify-content: center;
align-items: center;
}
.image {
border: 0;
height: 20px;
width: 83px;
}
:deep(a.afterpay-learn-more) {
color: $blue-700;
fill: $blue-700;
text-decoration: underline;
font-size: 0.875rem;
}
</style>

View file

@ -439,6 +439,109 @@ describe('coverageStatement.vue', () => {
expect(result).toBe(expected);
});
});
describe('displayAfterpayBanner', () => {
test('returns true if ITAC', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.ITAC
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeTruthy();
});
test('returns true if No Comp', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.NO_COMP
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeTruthy();
});
test('returns true if deductible is > $0', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
},
damage: {
isRepair: false
},
currentDeductible: {
replace: 500
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeTruthy();
});
test('returns false if deductible is <= $0', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
},
damage: {
isRepair: false
},
currentDeductible: {
replace: 0
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeFalsy();
});
test('returns false if Unverified', () => {
// Arrange
const mainInitialState = {
order: {
insuranceCoverage: {
coverageStatus: coverageStatuses.UNVERIFIED,
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.displayAfterpayBanner;
// Assert
expect(result).toBeFalsy();
});
});
});
describe('methods', () => {
describe('arePagePrerequisitesValid', () => {

View file

@ -70,6 +70,10 @@
class="d-flex justify-content-center cost mb-0 cost-underline">
{{ formatAmountInDollars(totalServicePrice) }}
</div>
<afterpayModalBanner
v-if="displayAfterpayBanner"
cmsWidgetName="AfterpayModalWidget"
:totalServicePrice="totalServicePrice" />
<buttonMain
ref="buttonMain"
class="full-width-button mt-5 mb-5"
@ -118,6 +122,7 @@ import textBlock from '@/digital-components/text-block/text-block.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import afterpayModalBanner from '@/layouts/coverage-statement/afterpay-modal-banner/afterpay-modal-banner.vue';
// Import Supporting Files
import {
fetchCmsContentForPage,
@ -152,7 +157,8 @@ export default {
textBlock,
textLink,
siteFooter,
cancelClaimModal
cancelClaimModal,
afterpayModalBanner
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -339,6 +345,11 @@ export default {
return 'centered-back-button';
}
return '';
},
displayAfterpayBanner() {
return this.isITACQuoteVisible
|| this.isNoCompQuoteVisible
|| (this.showDeductibleOnly && this.deductibleValue > 0);
}
},
watch: {