Merge pull request #246 from Safelite/feature/digital/SSR-313

Feature/digital/ssr 313
This commit is contained in:
DavidAtSafelite 2023-04-20 09:56:57 -04:00 committed by GitHub
commit 080b9b4b1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1584 additions and 673 deletions

3
.gitignore vendored
View file

@ -24,4 +24,5 @@ pnpm-debug.log*
# Misc
coverage/*
junit.xml
junit.xml
/.vs

View file

@ -14,5 +14,5 @@
@import "@/styles/common-error-styles.scss";
@import "@/styles/common-animations.scss";
@import "@/styles/shared-input-button-styles.scss";
@import "@/styles/client-customizations.scss"
@import "@/styles/client-customizations.scss";
</style>

View file

@ -11,6 +11,7 @@ const applicationConfig = {
CLIENTTAG_QUERYSTRING: 'CientTag',
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
ISS_DEV_CMS_DOMAIN: "https://digitalisscms.dev.safelite.io",
CASH_PARENT_ACCOUNT_NUMBER: 167132
};
export { applicationConfig };

View file

@ -2,7 +2,7 @@ const damageCustomLabels = Object.freeze({
MATCH: 'match',
REAR_WINDOW: 'rear window',
SIDE_WINDOW: 'side window',
WINDSHIELD: 'windshield',
WINDSHIELD: 'windshield'
});
export default damageCustomLabels;

View file

@ -3,7 +3,7 @@ const damageLocationsCms = {
SIDEDOOR: "SIDEDOOR",
REARWINDOW: "REARWINDOW",
DRIVERSIDE: "DRIVERSIDE",
PASSENGERSIDE: "PASSENGERSIDE",
PASSENGERSIDE: "PASSENGERSIDE"
};
export { damageLocationsCms };

View file

@ -15,7 +15,7 @@ const damageLocationsSelected = {
DRIVERSIDE: "DriverSide",
PASSENGERSIDE: "PassengerSide",
STATIONARY: "Stationary",
SLIDER: "Slider",
SLIDER: "Slider"
};
export { damageLocationsSelected };

View file

@ -2,7 +2,8 @@ const dynamicStrings = {
GLOBAL_STATE: "globalState",
CUSTOM: "custom",
ROUTER_LINK: "routerLink:",
MODAL_LINK: "modalLink"
MODAL_LINK: "modalLink",
TEXT_LINK: 'textLink'
};
export { dynamicStrings };

View file

@ -18,8 +18,8 @@ const customMappings = {
{ key: "Passenger Back", transformedValue: "passenger side back door" },
{ key: "Passenger Vent", transformedValue: "passenger side vent glass" },
{ key: "Passenger Quarter", transformedValue: "passenger side quarter panel" },
{ key: "Passenger SlideDoor", transformedValue: "passenger side sliding door" },
],
{ key: "Passenger SlideDoor", transformedValue: "passenger side sliding door" }
]
};
// Gets an instance of a string where the dynamic portion of the text {custom:KeyName}

View file

@ -39,6 +39,10 @@ const endpoints = {
url: "/parts/api/v1/parts/parts",
method: "POST",
},
GetPriceOrderItems: {
url: '/price/api/v1/price/order-items',
method: 'GET'
},
GetCapabilityQuestions: {
url: "/parts/api/v1/parts/capability-questions",
method: "GET",
@ -46,7 +50,19 @@ const endpoints = {
GetPartFromCapabilityAnswer: {
url: "/parts/api/v1/parts/part-from-capability-answer",
method: "POST",
},
},
GetWipers: {
url: '/parts/api/v1/parts/wipers',
method: 'GET'
},
GetRainDefense: {
url: '/parts/api/v1/parts/rain-defense',
method: 'GET'
},
GetSupportingItems: {
url: '/parts/api/v1/parts/supporting-items',
method: 'POST'
},
GetVehicle: {
url: "/vehicle/api/v1/vehicle/lookup",
method: "GET",
@ -64,20 +80,20 @@ const endpoints = {
method: "POST",
},
LookupVehicleByVin: {
url: "/vehicle/api/v1/vehicle/lookup",
method: "POST",
url: "/vehicle/api/v1/vehicle/lookup",
method: "POST",
},
LookupVinByAddress: {
url: "/vehicle/api/v1/vehicle/lookup-vin-by-address",
method: "POST",
method: "POST",
},
LookupVinByPlate: {
url: "/vehicle/api/v1/vehicle/lookup-vin-by-plate",
method: "POST",
},
InitializeSession: {
url: "/analytics/api/v1/analytics/initialize",
method: "POST",
url: "/analytics/api/v1/analytics/initialize",
method: "POST",
},
GetExperimentsByUser: {
url: "/analytics/api/v1/analytics/get-experiments",

View file

@ -36,7 +36,7 @@ const errorMessages = {
DAMAGE_OPTION_REQUIRED: "Please select an option",
POLICYHOLDER_FIRST_NAME_REQUIRED: "Please enter the policyholder first name",
POLICYHOLDER_LAST_NAME_REQUIRED: "Please enter the policyholder last name",
POLICYHOLDER_LAST_NAME_REQUIRED: "Please enter the policyholder last name"
};

View file

@ -1,17 +1,17 @@
const globalEvents = {
Categories: {
GLOBAL_ALERT: "GLOBAL_ALERT",
GLOBAL_ALERT: "GLOBAL_ALERT"
},
SubCategories: {
PAGE_NOT_FOUND: "PAGE_NOT_FOUND",
},
PAGE_NOT_FOUND: "PAGE_NOT_FOUND"
}
};
const globalEventTypes = {
Success: "alert-success",
Warning: "alert-warning",
Info: "alert-info",
Danger: "alert-danger",
Danger: "alert-danger"
};
export { globalEvents, globalEventTypes };

View file

@ -1,14 +1,14 @@
const experimentUniverses = {
ISS_FUNNEL: "ISSFunnel",
ISS_FUNNEL: "ISSFunnel"
};
const experimentSettings = {
GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index",
GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index"
};
const experimentTriggers = {
SITE_ENTRY: "SiteEntry",
PAGE_ENTRY: "PageEntry",
PAGE_ENTRY: "PageEntry"
};
export { experimentUniverses, experimentSettings, experimentTriggers };

View file

@ -1,3 +1,3 @@
export const headerKeys = {
EXPERIMENT: "X-Experiment-Data",
EXPERIMENT: "X-Experiment-Data"
};

View file

@ -3,8 +3,8 @@
const endpoints = {
GetRouteInfo: {
url: "https://mockey.qa.sagaws.net/service/ISS/Content/RouteInfo",
method: "GET",
},
method: "GET"
}
};

View file

@ -0,0 +1,8 @@
const partTypeStrings = {
FRONT_WIPER: 'FRONT WIPER',
REAR_WIPER: 'REAR WIPER',
RAIN_DEFENSE: 'RAIN DEFENSE',
RECALIBRATION: 'RECALIBRATION'
};
export { partTypeStrings };

View file

@ -49,5 +49,5 @@ export const states = {
WA: "Washington",
WV: "West Virginia",
WI: "Wisconsin",
WY: "Wyoming",
WY: "Wyoming"
};

View file

@ -34,7 +34,7 @@ const tintMap = {
{ name: "gray tint privacy", src: "Glass-NoShade-GrayTint.svg" },
// No shade or tint
{ name: "clear", src: "Glass-NoShade-NoTint.svg" },
{ name: "clear", src: "Glass-NoShade-NoTint.svg" }
],
windshield: [
@ -69,8 +69,8 @@ const tintMap = {
{ name: "gray tint privacy", src: "Windshield-NoShade-GrayTint.svg" },
// No shade or tint
{ name: "clear", src: "Windshield-NoShade-NoTint.svg" },
],
{ name: "clear", src: "Windshield-NoShade-NoTint.svg" }
]
};
// Gets the tint image source string given the glass location, and the tint description (like 'Green Tint')

View file

@ -5,7 +5,7 @@ const vehicleCategories = {
COMMERCIALVAN: "COMMERCIAL VAN",
SUV: "SUV",
MOTORHOME: "MOTOR HOME",
SEMI: "SEMI",
SEMI: "SEMI"
};
export { vehicleCategories };

View file

@ -1,7 +1,7 @@
const vinLookupMethodSelections = Object.freeze({
MANUALVIN: 'ManualVin',
LICENSEPLATE: 'LicensePlate',
HOMEADDRESS: 'HomeAddress',
HOMEADDRESS: 'HomeAddress'
});
export {vinLookupMethodSelections};

View file

@ -37,7 +37,7 @@
:buttonLabel="answer.buttonLabel"
:buttonLabelSubCopy="answer.buttonLabelSubCopy"
:buttonBodyCopy="answer.buttonBodyCopy"
:buttonAuxillaryCopy="answer.buttonAuxillaryCopy"
:buttonAuxiliaryCopy="answer.buttonAuxiliaryCopy"
:buttonFooterCopy="answer.buttonFooterCopy"
:buttonImage="answer.buttonImage"
:buttonImageId="answer.buttonImageId"
@ -194,7 +194,7 @@ export default {
altText: answer.altText ?? (answer.Name ? answer.Name : answer),
buttonLabelSubCopy: answer.buttonLabelSubCopy ?? answer.SubText,
buttonBodyCopy: answer.buttonBodyCopy ?? answer.buttonBodyCopy,
buttonAuxillaryCopy: answer.buttonAuxillaryCopy ?? answer.buttonAuxillaryCopy,
buttonAuxiliaryCopy: answer.buttonAuxiliaryCopy ?? answer.buttonAuxiliaryCopy,
buttonFooterCopy: answer.buttonFooterCopy ?? answer.buttonFooterCopy,
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
buttonImageId: answer.buttonImageId ?? answer.ImageId,

View file

@ -1,4 +1,4 @@
import { dynamicStrings } from "@/constants/dynamic-strings";
import { dynamicStrings } from '@/constants/dynamic-strings';
import { useMainStore } from '@/store';
export function fetchCmsContentForPage(issPage) {
@ -18,7 +18,7 @@ export function fetchCmsContentForPage(issPage) {
}
else {
// Else get the client override page.
const pageName = issPage + "_" + clientName.toLowerCase().replace(/ /g, "");
const pageName = issPage + '_' + clientName.toLowerCase().replace(/ /g, '');
return store.getPageData(pageName)
.then(
@ -26,7 +26,8 @@ export function fetchCmsContentForPage(issPage) {
// Process the client override if it exists.
return processPageData(baseResponse, clientResponse);
},
(error) => {
(error) => {
console.error(error);
// Process the just the base if no client override exists.
return processPageData(baseResponse, null);
}
@ -44,8 +45,11 @@ function processPageData(baseResponse, clientResponse) {
const pageDataFromCms = {};
let widgets = [];
if ( clientResponse === null )
{
if (!baseResponse?.data?.Result) {
console.error('No result data found'); // Something has gone terribly wrong.
return {}
}
if (!clientResponse?.data?.Result) {
widgets = baseResponse.data.Result;
}
else
@ -89,7 +93,7 @@ function processPageData(baseResponse, clientResponse) {
widgets.forEach((widget) => {
// Global state value replacement.
let widgetWithReplacements = findAndReplaceGlobalStateValues(
const widgetWithReplacements = findAndReplaceGlobalStateValues(
widget.Model,
widget.Name
);
@ -103,7 +107,7 @@ function processPageData(baseResponse, clientResponse) {
}
pageDataFromCms[widgetWithReplacements.Name] = [
widgetWithReplacements.Model,
widgetWithReplacements.Model
];
});
@ -121,7 +125,7 @@ function processPageData(baseResponse, clientResponse) {
function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
const objWithReplacements = {
Name: widgetName,
Model: {},
Model: {}
};
Object.keys(widgetModel).forEach((key) => {
@ -140,13 +144,13 @@ function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
// This is a recursive function, it will call itself until it runs out of items to iterate on given the object.
function processWidgetItemForReplacement(widgetModel, key) {
// 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)) {
widgetModel[key] = mapStringToState(widgetModel[key]);
}
@ -158,7 +162,7 @@ function processWidgetItemForReplacement(widgetModel, key) {
// If we have an object. array, etc
if (
typeof widgetModel[key] === "object" &&
typeof widgetModel[key] === 'object' &&
Object.keys(widgetModel[key]).length
) {
Object.keys(widgetModel[key]).forEach((item) => {
@ -173,47 +177,43 @@ function processWidgetItemForReplacement(widgetModel, key) {
}
function mapStringToModal(str) {
let startIndex = str.indexOf("{" + dynamicStrings.MODAL_LINK);
let linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf("}") + 1);
const startIndex = str.indexOf('{' + dynamicStrings.MODAL_LINK);
const linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
let params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length -1);
let splitParams = params.split(",");
const params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length -1)
const splitParams = params.split(',');
const bodyText = '<a modalTarget="' + splitParams[0] + '" class="modal-text" aria-label="Modal window">' + splitParams[1] + '</a>';
let bodyText = '<a modalTarget="' + splitParams[0] + '" class="modal-text" aria-label="Modal window">' + splitParams[1] + '</a>';
let returnVal = str.replace(linkToReplace, bodyText);
if (returnVal.includes(dynamicStrings.MODAL_LINK)) {
returnVal = mapStringToModal(returnVal);
returnVal = mapStringToModal(returnVal)
}
return returnVal;
return returnVal
}
// Function to convert a string, into a matching global state item.
function mapStringToState(str) {
// Pull all matches out of the string.
const regexExp = new RegExp("{(.*?):(.*?)}", "g");
const regexExp = new RegExp('{([^{}]*?):([^{}]*?)}', 'g');
const regexMatches = [...str.matchAll(regexExp)];
const globalStateMatches = regexMatches.filter(match => {
return match[1] === dynamicStrings.GLOBAL_STATE;
});
// Our final string value that will be built from the matches.
let stringBuilder = "";
let stringBuilder = '';
for (const match of globalStateMatches) {
// Reset store state for each match.
let storeState = useMainStore();
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 valueFromStore = getStoreValueFromString(match[2]);
if (!valueFromStore) {
console.warning('Unable to resolve global state data.');
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 (stringWithReplacement.includes(dynamicStrings.GLOBAL_STATE)) {
@ -227,18 +227,19 @@ function mapStringToState(str) {
return stringBuilder.trimStart();
}
function getStoreValueFromString(str) {
let storeOrStateObject = useMainStore();
for (const s of str.split('.')) {
if (s === 'getters') continue;
if (storeOrStateObject[s] != undefined) {
storeOrStateObject = storeOrStateObject[s];
} else {
return ''
}
}
return storeOrStateObject;
if (!str) return '';
let storeOrStateObject = useMainStore();
for (const s of str.split('.')) {
if (s === 'getters') continue; //For backward compatability
if (storeOrStateObject[s] != undefined) {
storeOrStateObject = storeOrStateObject[s];
} else {
break;
}
}
return storeOrStateObject ?? '';
}
///////////////////////////////////
@ -246,173 +247,179 @@ function getStoreValueFromString(str) {
///////////////////////////////////
/**
* 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
*/
* 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
);
//str = str.replace(/\r?\n|\r/g, '');
const containsRelevantIfStatement = new RegExp('{if:' + ifConditionKeyword + ':.+?}', 'g').test(
str
);
const hasEmbeddedCrLf = /\r?\n|\r/g.test(str);
if (!containsRelevantIfStatement || hasEmbeddedCrLf) {
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
);
}
const hasEmbeddedCrLf = /\r?\n|\r/g.test(str);
if (hasEmbeddedCrLf) {
console.warning('Processing of conditional string was skipped. String has embedded carriage return/linefeed.');
}
if (!containsRelevantIfStatement || hasEmbeddedCrLf) {
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++;
}
console.error('Did not find end in conditional logic');
return matches;
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++;
}
console.error('Did not find end in conditional logic');
return matches;
}
function flagMatchesForProcessing(matches, elseStatementIndex) {
matches[0].isFlaggedForProcessing = true;
matches[matches.length - 1].isFlaggedForProcessing = true;
if (elseStatementIndex) {
matches[elseStatementIndex].isFlaggedForProcessing = true;
}
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;
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;
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);
});
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];
}
}
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'
);
// 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 doesCopyContainTextLink(copy) {
return copy.includes(dynamicStrings.TEXT_LINK);
}
export function setupModalLinks(context) {
context.$nextTick(() => {
const elements = document.getElementsByClassName("modal-text")
@ -430,23 +437,35 @@ export function doesCopyContainRouterLink(copy) {
return copy.includes(this.dynamicStrings.ROUTER_LINK);
}
/**
* splits copy on { ... } such as {routerlink: ...}
* @returns array of strings
*/
export function splitCopyOnCMSPlaceHolder(copy) {
// splits copy on { ... } such as {routerlink: ...}
return copy.split(/{(.*?)}/g);
}
/**
* Returns string2 of input following this pattern: {string1:string2,string3}
* @returns string
*/
export function getRouterLinkRouteFromCopy(copy) {
// sample input: {routerLink:estimate,provide your VIN}
// first split would return 'estimate,provide your VIN'
// second split would return 'estimate'
return copy.split(":")[1].split(",")[0];
return copy.split(':')[1].split(',')[0];
}
/**
* Returns string3 of input following this pattern: { string1: string2, string3 }
* @returns string
*/
export function getRouterLinkDisplayTextFromCopy(copy) {
// sample input: {routerLink: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];
return copy.split(':')[1].split(',')[1];
}
// Copy returned from the CMS that has newlines will return blocks wrapped in
@ -455,5 +474,5 @@ export function getRouterLinkDisplayTextFromCopy(copy) {
// attributes present
export function splitCMSCopyOnParagraphTag(copy) {
// filter removes empty strings that are a result of string.split with regex
return copy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter((paragraph) => paragraph !== "");
return copy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter((paragraph) => paragraph !== '');
}

View file

@ -3,7 +3,7 @@
<img id="siteFooterImage" :src="footerImageURL" />
</div>
<footer class="footer container-fluid fixed-bottom g-5 bg-light py-4" id="infoBox">
<div class="row d-flex flex-row-reverse align-items-center vw-100">
<div class="row d-flex flex-row-reverse align-items-center vw-100 mx-0">
<div class="col button-col d-flex" id="stacked" >
<buttonMain
v-if="!isForwardButtonHidden"

View file

@ -30,13 +30,13 @@ export default({
isDismissible: false,
messageCopy: "",
messageHeadline: "",
type: "",
type: ""
},
headerAnswers: null
};
},
props: {
cmsWidgetName: String,
cmsWidgetName: String
},
computed: {
imageSrc()
@ -51,7 +51,7 @@ export default({
return {
"background-color": this.headerAnswers ? this.headerAnswers.HexCode : "#FFFFFF"
};
},
}
},
mounted() {
const answers = this.getCmsContent(this.cmsWidgetName, "Answers");

View file

@ -13,11 +13,14 @@ describe("site sub header", () => {
};
it("should contain the cms content", () => {
const wrapper = shallowMount(siteSubHeader, {
const wrapper = shallowMount(siteSubHeader, {
propsData: {
justification: '',
issContainingPage: ''
},
mixins: [mockMixin]
});
wrapper.getCmsContent = jest.fn();
const actual = wrapper.find("span");
expect(actual.html()).toContain(subHeaderText);
});

View file

@ -12,8 +12,8 @@
/>
</h5>
</div>
<div class="d-flex align-items-center justify-content-center container-fluid overflow-hidden">
<p class="text-center small fw-normal mb-0 subheader-secondary">
<div class="d-flex align-items-center container-fluid overflow-hidden" :class="justifySubheader">
<p class="text-center fw-normal mb-0 subheader-secondary" :class="alternateFormatting">
<span>
{{ subText }}
</span>
@ -31,23 +31,33 @@ import buttonBack from "@/iss-components/site-sub-header/button-back/button-back
props: {
cmsWidgetName: String,
hasBackButton: Boolean,
justification: String,
issContainingPage: String
},
computed: {
content()
{
content() {
return this.getCmsContent(this.cmsWidgetName, "SubHeaderText")
},
subText()
{
return this.getCmsContent(this.cmsWidgetName, "SecondaryText")
subText() {
const subText = this.getCmsContent(this.cmsWidgetName, "SecondaryText");
return subText ?? '';
},
backButtonAccessibleText()
{
return this.getCmsContent(this.cmsWidgetName, "BackButtonAccessibleText")
backButtonAccessibleText() {
return this.getCmsContent(this.cmsWidgetName, "BackButtonAccessibleText")
},
headerColor() {
return this.subText ? "dark-header" : "light-header";
},
justifySubheader() {
return (this.justification?.toLowerCase() === 'left') ?
'justify-content-left' :
'justify-content-center';
},
alternateFormatting() {
return (this.issContainingPage?.toLowerCase() === 'service-packages') ?
'service-packages-subtext mt-4 mb-2 px-5' :
'small';
}
},
methods: {
clickEvent() {
@ -60,22 +70,27 @@ import buttonBack from "@/iss-components/site-sub-header/button-back/button-back
<style lang="scss" scoped>
.dark-header {
color: $black;
}
.dark-header {
color: $black;
}
.light-header {
color: $gray-550;
}
.light-header {
color: $gray-550;
}
h5 {
line-height: 32px;
h5 {
line-height: 32px;
button {
color: inherit;
}
}
p.small {
color: $gray-550;
}
p.service-packages-subtext {
font-weight: 500 !important;
line-height: 24px;
}
button {
color: inherit;
}
}
p.small {
color: $gray-550;
}
</style>

View file

@ -0,0 +1,355 @@
<template>
<buttonQuestion ref="buttonQuestion"
:answers="servicePackageAnswers"
:groupName="groupName"
buttonTypeString="servicePackageRadio"
:buttonTypeObject="servicePackageRadio"
v-model="selectedPackageName"
:validationRules="validationRules"
:isRequired="isRequired" />
</template>
<script>
import buttonQuestion from '@/digital-components/button-question/button-question';
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';
import { useMainStore } from '@/store';
const packageNames = {
TIER_ONE: 'TierOne',
TIER_TWO: 'TierTwo',
TIER_THREE: 'TierThree',
};
export default {
name: 'servicePackageQuestion',
props: {
cmsWidgetName: String,
groupName: String,
validationRules: String,
isRequired: Boolean,
availableLineItems: []
},
data() {
return {
servicePackageRadio: servicePackageRadio,
selectedPackageName: null
};
},
watch: {
availableLineItems() {
const store = useMainStore();
if (this.allGlassPartsAndSupportingItemsHavePrices(store.order.lineItems)) {
this.selectDefaultPackage();
}
},
selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit('vapsItemsSelected', VapsProductsInSelectedPackage);
}
},
computed: {
nullSafeAvailableLineItems() {
return this.availableLineItems ?? [];
},
servicePackageAnswers() {
if (!this.cmsWidgetName) return {};
const cmsAnswersContent = [
{
Name: 'TierOne',
cmsWidgetName: 'EconomyServicePackage'
},
{
Name: 'TierTwo',
cmsWidgetName: 'StandardServicePackage'
},
{
Name: 'TierThree',
cmsWidgetName: 'PremiumServicePackage'
}
];
//if cms content has not yet loaded, skip
if (this.getCmsContent(cmsAnswersContent[0].cmsWidgetName, 'HeaderText') == '') {
return {};
}
const modifiedAnswers = cmsAnswersContent.map((answer) => ({
value: answer.Name,
buttonLabel: this.getHeaderTextFromCms(answer.cmsWidgetName),
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.cmsWidgetName),
buttonBodyCopy: this.getBodyTextFromCms(answer.cmsWidgetName),
buttonAuxiliaryCopy: this.getPackagePriceString(answer.Name),
buttonFooterCopy: this.getFooterTextFromCms(answer.cmsWidgetName)
}
));
return modifiedAnswers;
},
frontWipersApplicableForTierTwo() {
const store = useMainStore();
const frontWipersAreAvailable = this.lineItemsContainsPartType(
partTypeStrings.FRONT_WIPER
);
const isRepair = store.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);
const frontWipersAreAvailable = this.lineItemsContainsPartType(partTypeStrings.FRONT_WIPER);
return (
rearWiperIsAvailable &&
(this.glassToReplaceContainsGlassLocation(glassLocations.REAR) ||
!frontWipersAreAvailable)
);
},
rainDefenseApplicableForTierThree() {
if (
this.rearWiperApplicableForTierTwo &&
!this.frontWipersApplicableForTierTwo &&
this.frontWipersApplicableForTierThree
) {
return false;
} else {
return true;
}
},
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 '$' + formattedPriceFloat;
},
getPackagePrice(packageName) {
let priceFloat = 0;
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 += this.getTotalLineItemPrice(item);
}
});
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 += this.getTotalLineItemPrice(item);
}
});
return vapsPrice;
},
selectDefaultPackage() {
const store = useMainStore();
const vapsFromStore = store.lineItems.vaps;
let lowestTierForPackage = packageNames.TIER_ONE;
if (vapsFromStore?.length > 0) {
vapsFromStore.every((vapsItem) => {
let lowestTierForThisItem = this.getLowestTierForThisItem(vapsItem);
if (lowestTierForThisItem === packageNames.TIER_THREE) {
lowestTierForPackage = packageNames.TIER_THREE;
return false;
} else if (lowestTierForThisItem === packageNames.TIER_TWO) {
lowestTierForPackage = packageNames.TIER_TWO;
return true;
} else {
return true;
}
});
}
this.selectedPackageName = lowestTierForPackage;
},
allGlassPartsAndSupportingItemsHavePrices(lineItems) {
if (lineItems?.glassParts) {
for (let i = 0; i < lineItems.glassParts.length; i++) {
if (this.priceIsNullOrZero(lineItems.glassParts[i])) {
return false;
}
}
}
if (lineItems?.supportingItems) {
for (let i = 0; i < lineItems.supportingItems.length; i++) {
if (this.priceIsNullOrZero(lineItems.supportingItems[i])) {
return false;
}
}
}
return true;
},
priceIsNullOrZero(lineItem) {
return (
(lineItem.kitPrice == null || lineItem.kitPrice == 0) &&
(lineItem.laborAmount == null || lineItem.laborAmount == 0) &&
(lineItem.sellingPrice == null || lineItem.sellingPrice == 0)
);
},
getLowestTierForThisItem(vapsItem) {
let lowestTierForThisItem = null;
switch (vapsItem.partType) {
case partTypeStrings.FRONT_WIPER:
if (this.frontWipersApplicableForTierThree) {
lowestTierForThisItem = packageNames.TIER_THREE;
}
if (this.frontWipersApplicableForTierTwo) {
lowestTierForThisItem = packageNames.TIER_TWO;
}
break;
case partTypeStrings.REAR_WIPER:
if (this.rearWiperApplicableForTierThree) {
lowestTierForThisItem = packageNames.TIER_THREE;
}
if (this.rearWiperApplicableForTierTwo) {
lowestTierForThisItem = packageNames.TIER_TWO;
}
break;
case partTypeStrings.RAIN_DEFENSE:
if (this.rainDefenseApplicableForTierThree) {
lowestTierForThisItem = packageNames.TIER_THREE;
}
break;
}
return lowestTierForThisItem;
},
getVapsLineItemsForSelectedPackage(packageName) {
const vapsLineItemsForSelectedPackage = [];
if (packageName === packageNames.TIER_TWO) {
if (this.frontWipersApplicableForTierTwo) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER)
);
}
if (this.rearWiperApplicableForTierTwo) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER)
);
}
} else if (packageName === packageNames.TIER_THREE) {
if (this.frontWipersApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER)
);
}
if (this.rearWiperApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER)
);
}
if (this.rainDefenseApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.RAIN_DEFENSE)
);
}
}
return vapsLineItemsForSelectedPackage;
},
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;
}
},
getLineItemsContainingPartType(partType) {
const partTypeMatches = this.nullSafeAvailableLineItems.filter(
(lineItem) => lineItem.partType.toUpperCase() === partType
);
return partTypeMatches;
},
lineItemsContainsPartType(partType) {
const partTypeMatches = this.getLineItemsContainingPartType(partType);
return !!partTypeMatches.length;
},
glassToReplaceContainsGlassLocation(glassLocation) {
const store = useMainStore();
const glassLocationMatches =
store.order.damage.glassToReplace?.filter(
(glassToReplace) => glassToReplace.glassLocation === glassLocation
) ?? [];
return !!glassLocationMatches.length;
},
getTotalLineItemPrice(lineItem) {
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice
}
},
components: {
buttonQuestion
}
};
</script>

View file

@ -0,0 +1,127 @@
import { mount } from "@vue/test-utils"
import servicePackageRadio from "./service-package-radio"
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"
describe("service-package-radio.vue", () => {
it("Should include buttonLabel in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"]))
})
it("Should include buttonLabelAuxillaryCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelAuxillaryCopy"]))
})
it("Should include buttonLabelSubCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"]))
})
it("Should include buttonFooterCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonFooterCopy"]))
})
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a dashed (x5) buttonBodyCopy", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy)
// Assert
expect(results.length).toBe(5)
})
it("Should get strings from getArrayOfListItemsFromRawCmsCopy without dashes when provided with a dashed buttonBodyCopy", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy)
// Assert
const fileredResults = results.filter((result) => {
return result.includes(" -");
})
expect(fileredResults.length).toBe(0)
})
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when buttonBodyCopy has 6 total dashes, but one is empty ", async () => {
// Arrange
const moddedProps = mockProps
moddedProps["buttonBodyCopy"] = "- buttonBodyCopy test copy - 2 - 3 - 4 - 5 -"
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: moddedProps,
},
})
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy)
// Assert
expect(results.length).toBe(5)
})
})
const mockProps = {
buttonLabel: 'buttonLabel test copy',
buttonLabelAuxillaryCopy: 'buttonLabelAuxillaryCopy test copy',
buttonLabelSubCopy: 'buttonLabelSubCopy test copy',
buttonBodyCopy: '- buttonBodyCopy test copy - 2 - 3 - 4 - 5',
buttonFooterCopy: 'buttonFooterCopy test copy'
}
function setupMocks({ mountOptionsMockData = {} }) {
const wrapper = mount(servicePackageRadio, {
...mountOptionsMockData,
mixins: [inputButtonWrapperMixin]
})
return { wrapper }
}

View file

@ -0,0 +1,265 @@
<template>
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
<div class="package-label mb-4"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs">
<p class="m-0">
<span v-html="this.buttonLabel"></span>
<span class="pricing-info" v-html="this.buttonAuxiliaryCopy"></span>
</p>
<p class="sub-label m-0"
v-if="this.buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p>
<div>
<ul >
<li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem" class="mb-0">
<!-- 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="getRouterLinkDisplayTextFromCopy(copy)"
href="#!"
@click-event="
$emit('buttonEvent', {
eventName: 'openModal',
args: getRouterLinkRouteFromCopy(copy),
})
"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
aria-label="Modal window" />
</span>
</template>
</li>
</ul>
<!-- End of body text parsing -->
<!--ms-n6-->
<div class="package-footer fw-bold caption mt-4 ml-n4 mr-3"
v-if="this.buttonFooterCopy"
v-html="this.buttonFooterCopy"></div>
</div>
</div>
</div>
</baseInputButton>
</template>
<script>
import baseInputButton from '@/digital-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 {
getRouterLinkDisplayTextFromCopy,
getRouterLinkRouteFromCopy,
splitCopyOnCMSPlaceHolder,
doesCopyContainTextLink,
} from '@/helpers/cms-content-helper';
export default {
name: 'servicePackageRadio',
mixins: [inputButtonWrapperMixin],
components: {
baseInputButton,
textLink,
},
computed: {
arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
},
},
methods: {
getRouterLinkDisplayTextFromCopy,
getRouterLinkRouteFromCopy,
splitCopyOnCMSPlaceHolder,
doesCopyContainTextLink,
stripUlTagFromCopy(copy) {
return copy;
},
getArrayOfListItemsFromRawCmsCopy(copy) {
if (copy) {
return copy
.split('- ') //At some point we'll want a better delimiter
.filter((lineItem) => lineItem);
}
}
},
};
</script>
<style lang="scss" scoped>
.ml-n4 {
margin-left: -$spacer * 2;
}
.mr-3 {
margin-right: $spacer * 1.5;
}
.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: 60px;
&.has-subheader {
min-height: 80px;
}
&: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 {
.package-specs {
.hide-when-closed {
display: block;
}
}
}
+ .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 {
color: $red;
}
.package-specs {
display: flex;
flex-direction: column;
width: 100%;
max-height: 1000px;
transition: all 0.5s ease;
p {
font-weight: 500;
display: flex;
justify-content: space-between;
span {
&.pricing-info {
color: $green;
font-size: 0.875rem;
}
}
&.sub-label {
color: $green;
text-transform: uppercase;
font-size: 0.75rem;
}
}
ul {
margin: 1rem 0 0 -.6rem;
padding: 0;
li {
margin-bottom: 0.5rem;
font-size: 0.875rem;
line-height: 1.714;
a {
line-height: 1.714;
padding: 0;
}
}
}
@keyframes slideaway {
from {
display: block;
}
to {
transform: translateY(40px);
opacity: 0;
}
}
}
}
}
</style>

View file

@ -0,0 +1,150 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<div class="page-container-grouped-styles">
<loadingModal ref="loadingModal" />
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget"
justification="left"
issContainingPage="service-packages"/>
<div class="fade-on-route-transition sub-container make-tall">
<servicePackageQuestion ref="servicePackage"
cmsWidgetName="ServicePackage"
groupName="ServicePackageQuestion"
:availableLineItems="availableLineItems"
@vapsItemsSelected="vapsItemsSelectedAction"
v-on="{ 'buttonEvent.openModal': openModalAction }"
validationRules="option-required"
isRequired />
<textBlock cmsWidgetName="PriceDisclaimerWidget"
justifyText="left"
typeStyle="caption"
style="margin-bottom: 6rem;"
class="mt-2 mx-6" />
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
<siteFooter cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</Form>
</template>
<script>
// Components
import siteHeader from '@/iss-components/site-header/site-header';
import siteFooter from '@/iss-components/site-footer/site-footer';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header';
import servicePackageQuestion from './service-package-question/service-package-question';
import textBlock from '@/digital-components/text-block/text-block';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal';
import loadingModal from '@/iss-components/loading-modal/loading-modal.vue';
import { settleAllPromises } from '@/helpers/layout-helper';
import { useMainStore } from "@/store";
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { required } from '@/helpers/validation-rules';
import { errorMessages } from '@/constants/error-messages';
import { Form, defineRule } from 'vee-validate';
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
export default {
name: 'service-packages',
async beforeRouteEnter(to, from, next) {
const store = useMainStore();
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const wipersPromise = await store.getWipers();
const rainDefensePromise = await store.getRainDefense();
const supportingItemsPromise = await store.getSupportingItems();
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise,
},
{
resultKey: 'wipers',
promise: wipersPromise,
},
{
resultKey: 'rainDefense',
promise: rainDefensePromise,
},
{
resultKey: 'supportingItems',
promise: supportingItemsPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
const clonedGlassParts = store.order.lineItems.glassParts
? JSON.parse(JSON.stringify(store.order.lineItems.glassParts))
: [];
const availableLineItems = [
resultMap.rainDefense,
...resultMap.supportingItems,
...resultMap.wipers,
...clonedGlassParts,
];
const pricingResults = await store.getPriceOrderItems(availableLineItems);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.pricedGlassParts = clonedGlassParts;
vm.supportingItems = resultMap.supportingItems;
vm.availableLineItems = pricingResults;
});
},
data() {
return {
selectedVaps: null,
availableLineItems: null,
supportingItems: null,
pricedGlassParts: null
};
},
methods: {
openModalAction(modalName) {
this.$refs[modalName].openModal();
},
arePagePrerequisitesValid() {
const store = useMainStore();
return (
store.order.serviceLocation.zipCode &&
store.order.serviceLocation.zipCodeCtu &&
(store.order.damage.isRepair ||
(store.order.lineItems?.glassParts != null &&
store.order.lineItems.glassParts.length > 0)) &&
store.order.referralNumber?.length !== 6
);
},
vapsItemsSelectedAction(vapsItemsSelected) {
this.selectedVaps = vapsItemsSelected;
},
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {
//TODO: save items
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}
},
components: {
siteHeader,
siteFooter,
siteSubHeader,
Form, /* eslint-disable-line */
textBlock,
servicePackageQuestion,
loadingModal,
contentGroupModal
}
};
</script>

View file

@ -22,6 +22,12 @@ const vueApp = createApp(App);
*/
vueApp.config.unwrapInjectedRef = true;
vueApp.config.compilerOptions.isCustomElement = (tag) => {
return (tag === 'siteSubHeader' ||
tag === 'ServicePackages' ||
tag === 'servicePackageQuestion');
}
// Pinia
const pinia = createPinia();
vueApp.use(pinia);

View file

@ -10,7 +10,7 @@ export default {
buttonLabel: [Number, String],
buttonLabelSubCopy: String,
buttonBodyCopy: String,
buttonAuxillaryCopy: String,
buttonAuxiliaryCopy: String,
buttonFooterCopy: String,
buttonImage: String,
buttonImageId: String,

View file

@ -285,8 +285,7 @@ describe("vehicle-questions-mixin", () => {
const testCases = [
[issPageValues.PART_QUESTIONS, issPageValues.VEHICLE_PARTS, true],
[issPageValues.VEHICLE_PARTS, issPageValues.VEHICLE_PARTS, false],
[issPageValues.QUOTE, issPageValues.VEHICLE_PARTS, false],
[issPageValues.QUOTE, issPageValues.QUOTE, false],
[issPageValues.VEHICLE_PARTS, issPageValues.REVEAL, false],
[issPageValues.VEHICLE_PARTS, issPageValues.PART_QUESTIONS, false],
[issPageValues.PART_QUESTIONS, issPageValues.CAPABILITY_QUESTIONS, true],
[issPageValues.MOLDING_QUESTIONS, issPageValues.CAPABILITY_QUESTIONS, true],
@ -310,8 +309,7 @@ describe("vehicle-questions-mixin", () => {
const testCases = [
[issPageValues.PART_QUESTIONS, issPageValues.VEHICLE_PARTS, false],
[issPageValues.VEHICLE_PARTS, issPageValues.VEHICLE_PARTS, false],
[issPageValues.QUOTE, issPageValues.VEHICLE_PARTS, true],
[issPageValues.QUOTE, issPageValues.QUOTE, false],
[issPageValues.VEHICLE_PARTS, issPageValues.SERVICE_LOCATION, true],
[issPageValues.VEHICLE_PARTS, issPageValues.PART_QUESTIONS, true],
[issPageValues.PART_QUESTIONS, issPageValues.CAPABILITY_QUESTIONS, false],
[issPageValues.MOLDING_QUESTIONS, issPageValues.CAPABILITY_QUESTIONS, false],
@ -2079,188 +2077,9 @@ describe("vehicle-questions-mixin", () => {
);
});
});
// TODO KO UNSKIP FOR QUOTE MVP
describe.skip("should go to quote page", () => {
test("single glass location selected, has no part questions and has one part => go to heritage funnel", async () => {
// Arrange
const partsOrQuestions = [
{
glassName: "Single",
glassLocation: "Windshield",
parts: [
{
partNumber: "FW04186GTYN",
description: "solar, soundproofing, lane keep assist",
color: "Green Tint",
requiresRecalibration: true,
requiresCapabilityQuestions: false,
childParts: [
{
partNumber: "GGG 3563 KIT",
partType: "MOULDING",
description: "Kit, Top & Sides ",
},
],
},
],
partQuestions: null,
},
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions,
});
// Act
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
{ query: { issPage: "vin-lookup" } }
);
});
test("multiple glass locations selected, each has one part and no part questions => go to heritage funnel", async () => {
// Arrange
const partsOrQuestions = [
{
glassName: "Single",
glassLocation: "Windshield",
parts: [
{
partNumber: "FW04186GTYN",
description: "solar, soundproofing, lane keep assist",
color: "Green Tint",
requiresRecalibration: true,
requiresCapabilityQuestions: false,
childParts: [
{
partNumber: "GGG 3563 KIT",
partType: "MOULDING",
description: "Kit, Top & Sides ",
},
],
},
],
partQuestions: null,
},
{
glassName: "Back",
glassLocation: "Driver",
parts: [
{
partNumber: "FD25457GTYN",
description: "solar, driver side, rear",
color: "Green Tint",
requiresRecalibration: false,
requiresCapabilityQuestions: false,
childParts: null,
},
],
partQuestions: null,
},
{
glassName: "Front",
glassLocation: "Driver",
parts: [
{
partNumber: "FD27090GTYN",
description: "solar, driver side, front",
color: "Green Tint",
requiresRecalibration: false,
requiresCapabilityQuestions: false,
childParts: null,
},
],
partQuestions: null,
},
{
glassName: "Vent",
glassLocation: "Driver",
parts: [
{
partNumber: "FV25459GTNN",
description: "solar, driver side, rear",
color: "Green Tint",
requiresRecalibration: false,
requiresCapabilityQuestions: false,
childParts: null,
},
],
partQuestions: null,
},
{
glassName: "Stationary",
glassLocation: "Rear",
parts: [
{
partNumber: "FB25460GTYN",
description: "heated glass, solar",
color: "Green Tint",
requiresRecalibration: false,
requiresCapabilityQuestions: false,
childParts: null,
},
],
partQuestions: null,
},
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions,
});
// Act
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(useMainStore.updateGlassParts).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
{ query: { issPage: "vin-lookup" } }
);
});
});
});
describe("navigateBack", () => {
test("current page is quote and there are capability questions => go to capability questions", () => {
// Arrange
const { wrapper } = setupMocks({ issPage: issPageValues.QUOTE });
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act
wrapper.vm.navigateBack();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS,
{ query: { issPage: issPageValues.QUOTE } }
);
});
test("current page is quote and there are part questions and molding questions => go to molding questions", () => {
// Arrange
const { wrapper } = setupMocks({ issPage: issPageValues.QUOTE });
wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true);
wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true);
// Act
wrapper.vm.navigateBack();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS,
{ query: { issPage: issPageValues.QUOTE } }
);
});
test("current page is molding questions and there are part questions, multiple parts to choose, and capability questions => go to vehicle-parts", () => {
// Arrange
const { wrapper } = setupMocks({ issPage: issPageValues.MOLDING_QUESTIONS });

View file

@ -68,6 +68,7 @@ const routes = [
console.log(error);
GoToStartOn404(next);
}
return null;
}
}];
@ -77,10 +78,10 @@ const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 }
},
}
});
router.afterEach((to, from) => {
router.afterEach((to, from) => { /*eslint-disable-line*/
const store = useMainStore();
// Update lastPageVisited in the store
@ -110,12 +111,12 @@ async function GetRouteInfoFromPageName(pageName) {
routeData.push({
path: "/",
name: `${key}`,
component: lazyLoadComponent(jsonFromResponse[key].LayoutName),
component: lazyLoadComponent(jsonFromResponse[key].LayoutName)
});
});
return routeData;
};
}
//Use this navigation when you need to call next() explicitly. beforeRouteEnter is a good example.
router.overrideNavigation = (
@ -145,7 +146,7 @@ router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams =
}
// Navigate to the next route, depending on the scenario.
function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) {
function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) { /*eslint-disable-line*/
if (!scenario) {
console.error("No scenario provided. Please review the routing table.");
return;
@ -174,7 +175,7 @@ function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams =
router.push({
name: "root",
query: Object.assign(optionalQuery, {
issPage: matchingScenarioMap.destinationIssPageValue,
issPage: matchingScenarioMap.destinationIssPageValue
}),
params: optionalParams
});
@ -186,8 +187,8 @@ function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams =
// Navigate to an external url.
function navigateToUrl(url, optionalQuery = {}) {
// possibly show some loading screen in the future here.
let externalUrl = new URL(url);
const externalUrl = new URL(url);
for (const queryKey in optionalQuery) {
externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]);
}
@ -223,7 +224,7 @@ function GoToStartOn404(next) {
router.addRoute({
path: "/",
name: errorPageName,
component: lazyLoadComponent(errorPageName),
component: lazyLoadComponent(errorPageName)
});
// Put item on the bus
@ -234,13 +235,13 @@ function GoToStartOn404(next) {
isDismissible: true,
messageCopy: "You can get a quote by starting on this page.",
messageHeadline: "We're sorry, something went wrong.",
type: globalEventTypes.Danger,
type: globalEventTypes.Danger
}
);
next({
name: errorPageName,
query: {issPage: errorPageName },
query: {issPage: errorPageName }
});
};
@ -253,14 +254,14 @@ async function runExperiments(nextPage) {
await store.runExperimentsForTrigger({
userId: getDeviceIdValue(),
triggerEvent: experimentTriggers.SITE_ENTRY,
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE
});
}
await store.runExperimentsForTrigger({
userId: getDeviceIdValue(),
triggerEvent: experimentTriggers.PAGE_ENTRY,
triggerValue: nextPage,
triggerValue: nextPage
});
}

View file

@ -1,31 +1,28 @@
export const issPageValues = {
ENTRY_PAGE: "entry-page",
WELCOME_PAGE: "welcome-page",
POLICY_HOLDER_DETAILS: "policy-holder-details",
VEHICLE_MAKE: "vehicle-make",
VEHICLE_YEAR: "vehicle-year",
VEHICLE_MODEL: "vehicle-model",
VEHICLE_STYLE: "vehicle-style",
VEHICLE_DAMAGE: "vehicle-damage",
VEHICLE_LOOKUP: "vehicle-lookup",
ADDRESS_LOOKUP: "address-lookup",
ADDRESS_VEHICLES: "address-vehicles",
LICENSE_PLATE_LOOKUP: "license-plate-lookup",
VIN_LOOKUP: "vin-lookup",
VEHICLE_PARTS: "vehicle-parts",
PART_QUESTIONS: "part-questions",
MOLDING_QUESTIONS: "molding-questions",
CAPABILITY_QUESTIONS: "capability-questions",
COVERAGE_STATEMENT: "coverage-statement",
PROVIDER_PREFERENCE: "provider-preference",
SERVICE_LOCATION: "service-location",
SCHEDULE_PAGE: "schedule-page",
CONTACT_DETAILS: "contact-details",
PAYMENT_PAGE: "payment-page",
REVEAL: "reveal",
ESTIMATE: "estimate",
ADDRESS_VEHICLES: "address-vehicles",
QUOTE: "quote",
HERITAGE: "heritage",
ENTRY_PAGE: 'entry-page',
WELCOME_PAGE: 'welcome-page',
ADDRESS_LOOKUP: 'address-lookup',
ADDRESS_VEHICLES: 'address-vehicles',
CAPABILITY_QUESTIONS: 'capability-questions',
ESTIMATE: 'estimate',
COVERAGE_STATEMENT: 'coverage-statement',
LICENSE_PLATE_LOOKUP: 'license-plate-lookup',
MOLDING_QUESTIONS: 'molding-questions',
PART_QUESTIONS: 'part-questions',
POLICY_HOLDER_DETAILS: 'policy-holder-details',
PROVIDER_PREFERENCE: 'provider-preference',
REVEAL: 'reveal',
REVIEW_ORDER: 'review-order',
SERVICE_LOCATION: 'service-location',
SERVICE_PACKAGE: 'service-package',
VEHICLE_DAMAGE: 'vehicle-damage',
VEHICLE_LOOKUP: 'vehicle-lookup',
VEHICLE_MAKE: 'vehicle-make',
VEHICLE_MODEL: 'vehicle-model',
VEHICLE_PARTS: 'vehicle-parts',
VEHICLE_STYLE: 'vehicle-style',
VEHICLE_YEAR: 'vehicle-year',
VIN_LOOKUP: 'vin-lookup'
};

View file

@ -1,5 +1,5 @@
import { issPageValues } from "@/router/router-constants/issPage-values";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { issPageValues } from '@/router/router-constants/issPage-values';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
// Get store from router/index.js instead of importing it here to get updated values
const routingTable = function(store) {
@ -14,8 +14,8 @@ const routingTable = function(store) {
{
scenario: navigationScenarios.SELECTED_YEAR,
destinationIssPageValue: issPageValues.VEHICLE_MAKE
},
],
}
]
},
{
issPageValue: issPageValues.VEHICLE_MAKE,
@ -27,19 +27,19 @@ const routingTable = function(store) {
{
scenario: navigationScenarios.SELECTED_MAKE,
destinationIssPageValue: issPageValues.VEHICLE_MODEL
},
],
}
]
},
{
issPageValue: issPageValues.VEHICLE_MODEL,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_MAKE,
destinationIssPageValue: issPageValues.VEHICLE_MAKE
},
{
scenario: navigationScenarios.SELECTED_MODEL,
destinationIssPageValue: issPageValues.VEHICLE_STYLE,
destinationIssPageValue: issPageValues.VEHICLE_STYLE
}
]
},
@ -48,11 +48,11 @@ const routingTable = function(store) {
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_MODEL,
destinationIssPageValue: issPageValues.VEHICLE_MODEL
},
{
scenario: navigationScenarios.SELECTED_STYLE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
}
]
},
@ -61,298 +61,298 @@ const routingTable = function(store) {
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_STYLE,
destinationIssPageValue: issPageValues.VEHICLE_STYLE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_REPAIR,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_VIN,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
},
],
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
}
]
},
{
issPageValue: issPageValues.VEHICLE_LOOKUP,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{
scenario: navigationScenarios.SELECTED_MANUAL_VIN,
destinationIssPageValue: issPageValues.VIN_LOOKUP,
destinationIssPageValue: issPageValues.VIN_LOOKUP
},
{
scenario: navigationScenarios.SELECTED_LICENSE_PLATE,
destinationIssPageValue: issPageValues.LICENSE_PLATE_LOOKUP,
destinationIssPageValue: issPageValues.LICENSE_PLATE_LOOKUP
},
{
scenario: navigationScenarios.SELECTED_HOME_ADDRESS,
destinationIssPageValue: issPageValues.ADDRESS_LOOKUP,
},
],
destinationIssPageValue: issPageValues.ADDRESS_LOOKUP
}
]
},
{
issPageValue: issPageValues.VIN_LOOKUP,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
},
],
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
}
]
},
{
issPageValue: issPageValues.LICENSE_PLATE_LOOKUP,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
},
],
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
}
]
},
{
issPageValue: issPageValues.PART_QUESTIONS,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VIN_LOOKUP,
destinationIssPageValue: issPageValues.VIN_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
},
],
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
}
]
},
{
issPageValue: issPageValues.VEHICLE_PARTS,
maps: [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.REVEAL,
destinationIssPageValue: issPageValues.REVEAL
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VIN_LOOKUP,
destinationIssPageValue: issPageValues.VIN_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
},
],
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
}
]
},
{
issPageValue: issPageValues.MOLDING_QUESTIONS,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VIN_LOOKUP,
destinationIssPageValue: issPageValues.VIN_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
},
],
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
}
]
},
{
issPageValue: issPageValues.CAPABILITY_QUESTIONS,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VIN_LOOKUP,
destinationIssPageValue: issPageValues.VIN_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
},
],
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
}
]
},
{
issPageValue: issPageValues.ADDRESS_LOOKUP,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
destinationIssPageValue: issPageValues.ADDRESS_VEHICLES,
destinationIssPageValue: issPageValues.ADDRESS_VEHICLES
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
},
],
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
}
]
},
{
issPageValue: issPageValues.ADDRESS_VEHICLES,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.ADDRESS_LOOKUP,
destinationIssPageValue: issPageValues.ADDRESS_LOOKUP
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
},
],
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
}
]
},
{
issPageValue: issPageValues.ENTRY_PAGE,
@ -364,8 +364,8 @@ const routingTable = function(store) {
{
scenario: navigationScenarios.MOVE_FORWARD_ENTRY_PAGE,
destinationIssPageValue: issPageValues.WELCOME_PAGE
},
],
}
]
},
{
issPageValue: issPageValues.WELCOME_PAGE,
@ -377,8 +377,8 @@ const routingTable = function(store) {
{
scenario: navigationScenarios.CLICKED_FORWARD_WELCOME_PAGE,
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
},
],
}
]
},
{
issPageValue: issPageValues.POLICY_HOLDER_DETAILS,
@ -390,55 +390,54 @@ const routingTable = function(store) {
{
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_HOLDER_DETAILS,
destinationIssPageValue: issPageValues.VEHICLE_YEAR
},
],
}
]
},
{
issPageValue: issPageValues.COVERAGE_STATEMENT,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationIssPageValue: issPageValues.VIN_LOOKUP,
destinationIssPageValue: issPageValues.VIN_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP,
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS,
destinationIssPageValue: issPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationIssPageValue: issPageValues.VEHICLE_PARTS,
destinationIssPageValue: issPageValues.VEHICLE_PARTS
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS,
destinationIssPageValue: issPageValues.MOLDING_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS,
destinationIssPageValue: issPageValues.CAPABILITY_QUESTIONS
},
{
scenario: navigationScenarios.CLICKED_FORWARD_COVERAGE_STATEMENT,
destinationIssPageValue: issPageValues.PROVIDER_PREFERENCE,
},
],
destinationIssPageValue: issPageValues.PROVIDER_PREFERENCE
}
]
},
{
issPageValue: issPageValues.PROVIDER_PREFERENCE,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT,
destinationIssPageValue: issPageValues.COVERAGE_STATEMENT
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE,
destinationIssPageValue: issPageValues.SERVICE_LOCATION,
},
],
destinationIssPageValue: issPageValues.SERVICE_LOCATION
}
]
},
{
issPageValue: issPageValues.SERVICE_LOCATION,
@ -447,40 +446,52 @@ const routingTable = function(store) {
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.PROVIDER_PREFERENCE,
},
],
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.SERVICE_PACKAGES
}
]
},
{
issPageValue: issPageValues.SCHEDULE_PAGE,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.SERVICE_LOCATION,
},
],
destinationIssPageValue: issPageValues.SERVICE_LOCATION
}
]
},
{
issPageValue: issPageValues.CONTACT_DETAILS,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
}
]
},
{
issPageValue: issPageValues.SERVICE_PACKAGE,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.SERVICE_LOCATION
},
],
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.REVIEW_ORDER
}
]
},
{
issPageValue: issPageValues.PAYMENT_PAGE,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.REVIEW_PAGE,
},
],
},
destinationIssPageValue: issPageValues.REVIEW_PAGE
}
]
}
];
};

View file

@ -30,8 +30,8 @@ const getDefaultState = () => {
state: null,
zipCode: null,
firstName: null,
lastName: null,
},
lastName: null
}
},
damage: {
isRepair: null,
@ -39,7 +39,7 @@ const getDefaultState = () => {
glassToReplace: null,
partQuestionAnswers: null,
moldingQuestionAnswers: null,
capabilityQuestionAnswers: null,
capabilityQuestionAnswers: null
},
policy: {
policyNumber: null,
@ -47,7 +47,7 @@ const getDefaultState = () => {
damageCause: null,
damageState: null,
damageCity: null,
isDamageGlassOnly: null,
isDamageGlassOnly: null
},
customer: {
address: {
@ -55,12 +55,12 @@ const getDefaultState = () => {
streetAddress2: null,
city: null,
state: null,
zipCode: null,
zipCode: null
},
firstName: null,
lastName: null,
emailAddress: null,
phoneNumber: null,
phoneNumber: null
},
serviceLocation: {
address: null,
@ -78,16 +78,14 @@ const getDefaultState = () => {
payment: {
isInsurance: true,
insuranceCoverage: {
isVerified: false,
},
isVerified: false
}
},
referralNumber: null,
referralDate: null,
referralDate: null
},
applicationUser: {
lastPageVisited: null,
experiments: [],
triggeredSiteEntry: false,
eventBus: [],
pageData: {},
savedSessionTimeout: getDateForSavedSessionTimeout(),
@ -95,7 +93,7 @@ const getDefaultState = () => {
savedSessionId: null,
crmCustomerId: null,
lastPageVisited: null,
triggeredSiteEntry: false,
triggeredSiteEntry: false
},
issConfig: {
clientName: "Generic Insurance", // this is the default and will be overriden by the client's name
@ -106,7 +104,7 @@ const getDefaultState = () => {
isAuthenticated: false, // Indicates if user is authenticated or not.
enableTPAFlow: false, // Indicates if the TPA flow is supported for this client.
returnURL: null,
returnURL2: null,
returnURL2: null
}
};
};
@ -120,6 +118,7 @@ export const useMainStore = defineStore({
vehicle: (state) => state.order.vehicle,
damage: (state) => state.order.damage,
lineItems: (state) => state.order.lineItems,
hasAnyNonWindshieldGlassParts: (state) => !state.order.policy.isDamageGlassOnly,
eventBusItem: (state) => ( eventCategory, eventSubCategory) => {
const matchedEvent = state.applicationUser.eventBus.find(
@ -235,8 +234,8 @@ export const useMainStore = defineStore({
licenseLastName: licenseLastName,
licenseStreetAddress: licenseStreetAddress,
licenseZip: licenseZip,
licenseState: licenseState,
},
licenseState: licenseState
}
});
},
@ -245,14 +244,14 @@ export const useMainStore = defineStore({
method: endpoints.GetRouteInfo.method,
endpoint: endpoints.GetRouteInfo.url(applicationConfig.APPLICATION_ABBREVIATION),
payload: {
pageName: pageName,
pageName: pageName
},
});
},
getHomepageName() {
return globalMethods.callHttpClient({
method: endpoints.GetHomepageInfo.method,
endpoint: endpoints.GetHomepageInfo.url(applicationConfig.APPLICATION_ABBREVIATION),
endpoint: endpoints.GetHomepageInfo.url(applicationConfig.APPLICATION_ABBREVIATION)
});
},
@ -260,7 +259,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetPageData.method,
endpoint: endpoints.GetPageData.url(applicationConfig.APPLICATION_ABBREVIATION, pageName),
payload: {},
payload: {}
});
},
@ -269,7 +268,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetVehicleYears.method,
endpoint: endpoints.GetVehicleYears.url,
payload: {},
payload: {}
});
},
@ -277,7 +276,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetVehicleMakes.method,
endpoint: endpoints.GetVehicleMakes.url + this.order.vehicle.year,
payload: {},
payload: {}
});
},
@ -285,7 +284,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetVehicleModels.method,
endpoint: `${endpoints.GetVehicleModels.url}/${this.order.vehicle.year}/${this.order.vehicle.make}`,
payload: {},
payload: {}
});
},
@ -293,7 +292,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetVehicleStyles.method,
endpoint: `${endpoints.GetVehicleStyles.url}/${this.order.vehicle.year}/${this.order.vehicle.make}/${this.order.vehicle.model}`,
payload: {},
payload: {}
});
},
@ -301,7 +300,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
methods: endpoints.GetDamageOptions.method,
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
payload: {},
payload: {}
});
},
getIsVinbyAddressPermissible(){
@ -309,36 +308,36 @@ export const useMainStore = defineStore({
const response = globalMethods.callHttpClient({
method:endpoints.IsVinbyAddressPermissible.method,
endpoint:`${endpoints.IsVinbyAddressPermissible.url}?state=${this.order.customer.address.state}`,
payload: {},
payload: {}
});
return response;
} catch (responseError) {
return {
error: {
status: responseError.status,
},
status: responseError.status
}
};
}
},
async lookupVinByPlate(licensePlate, licenseState) {
try {
const response = await globalMethods.callHttpClient({
method: endpoints.LookupVinByPlate.method,
endpoint: endpoints.LookupVinByPlate.url,
payload: {
licensePlate: licensePlate,
licenseState: licenseState,
},
});
try {
const response = await globalMethods.callHttpClient({
method: endpoints.LookupVinByPlate.method,
endpoint: endpoints.LookupVinByPlate.url,
payload: {
licensePlate: licensePlate,
licenseState: licenseState
}
});
return response;
} catch (responseError) {
return {
error: {
status: responseError.status,
},
};
}
return response;
} catch (responseError) {
return {
error: {
status: responseError.status
}
};
}
},
// PartsOrQuestions API Actions
@ -363,8 +362,8 @@ export const useMainStore = defineStore({
carId: carId,
glassPieces: glassArrayForPayload,
zip: zipCode,
vin: vin,
},
vin: vin
}
});
// Flatten location and name properties
@ -398,8 +397,8 @@ export const useMainStore = defineStore({
glassPieces: glassArrayForPayload,
answerResults: resultsArrayForPayload,
zip: zipCode,
vin: vin,
},
vin: vin
}
});
// Flatten location and name properties
@ -413,7 +412,7 @@ export const useMainStore = defineStore({
getCapabilityQuestions(carId, partNumber) {
return globalMethods.callHttpClient({
method: endpoints.GetCapabilityQuestions.method,
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`
});
},
@ -431,11 +430,101 @@ export const useMainStore = defineStore({
method: endpoints.GetPartFromCapabilityAnswer.method,
endpoint: endpoints.GetPartFromCapabilityAnswer.url,
payload: {
part,
capabilityAnswerResults: capabilityQuestionAnswersForPart,
},
part,
capabilityAnswerResults: capabilityQuestionAnswersForPart
}
});
},
},
async getWipers() {
const carId = this.order.vehicle.carId;
///WARNING
///TODO: this is temp test code until serviceLocation is complete.
//const serviceZipCode = this.order.serviceLocation.zipCode;
const serviceZipCode = '44902';
return globalMethods
.callHttpClient({
method: endpoints.GetWipers.method,
endpoint: `${endpoints.GetWipers.url}/${carId}/${serviceZipCode}`
})
.catch((error) => {
// The wiper service sometimes returns 500s on legitimate carId/zipCode combination - return empty array instead of breaking flow
console.error(error);
return [];
});
},
async getRainDefense() {
return globalMethods
.callHttpClient({
method: endpoints.GetRainDefense.method,
endpoint: `${endpoints.GetRainDefense.url}`
})
.catch((error) => {
// The wiper service sometimes returns 500s on legitimate carId/zipCode combination - return empty array instead of breaking flow
console.error(error);
return [];
});
},
async getSupportingItems() {
const glassPartsArray = this.order.lineItems.glassParts ?? [];
const carId = this.order.vehicle.carId;
const isRepair = this.order.damage.isRepair;
const numberOfChips = this.order.damage.numberOfChips;
return globalMethods
.callHttpClient({
method: endpoints.GetSupportingItems.method,
endpoint: endpoints.GetSupportingItems.url,
payload: {
carId: carId,
serviceType: isRepair ? 'Repair' : 'Replace',
parentAccountNumber: applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
parts: glassPartsArray,
numberOfRepairChips: isRepair ? numberOfChips : 0
}
});
},
async getPriceOrderItems(availableLineItems) {
let zipCodeToUse = this.order.serviceLocation.zipCode;
let ctuToUse = this.order.serviceLocation.zipCodeCtu;
const availableLineItemsFormattedForRequest =
getLineItemQueryStringForPricing(availableLineItems);
const vehicle = this.order.vehicle;
///WARNING
///TODO: this is temp test code until serviceLocation is complete.
/// and ctu is available. Also, EON may need to be implemented.
zipCodeToUse = "44902"
ctuToUse = "01820"
let queryString =
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
`&CTU=${ctuToUse}` +
`&CarId=${vehicle.carId}` +
`&Make=${vehicle.make}` +
`&Model=${vehicle.model}` +
`&Year=${vehicle.year}` +
`&EON=0` +
`&ZipCode=${zipCodeToUse}` +
`${availableLineItemsFormattedForRequest}`;
const response = await globalMethods
.callHttpClient({
method: endpoints.GetPriceOrderItems.method,
endpoint: `${endpoints.GetPriceOrderItems.url}?${queryString}`
}).catch((error) => {
console.error(error);
return [];
});
availableLineItems = addPricesToLineItems(availableLineItems, response.data.lineItems)
return availableLineItems;
},
lookupVehicleByVin(vin) {
return globalMethods.callHttpClient({
method: endpoints.LookupVehicleByVin.method,
@ -451,7 +540,7 @@ export const useMainStore = defineStore({
.callHttpClient({
methods: endpoints.GetVehicle.method,
endpoint: `${endpoints.GetVehicle.url}/${this.order.vehicle.year}/${this.order.vehicle.make}/${this.order.vehicle.model}/${this.order.vehicle.style}`,
payload: {},
payload: {}
})
.then((response) => {
this.updateVehicle(response.data);
@ -775,7 +864,7 @@ export const useMainStore = defineStore({
this.updateCapabilityQuestionAnswers(null);
this.updatePageData({
page: issPageValues.CAPABILITY_QUESTIONS,
data: null,
data: null
});
}
@ -850,9 +939,9 @@ export const useMainStore = defineStore({
userPartitionNumber: experiment.userPartitionNumber,
assignmentId: experiment.assignmentId,
sessionKey: sessionKey,
pageName: pageName,
},
},
pageName: pageName
}
}
});
},
logPageView({ userId, sessionKey, pageName, sessionId, action, event, shouldUseSessionId, experimentsForUser })
@ -873,7 +962,7 @@ export const useMainStore = defineStore({
method: endpoints.LogPageView.method,
endpoint: endpoints.LogPageView.url,
payload: payload,
logApiCall: false,
logApiCall: false
});
},
logCustomEvent({ userId, sessionKey, pageName, sessionId, category, action, label, value, shouldUseSessionId, experimentsForUser})
@ -892,14 +981,14 @@ export const useMainStore = defineStore({
label: label,
value: value,
shouldUseSessionId: shouldUseSessionId,
experimentsForUser: experimentsForUser,
experimentsForUser: experimentsForUser
};
return globalMethods.callHttpClient({
method: endpoints.LogCustomEvent.method,
endpoint: endpoints.LogCustomEvent.url,
payload: payload,
logApiCall: false,
logApiCall: false
});
},
initializeSession({ userId, sessionId, userAgent, referrer }) {
@ -911,14 +1000,14 @@ export const useMainStore = defineStore({
userAgent: userAgent,
operatorId: "WEB",
userName: "SafeliteISS",
referrer: referrer,
referrer: referrer
};
return globalMethods.callHttpClient({
method: endpoints.InitializeSession.method,
endpoint: endpoints.InitializeSession.url,
payload: payload,
logApiCall: false,
logApiCall: false
});
},
@ -937,7 +1026,7 @@ export const useMainStore = defineStore({
return globalMethods.callHttpClient({
method: endpoints.GetExperimentsByUser.method,
endpoint: `${endpoints.GetExperimentsByUser.url}/${userId}`,
payload: {},
payload: {}
});
},
@ -951,13 +1040,13 @@ export const useMainStore = defineStore({
userId: userId,
triggerEvent: triggerEvent,
triggerValue: triggerValue,
experimentOrder: this.experimentOrder,
experimentOrder: this.experimentOrder
};
const response = await globalMethods.callHttpClient({
method: endpoints.RunExperimentsForTrigger.method,
endpoint: endpoints.RunExperimentsForTrigger.url,
payload: payload,
payload: payload
});
this.updateExperiments(response.data.experiments);
@ -966,14 +1055,14 @@ export const useMainStore = defineStore({
async validateZip({ zip }) {
return await globalMethods.callHttpClient({
methods: endpoints.ValidateZip.method,
endpoint: `${endpoints.ValidateZip.url}/${zip}`,
endpoint: `${endpoints.ValidateZip.url}/${zip}`
});
},
async validateClientTag(clientTag) {
return await globalMethods.callHttpClient({
methods: endpoints.ValidateClientTag.method,
endpoint: `${endpoints.ValidateClientTag.url}/${clientTag}`,
endpoint: `${endpoints.ValidateClientTag.url}/${clientTag}`
});
},
@ -1124,7 +1213,7 @@ function convertGlassPieceNamingForApi(glassArray) {
glassArray.forEach((glass) => {
converted.push({
location: glass.glassLocation,
name: glass.glassName,
name: glass.glassName
});
});
return converted;
@ -1137,7 +1226,7 @@ function convertResultsForApi(resultsArray) {
converted.push({
location: answer.glassLocation,
name: answer.glassName,
result: answer.result,
result: answer.result
});
});
return converted;
@ -1164,3 +1253,31 @@ function getAllPartNumbers(partsOrQuestions) {
.join(",")
: [];
}
function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems.findIndex(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
)
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems)
}
const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0]
lineItem.laborAmount = pricedLineItem.laborAmount
lineItem.sellingPrice = pricedLineItem.sellingPrice
lineItem.kitPrice = pricedLineItem.kitPrice
});
return lineItems;
}
function getLineItemQueryStringForPricing(lineItems) {
return lineItems
.map((lineItem) => {
let queryStringSnippet = `&LineItems=${lineItem.partNumber}`;
if (lineItem.childParts) {
queryStringSnippet += getLineItemQueryStringForPricing(lineItem.childParts);
}
return queryStringSnippet;
})
.join('');
}

View file

@ -3,7 +3,6 @@ process.env.VUE_APP_CURRENT_ENVIRONMENT = "Localhost";
process.env.VUE_APP_GOOGLE_PLACES_API_KEY =
"AIzaSyCuLhQcDdZTTb4JzpUFms1OCch2dk5lHF0";
// GA & GTM
// NOTE: Using the old ISS site GTM Cotnainer ID for now, will create a new one soon.
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = "(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-KKNWZ3');";
@ -26,7 +25,7 @@ module.exports = {
},
},
},
configureWebpack: {
devtool: 'source-map'
configureWebpack: {
devtool: 'source-map',
},
};