From 279076b5479b580780963a8b4d02cd6f70094983 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 4 Apr 2023 12:09:28 -0400 Subject: [PATCH 01/20] This is a draft of changes supporting 313. It is not ready for a PR --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fc42c524..b5400d3e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ pnpm-debug.log* # Misc coverage/* -junit.xml \ No newline at end of file +junit.xml +/.vs From d15798ce893e00f4f8c0d70c96a0c2c64d649e28 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 4 Apr 2023 12:43:07 -0400 Subject: [PATCH 02/20] Just a draft update --- src/App.vue | 2 +- src/constants/damage-custom-labels.js | 4 +- src/constants/damage-locations-cms.js | 10 +- src/constants/damage-locations-selected.js | 32 +- src/constants/dynamic-strings.js | 6 +- src/constants/dynamictext-mapper.js | 4 +- src/constants/endpoints.js | 24 +- src/constants/error-messages.js | 2 +- src/constants/events.js | 8 +- src/constants/experiments.js | 6 +- src/constants/header-keys.js | 2 +- src/constants/mock-endpoints.js | 4 +- src/constants/part-type-strings.js | 8 + src/constants/states.js | 2 +- src/constants/tint-mapper.js | 6 +- src/constants/vehicle-categories.js | 2 +- src/constants/vin-lookup-methods.js | 4 +- .../button-question/button-question.vue | 4 +- src/helpers/cms-content-helper.js | 233 +++++++++-- .../site-header/site-header.vue | 6 +- .../service-package-question.vue | 361 ++++++++++++++++++ .../service-package-radio.vue | 267 +++++++++++++ .../service-packages/service-packages.vue | 151 ++++++++ src/main.js | 6 + src/mixins/input-button-wrapper-mixin.js | 2 +- src/router/index.js | 26 +- src/router/router-constants/issPage-values.js | 49 ++- src/store/index.js | 54 ++- 28 files changed, 1161 insertions(+), 124 deletions(-) create mode 100644 src/constants/part-type-strings.js create mode 100644 src/layouts/service-packages/service-package-question/service-package-question.vue create mode 100644 src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue create mode 100644 src/layouts/service-packages/service-packages.vue diff --git a/src/App.vue b/src/App.vue index 1d64f934..fd4a5b43 100644 --- a/src/App.vue +++ b/src/App.vue @@ -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"; diff --git a/src/constants/damage-custom-labels.js b/src/constants/damage-custom-labels.js index b7ce7608..72448788 100644 --- a/src/constants/damage-custom-labels.js +++ b/src/constants/damage-custom-labels.js @@ -1,8 +1,8 @@ const damageCustomLabels = Object.freeze({ - MATCH: 'match', + MATCH: 'match', REAR_WINDOW: 'rear window', SIDE_WINDOW: 'side window', - WINDSHIELD: 'windshield', + WINDSHIELD: 'windshield' }); export default damageCustomLabels; diff --git a/src/constants/damage-locations-cms.js b/src/constants/damage-locations-cms.js index 82a43175..01c63d27 100644 --- a/src/constants/damage-locations-cms.js +++ b/src/constants/damage-locations-cms.js @@ -1,9 +1,9 @@ const damageLocationsCms = { - WINDSHIELD: "WINDSHIELD", - SIDEDOOR: "SIDEDOOR", - REARWINDOW: "REARWINDOW", - DRIVERSIDE: "DRIVERSIDE", - PASSENGERSIDE: "PASSENGERSIDE", + WINDSHIELD: "WINDSHIELD", + SIDEDOOR: "SIDEDOOR", + REARWINDOW: "REARWINDOW", + DRIVERSIDE: "DRIVERSIDE", + PASSENGERSIDE: "PASSENGERSIDE" }; export { damageLocationsCms }; diff --git a/src/constants/damage-locations-selected.js b/src/constants/damage-locations-selected.js index 7ede615c..58a9bd24 100644 --- a/src/constants/damage-locations-selected.js +++ b/src/constants/damage-locations-selected.js @@ -1,21 +1,21 @@ const damageLocationsSelected = { - WINDSHIELD: "Windshield", - SIDEDOOR: "SideDoor", - REARWINDOW: "RearWindow", - REPAIR: "Repair", - REPLACE: "Replace", - DRIVER: "Driver", - PASSENGER: "Passenger", - FRONT: "Front", - REAR: "Rear", - BACK: "Back", - QUARTER: "Quarter", - VENT: "Vent", - SINGLE: "Single", - DRIVERSIDE: "DriverSide", + WINDSHIELD: "Windshield", + SIDEDOOR: "SideDoor", + REARWINDOW: "RearWindow", + REPAIR: "Repair", + REPLACE: "Replace", + DRIVER: "Driver", + PASSENGER: "Passenger", + FRONT: "Front", + REAR: "Rear", + BACK: "Back", + QUARTER: "Quarter", + VENT: "Vent", + SINGLE: "Single", + DRIVERSIDE: "DriverSide", PASSENGERSIDE: "PassengerSide", - STATIONARY: "Stationary", - SLIDER: "Slider", + STATIONARY: "Stationary", + SLIDER: "Slider" }; export { damageLocationsSelected }; diff --git a/src/constants/dynamic-strings.js b/src/constants/dynamic-strings.js index b81c0f30..e59f3a0c 100644 --- a/src/constants/dynamic-strings.js +++ b/src/constants/dynamic-strings.js @@ -1,8 +1,8 @@ const dynamicStrings = { GLOBAL_STATE: "globalState", - CUSTOM: "custom", - ROUTER_LINK: "routerLink:", - MODAL_LINK: "modalLink" + CUSTOM: "custom", + ROUTER_LINK: "routerLink:", + MODAL_LINK: "modalLink" }; export { dynamicStrings }; \ No newline at end of file diff --git a/src/constants/dynamictext-mapper.js b/src/constants/dynamictext-mapper.js index 9191c878..2e0be064 100644 --- a/src/constants/dynamictext-mapper.js +++ b/src/constants/dynamictext-mapper.js @@ -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} diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 69da2eae..f67e97e0 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -46,7 +46,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 +76,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", diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index f1930ac0..0aeff385 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -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" }; diff --git a/src/constants/events.js b/src/constants/events.js index f416eeff..3f3821ae 100644 --- a/src/constants/events.js +++ b/src/constants/events.js @@ -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 }; diff --git a/src/constants/experiments.js b/src/constants/experiments.js index a75c711f..ed110578 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -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 }; diff --git a/src/constants/header-keys.js b/src/constants/header-keys.js index 2cfbba36..a59d7160 100644 --- a/src/constants/header-keys.js +++ b/src/constants/header-keys.js @@ -1,3 +1,3 @@ export const headerKeys = { - EXPERIMENT: "X-Experiment-Data", + EXPERIMENT: "X-Experiment-Data" }; diff --git a/src/constants/mock-endpoints.js b/src/constants/mock-endpoints.js index ca67c3fe..09f54749 100644 --- a/src/constants/mock-endpoints.js +++ b/src/constants/mock-endpoints.js @@ -3,8 +3,8 @@ const endpoints = { GetRouteInfo: { url: "https://mockey.qa.sagaws.net/service/ISS/Content/RouteInfo", - method: "GET", - }, + method: "GET" + } }; diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js new file mode 100644 index 00000000..d9c62491 --- /dev/null +++ b/src/constants/part-type-strings.js @@ -0,0 +1,8 @@ +const partTypeStrings = { + FRONT_WIPER: 'FRONT WIPER', + REAR_WIPER: 'REAR WIPER', + RAIN_DEFENSE: 'RAIN DEFENSE', + RECALIBRATION: 'RECALIBRATION' +}; + +export { partTypeStrings }; \ No newline at end of file diff --git a/src/constants/states.js b/src/constants/states.js index 00fb6edd..ba6b5f55 100644 --- a/src/constants/states.js +++ b/src/constants/states.js @@ -49,5 +49,5 @@ export const states = { WA: "Washington", WV: "West Virginia", WI: "Wisconsin", - WY: "Wyoming", + WY: "Wyoming" }; \ No newline at end of file diff --git a/src/constants/tint-mapper.js b/src/constants/tint-mapper.js index 308b9a92..f8f462bb 100644 --- a/src/constants/tint-mapper.js +++ b/src/constants/tint-mapper.js @@ -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') diff --git a/src/constants/vehicle-categories.js b/src/constants/vehicle-categories.js index 4fab279d..fa3ebb80 100644 --- a/src/constants/vehicle-categories.js +++ b/src/constants/vehicle-categories.js @@ -5,7 +5,7 @@ const vehicleCategories = { COMMERCIALVAN: "COMMERCIAL VAN", SUV: "SUV", MOTORHOME: "MOTOR HOME", - SEMI: "SEMI", + SEMI: "SEMI" }; export { vehicleCategories }; \ No newline at end of file diff --git a/src/constants/vin-lookup-methods.js b/src/constants/vin-lookup-methods.js index ce2f2859..b1369360 100644 --- a/src/constants/vin-lookup-methods.js +++ b/src/constants/vin-lookup-methods.js @@ -1,7 +1,7 @@ const vinLookupMethodSelections = Object.freeze({ - MANUALVIN: 'ManualVin', + MANUALVIN: 'ManualVin', LICENSEPLATE: 'LicensePlate', - HOMEADDRESS: 'HomeAddress', + HOMEADDRESS: 'HomeAddress' }); export {vinLookupMethodSelections}; diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index 03253171..b401a6a6 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -38,7 +38,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" @@ -195,7 +195,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, diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index 36bf7c70..4eb3b067 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -1,10 +1,10 @@ -import { dynamicStrings } from "@/constants/dynamic-strings"; +import { dynamicStrings } from '@/constants/dynamic-strings'; import { useMainStore } from '@/store'; export function fetchCmsContentForPage(issPage) { - const store = useMainStore() - const clientName = store.issConfig.clientName; - const accountNumber = store.issConfig.accountNumber; + const store = useMainStore() + const clientName = store.issConfig.clientName; + const accountNumber = store.issConfig.accountNumber; const clientOverride = (clientName.length > 0 && accountNumber > 0); return store.getPageData(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( @@ -140,7 +140,12 @@ 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]); } @@ -152,7 +157,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) => { @@ -167,13 +172,13 @@ function processWidgetItemForReplacement(widgetModel, key) { } function mapStringToModal(str) { - let startIndex = str.indexOf("{" + dynamicStrings.MODAL_LINK); + let startIndex = str.indexOf('{' + dynamicStrings.MODAL_LINK); let linkToReplace = str.substring(startIndex, str.length); - linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf("}") + 1); + linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1); let params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length -1) - let splitParams = params.split(","); - let bodyText = '' + splitParams[1] +'' + let splitParams = params.split(','); + let bodyText = '' + splitParams[1] + ''; return str.replace(linkToReplace, bodyText); } @@ -181,28 +186,22 @@ function mapStringToModal(str) { // 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 regexMatches = [...str.matchAll(regexExp)]; + 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) { + return ''; } - - 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)) { @@ -216,6 +215,186 @@ 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 Statement Processing Logic // +/////////////////////////////////// + +/** + * Recursive function - replaces all instances of if statements from the CMS that utilize the specified ifConditionKeyword + * @param {*} str string - Input string to be processed + * @param {*} ifConditionKeyword string - Defines which if statements to process ex: 'globalState' + * @param {*} replacePlaceholderCallback function - Callback to replace CMS placeholder values + * @returns The processed string + */ +export function processIfStatements(str, ifConditionKeyword, replacePlaceholderCallback) { + const containsRelevantIfStatement = new RegExp('{if:' + ifConditionKeyword + ':.+?}', 'g').test( + str + ); + if (!containsRelevantIfStatement) { + return str; + } else { + const ifStatementRegexExpression = getIfStatementRegexExpression(); + const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)]; + const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword( + ifStatementRegexMatches, + ifConditionKeyword + ); + executeIfStatementAndSetProcessedStrings( + completeIfStatementArray, + replacePlaceholderCallback + ); + const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches); + return processIfStatements( + reconstructedPostProcessedString, + ifConditionKeyword, + replacePlaceholderCallback + ); + } +} + +function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) { + let index = 0; + for (const match of matches) { + if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) { + let interiorIndex = 0; + let nestedLevel = 0; + let elseStatementIndex = null; + for (const interiorMatch of matches.slice(index + 1)) { + if (interiorMatch.groups.isIfStatement) { + if (interiorMatch.groups.ifConditionType === ifConditionKeyword) { + break; + } else { + nestedLevel++; + } + } else if (interiorMatch.groups.isElseStatement) { + if (!nestedLevel) { + elseStatementIndex = interiorIndex + 1; + } + } else if (interiorMatch.groups.isEndStatement) { + if (nestedLevel) { + nestedLevel--; + } else { + const ifStatementArray = matches.slice(index, index + interiorIndex + 2); + flagMatchesForProcessing(ifStatementArray, elseStatementIndex); + return ifStatementArray; + } + } + interiorIndex++; + } + } + index++; + } +} + +function flagMatchesForProcessing(matches, elseStatementIndex) { + matches[0].isFlaggedForProcessing = true; + matches[matches.length - 1].isFlaggedForProcessing = true; + if (elseStatementIndex) { + matches[elseStatementIndex].isFlaggedForProcessing = true; + } +} + +function joinProcessedRegexArray(regexMatches) { + let processedString = ''; + regexMatches.forEach((match) => { + const rawString = match[0]; + processedString += match.groups.processedString ?? rawString; + }); + return processedString; +} + +function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlaceholderCallback) { + const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition); + let isInsideDesiredBlock = ifCondition; + + ifStatementArray.forEach((entry) => { + if (entry.groups.isElseStatement && entry.isFlaggedForProcessing) { + isInsideDesiredBlock = !ifCondition; + } else if (entry.groups.isEndStatement && entry.isFlaggedForProcessing) { + isInsideDesiredBlock = true; + } + setProcessedStringOnEntry(entry, isInsideDesiredBlock); + }); +} + +function setProcessedStringOnEntry(entry, isInsideDesiredBlock) { + if (!isInsideDesiredBlock) { + entry.groups.processedString = ''; + } else { + if (entry.groups.isIfStatement) { + entry.groups.processedString = entry.isFlaggedForProcessing + ? entry.groups.ifTrailingString + : entry[0]; + } else if (entry.groups.isElseStatement) { + entry.groups.processedString = entry.isFlaggedForProcessing + ? entry.groups.elseTrailingString + : entry[0]; + } else { + entry.groups.processedString = entry.isFlaggedForProcessing + ? entry.groups.endTrailingString + : entry[0]; + } + } +} + +function getIfStatementRegexExpression() { + // Matches but does not capture: + // {if:...} or {else} or {end} + const anyLogicOperatorNonCapture = '(?:{(?:end|else|if:.*?)})'; + // NOTE: ? syntax stores the captured match like so: match.groups.variableName + const matchStartOfString = + '(?^.+?)' + // Match and Capture all characters (lazy), cannot be empty + '(?=(?:{if))'; // Looks ahead but does not capture {if + const matchIfOperator = + '(?{if:)' + // Match & Capture {if: + '(?.*?):' + // Match all chars up to and including next ':' - Capture all chars up to ':' + '(?.*?)}' + // Match all chars up to and including next '}' - Capture all chars up to '}' + '(?.*?)' + // Match and Capture all characters (lazy), can be empty + '(?=' + + anyLogicOperatorNonCapture + + ')'; // Looks ahead but does not capture the next logic operator + const matchElseOperator = + '(?{else})' + // Match & Capture {else} + '(?.*?)' + // Match & Capture all characters (lazy), can be empty + '(?=' + + anyLogicOperatorNonCapture + + ')'; // Looks ahead but does not capture the next logic operator + const matchEndOperator = + '(?{end})' + // Match & Capture {end} + '(?.*?)' + // Match & Capture all chracters (lazy), can be empty + '(?=' + + anyLogicOperatorNonCapture + + '|$)'; // Looks ahead but does not capture the next logic operator + // Combine all matching patterns, separated by 'or' pipes + return new RegExp( + matchStartOfString + + '|' + + matchIfOperator + + '|' + + matchElseOperator + + '|' + + matchEndOperator, + 'g' + ); +} + +////////////////////////////////////////// +// End of If Statement Processing Logic // +////////////////////////////////////////// + export function doesCopyContainRouterLink(copy) { return copy.includes(this.dynamicStrings.ROUTER_LINK); } @@ -229,14 +408,14 @@ 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]; } 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 @@ -245,5 +424,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>)/g).filter((paragraph) => paragraph !== ""); + return copy.split(/(?:)|(?:<\/p>)/g).filter((paragraph) => paragraph !== ''); } diff --git a/src/iss-components/site-header/site-header.vue b/src/iss-components/site-header/site-header.vue index d9d485be..75a82bef 100644 --- a/src/iss-components/site-header/site-header.vue +++ b/src/iss-components/site-header/site-header.vue @@ -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"); diff --git a/src/layouts/service-packages/service-package-question/service-package-question.vue b/src/layouts/service-packages/service-package-question/service-package-question.vue new file mode 100644 index 00000000..0b8b2f6e --- /dev/null +++ b/src/layouts/service-packages/service-package-question/service-package-question.vue @@ -0,0 +1,361 @@ + + + \ No newline at end of file diff --git a/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue new file mode 100644 index 00000000..74c38517 --- /dev/null +++ b/src/layouts/service-packages/service-package-question/service-package-radio/service-package-radio.vue @@ -0,0 +1,267 @@ + + + + + \ No newline at end of file diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue new file mode 100644 index 00000000..99d97a62 --- /dev/null +++ b/src/layouts/service-packages/service-packages.vue @@ -0,0 +1,151 @@ + + + \ No newline at end of file diff --git a/src/main.js b/src/main.js index d7e9ef55..a7b26869 100644 --- a/src/main.js +++ b/src/main.js @@ -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); diff --git a/src/mixins/input-button-wrapper-mixin.js b/src/mixins/input-button-wrapper-mixin.js index 286c3f21..88ba973e 100644 --- a/src/mixins/input-button-wrapper-mixin.js +++ b/src/mixins/input-button-wrapper-mixin.js @@ -10,7 +10,7 @@ export default { buttonLabel: [Number, String], buttonLabelSubCopy: String, buttonBodyCopy: String, - buttonAuxillaryCopy: String, + buttonAuxiliaryCopy: String, buttonFooterCopy: String, buttonImage: String, buttonImageId: String, diff --git a/src/router/index.js b/src/router/index.js index bc8579c6..16ea8cc4 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -54,6 +54,7 @@ const routes = [ router.addRoute({ path: routeData[0].path, // Always the same path, because we control it with query strings. name: routeData[0].name, + query: to.query, component: routeData[0].component, }); @@ -68,15 +69,16 @@ const routes = [ console.log(error); GoToStartOn404(next); } + return null; } }]; const router = createRouter({ history: createWebHistory("/"), - routes, + routes }); -router.afterEach((to, from) => { +router.afterEach((to, from) => { /*eslint-disable-line*/ const store = useMainStore(); // Update lastPageVisited in the store @@ -106,12 +108,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 = ( @@ -141,7 +143,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; @@ -170,7 +172,7 @@ function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams = router.push({ name: "root", query: Object.assign(optionalQuery, { - issPage: matchingScenarioMap.destinationIssPageValue, + issPage: matchingScenarioMap.destinationIssPageValue }), params: optionalParams }); @@ -182,7 +184,7 @@ 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]); @@ -219,7 +221,7 @@ function GoToStartOn404(next) { router.addRoute({ path: "/", name: errorPageName, - component: lazyLoadComponent(errorPageName), + component: lazyLoadComponent(errorPageName) }); // Put item on the bus @@ -230,13 +232,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 } }); }; @@ -249,14 +251,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 }); } diff --git a/src/router/router-constants/issPage-values.js b/src/router/router-constants/issPage-values.js index b660f98f..634039d4 100644 --- a/src/router/router-constants/issPage-values.js +++ b/src/router/router-constants/issPage-values.js @@ -1,28 +1,27 @@ 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", - REVEAL: "reveal", - ESTIMATE: "estimate", - ADDRESS_VEHICLES: "address-vehicles", - QUOTE: "quote", - HERITAGE: "heritage", + 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', + REVEAL: 'reveal', + ESTIMATE: 'estimate', + ADDRESS_VEHICLES: 'address-vehicles', + SERVICE_PACKAGE: 'service-package', }; \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index 4b6ac5b6..6b40617c 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -118,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( @@ -433,7 +434,58 @@ export const useMainStore = defineStore({ capabilityAnswerResults: capabilityQuestionAnswersForPart, }, }); - }, + }, + getWipers() { + const carId = this.order.vehicle.carId; + ///WARNING WARNING DANGER WILL ROBINSON + ///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 []; + }); + }, + + 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 []; + }); + + }, + + 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: 0, + parts: glassPartsArray, + numberOfRepairChips: isRepair ? numberOfChips : 0 + } + }); + }, + lookupVehicleByVin(vin) { return globalMethods.callHttpClient({ method: endpoints.LookupVehicleByVin.method, From 0588b182a0e1bdc8a6ae0dde0b6d5cdee550df83 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 4 Apr 2023 12:43:40 -0400 Subject: [PATCH 03/20] Draft Update --- package.json | 15 ++++++++------- vue.config.js | 5 ++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 66144ba2..70e8ffde 100644 --- a/package.json +++ b/package.json @@ -4,26 +4,27 @@ "private": true, "scripts": { "serve": "vue-cli-service serve", - "build": "vue-cli-service build", + "build": "vue-cli-service build --modern", "test:unit": "vue-cli-service test:unit --coverage --ci", "test:unit:lite": "vue-cli-service test:unit --ci" }, "dependencies": { - "@pinia/testing": "0.0.14", + "@popperjs/core": "^2.10.2", "axios": "^0.27.2", - "bootstrap": "^5.2.2", + "bootstrap": "^5.2.3", "jest-junit": "^13.0.0", "maska": "^1.5.0", - "pinia": "^2.0.22", + "pinia": "^2.0.33", "pinia-plugin-persistedstate": "^2.2.0", - "vee-validate": "^4.7.0", + "vee-validate": "^4.8.4", "vue": "^3.2.13", - "vue-plugin-load-script": "^2.1.0", - "vue-router": "4.1.3" + "vue-plugin-load-script": "^2.1.1", + "vue-router": "4.1.6" }, "devDependencies": { "@testing-library/jest-dom": "5.16.5", + "@pinia/testing": "0.0.14", "@testing-library/user-event": "14.4.3", "@testing-library/vue": "6.6.1", "@vue/cli-plugin-babel": "^5.0.8", diff --git a/vue.config.js b/vue.config.js index 90d9b20b..d35748ff 100644 --- a/vue.config.js +++ b/vue.config.js @@ -9,6 +9,8 @@ process.env.VUE_APP_GOOGLE_PLACES_API_KEY = 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');"; process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC = "https://www.googletagmanager.com/ns.html?id=GTM-KKNWZ3>m_auth=amlAYNhxUxuskQo7jmjadg>m_preview=env-38>m_cookies_win=x"; +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + module.exports = { publicPath: "/", css: { @@ -27,6 +29,7 @@ module.exports = { }, }, configureWebpack: { - devtool: 'source-map' + devtool: 'source-map', + plugins: [ new MiniCssExtractPlugin()] }, }; From af78d9b4e1363d6de4bf511f140405fb0961ffd8 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 4 Apr 2023 13:05:47 -0400 Subject: [PATCH 04/20] Updated Draft --- src/constants/application-config.js | 1 + .../service-package-question.vue | 625 +++++++++--------- .../service-packages/service-packages.vue | 16 +- src/store/index.js | 2 +- 4 files changed, 323 insertions(+), 321 deletions(-) diff --git a/src/constants/application-config.js b/src/constants/application-config.js index e0ae8df6..389c15d8 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -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 }; \ No newline at end of file diff --git a/src/layouts/service-packages/service-package-question/service-package-question.vue b/src/layouts/service-packages/service-package-question/service-package-question.vue index 0b8b2f6e..a8b030b7 100644 --- a/src/layouts/service-packages/service-package-question/service-package-question.vue +++ b/src/layouts/service-packages/service-package-question/service-package-question.vue @@ -10,352 +10,353 @@ \ No newline at end of file diff --git a/src/layouts/service-packages/service-packages.vue b/src/layouts/service-packages/service-packages.vue index 99d97a62..1800c8e7 100644 --- a/src/layouts/service-packages/service-packages.vue +++ b/src/layouts/service-packages/service-packages.vue @@ -91,19 +91,19 @@ ...resultMap.wipers, ...clonedGlassParts ]; - const pricingResults = await baseMixin.methods.dispatchStoreAction( - storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, - { - availableLineItems: availableLineItems - }, - false - ); + //const pricingResults = await baseMixin.methods.dispatchStoreAction( + // storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, + // { + // availableLineItems: availableLineItems + // }, + // false + //); // 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; + vm.availableLineItems = availableLineItems; //pricingResults; }); }, data() { diff --git a/src/store/index.js b/src/store/index.js index 6b40617c..527d854b 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -479,7 +479,7 @@ export const useMainStore = defineStore({ payload: { carId: carId, serviceType: isRepair ? 'Repair' : 'Replace', - parentAccountNumber: 0, + parentAccountNumber: applicationConfig.CASH_PARENT_ACCOUNT_NUMBER, parts: glassPartsArray, numberOfRepairChips: isRepair ? numberOfChips : 0 } From 3e9aa4d7fdfdf496111a2005a39718b726ecbf0c Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 6 Apr 2023 14:04:44 -0400 Subject: [PATCH 05/20] SSR-313 --- src/constants/endpoints.js | 4 + src/helpers/cms-content-helper.js | 23 +++-- .../site-footer/site-footer.vue | 2 +- .../site-sub-header/site-sub-header.vue | 63 ++++++++------ .../service-package-question.vue | 61 +++++++------- .../service-package-radio.vue | 50 ++++++----- .../service-packages/service-packages.vue | 83 +++++++++++-------- src/router/router-constants/issPage-values.js | 35 ++++---- src/router/router-constants/routing-table.js | 15 +++- src/store/index.js | 54 +++++++++++- 10 files changed, 243 insertions(+), 147 deletions(-) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index f67e97e0..32b25d2d 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -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", diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index 4eb3b067..6e0ba758 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -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); } @@ -42,10 +43,13 @@ export function fetchCmsContentForPage(issPage) { // clientResponse = contains the widgets from the client override page. (null if none) function processPageData(baseResponse, clientResponse) { const pageDataFromCms = {}; - let widgets = []; + 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 @@ -243,7 +247,10 @@ export function processIfStatements(str, ifConditionKeyword, replacePlaceholderC const containsRelevantIfStatement = new RegExp('{if:' + ifConditionKeyword + ':.+?}', 'g').test( str ); - if (!containsRelevantIfStatement) { + //str = str.replace(/\r?\n|\r/g, ''); + + const hasEmbeddedCrLf = /\r?\n|\r/g.test(str); + if (!containsRelevantIfStatement || hasEmbeddedCrLf) { return str; } else { const ifStatementRegexExpression = getIfStatementRegexExpression(); @@ -297,6 +304,8 @@ function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyw } index++; } + console.error('Did not find end in conditional logic'); + return matches; } function flagMatchesForProcessing(matches, elseStatementIndex) { @@ -395,6 +404,10 @@ function getIfStatementRegexExpression() { // End of If Statement Processing Logic // ////////////////////////////////////////// +export function doesCopyContainTextLink(copy) { + return copy.includes(dynamicStrings.TEXT_LINK); +} + export function doesCopyContainRouterLink(copy) { return copy.includes(this.dynamicStrings.ROUTER_LINK); } diff --git a/src/iss-components/site-footer/site-footer.vue b/src/iss-components/site-footer/site-footer.vue index b2537703..2ce2c41c 100644 --- a/src/iss-components/site-footer/site-footer.vue +++ b/src/iss-components/site-footer/site-footer.vue @@ -3,7 +3,7 @@