diff --git a/public/static/assets/Urbanist-Regular.woff b/public/static/assets/Urbanist-Regular.woff
new file mode 100644
index 000000000..7efe9bc42
Binary files /dev/null and b/public/static/assets/Urbanist-Regular.woff differ
diff --git a/public/static/assets/Urbanist-SemiBold.woff b/public/static/assets/Urbanist-SemiBold.woff
new file mode 100644
index 000000000..92949770b
Binary files /dev/null and b/public/static/assets/Urbanist-SemiBold.woff differ
diff --git a/public/static/assets/logo.svg b/public/static/assets/logo.svg
new file mode 100644
index 000000000..8f739ca15
--- /dev/null
+++ b/public/static/assets/logo.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/public/static/error/index.html b/public/static/error/index.html
new file mode 100644
index 000000000..a4c141f85
--- /dev/null
+++ b/public/static/error/index.html
@@ -0,0 +1,299 @@
+
+
+
+
+ Error - Safelite
+
+
+
+
+
+
+
+
+ We encountered an error while processing your appointment. You can return to our site and try scheduling again below.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/global-methods.js b/src/global-methods.js
index 0bd0549ea..fc0b3cb41 100644
--- a/src/global-methods.js
+++ b/src/global-methods.js
@@ -121,7 +121,7 @@ export default {
endpoint: endpoint,
};
- router.bailout(errorPayload);
+ router.handleSoftError(errorPayload);
// do not log 404 errors from services because we return NotFound
// when a service doesn't return an object
diff --git a/src/global-methods.spec.js b/src/global-methods.spec.js
index c09bf8e97..c8c4d6da9 100644
--- a/src/global-methods.spec.js
+++ b/src/global-methods.spec.js
@@ -38,7 +38,7 @@ it("Global Methods - Call Http Client - Should Reject Promise", () => {
isError: true,
});
analyticsMixIn.methods.pushEventToGA = jest.fn();
- router.bailout = jest.fn();
+ router.handleSoftError = jest.fn();
//Act
globalMethods.callHttpClient(httpArgs).catch((err) => {
diff --git a/src/helpers/heritage-integration/cookie-helper.js b/src/helpers/heritage-integration/cookie-helper.js
index 6e78d1304..d2f7046b6 100644
--- a/src/helpers/heritage-integration/cookie-helper.js
+++ b/src/helpers/heritage-integration/cookie-helper.js
@@ -125,6 +125,16 @@ export function getSessionKeyValue() {
return 0;
}
+export function getskeyValue() {
+ const cookieValue = getCookieValueByName(cookieNames.SESSION_KEY);
+
+ if (cookieValue) {
+ return cookieValue;
+ }
+
+ return 0;
+}
+
export function setSessionKeyIfUnset(value) {
if (!isCookieSet(cookieNames.FUNNEL_SESSION_KEY)) {
setCookieProperties(
diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js
index fbaccb113..14ddd14c6 100644
--- a/src/mixins/analytics-mixin.js
+++ b/src/mixins/analytics-mixin.js
@@ -10,6 +10,7 @@ import {
areAllSessionCookiesSet,
setSessionIdIfUnset,
setSessionKeyIfUnset,
+ getskeyValue,
} from "@/helpers/heritage-integration/cookie-helper";
import { queryStrings } from "@/constants/query-strings";
import { experimentSettings, experimentUniverses } from "@/constants/experiments";
@@ -231,11 +232,14 @@ export default {
(part) => `${part.glassLocation}-${part.glassName}`
);
+ var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`;
+
var sessionData = {};
sessionData.currentPage = getPageNameFromRouter();
sessionData.sid = getSessionIdValue();
sessionData.deviceId = getDeviceIdValue();
sessionData.fmgSessionId = applicationUser?.savedSessionId;
+ sessionData.skey = getskeyValue().toString();
sessionData.userId = getUserIdValue();
sessionData.carId = order?.vehicle?.carId;
sessionData.vehicleYear = order?.vehicle?.year;
@@ -266,7 +270,7 @@ export default {
sessionData.providerCtu = order?.serviceLocation?.provider?.address?.zipCodeCtu
? order?.serviceLocation?.provider?.address?.zipCodeCtu
: order?.serviceLocation?.zipCodeCtu;
- sessionData.appointmentDate = `${order?.schedule?.date} ${order?.schedule?.startTime}`;
+ sessionData.appointmentDate = appointment;
sessionData.serviceType = order?.serviceLocation?.appointmentType;
sessionData.promoCodes = promoCodes;
sessionData.hasTechnicianNotes = order?.serviceLocation?.techNotes ? true : false;
@@ -280,6 +284,7 @@ export default {
sessionData.isItac = order?.policy?.isItac;
sessionData.subTotalPrice = getSubTotal(order?.lineItems);
sessionData.totalPrice = getAmountDue(order?.lineItems, true);
+ sessionData.userAgent = navigator.userAgent;
await baseMixin.methods.dispatchStoreAction(
storeActions.LOG_FMG_SESSION_DATA,
diff --git a/src/router/constants/routes.js b/src/router/constants/routes.js
index 5632fe357..98d6c503a 100644
--- a/src/router/constants/routes.js
+++ b/src/router/constants/routes.js
@@ -112,7 +112,6 @@ export const routeData = {
path: "/virtual/auto-route",
virtual: true,
},
- // TODO: RENAME FROM BAILOUT
ERROR: {
name: "error",
path: "/virtual/error",
diff --git a/src/router/index.js b/src/router/index.js
index cd842c5cb..aa07a7a0b 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,7 +1,7 @@
import { routes } from "@/router/methods/routes";
import { afterEach } from "@/router/methods/after-each";
import { beforeEach } from "@/router/methods/before-each";
-import { bailout } from "@/router/methods/error";
+import { handleSoftError, handleHardError } from "@/router/methods/error";
import {
navigateWithoutSaving,
navigateWithSaving,
@@ -32,7 +32,8 @@ router.navigateWithoutSaving = navigateWithoutSaving;
router.navigateWithPageData = navigateWithPageData;
router.navigateAndForceTopLevelNavigation = navigateAndForceTopLevelNavigation;
-router.bailout = bailout;
+router.handleSoftError = handleSoftError;
+router.handleHardError = handleHardError;
router.navigateToExternalUrl = navigateToExternalUrl;
diff --git a/src/router/methods/before-each.js b/src/router/methods/before-each.js
index e742f89ff..67a6ecf84 100644
--- a/src/router/methods/before-each.js
+++ b/src/router/methods/before-each.js
@@ -8,7 +8,7 @@ import analyticsMixin from "@/mixins/analytics-mixin";
import { routeData, FUNNEL_START_PAGE } from "@/router/constants/routes";
import { runExperiments } from "@/router/methods/helpers/run-experiments";
-import { bailout } from "@/router/methods/error";
+import { handleSoftError, handleHardError } from "@/router/methods/error";
import { checkPagePrerequisites } from "@/router/methods/page-prerequisites";
import { checkLogParam } from "@/helpers/debug-log-helper";
import { debugLog } from "@/helpers/debug-log-helper";
@@ -41,7 +41,7 @@ export async function beforeEach(to, from) {
nextPage: to?.name,
};
- bailout(errorPayload, true);
+ handleSoftError(errorPayload, true);
return false;
}
@@ -88,7 +88,7 @@ export async function beforeEach(to, from) {
nextPage: to?.name,
};
- bailout(errorPayload);
+ handleSoftError(errorPayload);
return;
}
@@ -107,7 +107,8 @@ export async function beforeEach(to, from) {
console.log(error);
- bailout(errorPayload);
+ // Eject user from Vue app in this scenario and clear localstorage.
+ await handleHardError(errorPayload);
return;
}
}
diff --git a/src/router/methods/error.js b/src/router/methods/error.js
index 06eea69b0..42807ce04 100644
--- a/src/router/methods/error.js
+++ b/src/router/methods/error.js
@@ -4,8 +4,9 @@ import router from "@/router";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
+import experimentMixin from "../../mixins/experiment-mixin";
-export async function bailout(errorPayload, forceRestart = false) {
+export async function handleSoftError(errorPayload, forceRestart = false) {
if (forceRestart) {
await store.dispatch(storeActions.RESET_STATE);
deleteFunnelCookie();
@@ -17,3 +18,23 @@ export async function bailout(errorPayload, forceRestart = false) {
name: routeData.ERROR.name,
});
}
+
+export async function handleHardError(errorPayload) {
+ try {
+ const isInStaticErrorExperiment = experimentMixin.methods.hasSettingEqualTo(
+ "UseStaticErrorPage",
+ "true"
+ );
+
+ if (isInStaticErrorExperiment) {
+ analyticsMixin?.methods?.pushPageErrorToDataLayer(errorPayload);
+ window.top.location = "/fmg/static/error";
+ return;
+ } else {
+ await handleSoftError(errorPayload, true);
+ return;
+ }
+ } finally {
+ await handleSoftError(errorPayload, true);
+ }
+}
diff --git a/src/router/methods/helpers/create-route.js b/src/router/methods/helpers/create-route.js
index 32410e3b3..828777603 100644
--- a/src/router/methods/helpers/create-route.js
+++ b/src/router/methods/helpers/create-route.js
@@ -11,7 +11,7 @@ export function createRoute(routeDatum, beforeEnter = async (to, from) => undefi
return await beforeEnter(to, from);
} catch {
return {
- name: routeData.BAILOUT.name,
+ name: routeData.ERROR.name,
replace: true,
};
}
@@ -29,7 +29,7 @@ export function createVirtualRoute(routeDatum, beforeEnter = async (to, from) =>
} catch (error) {
console.log(error);
return {
- name: routeData.BAILOUT.name,
+ name: routeData.ERROR.name,
replace: true,
};
}
diff --git a/src/router/methods/navigate.js b/src/router/methods/navigate.js
index b15c955e9..25c58e7e5 100644
--- a/src/router/methods/navigate.js
+++ b/src/router/methods/navigate.js
@@ -5,7 +5,7 @@ import { savePageData } from "@/router/methods/helpers/save-page-data";
import router from "@/router";
import store from "@/store";
-import { bailout } from "@/router/methods/error";
+import { handleSoftError } from "@/router/methods/error";
async function navigate(scenario, currentPageName, withSaving = false, forceTopLevelNav = false) {
// Check to see if calling page is same as current page.
@@ -30,7 +30,7 @@ async function navigate(scenario, currentPageName, withSaving = false, forceTopL
nextPage: nextPage?.name,
};
- bailout(errorPayload);
+ handleSoftError(errorPayload);
return;
}
diff --git a/src/router/methods/route-logic/error.js b/src/router/methods/route-logic/error.js
index 8707a65d9..573d3cddd 100644
--- a/src/router/methods/route-logic/error.js
+++ b/src/router/methods/route-logic/error.js
@@ -5,15 +5,34 @@ import baseMixin from "@/mixins/base-mixin";
import router from "@/router";
import { routeData, FUNNEL_START_PAGE } from "@/router/constants/routes";
import store from "@/store";
+import { handleHardError } from "@/router/methods/error";
export async function errorBeforeEnter(to, from) {
- // If we've already encountered one error, clear session to avoid more.
- // Otherwise mark that we encoutnered an error here.
- if (store.getters.applicationUser.hasAlreadyTriggeredError) {
- return {
- name: routeData.RESTART.name,
- replace: true,
+ // Check if `hasAlreadyTriggeredErrror` is available.
+ // If we cannot check it (store, getters, or applicationUser is null-ish),
+ // act as if the flag is set, as we are in unstable state.
+ if (!store?.getters?.applicationUser) {
+ const errorPayload = {
+ cause: "Cannot access store during error process.",
+ currentPage: from?.name,
+ nextPage: to?.name,
};
+
+ handleHardError(errorPayload);
+ return;
+ }
+
+ // If we've already encountered one error, clear session to avoid more.
+ // Otherwise mark that we encountered an error here.
+ if (store.getters.applicationUser.hasAlreadyTriggeredError) {
+ const errorPayload = {
+ cause: "Successive errors triggered.",
+ currentPage: from?.name,
+ nextPage: to?.name,
+ };
+
+ handleHardError(errorPayload);
+ return;
} else {
await store.dispatch(storeActions.UPDATE_HAS_TRIGGERED_ERROR, true);
}
diff --git a/src/store/index.js b/src/store/index.js
index eb2ddcd4d..ce71d6f28 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1602,6 +1602,7 @@ export const actions = {
sid,
deviceId,
fmgSessionId,
+ skey,
userId,
carId,
vehicleYear,
@@ -1640,6 +1641,7 @@ export const actions = {
isItac,
subTotalPrice,
totalPrice,
+ userAgent,
}
) {
var payload = {
@@ -1647,6 +1649,7 @@ export const actions = {
sid: sid,
deviceId: deviceId,
fmgSessionId: fmgSessionId,
+ skey: skey,
userId: userId,
carId: carId,
vehicleYear: vehicleYear,
@@ -1685,6 +1688,7 @@ export const actions = {
isItac: isItac,
subTotalPrice: subTotalPrice,
totalPrice: totalPrice,
+ userAgent,
};
return globalMethods.callHttpClient({