diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js
index a213e2f4..610b5273 100644
--- a/src/constants/endpoints.js
+++ b/src/constants/endpoints.js
@@ -43,6 +43,10 @@ const endpoints = Object.freeze({
url: '/price/api/v1/price/order-items',
method: 'GET'
},
+ GetProviders: {
+ url: '/location/api/v1/location/providers',
+ method: 'GET'
+ },
GetCapabilityQuestions: {
url: '/parts/api/v1/parts/capability-questions',
method: 'GET'
@@ -63,6 +67,10 @@ const endpoints = Object.freeze({
url: '/parts/api/v1/parts/supporting-items',
method: 'POST'
},
+ GetMobileFeePart: {
+ url: '/parts/api/v1/parts/mobile-fee',
+ method: 'GET'
+ },
GetServiceabilityDetails: {
url: '/location/api/v1/location/serviceability-details',
method: 'GET'
diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js
new file mode 100644
index 00000000..eaa0d5fa
--- /dev/null
+++ b/src/constants/schedule-constants.js
@@ -0,0 +1,13 @@
+const AppointmentTypeStrings = {
+ IN_SHOP: 'Inshop',
+ MOBILE: 'Mobile',
+ DROP_OFF: 'Dropoff'
+};
+const PREMIUM_FEE_PART_TYPE = 'EARLY BIRD';
+const PREMIUM_TIME_SLOT_ID_FLAG = '-PREMIUM';
+const RouteCodeFlags = {
+ ALL_DAY_DROP_OFF: 'ALL DAY DROP OFF',
+ OVERNIGHT_DROP_OFF: 'OVERNIGHT DROP OFF'
+};
+
+export { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE, RouteCodeFlags };
diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue
index 13c3eb91..4787a6c0 100644
--- a/src/digital-components/modal/modal.vue
+++ b/src/digital-components/modal/modal.vue
@@ -47,6 +47,7 @@ import { Modal } from 'bootstrap';
import { useForm } from 'vee-validate';
export default {
+ // eslint-disable-next-line vue/multi-word-component-names
name: 'modal',
components: {
modalButtonMain
@@ -118,11 +119,19 @@ export default {
diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js
index 861eacec..5120c4db 100644
--- a/src/helpers/cms-content-helper.js
+++ b/src/helpers/cms-content-helper.js
@@ -12,9 +12,11 @@ function processWidgetItemForReplacement(widgetModel, key) {
// If we have a string, and it needs to be replaced.
if (typeof widgetModel[key] === 'string') {
if (widgetModel[key].includes('{if:')) {
- widgetModel[key] = processIfStatements(widgetModel[key],
+ widgetModel[key] = processIfStatements(
+ widgetModel[key],
dynamicStrings.GLOBAL_STATE,
- getStoreValueFromString);
+ getStoreValueFromString
+ );
}
if (widgetModel[key].includes(dynamicStrings.MODAL_LINK)) {
@@ -158,14 +160,16 @@ export function fetchCmsContentForPage(issPage) {
// Else get the client override page.
const pageName = `${issPage}_${clientName.toLowerCase().replace(/ /g, '')}`;
- return store.getPageData(pageName).then((clientResponse) =>
- // Process the client override if it exists.
- processPageData(baseResponse, clientResponse),
- (error) => {
- console.error(error);
- // Process the just the base if no client override exists.
- return processPageData(baseResponse, null);
- });
+ return store.getPageData(pageName).then(
+ (clientResponse) =>
+ // Process the client override if it exists.
+ processPageData(baseResponse, clientResponse),
+ (error) => {
+ console.error(error);
+ // Process the just the base if no client override exists.
+ return processPageData(baseResponse, null);
+ }
+ );
})
);
}
@@ -179,8 +183,10 @@ function mapStringToModal(str) {
let linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
- const params = linkToReplace.substring(dynamicStrings.MODAL_LINK.length + 2,
- linkToReplace.length - 1);
+ const params = linkToReplace.substring(
+ dynamicStrings.MODAL_LINK.length + 2,
+ linkToReplace.length - 1
+ );
const splitParams = params.split(',');
const bodyText = `${splitParams[1]}`;
@@ -202,8 +208,10 @@ function mapStringToLink(str) {
let linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
- const params = linkToReplace.substring(dynamicStrings.EXTERNAL_LINK.length + 2,
- linkToReplace.length - 1);
+ const params = linkToReplace.substring(
+ dynamicStrings.EXTERNAL_LINK.length + 2,
+ linkToReplace.length - 1
+ );
const splitParams = params.split(',');
const bodyText = `${splitParams[1]}`;
@@ -291,13 +299,17 @@ export function processIfStatements(str, ifConditionKeyword, replacePlaceholderC
}
const ifStatementRegexExpression = getIfStatementRegexExpression();
const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
- const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(ifStatementRegexMatches,
- ifConditionKeyword);
+ const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(
+ ifStatementRegexMatches,
+ ifConditionKeyword
+ );
executeIfStatementAndSetProcessedStrings(completeIfStatementArray, replacePlaceholderCallback);
const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
- return processIfStatements(reconstructedPostProcessedString,
+ return processIfStatements(
+ reconstructedPostProcessedString,
ifConditionKeyword,
- replacePlaceholderCallback);
+ replacePlaceholderCallback
+ );
}
/**
@@ -419,26 +431,28 @@ function getIfStatementRegexExpression() {
// {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
+ 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:
+ 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}
+ 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}
+ const matchEndOperator =
+ '(?{end})' // Match & Capture {end}
+ '(?.*?)' // Match & Capture all characters (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');
+ return new RegExp(
+ `${matchStartOfString}|${matchIfOperator}|${matchElseOperator}|${matchEndOperator}`,
+ 'g'
+ );
}
/// ///////////////////////////////////////
@@ -501,6 +515,16 @@ export function splitCopyOnCMSPlaceHolder(copy) {
return copy.split(/{(.*?)}/g);
}
+export function getExternalLink(copy) {
+ const url = copy.split(':')[1].split(',')[0];
+ if (url.includes('https-')) {
+ const prefixAdded = url.replace('https-', 'https://');
+ return prefixAdded;
+ }
+
+ return '#!';
+}
+
/**
* Returns string2 of input following this pattern: {string1:string2,string3}
* @param copy
diff --git a/src/helpers/object-helper.js b/src/helpers/object-helper.js
new file mode 100644
index 00000000..5c1cafd6
--- /dev/null
+++ b/src/helpers/object-helper.js
@@ -0,0 +1,80 @@
+// For nested objects, spread operator only creates new references to the top level fields,
+// the remaining nested fields actually reference the original object which can introduce problems.
+
+// The purpose of this method is to deep clone the data in an object recursively, this is useful
+// for cloning modelValues to internal models when regular two-way binding is not an option.
+// See: mobile-location-modal-questions.vue
+
+// Creates a deep clone of an object. Clones primitives, arrays and objects, excluding class instances.
+// https://www.30secondsofcode.org/js/s/deep-clone
+export function deepClone(object) {
+ if (object === null) {
+ return null;
+ }
+
+ const clone = { ...object };
+ // eslint-disable-next-line no-return-assign
+ Object.keys(clone).forEach((key) =>
+ (clone[key] = typeof object[key] === 'object' ? deepClone(object[key]) : object[key]));
+
+ if (Array.isArray(object)) {
+ clone.length = object.length;
+ return Array.from(clone);
+ }
+
+ return clone;
+}
+
+// The purpose of this method is to check for array or object equality recursively to determine if two complex objects are equal.
+// This is only a comparison of data, not functions.
+export function deepEqual(obj1, obj2) {
+ if (typeof obj1 !== typeof obj2) {
+ return false;
+ }
+
+ if (obj1 === null || obj2 === null) {
+ return obj1 === obj2;
+ }
+
+ if (Array.isArray(obj1) && Array.isArray(obj2)) {
+ if (obj1.length !== obj2.length) {
+ return false;
+ }
+
+ const sorted1 = obj1.slice().sort();
+ const sorted2 = obj2.slice().sort();
+
+ for (let i = 0; i < sorted1.length; i++) {
+ if (!deepEqual(sorted1[i], sorted2[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ if (typeof obj1 === 'object' && typeof obj2 === 'object') {
+ const keys1 = Object.keys(obj1);
+ const keys2 = Object.keys(obj2);
+
+ if (keys1.length !== keys2.length) {
+ return false;
+ }
+
+ const sortedKeys1 = keys1.sort();
+ const sortedKeys2 = keys2.sort();
+
+ for (let i = 0; i < sortedKeys1.length; i++) {
+ const key1 = sortedKeys1[i];
+ const key2 = sortedKeys2[i];
+
+ if (key1 !== key2 || !deepEqual(obj1[key1], obj2[key2])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return obj1 === obj2;
+}
diff --git a/src/helpers/service-location-helper.js b/src/helpers/service-location-helper.js
index 242752d3..a8ac5503 100644
--- a/src/helpers/service-location-helper.js
+++ b/src/helpers/service-location-helper.js
@@ -1,16 +1,5 @@
import { useMainStore } from '@/store';
-export async function getServiceabilityDetails(serviceZipCode, lineItems) {
- const serviceabilityDetails = await useMainStore().getServiceabilityDetails(
- {
- serviceZipCode,
- lineItems
- },
- false
- );
- return Promise.resolve(serviceabilityDetails);
-}
-
export async function getZipCodeData(zipCode) {
const serviceZipValidationResponse = await useMainStore().validateZip({ zip: zipCode });
@@ -22,3 +11,57 @@ export async function getZipCodeData(zipCode) {
zipCodeCtu: serviceZipValidationResponse.data.zipCodeCtu
};
}
+
+export async function getPricedMobileFeePart(serviceZipCode) {
+ if (!serviceZipCode) {
+ return Promise.resolve(null);
+ }
+ const zipCodeData = await getZipCodeData(serviceZipCode);
+
+ // Get the Mobile Fee Part
+ const mobileFeePart = await useMainStore().getMobileFeePart();
+
+ // Get the Mobile Fee Part Price
+ const pricingResults = await useMainStore()
+ .priceOrderItemsAndSaveServerData([mobileFeePart.data], serviceZipCode, zipCodeData.zipCodeCtu);
+
+ return Promise.resolve(pricingResults[0]);
+}
+
+export async function getServiceabilityDetails(serviceZipCode, lineItems) {
+ const serviceabilityDetails = await useMainStore().getServiceabilityDetails(
+ {
+ serviceZipCode,
+ lineItems
+ },
+ false
+ );
+ return Promise.resolve(serviceabilityDetails);
+}
+
+export async function getAvailabilityRating(
+ startDate,
+ endDate,
+ shopAppointmentType,
+ providerNumber
+) {
+ // For a given shop provider number and date range, get the appointment time slots available
+ const shopTimeSlots = await useMainStore().getShopTimeSlots(
+ {
+ providerNumber,
+ startDate,
+ endDate,
+ shopAppointmentType
+ },
+ false
+ );
+
+ const numberOfDaysToEvaluate = 2;
+ const isGoodAvailability =
+ shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length
+ >= numberOfDaysToEvaluate;
+
+ const shopStatus = isGoodAvailability ? 'high' : 'low';
+
+ return Promise.resolve(shopStatus);
+}
diff --git a/src/iss-components/site-footer/site-footer.vue b/src/iss-components/site-footer/site-footer.vue
index f83d8372..2c4a4cb0 100644
--- a/src/iss-components/site-footer/site-footer.vue
+++ b/src/iss-components/site-footer/site-footer.vue
@@ -2,7 +2,7 @@