CSR-731 | Working version before refactor
This commit is contained in:
parent
a8d9060b22
commit
a60398a55b
10 changed files with 488 additions and 116 deletions
|
|
@ -52,7 +52,8 @@
|
||||||
:selectOnKeypress="selectOnKeypress"
|
:selectOnKeypress="selectOnKeypress"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
@focus="handleFocus"
|
@focus="handleFocus"
|
||||||
@buttonClicked="handleAnswerChange" />
|
@buttonClicked="handleAnswerChange"
|
||||||
|
:additionalData="additionalData" />
|
||||||
<!-- For nested questions -->
|
<!-- For nested questions -->
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<div
|
<div
|
||||||
|
|
@ -121,6 +122,7 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
additionalData: null
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -157,7 +159,7 @@ export default {
|
||||||
classes = "ui-radio d-flex";
|
classes = "ui-radio d-flex";
|
||||||
break;
|
break;
|
||||||
case "servicePackageRadio":
|
case "servicePackageRadio":
|
||||||
classes = "ui-radio d-flex service-package";
|
classes = "package-main";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return classes;
|
return classes;
|
||||||
|
|
@ -165,10 +167,12 @@ export default {
|
||||||
getComponentWrapperClasses() {
|
getComponentWrapperClasses() {
|
||||||
let classes = "";
|
let classes = "";
|
||||||
|
|
||||||
classes += this.isWid ? "col-12" : "col";
|
classes += this.isWide ? "col-12" : "col";
|
||||||
|
|
||||||
if (this.buttonType == "radio") {
|
if (this.buttonType == "radio") {
|
||||||
classes += " radio-button-container";
|
classes += " radio-button-container";
|
||||||
|
} else if (this.buttonType == "servicePackageRadio") {
|
||||||
|
classes = "package-wrapper";
|
||||||
}
|
}
|
||||||
|
|
||||||
return classes;
|
return classes;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
const dynamicStrings = {
|
const dynamicStrings = {
|
||||||
GLOBAL_STATE: "globalState",
|
GLOBAL_STATE: "globalState",
|
||||||
CUSTOM: "custom",
|
CUSTOM: "custom",
|
||||||
ROUTER_LINK: "routerLink:"
|
ROUTER_LINK: "routerLink:",
|
||||||
|
TEXT_LINK: "textLink:",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { dynamicStrings };
|
export { dynamicStrings };
|
||||||
|
|
@ -46,22 +46,16 @@ function mapStringToState(str) {
|
||||||
return match[1] === dynamicStrings.GLOBAL_STATE;
|
return match[1] === dynamicStrings.GLOBAL_STATE;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// Our final string value that will be built from the matches.
|
// Our final string value that will be built from the matches.
|
||||||
let stringBuilder = "";
|
let stringBuilder = "";
|
||||||
|
|
||||||
for (const match of globalStateMatches) {
|
for (const match of globalStateMatches) {
|
||||||
// Reset store state for each match.
|
const valueFromStore = getStoreValueFromString(match[2]);
|
||||||
let storeState = store.state;
|
if (!valueFromStore) {
|
||||||
|
return ""; // if we can't map our string to state data, return an empty string.
|
||||||
for (const s of match[2].split(".")) {
|
|
||||||
if (storeState[s] != undefined) {
|
|
||||||
storeState = storeState[s];
|
|
||||||
} else {
|
|
||||||
return ""; // if we can't map our string to state data, return an empty string.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stringWithReplacement = str.replace(match[0], storeState);
|
const stringWithReplacement = str.replace(match[0], valueFromStore);
|
||||||
|
|
||||||
// If we still have values we need to substitute, call this function again.
|
// If we still have values we need to substitute, call this function again.
|
||||||
if (stringWithReplacement.includes(dynamicStrings.GLOBAL_STATE)) {
|
if (stringWithReplacement.includes(dynamicStrings.GLOBAL_STATE)) {
|
||||||
|
|
@ -71,7 +65,6 @@ function mapStringToState(str) {
|
||||||
// Concatenate the string.
|
// Concatenate the string.
|
||||||
stringBuilder = `${stringBuilder} ${stringWithReplacement}`;
|
stringBuilder = `${stringBuilder} ${stringWithReplacement}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringBuilder.trimStart();
|
return stringBuilder.trimStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,10 +93,11 @@ function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
|
||||||
function processWidgetItemForReplacement(widgetModel, key) {
|
function processWidgetItemForReplacement(widgetModel, key) {
|
||||||
// If we have a string, and it needs to be replaced.
|
// If we have a string, and it needs to be replaced.
|
||||||
if (typeof widgetModel[key] === "string") {
|
if (typeof widgetModel[key] === "string") {
|
||||||
|
widgetModel[key] = checkForIfStatements(widgetModel[key], dynamicStrings.GLOBAL_STATE, getStoreValueFromString);
|
||||||
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
|
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
|
||||||
widgetModel[key] = mapStringToState(widgetModel[key]);
|
widgetModel[key] = mapStringToState(widgetModel[key]);
|
||||||
}
|
}
|
||||||
return widgetModel[key];
|
return reconstructPageLevelIfStatements(widgetModel[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have an object. array, etc
|
// If we have an object. array, etc
|
||||||
|
|
@ -122,6 +116,89 @@ function processWidgetItemForReplacement(widgetModel, key) {
|
||||||
return widgetModel[key];
|
return widgetModel[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkForIfStatements(str, ifConditionString, replaceStringCallback) {
|
||||||
|
// Pattern to split off each {if:...}, {else}, {end} and their trailing strings
|
||||||
|
const regexExp = new RegExp("(?<cleanString>^.+?)(?=(?:{if))|(?<ifOperator>{if:)(?<ifConditionType>.*?:)(?<ifCondition>.*?)}(?<ifTrailingString>.*?)(?=(?:{(?:end|else|if:.*?)}))|(?<elseOperator>{else})(?<elseTrailingString>.*?)(?=(?:{(?:end|else|if:.*?)}))|(?<endOperator>{end})(?<endTrailingString>.*?)(?=(?:{(?:end|else|if:.*?)})|$)", "g");
|
||||||
|
const regexMatches = [...str.matchAll(regexExp)];
|
||||||
|
// Find and evaluate full globalState if statements
|
||||||
|
if (regexMatches.length) {
|
||||||
|
regexMatches.forEach((match, index) => {
|
||||||
|
if(match.groups.ifOperator) {
|
||||||
|
if(regexMatches[index+1].groups.endOperator) {
|
||||||
|
processIfStatement(regexMatches.slice(index, index+2), ifConditionString, replaceStringCallback) + regexMatches[index+1].groups.endTrailingString;
|
||||||
|
}
|
||||||
|
else if(regexMatches[index+2].groups.endOperator) {
|
||||||
|
processIfStatement(regexMatches.slice(index, index+3), ifConditionString, replaceStringCallback)+ regexMatches[index+2].groups.endTrailingString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const processedString = joinProcessedRegexArray(regexMatches);
|
||||||
|
return checkForIfStatements(processedString, ifConditionString, replaceStringCallback);
|
||||||
|
} else {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reconstructPageLevelIfStatements(str) {
|
||||||
|
return str.replaceAll("(((", "{").replaceAll(")))","}");
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinProcessedRegexArray(regexMatches) {
|
||||||
|
let processedString = "";
|
||||||
|
regexMatches.forEach((match) => {
|
||||||
|
processedString += match.groups.cleanString ?? match[0];
|
||||||
|
});
|
||||||
|
return processedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processIfStatement(ifStatementArray, ifConditionString, replaceStringCallback) {
|
||||||
|
if(ifStatementArray[0].groups.ifConditionType.includes(ifConditionString)) {
|
||||||
|
//replace the if condition with a real value
|
||||||
|
const ifCondition = replaceStringCallback(ifStatementArray[0].groups.ifCondition);
|
||||||
|
//convert the entire if statement
|
||||||
|
let processedIfStatementString;
|
||||||
|
if (ifCondition) {
|
||||||
|
processedIfStatementString = ifStatementArray[0].groups.ifTrailingString;
|
||||||
|
} else {
|
||||||
|
processedIfStatementString = ifStatementArray[1].groups.elseTrailingString ?? "";
|
||||||
|
}
|
||||||
|
ifStatementArray.forEach((entry) => {
|
||||||
|
if (entry.groups.ifOperator) {
|
||||||
|
entry.groups.cleanString = processedIfStatementString;
|
||||||
|
} else if (entry.groups.endOperator) {
|
||||||
|
entry.groups.cleanString = entry.groups.endTrailingString;
|
||||||
|
} else {
|
||||||
|
entry.groups.cleanString = "";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// stow the if statement to reconstruct later when passing to the page
|
||||||
|
ifStatementArray.forEach((entry) => {
|
||||||
|
const groups = entry.groups;
|
||||||
|
if (groups.ifOperator)
|
||||||
|
groups.cleanString = "(((if:" + groups.ifConditionType + groups.ifCondition + ")))" + groups.ifTrailingString;
|
||||||
|
else if (groups.elseOperator)
|
||||||
|
groups.cleanString = "(((else)))" + groups.elseTrailingString;
|
||||||
|
else
|
||||||
|
groups.cleanString = "(((end)))" + groups.endTrailingString;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStoreValueFromString(str) {
|
||||||
|
let storeOrStateObject = str.includes('getters') ? store :store.state;
|
||||||
|
for (const s of str.split(".")) {
|
||||||
|
if (storeOrStateObject[s] != undefined) {
|
||||||
|
storeOrStateObject = storeOrStateObject[s];
|
||||||
|
} else {
|
||||||
|
return ""; // if we can't map our string to state data, return an empty string.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return storeOrStateObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function doesCopyContainRouterLink(copy) {
|
export function doesCopyContainRouterLink(copy) {
|
||||||
return copy.includes(this.dynamicStrings.ROUTER_LINK);
|
return copy.includes(this.dynamicStrings.ROUTER_LINK);
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@
|
||||||
<alert
|
<alert
|
||||||
v-if="displayNonServiceableZipAlert"
|
v-if="displayNonServiceableZipAlert"
|
||||||
class="my-3"
|
class="my-3"
|
||||||
|
cmsWidgetName="AlertNonServiceableZipWidget"
|
||||||
:manualHeadline="AlertNonServiceableZipHeader"
|
:manualHeadline="AlertNonServiceableZipHeader"
|
||||||
:manualCopy="AlertNonServiceableZipBody"
|
:manualCopy="AlertNonServiceableZipBody"
|
||||||
alertClass="alert-danger"
|
alertClass="alert-danger"
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,17 @@
|
||||||
<cashOrInsuranceQuestion
|
<cashOrInsuranceQuestion
|
||||||
ref="cashOrInsurance"
|
ref="cashOrInsurance"
|
||||||
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
||||||
v-model="isInsurance"
|
v-model="isInsuranceSelected"
|
||||||
groupName="CashOrInsuranceQuestion"
|
groupName="CashOrInsuranceQuestion"
|
||||||
/>
|
/>
|
||||||
<radioServicePackage/>
|
|
||||||
<servicePackageQuestion
|
<servicePackageQuestion
|
||||||
ref="servicePackage"
|
ref="servicePackage"
|
||||||
cashCmsWidgetName="CashServicePackageQuestionWidget"
|
cashCmsWidgetName="CashServicePackageQuestionWidget"
|
||||||
insuranceCmsWidgetName="InsuranceServicePackageWidget"
|
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
|
||||||
v-model="selectedPackage"
|
v-model="selectedPackage"
|
||||||
groupName="ServicePackageQuestion"
|
groupName="ServicePackageQuestion"
|
||||||
|
:lineItems="lineItems"
|
||||||
|
:isInsuranceSelected="isInsuranceSelected"
|
||||||
/>
|
/>
|
||||||
<textBlock
|
<textBlock
|
||||||
cmsWidgetName="quoteDisclaimer"
|
cmsWidgetName="quoteDisclaimer"
|
||||||
|
|
@ -125,7 +126,6 @@ import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||||
import radioServicePackage from "@/ux-components/radio-service-package/radio-service-package";
|
|
||||||
import cashOrInsuranceQuestion from "./cash-or-insurance-question/cash-or-insurance-question";
|
import cashOrInsuranceQuestion from "./cash-or-insurance-question/cash-or-insurance-question";
|
||||||
import servicePackageQuestion from "./service-package-question/service-package-question";
|
import servicePackageQuestion from "./service-package-question/service-package-question";
|
||||||
import textBlock from "@/common-components/text-block/text-block";
|
import textBlock from "@/common-components/text-block/text-block";
|
||||||
|
|
@ -156,34 +156,68 @@ export default {
|
||||||
|
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
console.log("cmsContent", cmsContent);
|
|
||||||
vm.setCmsContent(cmsContent);
|
vm.setCmsContent(cmsContent);
|
||||||
vm.isInsurance = vm.getDefaultIsInsuranceValue();
|
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue();
|
||||||
vm.selectedPackage = vm.getDefaultSelectedPackageValue();
|
vm.selectedPackage = vm.getDefaultSelectedPackageValue();
|
||||||
|
vm.lineItems = [
|
||||||
|
{
|
||||||
|
PartNumber : "001",
|
||||||
|
Description : "Windshield with Recal",
|
||||||
|
PartType : "Windshield",
|
||||||
|
Quantity : "1",
|
||||||
|
BasePartNumber : "001",
|
||||||
|
Color : "Green",
|
||||||
|
CanSafeliteRecalibrate: true,
|
||||||
|
Price : 350.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PartNumber : "002",
|
||||||
|
Description : "Front Wipers",
|
||||||
|
PartType : "frontWiper",
|
||||||
|
Quantity : "1",
|
||||||
|
Price : 35.00,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PartNumber : "003",
|
||||||
|
Description : "Rear Wiper",
|
||||||
|
PartType : "rearWiper",
|
||||||
|
Quantity : "1",
|
||||||
|
Price : 25.00,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PartNumber : "004",
|
||||||
|
Description : "Rain Defense",
|
||||||
|
PartType : "rainDefense",
|
||||||
|
Quantity : "1",
|
||||||
|
Price : 30.00,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PartNumber : "005",
|
||||||
|
Description : "Recalibration",
|
||||||
|
PartType : "recalibration",
|
||||||
|
Quantity : "1",
|
||||||
|
Price : 150.00,
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
cashOrInsuranceThreshhold: 600,
|
cashOrInsuranceThreshhold: 600,
|
||||||
pricingRequestResult: null,
|
pricingRequestResult: null,
|
||||||
isInsurance: null,
|
isInsuranceSelected: null,
|
||||||
selectedPackage: null,
|
selectedPackage: null,
|
||||||
}
|
lineItems: null,
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selectedPackage(newPackage, oldPackage) {
|
|
||||||
console.log("new package: ", newPackage);
|
|
||||||
console.log("old package: ", oldPackage);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
return store.getters.order.damage.isRepair || (store.getters.order.lineItems?.glassParts != null && store.getters.order.lineItems.glassParts.length > 0);
|
return store.getters.order.damage.isRepair || (store.getters.order.lineItems?.glassParts != null && store.getters.order.lineItems.glassParts.length > 0);
|
||||||
},
|
},
|
||||||
getDefaultIsInsuranceValue() {
|
getDefaultIsInsuranceSelectedValue() {
|
||||||
var defaultIsInsuranceValue = this.$store.getters.order.payment.isInsurance;
|
const defaultIsInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance;
|
||||||
if(defaultIsInsuranceValue != null) {
|
if(defaultIsInsuranceSelectedValue != null) {
|
||||||
return defaultIsInsuranceValue;
|
return defaultIsInsuranceSelectedValue;
|
||||||
} else {
|
} else {
|
||||||
return this.economyPackagePrice > this.cashOrInsuranceThreshhold;
|
return this.economyPackagePrice > this.cashOrInsuranceThreshhold;
|
||||||
}
|
}
|
||||||
|
|
@ -194,16 +228,6 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
years() {
|
|
||||||
return ["2011", "2012"];
|
|
||||||
},
|
|
||||||
answersFromCms() {
|
|
||||||
return ["Pay on my own", "Pay with insurance"];
|
|
||||||
},
|
|
||||||
economyPackagePrice() {
|
|
||||||
//TODO build out the pricing logic CSR-504
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
selectedChipCountValues: {
|
selectedChipCountValues: {
|
||||||
get: function() {
|
get: function() {
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
|
|
@ -219,7 +243,6 @@ export default {
|
||||||
vehicleBanner,
|
vehicleBanner,
|
||||||
funnelSubHeader,
|
funnelSubHeader,
|
||||||
Form,
|
Form,
|
||||||
radioServicePackage,
|
|
||||||
textBlock,
|
textBlock,
|
||||||
cashOrInsuranceQuestion,
|
cashOrInsuranceQuestion,
|
||||||
servicePackageQuestion,
|
servicePackageQuestion,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="service-package-question">
|
<buttonQuestion
|
||||||
<buttonQuestion
|
v-if="!isInsuranceSelected"
|
||||||
:answers="cashAnswersFromCms"
|
:answers="cashAnswersFromCms"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="servicePackageRadio"
|
buttonType="servicePackageRadio"
|
||||||
v-model="selectedValues"
|
v-model="selectedValues"
|
||||||
isRequired
|
isRequired
|
||||||
/>
|
:additionalData="additionalData"
|
||||||
</div>
|
/>
|
||||||
|
<buttonQuestion
|
||||||
|
v-if="isInsuranceSelected"
|
||||||
|
:answers="insuranceAnswersFromCms"
|
||||||
|
:groupName="groupName"
|
||||||
|
buttonType="servicePackageRadio"
|
||||||
|
v-model="selectedValues"
|
||||||
|
isRequired
|
||||||
|
:additionalData="additionalData"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -20,22 +29,33 @@ export default ({
|
||||||
groupName: String,
|
groupName: String,
|
||||||
cashCmsWidgetName: String,
|
cashCmsWidgetName: String,
|
||||||
insuranceCmsWidgetName: String,
|
insuranceCmsWidgetName: String,
|
||||||
|
isInsuranceSelected: Boolean,
|
||||||
|
lineItems: null,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
cashAnswersFromCms(){
|
cashAnswersFromCms(){
|
||||||
let cashAnswers = "";
|
let cashAnswers = "";
|
||||||
let cmsContent = this.getCmsContent(this.cashCmsWidgetName, 'Answers');
|
const cmsContent = this.getCmsContent(this.cashCmsWidgetName, 'Answers');
|
||||||
if (cmsContent) {
|
if (cmsContent) {
|
||||||
cashAnswers = cmsContent?.map((x) => ({
|
cashAnswers = cmsContent?.map((x) => ({
|
||||||
buttonLabel : x.Name,
|
buttonLabel : x.Name,
|
||||||
value : x.SubWidgetName
|
value : x.SubWidgetName,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cashAnswers;
|
return cashAnswers;
|
||||||
},
|
},
|
||||||
insuranceAnswersFromCms() {
|
// I'm starting to think there is no reason to have a separate insurance package answers
|
||||||
return this.getCmsContent(this.insuranceCmsWidgetName, '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() {
|
||||||
|
|
@ -45,9 +65,15 @@ export default ({
|
||||||
this.$emit("update:modelValue", newValue);
|
this.$emit("update:modelValue", newValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
additionalData() {
|
||||||
|
return {
|
||||||
|
lineItems: this.lineItems,
|
||||||
|
isInsuranceSelected: this.isInsuranceSelected
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
buttonQuestion,
|
buttonQuestion,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,50 @@
|
||||||
<template>
|
<template>
|
||||||
<baseInputButton
|
<baseInputButton
|
||||||
|
v-if="shouldDisplayThisPackage"
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
buttonWrapperClasses="ui-radio form-check test000"
|
|
||||||
inputClasses="form-check-input"
|
|
||||||
@buttonClicked="handleAnswerChange">
|
@buttonClicked="handleAnswerChange">
|
||||||
<div class="d-flex align-items-start form-check-label">
|
<div class="package-label" for="testradio">
|
||||||
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
|
<div class="package-specs">
|
||||||
<span v-if="screenReaderOnlyText" class="sr-only">{{
|
<p :class="[this.getSubHeaderTextFromCms ? 'mb-2' : 'm-0']">
|
||||||
screenReaderOnlyText
|
<span v-html="this.getHeaderTextFromCms"></span>
|
||||||
}}</span>
|
<span class="pricing-info" v-html="getPackagePriceString"></span>
|
||||||
|
</p>
|
||||||
|
<p class="sub-label m-0" v-if="this.getSubHeaderTextFromCms" v-html="this.getSubHeaderTextFromCms"></p>
|
||||||
|
<!-- Parse the body text containing <ul> with logic -->
|
||||||
|
<ul>
|
||||||
|
<li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem">
|
||||||
|
<!-- no textLink -->
|
||||||
|
<span v-if="!doesCopyContainTextLink(listItem)" v-html="listItem"></span>
|
||||||
|
<!-- with textLink -->
|
||||||
|
<template v-else v-for="copy in splitCopyOnCMSPlaceHolder(listItem)" :key="copy">
|
||||||
|
<span v-if="!doesCopyContainTextLink(copy)" v-html="copy"></span>
|
||||||
|
<span v-else>
|
||||||
|
<textLink linkType="dashedUnderline" :text="getTextLinkDisplayTextFromCopy(copy)" href="#!" data-bs-toggle="modal" :data-bs-target="'#' + getTextAreaTargetFromCopy(copy)" aria-label="Modal window" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!-- End of body text parsing -->
|
||||||
|
<div class="package-footer" v-if="getFooterTextFromCms" v-html="getFooterTextFromCms"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</baseInputButton>
|
</baseInputButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
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 inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
import { splitCopyOnCMSPlaceHolder, checkForIfStatements } from "@/helpers/cms-content-helper";
|
||||||
|
import { dynamicStrings } from "@/constants/dynamic-strings";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "servicePackageRadio",
|
name: "servicePackageRadio",
|
||||||
mixins: [inputButtonWrapperMixin],
|
mixins: [inputButtonWrapperMixin],
|
||||||
components: {
|
components: {
|
||||||
baseInputButton,
|
baseInputButton,
|
||||||
|
textLink,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
getHeaderTextFromCms(){
|
getHeaderTextFromCms(){
|
||||||
|
|
@ -31,63 +54,269 @@ export default {
|
||||||
return this.getCmsContent(this.value, 'SubheaderText');
|
return this.getCmsContent(this.value, 'SubheaderText');
|
||||||
},
|
},
|
||||||
getBodyTextFromCms(){
|
getBodyTextFromCms(){
|
||||||
return this.getCmsContent(this.value, 'BodyText');
|
const bodyText = this.getCmsContent(this.value, 'BodyText');
|
||||||
|
return this.checkForIfStatements(bodyText, "custom", this.getCustomValueFromString);
|
||||||
},
|
},
|
||||||
getFooterTextFromCms(){
|
getFooterTextFromCms(){
|
||||||
return this.getCmsContent(this.value, 'FooterText');
|
const footerText = this.getCmsContent(this.value, 'FooterText');
|
||||||
|
return this.checkForIfStatements(footerText, "custom", this.getCustomValueFromString);
|
||||||
|
},
|
||||||
|
getPackagePriceString() {
|
||||||
|
const formattedPriceFloat = parseFloat(this.getPackagePrice()).toFixed(2);
|
||||||
|
return (this.additionalData.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
|
||||||
|
},
|
||||||
|
arrayOfListItemsFromBodyText() {
|
||||||
|
return this.getArrayOfListItemsFromRawCmsCopy(this.getBodyTextFromCms);
|
||||||
|
},
|
||||||
|
isRecalibrationOnOrder() {
|
||||||
|
return this.lineItemsContainsPartType("recalibration");
|
||||||
|
},
|
||||||
|
frontWipersApplicableForStandard() {
|
||||||
|
const frontWipersAreAvailable = this.lineItemsContainsPartType("frontWiper");
|
||||||
|
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("rearWiper");
|
||||||
|
return this.glassToReplaceContainsGlassLocation("Rear") && rearWiperIsAvailable;
|
||||||
|
},
|
||||||
|
frontWipersApplicableForPremium() {
|
||||||
|
const frontWipersAreAvailable = this.lineItemsContainsPartType("frontWiper");
|
||||||
|
return frontWipersAreAvailable;
|
||||||
|
},
|
||||||
|
rearWiperApplicableForPremium() {
|
||||||
|
const rearWiperIsAvailable = this.lineItemsContainsPartType("rearWiper");
|
||||||
|
return this.glassToReplaceContainsGlassLocation("Rear") && rearWiperIsAvailable;
|
||||||
|
},
|
||||||
|
rainDefenseApplicableForPremium() {
|
||||||
|
const frontWipersAreAvailable = this.lineItemsContainsPartType("frontWiper");
|
||||||
|
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: {
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
checkForIfStatements,
|
||||||
|
stripUlTagFromCopy(copy) {
|
||||||
|
const regex = /(?:<ul(?:.*?)>)|(?:<\/ul>)/g;
|
||||||
|
return copy.replace(regex, '');
|
||||||
|
},
|
||||||
|
getArrayOfListItemsFromRawCmsCopy(copy) {
|
||||||
|
const withoutUlTags = this.stripUlTagFromCopy(copy);
|
||||||
|
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 === 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 === "frontWiper") || (priceRearWipers && item.PartType === "rearWiper")) {
|
||||||
|
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 === "frontWiper") || (priceRearWipers && item.PartType === "rearWiper") || (priceRainDefense && item.PartType === "rainDefense")) {
|
||||||
|
vapsPrice += item.Price;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return vapsPrice;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.form-check {
|
.package-main {
|
||||||
position: relative;
|
.package-wrapper {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
|
||||||
.form-check-input {
|
label {
|
||||||
border: 1px solid $gray-500;
|
display: block;
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
opacity: 1;
|
|
||||||
height: 1em;
|
|
||||||
width: 1em;
|
|
||||||
|
|
||||||
&:checked {
|
|
||||||
background-color: $white;
|
|
||||||
background-size: 71%;
|
|
||||||
background-position: center;
|
|
||||||
border: 1px solid $blue;
|
|
||||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' xml:space='preserve'%3e%3ccircle cx='25' cy='25' r='25' fill='%231574A1'/%3e%3c/svg%3e");
|
|
||||||
+ .form-check-label {
|
|
||||||
p {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: $black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&,
|
|
||||||
& + .form-check-label {
|
|
||||||
margin-top: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
input[type="radio"] {
|
||||||
.form-check-input {
|
opacity: 0;
|
||||||
box-shadow: 0 0 0 4px $blue-300;
|
position: absolute;
|
||||||
|
left: -9999px;
|
||||||
|
|
||||||
|
+ .package-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid $gray-300;
|
||||||
|
box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: .5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 58px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
position: relative;
|
||||||
|
top: 5px;
|
||||||
|
margin-right: 1rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
min-width: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 19px;
|
||||||
|
top: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
min-width: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
+ .package-label {
|
||||||
|
&:before {
|
||||||
|
border: 1px solid #8E9292;
|
||||||
|
box-shadow: 0px 0px 0px 4px #9FCEE6, 0px 1px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
+ .package-label {
|
||||||
|
.package-specs {
|
||||||
|
max-height: 1000px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .package-label {
|
||||||
|
background-color: $blue-100;
|
||||||
|
border: 1px solid $blue;
|
||||||
|
max-height: 500px;
|
||||||
|
}
|
||||||
|
+ .package-label {
|
||||||
|
&:before {
|
||||||
|
box-shadow: 0px 0px 0px 1px $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .package-label {
|
||||||
|
&:after {
|
||||||
|
background: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
+ .package-label {
|
||||||
|
&:before {
|
||||||
|
border: 2px solid $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p {
|
|
||||||
font-weight: 400;
|
.package-specs {
|
||||||
font-size: 0.875rem;
|
display: flex;
|
||||||
color: $gray-600;
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 0;
|
||||||
|
transition: all .75s ease;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
span.pricing-info {
|
||||||
|
color: $green;
|
||||||
|
}
|
||||||
|
&.sub-label {
|
||||||
|
color: $green;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin: 1rem 0 0 -15px;
|
||||||
|
padding: 0;
|
||||||
|
li {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,14 @@ export default {
|
||||||
isServiceable: serviceZipValidationResponse.data.isServiceable,
|
isServiceable: serviceZipValidationResponse.data.isServiceable,
|
||||||
state: serviceZipValidationResponse.data.state
|
state: serviceZipValidationResponse.data.state
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
getEconomyPackagePrice(lineItems) {
|
||||||
|
const nonVapsItems = lineItems.filter((lineItem) => {
|
||||||
|
return lineItem.PartType != "frontWiper" && lineItem.PartType != "rearWiper" && lineItem.PartType != "rainDefense";
|
||||||
|
});
|
||||||
|
let totalPrice = 0;
|
||||||
|
nonVapsItems.forEach(item => totalPrice += item.Price);
|
||||||
|
return totalPrice;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ export default {
|
||||||
valueToLogType: String,
|
valueToLogType: String,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
isWide: Boolean,
|
isWide: Boolean,
|
||||||
isRequired: Boolean
|
isRequired: Boolean,
|
||||||
|
additionalData: null,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -31,7 +32,6 @@ export default {
|
||||||
if (this.preHandleAnswerChange) {
|
if (this.preHandleAnswerChange) {
|
||||||
this.preHandleAnswerChange(e)
|
this.preHandleAnswerChange(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit("change", e);
|
this.$emit("change", e);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ const getDefaultState = () => {
|
||||||
glassToReplace: null,
|
glassToReplace: null,
|
||||||
partQuestionAnswers: null,
|
partQuestionAnswers: null,
|
||||||
moldingQuestionAnswers: null,
|
moldingQuestionAnswers: null,
|
||||||
capabilityQuestionAnswers: null
|
capabilityQuestionAnswers: null,
|
||||||
},
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: null
|
glassParts: null
|
||||||
|
|
@ -372,6 +372,10 @@ export const getters = {
|
||||||
},
|
},
|
||||||
eventBus: (state) => state.applicationUser.eventBus,
|
eventBus: (state) => state.applicationUser.eventBus,
|
||||||
damage: (state) => state.order.damage,
|
damage: (state) => state.order.damage,
|
||||||
|
hasAnyNonWindshieldGlassParts: (state) => {
|
||||||
|
const nonWindshieldItems = state.order.damage.glassToReplace.filter(glassToReplace => glassToReplace.glassLocation != "Windshield");
|
||||||
|
return !!nonWindshieldItems.length;
|
||||||
|
},
|
||||||
lineItems: (state) => state.order.lineItems,
|
lineItems: (state) => state.order.lineItems,
|
||||||
pageData: (state) => (page) => { return state.applicationUser.pageData[page]; },
|
pageData: (state) => (page) => { return state.applicationUser.pageData[page]; },
|
||||||
applicationUser: (state) => state.applicationUser,
|
applicationUser: (state) => state.applicationUser,
|
||||||
|
|
@ -1133,7 +1137,6 @@ function getHasRecalibrationPart(state) {
|
||||||
} else { // Does not have 'requiresRecalibration'
|
} else { // Does not have 'requiresRecalibration'
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue