Merge pull request #1218 from Safelite/feature/jzimmerman/INSR-9648

INSR-9648: Adding static page and relevant logic.
This commit is contained in:
Jeremy-Z 2026-05-08 13:36:59 -04:00 committed by GitHub
commit b396051183
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 483 additions and 5 deletions

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 439.8 73.6" viewBox="0 0 439.8 73.6">
<path d="M47.2 22.8c-.8 0-1.4-.6-1.4-1.4v-1.5c0-4.1-2.6-7.5-8.1-7.5-6.1 0-8.8 4.3-8.8 7.4 0 13.7 46.4 7 46.4 32.3 0 14.7-13.6 21.3-38.6 21.3C13.1 73.4 0 68.2 0 53.9v-1c0-.8.6-1.4 1.4-1.4H26c.8 0 1.4.6 1.4 1.4v1.5c0 5.9 3.8 8 9.3 8 5.8 0 9.2-4.2 9.2-8 0-13.7-44.5-6.9-44.5-31.4C1.4 9 13.1 1.4 37.1 1.4c23.8 0 34.5 6 35.2 19.8 0 .8-.6 1.5-1.4 1.5l-23.7.1zm106.4-7.1c0-9.8 6-13.4 23.8-13.4 3.3 0 7.3.1 11 .4.7.1 1.3.7 1.3 1.4v6c0 .8-.7 1.5-1.6 1.4-.8-.1-1.6-.1-2.7-.1-3.1 0-6.1 1-6.1 3.4v6.9c0 .8.6 1.4 1.4 1.4h7.6c.8 0 1.4.6 1.4 1.4v8.1c0 .8-.6 1.4-1.4 1.4h-7.6c-.8 0-1.4.6-1.4 1.4v35.2c0 .8-.6 1.4-1.4 1.4h-22.8c-.8 0-1.4-.6-1.4-1.4l-.1-54.9zM218 50.6c-.8 0-1.4.6-1.4 1.4v6.3c0 5.3 3.4 6.6 7 6.6 4.3 0 6.9-2.7 7.2-8.5 0-.7.7-1.3 1.4-1.3h20.2c.9 0 1.5.7 1.4 1.6-1 10.9-9.9 16.9-30.2 16.9-24.8 0-32.8-7.1-32.8-25.8 0-18.8 9.7-25.9 32.8-25.9 20.5 0 31 5.8 31 23.6v3.6c0 .8-.6 1.4-1.4 1.4l-35.2.1zm12.6-12.4c0-5.2-1.7-7.5-7.1-7.5-5.3 0-7 2.4-7 7.5v2.1c0 .8.6 1.4 1.4 1.4h11.3c.8 0 1.4-.6 1.4-1.4v-2.1zM265.2 0H288c.8 0 1.4.6 1.4 1.4v69.5c0 .8-.6 1.4-1.4 1.4h-22.8c-.8 0-1.4-.6-1.4-1.4V1.4c-.1-.8.6-1.4 1.4-1.4zM302 23.2h22.8c.8 0 1.4.6 1.4 1.4v46.3c0 .8-.6 1.4-1.4 1.4H302c-.8 0-1.4-.6-1.4-1.4V24.6c-.1-.8.6-1.4 1.4-1.4zM302 0h22.8c.8 0 1.4.6 1.4 1.4v12.1c0 .8-.6 1.4-1.4 1.4L302 15c-.8 0-1.4-.6-1.4-1.4V1.5c-.1-.9.6-1.5 1.4-1.5zm101.2 50.6c-.8 0-1.4.6-1.4 1.4v6.3c0 5.3 3.4 6.6 7 6.6 4.2 0 6.9-2.7 7.2-8.5 0-.7.7-1.3 1.4-1.3h20.2c.9 0 1.5.7 1.4 1.6-1 10.9-9.9 16.9-30.1 16.9-24.8 0-32.8-7.1-32.8-25.8 0-18.8 9.7-25.9 32.8-25.9 20.5 0 31 5.8 31 23.6v3.6c0 .8-.6 1.4-1.4 1.4l-35.3.1zm12.7-12.4c0-5.2-1.7-7.5-7.1-7.5-5.3 0-7 2.4-7 7.5v2.1c0 .8.6 1.4 1.4 1.4h11.3c.8 0 1.4-.6 1.4-1.4v-2.1zm-271.5-.5C144.3 25 131.8 22 113.9 22c-11.2 0-28.2 2.3-29.5 14.9-.1.8.6 1.6 1.4 1.6h19.4c.7 0 1.3-.6 1.4-1.3.5-4.3 2.8-6.5 7.2-6.5s6.5 1.5 6.5 4.5v4.4c0 .7-.5 1.3-1.2 1.4-1.2.2-2.9.4-6.3.9l-10.1 1.5c-16.4 2.4-21.4 6.7-21.4 15.8 0 8.4 6 14.4 21.4 14.4 7.3 0 13.9-2.2 17-6.2.2-.2.6-.2.6.1.3 1 .9 2.9 1.2 3.9.2.6.7.9 1.3.9h20c.8 0 1.4-.6 1.4-1.4l.2-33.2zm-32 26.3c-3.9 0-6.3-3.4-6.3-6.7 0-3.3 1.5-5.3 5.3-6.3 2.4-.7 5.5-1.1 7.4-1.4.9-.1 1.6.5 1.6 1.4v4.9c0 5.3-2.2 8.1-8 8.1zm251.8-29.7h7.3c.8 0 1.4-.6 1.4-1.4v-8.1c0-.8-.6-1.4-1.4-1.4h-7.3c-.8 0-1.4-.6-1.4-1.4l-.1-20.4v-.4c0-.6-.5-1.1-1.1-1.1-.2 0-.3 0-.4.1h-.1l-22.6 13.4c-1 .6-1.4.7-1.4 1.4v45.4c0 10.9 6.1 12.5 21.8 12.5 3.9 0 8.3-.2 12.7-.4.7-.1 1.3-.7 1.3-1.4v-6.8c0-.8-.6-1.4-1.4-1.4h-2.6c-4.1 0-6.1-1-6.1-3.8V35.8c0-.9.6-1.5 1.4-1.5z" style="fill:#dc1f26"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,435 @@
<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: Urbanist;
src: url("/static/assets/Urbanist-Regular.woff") format("woff");
}
@font-face {
font-family: UrbanistSemiBold;
src: url("/static/assets/Urbanist-SemiBold.woff") format("woff");
}
body {
font-family: 'Urbanist', Roboto, Arial, Helvetica, sans-serif;
}
.container {
max-width: 960px;
margin: auto;
padding: 1rem;
}
.header, .copy, .cta {
margin-bottom: 3rem;
}
.header .header-image {
width: 165px;
}
.subheader {
font-weight: normal;
font-size: 1.5rem;
}
.cta-button {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 2rem;
padding-right: 2rem;
border-radius: 50rem;
background-color: #db0020;
color: #ffffff;
font-family: inherit;
font-size: 1rem;
border: 0px;
}
.diagnostic-info {
color: #db0020;
font-family: 'UrbanistSemiBold', Roboto, Arial, Helvetica, sans-serif;
}
</style>
<title>Error - Safelite</title>
<link rel="icon" href="/favicon.ico">
</head>
<body>
<div class="container">
<div class="header">
<img class="header-image" src="/static/assets/logo.svg" />
</div>
<div class="copy">
<h2 class="subheader">
We're not able to schedule at this time. We apologize for the inconvenience
</h2>
<p>
We encountered an error while processing your appointment. You can return to our site and try scheduling again at a later time.
</p>
</div>
<!--
<div class="cta">
<a href="/">
<button class="cta-button">
Continue to Safelite.com
</button>
</a>
</div>
-->
<div class="diagnostic-info" id="diagnostic-container">
<hr />
<h2 class="subheader">
Bailout Information:
</h2>
<div id="bailout-info-container">
</div>
</div>
</div>
<script>
// Helpers
function getCookieValueByName(name) {
const value = "; " + document.cookie;
const parts = value.split("; " + name + "=");
if (parts.length === 2) {
return parts.pop().split(";").shift();
}
return "";
}
function getCurrentEnvironmentData() {
const environmentData = [
{
name: 'Localhost',
hostName: 'localhost',
apiHostname: 'digitalapi.dev.safelite.io',
debug: true,
},
{
name: 'Dev',
hostName: 'selfservice.dev.glassclaim.com',
apiHostname: 'digitalapi.dev.safelite.io',
debug: true,
},
{
name: 'SysTest',
hostName: 'selfservice.test.glassclaim.com',
apiHostname: 'digitalapi.test.safelite.io',
debug: true,
},
{
name: 'QA',
hostName: 'selfservice.qa.glassclaim.com',
apiHostname: 'digitalapi.qa.safelite.io',
debug: false,
},
{
name: 'Prod',
hostName: 'selfservice.glassclaim.com',
apiHostname: 'digitalapi.safelite.io',
debug: false,
},
];
const hostName = window.location.hostname;
const match = environmentData.find(
data => (data.hostName === hostName)
);
return match;
}
function getExperimentSettingsFromVuex(storeData) {
const experiments = storeData?.applicationUser?.experiments;
if(experiments) {
return experiments
.filter((e) => !!e.isActive)
.map((e) => e.settings)
.reduce((prev, next) => Object.assign(prev, next), {}) ?? {};
}
return {};
}
function extractDataFromStore(mainStore) {
let bailoutInfo = [];
let headerInfo = [];
try {
// =============== Collect diagnostic data:
bailoutInfo = [];
// App & Site Name
bailoutInfo.push({ name: 'App Name', value: 'ISS' });
bailoutInfo.push({ name: 'Site Name', value: 'ISS 2.0' });
// 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: mainStore.order?.referralSequenceNumber });
bailoutInfo.push({ name: 'Referral Number', value: mainStore.order?.referralNumber });
bailoutInfo.push({ name: 'Referral Date', value: mainStore.order?.referralDate });
bailoutInfo.push({ name: 'Referral Provider Number', value: mainStore.order?.serviceLocation?.provider?.providerNumber });
bailoutInfo.push({ name: 'Referral CTU', value: mainStore.order?.serviceLocation?.provider?.address?.zipCodeCtu });
// - Referral Insured Zip
// - Referral Insured Service Zip
bailoutInfo.push({ name: 'Referral CarID', value: mainStore.order?.vehicle?.carId });
const vehicle = mainStore.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: mainStore.order?.workOrderNumber });
bailoutInfo.push({ name: 'Work Order ID', value: mainStore.order?.workOrderId });
bailoutInfo.push({ name: 'Parent Account Number', value: mainStore.order?.parentAccountNumber });
const environmentData = getCurrentEnvironmentData();
const userIdCookieName = `ISSUserId-${environmentData?.name}`;
bailoutInfo.push({ name: 'User Id', value: getCookieValueByName(userIdCookieName) });
bailoutInfo.push({ name: 'Bailout URL', value: mainStore.applicationUser.pageData['bailout-page']?.url });
bailoutInfo.push({ name: 'Bailout Code', value: mainStore.applicationUser.pageData['bailout-page']?.bailoutCode });
bailoutInfo.push({ name: 'Bailout Message', value: mainStore.applicationUser.pageData['bailout-page']?.errorMessage });
// - Parent Account Name
// - Client GUID
// =============== Collect header data:
headerInfo = [];
headerInfo.push({ name: 'X-Experiment-Data', value: JSON.stringify(getExperimentSettingsFromVuex(mainStore)) });
headerInfo.push({ name: 'X-Application-Name', value: 'ISS' });
const sessionKeyCookieName = `ISSSessionKey-${environmentData?.name}`;
headerInfo.push({ name: 'X-Session-Sequence-Number', value: getCookieValueByName(sessionKeyCookieName) });
headerInfo.push({ name: 'X-Referral-Sequence-Number', value: mainStore.order?.referralSequenceNumber });
headerInfo.push({ name: 'X-Enterprise-Order-Number', value: mainStore.order?.eon });
headerInfo.push({ name: 'log-enabled', value: mainStore.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() {
/*
const environmentData = getCurrentEnvironmentData();
const cookieNames = [
`ISSUserId-${environmentData?.name}`,
`ISSSessionKey-${environmentData?.name}`,
`ISSSessionInfo-${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 existingStoreDataJSON = window.sessionStorage.getItem('main');
const mainStore = existingStoreDataJSON ? JSON.parse(existingStoreDataJSON) : null;
const bailoutData = mainStore.applicationUser.pageData['bailout-page'];
console.log(`================ VUEX DATA`);
console.log(mainStore);
/*
console.log(`================ PRIOR BAILOUT DATA`);
console.log(existingBailoutInfo);
console.log(`================ PRIOR HEADER DATA`);
console.log(existingHeaderInfo);
*/
let bailoutInfo = null;
let headerInfo = null;
if(mainStore) {
const results = extractDataFromStore(mainStore);
bailoutInfo = results.bailoutInfo;
headerInfo = results.headerInfo;
//clearApplicationData();
if(bailoutInfo) {
window.sessionStorage.setItem('bailoutInfo', JSON.stringify(bailoutInfo));
}
if(headerInfo) {
window.sessionStorage.setItem('headerInfo', JSON.stringify(headerInfo));
}
} else {
bailoutInfo = existingBailoutInfo ?? [];
headerInfo = existingHeaderInfo ?? [];
}
const environmentInfo = getCurrentEnvironmentData();
const shouldShowDebugInfo = environmentInfo?.debug;
if(bailoutInfo && shouldShowDebugInfo) {
try {
// =============== Display diagnostic data:
// Create display nodes
const elements = bailoutInfo.map(
dataPoint => {
const element = document.createElement('li');
element.textContent = `${dataPoint.name}: ${dataPoint.value}`;
return element;
}
);
const fragment = new DocumentFragment();
elements.forEach(
(element) => {
fragment.append(element);
}
);
// Attach nodes to DOM and render
const attachNode = document.getElementById('bailout-info-container');
if(attachNode) {
const ul = attachNode.appendChild(document.createElement('ul'));
ul.append(fragment);
}
} catch(e) {
console.error(`=== ERROR DISPLAYING INFO`);
console.error(e);
const diagnosticContainer = document.getElementById('diagnostic-container');
diagnosticContainer.remove();
}
} else {
const diagnosticContainer = document.getElementById('diagnostic-container');
diagnosticContainer.remove();
}
// TODO: Re-enable logging after some further testing.
/*
const apiHostname = environmentInfo?.apiHostname;
if(apiHostname) {
try {
const endpointUrl = `https://${apiHostname}/analytics/api/v1/logging/log-error`;
const infoString = bailoutInfo?.map(
(entry) => `${entry.name}: ${entry.value}`
)?.reduce(
(prev, next) => `${prev}\n${next}`
);
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: headers,
body: JSON.stringify({
entry: entryString,
}),
});
const response = await fetch(request);
// Don't need data from response, but could get it here with:
// const responseData = await response.json();
} catch(e) {
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: "ISS",
action: "",
applicationName: "ISS",
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>
</body>
</html>

View file

@ -7,6 +7,8 @@ import headerKeys from '@/constants/header-keys';
import axiosResponseInterceptorMessages from '@/constants/axios-response-interceptor-messages.js';
import { getSessionKeyValue } from '@/helpers/cookie-helper';
import endpoints from "@/constants/endpoints";
import queryStrings from '@/constants/query-strings';
import issPageValues from '@/router/router-constants/issPage-values';
// Add a response interceptor for global axios error handing.
axios.interceptors.response.use(
@ -48,9 +50,19 @@ axios.interceptors.response.use(
);
export default {
getPageNameByQueryString() {
const params = new URLSearchParams(location.search);
if (params.has(queryStrings.ISS_PAGE)) {
return params.get(queryStrings.ISS_PAGE);
}
return '';
},
callHttpClient({ method, endpoint, payload, logApiCall = true, bailoutOnError = true }) {
return new Promise((resolve, reject) => {
const store = useMainStore();
const currentPageName = this.getPageNameByQueryString().toLowerCase();
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
const payloadAndAnalyticsData = { ...payload, AppName: 'ISS' };
const sessionKey = getSessionKeyValue();
@ -103,11 +115,20 @@ export default {
}
if (error.response.status !== 404) {
console.error("http call error - page: " + currentPageName + " - bailoutonError: " + bailoutOnError);
const errorData = { method, url, payload, error };
global.$logger.logError(`${method}: ${endpoint}`, errorData);
if (bailoutOnError && global.bailoutOnAxiosError !== undefined)
{
global.bailoutOnAxiosError(errorData);
if (bailoutOnError && global.bailoutOnAxiosError !== undefined ) {
// Check for bailout or entry page here ... if we are on these pages we cannot bailout.
if ( currentPageName != issPageValues.ENTRY_PAGE && currentPageName != issPageValues.BAILOUT_PAGE) {
global.bailoutOnAxiosError(errorData);
}
else {
console.error("Fatal - Unable to bailout on the entry page or bailout page.");
// Redirect to a generic static error page on fatal error on entry and bailout page.
window.top.location = '/static/error';
}
}
}
return reject(error.response);

View file

@ -48,6 +48,15 @@ export class Logger {
const store = useMainStore();
const sessionKey = getSessionKeyValue();
// TODO: Work in progress.
/*
if ( store.applicationUser.logErrorCalls> 10 ) {
console.error("Exceeded maximum log error calls for this session. Further logging will be suppressed to prevent potential infinite loops.");
return;
}
store.applicationUser.logErrorCalls += 1;
*/
return new Promise((resolve, reject) => {
// If running locally or in the Dev environment show the log entries in the console.
if (

View file

@ -38,7 +38,7 @@ const routes = [
await analyticsMixin.methods.validateSession();
// Do not run these for the main entry page - as it is not part of the user flow.
if (issPageToUse !== issPageValues.ENTRY_PAGE) {
if (issPageToUse !== issPageValues.ENTRY_PAGE && issPageToUse !== issPageValues.BAILOUT_PAGE) {
try {
await runExperiments(issPageToUse);
} catch (error) {
@ -82,7 +82,14 @@ const routes = [
return next({name: issPageToUse, query: to.query, params: to.params});
}
const routeData = await GetRouteInfoFromPageName(issPageToUse);
var routeData = [];
try {
routeData = await GetRouteInfoFromPageName(issPageToUse);
}
catch ( error ) {
window.top.location = '/static/error';
}
if (routeData[0].name.toLowerCase() === 'error') {
throw new Error('Page not found!');
@ -284,6 +291,8 @@ function navigate(
// Match our maps up and navigate if we have a destination.
let matchingScenarioMap = getNavigationMap(scenario, currentRoute);
if (!matchingScenarioMap && scenario === navigationScenarios.BAILOUT) {
// TODO: Send to static error page here?
//console.error("No matching map for bailout page - forced bailout page map");
matchingScenarioMap = { destinationIssPageValue: issPageValues.BAILOUT_PAGE }
}

View file

@ -245,6 +245,7 @@ export const getDefaultState = () => ({
duplicateOrders: [],
hasSentSaveQuoteEmail: null,
coverageLookupAttempts: 0,
//logErrorCalls: 0,
firstHit: true
},
issConfig: {