Merge branch 'develop' into jnou/add-oem-test
This commit is contained in:
commit
557fe7cf9e
10 changed files with 433 additions and 143 deletions
|
|
@ -114,7 +114,7 @@ body {
|
|||
debug: true,
|
||||
},
|
||||
{
|
||||
name: 'Test',
|
||||
name: 'SysTest',
|
||||
hostName: 'www-test2.safelite.com',
|
||||
apiHostname: 'digitalapi.test.safelite.io',
|
||||
debug: true,
|
||||
|
|
@ -142,83 +142,178 @@ body {
|
|||
return match;
|
||||
}
|
||||
|
||||
function getExperimentSettingsFromVuex(vuexData) {
|
||||
const experiments = vuexData?.applicationUser?.experiments;
|
||||
|
||||
if(experiments) {
|
||||
return experiments
|
||||
.filter((e) => !!e.isActive)
|
||||
.map((e) => e.settings)
|
||||
.reduce((prev, next) => Object.assign(prev, next), {}) ?? {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function extractDataFromVuex(existingVuexData) {
|
||||
let bailoutInfo = [];
|
||||
let headerInfo = [];
|
||||
|
||||
try {
|
||||
// =============== Collect diagnostic data:
|
||||
bailoutInfo = [];
|
||||
|
||||
// App & Site Name
|
||||
bailoutInfo.push({ name: 'App Name', value: 'FixMyGlass' });
|
||||
bailoutInfo.push({ name: 'Site Name', value: 'SafeliteDotcom' });
|
||||
|
||||
// Not included (yet) from heritage:
|
||||
// - Site ID
|
||||
// - Code
|
||||
// - Module
|
||||
// - Page
|
||||
|
||||
bailoutInfo.push({ name: 'Timestamp', value: `${new Date()}` });
|
||||
|
||||
// - URL
|
||||
// - Server
|
||||
|
||||
const sessionId = getCookieValueByName('sid');
|
||||
bailoutInfo.push({ name: 'Session Id', value: sessionId });
|
||||
|
||||
const userAgent = window.navigator.userAgent;
|
||||
bailoutInfo.push({ name: 'User Agent', value: userAgent });
|
||||
|
||||
// - IP
|
||||
// - Session Log Sequence Number
|
||||
|
||||
bailoutInfo.push({ name: 'Referral Seq Number', value: existingVuexData.order?.referralSequenceNumber });
|
||||
bailoutInfo.push({ name: 'Referral Number', value: existingVuexData.order?.referralNumber });
|
||||
bailoutInfo.push({ name: 'Referral Date', value: existingVuexData.order?.referralDate });
|
||||
bailoutInfo.push({ name: 'Referral Provider Number', value: existingVuexData.order?.serviceLocation?.provider?.providerNumber });
|
||||
bailoutInfo.push({ name: 'Referral CTU', value: existingVuexData.order?.serviceLocation?.provider?.address?.zipCodeCtu });
|
||||
|
||||
// - Referral Insured Zip
|
||||
// - Referral Insured Service Zip
|
||||
|
||||
bailoutInfo.push({ name: 'Referral CarID', value: existingVuexData.order?.vehicle?.carId });
|
||||
|
||||
const vehicle = existingVuexData.order?.vehicle;
|
||||
const vehicleString = vehicle?.year
|
||||
? `${vehicle.year} ${vehicle.make} ${vehicle.model} - ${vehicle.style}`
|
||||
: null;
|
||||
bailoutInfo.push({ name: 'Referral Vehicle', value: vehicleString });
|
||||
bailoutInfo.push({ name: 'Work Order Number', value: existingVuexData.order?.workOrderNumber });
|
||||
bailoutInfo.push({ name: 'Work Order ID', value: existingVuexData.order?.workOrderId });
|
||||
bailoutInfo.push({ name: 'Parent Account Number', value: existingVuexData.order?.payment?.parentAccountNumber });
|
||||
|
||||
const environmentData = getCurrentEnvironmentData();
|
||||
const userIdCookieName = `FunnelUserId-${environmentData?.name}`;
|
||||
bailoutInfo.push({ name: 'User Id', value: getCookieValueByName(userIdCookieName) });
|
||||
|
||||
// - Parent Account Name
|
||||
// - Client GUID
|
||||
|
||||
// =============== Collect header data:
|
||||
headerInfo = [];
|
||||
|
||||
headerInfo.push({ name: 'X-Experiment-Data', value: JSON.stringify(getExperimentSettingsFromVuex(existingVuexData)) });
|
||||
headerInfo.push({ name: 'X-Application-Name', value: 'FixMyGlass' });
|
||||
|
||||
const sessionKeyCookieName = `FunnelSessionKey-${environmentData?.name}`;
|
||||
|
||||
headerInfo.push({ name: 'X-Session-Sequence-Number', value: getCookieValueByName(sessionKeyCookieName) });
|
||||
headerInfo.push({ name: 'X-Referral-Sequence-Number', value: existingVuexData?.order?.referralSequenceNumber });
|
||||
headerInfo.push({ name: 'X-Enterprise-Order-Number', value: existingVuexData?.order?.eon });
|
||||
headerInfo.push({ name: 'log-enabled', value: existingVuexData?.applicationUser?.loggingOption ?? false });
|
||||
} finally {
|
||||
return {
|
||||
headerInfo: headerInfo,
|
||||
bailoutInfo: bailoutInfo
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function generateRequestHeader(headerInfo) {
|
||||
let headers = {};
|
||||
|
||||
headerInfo.forEach(
|
||||
keyPair => {
|
||||
headers[keyPair.name] = keyPair.value;
|
||||
}
|
||||
);
|
||||
|
||||
headers['X-Transaction-Id'] = crypto.randomUUID();
|
||||
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function getFieldWithName(info, name) {
|
||||
const match = info?.find(
|
||||
(row) => row.name === name
|
||||
);
|
||||
|
||||
return match?.value;
|
||||
}
|
||||
|
||||
function clearApplicationData() {
|
||||
window.localStorage.removeItem('vuex');
|
||||
|
||||
const environmentData = getCurrentEnvironmentData();
|
||||
const cookieNames = [
|
||||
`FunnelUserId-${environmentData?.name}`,
|
||||
`FunnelSessionKey-${environmentData?.name}`,
|
||||
`FunnelSessionInfo-${environmentData?.name}`,
|
||||
`sid`,
|
||||
`dxdev`,
|
||||
];
|
||||
|
||||
cookieNames.forEach(
|
||||
(cookieName) => {
|
||||
let cookieToAdd = `${cookieName}=undefined; path=/; domain=${environmentData.hostName}; max-age=0`;
|
||||
document.cookie = cookieToAdd;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
window.onload = async () => {
|
||||
// =============== Check for session info:
|
||||
const existingVuexDataJSON = window.localStorage.getItem('vuex');
|
||||
const existingVuexData = existingVuexDataJSON ? JSON.parse(existingVuexDataJSON) : null;
|
||||
const existingBailoutInfoJSON = window.sessionStorage.getItem('bailoutInfo');
|
||||
const existingBailoutInfo = existingBailoutInfoJSON ? JSON.parse(existingBailoutInfoJSON) : null;
|
||||
const existingHeaderInfoJSON = window.sessionStorage.getItem('headerInfo');
|
||||
const existingHeaderInfo = existingHeaderInfoJSON ? JSON.parse(existingHeaderInfoJSON) : null;
|
||||
|
||||
console.log(`================ VUEX DATA`);
|
||||
console.log(existingVuexData);
|
||||
console.log(`================ PRIOR BAILOUT DATA`);
|
||||
console.log(existingBailoutInfo);
|
||||
console.log(`================ PRIOR HEADER DATA`);
|
||||
console.log(existingHeaderInfo);
|
||||
|
||||
let bailoutInfo = null;
|
||||
let headerInfo = null;
|
||||
|
||||
if(existingVuexData) {
|
||||
try {
|
||||
// =============== Collect diagnostic data:
|
||||
bailoutInfo = [];
|
||||
const results = extractDataFromVuex(existingVuexData);
|
||||
bailoutInfo = results.bailoutInfo;
|
||||
headerInfo = results.headerInfo;
|
||||
|
||||
// App & Site Name
|
||||
bailoutInfo.push({ name: 'App Name', value: 'FixMyGlass' });
|
||||
bailoutInfo.push({ name: 'Site Name', value: 'SafeliteDotcom' });
|
||||
clearApplicationData();
|
||||
|
||||
// Not included (yet) from heritage:
|
||||
// - Site ID
|
||||
// - Code
|
||||
// - Module
|
||||
// - Page
|
||||
|
||||
bailoutInfo.push({ name: 'Timestamp', value: `${new Date()}` });
|
||||
|
||||
// - URL
|
||||
// - Server
|
||||
|
||||
const sessionId = getCookieValueByName('sid');
|
||||
bailoutInfo.push({ name: 'Session Id', value: sessionId });
|
||||
|
||||
const userAgent = window.navigator.userAgent;
|
||||
bailoutInfo.push({ name: 'User Agent', value: userAgent });
|
||||
|
||||
// - IP
|
||||
// - Session Log Sequence Number
|
||||
|
||||
bailoutInfo.push({ name: 'Referral Seq Number', value: existingVuexData.order?.referralSequenceNumber });
|
||||
bailoutInfo.push({ name: 'Referral Number', value: existingVuexData.order?.referralNumber });
|
||||
bailoutInfo.push({ name: 'Referral Date', value: existingVuexData.order?.referralDate });
|
||||
bailoutInfo.push({ name: 'Referral Provider Number', value: existingVuexData.order?.serviceLocation?.provider?.providerNumber });
|
||||
bailoutInfo.push({ name: 'Referral CTU', value: existingVuexData.order?.serviceLocation?.provider?.address?.zipCodeCtu });
|
||||
|
||||
// - Referral Insured Zip
|
||||
// - Referral Insured Service Zip
|
||||
|
||||
bailoutInfo.push({ name: 'Referral CarID', value: existingVuexData.order?.vehicle?.carId });
|
||||
|
||||
const vehicle = existingVuexData.order?.vehicle;
|
||||
const vehicleString = vehicle?.year
|
||||
? `${vehicle.year} ${vehicle.make} ${vehicle.model} - ${vehicle.style}`
|
||||
: null;
|
||||
bailoutInfo.push({ name: 'Referral Vehicle', value: vehicleString });
|
||||
bailoutInfo.push({ name: 'Work Order Number', value: existingVuexData.order?.workOrderNumber });
|
||||
bailoutInfo.push({ name: 'Work Order ID', value: existingVuexData.order?.workOrderId });
|
||||
bailoutInfo.push({ name: 'Parent Account Number', value: existingVuexData.order?.payment?.parentAccountNumber });
|
||||
|
||||
// - Parent Account Name
|
||||
// - Client GUID
|
||||
} finally {
|
||||
// If any information gathered, write to session storage.
|
||||
if(bailoutInfo) {
|
||||
window.sessionStorage.setItem('bailoutInfo', JSON.stringify(bailoutInfo));
|
||||
}
|
||||
|
||||
// Then *always* clear vuex data.
|
||||
window.localStorage.removeItem('vuex');
|
||||
if(bailoutInfo) {
|
||||
window.sessionStorage.setItem('bailoutInfo', JSON.stringify(bailoutInfo));
|
||||
}
|
||||
} else if(existingBailoutInfo) {
|
||||
// Proceed with prior data.
|
||||
bailoutInfo = existingBailoutInfo;
|
||||
|
||||
if(headerInfo) {
|
||||
window.sessionStorage.setItem('headerInfo', JSON.stringify(headerInfo));
|
||||
}
|
||||
} else {
|
||||
bailoutInfo = existingBailoutInfo ?? [];
|
||||
headerInfo = existingHeaderInfo ?? [];
|
||||
}
|
||||
|
||||
const environmentInfo = getCurrentEnvironmentData();
|
||||
|
|
@ -275,11 +370,11 @@ body {
|
|||
);
|
||||
const entryString = `User encountered bailout page.\n${new Date()}\n${infoString ?? 'No information recoverable'}`;
|
||||
|
||||
const headers = generateRequestHeader(headerInfo);
|
||||
|
||||
const request = new Request(endpointUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers: headers,
|
||||
body: JSON.stringify({
|
||||
entry: entryString,
|
||||
}),
|
||||
|
|
@ -292,6 +387,38 @@ body {
|
|||
console.error(`=== ERROR SENDING LOGGING`);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
try {
|
||||
const endpointUrl = `https://${apiHostname}/analytics/api/v1/analytics/log-page-view`;
|
||||
|
||||
const headers = generateRequestHeader(headerInfo);
|
||||
|
||||
const payload = {
|
||||
AppName: "FixMyGlass",
|
||||
action: "",
|
||||
applicationName: "FixMyGlassNextGen",
|
||||
event: "ENTRY",
|
||||
experimentsForUser: [],
|
||||
pageName: "static-error",
|
||||
parentAccountNumber: getFieldWithName(bailoutInfo, 'Parent Account Number') ?? 0,
|
||||
referralSequenceNumber: getFieldWithName(bailoutInfo, 'Referral Seq Number'),
|
||||
sessionId: getFieldWithName(bailoutInfo, 'Session Id'),
|
||||
sessionKey: getFieldWithName(headerInfo, 'X-Session-Sequence-Number'),
|
||||
shouldUseSessionId: false,
|
||||
userId: getFieldWithName(bailoutInfo, 'User Id'),
|
||||
};
|
||||
|
||||
const request = new Request(endpointUrl, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const response = await fetch(request);
|
||||
} catch(e) {
|
||||
console.error(`=== ERROR SENDING PAGEVIEW`);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@
|
|||
'AccountService': {
|
||||
baseUrl: apiUrl + '/account/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/account/insurance-companies', method: 'GET' },
|
||||
{ path: '/account/account-details/373341', method: 'GET' },
|
||||
{ path: '/account/bill-to-account-number?ParentAccountNumber=373341&ProviderNumber=01820&IsItac=false&TypeOfClaim=Personal&LineOfBusiness=Commercial', method: 'GET' }
|
||||
|
|
@ -518,7 +518,7 @@
|
|||
'LocationService': {
|
||||
baseUrl: apiUrl + '/location/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/location/zip/43015/Replace', method: 'GET' },
|
||||
{ path: '/location/alert-reasons/03357', method: 'GET' },
|
||||
{ path: '/location/providers/43015/Replace/100/167132/true/CR00046193/false', method: 'GET' },
|
||||
|
|
@ -528,7 +528,7 @@
|
|||
'VehicleService': {
|
||||
baseUrl: apiUrl + '/vehicle/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/vehicle/years', method: 'GET' },
|
||||
{ path: '/vehicle/makes/1997', method: 'GET' },
|
||||
{ path: '/vehicle/Models/1997/Toyota', method: 'GET' },
|
||||
|
|
@ -539,7 +539,7 @@
|
|||
'PartService': {
|
||||
baseUrl: apiUrl + '/parts/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/parts/damage-options/CR00046193', method: 'GET' },
|
||||
{ path: '/parts/rain-repel', method: 'GET' },
|
||||
{ path: '/parts/parts-or-questions', method: 'POST', body: {"carId":"CR00046193","glassPieces":[{"location":"Windshield","name":"Single"}],"zip":"43015","vin":null,"serviceType":null,"referralSeqNumber":environmentContext.referralNumber,"AppName":"FixMyGlass"} },
|
||||
|
|
@ -553,7 +553,7 @@
|
|||
'PriceService': {
|
||||
baseUrl: apiUrl + '/price/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/price/taxed-order-items?ParentAccountNumber=167132&BillToAccountNumber=87291&ProviderNumber=003357&AppointmentType=Mobile&ServiceLocation.City=asdf&ServiceLocation.State=OH&ServiceLocation.ZipCode=43015&lineItems[0].partNumber=FW02018GBNN&lineItems[0].laborAmount=60&lineItems[0].kitPrice=0&lineItems[0].sellingPrice=297.33&lineItems[1].partNumber=GGG%202018&lineItems[1].laborAmount=0&lineItems[1].kitPrice=0&lineItems[1].sellingPrice=13.4&lineItems[2].partNumber=RECYCLE%20FEE&lineItems[2].laborAmount=39.99&lineItems[2].kitPrice=0&lineItems[2].sellingPrice=0&ServerData=' + encodeURIComponent(environmentContext.serverData), method: 'GET' },
|
||||
{ path: '/price/order-items', method: 'POST', body: { "parentAccountNumber": 167132, "billToAccountNumber": "87291", "providerNumber": "03357", "carId": "CR00046193", "year": 1997, "make": "Toyota", "model": "Camry", "eon": environmentContext.eon, "state": "OH", "referralNumber": environmentContext.referralNumber, "referralSequenceNumber": environmentContext.referralNumber, "zipCode": "43015", "serviceZipCode": "43015", "referralDate": "2025-10-29T10:55:27.18", "isReplacement": true, "deductible": 0, "coverageStatus": null, "lineItems": [ { "partNumber": "RECYCLE FEE" }, { "partNumber": "FW02018GBNN" }, { "partNumber": "GGG 2018" }, { "partNumber": "SBB19" }, { "partNumber": "SBB21" } ], "serverData": "", "AppName": "FixMyGlass" } },
|
||||
{ path: '/price/order-promo', method: 'POST', body: {"promoCode":"WEBSL45","addableVaps":[{"partNumber":"RAIN REPEL","description":null,"partType":"RAIN REPEL","isInsurable":null,"id":"ccb0865d-1c0a-4be7-8605-bc7b9cb3d221","isChildPart":false,"laborAmount":0,"sellingPrice":44.99,"kitPrice":0,"salesTax":null},{"partNumber":"SBB19","description":"SAFELITE BEAM BLADE 19","partType":"FRONT WIPER","isInsurable":null,"id":"4db19a8b-739f-4f7b-983e-f6308c5f5901","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":null},{"partNumber":"SBB21","description":"SAFELITE BEAM BLADE 21","partType":"FRONT WIPER","isInsurable":null,"id":"e27ae20a-3a60-4fdf-a738-4b56d2e761cb","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":null}],"order":{"appointmentType":"Mobile","carId":"CR00046193","correlationId":"b0261e48-1111-44fc-abc9-34b41380687c","eon":environmentContext.eon,"isInsurance":false,"isRepair":false,"glassToReplace":[{"location":"Windshield","name":"Single"}],"lineItemsOnOrder":[{"partNumber":"FW02018GBNN","basePartNumber":"FW02018","description":"solar","color":"Green Tint, Blue Shade","partType":"WINDSHIELD","canSafeliteRecalibrate":false,"requiresRecalibration":false,"requiresCapabilityQuestions":false,"recalibrationType":null,"childParts":[{"partNumber":"GGG 2018","safelitePartNumber":"GGG 2018","id":"329cf0ec-8106-4e62-ab66-de0da8f18a1d","isChildPart":true,"laborAmount":0,"sellingPrice":13.4,"kitPrice":0,"salesTax":0.94}],"kitPrice":0,"sellingPrice":368.94,"laborAmount":60,"id":"4326cee6-b39e-41ff-bb29-df9220e32298","isChildPart":false,"salesTax":30.02},{"partNumber":"GGG 2018","safelitePartNumber":"GGG 2018","id":"329cf0ec-8106-4e62-ab66-de0da8f18a1d","isChildPart":true,"laborAmount":0,"sellingPrice":13.4,"kitPrice":0,"salesTax":0.94},{"partNumber":"RECYCLE FEE","description":null,"partType":"REPLACE FEE","isInsurable":null,"isChildPart":false,"laborAmount":39.99,"sellingPrice":0,"kitPrice":0,"salesTax":2.8,"id":"3771aed5-0de1-4f0e-b1ad-394f73e08f38","cartItemType":"REPLACE FEE"},{"partNumber":"MOBILE FEE","description":null,"partType":"MOBILE FEE","isInsurable":true,"isChildPart":false,"laborAmount":0,"sellingPrice":0,"kitPrice":0,"salesTax":0,"id":"2bc936ec-ef5e-4fe2-95fb-e946792b1ae5"},{"partNumber":"SBB19","description":"SAFELITE BEAM BLADE 19","partType":"FRONT WIPER","isInsurable":null,"id":"4db19a8b-739f-4f7b-983e-f6308c5f5901","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":2.45,"cartItemType":"FRONT WIPERS"},{"partNumber":"SBB21","description":"SAFELITE BEAM BLADE 21","partType":"FRONT WIPER","isInsurable":null,"id":"e27ae20a-3a60-4fdf-a738-4b56d2e761cb","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":2.45,"cartItemType":"FRONT WIPERS"},{"partNumber":"RAIN REPEL","description":null,"partType":"RAIN REPEL","isInsurable":null,"id":"ccb0865d-1c0a-4be7-8605-bc7b9cb3d221","isChildPart":false,"laborAmount":0,"sellingPrice":44.99,"kitPrice":0,"salesTax":3.15,"cartItemType":"RAIN REPEL"}],"parentAccountNumber":"167132","referralSequenceNumber":environmentContext.referralNumber,"serviceState":"OH","serverData":environmentContext.serverData,"vehicleYear":"1997","zipCodeOrProviderCtu":"03357"},"AppName":"FixMyGlass"} },
|
||||
|
|
@ -563,7 +563,7 @@
|
|||
'ScheduleService': {
|
||||
baseUrl: apiUrl + '/schedule/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/schedule/mobile-time-slots', method: 'POST', body: {"startDate":startDate,"endDate":endDate,"applicationName":"FixMyGlass","parentAccountNumber":"167132","carId":"CR00046193","lineItems":[{"partNumber":"RECYCLE FEE","partType":"REPLACE FEE","isGlassPart":false},{"partNumber":"SBB19","partType":"FRONT WIPER","isGlassPart":false},{"partNumber":"SBB21","partType":"FRONT WIPER","isGlassPart":false},{"partNumber":"RAIN REPEL","partType":"RAIN REPEL","isGlassPart":false},{"partNumber":"FW02018GBNN","partType":"WINDSHIELD","isGlassPart":true},{"partNumber":"GGG 2018","isGlassPart":false}],"glassPieces":[{"location":"Windshield","name":"Single"}],"eon":environmentContext.eon,"billToAccountNumber":"87291","coverage":{"status":null,"deductible":0,"additionalAuthFlag":null},"partSelection":{"hasAnsweredPartQuestions":false,"hasAnsweredMoldingQuestions":false,"hasAnsweredCapabilityQuestions":false,"hasManuallySelectedParts":false},"vehicle":{"year":1997,"make":"Toyota","model":"Camry","style":"4 door sedan","vin":""},"zipCode":"43015","AppName":"FixMyGlass"} },
|
||||
{ path: '/schedule/shop-time-slots', method: 'POST', body: {"providerNumber":"003417","startDate":startDate,"endDate":endDate,"shopAppointmentType":"InshopOrDropoff","applicationName":"FixMyGlass","parentAccountNumber":"167132","carId":"CR00046193","lineItems":[{"partNumber":"RECYCLE FEE","partType":"REPLACE FEE","isGlassPart":false},{"partNumber":"FW02018GBNN","partType":"WINDSHIELD","isGlassPart":true},{"partNumber":"GGG 2018","isGlassPart":false}],"glassPieces":[{"location":"Windshield","name":"Single"}],"eon":environmentContext.eon,"billToAccountNumber":"87291","coverage":{"status":null,"deductible":0,"additionalAuthFlag":null},"partSelection":{"hasAnsweredPartQuestions":false,"hasAnsweredMoldingQuestions":false,"hasAnsweredCapabilityQuestions":false,"hasManuallySelectedParts":false},"vehicle":{"year":1997,"make":"Toyota","model":"Camry","style":"4 door sedan","vin":""},"AppName":"FixMyGlass"} }
|
||||
]
|
||||
|
|
@ -571,25 +571,25 @@
|
|||
'AnalyticsService': {
|
||||
baseUrl: apiUrl + '/analytics/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
]
|
||||
},
|
||||
'ExperimentService': {
|
||||
baseUrl: apiUrl + '/experiments/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
]
|
||||
},
|
||||
'OrderService': {
|
||||
baseUrl: apiUrl + '/order/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
]
|
||||
},
|
||||
'ContentService': {
|
||||
baseUrl: apiUrl + '/content/api/v1',
|
||||
endpoints: [
|
||||
//{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/healthcheck', method: 'GET' },
|
||||
{ path: '/content/fmg/vehicle', method: 'GET' },
|
||||
]
|
||||
}
|
||||
|
|
@ -674,7 +674,7 @@
|
|||
}
|
||||
|
||||
function updateAtAGlanceText() {
|
||||
const healthyCount = Object.values(healthData).filter(service => service.status === 'healthy').length;
|
||||
const healthyCount = Object.values(healthData).filter(service => (service.status === 'healthy' || service.status === 'degraded')).length;
|
||||
const totalCount = Object.values(healthData).length;
|
||||
let text = 'Healthy';
|
||||
let className = 'stat-pill stat-pill-healthy';
|
||||
|
|
@ -763,14 +763,15 @@
|
|||
// Determine overall service status
|
||||
const endpoints = healthData[serviceName].endpoints;
|
||||
const healthyCount = endpoints.filter(ep => !isDegraded(ep) && !isUnhealthy(ep)).length;
|
||||
const unhealthyCount = endpoints.filter(ep => isUnhealthy(ep)).length;
|
||||
const totalCount = endpoints.length;
|
||||
|
||||
if (healthyCount === totalCount) {
|
||||
healthData[serviceName].status = 'healthy';
|
||||
} else if (healthyCount > 0) {
|
||||
healthData[serviceName].status = 'degraded';
|
||||
} else {
|
||||
} else if (unhealthyCount > 0) {
|
||||
healthData[serviceName].status = 'unhealthy';
|
||||
} else {
|
||||
healthData[serviceName].status = 'degraded';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
<div class="modal-footer">
|
||||
<slot name="modal-footer-slot"></slot>
|
||||
<modalButtonMain
|
||||
v-if="!isFooterButtonSuppressed"
|
||||
:isPrimary="isFooterButtonPrimary"
|
||||
class="w-100 modal-footer-button"
|
||||
:id="modalId + '-modalbtn'"
|
||||
|
|
@ -67,6 +68,7 @@ export default {
|
|||
staticBackdrop: Boolean,
|
||||
footerButtonDisabled: Boolean,
|
||||
isFooterButtonPrimary: Boolean,
|
||||
isFooterButtonSuppressed: Boolean,
|
||||
onModalOpenedCallback: {
|
||||
type: Function,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<div id="afterpay-banner" role="alert">
|
||||
<component
|
||||
:is="'script'"
|
||||
src="https://js.squarecdn.com/square-marketplace.js"
|
||||
async></component>
|
||||
|
||||
<div class="afterpay-modal-banner" role="alert">
|
||||
<div>
|
||||
<span
|
||||
class="callout"
|
||||
|
|
@ -22,22 +17,52 @@
|
|||
<span v-else v-html="token"></span>
|
||||
<span class="nbsp"> </span>
|
||||
</span>
|
||||
<a
|
||||
id="afterpay-learnmore"
|
||||
href="#"
|
||||
data-afterpay-modal="en_US"
|
||||
data-bind="click:afterpayLearnMore">
|
||||
{{ modalCopy }}
|
||||
</a>
|
||||
<textLink
|
||||
linkType="text"
|
||||
:text="bannerCta"
|
||||
href="javascript:void(0)"
|
||||
@click-event="openModal" />
|
||||
</div>
|
||||
</div>
|
||||
<modal
|
||||
:ref="modalName"
|
||||
class="afterpay-modal"
|
||||
:headerText="modalHeaderText"
|
||||
:isFooterButtonSuppressed="true">
|
||||
<template v-slot:modal-header-slot>
|
||||
<span>{{ modalSubHeaderText }}</span>
|
||||
</template>
|
||||
<template v-slot>
|
||||
<span class="afterpay-sections">
|
||||
<span
|
||||
v-for="section in afterpaySectionsContent"
|
||||
:key="section"
|
||||
class="afterpay-section">
|
||||
<span class="section-header">
|
||||
<img v-if="section.AnswerImageUrl" :src="section.AnswerImageUrl" />
|
||||
<h3 v-if="section.Text" v-html="section.Text"></h3>
|
||||
</span>
|
||||
<span v-html="section.content"></span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:modal-footer-slot>
|
||||
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { splitCopyOnCMSPlaceHolder, splitCMSCopyOnBR } from "@/helpers/cms-content-helper";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { getPromosThatMatchLineItemsOnOrder } from "@/helpers/promotions-helper";
|
||||
import { getArrayOfAllLineItemsAndChildParts } from "@/store";
|
||||
import {
|
||||
doesCopyContainTextLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
getExternalLink,
|
||||
splitCMSCopyOnBR,
|
||||
} from "@/helpers/cms-content-helper";
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import modal from "@/digital-components/modal/modal";
|
||||
|
||||
const INLINE_IMAGE_TOKEN = "custom:inlineImage";
|
||||
const AFTERPAY_PRICE_TOKEN = "custom:afterpayPrice";
|
||||
|
|
@ -48,20 +73,16 @@ function isInlineImageToken(token) {
|
|||
function isAfterpayPriceToken(token) {
|
||||
return token.includes(AFTERPAY_PRICE_TOKEN);
|
||||
}
|
||||
|
||||
function getInlineAltText(token) {
|
||||
const innerTokens = token.split(",");
|
||||
|
||||
return innerTokens[1] ?? "";
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "afterpay-modal-banner",
|
||||
name: "afterpay-banner",
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
lineItems: Array,
|
||||
isInsuranceSelected: Boolean,
|
||||
afterpayExtendedPayOptionThreshold: Number,
|
||||
modalWidgetName: String,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
|
|
@ -70,6 +91,17 @@ export default {
|
|||
isInlineImageToken,
|
||||
isAfterpayPriceToken,
|
||||
getInlineAltText,
|
||||
doesCopyContainTextLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
getExternalLink,
|
||||
openModal() {
|
||||
this.modal?.openModal();
|
||||
},
|
||||
closeModal() {
|
||||
this.modal?.closeModal();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
|
|
@ -84,39 +116,40 @@ export default {
|
|||
afterpayCopyTokens() {
|
||||
return splitCopyOnCMSPlaceHolder(this.getCmsContent(this.cmsWidgetName, "BodyText"));
|
||||
},
|
||||
modalCopy() {
|
||||
bannerCta() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||
},
|
||||
getTierOnePackagePrice() {
|
||||
let allLineItems = getArrayOfAllLineItemsAndChildParts(this.lineItems);
|
||||
if (this.lineItems.promos) {
|
||||
allLineItems = allLineItems?.filter((item) => item.partType !== "PROMO_DISCOUNT");
|
||||
}
|
||||
|
||||
let price = baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(allLineItems)
|
||||
);
|
||||
|
||||
if (this.lineItems.promos) {
|
||||
let allLineItems = getArrayOfAllLineItemsAndChildParts(this.lineItems);
|
||||
const promos = getPromosThatMatchLineItemsOnOrder(
|
||||
this.lineItems.promos,
|
||||
allLineItems
|
||||
);
|
||||
promos.forEach((promo) => {
|
||||
price += baseMixin.methods.getTotalLineItemPrice(promo);
|
||||
});
|
||||
}
|
||||
|
||||
return price;
|
||||
modalHeaderText() {
|
||||
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||
},
|
||||
modalDisclaimerText() {
|
||||
return this.getCmsContent(this.modalWidgetName, "BodyText2");
|
||||
},
|
||||
modalName() {
|
||||
return this.modalWidgetName;
|
||||
},
|
||||
modal() {
|
||||
return this.$refs[this.modalName];
|
||||
},
|
||||
afterpaySectionsContent() {
|
||||
const sections = this.getCmsContent("AfterpayModalSectionsWidget", "Answers");
|
||||
if (!sections) return [];
|
||||
sections.forEach((section) => {
|
||||
const sectionContent = this.getCmsContent(section.Name, "BodyText");
|
||||
section["content"] = sectionContent;
|
||||
});
|
||||
return sections;
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
components: {
|
||||
textLink,
|
||||
modal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#afterpay-banner {
|
||||
.afterpay-modal-banner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $blue-150;
|
||||
|
|
@ -130,6 +163,7 @@ export default {
|
|||
|
||||
.callout {
|
||||
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
> div:first-of-type {
|
||||
|
|
@ -163,15 +197,119 @@ export default {
|
|||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-left: 1rem;
|
||||
max-width: 32rem;
|
||||
max-width: 36rem;
|
||||
}
|
||||
}
|
||||
& .alert-heading {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.afterpay-modal {
|
||||
&.modal.modal-component {
|
||||
:deep(.modal-dialog) {
|
||||
margin: 0 1rem;
|
||||
top: 0;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
width: 100%;
|
||||
max-width: 840px;
|
||||
top: 3.4rem;
|
||||
right: 0;
|
||||
left: 0;
|
||||
transform: none;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
.modal-content {
|
||||
border-radius: 0.5rem;
|
||||
margin: 1.5rem 0;
|
||||
|
||||
.modal-header.mt-6 {
|
||||
background-color: $blue-100;
|
||||
margin-top: 0;
|
||||
padding: 1.5rem 2rem 1.5rem 1.5rem;
|
||||
font-family: UrbanistSemibold;
|
||||
|
||||
.modal-title.justify-content-center {
|
||||
// OVERRIDE
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.btn-close {
|
||||
top: 1.25rem;
|
||||
right: 1.5rem;
|
||||
}
|
||||
}
|
||||
.afterpay-section {
|
||||
display: block;
|
||||
border: 1px solid $gray-200;
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
h3 {
|
||||
font-family: UrbanistSemibold;
|
||||
font-size: 1rem;
|
||||
margin: 0 0 0 0.5rem;
|
||||
}
|
||||
}
|
||||
p {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
strong {
|
||||
font-family: UrbanistSemibold;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
|
||||
.afterpay-sections {
|
||||
@include media-breakpoint-up(md) {
|
||||
display: flex;
|
||||
column-gap: 1rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
.afterpay-section {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
flex: 0 1 33%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.modal-dialog-centered {
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.modal-footer) {
|
||||
position: relative;
|
||||
background: $gray-100;
|
||||
|
||||
.modal-disclaimer {
|
||||
font-style: italic;
|
||||
font-size: 0.75rem;
|
||||
a {
|
||||
padding: 0;
|
||||
line-height: 1.625;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#afterpay-learnmore {
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -45,10 +45,8 @@
|
|||
<!-- If Cash AND vehicle does not require recal OR if Cash AND State requires showing recal, then show Afterpay banner -->
|
||||
<afterpayModalBanner
|
||||
v-if="showAfterpayBanner"
|
||||
cmsWidgetName="AfterpayModalWidget"
|
||||
:afterpayExtendedPayOptionThreshold="afterpayExtendedPayOptionThreshold"
|
||||
:isInsuranceSelected="isInsuranceSelected"
|
||||
:lineItems="lineItems" />
|
||||
cmsWidgetName="AfterpayBannerWidget"
|
||||
modalWidgetName="AfterpayModalWidget" />
|
||||
|
||||
<!-- If vehicle requires recal AND we are hiding recal pricing info, then show recal disclaimer banner. -->
|
||||
<recalDisclaimer
|
||||
|
|
|
|||
|
|
@ -958,6 +958,8 @@ function getPageNameFromRouter() {
|
|||
) {
|
||||
return router.currentRoute.value.name;
|
||||
}
|
||||
|
||||
return window.location.href.replace(/\/$/, "").split("/").pop();
|
||||
}
|
||||
|
||||
function getValueToLog(value, valueToLogType) {
|
||||
|
|
|
|||
|
|
@ -3,16 +3,33 @@ import { queryStrings } from "@/constants/query-strings";
|
|||
import store from "@/store";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import { updateOrCreateFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||
|
||||
export async function consumeReferralQuerystrings() {
|
||||
const referralNumber = getQuerystringParameter(queryStrings.REFERRAL_NUMBER);
|
||||
const parentAccount = getQuerystringParameter(queryStrings.PARENT_ACCOUNT);
|
||||
const correlationId = getQuerystringParameter(queryStrings.CORRELATION_ID);
|
||||
let referralNumber = getQuerystringParameter(queryStrings.REFERRAL_NUMBER);
|
||||
let parentAccount = getQuerystringParameter(queryStrings.PARENT_ACCOUNT);
|
||||
let correlationId = getQuerystringParameter(queryStrings.CORRELATION_ID);
|
||||
let referralDate = null;
|
||||
|
||||
// If no referral number in querystring, check if heritage funnel updated last.
|
||||
// This would indicate a customer that went to heritage but did not return through the
|
||||
// normal route. ie: left the site in search of a discount and returned without querystrings.
|
||||
const didHeritageFunnelUpdateLast = getFunnelCookie()?.DidHeritageFunnelUpdateLast;
|
||||
if (didHeritageFunnelUpdateLast && !referralNumber) {
|
||||
referralNumber = getFunnelCookie().ReferralNumber;
|
||||
parentAccount = getFunnelCookie().ReferralParentAccountNumber;
|
||||
correlationId = getFunnelCookie().ReferralCorrelationId;
|
||||
referralDate = getFunnelCookie().ReferralDate;
|
||||
}
|
||||
|
||||
if (referralNumber) {
|
||||
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||
store.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccount);
|
||||
store.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, correlationId);
|
||||
// log(" --update cookie");
|
||||
|
||||
if (referralDate) {
|
||||
store.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
||||
}
|
||||
updateOrCreateFunnelCookie();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import { queryStrings } from "@/constants/query-strings";
|
|||
import { consumeReferralQuerystrings } from "@/router/methods/helpers/consume-referral-info";
|
||||
import { loadSessionIfPresent } from "@/helpers/heritage-integration/order-helper";
|
||||
import { routeData } from "@/router/constants/routes";
|
||||
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
|
||||
|
||||
export async function handleHeritageReturn(to, from) {
|
||||
const fromHeritageFlag = to.query[queryStrings.FROM_HERITAGE];
|
||||
const didHeritageFunnelUpdateLast = getFunnelCookie()?.DidHeritageFunnelUpdateLast;
|
||||
|
||||
if (fromHeritageFlag) {
|
||||
if (fromHeritageFlag || didHeritageFunnelUpdateLast) {
|
||||
// Get new referral info from querystring in case passed there.
|
||||
await consumeReferralQuerystrings();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import router from "@/router";
|
|||
import { routeData, FUNNEL_START_PAGE } from "@/router/constants/routes";
|
||||
import store from "@/store";
|
||||
import { handleHardError } from "@/router/methods/error";
|
||||
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||
|
||||
export async function errorBeforeEnter(to, from) {
|
||||
// Check if `hasAlreadyTriggeredErrror` is available.
|
||||
|
|
@ -18,6 +19,7 @@ export async function errorBeforeEnter(to, from) {
|
|||
nextPage: to?.name,
|
||||
};
|
||||
|
||||
analyticsMixin.methods.logDigitalConsumer();
|
||||
await handleHardError(errorPayload);
|
||||
return;
|
||||
}
|
||||
|
|
@ -31,6 +33,7 @@ export async function errorBeforeEnter(to, from) {
|
|||
nextPage: to?.name,
|
||||
};
|
||||
|
||||
analyticsMixin.methods.logDigitalConsumer();
|
||||
await handleHardError(errorPayload);
|
||||
return;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -123,8 +123,8 @@ $body-color: $gray-600;
|
|||
|
||||
//Fonts
|
||||
$font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif;
|
||||
$font-family-monospace:
|
||||
UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
$font-family-monospace: UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
||||
monospace;
|
||||
// stylelint-enable value-keyword-case
|
||||
$font-family-base: $font-family-sans-serif;
|
||||
$font-family-code: $font-family-monospace;
|
||||
|
|
|
|||
Loading…
Reference in a new issue