commit
0359697212
6 changed files with 206 additions and 4 deletions
|
|
@ -4,8 +4,14 @@
|
||||||
:modalId="ModalName"
|
:modalId="ModalName"
|
||||||
:footerButtonText="ModalCloseButtonText"
|
:footerButtonText="ModalCloseButtonText"
|
||||||
@footer-button-event="footerButtonClick">
|
@footer-button-event="footerButtonClick">
|
||||||
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
|
<div v-if="ModalName == 'RecalModal'">
|
||||||
<h5 class="mb-4" v-html="ModalHeadline"></h5>
|
<h5 class="mb-4 recal-header" v-html="ModalHeadline"></h5>
|
||||||
|
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
|
||||||
|
<h5 class="mb-4" v-html="ModalHeadline"></h5>
|
||||||
|
</div>
|
||||||
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
|
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
|
||||||
<p class="mb-0" v-html="ModalBodyText"></p>
|
<p class="mb-0" v-html="ModalBodyText"></p>
|
||||||
<p
|
<p
|
||||||
|
|
@ -78,6 +84,10 @@ export default {
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
.modal-content {
|
.modal-content {
|
||||||
.modal-body {
|
.modal-body {
|
||||||
|
.recal-header {
|
||||||
|
line-height: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.modal-sub-body {
|
.modal-sub-body {
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-h
|
||||||
import { nextTick } from "vue";
|
import { nextTick } from "vue";
|
||||||
import { experimentSettings } from "@/constants/experiments";
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
import { containsLineItemWithPartType } from "../../helpers/service-package-helper";
|
||||||
|
|
||||||
jest.mock("@/store", () => ({
|
jest.mock("@/store", () => ({
|
||||||
commit: jest.fn(),
|
commit: jest.fn(),
|
||||||
|
|
@ -22,6 +23,9 @@ jest.mock("@/helpers/cms-content-helper", () => ({
|
||||||
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
|
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
|
||||||
navigateToHeritageFunnel: jest.fn(),
|
navigateToHeritageFunnel: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock("@/helpers/service-package-helper", () => ({
|
||||||
|
containsLineItemWithPartType: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock("@/helpers/promotions-helper", () => ({
|
jest.mock("@/helpers/promotions-helper", () => ({
|
||||||
revalidatePromosAndValidateQueryStringPromo: jest.fn(() => {
|
revalidatePromosAndValidateQueryStringPromo: jest.fn(() => {
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,19 @@
|
||||||
isRequired />
|
isRequired />
|
||||||
|
|
||||||
<afterpayModalBanner
|
<afterpayModalBanner
|
||||||
v-if="!isInsuranceSelected"
|
v-if="!isInsuranceSelected && !isRecalibrationOnOrder"
|
||||||
cmsWidgetName="AfterpayModalWidget"
|
cmsWidgetName="AfterpayModalWidget"
|
||||||
:activePromos="lineItems.promos"
|
:activePromos="lineItems.promos"
|
||||||
:availableLineItems="availableLineItems"
|
:availableLineItems="availableLineItems"
|
||||||
:lineItems="lineItems" />
|
:lineItems="lineItems" />
|
||||||
|
|
||||||
|
<recalDisclaimer
|
||||||
|
class="my-5"
|
||||||
|
cmsWidgetName="RecalDisclaimerWidget"
|
||||||
|
v-if="isRecalibrationOnOrder"
|
||||||
|
v-on="{ textLinkClicked: openModalAction }"
|
||||||
|
alertClass="alert-info" />
|
||||||
|
|
||||||
<promoModalQuestion
|
<promoModalQuestion
|
||||||
class="small mt-4"
|
class="small mt-4"
|
||||||
v-model="lineItems"
|
v-model="lineItems"
|
||||||
|
|
@ -86,6 +93,7 @@ import textBlock from "@/digital-components/text-block/text-block";
|
||||||
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
||||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||||
import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner";
|
import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner";
|
||||||
|
import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||||
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
|
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
|
||||||
|
|
@ -108,6 +116,8 @@ import { queryStrings } from "@/constants/query-strings";
|
||||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||||
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
|
||||||
import { experimentSettings } from "@/constants/experiments";
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
|
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
|
||||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -256,6 +266,12 @@ export default {
|
||||||
lineItemsCloneForWatcher() {
|
lineItemsCloneForWatcher() {
|
||||||
return Object.assign({}, this.lineItems);
|
return Object.assign({}, this.lineItems);
|
||||||
},
|
},
|
||||||
|
isRecalibrationOnOrder() {
|
||||||
|
return containsLineItemWithPartType(
|
||||||
|
partTypeStrings.RECALIBRATION,
|
||||||
|
this?.availableLineItems
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openModalAction(modalName) {
|
openModalAction(modalName) {
|
||||||
|
|
@ -405,6 +421,7 @@ export default {
|
||||||
loadingModal,
|
loadingModal,
|
||||||
afterpayModalBanner,
|
afterpayModalBanner,
|
||||||
promoModalQuestion,
|
promoModalQuestion,
|
||||||
|
recalDisclaimer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
51
src/layouts/quote/recal-disclaimer/recal-disclaimer.spec.js
Normal file
51
src/layouts/quote/recal-disclaimer/recal-disclaimer.spec.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { mount } from "@vue/test-utils";
|
||||||
|
import recalDisclamer from "./recal-disclaimer";
|
||||||
|
|
||||||
|
describe("recal-disclaimer.vue", () => {
|
||||||
|
it("Should display header text when HeaderText is defined in the CMS", async () => {
|
||||||
|
const wrapper = mount(recalDisclamer, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: "test",
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should insert image url when Image is defined in the CMS", async () => {
|
||||||
|
const wrapper = mount(recalDisclamer, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: "test",
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should display subheader text when SubheaderText is defined in the CMS", async () => {
|
||||||
|
const wrapper = mount(recalDisclamer, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: "test",
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||||
|
return mockCmsContent[cmsFieldName];
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCmsContent = {
|
||||||
|
HeaderText: "Sample header text here.",
|
||||||
|
SubheaderText: "Sample subheader text here.",
|
||||||
|
Image: "https://www.sampleImage.sample",
|
||||||
|
};
|
||||||
115
src/layouts/quote/recal-disclaimer/recal-disclaimer.vue
Normal file
115
src/layouts/quote/recal-disclaimer/recal-disclaimer.vue
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<div class="alert fade show alert-info recal-alert" role="alert">
|
||||||
|
<div class="alert-heading">
|
||||||
|
<span>
|
||||||
|
<img v-if="hasImage" :src="imageUrl" />
|
||||||
|
<span class="px-2"> {{ recalHeaderText }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span v-for="copy in recalBody" :key="copy">
|
||||||
|
<span v-if="doesCopyContainTextLink(copy)" class="recal-link">
|
||||||
|
<textLink
|
||||||
|
linkType="text"
|
||||||
|
:text="getRouterLinkDisplayTextFromCopy(copy)"
|
||||||
|
href="#!"
|
||||||
|
@click-event="$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))"
|
||||||
|
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)" />
|
||||||
|
</span>
|
||||||
|
<span v-else v-html="copy" class="alert-body"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
|
|
||||||
|
// Supporting files
|
||||||
|
import {
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getExternalLink,
|
||||||
|
} from "@/helpers/cms-content-helper";
|
||||||
|
|
||||||
|
function getInlineAltText(token) {
|
||||||
|
const innerTokens = token.split(",");
|
||||||
|
|
||||||
|
return innerTokens[1] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "recal-disclaimer",
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getInlineAltText,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getExternalLink,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
imageUrl() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, "Image");
|
||||||
|
},
|
||||||
|
hasImage() {
|
||||||
|
return !!this.imageUrl;
|
||||||
|
},
|
||||||
|
recalHeaderText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
recalBody() {
|
||||||
|
return splitCopyOnCMSPlaceHolder(
|
||||||
|
this.getCmsContent(this.cmsWidgetName, "SubheaderText")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
textLink,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.alert {
|
||||||
|
text-align: center;
|
||||||
|
&.recal-alert {
|
||||||
|
padding: 16px 18px;
|
||||||
|
}
|
||||||
|
&.alert-info {
|
||||||
|
background-color: #e5f1fa;
|
||||||
|
color: #525656;
|
||||||
|
.alert-heading {
|
||||||
|
color: #0070d1;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 17.3px;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
fill: #0070d1;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& .alert-heading {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.alert-body {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400px;
|
||||||
|
}
|
||||||
|
.recal-link {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -234,10 +234,15 @@ export default {
|
||||||
},
|
},
|
||||||
getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) {
|
getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) {
|
||||||
var lineItemsToPrice = [...this.nullSafeAvailableLineItems];
|
var lineItemsToPrice = [...this.nullSafeAvailableLineItems];
|
||||||
|
if (this.isRecalibrationOnOrder) {
|
||||||
|
lineItemsToPrice = this.nullSafeAvailableLineItems.filter((item) => {
|
||||||
|
return item.partType != partTypeStrings.RECALIBRATION;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//remove service package discount part
|
//remove service package discount part
|
||||||
if (this.isServicePackageDiscountOnOrder) {
|
if (this.isServicePackageDiscountOnOrder) {
|
||||||
lineItemsToPrice = this.nullSafeAvailableLineItems.filter((item) => {
|
lineItemsToPrice = lineItemsToPrice.filter((item) => {
|
||||||
return item.partType != this.servicePackageDiscountParts[0].partType;
|
return item.partType != this.servicePackageDiscountParts[0].partType;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue