CSR-731 | Final copy w/o tests

This commit is contained in:
Scott Kiener 2022-10-24 11:26:42 -04:00
parent 65cf2d3b84
commit 5e3abe4ebf
8 changed files with 235 additions and 231 deletions

View file

@ -40,6 +40,9 @@
:is="buttonType" :is="buttonType"
:buttonLabel="answer.buttonLabel" :buttonLabel="answer.buttonLabel"
:buttonLabelSubCopy="answer.buttonLabelSubCopy" :buttonLabelSubCopy="answer.buttonLabelSubCopy"
:buttonBodyCopy="answer.buttonBodyCopy"
:buttonAuxillaryCopy="answer.buttonAuxillaryCopy"
:buttonFooterCopy="answer.buttonFooterCopy"
:buttonImage="answer.buttonImage" :buttonImage="answer.buttonImage"
:buttonImageId="answer.buttonImageId" :buttonImageId="answer.buttonImageId"
:groupName="answer.groupName" :groupName="answer.groupName"
@ -49,7 +52,7 @@
:isWide="isWide" :isWide="isWide"
:validationRules="validationRules" :validationRules="validationRules"
:textPosition="textPosition" :textPosition="textPosition"
:additionalData="additionalData" /> :additionalData="additionalData"
:lastValuePushedToGa="lastValuePushedToGa" :lastValuePushedToGa="lastValuePushedToGa"
:setLastValuePushedToGa="setLastValuePushedToGa" :setLastValuePushedToGa="setLastValuePushedToGa"
v-model="selectedValues" v-model="selectedValues"
@ -187,6 +190,9 @@ export default {
answer.altText ?? (answer.Name ? answer.Name : answer), answer.altText ?? (answer.Name ? answer.Name : answer),
buttonLabelSubCopy: buttonLabelSubCopy:
answer.buttonLabelSubCopy ?? answer.SubText, answer.buttonLabelSubCopy ?? answer.SubText,
buttonBodyCopy: answer.buttonBodyCopy ?? answer.buttonBodyCopy,
buttonAuxillaryCopy: answer.buttonAuxillaryCopy ?? answer.buttonAuxillaryCopy,
buttonFooterCopy: answer.buttonFooterCopy ?? answer.buttonFooterCopy,
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl, buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
buttonImageId: answer.buttonImageId ?? answer.ImageId, buttonImageId: answer.buttonImageId ?? answer.ImageId,
groupName: this.formatString(this.groupName), groupName: this.formatString(this.groupName),

View file

@ -40,7 +40,6 @@ export function fetchCmsContentForPage(fmgPage) {
// Function to convert a string, into a matching global state item. // Function to convert a string, into a matching global state item.
function mapStringToState(str) { function mapStringToState(str) {
// Pull all matches out of the string. // Pull all matches out of the string.
//const regexExp = new RegExp("{(.*?):(.*?)}", "g");
const regexExp = new RegExp("{([^{}]*?):([^{}]*?)}", "g"); const regexExp = new RegExp("{([^{}]*?):([^{}]*?)}", "g");
const regexMatches = [...str.matchAll(regexExp)]; const regexMatches = [...str.matchAll(regexExp)];
const globalStateMatches = regexMatches.filter(match => { const globalStateMatches = regexMatches.filter(match => {
@ -147,32 +146,38 @@ export function processIfStatements(str, ifConditionKeyword, replacePlaceholderC
} else { } else {
const ifStatementRegexExpression = getIfStatementRegexExpression(); const ifStatementRegexExpression = getIfStatementRegexExpression();
const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)]; const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
const completeIfStatementArray = getFirstNonNestedIfStatement(ifStatementRegexMatches, ifConditionKeyword); const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(ifStatementRegexMatches, ifConditionKeyword);
const processedIfStatementString = executeIfStatementAndGetProcessedString(completeIfStatementArray, replacePlaceholderCallback); executeIfStatementAndSetProcessedStrings(completeIfStatementArray, replacePlaceholderCallback);
replaceIfStatementWithProcessedString(completeIfStatementArray, processedIfStatementString);
const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches); const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
return processIfStatements(reconstructedPostProcessedString, ifConditionKeyword, replacePlaceholderCallback); return processIfStatements(reconstructedPostProcessedString, ifConditionKeyword, replacePlaceholderCallback);
} }
} }
function getFirstNonNestedIfStatement(matches, ifConditionKeyword) { function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
let index = 0; let index = 0;
for(const match of matches) { for(const match of matches) {
if (match.groups.ifOperator && match.groups.ifConditionType === ifConditionKeyword) { if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) {
let interiorIndex = 0; let interiorIndex = 0;
let nestedLevel = 0; let nestedLevel = 0;
let elseStatementIndex = null;
for (const interiorMatch of matches.slice(index+1)) { for (const interiorMatch of matches.slice(index+1)) {
if (interiorMatch.groups.ifOperator) { if (interiorMatch.groups.isIfStatement) {
if(interiorMatch.groups.ifConditionType === ifConditionKeyword) { if(interiorMatch.groups.ifConditionType === ifConditionKeyword) {
break; break;
} else { } else {
nestedLevel++; nestedLevel++;
} }
} else if (interiorMatch.groups.endOperator) { } else if(interiorMatch.groups.isElseStatement) {
if (!nestedLevel) {
elseStatementIndex = interiorIndex+1;
}
} else if (interiorMatch.groups.isEndStatement) {
if (nestedLevel) { if (nestedLevel) {
nestedLevel--; nestedLevel--;
} else { } else {
return matches.slice(index, index + interiorIndex+2); const ifStatementArray = matches.slice(index, index + interiorIndex+2);
flagMatchesForProcessing(ifStatementArray, elseStatementIndex);
return ifStatementArray;
} }
} }
interiorIndex++; interiorIndex++;
@ -182,33 +187,51 @@ function getFirstNonNestedIfStatement(matches, ifConditionKeyword) {
} }
} }
function flagMatchesForProcessing(matches, elseStatementIndex) {
matches[0].isFlaggedForProcessing = true;
matches[matches.length-1].isFlaggedForProcessing = true;
if (elseStatementIndex) {
matches[elseStatementIndex].isFlaggedForProcessing = true;
}
}
function joinProcessedRegexArray(regexMatches) { function joinProcessedRegexArray(regexMatches) {
let processedString = ""; let processedString = "";
regexMatches.forEach((match) => { regexMatches.forEach((match) => {
processedString += match.groups.cleanString ?? match[0]; const rawString = match[0];
processedString += match.groups.processedString ?? rawString;
}); });
return processedString; return processedString;
} }
function executeIfStatementAndGetProcessedString(ifStatementArray, replacePlaceholderCallback) { function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlaceholderCallback) {
const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition); const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition);
if (ifCondition) { let isInsideDesiredBlock = ifCondition;
return ifStatementArray[0].groups.ifTrailingString;
} else { ifStatementArray.forEach(entry => {
return ifStatementArray[1].groups.elseTrailingString ?? ""; if (entry.groups.isElseStatement && entry.isFlaggedForProcessing) {
} isInsideDesiredBlock = !ifCondition;
} else if (entry.groups.isEndStatement && entry.isFlaggedForProcessing) {
isInsideDesiredBlock = true;
}
setProcessedStringOnEntry(entry, isInsideDesiredBlock);
});
} }
function replaceIfStatementWithProcessedString(ifStatementArray, processedIfStatementString) { function setProcessedStringOnEntry(entry, isInsideDesiredBlock) {
ifStatementArray.forEach((entry) => { if (!isInsideDesiredBlock) {
if (entry.groups.ifOperator) { entry.groups.processedString = "";
entry.groups.cleanString = processedIfStatementString; } else {
} else if (entry.groups.endOperator) { if (entry.groups.isIfStatement) {
entry.groups.cleanString = entry.groups.endTrailingString; entry.groups.processedString = entry.isFlaggedForProcessing ? entry.groups.ifTrailingString : entry[0];
} else if (entry.groups.isElseStatement) {
entry.groups.processedString = entry.isFlaggedForProcessing ? entry.groups.elseTrailingString : entry[0];
} else { } else {
entry.groups.cleanString = ""; entry.groups.processedString = entry.isFlaggedForProcessing ? entry.groups.endTrailingString : entry[0];
} }
}) }
} }
function getIfStatementRegexExpression() { function getIfStatementRegexExpression() {
@ -217,23 +240,23 @@ function getIfStatementRegexExpression() {
const anyLogicOperatorNonCapture = "(?:{(?:end|else|if:.*?)})"; const anyLogicOperatorNonCapture = "(?:{(?:end|else|if:.*?)})";
// NOTE: ?<variableName> syntax stores the captured match like so: match.groups.variableName // NOTE: ?<variableName> syntax stores the captured match like so: match.groups.variableName
const matchStartOfString = ( const matchStartOfString = (
"(?<cleanString>^.+?)" + // Match and Capture all characters (lazy), cannot be empty "(?<processedString>^.+?)" + // Match and Capture all characters (lazy), cannot be empty
"(?=(?:{if))" // Looks ahead but does not capture {if "(?=(?:{if))" // Looks ahead but does not capture {if
); );
const matchIfOperator = ( const matchIfOperator = (
"(?<ifOperator>{if:)" + // Match & Capture {if: "(?<isIfStatement>{if:)" + // Match & Capture {if:
"(?<ifConditionType>.*?):" + // Match all chars up to and including next ":" - Capture all chars up to ":" "(?<ifConditionType>.*?):" + // Match all chars up to and including next ":" - Capture all chars up to ":"
"(?<ifCondition>.*?)}" + // Match all chars up to and including next "}" - Capture all chars up to "}" "(?<ifCondition>.*?)}" + // Match all chars up to and including next "}" - Capture all chars up to "}"
"(?<ifTrailingString>.*?)" + // Match and Capture all characters (lazy), can be empty "(?<ifTrailingString>.*?)" + // Match and Capture all characters (lazy), can be empty
"(?=" + anyLogicOperatorNonCapture + ")" // Looks ahead but does not capture the next logic operator "(?=" + anyLogicOperatorNonCapture + ")" // Looks ahead but does not capture the next logic operator
); );
const matchElseOperator = ( const matchElseOperator = (
"(?<elseOperator>{else})" + // Match & Capture {else} "(?<isElseStatement>{else})" + // Match & Capture {else}
"(?<elseTrailingString>.*?)" + // Match & Capture all characters (lazy), can be empty "(?<elseTrailingString>.*?)" + // Match & Capture all characters (lazy), can be empty
"(?=" + anyLogicOperatorNonCapture + ")" // Looks ahead but does not capture the next logic operator "(?=" + anyLogicOperatorNonCapture + ")" // Looks ahead but does not capture the next logic operator
); );
const matchEndOperator = ( const matchEndOperator = (
"(?<endOperator>{end})" + // Match & Capture {end} "(?<isEndStatement>{end})" + // Match & Capture {end}
"(?<endTrailingString>.*?)" + // Match & Capture all chracters (lazy), can be empty "(?<endTrailingString>.*?)" + // Match & Capture all chracters (lazy), can be empty
"(?="+ anyLogicOperatorNonCapture + "|$)" // Looks ahead but does not capture the next logic operator "(?="+ anyLogicOperatorNonCapture + "|$)" // Looks ahead but does not capture the next logic operator
); );
@ -249,19 +272,23 @@ export function doesCopyContainRouterLink(copy) {
return copy.includes(this.dynamicStrings.ROUTER_LINK); return copy.includes(this.dynamicStrings.ROUTER_LINK);
} }
export function doesCopyContainTextLink(copy) {
return copy.includes(dynamicStrings.TEXT_LINK);
}
export function splitCopyOnCMSPlaceHolder(copy){ export function splitCopyOnCMSPlaceHolder(copy){
// splits copy on { ... } such as {routerlink: ...} // splits copy on { ... } such as {routerlink: ...}
return copy.split(/{(.*?)}/g); return copy.split(/{(.*?)}/g);
} }
export function getRouterLinkRouteFromCopy(copy){ export function getLinkTargetFromCopy(copy){
// sample input: {routerLink:estimate,provide your VIN} // sample input: {routerLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN' // first split would return 'estimate,provide your VIN'
// second split would return 'estimate' // second split would return 'estimate'
return copy.split(':')[1].split(',')[0]; return copy.split(':')[1].split(',')[0];
} }
export function getRouterLinkDisplayTextFromCopy(copy){ export function getLinkDisplayTextFromCopy(copy){
// sample input: {routerLink:estimate,provide your VIN} // sample input: {routerLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN' // first split would return 'estimate,provide your VIN'
// second split would return 'provide your VIN' // second split would return 'provide your VIN'

View file

@ -29,7 +29,7 @@
<div class="alert-provide-vin my-3" v-if="splitAlertProvideVinBodyForLink.length"> <div class="alert-provide-vin my-3" v-if="splitAlertProvideVinBodyForLink.length">
<span v-for="copy in splitAlertProvideVinBodyForLink" :key="copy"> <span v-for="copy in splitAlertProvideVinBodyForLink" :key="copy">
<span v-if="doesCopyContainRouterLink(copy)" class="text-body"> <span v-if="doesCopyContainRouterLink(copy)" class="text-body">
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link> <router-link :to="{query: {fmgPage: `${getLinkTargetFromCopy(copy)}`}, name: 'root'}">{{ getLinkDisplayTextFromCopy(copy) }}</router-link>
</span> </span>
<span v-else class="m-0 text-body" v-html="copy"></span> <span v-else class="m-0 text-body" v-html="copy"></span>
</span> </span>
@ -67,8 +67,8 @@ import { Form, defineRule } from "vee-validate";
import { isGlassAvailableForCarId } from "@/helpers/damage-helper"; import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { doesCopyContainRouterLink, import { doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy, getLinkTargetFromCopy,
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"; getLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper";
import { routerParams } from "@/router/router-constants/router-params"; import { routerParams } from "@/router/router-constants/router-params";
import vinPagesMixin from "@/mixins/vin-pages-mixin"; import vinPagesMixin from "@/mixins/vin-pages-mixin";
@ -148,8 +148,8 @@ export default {
methods: { methods: {
doesCopyContainRouterLink, doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy, getLinkTargetFromCopy,
getRouterLinkDisplayTextFromCopy, getLinkDisplayTextFromCopy,
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
if ( if (
store.getters.order.vehicle.carId store.getters.order.vehicle.carId

View file

@ -1,26 +1,18 @@
<template> <template>
<buttonQuestion <buttonQuestion
v-if="!isInsuranceSelected" :answers="servicePackageAnswers"
:answers="cashAnswersFromCms"
:groupName="groupName" :groupName="groupName"
buttonType="servicePackageRadio" buttonType="servicePackageRadio"
v-model="selectedValues" v-model="selectedValues"
isRequired isRequired
:additionalData="additionalData"
/>
<buttonQuestion
v-if="isInsuranceSelected"
:answers="insuranceAnswersFromCms"
:groupName="groupName"
buttonType="servicePackageRadio"
v-model="selectedValues"
isRequired
:additionalData="additionalData"
/> />
</template> </template>
<script> <script>
import buttonQuestion from "@/common-components/button-question/button-question"; import buttonQuestion from "@/common-components/button-question/button-question";
import baseMixin from "@/mixins/base-mixin.js";
import { splitCopyOnCMSPlaceHolder, processIfStatements } from "@/helpers/cms-content-helper";
import { damageLocationsSelected as glassLocation } from "@/constants/damage-locations-selected";
export default ({ export default ({
name: "servicePackageQuestion", name: "servicePackageQuestion",
@ -33,30 +25,6 @@ export default ({
lineItems: null, lineItems: null,
}, },
computed: { computed: {
cashAnswersFromCms(){
let cashAnswers = "";
const cmsContent = this.getCmsContent(this.cashCmsWidgetName, 'Answers');
if (cmsContent) {
cashAnswers = cmsContent?.map((x) => ({
buttonLabel : x.Name,
value : x.SubWidgetName,
}));
}
return cashAnswers;
},
// I'm starting to think there is no reason to have a separate insurance package answers
// the only benefit would be
insuranceAnswersFromCms() {
let insuranceAnswers = "";
const cmsContent = this.getCmsContent(this.insuranceCmsWidgetName, 'Answers');
if (cmsContent) {
insuranceAnswers = cmsContent?.map((x) => ({
buttonLabel : x.Name,
value : x.SubWidgetName,
}));
}
return insuranceAnswers;
},
selectedValues: { selectedValues: {
get: function() { get: function() {
return this.modelValue; return this.modelValue;
@ -65,11 +33,140 @@ export default ({
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
} }
}, },
additionalData() { servicePackageAnswers() {
return { const cmsWidgetName = this.isInsuranceSelected ? this.insuranceCmsWidgetName : this.cashCmsWidgetName;
lineItems: this.lineItems, let cmsAnswersContent = this.getCmsContent(cmsWidgetName, 'Answers');
isInsuranceSelected: this.isInsuranceSelected if (!this.shouldDisplayStandardPackage) {
}; cmsAnswersContent = cmsAnswersContent.filter(answer => answer.Name != "Standard");
}
const modifiedAnswers = cmsAnswersContent.map(answer => ({
value: answer.Name,
buttonLabel : this.getHeaderTextFromCms(answer.SubWidgetName),
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName),
buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName),
buttonAuxillaryCopy: this.getPackagePriceString(answer.Name),
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
}));
return modifiedAnswers;
},
isRecalibrationOnOrder() {
return this.lineItemsContainsPartNumber("RECAL");
},
frontWipersApplicableForStandard() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("FRONT WIPER");
const isRepair = this.$store.getters.order.damage.isRepair;
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation(glassLocation.WINDSHIELD);
if (frontWipersAreAvailable) {
if (isRepair) {
return true;
} else {
if (glassToReplaceContainsWindshield) {
return true;
} else {
return false;
}
}
} else {
return false;
}
},
rearWiperApplicableForStandard() {
const rearWiperIsAvailable = this.lineItemsContainsPartType("REAR WIPER");
return this.glassToReplaceContainsGlassLocation(glassLocation.REAR) && rearWiperIsAvailable;
},
frontWipersApplicableForPremium() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("FRONT WIPER");
return frontWipersAreAvailable;
},
rearWiperApplicableForPremium() {
const rearWiperIsAvailable = this.lineItemsContainsPartType("REAR WIPER");
return this.glassToReplaceContainsGlassLocation(glassLocation.REAR) && rearWiperIsAvailable;
},
rainDefenseApplicableForPremium() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("FRONT WIPER");
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation(glassLocation.WINDSHIELD);
const glassToReplaceContainsRearGlass = this.glassToReplaceContainsGlassLocation(glassLocation.REAR);
if(this.frontWipersApplicableForStandard) {
return true;
} else if (!frontWipersAreAvailable) {
return true;
} else if (!glassToReplaceContainsWindshield && !glassToReplaceContainsRearGlass) {
return true;
} else {
return false;
}
},
shouldDisplayStandardPackage() {
return this.frontWipersApplicableForStandard || this.rearWiperApplicableForStandard;
}
},
methods: {
splitCopyOnCMSPlaceHolder,
processIfStatements,
getHeaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, 'HeaderText');
},
getSubheaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, 'SubheaderText');
},
getBodyTextFromCms(cmsWidgetName) {
const bodyText = this.getCmsContent(cmsWidgetName, 'BodyText');
return this.processIfStatements(bodyText, "custom", this.getCustomValueFromString);
},
getFooterTextFromCms(cmsWidgetName){
const footerText = this.getCmsContent(cmsWidgetName, 'FooterText');
return this.processIfStatements(footerText, "custom", this.getCustomValueFromString);
},
getPackagePriceString(packageName) {
const formattedPriceFloat = parseFloat(this.getPackagePrice(packageName)).toFixed(2);
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
},
getPackagePrice(packageName) {
let priceFloat = this.isInsuranceSelected ? 0 : baseMixin.methods.getEconomyPackagePrice(this.lineItems);
if (packageName === "Standard") {
priceFloat += this.getStandardPackageVapsPrice();
} else if (packageName === "Premium") {
priceFloat += this.getPremiumPackageVapsPrice();
}
return priceFloat;
},
getStandardPackageVapsPrice() {
let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForStandard;
const priceRearWipers = this.rearWiperApplicableForStandard;
this.lineItems.forEach(item => {
if ((priceFrontWipers && item.partType.toUpperCase().includes("FRONT WIPER")) || (priceRearWipers && item.partType.toUpperCase().includes("REAR WIPER"))) {
vapsPrice += item.price;
}
});
return vapsPrice;
},
getPremiumPackageVapsPrice() {
let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForPremium;
const priceRearWipers = this.rearWiperApplicableForPremium;
const priceRainDefense = this.rainDefenseApplicableForPremium;
this.lineItems.forEach(item => {
if ((priceFrontWipers && item.partType.toUpperCase().includes("FRONT WIPER")) || (priceRearWipers && item.partType.toUpperCase().includes("REAR WIPER")) || (priceRainDefense && item.partType.toUpperCase().includes("RAIN DEFENSE"))) {
vapsPrice += item.price;
}
});
return vapsPrice;
},
getCustomValueFromString(str) {
return this[str];
},
lineItemsContainsPartType(partType) {
const partTypeMatches = this.lineItems.filter(lineItem => lineItem.partType.toUpperCase().includes(partType));
return !!partTypeMatches.length;
},
lineItemsContainsPartNumber(partNumber) {
const partNumberMatches = this.lineItems.filter(lineItem => lineItem.partNumber.toUpperCase().includes(partNumber));
return !!partNumberMatches.length;
},
glassToReplaceContainsGlassLocation(glassLocation) {
const glassLocationMatches = this.$store.getters.order.damage.glassToReplace.filter(glassToReplace => glassToReplace.glassLocation === glassLocation);
return !!glassLocationMatches.length;
}, },
}, },
components: { components: {

View file

@ -1,15 +1,14 @@
<template> <template>
<baseInputButton <baseInputButton
v-if="shouldDisplayThisPackage"
v-bind="$props" v-bind="$props"
@buttonClicked="handleAnswerChange"> @buttonClicked="handleAnswerChange">
<div class="package-label" for="testradio"> <div class="package-label" for="testradio">
<div class="package-specs"> <div class="package-specs">
<p :class="[this.getSubHeaderTextFromCms ? 'mb-2' : 'm-0']"> <p :class="[this.buttonLabelSubCopy ? 'mb-2' : 'm-0']">
<span v-html="this.getHeaderTextFromCms"></span> <span v-html="this.buttonLabel"></span>
<span class="pricing-info" v-html="getPackagePriceString"></span> <span class="pricing-info" v-html="this.buttonAuxillaryCopy"></span>
</p> </p>
<p class="sub-label m-0" v-if="this.getSubHeaderTextFromCms" v-html="this.getSubHeaderTextFromCms"></p> <p class="sub-label m-0" v-if="this.buttonLabelSubCopy" v-html="this.buttonLabelSubCopy"></p>
<!-- Parse the body text containing <ul> with logic --> <!-- Parse the body text containing <ul> with logic -->
<ul> <ul>
<li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem"> <li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem">
@ -19,13 +18,13 @@
<template v-else v-for="copy in splitCopyOnCMSPlaceHolder(listItem)" :key="copy"> <template v-else v-for="copy in splitCopyOnCMSPlaceHolder(listItem)" :key="copy">
<span v-if="!doesCopyContainTextLink(copy)" v-html="copy"></span> <span v-if="!doesCopyContainTextLink(copy)" v-html="copy"></span>
<span v-else> <span v-else>
<textLink linkType="dashedUnderline" :text="getTextLinkDisplayTextFromCopy(copy)" href="#!" data-bs-toggle="modal" :data-bs-target="'#' + getTextAreaTargetFromCopy(copy)" aria-label="Modal window" /> <textLink linkType="dashedUnderline" :text="getLinkDisplayTextFromCopy(copy)" href="#!" data-bs-toggle="modal" :data-bs-target="'#' + getLinkTargetFromCopy(copy)" aria-label="Modal window" />
</span> </span>
</template> </template>
</li> </li>
</ul> </ul>
<!-- End of body text parsing --> <!-- End of body text parsing -->
<div class="package-footer" v-if="getFooterTextFromCms" v-html="getFooterTextFromCms"></div> <div class="package-footer" v-if="this.buttonFooterCopy" v-html="this.buttonFooterCopy"></div>
</div> </div>
</div> </div>
</baseInputButton> </baseInputButton>
@ -35,9 +34,10 @@
import baseInputButton from "@/common-components/base-input-button/base-input-button"; import baseInputButton from "@/common-components/base-input-button/base-input-button";
import textLink from "@/ux-components/text-link/text-link"; import textLink from "@/ux-components/text-link/text-link";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
import baseMixin from "@/mixins/base-mixin.js"; import { getLinkDisplayTextFromCopy,
import { splitCopyOnCMSPlaceHolder, processIfStatements } from "@/helpers/cms-content-helper"; getLinkTargetFromCopy,
import { dynamicStrings } from "@/constants/dynamic-strings"; splitCopyOnCMSPlaceHolder,
doesCopyContainTextLink } from "@/helpers/cms-content-helper";
export default { export default {
name: "servicePackageRadio", name: "servicePackageRadio",
@ -47,87 +47,15 @@ export default {
textLink, textLink,
}, },
computed: { computed: {
getHeaderTextFromCms(){
return this.getCmsContent(this.value, 'HeaderText');
},
getSubHeaderTextFromCms(){
return this.getCmsContent(this.value, 'SubheaderText');
},
getBodyTextFromCms(){
const bodyText = this.getCmsContent(this.value, 'BodyText');
return this.processIfStatements(bodyText, "custom", this.getCustomValueFromString);
},
getFooterTextFromCms(){
const footerText = this.getCmsContent(this.value, 'FooterText');
return this.processIfStatements(footerText, "custom", this.getCustomValueFromString);
},
getPackagePriceString() {
const formattedPriceFloat = parseFloat(this.getPackagePrice()).toFixed(2);
return (this.additionalData.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
},
arrayOfListItemsFromBodyText() { arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.getBodyTextFromCms); return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
}, },
isRecalibrationOnOrder() {
return this.lineItemsContainsPartType("RECALIBRATION");
},
frontWipersApplicableForStandard() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("FRONT WIPER");
const isRepair = this.$store.getters.order.damage.isRepair;
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation("Windshield");
if (frontWipersAreAvailable) {
if (isRepair) {
return true;
} else {
if (glassToReplaceContainsWindshield) {
return true;
} else {
return false;
}
}
} else {
return false;
}
},
rearWiperApplicableForStandard() {
const rearWiperIsAvailable = this.lineItemsContainsPartType("REAR WIPER");
return this.glassToReplaceContainsGlassLocation("Rear") && rearWiperIsAvailable;
},
frontWipersApplicableForPremium() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("FRONT WIPER");
return frontWipersAreAvailable;
},
rearWiperApplicableForPremium() {
const rearWiperIsAvailable = this.lineItemsContainsPartType("REAR WIPER");
return this.glassToReplaceContainsGlassLocation("Rear") && rearWiperIsAvailable;
},
rainDefenseApplicableForPremium() {
const frontWipersAreAvailable = this.lineItemsContainsPartType("FRONT WIPER");
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation("Windshield");
const glassToReplaceContainsRearGlass = this.glassToReplaceContainsGlassLocation("Rear");
if(this.frontWipersApplicableForStandard) {
return true;
} else if (!frontWipersAreAvailable) {
return true;
} else if (!glassToReplaceContainsWindshield && !glassToReplaceContainsRearGlass) {
return true;
} else {
return false;
}
},
shouldDisplayThisPackage() {
if (this.value != "StandardServicePackage") {
return true;
} else if (this.frontWipersApplicableForStandard || this.rearWiperApplicableForStandard) {
return true;
} else {
return false;
}
}
}, },
methods: { methods: {
getLinkDisplayTextFromCopy,
getLinkTargetFromCopy,
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
processIfStatements, doesCopyContainTextLink,
stripUlTagFromCopy(copy) { stripUlTagFromCopy(copy) {
const regex = /(?:<ul(?:.*?)>)|(?:<\/ul>)/g; const regex = /(?:<ul(?:.*?)>)|(?:<\/ul>)/g;
return copy.replace(regex, ''); return copy.replace(regex, '');
@ -136,64 +64,6 @@ export default {
const withoutUlTags = this.stripUlTagFromCopy(copy); const withoutUlTags = this.stripUlTagFromCopy(copy);
return withoutUlTags.split(/(?:<li(?:.*?)>)|(?:<\/li>)/g).filter(lineItem => lineItem); return withoutUlTags.split(/(?:<li(?:.*?)>)|(?:<\/li>)/g).filter(lineItem => lineItem);
}, },
doesCopyContainTextLink(copy) {
return copy.includes(dynamicStrings.TEXT_LINK);
},
getTextAreaTargetFromCopy(copy){
// sample input: {textLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN'
// second split would return 'estimate'
return copy.split(':')[1].split(',')[0];
},
getTextLinkDisplayTextFromCopy(copy){
// sample input: {textLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN'
// second split would return 'provide your VIN'
return copy.split(':')[1].split(',')[1];
},
getCustomValueFromString(str) {
return this[str];
},
lineItemsContainsPartType(partType) {
const results = this.additionalData.lineItems.filter(lineItem => lineItem.partType.toUpperCase().includes(partType));
return !!results.length;
},
glassToReplaceContainsGlassLocation(glassLocation) {
const glassLocationMatches = this.$store.getters.order.damage.glassToReplace.filter(glassToReplace => glassToReplace.glassLocation === glassLocation);
return !!glassLocationMatches.length;
},
getPackagePrice() {
let priceFloat = this.additionalData.isInsuranceSelected ? 0 : baseMixin.methods.getEconomyPackagePrice(this.additionalData.lineItems);
if (this.value === "StandardServicePackage") {
priceFloat += this.getStandardPackageVapsPrice();
} else if (this.value === "PremiumServicePackage") {
priceFloat += this.getPremiumPackageVapsPrice();
}
return priceFloat;
},
getStandardPackageVapsPrice() {
let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForStandard;
const priceRearWipers = this.rearWiperApplicableForStandard;
this.additionalData.lineItems.forEach(item => {
if ((priceFrontWipers && item.partType.toUpperCase().includes("FRONT WIPER")) || (priceRearWipers && item.partType.toUpperCase().includes("REAR WIPER"))) {
vapsPrice += item.price;
}
});
return vapsPrice;
},
getPremiumPackageVapsPrice() {
let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForPremium;
const priceRearWipers = this.rearWiperApplicableForPremium;
const priceRainDefense = this.rainDefenseApplicableForPremium;
this.additionalData.lineItems.forEach(item => {
if ((priceFrontWipers && item.partType.toUpperCase().includes("FRONT WIPER")) || (priceRearWipers && item.partType.toUpperCase().includes("REAR WIPER")) || (priceRainDefense && item.partType.toUpperCase().includes("RAIN DEFENSE"))) {
vapsPrice += item.price;
}
});
return vapsPrice;
},
}, },
}; };
</script> </script>

View file

@ -62,11 +62,13 @@ export default {
}; };
}, },
getEconomyPackagePrice(lineItems) { getEconomyPackagePrice(lineItems) {
const nonVapsItems = lineItems.filter((lineItem) => {
return lineItem.PartType != "frontWiper" && lineItem.PartType != "rearWiper" && lineItem.PartType != "rainDefense";
});
let totalPrice = 0; let totalPrice = 0;
nonVapsItems.forEach(item => totalPrice += item.price); lineItems.forEach((lineItem) => {
const partType = lineItem.partType.toUpperCase();
if (!partType.includes("FRONT WIPER") && !partType.includes("REAR WIPER") && !partType.includes("RAIN DEFENSE")) {
totalPrice += lineItem.price;
}
});
return totalPrice; return totalPrice;
} }
}, },

View file

@ -9,6 +9,9 @@ export default {
...inputButtonProps, ...inputButtonProps,
buttonLabel: [Number, String], buttonLabel: [Number, String],
buttonLabelSubCopy: String, buttonLabelSubCopy: String,
buttonBodyCopy: String,
buttonAuxillaryCopy: String,
buttonFooterCopy: String,
buttonImage: String, buttonImage: String,
altText: { altText: {
type: String, type: String,
@ -17,7 +20,6 @@ export default {
textPosition: String, textPosition: String,
screenReaderOnlyText: String, screenReaderOnlyText: String,
isWide: Boolean, isWide: Boolean,
additionalData: null,
}, },
computed: { computed: {
selectedValue: { selectedValue: {

View file

@ -11,7 +11,7 @@
<template v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)" :key="copy"> <template v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)" :key="copy">
<span v-if="!doesCopyContainRouterLink(copy)" v-html="copy"></span> <span v-if="!doesCopyContainRouterLink(copy)" v-html="copy"></span>
<span v-else> <span v-else>
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link> <router-link :to="{query: {fmgPage: `${getLinkTargetFromCopy(copy)}`}, name: 'root'}">{{ getLinkDisplayTextFromCopy(copy) }}</router-link>
</span> </span>
</template> </template>
</p> </p>
@ -38,8 +38,8 @@
<script> <script>
import { doesCopyContainRouterLink, import { doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy, getLinkTargetFromCopy,
getRouterLinkDisplayTextFromCopy, getLinkDisplayTextFromCopy,
splitCMSCopyOnParagraphTag } from "@/helpers/cms-content-helper" splitCMSCopyOnParagraphTag } from "@/helpers/cms-content-helper"
export default { export default {
@ -84,8 +84,8 @@ export default {
methods: { methods: {
doesCopyContainRouterLink, doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy, getLinkTargetFromCopy,
getRouterLinkDisplayTextFromCopy, getLinkDisplayTextFromCopy,
ensureAlertIsInViewPort() { ensureAlertIsInViewPort() {
if (this.shouldScrollToOnMount && this.$el.style.display != 'none') { if (this.shouldScrollToOnMount && this.$el.style.display != 'none') {
var footerHeight = this.getFooterInfoBoxHeight(); var footerHeight = this.getFooterInfoBoxHeight();