Merge pull request #3181 from Safelite/feature/CASH-613

Visual build of coverage-statement
This commit is contained in:
chloeherdsafelite 2026-05-19 10:04:34 -04:00 committed by GitHub
commit bb396078c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 235 additions and 4 deletions

View file

@ -62,6 +62,28 @@ function setupMocks() {
navigateWithSaving: jest.fn(), navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(), navigateWithoutSaving: jest.fn(),
}, },
store: {
getters: {
order: {
policy: {
currentDeductible: 1000,
},
lineItems: {
glassParts: [],
promos: [],
supportingItems: [],
vaps: [],
},
},
},
},
mixins: [
{
methods: {
getTotalPriceOfAllLineItemsAndChildParts: jest.fn().mockReturnValue(250),
},
},
],
}); });
const wrapper = shallowMount(coverageStatement, mountOptions); const wrapper = shallowMount(coverageStatement, mountOptions);

View file

@ -4,10 +4,41 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-md-10 col-lg-8 col-xl-7"> <div class="col-12 col-md-10 col-lg-8 col-xl-7">
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" /> <div class="my-5" ref="unverified-block">
<!-- <funnelSubHeader cmsWidgetName="SubHeaderWidgetUnverified" alignLeft />
... Page code goes here. <customerInstructions
--> cmsWidgetName="InstructionsWidgetUnverified"
v-on="{ textLinkClicked: openModalAction }" />
</div>
<div class="my-5" ref="verified-block">
<funnelSubHeader cmsWidgetName="SubHeaderWidgetVerified" alignLeft />
<priceDisplay
cmsWidgetName="PriceWidgetVerified"
:price="deductibleAmount" />
<customerInstructions
cmsWidgetName="InstructionsWidgetVerified"
v-on="{ textLinkClicked: openModalAction }" />
<contentGroupModal ref="OemModal" cmsWidgetName="OemModalWidget" />
<afterpayBreakout
cmsWidgetName="AfterpayBreakoutWidget"
:totalAmount="cashPrice" />
</div>
<div class="my-5" ref="itac-block">
<funnelSubHeader
class="mb-4"
cmsWidgetName="SubHeaderWidgetItac"
alignLeft />
<div class="price-compare">
<priceDisplay
cmsWidgetName="DeductibleWidgetItac"
:price="deductibleAmount" />
<priceDisplay cmsWidgetName="PriceWidgetItac" :price="cashPrice" />
</div>
</div>
<saveProgressModalQuestion
modalWidgetName="SaveProgressModalWidget"
modalName="SaveProgressModal"
pageName="coverage-statement" />
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
ref="navbar" ref="navbar"
@ -28,6 +59,12 @@ import { Form } from "vee-validate";
import store from "@/store"; import store from "@/store";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import customerInstructions from "./customer-instructions/customer-instructions.vue";
import priceDisplay from "./price-display/price-display.vue";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question.vue";
import afterpayBreakout from "../payment-method/afterpay-breakout/afterpay-breakout.vue";
import modal from "@/digital-components/modal/modal";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal.vue";
export default { export default {
name: "coverage-statement", name: "coverage-statement",
@ -43,6 +80,11 @@ export default {
funnelHeader, funnelHeader,
navbar, navbar,
funnelSubHeader, funnelSubHeader,
customerInstructions,
priceDisplay,
saveProgressModalQuestion,
afterpayBreakout,
contentGroupModal,
}, },
// Ensure CMS content is fetched during route navigation without depending on `to`/`from` // Ensure CMS content is fetched during route navigation without depending on `to`/`from`
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -98,6 +140,38 @@ export default {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return true; return true;
}, },
openModalAction(modalName) {
this.$refs[modalName]?.openModal();
},
},
computed: {
deductibleAmount() {
return this.$store.getters.order.policy.currentDeductible;
},
cashPrice() {
const lineItems = this.$store.getters.order.lineItems;
const lineItemsFlattened = [
...(lineItems?.glassParts ?? []),
...(lineItems?.promos ?? []),
...(lineItems?.supportingItems ?? []),
...(lineItems?.vaps ?? []),
];
const price = this.getTotalPriceOfAllLineItemsAndChildParts(lineItemsFlattened, false);
return price;
},
}, },
}; };
</script> </script>
<style lang="scss">
.price-compare {
display: flex;
> :not(:last-child) {
padding-right: 1em;
margin-right: 1em;
border-right: 2px solid $gray-300;
}
}
</style>

View file

@ -0,0 +1,82 @@
<template>
<div>
<div>
<strong>
{{ headerText }}
</strong>
</div>
<div>
<ol>
<li v-for="block in instructionBlocks" :key="block">
<span v-for="token in block" :key="token">
<span v-if="doesCopyContainTextLink(token)" class="link">
<textLink
linkType="text"
:text="getRouterLinkDisplayTextFromCopy(token)"
href="#!"
@click-event="
$emit('textLinkClicked', getRouterLinkRouteFromCopy(token))
"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(token)" />
</span>
<span v-else v-html="token" class="copy"></span>
</span>
</li>
</ol>
</div>
</div>
</template>
<script>
import textLink from "@/ux-components/text-link/text-link";
import {
doesCopyContainTextLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy,
getExternalLink,
} from "@/helpers/cms-content-helper";
export default {
name: "customer-instructions",
data() {
return {};
},
props: {
cmsWidgetName: String,
},
methods: {
doesCopyContainTextLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy,
getExternalLink,
},
computed: {
headerText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
},
instructionBlocks() {
const answers = this.getCmsContent(this.cmsWidgetName, "Answers");
const instructionBlocksRaw = answers.map((ans) => ans.Text);
const instructionBlocksAsTokens = instructionBlocksRaw.map((raw) =>
splitCopyOnCMSPlaceHolder(raw)
);
return instructionBlocksAsTokens;
},
},
components: {
textLink,
},
};
</script>
<style lang="scss" scoped>
:deep(li) {
margin-top: 0.5em;
}
</style>

View file

@ -0,0 +1,53 @@
<template>
<div class="mb-4">
<div>
<strong>
{{ headerText }}
</strong>
</div>
<div class="price" :class="markAsHigher ? 'red' : 'green'">
{{ priceText }}
</div>
</div>
</template>
<script>
export default {
name: "price-display",
data() {
return {};
},
props: {
cmsWidgetName: String,
price: Number,
markAsHigher: Boolean,
},
methods: {},
computed: {
headerText() {
return this.getCmsContent(this.cmsWidgetName, "Text");
},
priceText() {
const toFixed = this.price.toFixed(2);
return `$${toFixed}`;
},
},
components: {},
};
</script>
<style lang="scss">
.price {
font-size: 1.5em;
display: inline-block;
&.green {
color: $green-700;
border-bottom: 3px solid $red-800;
}
&.red {
color: $red-700;
border-bottom: 3px solid $red-800;
}
}
</style>