commit
cf9adbb13b
26 changed files with 846 additions and 376 deletions
|
|
@ -28,7 +28,7 @@ module.exports = {
|
||||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
statements: 83,
|
statements: 82,
|
||||||
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
|
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ describe("buttonQuestion.vue", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(buttonQuestion, {
|
const wrapper = shallowMount(buttonQuestion, {
|
||||||
propsData: {
|
propsData: {
|
||||||
buttonType: "listCard",
|
buttonTypeString: "listCard",
|
||||||
groupName: "group-name",
|
groupName: "group-name",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -46,7 +46,7 @@ describe("buttonQuestion.vue", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(buttonQuestion, {
|
const wrapper = shallowMount(buttonQuestion, {
|
||||||
propsData: {
|
propsData: {
|
||||||
buttonType: "listButtonHorizontal",
|
buttonTypeString: "listButtonHorizontal",
|
||||||
groupName: "group-name",
|
groupName: "group-name",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -61,7 +61,7 @@ describe("buttonQuestion.vue", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(buttonQuestion, {
|
const wrapper = shallowMount(buttonQuestion, {
|
||||||
propsData: {
|
propsData: {
|
||||||
buttonType: "radio",
|
buttonTypeString: "radio",
|
||||||
groupName: "group-name",
|
groupName: "group-name",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,12 @@
|
||||||
v-for="answer in buttonsInfo"
|
v-for="answer in buttonsInfo"
|
||||||
:key="answer.value ? answer.value : answer">
|
:key="answer.value ? answer.value : answer">
|
||||||
<component
|
<component
|
||||||
:is="buttonType"
|
:is="buttonTypeString"
|
||||||
: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"
|
||||||
|
|
@ -45,6 +48,7 @@
|
||||||
:isWide="isWide"
|
:isWide="isWide"
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
:textPosition="textPosition"
|
:textPosition="textPosition"
|
||||||
|
:additionalButtonStyling="additionalButtonStyling"
|
||||||
:lastValuePushedToGa="lastValuePushedToGa"
|
:lastValuePushedToGa="lastValuePushedToGa"
|
||||||
:setLastValuePushedToGa="setLastValuePushedToGa"
|
:setLastValuePushedToGa="setLastValuePushedToGa"
|
||||||
v-model="selectedValues" />
|
v-model="selectedValues" />
|
||||||
|
|
@ -78,10 +82,14 @@ import { ErrorMessage } from "vee-validate";
|
||||||
export default {
|
export default {
|
||||||
name: "buttonQuestion",
|
name: "buttonQuestion",
|
||||||
props: {
|
props: {
|
||||||
buttonType: {
|
buttonTypeString: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "listButton",
|
default: "listButton",
|
||||||
},
|
},
|
||||||
|
buttonTypeObject: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
isMultiSelect: Boolean,
|
isMultiSelect: Boolean,
|
||||||
groupName: String,
|
groupName: String,
|
||||||
questionText: String,
|
questionText: String,
|
||||||
|
|
@ -109,6 +117,12 @@ export default {
|
||||||
suppressError: Boolean,
|
suppressError: Boolean,
|
||||||
useTextForValue: Boolean,
|
useTextForValue: Boolean,
|
||||||
valueToLogType: String,
|
valueToLogType: String,
|
||||||
|
additionalButtonStyling: String,
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
if (this.buttonTypeObject) {
|
||||||
|
this.$options.components[this.buttonTypeString] = this.buttonTypeObject;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -119,7 +133,7 @@ export default {
|
||||||
getFieldSetClasses() {
|
getFieldSetClasses() {
|
||||||
if (this.isOverflowScrollable) {
|
if (this.isOverflowScrollable) {
|
||||||
return "container-fluid overflow-scroll position-absolute px-5 pt-1 py-0";
|
return "container-fluid overflow-scroll position-absolute px-5 pt-1 py-0";
|
||||||
} else if (this.buttonType == "listCard") {
|
} else if (this.buttonTypeString == "listCard") {
|
||||||
return "w-100";
|
return "w-100";
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -127,7 +141,7 @@ export default {
|
||||||
},
|
},
|
||||||
getComponentLoopWrapperClasses() {
|
getComponentLoopWrapperClasses() {
|
||||||
let classes;
|
let classes;
|
||||||
switch (this.buttonType) {
|
switch (this.buttonTypeString) {
|
||||||
case "listButton":
|
case "listButton":
|
||||||
classes = "w-100";
|
classes = "w-100";
|
||||||
break;
|
break;
|
||||||
|
|
@ -143,6 +157,9 @@ export default {
|
||||||
case "radio":
|
case "radio":
|
||||||
classes = "ui-radio d-flex";
|
classes = "ui-radio d-flex";
|
||||||
break;
|
break;
|
||||||
|
case "servicePackageRadio":
|
||||||
|
classes = "package-main";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return classes;
|
return classes;
|
||||||
},
|
},
|
||||||
|
|
@ -151,8 +168,10 @@ export default {
|
||||||
|
|
||||||
classes += this.isWide ? "col-12" : "col";
|
classes += this.isWide ? "col-12" : "col";
|
||||||
|
|
||||||
if (this.buttonType == "radio") {
|
if (this.buttonTypeString == "radio") {
|
||||||
classes += " radio-button-container";
|
classes += " radio-button-container";
|
||||||
|
} else if (this.buttonTypeString == "servicePackageRadio") {
|
||||||
|
classes = "package-wrapper";
|
||||||
}
|
}
|
||||||
|
|
||||||
return classes;
|
return classes;
|
||||||
|
|
@ -162,6 +181,9 @@ export default {
|
||||||
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
|
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
|
||||||
altText: answer.altText ?? (answer.Name ? answer.Name : answer),
|
altText: answer.altText ?? (answer.Name ? answer.Name : answer),
|
||||||
buttonLabelSubCopy: answer.buttonLabelSubCopy ?? answer.SubText,
|
buttonLabelSubCopy: 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),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ 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 };
|
||||||
|
|
|
||||||
8
src/constants/part-type-strings.js
Normal file
8
src/constants/part-type-strings.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
const partTypeStrings = {
|
||||||
|
FRONT_WIPER: "FRONT WIPER",
|
||||||
|
REAR_WIPER: "REAR WIPER",
|
||||||
|
RAIN_DEFENSE: "RAIN DEFENSE",
|
||||||
|
RECALIBRATION: "RECALIBRATION",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { partTypeStrings };
|
||||||
|
|
@ -31,7 +31,7 @@ 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 regexMatches = [...str.matchAll(regexExp)];
|
const regexMatches = [...str.matchAll(regexExp)];
|
||||||
const globalStateMatches = regexMatches.filter((match) => {
|
const globalStateMatches = regexMatches.filter((match) => {
|
||||||
return match[1] === dynamicStrings.GLOBAL_STATE;
|
return match[1] === dynamicStrings.GLOBAL_STATE;
|
||||||
|
|
@ -39,20 +39,13 @@ function mapStringToState(str) {
|
||||||
|
|
||||||
// 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)) {
|
||||||
|
|
@ -62,7 +55,6 @@ function mapStringToState(str) {
|
||||||
// Concatenate the string.
|
// Concatenate the string.
|
||||||
stringBuilder = `${stringBuilder} ${stringWithReplacement}`;
|
stringBuilder = `${stringBuilder} ${stringWithReplacement}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringBuilder.trimStart();
|
return stringBuilder.trimStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,6 +80,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] = processIfStatements(
|
||||||
|
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]);
|
||||||
}
|
}
|
||||||
|
|
@ -107,23 +104,217 @@ function processWidgetItemForReplacement(widgetModel, key) {
|
||||||
return widgetModel[key];
|
return widgetModel[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// If Statement Processing Logic //
|
||||||
|
///////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive function - replaces all instances of if statements from the CMS that utilize the specified ifConditionKeyword
|
||||||
|
* @param {*} str string - Input string to be processed
|
||||||
|
* @param {*} ifConditionKeyword string - Defines which if statements to process ex: 'globalState'
|
||||||
|
* @param {*} replacePlaceholderCallback function - Callback to replace CMS placeholder values
|
||||||
|
* @returns The processed string
|
||||||
|
*/
|
||||||
|
export function processIfStatements(str, ifConditionKeyword, replacePlaceholderCallback) {
|
||||||
|
const containsRelevantIfStatement = new RegExp("{if:" + ifConditionKeyword + ":.+?}", "g").test(
|
||||||
|
str
|
||||||
|
);
|
||||||
|
if (!containsRelevantIfStatement) {
|
||||||
|
return str;
|
||||||
|
} else {
|
||||||
|
const ifStatementRegexExpression = getIfStatementRegexExpression();
|
||||||
|
const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
|
||||||
|
const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(
|
||||||
|
ifStatementRegexMatches,
|
||||||
|
ifConditionKeyword
|
||||||
|
);
|
||||||
|
executeIfStatementAndSetProcessedStrings(
|
||||||
|
completeIfStatementArray,
|
||||||
|
replacePlaceholderCallback
|
||||||
|
);
|
||||||
|
const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
|
||||||
|
return processIfStatements(
|
||||||
|
reconstructedPostProcessedString,
|
||||||
|
ifConditionKeyword,
|
||||||
|
replacePlaceholderCallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
|
||||||
|
let index = 0;
|
||||||
|
for (const match of matches) {
|
||||||
|
if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) {
|
||||||
|
let interiorIndex = 0;
|
||||||
|
let nestedLevel = 0;
|
||||||
|
let elseStatementIndex = null;
|
||||||
|
for (const interiorMatch of matches.slice(index + 1)) {
|
||||||
|
if (interiorMatch.groups.isIfStatement) {
|
||||||
|
if (interiorMatch.groups.ifConditionType === ifConditionKeyword) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nestedLevel++;
|
||||||
|
}
|
||||||
|
} else if (interiorMatch.groups.isElseStatement) {
|
||||||
|
if (!nestedLevel) {
|
||||||
|
elseStatementIndex = interiorIndex + 1;
|
||||||
|
}
|
||||||
|
} else if (interiorMatch.groups.isEndStatement) {
|
||||||
|
if (nestedLevel) {
|
||||||
|
nestedLevel--;
|
||||||
|
} else {
|
||||||
|
const ifStatementArray = matches.slice(index, index + interiorIndex + 2);
|
||||||
|
flagMatchesForProcessing(ifStatementArray, elseStatementIndex);
|
||||||
|
return ifStatementArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interiorIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flagMatchesForProcessing(matches, elseStatementIndex) {
|
||||||
|
matches[0].isFlaggedForProcessing = true;
|
||||||
|
matches[matches.length - 1].isFlaggedForProcessing = true;
|
||||||
|
if (elseStatementIndex) {
|
||||||
|
matches[elseStatementIndex].isFlaggedForProcessing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinProcessedRegexArray(regexMatches) {
|
||||||
|
let processedString = "";
|
||||||
|
regexMatches.forEach((match) => {
|
||||||
|
const rawString = match[0];
|
||||||
|
processedString += match.groups.processedString ?? rawString;
|
||||||
|
});
|
||||||
|
return processedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlaceholderCallback) {
|
||||||
|
const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition);
|
||||||
|
let isInsideDesiredBlock = ifCondition;
|
||||||
|
|
||||||
|
ifStatementArray.forEach((entry) => {
|
||||||
|
if (entry.groups.isElseStatement && entry.isFlaggedForProcessing) {
|
||||||
|
isInsideDesiredBlock = !ifCondition;
|
||||||
|
} else if (entry.groups.isEndStatement && entry.isFlaggedForProcessing) {
|
||||||
|
isInsideDesiredBlock = true;
|
||||||
|
}
|
||||||
|
setProcessedStringOnEntry(entry, isInsideDesiredBlock);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProcessedStringOnEntry(entry, isInsideDesiredBlock) {
|
||||||
|
if (!isInsideDesiredBlock) {
|
||||||
|
entry.groups.processedString = "";
|
||||||
|
} else {
|
||||||
|
if (entry.groups.isIfStatement) {
|
||||||
|
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 {
|
||||||
|
entry.groups.processedString = entry.isFlaggedForProcessing
|
||||||
|
? entry.groups.endTrailingString
|
||||||
|
: entry[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIfStatementRegexExpression() {
|
||||||
|
// Matches but does not capture:
|
||||||
|
// {if:...} or {else} or {end}
|
||||||
|
const anyLogicOperatorNonCapture = "(?:{(?:end|else|if:.*?)})";
|
||||||
|
// NOTE: ?<variableName> syntax stores the captured match like so: match.groups.variableName
|
||||||
|
const matchStartOfString =
|
||||||
|
"(?<processedString>^.+?)" + // Match and Capture all characters (lazy), cannot be empty
|
||||||
|
"(?=(?:{if))"; // Looks ahead but does not capture {if
|
||||||
|
const matchIfOperator =
|
||||||
|
"(?<isIfStatement>{if:)" + // Match & Capture {if:
|
||||||
|
"(?<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 "}"
|
||||||
|
"(?<ifTrailingString>.*?)" + // Match and Capture all characters (lazy), can be empty
|
||||||
|
"(?=" +
|
||||||
|
anyLogicOperatorNonCapture +
|
||||||
|
")"; // Looks ahead but does not capture the next logic operator
|
||||||
|
const matchElseOperator =
|
||||||
|
"(?<isElseStatement>{else})" + // Match & Capture {else}
|
||||||
|
"(?<elseTrailingString>.*?)" + // Match & Capture all characters (lazy), can be empty
|
||||||
|
"(?=" +
|
||||||
|
anyLogicOperatorNonCapture +
|
||||||
|
")"; // Looks ahead but does not capture the next logic operator
|
||||||
|
const matchEndOperator =
|
||||||
|
"(?<isEndStatement>{end})" + // Match & Capture {end}
|
||||||
|
"(?<endTrailingString>.*?)" + // Match & Capture all chracters (lazy), can be empty
|
||||||
|
"(?=" +
|
||||||
|
anyLogicOperatorNonCapture +
|
||||||
|
"|$)"; // Looks ahead but does not capture the next logic operator
|
||||||
|
// Combine all matching patterns, separated by "or" pipes
|
||||||
|
return new RegExp(
|
||||||
|
matchStartOfString +
|
||||||
|
"|" +
|
||||||
|
matchIfOperator +
|
||||||
|
"|" +
|
||||||
|
matchElseOperator +
|
||||||
|
"|" +
|
||||||
|
matchEndOperator,
|
||||||
|
"g"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// End of If Statement Processing Logic //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
|
||||||
export function doesCopyContainRouterLink(copy) {
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* splits copy on { ... } such as {routerlink: ...}
|
||||||
|
* @returns array of strings
|
||||||
|
*/
|
||||||
export function splitCopyOnCMSPlaceHolder(copy) {
|
export function splitCopyOnCMSPlaceHolder(copy) {
|
||||||
// splits copy on { ... } such as {routerlink: ...}
|
|
||||||
return copy.split(/{(.*?)}/g);
|
return copy.split(/{(.*?)}/g);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRouterLinkRouteFromCopy(copy) {
|
/**
|
||||||
|
* Returns string2 of input following this pattern: {string1:string2,string3}
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
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) {
|
/**
|
||||||
|
* Returns string3 of input following this pattern: {string1:string2,string3}
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
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'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
class="address-vehicles-question"
|
class="address-vehicles-question"
|
||||||
buttonType="listButton"
|
buttonTypeString="listButton"
|
||||||
groupName="ChooseAddressVehicle"
|
groupName="ChooseAddressVehicle"
|
||||||
:questionText="questionText"
|
:questionText="questionText"
|
||||||
:answers="vehicles"
|
:answers="vehicles"
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@
|
||||||
<span v-if="doesCopyContainRouterLink(copy)" class="text-body">
|
<span v-if="doesCopyContainRouterLink(copy)" class="text-body">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
query: { fmgPage: `${getRouterLinkRouteFromCopy(copy)}` },
|
query: { fmgPage: `${getLinkTargetFromCopy(copy)}` },
|
||||||
name: 'root',
|
name: 'root',
|
||||||
}"
|
}"
|
||||||
>{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link
|
>{{ 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>
|
||||||
|
|
@ -68,8 +68,8 @@ import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
|
||||||
import {
|
import {
|
||||||
doesCopyContainRouterLink,
|
doesCopyContainRouterLink,
|
||||||
splitCopyOnCMSPlaceHolder,
|
splitCopyOnCMSPlaceHolder,
|
||||||
getRouterLinkRouteFromCopy,
|
getLinkTargetFromCopy,
|
||||||
getRouterLinkDisplayTextFromCopy,
|
getLinkDisplayTextFromCopy,
|
||||||
} from "@/helpers/cms-content-helper";
|
} 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";
|
||||||
|
|
@ -153,8 +153,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 &&
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
cmsWidgetName="VinLookupMethod"
|
cmsWidgetName="VinLookupMethod"
|
||||||
:answers="answersFromCms"
|
:answers="answersFromCms"
|
||||||
groupName="vinLookupMethodOption"
|
groupName="vinLookupMethodOption"
|
||||||
buttonType="listButton"
|
buttonTypeString="listButton"
|
||||||
v-model="selectedVinLookupMethod"
|
v-model="selectedVinLookupMethod"
|
||||||
isRequired
|
isRequired
|
||||||
validationRules="option-required" />
|
validationRules="option-required" />
|
||||||
|
|
@ -174,6 +174,7 @@ export default {
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
if (this.isRepair) {
|
if (this.isRepair) {
|
||||||
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
|
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
|
||||||
|
//todo: validation
|
||||||
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);
|
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);
|
||||||
await this.dispatchStoreAction(
|
await this.dispatchStoreAction(
|
||||||
storeActions.SAVE_SERVICE_LOCATION,
|
storeActions.SAVE_SERVICE_LOCATION,
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
:answers="answersFromCms"
|
:answers="answersFromCms"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="listButtonHorizontal"
|
buttonTypeString="listButtonHorizontal"
|
||||||
v-model="selectedValues"
|
v-model="selectedValues"
|
||||||
isCashOrInsurance
|
additionalButtonStyling="listButtonHorizontalStrong"
|
||||||
isRequired />
|
isRequired />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -17,72 +17,23 @@
|
||||||
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
||||||
v-model="isInsuranceSelected"
|
v-model="isInsuranceSelected"
|
||||||
groupName="CashOrInsuranceQuestion" />
|
groupName="CashOrInsuranceQuestion" />
|
||||||
<radioServicePackage />
|
<servicePackageQuestion
|
||||||
<textBlock
|
ref="servicePackage"
|
||||||
cmsWidgetName="quoteDisclaimer"
|
cashCmsWidgetName="CashServicePackageQuestionWidget"
|
||||||
justifyText="left"
|
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
|
||||||
typeStyle="caption"
|
v-model="selectedPackage"
|
||||||
class="mt-4" />
|
groupName="ServicePackageQuestion"
|
||||||
<h3 style="m-0">h1</h3>
|
:availableLineItems="availableLineItems"
|
||||||
<textBlock
|
:isInsuranceSelected="isInsuranceSelected" />
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="h1"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">h2</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="h2"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">h3</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="h3"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">h4</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="h4"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">h5</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="h5"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">h6</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="h6"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">Body</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="body"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">Label</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="label"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">Small</h3>
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="quoteDisclaimer"
|
|
||||||
justifyText="left"
|
|
||||||
typeStyle="small"
|
|
||||||
class="mt-4" />
|
|
||||||
<h3 style="m-0">Caption</h3>
|
|
||||||
<textBlock
|
<textBlock
|
||||||
cmsWidgetName="quoteDisclaimer"
|
cmsWidgetName="quoteDisclaimer"
|
||||||
justifyText="left"
|
justifyText="left"
|
||||||
typeStyle="caption"
|
typeStyle="caption"
|
||||||
class="mt-4" />
|
class="mt-4" />
|
||||||
|
<modal cmsWidgetName="EstimateRainDefenseModal" />
|
||||||
|
<modal cmsWidgetName="EstimateFrontWiperModal" />
|
||||||
|
<modal cmsWidgetName="EstimateRearWiperModal" />
|
||||||
|
<modal cmsWidgetName="EstimateRecalModal" />
|
||||||
<funnel-footer
|
<funnel-footer
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
:isForwardActionDisabled="!meta.valid"
|
:isForwardActionDisabled="!meta.valid"
|
||||||
|
|
@ -100,10 +51,11 @@ 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 textBlock from "@/common-components/text-block/text-block";
|
import textBlock from "@/common-components/text-block/text-block";
|
||||||
import store from "@/store";
|
import modal from "@/common-components/modal/modal";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { Form } from "vee-validate";
|
import { Form } from "vee-validate";
|
||||||
|
|
@ -116,9 +68,9 @@ export default {
|
||||||
|
|
||||||
// TODOS - modify as needed, just a rough sketch to place initializations
|
// TODOS - modify as needed, just a rough sketch to place initializations
|
||||||
|
|
||||||
// get supporting lineitems
|
// get supporting availableLineItems
|
||||||
|
|
||||||
// get wiper and rain defense lineitems
|
// get wiper and rain defense availableLineItems
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
|
|
||||||
|
|
@ -129,13 +81,63 @@ 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) => {
|
||||||
vm.setCmsContent(cmsContent);
|
vm.setCmsContent(cmsContent);
|
||||||
vm.isInsuranceSelected = vm.getDefaultisInsuranceSelectedValue();
|
vm.availableLineItems = [
|
||||||
|
{
|
||||||
|
partNumber: "001",
|
||||||
|
Description: "Windshield with Recal",
|
||||||
|
partType: "Windshield",
|
||||||
|
Quantity: "1",
|
||||||
|
BasePartNumber: "001",
|
||||||
|
Color: "Green",
|
||||||
|
CanSafeliteRecalibrate: true,
|
||||||
|
price: 350.22,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partNumber: "SBB16",
|
||||||
|
description: "SAFELITE BEAM BLADE 16",
|
||||||
|
partType: "FRONT WIPER",
|
||||||
|
price: 32.64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partNumber: "A DISPOSAL FEE",
|
||||||
|
partType: "DISPOSAL FEE",
|
||||||
|
price: 15.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partNumber: "SBB26",
|
||||||
|
description: "SAFELITE BEAM BLADE 26",
|
||||||
|
partType: "FRONT WIPER",
|
||||||
|
price: 53.04,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partNumber: "SBBR12A",
|
||||||
|
description: "SAFELITE REAR BLADE 12A",
|
||||||
|
partType: "REAR WIPER",
|
||||||
|
price: 24.48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partNumber: "RAIN DEFENSE",
|
||||||
|
description: null,
|
||||||
|
partType: "RAIN DEFENSE",
|
||||||
|
price: 35.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partNumber: "RECAL STATIC",
|
||||||
|
Description: "Recalibration",
|
||||||
|
partType: "recalibration",
|
||||||
|
Quantity: "1",
|
||||||
|
price: 150.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
pricingRequestResult: null,
|
pricingRequestResult: null,
|
||||||
isInsurance: null,
|
isInsuranceSelected: null,
|
||||||
|
selectedPackage: null,
|
||||||
|
availableLineItems: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -143,38 +145,27 @@ export default {
|
||||||
return true;
|
return true;
|
||||||
//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);
|
||||||
},
|
},
|
||||||
getDefaultisInsuranceSelectedValue() {
|
getDefaultIsInsuranceSelectedValue() {
|
||||||
var defaultisInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance;
|
const defaultIsInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance;
|
||||||
if (defaultisInsuranceSelectedValue != null) {
|
if (defaultIsInsuranceSelectedValue != null) {
|
||||||
return defaultisInsuranceSelectedValue;
|
return defaultIsInsuranceSelectedValue;
|
||||||
} else {
|
} else {
|
||||||
return this.economyPackagePrice > 600;
|
return this.availableLineItems
|
||||||
|
? baseMixin.methods.getTierOnePackagePrice(this.availableLineItems) > 500
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
economyPackagePrice() {
|
|
||||||
//TODO build out the pricing logic CSR-504
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
selectedChipCountValues: {
|
|
||||||
get: function () {
|
|
||||||
return this.modelValue;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.$emit("update:modelValue", newValue);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
funnelFooter,
|
funnelFooter,
|
||||||
vehicleBanner,
|
vehicleBanner,
|
||||||
funnelSubHeader,
|
funnelSubHeader,
|
||||||
Form,
|
Form,
|
||||||
radioServicePackage,
|
|
||||||
textBlock,
|
textBlock,
|
||||||
cashOrInsuranceQuestion,
|
cashOrInsuranceQuestion,
|
||||||
|
servicePackageQuestion,
|
||||||
|
modal,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,244 @@
|
||||||
|
<template>
|
||||||
|
<buttonQuestion
|
||||||
|
:answers="servicePackageAnswers"
|
||||||
|
:groupName="groupName"
|
||||||
|
buttonTypeString="servicePackageRadio"
|
||||||
|
:buttonTypeObject="servicePackageRadio"
|
||||||
|
v-model="selectedValues"
|
||||||
|
isRequired />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
import { processIfStatements } from "@/helpers/cms-content-helper";
|
||||||
|
import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected";
|
||||||
|
import servicePackageRadio from "./service-package-radio/service-package-radio";
|
||||||
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
|
|
||||||
|
const packageNames = {
|
||||||
|
TIER_ONE: "TierOne",
|
||||||
|
TIER_TWO: "TierTwo",
|
||||||
|
TIER_THREE: "TierThree",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "servicePackageQuestion",
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
groupName: String,
|
||||||
|
cashCmsWidgetName: String,
|
||||||
|
insuranceCmsWidgetName: String,
|
||||||
|
isInsuranceSelected: Boolean,
|
||||||
|
availableLineItems: null,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
servicePackageRadio: servicePackageRadio,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedValues: {
|
||||||
|
get: function () {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.$emit("update:modelValue", newValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nullSafeAvailableLineItems() {
|
||||||
|
return this.availableLineItems ?? [];
|
||||||
|
},
|
||||||
|
servicePackageAnswers() {
|
||||||
|
const cmsWidgetName = this.isInsuranceSelected
|
||||||
|
? this.insuranceCmsWidgetName
|
||||||
|
: this.cashCmsWidgetName;
|
||||||
|
let cmsAnswersContent = this.getCmsContent(cmsWidgetName, "Answers");
|
||||||
|
if (!cmsAnswersContent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!this.shouldDisplayTierTwoPackage) {
|
||||||
|
cmsAnswersContent = cmsAnswersContent.filter(
|
||||||
|
(answer) => answer.Name != packageNames.TIER_TWO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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.lineItemsContainsPartType(partTypeStrings.RECALIBRATION);
|
||||||
|
},
|
||||||
|
frontWipersApplicableForTierTwo() {
|
||||||
|
const frontWipersAreAvailable = this.lineItemsContainsPartType(
|
||||||
|
partTypeStrings.FRONT_WIPER
|
||||||
|
);
|
||||||
|
const isRepair = this.$store.getters.order.damage.isRepair;
|
||||||
|
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation(
|
||||||
|
glassLocations.WINDSHIELD
|
||||||
|
);
|
||||||
|
if (frontWipersAreAvailable) {
|
||||||
|
if (isRepair) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (glassToReplaceContainsWindshield) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rearWiperApplicableForTierTwo() {
|
||||||
|
const rearWiperIsAvailable = this.lineItemsContainsPartType(partTypeStrings.REAR_WIPER);
|
||||||
|
return (
|
||||||
|
this.glassToReplaceContainsGlassLocation(glassLocations.REAR) &&
|
||||||
|
rearWiperIsAvailable
|
||||||
|
);
|
||||||
|
},
|
||||||
|
frontWipersApplicableForTierThree() {
|
||||||
|
const frontWipersAreAvailable = this.lineItemsContainsPartType(
|
||||||
|
partTypeStrings.FRONT_WIPER
|
||||||
|
);
|
||||||
|
return frontWipersAreAvailable;
|
||||||
|
},
|
||||||
|
rearWiperApplicableForTierThree() {
|
||||||
|
const rearWiperIsAvailable = this.lineItemsContainsPartType(partTypeStrings.REAR_WIPER);
|
||||||
|
return (
|
||||||
|
this.glassToReplaceContainsGlassLocation(glassLocations.REAR) &&
|
||||||
|
rearWiperIsAvailable
|
||||||
|
);
|
||||||
|
},
|
||||||
|
rainDefenseApplicableForTierThree() {
|
||||||
|
const frontWipersAreAvailable = this.lineItemsContainsPartType(
|
||||||
|
partTypeStrings.FRONT_WIPER
|
||||||
|
);
|
||||||
|
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation(
|
||||||
|
glassLocations.WINDSHIELD
|
||||||
|
);
|
||||||
|
const glassToReplaceContainsRearGlass = this.glassToReplaceContainsGlassLocation(
|
||||||
|
glassLocations.REAR
|
||||||
|
);
|
||||||
|
if (this.frontWipersApplicableForTierTwo) {
|
||||||
|
return true;
|
||||||
|
} else if (!frontWipersAreAvailable) {
|
||||||
|
return true;
|
||||||
|
} else if (!glassToReplaceContainsWindshield && !glassToReplaceContainsRearGlass) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shouldDisplayTierTwoPackage() {
|
||||||
|
return this.frontWipersApplicableForTierTwo || this.rearWiperApplicableForTierTwo;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
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.getTierOnePackagePrice(this.nullSafeAvailableLineItems);
|
||||||
|
if (packageName === packageNames.TIER_TWO) {
|
||||||
|
priceFloat += this.getTierTwoPackageVapsPrice();
|
||||||
|
} else if (packageName === packageNames.TIER_THREE) {
|
||||||
|
priceFloat += this.getTierThreePackageVapsPrice();
|
||||||
|
}
|
||||||
|
return priceFloat;
|
||||||
|
},
|
||||||
|
getTierTwoPackageVapsPrice() {
|
||||||
|
let vapsPrice = 0;
|
||||||
|
const priceFrontWipers = this.frontWipersApplicableForTierTwo;
|
||||||
|
const priceRearWipers = this.rearWiperApplicableForTierTwo;
|
||||||
|
this.nullSafeAvailableLineItems.forEach((item) => {
|
||||||
|
if (
|
||||||
|
(priceFrontWipers &&
|
||||||
|
item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER) ||
|
||||||
|
(priceRearWipers && item.partType.toUpperCase() === partTypeStrings.REAR_WIPER)
|
||||||
|
) {
|
||||||
|
vapsPrice += item.price;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return vapsPrice;
|
||||||
|
},
|
||||||
|
getTierThreePackageVapsPrice() {
|
||||||
|
let vapsPrice = 0;
|
||||||
|
const priceFrontWipers = this.frontWipersApplicableForTierThree;
|
||||||
|
const priceRearWipers = this.rearWiperApplicableForTierThree;
|
||||||
|
const priceRainDefense = this.rainDefenseApplicableForTierThree;
|
||||||
|
this.nullSafeAvailableLineItems.forEach((item) => {
|
||||||
|
if (
|
||||||
|
(priceFrontWipers &&
|
||||||
|
item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER) ||
|
||||||
|
(priceRearWipers &&
|
||||||
|
item.partType.toUpperCase() === partTypeStrings.REAR_WIPER) ||
|
||||||
|
(priceRainDefense &&
|
||||||
|
item.partType.toUpperCase() === partTypeStrings.RAIN_DEFENSE)
|
||||||
|
) {
|
||||||
|
vapsPrice += item.price;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return vapsPrice;
|
||||||
|
},
|
||||||
|
getCustomValueFromString(str) {
|
||||||
|
switch (str) {
|
||||||
|
case "isRecalibrationOnOrder":
|
||||||
|
return this.isRecalibrationOnOrder;
|
||||||
|
case "frontWipersApplicableForTierTwo":
|
||||||
|
return this.frontWipersApplicableForTierTwo;
|
||||||
|
case "rearWiperApplicableForTierTwo":
|
||||||
|
return this.rearWiperApplicableForTierTwo;
|
||||||
|
case "frontWipersApplicableForTierThree":
|
||||||
|
return this.frontWipersApplicableForTierThree;
|
||||||
|
case "rearWiperApplicableForTierThree":
|
||||||
|
return this.rearWiperApplicableForTierThree;
|
||||||
|
case "rainDefenseApplicableForTierThree":
|
||||||
|
return this.rainDefenseApplicableForTierThree;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lineItemsContainsPartType(partType) {
|
||||||
|
const partTypeMatches = this.nullSafeAvailableLineItems.filter(
|
||||||
|
(lineItem) => lineItem.partType.toUpperCase() === partType
|
||||||
|
);
|
||||||
|
return !!partTypeMatches.length;
|
||||||
|
},
|
||||||
|
glassToReplaceContainsGlassLocation(glassLocation) {
|
||||||
|
const glassLocationMatches = this.$store.getters.order.damage.glassToReplace.filter(
|
||||||
|
(glassToReplace) => glassToReplace.glassLocation === glassLocation
|
||||||
|
);
|
||||||
|
return !!glassLocationMatches.length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
buttonQuestion,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
<template>
|
||||||
|
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange">
|
||||||
|
<div class="package-label" for="testradio">
|
||||||
|
<div class="package-specs">
|
||||||
|
<p :class="[this.buttonLabelSubCopy ? 'mb-2' : 'm-0']">
|
||||||
|
<span v-html="this.buttonLabel"></span>
|
||||||
|
<span class="pricing-info" v-html="this.buttonAuxillaryCopy"></span>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class="sub-label m-0"
|
||||||
|
v-if="this.buttonLabelSubCopy"
|
||||||
|
v-html="this.buttonLabelSubCopy"></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="text"
|
||||||
|
:text="getLinkDisplayTextFromCopy(copy)"
|
||||||
|
href="#!"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
:data-bs-target="'#' + getLinkTargetFromCopy(copy)"
|
||||||
|
aria-label="Modal window" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!-- End of body text parsing -->
|
||||||
|
<div
|
||||||
|
class="package-footer"
|
||||||
|
v-if="this.buttonFooterCopy"
|
||||||
|
v-html="this.buttonFooterCopy"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</baseInputButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
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 {
|
||||||
|
getLinkDisplayTextFromCopy,
|
||||||
|
getLinkTargetFromCopy,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
} from "@/helpers/cms-content-helper";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "servicePackageRadio",
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
components: {
|
||||||
|
baseInputButton,
|
||||||
|
textLink,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
arrayOfListItemsFromBodyText() {
|
||||||
|
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getLinkDisplayTextFromCopy,
|
||||||
|
getLinkTargetFromCopy,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.package-main {
|
||||||
|
.package-wrapper {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
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: 0.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-footer {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #d4281c;
|
||||||
|
margin-left: -32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 0;
|
||||||
|
transition: all 0.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: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
isMultiSelect
|
isMultiSelect
|
||||||
:answers="answersToDisplay"
|
:answers="answersToDisplay"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="listCard"
|
buttonTypeString="listCard"
|
||||||
isRequired
|
isRequired
|
||||||
v-model="selectedValues"
|
v-model="selectedValues"
|
||||||
validationRules="damage-location-required" />
|
validationRules="damage-location-required" />
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
:isMultiSelect="isMultiSelect"
|
:isMultiSelect="isMultiSelect"
|
||||||
:answers="answersToDisplay"
|
:answers="answersToDisplay"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="listCard"
|
buttonTypeString="listCard"
|
||||||
v-model="selectedValues"
|
v-model="selectedValues"
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
:suppressError="suppressError"
|
:suppressError="suppressError"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
isMultiSelect
|
isMultiSelect
|
||||||
:answers="answersToDisplay"
|
:answers="answersToDisplay"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="listCard"
|
buttonTypeString="listCard"
|
||||||
v-model="selectedDoorSidesValues"
|
v-model="selectedDoorSidesValues"
|
||||||
validationRules="damage-side-required"
|
validationRules="damage-side-required"
|
||||||
isRequired />
|
isRequired />
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
:questionText="questionText"
|
:questionText="questionText"
|
||||||
:answers="answersFromCms"
|
:answers="answersFromCms"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="listButtonHorizontal"
|
buttonTypeString="listButtonHorizontal"
|
||||||
useTextForValue
|
useTextForValue
|
||||||
v-model="selectedValue"
|
v-model="selectedValue"
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
:questionText="questionText"
|
:questionText="questionText"
|
||||||
:answers="answersFromCms"
|
:answers="answersFromCms"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="listCard"
|
buttonTypeString="listCard"
|
||||||
v-model="selectedValues"
|
v-model="selectedValues"
|
||||||
:suppressError="suppressError"
|
:suppressError="suppressError"
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
v-model="selectedTint"
|
v-model="selectedTint"
|
||||||
:answers="tintSelectionOptions"
|
:answers="tintSelectionOptions"
|
||||||
buttonType="listCard"
|
buttonTypeString="listCard"
|
||||||
:isWide="true"
|
:isWide="true"
|
||||||
altText=""
|
altText=""
|
||||||
isRequired
|
isRequired
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
v-model="selectedPartNumber"
|
v-model="selectedPartNumber"
|
||||||
buttonType="radio"
|
buttonTypeString="radio"
|
||||||
class="radioQuestion"
|
class="radioQuestion"
|
||||||
:questionText="glassFeatureQuestion"
|
:questionText="glassFeatureQuestion"
|
||||||
:answers="featureListData[selectedTint]"
|
:answers="featureListData[selectedTint]"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||||
import { routerParams } from "@/router/router-constants/router-params";
|
import { routerParams } from "@/router/router-constants/router-params";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { dynamicStrings } from "@/constants/dynamic-strings";
|
import { dynamicStrings } from "@/constants/dynamic-strings";
|
||||||
|
import { partTypeStrings } from "../constants/part-type-strings";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -61,6 +62,20 @@ export default {
|
||||||
state: serviceZipValidationResponse.data.state,
|
state: serviceZipValidationResponse.data.state,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getTierOnePackagePrice(lineItems) {
|
||||||
|
let totalPrice = 0;
|
||||||
|
lineItems.forEach((lineItem) => {
|
||||||
|
const partType = lineItem.partType.toUpperCase();
|
||||||
|
if (
|
||||||
|
partType != partTypeStrings.FRONT_WIPER &&
|
||||||
|
partType != partTypeStrings.REAR_WIPER &&
|
||||||
|
partType != partTypeStrings.RAIN_DEFENSE
|
||||||
|
) {
|
||||||
|
totalPrice += lineItem.price;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return totalPrice;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
storeActions() {
|
storeActions() {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
buttonImageId: String,
|
buttonImageId: String,
|
||||||
altText: {
|
altText: {
|
||||||
|
|
@ -18,6 +21,7 @@ export default {
|
||||||
textPosition: String,
|
textPosition: String,
|
||||||
screenReaderOnlyText: String,
|
screenReaderOnlyText: String,
|
||||||
isWide: Boolean,
|
isWide: Boolean,
|
||||||
|
additionalButtonStyling: String,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
selectedValue: {
|
selectedValue: {
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,12 @@ 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) => {
|
pageData: (state) => (page) => {
|
||||||
return state.applicationUser.pageData[page];
|
return state.applicationUser.pageData[page];
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
query: { fmgPage: `${getRouterLinkRouteFromCopy(copy)}` },
|
query: { fmgPage: `${getLinkTargetFromCopy(copy)}` },
|
||||||
name: 'root',
|
name: 'root',
|
||||||
}"
|
}"
|
||||||
>{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link
|
>{{ getLinkDisplayTextFromCopy(copy) }}</router-link
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -41,8 +41,8 @@
|
||||||
import {
|
import {
|
||||||
doesCopyContainRouterLink,
|
doesCopyContainRouterLink,
|
||||||
splitCopyOnCMSPlaceHolder,
|
splitCopyOnCMSPlaceHolder,
|
||||||
getRouterLinkRouteFromCopy,
|
getLinkTargetFromCopy,
|
||||||
getRouterLinkDisplayTextFromCopy,
|
getLinkDisplayTextFromCopy,
|
||||||
splitCMSCopyOnParagraphTag,
|
splitCMSCopyOnParagraphTag,
|
||||||
} from "@/helpers/cms-content-helper";
|
} from "@/helpers/cms-content-helper";
|
||||||
|
|
||||||
|
|
@ -92,8 +92,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();
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,12 @@ describe("list-button-horizontal.vue", () => {
|
||||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is cash or insurance button => has 'radio-fancy' class", () => {
|
it("is cash or insurance button => has 'strong' class", () => {
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
const { wrapper } = setupMocks({
|
const { wrapper } = setupMocks({
|
||||||
mockData: {
|
mockData: {
|
||||||
propsData: {
|
propsData: {
|
||||||
isCashOrInsurance: true,
|
additionalButtonStyling: "listButtonHorizontalStrong",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -65,7 +65,7 @@ describe("list-button-horizontal.vue", () => {
|
||||||
// Assert
|
// Assert
|
||||||
const label = wrapper.find("label");
|
const label = wrapper.find("label");
|
||||||
|
|
||||||
expect(label.classes()).toContain("radio-fancy");
|
expect(label.classes()).toContain("strong");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("has buttonLabel => displays buttonLabel", () => {
|
test("has buttonLabel => displays buttonLabel", () => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
:buttonWrapperClasses="[
|
:buttonWrapperClasses="[
|
||||||
'list-group list-button-horizontal d-flex flex-column w-100',
|
'list-group list-button-horizontal d-flex flex-column w-100',
|
||||||
{ 'radio-fancy': isCashOrInsurance },
|
{ strong: isStrongStyling },
|
||||||
]"
|
]"
|
||||||
v-model="selectedValue">
|
v-model="selectedValue">
|
||||||
<div
|
<div
|
||||||
|
|
@ -28,8 +28,10 @@ import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
export default {
|
export default {
|
||||||
name: "listButtonHorizontal",
|
name: "listButtonHorizontal",
|
||||||
mixins: [inputButtonWrapperMixin],
|
mixins: [inputButtonWrapperMixin],
|
||||||
props: {
|
computed: {
|
||||||
isCashOrInsurance: Boolean,
|
isStrongStyling() {
|
||||||
|
return this.additionalButtonStyling === "listButtonHorizontalStrong";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
baseInputButton,
|
baseInputButton,
|
||||||
|
|
@ -104,7 +106,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cash/Insurance option radio button styling
|
// Cash/Insurance option radio button styling
|
||||||
&.radio-fancy {
|
&.strong {
|
||||||
.list-button-horizontal-content {
|
.list-button-horizontal-content {
|
||||||
border: 1px solid $blue-700;
|
border: 1px solid $blue-700;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
@ -194,7 +196,7 @@ export default {
|
||||||
|
|
||||||
//Cash/insurance styling
|
//Cash/insurance styling
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
.list-button-horizontal.radio-fancy {
|
.list-button-horizontal.strong {
|
||||||
input[type="radio"] {
|
input[type="radio"] {
|
||||||
&:checked + .list-button-horizontal-content {
|
&:checked + .list-button-horizontal-content {
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
|
|
@ -210,7 +212,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
.list-button-horizontal.radio-fancy {
|
.list-button-horizontal.strong {
|
||||||
input[type="radio"] {
|
input[type="radio"] {
|
||||||
&:checked + .list-button-horizontal-content {
|
&:checked + .list-button-horizontal-content {
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
|
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="package-main">
|
|
||||||
<div class="package-wrapper">
|
|
||||||
<input type="radio" name="test-radio" id="testradio" />
|
|
||||||
<label for="testradio">
|
|
||||||
<div class="package-specs">
|
|
||||||
<p class="m-0">Economy service <span>As little as $0.00</span></p>
|
|
||||||
<ul>
|
|
||||||
<li>New replacement windshield</li>
|
|
||||||
<li>Expert installation</li>
|
|
||||||
<li>Nationwide lifetime warranty</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="package-wrapper">
|
|
||||||
<input type="radio" name="test-radio" id="testradio-2" />
|
|
||||||
<label for="testradio-2">
|
|
||||||
<div class="package-specs">
|
|
||||||
<p class="mb-2">Standard service <span>As little as 59.97</span></p>
|
|
||||||
<p class="sub-label m-0">most popular</p>
|
|
||||||
<ul>
|
|
||||||
<li>New replacement windshield</li>
|
|
||||||
<li>
|
|
||||||
<textLink
|
|
||||||
linkType="text"
|
|
||||||
text="Rain Defense"
|
|
||||||
href="#!"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#EstimateRainDefenseModal"
|
|
||||||
aria-label="Modal window" />
|
|
||||||
treatment
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
New
|
|
||||||
<textLink
|
|
||||||
linkType="text"
|
|
||||||
text="front wiper blades"
|
|
||||||
href="#!"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#EstimateFrontWiperModal"
|
|
||||||
aria-label="Modal window" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
New
|
|
||||||
<textLink
|
|
||||||
linkType="text"
|
|
||||||
text="rear wiper blade"
|
|
||||||
href="#!"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#EstimateRearWiperModal"
|
|
||||||
aria-label="Modal window" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Expert
|
|
||||||
<textLink
|
|
||||||
linkType="text"
|
|
||||||
text="recalibration"
|
|
||||||
href="#!"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#EstimateRecalModal"
|
|
||||||
aria-label="Modal window" />
|
|
||||||
</li>
|
|
||||||
<li>Nationwide lifetime warranty</li>
|
|
||||||
<li>New wiper blades</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="package-wrapper">
|
|
||||||
<input type="radio" name="test-radio" id="testradio-3" />
|
|
||||||
<label for="testradio-3">
|
|
||||||
<div class="package-specs">
|
|
||||||
<p class="mb-2">Premium service <span>As little as $94.97</span></p>
|
|
||||||
<ul>
|
|
||||||
<li>New replacement windshield</li>
|
|
||||||
<li>Expert installation</li>
|
|
||||||
<li>Nationwide lifetime warranty</li>
|
|
||||||
<li>New wiper blades</li>
|
|
||||||
<li>Rain Defense</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<modal cmsWidgetName="EstimateRainDefenseModal" />
|
|
||||||
<modal cmsWidgetName="EstimateFrontWiperModal" />
|
|
||||||
<modal cmsWidgetName="EstimateRearWiperModal" />
|
|
||||||
<modal cmsWidgetName="EstimateRecalModal" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import textLink from "@/ux-components/text-link/text-link";
|
|
||||||
import modal from "@/common-components/modal/modal";
|
|
||||||
export default {
|
|
||||||
name: "radioServicePackage",
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isActive: false,
|
|
||||||
hasArrow: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleClass: function (event) {
|
|
||||||
this.isActive = !this.isActive;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
textLink,
|
|
||||||
modal,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.package-main {
|
|
||||||
.package-wrapper {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
input[type="radio"] {
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
left: -9999px;
|
|
||||||
|
|
||||||
+ 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: 0.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 {
|
|
||||||
+ label {
|
|
||||||
&:before {
|
|
||||||
border: 1px solid #8e9292;
|
|
||||||
box-shadow: 0px 0px 0px 4px #9fcee6, 0px 1px 4px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked {
|
|
||||||
+ label {
|
|
||||||
.package-specs {
|
|
||||||
max-height: 1000px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+ label {
|
|
||||||
background-color: $blue-100;
|
|
||||||
border: 1px solid $blue;
|
|
||||||
max-height: 500px;
|
|
||||||
}
|
|
||||||
+ label {
|
|
||||||
&:before {
|
|
||||||
box-shadow: 0px 0px 0px 1px $blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+ label {
|
|
||||||
&:after {
|
|
||||||
background: $blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
+ label {
|
|
||||||
&:before {
|
|
||||||
border: 2px solid $blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.package-specs {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 0;
|
|
||||||
transition: all 0.75s ease;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-weight: 500;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
span {
|
|
||||||
color: $green;
|
|
||||||
}
|
|
||||||
&.sub-label {
|
|
||||||
color: $green;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
margin: 1rem 0 0 -15px;
|
|
||||||
padding: 0;
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
Reference in a new issue