Merge remote-tracking branch 'origin/develop' into CASH-631

This commit is contained in:
maguire-arman 2025-05-19 14:30:23 -04:00
commit f249890f9b
124 changed files with 2473 additions and 2608 deletions

View file

@ -36,6 +36,9 @@ export default {
loadingModal,
funnelFooter,
},
mounted() {
showFmgLoadingModal(true);
},
};
// Add a global Javascript exception handler for logging exceptions, this handler will log when there is an uncaught exception

View file

@ -23,7 +23,6 @@ const GaActions = {
SUBMITTED: "Submitted",
DISPLAYED: "Displayed",
CASH_QUOTE_DISPLAYED: "Cash quote displayed",
SERVICE_PACKAGE_PRICE: "Service package price",
MOBILE_AVAILABLE: "mobile_available",
SHOPS_FIRST_DISPLAYED: "shops_first_displayed",
MORE_LOCATIONS_CLICKED: "more_locations_clicked",

View file

@ -6,6 +6,7 @@ const applicationConfig = {
COOKIE_PATH: "/",
CURRENT_ENVIRONMENT: process.env.VUE_APP_CURRENT_ENVIRONMENT, // "Localhost", "Dev", "QA", and "Prod"
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL,
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP,
APPLICATION_NAME: "FixMyGlass",
ANALYTICS_APPLICATION_NAME: "FixMyGlassNextGen",
APPLICATION_ABBREVIATION: "fmg",
@ -15,21 +16,12 @@ const applicationConfig = {
CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER: "87291",
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT,
PIA_RESPONSE_URL:
location.protocol +
"//" +
location.host +
"/fmg/?fmgPage=payment-pia-return&src=concept-funnel",
location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel",
PIA_CANCEL_URL:
location.protocol +
"//" +
location.host +
"/fmg/?fmgPage=payment-method&src=concept-funnel",
location.protocol + "//" + location.host + "/fmg/payment-method?src=concept-funnel",
PIA_ERROR_URL:
location.protocol +
"//" +
location.host +
"/fmg/?fmgPage=payment-pia-return&src=concept-funnel",
CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/?fmgPage=confirmation",
location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel",
CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/confirmation",
GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE",
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",
OUTLOOK_CALENDAR:
@ -38,7 +30,7 @@ const applicationConfig = {
AFFILIATE_COOKIE_CONTAINING_NAME: "_Track",
COOKIE_NAME_LENGTH_MAX_LIMIT: 44,
SESSION_TIMEOUT_MINUTES: process.env.VUE_APP_SESSION_TIMEOUT_MINUTES,
RETURN_USER_PAGE: "/fmg/?fmgPage=return-user",
RETURN_USER_PAGE: "/fmg/return-user",
};
export { applicationConfig };

View file

@ -117,6 +117,7 @@ const storeActions = {
UPDATE_EXTERNAL_PARAMETER_MMS: "updateExternalParameterMMS",
SAVE_LOGGING_OPTION: "saveLoggingOption",
UPDATE_HAS_TRIGGERED_ERROR: "updateHasTriggeredError",
};
export { storeActions };

View file

@ -69,6 +69,7 @@ const storeMutations = {
UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount",
UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn",
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",
// EVENT BUS MUTATIONS
ADD_EVENT_TO_BUS: "addEventToBus",
@ -93,6 +94,7 @@ const storeMutations = {
UPDATE_SAVE_SESSION_PROMISE: "updateSaveSessionPromise",
UPDATE_LAST_PAGE_VISITED: "updateLastPageVisited",
UPDATE_LOGGING_OPTION: "updateLoggingOption",
UPDATE_HAS_TRIGGERED_ERROR: "updateHasTriggeredError",
// EXPERIMENT MUTATIONS
UPDATE_EXPERIMENTS: "updateExperiments",

View file

@ -35,4 +35,9 @@ export const inputButtonProps = {
default: false,
},
suppressError: Boolean,
labelBold: {
type: Boolean,
required: false,
default: false,
},
};

View file

@ -1143,7 +1143,7 @@ describe("buttonQuestion.vue", () => {
});
function setupMocks(mountOptionsMockData = {}) {
const defaultMountOptions = { route: { query: { fmgPage: "page-name" } } };
const defaultMountOptions = { route: { name: "page-name" } };
const baseMountOptions = getMountOptions(
Object.assign(defaultMountOptions, mountOptionsMockData)
);

View file

@ -9,7 +9,9 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
v-if="questionText && answers && answers.length > 0"
class="question-text d-flex"
:class="{ 'small-question-text': isSmallQuestionText }">
<span class="fw-bold w-100">{{ questionText }}</span>
<span class="fw-bold w-100" :class="[labelBold ? 'span-bold' : '']">
{{ questionText }}
</span>
</div>
<div class="w-100 d-flex justify-content-center">
@ -57,6 +59,7 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
:setLastValuePushedToGa="setLastValuePushedToGa"
:isInsuranceSelected="isInsuranceSelected"
:suppressError="suppressError"
:labelBold="labelBold"
@buttonEvent="handleButtonEvent"
v-model="selectedValues" />
<!-- For nested questions -->
@ -141,6 +144,11 @@ export default {
default: false,
},
isInsuranceSelected: Boolean,
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const propsClone = Object.assign({}, props);
@ -360,6 +368,9 @@ export default {
& > span {
text-align: center;
}
.span-bold {
font-weight: 600;
}
}
.small-question-text {
&.question-text {

View file

@ -688,17 +688,18 @@ export default {
if (!this.months) return;
this.isLoading = true;
let monthToShow;
let monthStartDateNum = 0;
let monthStartDate;
if (this.hideSomeDaysForInitialView) {
monthToShow = this.months.find(
({ isMonthThatHidesSomeDaysForInitialView }) =>
isMonthThatHidesSomeDaysForInitialView
);
// find the first day-hidden to become the next api call start date
monthStartDateNum =
monthToShow.dates.find(({ dayClasses }) => dayClasses.includes("day-hidden"))
.dateNum - 1;
monthStartDate = monthToShow.dates.find(({ dayClasses }) =>
dayClasses.includes("day-hidden")
);
} else {
if (this.calendarViewDirection === "future") {
monthToShow = this.months.find((month) =>
@ -713,9 +714,13 @@ export default {
}
}
if (monthToShow) {
const monthStartDateString = monthStartDate
? monthStartDate.inputValue
: monthToShow.dates[0].inputValue;
// make new API call with this month's start and end dates
const moreSelectableDatesData = await this.updateSelectableDates(
monthToShow.dates[monthStartDateNum].inputValue,
monthStartDateString,
monthToShow.dates[monthToShow.dates.length - 1].inputValue
);
this.isLoading = false;
@ -1042,6 +1047,9 @@ export default {
min-width: 3rem;
width: 3rem;
}
&:hover {
border: 2px solid $blue;
}
}
}
&.unavailable-day {
@ -1083,7 +1091,6 @@ export default {
}
input[type="radio"]:checked + label:after {
background-color: $black;
top: 2rem;
}
}
}

View file

@ -6,6 +6,7 @@
:for="dropdownId"
:aria-label="questionText"
class="form-label"
:class="[labelBold ? 'label-bold' : '']"
v-html="questionText"></label>
<select
v-model="selectedOption"
@ -61,6 +62,11 @@ export default {
required: false,
default: false,
},
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const uuid = uuidv4();
@ -133,6 +139,10 @@ export default {
color: $black;
font-weight: 500;
}
.label-bold {
color: $black;
font-weight: 600;
}
.form-label {
margin-bottom: 0.25rem;
}

View file

@ -46,9 +46,7 @@
</template>
<script>
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import analyticsMixin from "@/mixins/analytics-mixin";
import router from "@/router/index.js";
import modalButtonMain from "@/digital-components/modal/ux-components/modal-button-main/modal-button-main";
import { Modal } from "bootstrap";
import { useForm } from "vee-validate";
@ -91,11 +89,7 @@ export default {
const validationResult = await this.validate();
if (validationResult.valid) {
if (analyticsMixin.methods.sessionExpired()) {
analyticsMixin.methods.initSession();
router.push({
path: "/",
query: { fmgPage: fmgPageValues.RETURN_USER },
});
this.$router.sendToReturnUser();
} else {
this.$emit("footer-button-event");
}
@ -119,12 +113,14 @@ export default {
},
onModalOpened() {
this.onModalOpenedCallback?.();
this.$emit("isModalOpened", true);
},
onModalClosed() {
this.resetButtonStyle();
this.onModalClosedCallback?.();
if (this.suppressPageScroll)
document.querySelector("body").classList.remove("prevent-modal-scroll");
this.$emit("isModalOpened", false);
},
openModal() {
if (this.suppressPageScroll)

View file

@ -1,5 +1,7 @@
<template>
<span class="salesforce-webchat"></span>
<span class="salesforce-webchat">
<button v-show="hideSalesforceWebchatLaunchButton" class="salesforce-chat-button"></button>
</span>
</template>
<script>
@ -49,12 +51,121 @@ export default {
};
</script>
<style type="text/css">
.embeddedServiceHelpButton .helpButton .uiButton {
background-color: #167cac;
font-family: "Arial", sans-serif;
<style lang="scss">
.salesforce-webchat {
.salesforce-chat-button {
width: 1.5rem;
height: 1.5rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' id='Layer_2' x='0' y='0' style='enable-background:new 0 0 24 24' version='1.1' viewBox='0 0 24 24'%3E%3Cstyle%3E .st0%7Bfill:%232e6fb6%7D %3C/style%3E%3Ccircle cx='12' cy='12' r='12' class='st0'/%3E%3Cpath d='M18.3 12c0 3.5-2.8 6.3-6.3 6.3-1.2 0-2.4-.4-3.4-1l-2.3.7s-.5.1-.3-.4.5-1.7.7-2.2c-.6-1-1-2.1-1-3.3 0-3.5 2.8-6.3 6.3-6.3 3.5-.1 6.3 2.7 6.3 6.2z' style='fill:%23fff'/%3E%3Ccircle cx='10.2' cy='12' r='.6' class='st0'/%3E%3Ccircle cx='12' cy='12' r='.6' class='st0'/%3E%3Ccircle cx='13.8' cy='12' r='.6' class='st0'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: 1.5rem 1.5rem;
background-position: center;
border: none;
background-color: transparent;
}
}
.embeddedServiceHelpButton .helpButton .uiButton:focus {
outline: 1px solid #167cac;
.dockableContainer {
&.showDockableContainer {
font-family: AvertaRegular;
color: $gray-600;
.sidebarBody {
button {
background: $blue;
color: $white;
&:hover {
background: $blue-700 !important;
}
}
.form-element__help {
color: $red;
}
.embeddedServiceSidebarFormField {
.uiInput {
.uiLabel-left {
color: $gray-600;
}
}
}
.embeddedServiceSidebarButton {
&.uiButton--inverse {
.label {
color: $white;
text-decoration: underline;
}
&:not(:disabled):focus {
background-color: $blue;
}
}
}
.embeddedServiceLiveAgentStateChatAvatar {
&.isLightningOutContext {
.agentIconColor0 {
background-color: $blue;
}
}
}
.embeddedServiceLiveAgentStateChatInputFooter {
.footerMenuWrapper {
.footer-menu-items {
.slds-dropdown__item {
> a {
padding-left: 0;
max-width: 100%;
display: flex;
justify-content: center;
}
}
}
}
}
.embeddedServiceLiveAgentStateChatInputFooter {
.footerMenuWrapper {
.footer-menu-items {
a {
margin: 0 auto;
}
}
}
}
}
.chatHeaderBranding {
background-color: $blue;
}
span {
&.required {
color: $red;
}
}
}
}
.embeddedServiceHelpButton {
.helpButton {
.uiButton {
background-color: $blue !important;
}
}
.embeddedServiceIcon {
display: inline-block !important;
}
}
.modalContainer {
&.sidebarMinimized {
&.layout-docted {
&.embeddedServiceSidebar {
.embeddedServiceSidebarMinimizedDefaultUI {
.helpButton,
.sidebarHeader {
&.minimizedContainer {
.embeddedServiceSidebarMinimizedDefaultUI {
background-color: $blue;
border: 1px solid $blue;
}
}
}
}
}
}
}
}
</style>

View file

@ -58,7 +58,7 @@ export default {
getRouterLinkDisplayTextFromCopy,
getExternalLink,
navigateWithScenario(scenarioName) {
this.$router.navigateWithoutSaving(scenarioName, this.$route);
this.$router.navigateWithoutSaving(scenarioName, this.pageName);
},
},
computed: {

View file

@ -13,6 +13,7 @@
:class="[
questionAlignment === 'center' ? 'text-center w-100 mb-5' : '',
hideInput ? 'hide-input' : '',
labelBold ? 'label-bold' : '',
]"
v-html="questionText"></label>
<div
@ -132,6 +133,11 @@ export default {
isRequired: false,
},
autocomplete: String,
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const uuid = uuidv4();
@ -251,6 +257,9 @@ export default {
color: $black;
font-weight: 500;
}
.label-bold {
font-weight: 600;
}
.input-wrapper {
position: relative;
&.has-search-icon {

View file

@ -117,6 +117,9 @@ export default {
);
return displayPrice != this.additionalButtonData.strikeThroughPrice;
},
totalAfterpayPrice() {
return parseFloat(this.buttonAuxillaryCopy.replace("$", ""));
},
},
methods: {
getRouterLinkDisplayTextFromCopy,
@ -134,11 +137,12 @@ export default {
.filter((lineItem) => lineItem);
},
afterpayPrice() {
const decimalPrice = parseFloat(this.buttonAuxillaryCopy.replace("$", ""));
const decimalPrice = this.totalAfterpayPrice;
return "$" + (decimalPrice / 4).toFixed(2);
},
truncatedSinglePayment() {
return "$" + Math.trunc(this.buttonAuxillaryCopy.replace("$", ""));
const decimalPrice = this.totalAfterpayPrice;
return "$" + decimalPrice.toFixed(2);
},
hasPackageDiscount() {
return (

View file

@ -14,7 +14,8 @@
hasIcon
validationRules="street-address-required"
autocomplete="street-address"
@keydown.enter.prevent />
@keydown.enter.prevent
:labelBold="labelBold" />
</div>
</div>
<transition name="fade" mode="out-in">
@ -27,7 +28,8 @@
customInputId="apartmentNumberOrBusinessName"
cmsWidgetName="ApartmentNumberOrBusinessNameQuestionWidget"
v-model="addressModel.apartmentNumberOrBusinessName"
ref="apartmentNumberOrBusinessName" />
ref="apartmentNumberOrBusinessName"
:labelBold="labelBold" />
</div>
</div>
</transition>
@ -40,7 +42,8 @@
v-model="addressModel.city"
ref="city"
validationRules="city-required"
autocomplete="address-level2" />
autocomplete="address-level2"
:labelBold="labelBold" />
</div>
</div>
</transition>
@ -54,7 +57,8 @@
ref="state"
:options="stateOptions"
validationRules="state-required"
autocomplete="address-level1" />
autocomplete="address-level1"
:labelBold="labelBold" />
</div>
<div class="col">
<textboxQuestion
@ -64,7 +68,8 @@
ref="zipCode"
mask="#####"
validationRules="zip-code-required|zip-code-format"
autocomplete="postal-code" />
autocomplete="postal-code"
:labelBold="labelBold" />
</div>
</div>
</transition>
@ -124,6 +129,11 @@ export default {
type: Boolean,
default: false,
},
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {

View file

@ -1,13 +1,21 @@
<template>
<div
class="funnel-header d-flex justify-content-center align-items-center flex-column"
v-if="imageSrc">
<!-- HIDDEN FOR 05.08 <salesforceWebchat hideSalesforceWebchatLaunchButton />-->
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
<!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. -->
<menuModal />
<!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. -->
<progressBar />
<div class="funnel-header" v-if="imageSrc">
<div class="d-flex justify-content-between align-items-center">
<div class="button-container">
<!-- HIDDEN FOR 05.08 <salesforceWebchat hideSalesforceWebchatLaunchButton /> -->
</div>
<div class="site-logo">
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
</div>
<div class="button-container">
<!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. -->
<menuModal />
<!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. -->
</div>
</div>
<div class="d-flex w-100">
<progressBar />
</div>
</div>
<template v-for="globalAlert in globalAlertMessages" :key="globalAlert.id">
<transition name="fade" mode="out-in">
@ -45,7 +53,7 @@ export default {
cmsWidgetName: String,
hideSalesforceWebchatLaunchButton: {
type: Boolean,
default: false,
default: true,
},
},
computed: {
@ -100,16 +108,23 @@ export default {
</script>
<style lang="scss" scoped>
:deep(.menu-modal-container) {
display: none; // This is temporary until the hamburger menu is re-implemented for use with the progress bar
}
.funnel-header {
position: relative;
padding-top: 0.97rem;
}
padding: 1rem 0;
.logo-image {
width: auto;
height: 1.1875rem;
.button-container {
width: 1.5rem;
height: 1.625rem;
}
.logo-image {
width: auto;
height: 1.1875rem;
margin: 0 auto;
}
:deep(.menu-modal-container) {
display: none; // This is temporary until the hamburger menu is re-implemented for use with the progress bar
}
}
</style>

View file

@ -11,21 +11,6 @@
<div class="bar3"></div>
</button>
</div>
<div class="menu-modal-container">
<button
aria-hidden="true"
tabindex="-1"
class="menu-button"
type="button"
:class="[isActive ? 'active' : '']"
@click="toggleModal"
aria-label="Hamburger Menu (modal window)">
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</button>
</div>
<!-- Modal -->
<div
class="modal menu-modal fade"
data-bs-backdrop="false"
@ -123,9 +108,6 @@ export default {
<style lang="scss" scoped>
.menu-modal-container {
position: absolute;
padding: 1.47rem 0 1.47rem 1.47rem;
right: 0;
button {
border: none;
&.menu-button {
@ -164,7 +146,7 @@ export default {
}
.modal {
&.menu-modal {
left: auto;
//left: auto;
height: calc(100% - 56px);
top: 56px;
border-top: 1px solid $gray-300;

View file

@ -5,6 +5,9 @@ import store from "@/store";
describe("progressBar", () => {
test("progress should be 0", () => {
// Arrange
mockMixin.computed = {
pageName: () => undefined,
};
// Act
const wrapper = shallowMount(progressBar, {
@ -20,10 +23,8 @@ describe("progressBar", () => {
describe("progressBar", () => {
test("progress should be 4%", () => {
// Arrange
store.getters = {
applicationUser: {
lastPageVisited: "vehicle",
},
mockMixin.computed = {
pageName: () => "vehicle",
};
// Act
@ -40,10 +41,8 @@ describe("progressBar", () => {
describe("progressBar", () => {
test("progress should be 48%", () => {
// Arrange
store.getters = {
applicationUser: {
lastPageVisited: "quote",
},
mockMixin.computed = {
pageName: () => "quote",
};
// Act
@ -60,10 +59,8 @@ describe("progressBar", () => {
describe("progressBar", () => {
test("progress should be 100%", () => {
// Arrange
store.getters = {
applicationUser: {
lastPageVisited: "confirmation",
},
mockMixin.computed = {
pageName: () => "confirmation",
};
// Act

View file

@ -10,15 +10,10 @@ import { getProgressBarPercentage } from "@/constants/progress-bar-mapper";
export default {
name: "progressBar",
data() {
return {
progress: this.getProgress(),
};
},
methods: {
getProgress() {
var pageName = store.getters.applicationUser.lastPageVisited;
return getProgressBarPercentage(pageName);
data() {},
computed: {
progress() {
return getProgressBarPercentage(this.pageName);
},
},
};

View file

@ -245,6 +245,7 @@ function setupMocks() {
};
const mountOptions = getMountOptions({
mixins: [baseMixin, vehicleQuestionsMixin],
route: { name: "questions" },
});
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(questionsPageLayout, mountOptions);

View file

@ -45,9 +45,7 @@
<script>
import textLink from "@/ux-components/text-link/text-link";
import buttonMain from "@/ux-components/button-main/button-main";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import analyticsMixin from "@/mixins/analytics-mixin";
import router from "@/router/index.js";
export default {
name: "navbar",
@ -113,12 +111,7 @@ export default {
}
},
routeReturnUser() {
analyticsMixin.methods.initSession();
router.push({
path: "/",
query: { fmgPage: fmgPageValues.RETURN_USER },
});
this.$router.sendToReturnUser();
},
},
};

View file

@ -117,7 +117,7 @@ export default {
endpoint: endpoint,
};
router.navigateError(errorPayload);
router.bailout(errorPayload);
// do not log 404 errors from services because we return NotFound
// when a service doesn't return an object

View file

@ -38,7 +38,7 @@ it("Global Methods - Call Http Client - Should Reject Promise", () => {
isError: true,
});
analyticsMixIn.methods.pushEventToGA = jest.fn();
router.navigateError = jest.fn();
router.bailout = jest.fn();
//Act
globalMethods.callHttpClient(httpArgs).catch((err) => {

View file

@ -0,0 +1,23 @@
import store from "@/store";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { queryStrings } from "@/constants/query-strings";
import { storeActions } from "@/constants/store-actions";
export function debugLog(message, data, forceLog = false) {
checkLogParam();
if (store?.getters?.applicationUser?.loggingOption || forceLog) {
data = data ?? "";
const outData = typeof data === "object" ? JSON.stringify(data) : data;
console.log(`${new Date()} ${message} ${outData}`);
}
}
export function checkLogParam() {
const logQueryString = getQuerystringParameter(queryStrings.LOG);
if (logQueryString) {
store?.dispatch(storeActions?.SAVE_LOGGING_OPTION, logQueryString);
}
}

View file

@ -1,123 +1,23 @@
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeActions } from "@/constants/store-actions.js";
import { settleAllPromises } from "@/helpers/layout-helper";
import experimentMixin from "@/mixins/experiment-mixin";
import { experimentSettings } from "@/constants/experiments";
import { includesWindshieldReplacement } from "@/helpers/damage-helper";
import { applicationConfig } from "@/constants/application-config.js";
import store from "@/store";
import router from "@/router";
export async function getPageToRouteExistingOrderTo(toRoute = {}) {
const fmgQueryPage = toRoute.query[queryStrings.FMG_PAGE];
const hasFmgQueryPage = !!fmgQueryPage;
if (hasFmgQueryPage) {
return await getDirectNavigation(toRoute);
} else {
return await getImplicitNavigation(toRoute);
}
}
/*
* Send the user to the specified page, except in specific scenarios.
*/
export async function getDirectNavigation(toRoute) {
const fmgPageValue = toRoute.query[queryStrings.FMG_PAGE];
// Verified insurance users need to be routed around some pages. Same goes for C&C unverified 6 digit referrals
// vehicle -> vehicle-damage
// quote -> vin-lookup/estimate
// insurance-company -> vin-lookup/estimate
if (
store.getters.payment?.insuranceCoverage?.isVerified ||
store.getters.order?.referralNumber?.length === 6
) {
if (fmgPageValue === fmgPageValues.VEHICLE) {
return fmgPageValues.VEHICLE_DAMAGE;
}
if (fmgPageValue === fmgPageValues.QUOTE) {
const skipVin = await skipVinLookup();
if (skipVin || !store.getters.vehicle?.vin) {
return fmgPageValues.ESTIMATE;
} else {
return fmgPageValues.VIN_LOOKUP;
}
}
}
// Otherwise, respect navigation from query string.
return fmgPageValue;
}
/*
* Determine which page in the flow the user should be sent to, based on their current order.
* General strategy: run through pages in reverse order, and select first where prerequisites are valid.
* QUOTE is the latest page to send to.
*/
export async function getImplicitNavigation(toRoute) {
const vehicleDamageComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_DAMAGE);
const estimateComponent = await getLazyLoadedComponent(fmgPageValues.ESTIMATE);
const vinLookupComponent = await getLazyLoadedComponent(fmgPageValues.VIN_LOOKUP);
const partQuestionsComponent = await getLazyLoadedComponent(fmgPageValues.PART_QUESTIONS);
const vehiclePartsComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_PARTS);
const moldingQuestionsComponent = await getLazyLoadedComponent(fmgPageValues.MOLDING_QUESTIONS);
const capabilityQuestionsComponent = await getLazyLoadedComponent(
fmgPageValues.CAPABILITY_QUESTIONS
);
const quoteComponent = await getLazyLoadedComponent(fmgPageValues.QUOTE);
const skipVin = await skipVinLookup();
if (store.getters.order.payment.insuranceCoverage?.isVerified) {
return fmgPageValues.HERITAGE;
}
if (quoteComponent.methods.arePagePrerequisitesValid()) {
// Do not send to quote if verified insurance user.
if (store.getters.requiresVerifiedRedirecting) {
return fmgPageValues.HERITAGE;
} else {
return fmgPageValues.QUOTE;
}
} else if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.CAPABILITY_QUESTIONS;
} else if (moldingQuestionsComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.MOLDING_QUESTIONS;
} else if (vehiclePartsComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_PARTS;
} else if (partQuestionsComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.PART_QUESTIONS;
} else if (vinLookupComponent.methods.arePagePrerequisitesValid() && !skipVin) {
return fmgPageValues.VIN_LOOKUP;
} else if (estimateComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.ESTIMATE;
} else if (vehicleDamageComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_DAMAGE;
} else {
return fmgPageValues.VEHICLE;
}
}
import { debugLog } from "@/helpers/debug-log-helper";
/*
Used to navigate to the heritage funnel with the correct query string and url.
*/
export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLog, navType }) {
const log = getQuerystringParameter(queryStrings.LOG);
if (log === "true") {
console.log("------------- Navigate to heritage start -----------------");
console.log(new Date() + " applicationConfig: " + JSON.stringify(applicationConfig));
console.log(new Date() + " navigateToHeritageFunnel: " + applicationConfig.HERITAGE_FUNNEL);
console.log("------------- Navigate to heritage end -----------------");
}
debugLog("--- Navigate to heritage start ---");
debugLog("applicationConfig:", applicationConfig);
debugLog("navigateToHeritageFunnel:", applicationConfig.HERITAGE_FUNNEL);
debugLog("--- Navigate to heritage end ---");
// Create the order (or save existing order) when navigating to Heritage Funnel.
if (shouldSaveSession) {
@ -191,7 +91,3 @@ export async function skipVinLookupNotRepair() {
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
);
}
async function getLazyLoadedComponent(pageName) {
return (await lazyLoadComponent(pageName)()).default;
}

View file

@ -1,12 +1,11 @@
import * as navigationHelper from "@/helpers/heritage-integration/navigation-helper";
import * as orderHelper from "@/helpers/heritage-integration/order-helper";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { lazyLoadComponent } from "@/router/methods/helpers/lazy-load-component";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { setupMocksForJsFiles, getMockOrderInfo } from "@/helpers/unit-test-helper.js";
import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { externalUrls } from "@/router/constants/externalUrl-values";
import { queryStrings } from "@/constants/query-strings";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected";
@ -17,476 +16,10 @@ const getPageToRouteExistingOrderTo = navigationHelper.getPageToRouteExistingOrd
const navigateToHeritageFunnel = navigationHelper.navigateToHeritageFunnel;
// Mock Lazy Load
jest.mock("@/router/dynamic-routing/component-loader.js", () => ({
jest.mock("@/router/methods/helpers/lazy-load-component", () => ({
lazyLoadComponent: jest.fn(),
}));
describe("getPageToRouteExistingOrderTo", () => {
describe("Explicit vs. Implicit Routing", () => {
test("Should call explicit routing if `fmgPage` parameter is present in query", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: "schedule",
},
};
mockLazyLoadComponentReturnValues();
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
// If direct, should be "schedule".
// Otherwise, not.
expect(result).toBe("schedule");
});
test("Should call implicit routing if `fmgPage` parameter is NOT present in query", async () => {
// Arrange
const toRoute = {
query: {},
};
mockLazyLoadComponentReturnValues();
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
// If direct, should be "schedule".
// Otherwise, not.
expect(result).not.toBe("schedule");
});
});
describe("Implicit Routing", () => {
test("should return vehicle", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: false,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.VEHICLE);
});
test("should return vehicle-damage", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: false,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
});
test("user has YMMS and no vehicle questions > should return estimate", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
const mockExperimentsList = [
{
universeName: "ConceptFunnel",
settings: {
SuppressVinCapture: false,
},
},
];
store.commit(storeMutations.UPDATE_EXPERIMENTS, mockExperimentsList);
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
[fmgPageValues.MOLDING_QUESTIONS]: false,
[fmgPageValues.VEHICLE_PARTS]: false,
[fmgPageValues.PART_QUESTIONS]: false,
[fmgPageValues.VIN_LOOKUP]: true,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.ESTIMATE);
});
test("user has YMMS but no questions or carId > should return estimate", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
[fmgPageValues.MOLDING_QUESTIONS]: false,
[fmgPageValues.VEHICLE_PARTS]: false,
[fmgPageValues.PART_QUESTIONS]: false,
[fmgPageValues.VIN_LOOKUP]: false,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.ESTIMATE);
});
test("user has capability questions and molding questions > should return capability questions", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: true,
[fmgPageValues.MOLDING_QUESTIONS]: true,
[fmgPageValues.VEHICLE_PARTS]: false,
[fmgPageValues.PART_QUESTIONS]: false,
[fmgPageValues.VIN_LOOKUP]: false,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.CAPABILITY_QUESTIONS);
});
test("user has molding questions and part questions > should return molding questions", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
[fmgPageValues.MOLDING_QUESTIONS]: true,
[fmgPageValues.VEHICLE_PARTS]: false,
[fmgPageValues.PART_QUESTIONS]: true,
[fmgPageValues.VIN_LOOKUP]: false,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.MOLDING_QUESTIONS);
});
test("user has vehicle parts questions > should return vehicle-parts", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
[fmgPageValues.MOLDING_QUESTIONS]: false,
[fmgPageValues.VEHICLE_PARTS]: true,
[fmgPageValues.PART_QUESTIONS]: true,
[fmgPageValues.VIN_LOOKUP]: false,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.VEHICLE_PARTS);
});
test("user has part questions > should return part-questions", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
[fmgPageValues.MOLDING_QUESTIONS]: false,
[fmgPageValues.VEHICLE_PARTS]: false,
[fmgPageValues.PART_QUESTIONS]: true,
[fmgPageValues.VIN_LOOKUP]: false,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.PART_QUESTIONS);
});
test("should return quote, if not verified insurance", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
[fmgPageValues.MOLDING_QUESTIONS]: false,
[fmgPageValues.VEHICLE_PARTS]: false,
[fmgPageValues.PART_QUESTIONS]: true,
[fmgPageValues.VIN_LOOKUP]: false,
[fmgPageValues.QUOTE]: true,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.QUOTE);
});
test("should not return pages beyond quote even if prerequisites are met", async () => {
// Arrange
const toRoute = {
query: {},
};
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE]: true,
[fmgPageValues.VEHICLE_DAMAGE]: true,
[fmgPageValues.ESTIMATE]: true,
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
[fmgPageValues.MOLDING_QUESTIONS]: false,
[fmgPageValues.VEHICLE_PARTS]: false,
[fmgPageValues.PART_QUESTIONS]: true,
[fmgPageValues.VIN_LOOKUP]: false,
[fmgPageValues.QUOTE]: true,
[fmgPageValues.SERVICE_LOCATION]: true,
[fmgPageValues.SCHEDULE]: true,
});
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
//Assert
expect(result).toBe(fmgPageValues.QUOTE);
});
});
describe("Explicit Routing", () => {
test("Returns the value passed in in nominal case", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: "testValue",
},
};
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
expect(result).toBe("testValue");
});
describe("Verified users", () => {
test("Replace navigation to `vehicle` with `vehicle-damage`", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: fmgPageValues.VEHICLE,
},
};
store.commit(storeMutations.UPDATE_IS_VERIFIED_INSURANCE, true);
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, "666666");
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
});
describe("Vin-Exempt", () => {
test("Replace navigation to `quote` with `estimate`", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: fmgPageValues.QUOTE,
},
};
store.commit(storeMutations.UPDATE_IS_VERIFIED_INSURANCE, true);
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, "666666");
store.commit(storeMutations.UPDATE_IS_REPAIR, true);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
expect(result).toBe(fmgPageValues.ESTIMATE);
});
});
describe("Vin-Required", () => {
test("Replace navigation to `quote` with `vin-lookup`", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: fmgPageValues.QUOTE,
},
};
store.commit(storeMutations.UPDATE_IS_VERIFIED_INSURANCE, true);
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, "666666");
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
store.commit(storeMutations.UPDATE_GLASS_TO_REPLACE, [
{ glassLocation: glassLocations.WINDSHIELD },
]);
store.commit(storeMutations.UPDATE_VEHICLE_VIN, "abcd1234");
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
expect(result).toBe(fmgPageValues.VIN_LOOKUP);
});
});
});
describe("Non-verified users", () => {
test("Don't replace navigation to `vehicle` with `vehicle-damage`", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: fmgPageValues.VEHICLE,
},
};
store.commit(storeMutations.UPDATE_IS_VERIFIED_INSURANCE, false);
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, "55555");
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
expect(result).toBe(fmgPageValues.VEHICLE);
});
test("Don't replace navigation to `quote` with `estimate` or `vin-lookup`", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: fmgPageValues.QUOTE,
},
};
store.commit(storeMutations.UPDATE_IS_VERIFIED_INSURANCE, false);
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, "55555");
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
expect(result).toBe(fmgPageValues.QUOTE);
});
test("Don't replace navigation to `insurance-company` with `estimate` or `vin-lookup`", async () => {
// Arrange
const toRoute = {
query: {
fmgPage: fmgPageValues.INSURANCE_COMPANY,
},
};
store.commit(storeMutations.UPDATE_IS_VERIFIED_INSURANCE, false);
store.commit(storeMutations.UPDATE_REFERRAL_NUMBER, "55555");
// Act
const result = await getPageToRouteExistingOrderTo(toRoute);
// Assert
expect(result).toBe(fmgPageValues.INSURANCE_COMPANY);
});
});
});
});
describe("navigateToHeritageFunnel", () => {
test("should save session", async () => {
// Arrange

View file

@ -59,25 +59,15 @@ export async function saveSession({
submitAfterSave = false,
createUnscheduledStatusWorkOrderForPIA = false,
}) {
var saveSessionPromise;
if (store.getters.applicationUser.saveSessionPromise) {
// queue newest request after current saveSessionPromise resolves
saveSessionPromise = store.getters.applicationUser.saveSessionPromise.then(() => {
// get a new saveSessionPromise
return saveSessionHelper(
pageNameToLog,
submitAfterSave,
createUnscheduledStatusWorkOrderForPIA
);
});
} else {
// create an initial saveSessionPromise
saveSessionPromise = saveSessionHelper(
// Add next save session to end of queue
const saveSessionPromise = flushSaveSessionQueue().then(() => {
return saveSessionHelper(
pageNameToLog,
submitAfterSave,
createUnscheduledStatusWorkOrderForPIA
);
}
});
store.commit(storeMutations.UPDATE_SAVE_SESSION_PROMISE, saveSessionPromise);
// await here to allow for a caller to await and make the function synchronous
if (!store.getters.applicationUser.savedSessionId || shouldAwaitSaveSessionQueue) {
@ -133,7 +123,7 @@ async function loadSession(
pageNameToLog
) {
// await the saveSessionPromise in the store to make sure we're loading up to date information
await store.getters.applicationUser.saveSessionPromise;
await flushSaveSessionQueue();
const response = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.LOAD_SESSION,
@ -193,3 +183,7 @@ async function saveSessionHelper(
// Update the cookie with the referral information when saved.
updateOrCreateFunnelCookie();
}
export async function flushSaveSessionQueue() {
return Promise.resolve(store.getters.applicationUser.saveSessionPromise);
}

View file

@ -1,8 +1,7 @@
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
import { navigationScenarios } from "@/router/constants/navigation-scenarios.js";
import { vehicleCategories } from "@/constants/vehicle-categories.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { cookieNames } from "@/constants/cookie-names";
import { Form } from "vee-validate";
import baseMixin from "@/mixins/base-mixin";
@ -19,7 +18,6 @@ import {
ValueToLogTypes,
} from "@/constants/analytics";
import { queryStrings } from "@/constants/query-strings";
import { routerParams } from "@/router/router-constants/router-params";
// Common methods
export function getMountOptions(mockData) {
@ -60,7 +58,6 @@ export function getMountOptions(mockData) {
mocks.storeMutations = storeMutations;
mocks.navigationScenarios = navigationScenarios;
mocks.vehicleCategories = vehicleCategories;
mocks.fmgPageValues = fmgPageValues;
mocks.analyticsPageEvents = analyticsPageEvents;
mocks.GaCategories = GaCategories;
mocks.GaActions = GaActions;
@ -68,7 +65,6 @@ export function getMountOptions(mockData) {
mocks.GaEvents = GaEvents;
mocks.ValueToLogTypes = ValueToLogTypes;
mocks.queryStrings = queryStrings;
mocks.routerParams = routerParams;
// Mock $store and $router when accessing this.$store/$router
mocks.$store = mockData.store;
@ -80,6 +76,11 @@ export function getMountOptions(mockData) {
mocks: mocks,
mixins: mockData.mixins,
stubs: { Form },
computed: {
pageName() {
return mockData?.route?.name;
},
},
};
const props = mockData.propsData || {};

View file

@ -7,7 +7,7 @@ import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import store from "@/store";
import { experimentSettings } from "@/constants/experiments";
@ -308,11 +308,9 @@ describe("address-lookup.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
undefined,
{},
{},
carsFound
);
});
@ -399,9 +397,7 @@ describe("address-lookup.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
undefined,
{},
{ displayVehicleChangeAlert: true }
undefined
);
});
@ -717,6 +713,7 @@ function setupMocks({
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
store: {
getters: {

View file

@ -118,12 +118,11 @@ import { errorMessages } from "@/constants/error-messages";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions";
import { routerParams } from "@/router/router-constants/router-params";
import { getDamageString, isGlassAvailableForCarId } from "@/helpers/damage-helper";
import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
// DEFINE VALIDATION RULES - Note: Additional rules are defined in Customer Questions and Address Questions components
defineRule("service-zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -137,7 +136,7 @@ export default {
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -193,7 +192,10 @@ export default {
},
backButtonAction() {
// route to move backwards
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
attachCustomEvents() {
this.prependActionToMethod(this, this.forwardButtonAction, () => {
@ -409,7 +411,7 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_PAGE_DATA,
{
page: fmgPageValues.EMAIL_SMS_PAGES,
page: routeData.SERVICE_ZIP.name,
data: { emailOrSmsValue: this.customerQuestions.emailOrSms },
},
false
@ -442,18 +444,14 @@ export default {
) {
this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
this.pageName
);
} else if (matchingCars.length === 1) {
await this.navigateForwardWithSingleCarMatch();
} else {
this.$router.navigateWithSaving(
this.$router.navigateWithPageData(
this.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
this.$route,
{},
{},
this.pageName,
carsFound
);
}

View file

@ -60,7 +60,7 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
@ -71,7 +71,6 @@ import {
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy,
} from "@/helpers/cms-content-helper";
import { routerParams } from "@/router/router-constants/router-params";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
// DEFINE VALIDATION RULES
@ -82,7 +81,7 @@ export default {
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -144,7 +143,7 @@ export default {
return mappedData;
},
VehiclesFromApi() {
return store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES);
return store.getters.pageData(routeData.ADDRESS_VEHICLES.name);
},
selectedVehicle() {
return this.VehiclesForQuestions.find(({ vin }) => vin === this.selectedVehicleVin);
@ -159,14 +158,17 @@ export default {
if (
store.getters.order.vehicle.carId &&
store.getters.order.serviceLocation.zipCode &&
store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES)
store.getters.pageData(routeData.ADDRESS_VEHICLES.name)
) {
return true;
}
return false;
},
backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
async forwardButtonAction() {
const vinLookup = await this.dispatchStoreActionWithLogging(
@ -205,9 +207,7 @@ export default {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
this.pageName
);
} else {
await this.navigateForwardWithSingleCarMatch();

View file

@ -37,7 +37,7 @@ export default {
name: "bailout",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [

View file

@ -32,7 +32,7 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
@ -47,7 +47,7 @@ export default {
mixins: [vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -97,7 +97,7 @@ export default {
return this.getCmsContent("AdditionalPartsQuestionsAlert", "BodyText");
},
partsOrQuestionsData() {
return this.$store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)
return this.$store.getters.pageData(routeData.CAPABILITY_QUESTIONS.name)
.partsOrQuestions;
},
},
@ -107,7 +107,7 @@ export default {
methods: {
arePagePrerequisitesValid() {
const capabilityQuestionsPageData = store.getters.pageData(
fmgPageValues.CAPABILITY_QUESTIONS
routeData.CAPABILITY_QUESTIONS.name
);
return (
// has capabilityQuestions array and has glassName not null

View file

@ -123,6 +123,7 @@ import experimentMixin from "@/mixins/experiment-mixin.js";
import { containsRecalParts } from "@/helpers/recal-helper.js";
import donationBlock from "@/experiment-components/donation-block.vue";
import { partNumberStrings } from "@/constants/part-number-strings";
import analyticsMixin from "@/mixins/analytics-mixin";
export default {
name: "confirmation",
@ -179,7 +180,7 @@ export default {
// Call APIs
const submittedOrder = baseMixin.methods.getSubmittedOrder();
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_WIPERS,
@ -244,6 +245,8 @@ export default {
vm.showDonationSuccess = lineItemsFromSubmittedOrder.supportingItems?.some(
(item) => item.partType === partNumberStrings.DONATION
);
vm.pushAnalytics();
});
},
data() {
@ -556,6 +559,16 @@ export default {
this.$refs.donationBlock.removeLoader();
}
},
pushAnalytics() {
//Push Ecommerce Cart to Data Layer
analyticsMixin.methods.pushECommerceCartToDataLayer();
//Push Product Array to Data Layer
analyticsMixin.methods.pushProductArrayToDataLayer();
//Push Commission Junction Gtm Data To Data Layer
analyticsMixin.methods.pushCommissionJunctionGtmDataToDataLayer();
},
},
components: {
funnelHeader,

View file

@ -84,6 +84,7 @@ import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import store from "@/store";
import { paymentMethods } from "@/constants/payment-method-constants";
import { flushSaveSessionQueue } from "@/helpers/heritage-integration/order-helper";
// DEFINE VALIDATION RULES
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
@ -102,7 +103,7 @@ export default {
name: "customer-details",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -157,7 +158,10 @@ export default {
return store.getters.order.serviceLocation.techNotes;
},
backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
async forwardButtonAction() {
this.dispatchStoreAction(
@ -186,10 +190,13 @@ export default {
paymentMethods.NONE,
false
);
await store.getters.applicationUser.saveSessionPromise;
await flushSaveSessionQueue();
}
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.pageName
);
},
},
components: {

View file

@ -215,7 +215,7 @@ describe("estimate.vue", () => {
});
describe("estimate.vue", () => {
test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
test.skip("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
store.getters = {
externalParameterState: { isExternalParameter: true },
externalParameterEstimate: { vinSelection: "decline" },
@ -263,7 +263,7 @@ function setupMocks({
navigateWithoutSaving: jest.fn(),
},
route: {
query: {},
name: "estimate",
},
},
}) {
@ -271,7 +271,9 @@ function setupMocks({
const cmsContent = {
groupName: groupName,
QuestionText: cmsQuestionText,
Answers: cmsAnswers,
VinLookupMethod: {
Answers: cmsAnswers,
},
FunnelFooterWidget: FunnelFooterWidget,
};
const mockMixin = {

View file

@ -62,6 +62,7 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import baseMixin from "@/mixins/base-mixin.js";
import { queryStrings } from "@/constants/query-strings";
import { nextTick } from "vue";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
// Define Validation Rules
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -89,7 +90,7 @@ export default {
async beforeRouteEnter(to, from, next) {
//Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
@ -98,9 +99,9 @@ export default {
lowerCaseParams.append(name.toLowerCase(), value);
}
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE)
? lowerCaseParams.get(queryStrings.ZIP_CODE)
: store.getters.order.serviceLocation.zipCode;
const zip =
consumeQueryFromStash(queryStrings.ZIP_CODE) ??
store.getters.order.serviceLocation.zipCode;
var vinByAddressPromise;
if (zip) {
@ -184,32 +185,35 @@ export default {
},
backButtonAction() {
// route to move backwards
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
async forwardButtonAction() {
if (this.selectedVinLookupMethod === vinLookupMethodSelections.MANUALVIN) {
await this.dispatchStoreAction(storeActions.CLEAR_VIN);
return this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_MANUAL_VIN,
this.$route
this.pageName
);
}
if (this.selectedVinLookupMethod === vinLookupMethodSelections.LICENSEPLATE) {
return this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_LICENSE_PLATE,
this.$route
this.pageName
);
}
if (this.selectedVinLookupMethod === vinLookupMethodSelections.HOMEADDRESS) {
return this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_HOME_ADDRESS,
this.$route
this.pageName
);
}
if (this.selectedVinLookupMethod === vinLookupMethodSelections.DECLINE) {
return this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_NO_VIN,
this.$route
this.pageName
);
}
},

View file

@ -26,6 +26,7 @@ jest.mock("@/mixins/base-mixin.js", () => ({
return {
navigationScenarios: {
CLICKED_BACK: "clicked_back",
CLICKED_PAY_ON_MY_OWN: "clicked_pay_on_my_own",
},
};
},
@ -64,7 +65,7 @@ describe("insurance-company.vue", () => {
expect(wrapper.vm.isCashParentAccountNumber()).toBe(false);
});
test("should call navigateWithoutSaving with { isCashSelected: true } when forceCashSelection is true", () => {
test("should call navigateWithoutSaving with CLICKED_PAY_ON_MY_OWN when forceCashSelection is true", () => {
const wrapper = shallowMount(InsuranceCompany, {
global: {
mocks: {
@ -77,9 +78,8 @@ describe("insurance-company.vue", () => {
wrapper.vm.backButtonAction(true);
expect(mockRouter.navigateWithoutSaving).toHaveBeenCalledWith(
wrapper.vm.navigationScenarios.CLICKED_BACK,
wrapper.vm.$route,
{ isCashSelected: true }
wrapper.vm.navigationScenarios.CLICKED_PAY_ON_MY_OWN,
wrapper.vm.$route
);
});

View file

@ -58,6 +58,7 @@ import { experimentSettings } from "@/constants/experiments";
import { partNumberStrings } from "@/constants/part-number-strings";
import { deepClone } from "@/helpers/object-helper";
import store from "@/store";
import { flushSaveSessionQueue } from "@/helpers/heritage-integration/order-helper";
export default {
name: "insurance-company",
@ -81,7 +82,7 @@ export default {
}
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const insuranceCompanyListPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_INSURANCE_COMPANY_LIST,
{},
@ -146,18 +147,20 @@ export default {
// Go back to Quote page
if (forceCashSelectionOnQuote) {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.$route,
{ isCashSelected: true }
this.navigationScenarios.CLICKED_PAY_ON_MY_OWN,
this.pageName
);
return;
}
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
async forwardButtonAction() {
// await any pending save-sessions. if you don't, we will save the new parent account into vuex and then a pending async save-session response overwrites it
await store.getters.applicationUser.saveSessionPromise;
await flushSaveSessionQueue();
await this.dispatchStoreAction(
this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER,

View file

@ -112,13 +112,14 @@ import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions";
import { errorMessages } from "@/constants/error-messages";
import { getDamageString, isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { routerParams } from "@/router/router-constants/router-params";
import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { queryStrings } from "@/constants/query-strings";
// DEFINE VALIDATION RULES
defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED));
@ -138,7 +139,7 @@ export default {
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -163,7 +164,8 @@ export default {
data() {
return {
licensePlate: this.getLicensePlateFromStore(),
registrationZipCode: this.getRegistrationZipFromStore() ?? this.$route.query.zipcode,
registrationZipCode:
this.getRegistrationZipFromStore() ?? consumeQueryFromStash(queryStrings.ZIP_CODE),
emailOrSms: this.getEmailOrSmsFromStore(),
serviceZipCode: this.getServiceZipFromStore(),
carId: this.getCarIdfromStore(),
@ -187,7 +189,10 @@ export default {
},
backButtonAction() {
// route to move backwards
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
attachCustomEvents() {
this.prependActionToMethod(this, this.forwardButtonAction, () => {
@ -361,7 +366,7 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_PAGE_DATA,
{
page: fmgPageValues.EMAIL_SMS_PAGES,
page: routeData.SERVICE_ZIP.name,
data: { emailOrSmsValue: this.emailOrSms },
},
false
@ -392,9 +397,7 @@ export default {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
this.pageName
);
} else {
await this.navigateForwardWithSingleCarMatch();

View file

@ -336,6 +336,7 @@ function setupMocks({
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
store: {
getters: store.getters,

View file

@ -32,7 +32,7 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
@ -47,7 +47,7 @@ export default {
mixins: [vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -112,7 +112,7 @@ export default {
return this.getCmsContent("AdditionalPartsQuestionsAlert", "BodyText");
},
partsOrQuestionsData() {
return this.$store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS).partsOrQuestions;
return this.$store.getters.pageData(routeData.MOLDING_QUESTIONS.name).partsOrQuestions;
},
},
mounted() {
@ -121,7 +121,7 @@ export default {
methods: {
arePagePrerequisitesValid() {
const moldingQuestionsFromPageData = store.getters.pageData(
fmgPageValues.MOLDING_QUESTIONS
routeData.MOLDING_QUESTIONS.name
);
return (
// has childPartQuestions array and has glassName not null

View file

@ -32,7 +32,7 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
@ -47,7 +47,7 @@ export default {
mixins: [vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -114,7 +114,7 @@ export default {
return this.getCmsContent("AlertPartsQuestions", "BodyText");
},
partsOrQuestionsData() {
return this.$store.getters.pageData(fmgPageValues.PART_QUESTIONS).partsOrQuestions;
return this.$store.getters.pageData(routeData.PART_QUESTIONS.name).partsOrQuestions;
},
},
mounted() {
@ -122,7 +122,7 @@ export default {
},
methods: {
arePagePrerequisitesValid() {
const partQuestionsFromPageData = store.getters.pageData(fmgPageValues.PART_QUESTIONS);
const partQuestionsFromPageData = store.getters.pageData(routeData.PART_QUESTIONS.name);
return (
// has .partQuestions array and has glassName not null
partQuestionsFromPageData?.partsOrQuestions?.some((part) => part?.glassName) &&

View file

@ -147,7 +147,6 @@ import {
getNewlyInactivatedPromos,
} from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings";
import { routerParams } from "@/router/router-constants/router-params";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper";
@ -168,6 +167,8 @@ import {
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { debugLog } from "@/helpers/debug-log-helper";
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
@ -179,7 +180,7 @@ export default {
},
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const lineItemsFromStore = deepClone(store.getters.order.lineItems);
@ -281,7 +282,7 @@ export default {
const oldActivePromos = store.getters.lineItems.promos?.slice(0);
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
const promoCodeFromQueryString = consumeQueryFromStash(queryStrings.PROMO);
const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString,
@ -374,7 +375,7 @@ export default {
doesCopyContainRouterLink,
getRouterLinkRouteFromCopy,
navigateWithScenario(scenarioName) {
this.$router.navigateWithoutSaving(scenarioName, this.$route);
this.$router.navigateWithoutSaving(scenarioName, this.pageName);
},
arePagePrerequisitesValid() {
// Service Location
@ -440,26 +441,25 @@ export default {
// prettier-ignore
{
const log = getQuerystringParameter(queryStrings.LOG);
const logAsBool = getBoolFromString(log);
if (logAsBool || !preReqResult) {
console.log("------------- payment-method.vue pagePrereqs start -----------------");
console.log(new Date() + " serviceLocationReqs::isMobile: " + isMobile);
console.log(new Date() + " serviceLocationReqs::mobileReqs: " + mobileReqs);
console.log(new Date() + " serviceLocationReqs::dropOffInshopReqs: " + dropOffInshopReqs);
console.log(new Date() + " serviceLocationReqs:: result " + serviceLocationReqs);
console.log(new Date() + " isInsuranceSet:: " + isInsuranceSet());
console.log(new Date() + " scheduleReqs::schedule.date:" + schedule.date);
console.log(new Date() + " scheduleReqs::schedule.startTime:" + schedule.startTime);
console.log(new Date() + " scheduleReqs::schedule.endTime:" + schedule.endTime);
console.log(new Date() + " scheduleReqs::schedule.jobMaxMinutes:" + schedule.jobMaxMinutes);
console.log(new Date() + " scheduleReqs::schedule.jobMinMinutes:" + schedule.jobMinMinutes);
console.log(new Date() + " customerReqs::customer.firstName:" + customer.firstName);
console.log(new Date() + " customerReqs::customer.lastName:" + customer.lastName);
console.log(new Date() + " customerReqs::customer.phoneNumber:" + customer.phoneNumber);
console.log(new Date() + " customerReqs::customer.emailAddress:" + customer.emailAddress);
console.log("------------- payment-method.vue pagePrereqs end -----------------");
}
debugLog("--- payment-method.vue pagePrereqs start ---", null, !preReqResult);
debugLog("serviceLocationRequs::isMobile:", isMobile, !preReqResult);
debugLog("serviceLocationReqs::mobileReqs:", mobileReqs, !preReqResult);
debugLog("serviceLocationReqs::dropOffInshopReqs:", dropOffInshopReqs, !preReqResult);
debugLog("serviceLocationReqs:: result", serviceLocationReqs, !preReqResult);
debugLog("isInsuranceSet::", isInsuranceSet(), !preReqResult);
debugLog("scheduleReqs::schedule.date:", schedule.date, !preReqResult);
debugLog("scheduleReqs::schedule.startTime:", schedule.startTime, !preReqResult);
debugLog("scheduleReqs::schedule.endTime:", schedule.endTime, !preReqResult);
debugLog("scheduleReqs::schedule.jobMaxMinutes:", schedule.jobMaxMinutes, !preReqResult);
debugLog("scheduleReqs::schedule.jobMinMinutes:", schedule.jobMinMinutes, !preReqResult);
debugLog("customerReqs::customer.firstName:", customer.firstName, !preReqResult);
debugLog("customerReqs::customer.lastName:", customer.lastName, !preReqResult);
debugLog("customerReqs::customer.phoneNumber:", customer.phoneNumber, !preReqResult);
debugLog("customerReqs::customer.emailAddress:", customer.emailAddress, !preReqResult);
debugLog("--- payment-method.vue pagePrereqs end ---", null, !preReqResult);
}
return preReqResult;
@ -537,7 +537,10 @@ export default {
return this.$store.getters.order.isRecalAckOptIn;
},
backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
async forwardButtonAction() {
this.forwardClicked = true;
@ -575,16 +578,14 @@ export default {
await submitWorkOrder({ pageNameToLog: "payment-method", submitAfterSave: true });
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.$route,
{},
{ [routerParams.DISPLAY_PIA_ALERT]: false }
this.pageName
);
} else {
if (this.paymentMethod == paymentMethods.INSURANCE) {
this.dispatchStoreAction(this.storeActions.SAVE_PAYMENT_TYPE, true, false);
return this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_INSURANCE,
this.$route
this.pageName
);
} else {
this.setupPia();
@ -603,16 +604,14 @@ export default {
} catch (error) {
console.log("error: response from pia submit work order:" + error.message);
this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
this.$route.query[queryStrings.DISPLAY_PIA_ALERT] = true;
this.$route.query.piaError = true;
return;
}
this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_PAY_NOW,
this.$route,
{},
{ [routerParams.DISPLAY_PIA_ALERT]: false }
this.pageName
);
},
hasSubmittedOrder() {
@ -797,10 +796,7 @@ export default {
return this.paymentMethodInternalModel;
},
shouldDisplayPiaAlert() {
return (
this.$route.query[queryStrings.DISPLAY_PIA_ALERT] ||
getBoolFromString(window.history.state.displayPiaAlert)
);
return getBoolFromString(this.$route?.query?.piaError);
},
// Necessary to make the watcher of lineItems work
// JavaScript does not keep a record of the old value, only a reference to it's location in the memory.

View file

@ -13,7 +13,6 @@ import store from "@/store";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form } from "vee-validate";
import { paymentMethods } from "@/constants/payment-method-constants";
import { routerParams } from "@/router/router-constants/router-params";
import baseMixin from "@/mixins/base-mixin.js";
import {
getDisplayAmountDue,
@ -39,20 +38,22 @@ export default {
store.getters.order.payment.piaType === paymentMethods.APPLE_PAY ||
store.getters.order.payment.piaType === paymentMethods.AFTERPAY;
if (paymentPageNavScenario) {
this.$router.navigateWithoutSaving(
this.navigationScenarios.PIA_CC_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PIA_ALERT]: store.getters.order.payment.piaType,
}
const scenarios = {
[paymentMethods.CREDIT_CARD]: this.navigationScenarios.PIA_CC_ERROR,
[paymentMethods.APPLE_PAY]: this.navigationScenarios.PIA_APPLE_ERROR,
[paymentMethods.AFTERPAY]: this.navigationScenarios.PIA_AFTERPAY_ERROR,
};
const relevantScenario = scenarios[store.getters.order.payment.piaType];
await this.$router.navigateAndForceTopLevelNavigation(
relevantScenario,
this.pageName
);
} else {
this.$router.navigateWithoutSaving(
await this.$router.navigateAndForceTopLevelNavigation(
this.navigationScenarios.PIA_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PIA_ALERT]: true,
}
this.pageName
);
}
} else {
@ -84,11 +85,9 @@ export default {
} else {
var msg = "Unknown Pia type: " + store.getters.order.payment.piaType;
console.log(msg);
this.$router.navigateWithoutSaving(
await this.$router.navigateAndForceTopLevelNavigation(
this.navigationScenarios.PIA_ERROR,
this.$route,
{},
{ [routerParams.DISPLAY_PIA_ALERT]: true }
this.pageName
);
}
}
@ -123,11 +122,9 @@ export default {
" " +
store.getters.order.referralSequenceNumber
);
this.$router.navigateWithoutSaving(
this.$router.navigateAndForceTopLevelNavigation(
this.navigationScenarios.PIA_ERROR,
this.$route,
{},
{ [routerParams.DISPLAY_PIA_ALERT]: true }
this.pageName
);
} else {
const expMonth = getQuerystringParameter(queryStrings.CARD_EXPIRATION_MONTH);
@ -181,11 +178,9 @@ export default {
});
} catch (error) {
console.log("error: response from submit work order:" + error.message);
this.$router.navigateWithoutSaving(
this.$router.navigateAndForceTopLevelNavigation(
this.navigationScenarios.PIA_ERROR,
this.$route,
{},
{ [routerParams.DISPLAY_PIA_ALERT]: true }
this.pageName
);
this.$refs.loadingModal.isModalVisible = false;
return;
@ -193,7 +188,10 @@ export default {
this.$refs.loadingModal.isModalVisible = false;
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_STATE);
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_SUCCESS, this.$route);
this.$router.navigateAndForceTopLevelNavigation(
this.navigationScenarios.PIA_SUCCESS,
this.pageName
);
},
},
components: {

View file

@ -599,7 +599,13 @@ describe("payment.vue", () => {
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions,
route: { query: { fmgPage: "page-name" }, params: {} },
route: { name: "payment-method" },
router: {
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
navigateAndForceTopLevelNavigation: jest.fn(),
},
});
// set all mock stuff

View file

@ -62,6 +62,23 @@
:isNoComp="isNoComp"
:isExpandedOnLoad="true" />
<div class="payment-method-question">
<buttonQuestion
v-if="!isAfterpay"
groupName="payment-method"
buttonTypeString="payment-method-list-button"
:buttonTypeObject="paymentMethodListButton"
isWide
:questionText="paymentMethodQuestionText"
:answers="paymentMethodAnswerData"
isRequired
:validationRules="validationRules"
:showApplePay="false"
:isInsurance="isInsurance"
v-model="switchedPaymentMethod"
textPosition="text-start" />
</div>
<hr class="mb-5" />
<navbar
@ -213,7 +230,6 @@ import alert from "@/ux-components/alert/alert";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { applicationConfig } from "@/constants/application-config";
@ -231,7 +247,6 @@ import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper";
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js";
import { routerParams } from "@/router/router-constants/router-params";
import { coverageStatus } from "@/constants/insurance";
import { getBoolFromString } from "@/helpers/boolean-helper.js";
import {
@ -240,12 +255,15 @@ import {
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { debugLog } from "@/helpers/debug-log-helper.js";
import buttonQuestion from "@/digital-components/button-question/button-question";
import paymentMethodListButton from "@/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button";
export default {
name: "payment",
data() {
return {
checkoutUrl: externalUrls.SAFELITE_HOP,
checkoutUrl: applicationConfig.SAFELITE_HOP,
piaResponseUrl: applicationConfig.PIA_RESPONSE_URL,
piaCancelUrl: applicationConfig.PIA_CANCEL_URL,
piaErrorUrl: applicationConfig.PIA_ERROR_URL,
@ -273,8 +291,12 @@ export default {
lineItems: [],
availableVaps: [],
shouldBlockInteraction: false,
paymentMethodListButton: paymentMethodListButton,
};
},
props: {
switchedpaymentTypeValue: String,
},
directives: {
resize: {
mounted: function (el) {
@ -289,7 +311,7 @@ export default {
},
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const signaturePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_SIGNATURE,
@ -420,6 +442,17 @@ export default {
});
},
computed: {
switchedPaymentMethod: {
get: function () {
return this.switchedpaymentTypeValue;
},
set: function (switchedPaymentMethod) {
this.$emit("update:switchedpaymentTypeValue", switchedPaymentMethod);
if (switchedPaymentMethod === paymentMethods.AFTERPAY) {
this.switchToAfterpay();
}
},
},
parentNomainName() {
return window.location.hostname;
},
@ -481,12 +514,36 @@ export default {
}
return false;
},
isAfterpay() {
if (this.paymentType == "ap") {
return true;
}
return false;
},
shouldDisplayPiaCCAlert() {
return this.shouldDisplayPiaAlert(paymentMethods.CREDIT_CARD);
},
shouldDisplayPiaAPAlert() {
return this.shouldDisplayPiaAlert(paymentMethods.AFTERPAY);
},
paymentMethodQuestionText() {
return this.getCmsContent("SwitchPaymentMethodWidget", "QuestionText");
},
paymentMethodAnswerData() {
var cmsData = this.getAnswersNullSafe("SwitchPaymentMethodWidget");
var tempItems = cmsData.map((answer) => {
return {
buttonLabel: answer.Text,
altText: answer.Text,
groupName: "payment-method",
value: answer.Name,
buttonImage: answer.AnswerImageUrl,
};
});
return tempItems;
},
},
methods: {
arePagePrerequisitesValid() {
@ -547,45 +604,63 @@ export default {
// prettier-ignore
{
if (store.getters.applicationUser.loggingOption || !preReqResult) {
console.log("------------- payment.vue pagePrereqs start -----------------");
console.log(new Date() + "mobileReqs:" + mobileReqs);
console.log(new Date() + "serviceLocation.address:" + serviceLocation.address);
console.log(new Date() + "serviceLocation.city:" + serviceLocation.city);
console.log(new Date() + "serviceLocation.state:" + serviceLocation.state);
console.log(new Date() + "serviceLocation.zipCode:" + serviceLocation.zipCode);
console.log("");
console.log(new Date() + "dropOffInshopReqs:" + dropOffInshopReqs);
console.log(new Date() + "serviceLocationReqs:" + serviceLocationReqs);
console.log(new Date() + "providerLocation.streetAddress:" + providerLocation.streetAddress);
console.log(new Date() + "providerLocation.city:" + providerLocation.city);
console.log(new Date() + "providerLocation.state:" + providerLocation.state);
console.log(new Date() + "providerLocation.zipCode:" + providerLocation.zipCode);
console.log("");
console.log(new Date() + "isInsuranceSet:" + isInsuranceSet);
console.log("");
console.log(new Date() + "scheduleReqs:" + scheduleReqs);
console.log(new Date() + "schedule.date:" + schedule.date);
console.log(new Date() + "schedule.startTime:" + schedule.startTime);
console.log(new Date() + "schedule.endTime:" + schedule.endTime);
console.log(new Date() + "schedule.jobMaxMinutes:" + schedule.jobMaxMinutes);
console.log(new Date() + "schedule.jobMinMinutes:" + schedule.jobMinMinutes);
console.log("");
console.log(new Date() + "customerReqs:" + customerReqs);
console.log(new Date() + "customer.firstName:" + customer.firstName);
console.log(new Date() + "customer.lastName:" + customer.lastName);
console.log(new Date() + "customer.phoneNumber:" + customer.phoneNumber);
console.log(new Date() + "customer.emailAddress:" + customer.emailAddress);
console.log("");
console.log(new Date() + "paymentMethodReqs:" + paymentMethodReqs);
console.log(new Date() + "store.getters.order.payment.isPia:" + store.getters.order.payment.isPia);
console.log(new Date() + "store.getters.order.payment.piaType:" + store.getters.order.payment.piaType);
console.log("------------- payment.vue pagePrereqs end -------------------");
}
debugLog("--- payment.vue pagePrereqs start ---", null, !preReqResult);
debugLog("mobileReqs:", mobileReqs, !preReqResult);
debugLog("serviceLocation.address:", serviceLocation.address, !preReqResult);
debugLog("serviceLocation.city:", serviceLocation.city, !preReqResult);
debugLog("serviceLocation.state:", serviceLocation.state, !preReqResult);
debugLog("serviceLocation.zipCode:", serviceLocation.zipCode, !preReqResult);
debugLog("", null, !preReqResult);
debugLog("dropOffInshopReqs:", dropOffInshopReqs, !preReqResult);
debugLog("serviceLocationReqs:", serviceLocationReqs, !preReqResult);
debugLog("providerLocation.streetAddress:", providerLocation.streetAddress, !preReqResult);
debugLog("providerLocation.city:", providerLocation.city, !preReqResult);
debugLog("providerLocation.state:", providerLocation.state, !preReqResult);
debugLog("providerLocation.zipCode:", providerLocation.zipCode, !preReqResult);
debugLog("", null, !preReqResult);
debugLog("isInsuranceSet:", isInsuranceSet, !preReqResult);
debugLog("", null, !preReqResult);
debugLog("scheduleReqs:", scheduleReqs, !preReqResult);
debugLog("schedule.date:", schedule.date, !preReqResult);
debugLog("schedule.startTime:", schedule.startTime, !preReqResult);
debugLog("schedule.endTime:", schedule.endTime, !preReqResult);
debugLog("schedule.jobMaxMinutes:", schedule.jobMaxMinutes, !preReqResult);
debugLog("schedule.jobMinMinutes:", schedule.jobMinMinutes, !preReqResult);
debugLog("", null, !preReqResult);
debugLog("customerReqs:", customerReqs, !preReqResult);
debugLog("customer.firstName:", customer.firstName, !preReqResult);
debugLog("customer.lastName:", customer.lastName, !preReqResult);
debugLog("customer.phoneNumber:", customer.phoneNumber, !preReqResult);
debugLog("customer.emailAddress:", customer.emailAddress, !preReqResult);
debugLog("", null, !preReqResult);
debugLog("paymentMethodReqs:", paymentMethodReqs, !preReqResult);
debugLog("store.getters.order.payment.isPia:", store.getters.order?.payment?.isPia, !preReqResult);
debugLog("store.getters.order.payment.piaType:", store.getters.order?.payment?.piaType, !preReqResult);
debugLog("--- payment.vue pagePrereqs end ---", null, !preReqResult);
}
return preReqResult;
},
getAnswersNullSafe(widgetName) {
const rawData = this.getCmsContent(widgetName, "Answers");
if (!rawData) {
return [];
} else {
return rawData;
}
},
getWOrkOrderNumber() {
if (store.getters.order.workOrderNumber) {
const items = store.getters.order.workOrderNumber.split("-");
@ -684,30 +759,29 @@ export default {
});
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_STATE);
window.location = applicationConfig.CONFIRMATION_URL;
this.$router.navigateAndForceTopLevelNavigation(
this.navigationScenarios.CLICKED_PAY_LATER,
this.pageName
);
//window.location = applicationConfig.CONFIRMATION_URL;
} catch (error) {
console.log("error: response from submit work order(payment pg):" + error.message);
this.$router.navigateWithoutSaving(
this.navigationScenarios.PIA_ERROR,
this.$route,
{},
{ [routerParams.DISPLAY_PIA_ALERT]: true }
this.pageName
);
this.$refs.loadingModal.isModalVisible = false;
return;
}
},
backButtonAction() {
// The only way I could figure out how to get navigation to work on a page with the iframe.
// This is also how a cancel from Paypal would work. Just a redirect to the payment-method page.
// The normal navigation sort of works but the page does not render. The "next" callback function does not get
// invoked in payment-method's beforeroute enter. I also spotted an error in vue-router.esm-bundler.js when this
// happens but was unable to ignore it with a try catch. The navigation guard error is:
// Error: Redirected from "/?fmgPage=payment-method" to "{ params: {isSavingNation: "false"}, query: "fmgPage: "payment-method"}"
// I don't know if the navigation guard thinks the to and from are the same page.
// Also tried overriding with a $router.push
window.location = applicationConfig.PIA_CANCEL_URL;
//this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
// Used to set the url directly here.
// Now hooks into a shared set of logic for doing this, also used with `payment-pia-return`.
// And uses nav scenarios properly.
this.$router.navigateAndForceTopLevelNavigation(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
submitHopForm() {
this.$nextTick(() => {
@ -790,6 +864,18 @@ export default {
);
}
},
switchToAfterpay() {
this.dispatchStoreAction(
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
paymentMethods.AFTERPAY,
false
);
const iframe = this.$refs.paymentFrame;
if (iframe) {
iframe.contentWindow.postMessage("afterpay", "*");
window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
}
},
setIFrameListener() {
window.addEventListener("message", (event) =>
this.handleIFrameContentWindowMessage(event)
@ -804,10 +890,7 @@ export default {
}
},
shouldDisplayPiaAlert(payMethod) {
return (
this.$route.query[queryStrings.DISPLAY_PIA_ALERT] === payMethod ||
getBoolFromString(window.history.state.displayPiaAlert)
);
return this.$route?.query?.piaErrorType === payMethod;
},
},
components: {
@ -816,6 +899,7 @@ export default {
loadingModal,
cart,
alert,
buttonQuestion,
},
};
</script>

View file

@ -53,6 +53,7 @@ export default {
cmsWidgetName: String,
lineItems: Array,
isAfterpayBreakoutDisplay: Boolean,
isInsuranceSelected: Boolean,
},
data() {
return {};
@ -72,9 +73,14 @@ export default {
afterpayCopyTokens() {
if (this.isAfterpayBreakoutDisplay) {
// Text for Afterpay Breakout experiment
return splitCopyOnCMSPlaceHolder(
this.getCmsContent(this.cmsWidgetName, "BodyText2")
);
var afterpayBannerCopy = this.getCmsContent(this.cmsWidgetName, "BodyText2");
afterpayBannerCopy =
afterpayBannerCopy &&
afterpayBannerCopy.replaceAll(
"{custom:paymentType}",
this.isInsuranceSelected ? "deductible" : "spending"
);
return splitCopyOnCMSPlaceHolder(afterpayBannerCopy);
} else {
return splitCopyOnCMSPlaceHolder(
this.getCmsContent(this.cmsWidgetName, "HeaderText")

View file

@ -12,8 +12,30 @@ import baseMixin from "@/mixins/base-mixin.js";
import { containsLineItemWithPartType } from "../../helpers/service-package-helper";
jest.mock("@/store", () => ({
commit: jest.fn(),
commit: jest.fn(), // Mock store.commit
dispatch: jest.fn(),
getters: {
order: {
lineItems: {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
},
payment: {
insuranceCoverage: {},
isInsurance: false,
parentAccountNumber: 167132,
},
},
payment: {
insuranceCoverage: {},
isInsurance: false,
},
customer: {
emailAddress: "test@test.com",
},
},
}));
// Mock fetchCmsContentForPage
@ -157,6 +179,7 @@ afterEach(() => {
describe("quote.vue", () => {
test("IsInsurance false should navigateWithSaving", async () => {
//Arrange
store.commit = jest.fn(); // Ensure store.commit is mocked
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
@ -628,7 +651,7 @@ describe("quote.vue", () => {
//Assert
expect(wrapper.vm.isInsuranceSelected).toBe(false);
});
test("should default to cash if isCashSelected is true, despite all other checks pointing to insurance selection", async () => {
test("should default to cash if offer = cash, despite all other checks pointing to insurance selection", async () => {
// Also needs no query parameter or previous selection in store to be present
//Arrange
@ -672,7 +695,7 @@ describe("quote.vue", () => {
//Act
await quote.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "quote", isInsuranceSelected: true, isCashSelected: true } },
{ name: "quote", query: { isInsuranceSelected: true, offer: "cash" } },
undefined,
(c) => c(wrapper.vm)
);
@ -785,6 +808,7 @@ describe("quote.vue", () => {
});
test("On forward button action save promos", async () => {
//Arrange
store.commit = jest.fn(); // Ensure store.commit is mocked
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
@ -809,7 +833,8 @@ describe("quote.vue", () => {
expect(wrapper.vm.lineItems.promos !== null).toBe(true);
});
test("On forward button action save supporting lineItems", async () => {
//Arrange
// Arrange
store.commit = jest.fn(); // Ensure store.commit is mocked
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
@ -820,6 +845,7 @@ describe("quote.vue", () => {
parentAccountNumber: 167132,
},
};
const { wrapper } = setupMocks({
customMountOptions: {
router: {

View file

@ -47,6 +47,7 @@
v-if="showAfterpayBanner"
cmsWidgetName="AfterpayModalWidget"
:isAfterpayBreakoutDisplay="isAfterpayBreakoutDisplay"
:isInsuranceSelected="isInsuranceSelected"
:lineItems="lineItems" />
<!-- If vehicle requires recal AND we are hiding recal pricing info, then show recal disclaimer banner. -->
@ -167,17 +168,25 @@ import {
getDeviceIdValue,
} from "@/helpers/heritage-integration/cookie-helper";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { storeMutations } from "@/constants/store-mutations";
import { debugLog } from "@/helpers/debug-log-helper";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
const INSURANCE_TAB_TO_DISPLAY_THRESHOLD_DEFAULT = 300;
const ACCEPTED_QUOTE_TYPES = {
CASH: "cash",
INSURANCE: "insurance",
};
export default {
name: "quote",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_WIPERS,
{
@ -214,7 +223,7 @@ export default {
userId: getUserIdValue(),
deviceId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(),
pageName: to.query.fmgPage,
pageName: to.name,
experiment: skipQuoteExperiment,
},
"quote",
@ -244,7 +253,7 @@ export default {
userId: getUserIdValue(),
deviceId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(),
pageName: to.query.fmgPage,
pageName: to.name,
experiment: afterpayBreakoutDisplayExperiment,
},
"quote",
@ -275,7 +284,7 @@ export default {
const emailFromStore = store.getters.order.customer.emailAddress;
const isEmailInStoreOnPageLoad = emailFromStore?.length > 0;
const isPopupSkipped =
store.getters.pageData(fmgPageValues.QUOTE)?.saveProgressPopupSkipped === true;
store.getters.pageData(routeData.QUOTE.name)?.saveProgressPopupSkipped === true;
const showSaveProgressPopup = !(isEmailInStoreOnPageLoad || isPopupSkipped);
const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true;
const shouldSkipToInsurance =
@ -351,7 +360,7 @@ export default {
const oldActivePromos = store.getters.lineItems.promos?.slice(0);
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
const promoCodeFromQueryString = consumeQueryFromStash(queryStrings.PROMO);
const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString,
@ -401,10 +410,15 @@ export default {
}
const getIsInsuranceSelectedValue = (availableLineItems, insuranceThreshold) => {
// query param checked here is set on insurance-selection "Pay on my own" link click
if (to.query?.isCashSelected || to.query?.iscashselected) {
// Override auto-select if specified
const quoteTypeParam = to.query?.offer?.toLowerCase();
if (quoteTypeParam === ACCEPTED_QUOTE_TYPES.CASH) {
return false;
} else if (quoteTypeParam === ACCEPTED_QUOTE_TYPES.INSURANCE) {
return true;
}
const serviceLocationState = store.getters.order.serviceLocation.state;
// usually true when returning from heritage but can be false when returning from heritage on a save quote
@ -449,9 +463,7 @@ export default {
baseMixin.methods.getTierOnePackagePrice(lineItemsNoDiscount);
// prettier-ignore
{
if (store.getters.applicationUser.loggingOption) {
console.log(new Date() + " quote tierOnePrice comparison: " + tierOnePrice + " > " + insuranceThreshold + "=" + (tierOnePrice > insuranceThreshold));
}
debugLog("quote tierOnePrice comparison: " + tierOnePrice + " > " + insuranceThreshold + "=" + (tierOnePrice > insuranceThreshold));
}
// compare base price vs arbitrary threshold (representing insurance price)
@ -472,21 +484,17 @@ export default {
// prettier-ignore
{
if (store.getters.applicationUser.loggingOption) {
console.log("------------- quote.vue thresholds start -----------------");
console.log(new Date() + " isExternalParameter: " + isExternalParameter);
console.log(new Date() + " internalThreshold: " + internalThreshold);
console.log(new Date() + " externalThreshold: " + externalThreshold);
console.log("------------- quote.vue thresholds end -----------------");
}
debugLog("--- quote.vue thresholds start ---");
debugLog(" isExternalParameter: ", isExternalParameter);
debugLog(" internalThreshold: ", internalThreshold);
debugLog(" externalThreshold: ", externalThreshold);
debugLog("--- quote.vue thresholds end ---");
}
if (isExternalParameter === externalParameterStatus.NOT_SET) {
// prettier-ignore
{
if (store.getters.applicationUser.loggingOption) {
console.log(new Date() + "quote.vue tab select EXTERNAL not set");
}
debugLog("quote.vue tab select EXTERNAL not set");
}
// there are no active or inactive external parameters; use internal threshold
if (internalThreshold) thresholdToUse = internalThreshold;
@ -501,9 +509,7 @@ export default {
if (getBoolFromString(store.getters.externalParameterQuote?.isInsurance)) {
// prettier-ignore
{
if (store.getters.applicationUser.loggingOption) {
console.log(new Date() + "quote.vue tab select EXTERNAL ISINSURANCE");
}
debugLog("quote.vue tab select EXTERNAL ISINSURANCE");
}
const insuranceSelection = true;
@ -522,9 +528,7 @@ export default {
// prettier-ignore
{
if (store.getters.applicationUser.loggingOption) {
console.log(new Date() + "quote.vue tab select NOT EXTERNAL");
}
debugLog("quote.vue tab select NOT EXTERNAL");
}
vm.isInsuranceSelected = getIsInsuranceSelectedValue(
@ -588,7 +592,7 @@ export default {
},
showAfterpayBanner() {
return (
!this.isInsuranceSelected &&
(this.isAfterpayBreakoutDisplay || !this.isInsuranceSelected) &&
(!this.isRecalibrationOnOrder || !this.shouldHideRecalibration)
);
},
@ -640,16 +644,13 @@ export default {
// prettier-ignore
{
const log = getQuerystringParameter(queryStrings.LOG);
if (log === "true" || !preReqResult) {
console.log("------------- quote.vue pagePrereqs start -----------------");
console.log(new Date() + " store.getters.order.serviceLocation.zipCode: " + store.getters.order.serviceLocation?.zipCode);
console.log(new Date() +" store.getters.order.serviceLocation.zipCodeCtu: " + store.getters.order.serviceLocation?.zipCodeCtu);
console.log(new Date() + " store.getters.order.damage.isRepair: " + store.getters.order.damage?.isRepair);
console.log(new Date() + " store.getters.order.lineItems?.glassParts: " + JSON.stringify(store.getters.order.lineItems?.glassParts));
console.log(new Date() + " store.getters.order.payment.insuranceCoverage?.isVerified: " + payment?.insuranceCoverage?.isVerified);
console.log("------------- quote.vue pagePrereqs end -----------------");
}
debugLog("--- quote.vue pagePrereqs start ---", null, !preReqResult);
debugLog("store.getters.order.serviceLocation.zipCode:", store.getters.order.serviceLocation?.zipCode, !preReqResult);
debugLog("store.getters.order.serviceLocation.zipCodeCtu:", store.getters.order.serviceLocation?.zipCodeCtu, !preReqResult);
debugLog("store.getters.order.damage.isRepair:", store.getters.order.damage?.isRepair, !preReqResult);
debugLog("store.getters.order.lineItems?.glassParts:", store.getters.order.lineItems?.glassParts, !preReqResult);
debugLog("store.getters.order.payemt.insuranceCoverage?.isVerified:", store.getters.order.payment?.insuranceCoverage?.isVerified, !preReqResult);
debugLog("--- quote.vue pagePrereqs end ---", null, !preReqResult);
}
return preReqResult;
@ -672,7 +673,7 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_PAGE_DATA,
{
page: fmgPageValues.QUOTE,
page: routeData.QUOTE.name,
data: { saveProgressPopupSkipped: true },
},
false
@ -758,14 +759,30 @@ export default {
if (payment.isInsurance) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_INSURANCE,
this.$route
this.pageName
);
} else {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_CASH,
this.$route
this.pageName
);
}
const lineItems = store.getters.order?.lineItems ?? {};
const combinedLineItems = [
...(lineItems.glassParts || []),
...(lineItems.supportingItems || []),
...(lineItems.vaps || []),
...(lineItems.promos || []),
];
const cashPriceSubtotal = combinedLineItems.length
? baseMixin.methods
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
.toFixed(2)
: "";
store.commit(storeMutations.UPDATE_CASH_PRICE_SUBTOTAL, cashPriceSubtotal);
},
attachCustomEventsForAnalytics() {
this.prependActionToMethod(this, this.forwardButtonAction, () => {
@ -789,8 +806,10 @@ export default {
});
this.$nextTick(() => {
const availablePackageNames = this.$refs.servicePackage?.servicePackageAnswers;
var eventLabel = "";
availablePackageNames?.forEach((tier) => {
var priceLabel = "";
if (availablePackageNames?.length > 1 || availablePackageNames?.length == 1) {
var tier = availablePackageNames[0];
if (tier) {
let lineItemsToPrice = this.availableLineItems;
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
@ -815,14 +834,11 @@ export default {
baseMixin.methods.filterOutFees(lineItemsToPrice)
);
price += this.$refs.servicePackage.getVapsPrice(tier.value);
const formattedPrice = parseFloat(price).toFixed(2);
eventLabel += tier.value + "_" + formattedPrice;
priceLabel = price.toFixed(2);
}
eventLabel += ",";
});
eventLabel = eventLabel.slice(0, -1);
this.pushEventToGA("quote", this.GaActions.SERVICE_PACKAGE_PRICE, eventLabel, true);
}
const subTotalLabel = this.Variables?.CASH_SUBTOTAL;
this.pushVariableToDataLayer?.({ [subTotalLabel]: priceLabel });
});
},
async skip(insuranceSelection, packageSelection) {

View file

@ -21,6 +21,8 @@ import servicePackageRadioExperiment from "@/experiment-components/service-packa
import servicePackageRadioAfterpayExperiment from "@/experiment-components/service-package-radio-for-afterpay";
import { partTypeStrings } from "@/constants/part-type-strings";
import { packageNames } from "@/constants/package-names";
import store from "@/store";
import { storeMutations } from "@/constants/store-mutations";
import {
shouldFrontWipersBeAvailable,
shouldRearWipersBeAvailable,
@ -161,6 +163,7 @@ export default {
isInsuranceSelected: this.isInsuranceSelected,
},
}));
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
return modifiedAnswers;
},
@ -289,7 +292,7 @@ export default {
})
).toFixed(2);
if (this.hasPricingByDay()) {
if (this.hasPricingByDay() && !this.isAfterpayBreakoutDisplay()) {
return "$" + formattedPriceFloat;
}
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;

View file

@ -13,7 +13,7 @@ import router from "@/router";
import navbar from "@/fmg-components/nav-bar/nav-bar";
import { Form } from "vee-validate";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { settleAllPromises } from "@/helpers/layout-helper";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
@ -257,7 +257,7 @@ describe("return-user.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
navigationScenarios.CLICKED_FORWARD,
wrapper.vm.$route
wrapper.vm.$route.name
);
});
test("expect functions in startOver to be called", async () => {
@ -272,18 +272,15 @@ describe("return-user.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
navigationScenarios.CLICKED_FORWARD,
wrapper.vm.$route
navigationScenarios.CLICKED_RESTART,
wrapper.vm.$route.name
);
expect(dispatchStoreActionSpy).toHaveBeenCalledWith(storeActions.RESET_STATE);
expect(deleteFunnelCookie).toHaveBeenCalled();
});
});
});
function setupMocks({ customMountOptions }) {
const route = { query: { fmgPage: "return-user" }, params: {} };
const route = { name: "return-user" };
baseMixin.methods.ResetExternalParamsAndHideModal = jest.fn();
@ -294,6 +291,7 @@ function setupMocks({ customMountOptions }) {
mountOptions.global.mocks["$store"] = store;
mountOptions.global.mocks["$router"] = router;
mountOptions.global.mocks.pageName = route.name;
baseMixin.methods.isFormValid = jest.fn().mockReturnValue(true);
mountOptions["attachTo"] = document.body;

View file

@ -55,7 +55,7 @@ export default {
name: "return-user",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
{
@ -85,22 +85,15 @@ export default {
async forwardButtonAction() {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
this.pageName
);
},
async startOver() {
this.pushEventToGA(
this.$route.query[this.queryStrings.FMG_PAGE],
this.GaActions.CLICKED,
"Start over",
true
);
deleteFunnelCookie();
await this.dispatchStoreAction(storeActions.RESET_STATE);
this.pushEventToGA(this.pageName, this.GaActions.CLICKED, "Start over", true);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
this.navigationScenarios.CLICKED_RESTART,
this.pageName
);
},
},

View file

@ -463,14 +463,13 @@ describe("schedule.vue...", () => {
expect(testOutput4).toBe("3:30 PM");
});
test("getDisplayTextForMilitaryTime should return the correctly formatted string", () => {
test("Clicking back should fire correct navigation", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.selectableDatesData = {
days: [],
};
wrapper.vm.$router.navigateWithoutSaving = jest.fn();
wrapper.vm.$route = "testRoute";
// Act
wrapper.vm.backButtonAction();
@ -478,7 +477,7 @@ describe("schedule.vue...", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
"CLICKED_BACK",
"testRoute"
"schedule"
);
});
});
@ -598,6 +597,7 @@ const mockCmsContent = {};
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions,
route: { name: "schedule" },
});
mountOptions.global.mocks["$store"] = store;
@ -613,6 +613,7 @@ function setupMocks({ customMountOptions }) {
},
},
];
mountOptions.global.mocks.pageName = "schedule";
const wrapper = shallowMount(schedule, mountOptions);

View file

@ -94,6 +94,7 @@ import { getAmountDue, getPricingByDayPartWithPrice } from "@/helpers/pricing-he
import { getItemsWithoutRecalParts } from "@/helpers/recal-helper";
import { partNumberStrings } from "@/constants/part-number-strings";
import { deepClone } from "@/helpers/object-helper";
import { debugLog } from "@/helpers/debug-log-helper";
// DEFINE VALIDATION RULES
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
@ -267,7 +268,7 @@ export default {
}
// Set up promises
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
store.getters.order.serviceLocation.zipCodeCtu,
@ -375,6 +376,10 @@ export default {
setTimeout(() => {
this.$refs.datePicker.showAnotherMonth().then((moreSelectableDatesData) => {
this.selectableDatesData = moreSelectableDatesData;
if (selectableDatesData?.days?.length > 0) {
this.selectedDate = selectableDatesData.days[0].date;
}
this.setDisplayWaitList();
});
}, 50);
@ -424,18 +429,15 @@ export default {
// prettier-ignore
{
const log = getQuerystringParameter(queryStrings.LOG);
if (log === "true" || !preReqResult) {
console.log("------------- schedule.vue pagePrereqs start -----------------");
console.log(new Date() + " store.getters.order.serviceLocation.zipCode: " + serviceLocation.zipCode);
console.log(new Date() + " store.getters.order.serviceLocation.zipCodeCtu: " + serviceLocation.zipCodeCtu);
console.log(new Date() + " store.getters.order.serviceLocation.appointmentType: " + serviceLocation.appointmentType);
console.log(new Date() + " store.getters.order.serviceLocation.provider.providerNumber: " + serviceLocation.provider.providerNumber);
console.log(new Date() + " store.getters.payment.isInsurance: " + store.getters.payment?.isInsurance);
console.log(new Date() + " store.getters.order.damage.isRepair: " + store.getters.order.damage.isRepair);
console.log(new Date() + " store.getters.order.lineItems.glassParts: " + JSON.stringify(store.getters.order.lineItems?.glassParts));
console.log("------------- schedule.vue pagePrereqs end -------------------");
}
debugLog("--- schedule.vue pagePrereqs start ---", null, !preReqResult);
debugLog("store.getters.order.serviceLocation.zipCode:", serviceLocation.zipCode, !preReqResult);
debugLog("store.getters.order.serviceLocation.zipCodeCtu:", serviceLocation.zipCodeCtu, !preReqResult);
debugLog("store.getters.order.serviceLocation.appointmentType:", serviceLocation.appointmentType, !preReqResult);
debugLog("store.getters.order.serviceLocation.provider.providerNumber:", serviceLocation.provider?.providerNumber, !preReqResult);
debugLog("store.getters.payment.isInsurance:", store.getters.payment?.isInsurance, !preReqResult);
debugLog("store.getters.order.damage.isRepair:", store.getters.order.damage?.isRepair, !preReqResult);
debugLog("store.getters.order.lineItems.glassParts:", store.getters.order.lineItems?.glassParts, !preReqResult);
debugLog("--- schedule.vue pagePrereqs end ---", null, !preReqResult);
}
return preReqResult;
},
@ -532,7 +534,10 @@ export default {
}
},
backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
forwardButtonAction() {
this.updateSupportingItems();
@ -581,7 +586,10 @@ export default {
);
}
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.pageName
);
},
setDisplayWaitList() {
if (

View file

@ -11,7 +11,8 @@
v-model="selectedValue"
:suppressError="suppressError"
:validationRules="validationRules"
isRequired />
isRequired
:labelBold="labelBold" />
</div>
</transition>
</template>
@ -34,6 +35,12 @@ export default {
isServiceableInshop: Boolean,
isServiceableDropoff: Boolean,
mobileFeeApplies: Boolean,
labelBold: {
type: Boolean,
required: false,
default: false,
},
zipCode: String,
},
computed: {
questionText() {
@ -47,6 +54,7 @@ export default {
const shouldShowInshop = this.isServiceableInshop;
const shouldShowDropoff =
this.isServiceableDropoff && !this.$store.getters.damage.isRepair;
const zipCode = this.zipCode;
var answers = this.answersFromCms
? this.answersFromCms.filter((answer) => {
@ -87,12 +95,15 @@ export default {
watch: {
answersToDisplay: {
handler(newValue) {
// If there is only one option to display and that option is 'Mobile' then select it
if (
newValue.length == 1 &&
newValue.findIndex((answer) => answer.Name == "Mobile") != -1
) {
this.selectedValue = "Mobile";
// If there is only one option to display and that option is 'Mobile' or 'Inshop' then select it
if (newValue.length == 1) {
const name = newValue[0].Name;
if (
name === AppointmentTypeStrings.MOBILE ||
name === AppointmentTypeStrings.IN_SHOP
) {
this.selectedValue = name;
}
}
},
immediate: true,
@ -100,14 +111,14 @@ export default {
isMobileOnly: {
handler(newValue) {
if (newValue) {
this.selectedValue = "Mobile";
this.selectedValue = AppointmentTypeStrings.MOBILE;
}
},
},
isInshopOnly: {
handler(newValue) {
if (newValue) {
this.selectedValue = "Inshop";
this.selectedValue = AppointmentTypeStrings.IN_SHOP;
}
},
},

View file

@ -8,6 +8,7 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
}
const zipCodeData = await getZipCodeData(serviceZipCode);
const providerData = await getShopProviderData(serviceZipCode);
// Get the Mobile Fee Part
const mobileFeePart = await baseMixin.methods.dispatchStoreActionWithLogging(
@ -15,6 +16,7 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
{
serviceZipCode: serviceZipCode,
serviceZipCodeCtu: zipCodeData.zipCodeCtu,
mobileProviderNumber: providerData.data.mobileProviderNumber,
},
pageNameToLog,
false

View file

@ -91,6 +91,7 @@ const mockStoreActionPriceOrderItemsAndSaveServerData =
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
const mockStoreActionGetServiceabilityDetails = storeActions.GET_SERVICEABILITY_DETAILS;
const mockStoreActionGetShopTimeSlots = storeActions.GET_SHOP_TIME_SLOTS;
const mockStoreActionGetProviders = storeActions.GET_PROVIDERS;
const mockGetShopTimeSlotsGoodAvailability = {
data: {
@ -236,6 +237,15 @@ jest.mock("@/mixins/base-mixin.js", () => ({
return Promise.resolve(mockGetShopTimeSlotsLowAvailability);
}
if (actionName === mockStoreActionGetProviders) {
return Promise.resolve({
data: {
mobileProviderNumber: "001820",
shopProviders: [{}],
},
});
}
}),
},
}));

View file

@ -189,41 +189,7 @@ describe("mobile-location-modal-questions.vue", () => {
expect(wrapper.vm.internalModel.serviceZipCode).toEqual("55555");
});
it("Should open the modal when the 'Enter your service address' link is clicked", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
streetAddress: "555 Some St",
apartmentNumberOrBusinessName: "Apt 1",
city: "Funkytown",
state: "OH",
zipCode: "55555",
},
isVehicleProtected: true,
serviceZipCode: "55555",
};
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
modelValue: mobileLocationQuestions,
},
mountOptions: {
attachTo: document.body,
},
});
wrapper.vm.$refs.MobileLocationModalWidget.openModal = jest.fn();
const mobileLocationLink = wrapper.findComponent({ ref: "mobileLocationLink" });
// // Act
await mobileLocationLink.trigger("click-event");
// // Assert
expect(wrapper.vm.$refs.MobileLocationModalWidget.openModal).toHaveBeenCalled();
});
it("Should emit setMobileLocationAndProceed on setMobileLocation for a valid address and vehicle protection answer", async () => {
it("Should emit setMobileLocation on setMobileLocation for a valid address and vehicle protection answer", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
@ -258,7 +224,6 @@ describe("mobile-location-modal-questions.vue", () => {
},
attachTo: document.body,
});
wrapper.vm.$refs.MobileLocationModalWidget.closeModal = jest.fn();
// Act
wrapper.vm.internalModel = newMobileLocationQuestions;
@ -266,13 +231,13 @@ describe("mobile-location-modal-questions.vue", () => {
await wrapper.vm.setMobileLocation();
// Assert
expect(wrapper.emitted()["setMobileLocationAndProceed"]).toBeTruthy();
expect(wrapper.emitted()["setMobileLocationAndProceed"][0][0]).toStrictEqual(
expect(wrapper.emitted()["setMobileLocation"]).toBeTruthy();
expect(wrapper.emitted()["setMobileLocation"][0][0]).toStrictEqual(
newMobileLocationQuestions
);
});
it("Should not emit setMobileLocationAndProceed on setMobileLocation for an invalid address zip code", async () => {
it("Should not emit setMobileLocation on setMobileLocation for an invalid address zip code", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
@ -307,15 +272,13 @@ describe("mobile-location-modal-questions.vue", () => {
},
attachTo: document.body,
});
wrapper.vm.$refs.MobileLocationModalWidget.closeModal = jest.fn();
wrapper.vm.resetModalButtonStyle = jest.fn();
// Act
wrapper.vm.internalModel = newMobileLocationQuestions;
await wrapper.vm.setMobileLocation();
// Assert
expect(wrapper.emitted("setMobileLocationAndProceed")).not.toBeTruthy();
expect(wrapper.emitted("setMobileLocation")).not.toBeTruthy();
});
it("Should display invalid zip alert for invalid address zip inputs", async () => {

View file

@ -1,52 +1,48 @@
<template>
<div class="mobile-location-questions">
<div class="text-center" :id="componentId">
<label
for="mobileLocationLinkPromptId"
:aria-label="mobileLocationLinkPromptText"
class="form-label fw-bold w-100 ps-4 pe-4 pt-4 text-black"
v-html="mobileLocationLinkPromptText"></label>
<div class="update-mobile-location-text-link">
<textLink
ref="mobileLocationLink"
id="mobileLocationLinkPromptId"
linkType="text"
:text="mobileLocationLinkText"
href="#!"
@click-event="openModal" />
<transition name="fade" mode="out-in">
<div class="mobile-location-questions">
<div class="text-center" :id="componentId">
<label
for="mobileLocationLinkPromptId"
:aria-label="mobileLocationLinkPromptText"
class="form-label fw-bold w-100 ps-4 pe-4 pt-4 text-black"
v-html="mobileLocationLinkPromptText"></label>
<div class="update-mobile-location-text-link">
<textLink
ref="mobileLocationLink"
id="mobileLocationLinkPromptId"
linkType="text"
:text="mobileLocationLinkText"
href="#!"
@click-event="toggleMobileLocation" />
</div>
<div v-show="errorMessage" class="row my-1 form-test-error">
<span
class="d-inline-flex small mt-0 center-error-message"
aria-atomic="true"
aria-live="polite">
{{ errorMessage }}
</span>
</div>
<textBlock
v-if="mobileFeeApplies"
:customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" />
</div>
<div v-show="errorMessage" class="row my-1 form-test-error">
<span
class="d-inline-flex small mt-0 center-error-message"
aria-atomic="true"
aria-live="polite">
{{ errorMessage }}
</span>
</div>
<textBlock
v-if="mobileFeeApplies"
:customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" />
</div>
<modal
:ref="modalName"
:headerText="modalHeaderText"
:footerButtonText="modalFooterText"
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
@isModalOpened="setModalStatus"
@footer-button-event="setMobileLocation">
<template v-if="isModalOpened">
<div v-if="isMobileLocationOpened" class="address-questions-container">
<div v-html="headerText" class="HeaderText" />
<addressQuestions
ref="addressQuestions"
v-model="internalModel.addressQuestions"
captureApartmentNumberOrBusinessName="true"
preserveCityAndStateOnReset="true" />
preserveCityAndStateOnReset="true"
labelBold="true" />
<vehicleProtectedQuestion
ref="vehicleProtectedQuestion"
v-model="internalModel.isVehicleProtected"
cmsWidgetName="VehicleProtectedQuestionWidget" />
cmsWidgetName="VehicleProtectedQuestionWidget"
labelBold="true" />
<textBlock cmsWidgetName="WorkspaceRequirementsWidget" typeStyle="caption" />
<alert
ref="alertInvalidZip"
@ -62,16 +58,14 @@
cmsWidgetName="AlertMismatchStateAndZipWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
</template>
</modal>
</div>
</div>
</div>
</transition>
</template>
<script>
// Components
import textLink from "@/ux-components/text-link/text-link";
import textBlock from "@/digital-components/text-block/text-block";
import modal from "@/digital-components/modal/modal";
import alert from "@/ux-components/alert/alert";
import addressQuestions from "@/fmg-components/address-questions/address-questions";
import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location-modal-questions/vehicle-protected-question/vehicle-protected-question";
@ -96,13 +90,14 @@ export default {
"updated-mobile-fee-part",
"updated-contains-military-base",
"updated-bill-to-account-number",
"setMobileLocationAndProceed",
"setMobileLocation",
"set-mobile-location-invalid",
],
data() {
return {
internalModel: deepClone(this.modelValue),
displayInvalidZipAlert: false,
isModalOpened: false,
isMobileLocationOpened: false,
displayMismatchStateAndZipAlert: false,
};
},
@ -188,23 +183,29 @@ export default {
this.mobileFeePart.kitPrice
);
},
modalName() {
return "MobileLocationModalWidget";
},
modalHeaderText() {
headerText() {
return this.getCmsContent(this.modalWidgetName, "HeaderText");
},
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, "FooterText");
},
modal() {
return this.$refs[this.modalName];
},
addressModel: {
get: function () {
return this.modelValue.addressQuestions;
},
},
isMobileAddressAvailable: {
get: function () {
return (
this.addressModel.streetAddress &&
this.addressModel.streetAddress !== "" &&
this.addressModel.city &&
this.addressModel.city !== "" &&
this.addressModel.state &&
this.addressModel.state !== "" &&
this.addressModel.zipCode &&
this.addressModel.zipCode !== "" &&
this.internalModel.isVehicleProtected !== null
);
},
},
},
methods: {
isMobileAddressComplete() {
@ -220,24 +221,14 @@ export default {
this.internalModel.isVehicleProtected !== null
);
},
openModal() {
this.modal.openModal();
getIsMobleLocationOpened() {
if (this.isServiceAddressFromStoreAvailable) {
return false;
}
return true;
},
onModalOpened() {
this.internalModel = deepClone(this.modelValue);
},
setModalStatus(isOpened) {
this.isModalOpened = isOpened;
},
closeModal() {
this.modal.closeModal();
},
onModalClosed() {
this.resetAlerts();
this.internalModel = deepClone(this.modelValue);
},
resetModalButtonStyle() {
this.modal.resetButtonStyle();
setMobileLocationInvalid(isFormInvalid) {
this.$emit("set-mobile-location-invalid", isFormInvalid);
},
resetAlerts() {
this.displayInvalidZipAlert = false;
@ -246,6 +237,10 @@ export default {
async setMobileLocation() {
this.resetAlerts();
// Validate the Zip Code
if (this.internalModel.addressQuestions.zipCode === "") {
this.setMobileLocationInvalid(true);
return;
}
const zipCodeData = await this.getZipCodeData(
this.internalModel.addressQuestions.zipCode,
"service-location"
@ -253,11 +248,11 @@ export default {
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
this.setMobileLocationInvalid(true);
return;
} else if (zipCodeData.state != this.internalModel.addressQuestions.state) {
this.displayMismatchStateAndZipAlert = true;
this.resetModalButtonStyle();
this.setMobileLocationInvalid(true);
return;
} else if (
this.internalModel.addressQuestions.zipCode !==
@ -296,10 +291,21 @@ export default {
this.$emit("updated-bill-to-account-number", billToAccountNumber);
}
// update the page level model
this.$emit("setMobileLocationAndProceed", this.internalModel);
this.$emit("setMobileLocation", this.internalModel);
this.setMobileLocationInvalid(false);
},
toggleMobileLocation() {
this.isMobileLocationOpened = !this.isMobileLocationOpened;
},
},
watch: {
internalModel: {
handler() {
this.setMobileLocation();
},
deep: true,
},
modelValue: {
handler(newValue) {
this.internalModel = deepClone(newValue);
@ -308,15 +314,11 @@ export default {
deep: true,
},
},
unmounted() {
if (this.isModalOpened) this.closeModal();
},
components: {
textLink,
modal,
addressQuestions,
vehicleProtectedQuestion,
textBlock,
textLink,
alert,
},
};
@ -327,28 +329,34 @@ export default {
.center-error-message {
justify-content: center !important;
}
}
.update-zip-text-link::before {
content: "";
display: inline-block;
width: 13px;
height: 16px;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.49635 1.00142e-07C5.64734 -0.000153295 4.80722 0.175918 4.0274 0.517444C3.24757 0.858969 2.54443 1.35877 1.96099 1.98626C0.765713 3.27588 0.0999756 4.98141 0.0999756 6.75394C0.0999756 8.52646 0.765713 10.232 1.96099 11.5216L5.98324 15.777C6.04954 15.8475 6.12918 15.9036 6.21736 15.9419C6.30555 15.9802 6.40045 16 6.49635 16C6.59225 16 6.68716 15.9802 6.77534 15.9419C6.86353 15.9036 6.94317 15.8475 7.00946 15.777L11.0317 11.52C12.2391 10.2383 12.909 8.52914 12.8999 6.75394C12.9094 4.97818 12.2394 3.26832 11.0317 1.98626C10.4481 1.35899 9.74493 0.859347 8.96514 0.517839C8.18535 0.176331 7.34532 0.000130509 6.49635 1.00142e-07V1.00142e-07ZM6.49635 9.13131C6.02507 9.13131 5.56437 8.98913 5.17251 8.72275C4.78065 8.45637 4.47524 8.07776 4.29488 7.63479C4.11453 7.19181 4.06734 6.70438 4.15928 6.23412C4.25123 5.76387 4.47817 5.33191 4.81142 4.99287C5.14467 4.65384 5.56925 4.42295 6.03148 4.32941C6.49371 4.23587 6.97282 4.28388 7.40823 4.46736C7.84364 4.65085 8.21579 4.96157 8.47762 5.36023C8.73945 5.7589 8.87921 6.2276 8.87921 6.70707C8.87921 7.34974 8.62837 7.96611 8.18185 8.4207C7.73532 8.87528 7.12964 9.13088 6.49794 9.13131H6.49635Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
background-size: contain;
vertical-align: middle;
margin-right: 0.5em;
}
.update-zip-text-link::before {
content: "";
display: inline-block;
width: 13px;
height: 16px;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.49635 1.00142e-07C5.64734 -0.000153295 4.80722 0.175918 4.0274 0.517444C3.24757 0.858969 2.54443 1.35877 1.96099 1.98626C0.765713 3.27588 0.0999756 4.98141 0.0999756 6.75394C0.0999756 8.52646 0.765713 10.232 1.96099 11.5216L5.98324 15.777C6.04954 15.8475 6.12918 15.9036 6.21736 15.9419C6.30555 15.9802 6.40045 16 6.49635 16C6.59225 16 6.68716 15.9802 6.77534 15.9419C6.86353 15.9036 6.94317 15.8475 7.00946 15.777L11.0317 11.52C12.2391 10.2383 12.909 8.52914 12.8999 6.75394C12.9094 4.97818 12.2394 3.26832 11.0317 1.98626C10.4481 1.35899 9.74493 0.859347 8.96514 0.517839C8.18535 0.176331 7.34532 0.000130509 6.49635 1.00142e-07V1.00142e-07ZM6.49635 9.13131C6.02507 9.13131 5.56437 8.98913 5.17251 8.72275C4.78065 8.45637 4.47524 8.07776 4.29488 7.63479C4.11453 7.19181 4.06734 6.70438 4.15928 6.23412C4.25123 5.76387 4.47817 5.33191 4.81142 4.99287C5.14467 4.65384 5.56925 4.42295 6.03148 4.32941C6.49371 4.23587 6.97282 4.28388 7.40823 4.46736C7.84364 4.65085 8.21579 4.96157 8.47762 5.36023C8.73945 5.7589 8.87921 6.2276 8.87921 6.70707C8.87921 7.34974 8.62837 7.96611 8.18185 8.4207C7.73532 8.87528 7.12964 9.13088 6.49794 9.13131H6.49635Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
background-size: contain;
vertical-align: middle;
margin-right: 0.5em;
}
.address-questions {
margin-top: 0em;
}
.update-mobile-location-text-link {
white-space: pre-line;
}
.address-questions-container {
margin-top: 1rem;
}
.update-mobile-location-text-link {
white-space: pre-line;
}
.text-black {
color: $black;
.text-black {
color: $black;
}
.HeaderText {
font-size: 1rem;
font-weight: 600;
line-height: 1.625rem;
color: $black;
text-align: center;
}
}
</style>

View file

@ -7,7 +7,8 @@
textPosition="text-center"
v-model="selectedValue"
validationRules="option-required"
isRequired />
isRequired
:labelBold="labelBold" />
</template>
<script>
@ -27,6 +28,11 @@ export default {
isVehicleProtected: Boolean,
},
cmsWidgetName: String,
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
components: {
buttonQuestion,

View file

@ -343,7 +343,6 @@ describe("service-location.vue", () => {
zipCode: "43054",
},
isVehicleProtected: true,
isMobileSelected: true,
};
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
@ -356,7 +355,6 @@ describe("service-location.vue", () => {
zipCode: "61606",
},
isVehicleProtected: null,
isMobileSelected: false,
};
// Act
@ -459,7 +457,6 @@ describe("service-location.vue", () => {
zipCode: "",
},
isVehicleProtected: null,
isMobileSelected: false,
};
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
const newMobileLocationQuestions = {
@ -471,15 +468,14 @@ describe("service-location.vue", () => {
zipCode: "43054",
},
isVehicleProtected: true,
isMobileSelected: true,
};
wrapper.vm.closeModalAction = jest.fn();
//wrapper.vm.closeModalAction = jest.fn();
// Act
// Trigger the event
await mobileLocationQuestionsComponent.vm.$emit(
"setMobileLocationAndProceed",
"setMobileLocation",
newMobileLocationQuestions
);
@ -521,7 +517,7 @@ describe("service-location.vue", () => {
zipCode: "",
},
isVehicleProtected: null,
isMobileSelected: false,
isMobileSelectedAndModalClosed: false,
mobileFeePart: null,
};
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
@ -535,7 +531,7 @@ describe("service-location.vue", () => {
zipCode: "43081",
},
isVehicleProtected: true,
isMobileSelected: true,
isMobileSelectedAndModalClosed: true,
mobileFeePart: null,
};
@ -556,7 +552,7 @@ describe("service-location.vue", () => {
// Trigger the event
await mobileLocationQuestionsComponent.vm.$emit(
"setMobileLocationAndProceed",
"setMobileLocation",
newMobileLocationQuestions
);
@ -599,15 +595,12 @@ describe("service-location.vue", () => {
zipCode: "43081",
},
isVehicleProtected: "YesAnswer",
isMobileSelected: true,
isMobileSelectedAndModalClosed: true,
};
wrapper.vm.closeModalAction = jest.fn();
// Act
mobileLocationQuestionsComponent.vm.$emit(
"setMobileLocationAndProceed",
mobileLocationQuestions
);
mobileLocationQuestionsComponent.vm.$emit("setMobileLocation", mobileLocationQuestions);
// Assert
expect(wrapper.vm.selectedAppointmentType).toStrictEqual("Mobile");

View file

@ -79,10 +79,12 @@
:isServiceableDropoff="isServiceableDropoff"
:isDisplayed="isAppointmentTypeDisplayed"
:mobileFeeApplies="mobileFeeApplies"
:zipCode="zipCode"
ref="appointmentTypeQuestion"
groupName="appointmentTypeQuestion"
cmsWidgetName="AppointmentTypeQuestionWidget"
validationRules="option-required" />
validationRules="option-required"
labelBold="true" />
</div>
</div>
<div class="row justify-content-center">
@ -102,7 +104,8 @@
ref="mobileLocationQuestions"
linkWidgetName="MobileLocationLinkWidget"
modalWidgetName="MobileLocationModalWidget"
@setMobileLocationAndProceed="setMobileLocationQuestions" />
@setMobileLocation="setMobileLocationQuestions"
@set-mobile-location-invalid="setMobileLocationInValid" />
<shopQuestion
ref="shopQuestion"
@ -111,15 +114,14 @@
:selectedAppointmentType="selectedAppointmentType"
:shopProviderData="shopProviderData"
:isDisplayed="isShopQuestionDisplayed"
cmsWidgetName="ShopQuestionWidget"
:isInshopOnly="isInshopOnly" />
cmsWidgetName="ShopQuestionWidget" />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
:isForwardActionDisabled="!meta.valid || isForwardActionDisabled"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
@ -173,12 +175,12 @@ const MOBILE_FEE_PART_TYPE = "MOBILE FEE";
// DEFINE VALIDATION RULES
defineRule("mobile-location-required", (value) => {
if (
value.isMobileSelected &&
(!value.addressQuestions.streetAddress ||
!value.addressQuestions.city ||
!value.addressQuestions.state ||
!value.addressQuestions.zipCode ||
!value.isVehicleProtected)
!value.isVehicleProtected) &&
value.isMobileSelected
) {
return errorMessages.MOBILE_LOCATION_REQUIRED;
}
@ -212,11 +214,12 @@ export default {
billToAccountNumber: null,
shopProviderData: null,
navigatingForward: false,
isMobileAddressValid: true,
};
},
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const zipCodeDataPromise = getZipCodeData(serviceZipCode);
@ -302,7 +305,6 @@ export default {
zipCode: this.zipCode,
},
isVehicleProtected: this.isVehicleProtected,
isMobileSelected: this.selectedAppointmentType === "Mobile",
};
},
},
@ -416,10 +418,8 @@ export default {
isNoComp() {
return store.getters.order.policy.isNoComp;
},
isInshopOnly() {
return (
this.isServiceableInshop && !this.isServiceableMobile && !this.isServiceableDropoff
);
isForwardActionDisabled() {
return this.displayNoShopsAlert || !this.isMobileAddressValid;
},
},
methods: {
@ -523,10 +523,7 @@ export default {
}
if (this.isServiceableMobile) {
this.setMobileLocation(newValue);
//proceed to next page
this.forwardButtonAction();
} else {
this.closeModalAction("mobileLocationQuestions");
await getZipCodeData(newZipCode).then((zipCodeData) => {
this.serviceZipCodeQuestion = {
state: zipCodeData.state,
@ -614,7 +611,7 @@ export default {
} else {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.$route
this.pageName
);
}
},
@ -694,7 +691,7 @@ export default {
},
async forwardButtonAction() {
this.navigatingForward = true;
var ctu = this.zipCodeCtu;
if (
this.selectedAppointmentType == AppointmentTypeStrings.MOBILE &&
this.selectedProvider &&
@ -705,18 +702,13 @@ export default {
this.selectedProvider.address.state = null;
this.selectedProvider.address.zipCode = null;
this.selectedProvider.address.zipCodeCtu = null;
}
var ctu = this.zipCodeCtu;
if (this.selectedAppointmentType == AppointmentTypeStrings.IN_SHOP) {
} else {
// if we have a provider.zipCodeCtu that's different than the servicelocation.zipCodeCtu then they
// may have selected a shop in a different ctu. change the servicelocation zip/ctu if different
if (this.zipCodeCtu != this.selectedProvider.address.zipCodeCtu) {
ctu = this.selectedProvider.address.zipCodeCtu;
}
this.city = null;
this.streetAddress = null;
this.resetMobileLocation();
}
await this.dispatchStoreAction(
@ -754,13 +746,20 @@ export default {
this.updateAndSaveSupportingItems();
this.updateAndSaveIsMSRFeeApplicable();
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.pageName
);
},
openModalAction(modalName) {
this.$refs[modalName].openModal();
setMobileLocationInValid(isMobileLocationInValid) {
this.isMobileAddressValid = !isMobileLocationInValid;
},
closeModalAction(modalName) {
this.$refs[modalName].closeModal();
displayMobileAddressQuestion(openMobileLocation) {
var mobileLocationQuestionsRef = this.$refs.mobileLocationQuestions;
var isMobileAddressComplete = mobileLocationQuestionsRef.isMobileAddressComplete;
if (isMobileAddressComplete) {
mobileLocationQuestionsRef.isMobileLocationOpened = openMobileLocation;
}
},
},
watch: {
@ -773,8 +772,10 @@ export default {
this.selectedProvider = new Provider(
this.shopProviderData.mobileProviderNumber
);
this.displayMobileAddressQuestion(true);
} else {
this.selectedProvider = new Provider();
this.displayMobileAddressQuestion(false);
}
});
}
@ -786,12 +787,10 @@ export default {
this.selectedProvider = new Provider(
this.shopProviderData.mobileProviderNumber
);
var mobileLocationQs = this.$refs.mobileLocationQuestions;
if (!mobileLocationQs.isMobileAddressComplete()) {
mobileLocationQs.openModal();
}
this.displayMobileAddressQuestion(true);
} else {
this.selectedProvider = new Provider();
this.displayMobileAddressQuestion(false);
}
},
},

View file

@ -77,7 +77,6 @@ export default {
cmsWidgetName: String,
validationRules: String,
isDisplayed: Boolean,
isInshopOnly: Boolean,
},
computed: {
questionText() {
@ -234,13 +233,9 @@ export default {
// nothing to do with this component. We could mitigate this by showing / hiding this component with v-if but that messes
// up the component initialization on page load.
async handler() {
//We do not need to run this handler in case of Inshop only else it will ended running twice function getNextShopsFromList and will cause pagination
if (!this.isInshopOnly) {
this.resetAnswers();
await nextTick();
this.getNextShopsFromList();
}
await nextTick();
this.resetAnswers();
this.getNextShopsFromList();
},
},
shopProviders: {

View file

@ -2,12 +2,12 @@
import serviceZip from "@/layouts/service-zip/service-zip";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { mount, shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper";
import baseMixin from "@/mixins/base-mixin";
import store from "@/store";
import router from "@/router";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { experimentSettings } from "../../constants/experiments";
@ -251,20 +251,6 @@ describe("service-zip.vue", () => {
expect(wrapper.vm.serviceZipCode).toEqual("11111");
expect(wrapper.vm.emailOrSms).toEqual("builddigitaltest@safelite.com");
});
test("Zip in querystring -> pushed to data", () => {
// Arrange
// no changes to store
applyMockStoreDataToGetters();
// Act
const wrapper = setupMocks({
customZipQuery: "11111",
});
// Assert
expect(wrapper.vm.serviceZipCode).toEqual("11111");
});
});
describe("Navigation", () => {
@ -285,7 +271,7 @@ describe("service-zip.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN,
wrapper.vm.$route
wrapper.vm.$route.name
);
});
@ -301,7 +287,7 @@ describe("service-zip.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
navigationScenarios.CLICKED_BACK,
wrapper.vm.$route
wrapper.vm.$route.name
);
});
});
@ -326,7 +312,7 @@ describe("service-zip.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toBeCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
wrapper.vm.$route
wrapper.vm.$route.name
);
expect(wrapper.vm.navigateForwardWithSingleCarMatch).not.toBeCalled();
});
@ -486,7 +472,7 @@ describe("service-zip.vue", () => {
});
function setupMocks({ customMountOptions, customZipQuery, customZipDataResponse }) {
const route = { query: { fmgPage: "service-zip" }, params: {} };
const route = { name: "service-zip" };
if (customZipQuery) {
route.query.zipcode = customZipQuery;
}
@ -499,6 +485,7 @@ function setupMocks({ customMountOptions, customZipQuery, customZipDataResponse
mountOptions.global.mocks["$store"] = store;
mountOptions.global.mocks["$router"] = router;
mountOptions.global.mocks.pageName = route.name;
mountOptions.mixins = [
{
methods: {

View file

@ -95,7 +95,8 @@ import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import baseMixin from "@/mixins/base-mixin.js";
import { queryStrings } from "@/constants/query-strings";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
// Define Validation Rules
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -115,7 +116,7 @@ export default {
mixins: [vinPagesMixin],
data() {
return {
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
serviceZipCode: this.getZipFromStore() ?? consumeQueryFromStash(queryStrings.ZIP_CODE),
emailOrSms: this.getEmailOrSmsFromStore(),
carId: this.getCarIdfromStore(),
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
@ -127,7 +128,7 @@ export default {
async beforeRouteEnter(to, from, next) {
//Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
//Settle promises and get results
const promiseResultMap = [
@ -214,12 +215,12 @@ export default {
if (skipVin) {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN,
this.$route
this.pageName
);
} else {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.$route
this.pageName
);
}
},
@ -242,7 +243,7 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_PAGE_DATA,
{
page: fmgPageValues.EMAIL_SMS_PAGES,
page: routeData.SERVICE_ZIP.name,
data: { emailOrSmsValue: this.emailOrSms },
},
false
@ -317,7 +318,7 @@ export default {
await saveSession({ pageNameToLog: "service-zip" });
return this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
this.$route
this.pageName
);
} else {
// if we're skipping the vin-lookup but it's not a repair, we still need to get the parts

View file

@ -16,7 +16,6 @@ import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import { validate } from "vee-validate";
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
import { routerParams } from "@/router/router-constants/router-params";
import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper";
// Mock our module for promises.
@ -310,11 +309,11 @@ describe("vehicle-damage.vue", () => {
describe("alert", () => {
test("when displayVehicleChangeAlert router params is true, the alert: 'vehicleChangeAlert' should be rendered", () => {
// Arrange & Act
window.history.pushState({ displayVehicleChangeAlert: "true" }, "", "");
const { wrapper } = setupMocks({
mountOptionsMockData: {
route: {
state: { displayVehicleChangeAlert: true },
name: "vehicle-damage",
query: { displayVehicleChangeAlert: true },
},
},
});
@ -328,8 +327,9 @@ describe("vehicle-damage.vue", () => {
const { wrapper } = setupMocks({
mountOptionsMockData: {
route: {
state: {
[routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false,
name: "vehicle-damage",
query: {
displayVehicleChangeAlert: false,
},
},
},
@ -345,8 +345,9 @@ describe("vehicle-damage.vue", () => {
const { wrapper } = setupMocks({
mountOptionsMockData: {
route: {
params: {
[routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: undefined,
name: "vehicle-damage",
query: {
displayVehicleChangeAlert: undefined,
},
},
},
@ -852,9 +853,8 @@ function setupMocks({ pageHeaderWidgetHeaderText, mountOptionsMockData, funnelCo
navigateWithSaving: jest.fn(),
},
route: {
state: {
[routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false,
},
name: "vehicle-damage",
query: {},
},
store: {
getters: {

View file

@ -97,7 +97,6 @@ import { errorMessages } from "@/constants/error-messages";
import { damageLocationsCms } from "@/constants/damage-locations-cms.js";
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { routerParams } from "@/router/router-constants/router-params";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
@ -110,7 +109,7 @@ export default {
name: "vehicle-damage",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const damageOptionsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_DAMAGE_OPTIONS,
@ -249,7 +248,10 @@ export default {
backButtonAction() {
// route to move backwards
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
},
getDamageLocationsFromStore() {
@ -455,34 +457,26 @@ export default {
if (skipVin) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_SKIP_VIN,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false }
this.pageName
);
} else {
if (store.getters.damage.isRepair) {
this.$router.navigateWithSaving(
this.navigationScenarios
.CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false }
this.pageName
);
} else {
if (store.getters.vehicle.vin) {
this.$router.navigateWithSaving(
this.navigationScenarios
.CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false }
this.pageName
);
} else {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false }
this.pageName
);
}
}
@ -492,23 +486,17 @@ export default {
else if (store.getters.vehicle.vin) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_VIN,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false }
this.pageName
);
} else if (skipVin) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_SKIP_VIN,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false }
this.pageName
);
} else {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false }
this.pageName
);
}
},
@ -637,7 +625,7 @@ export default {
);
},
shouldDisplayVehicleChangeAlert() {
return getBoolFromString(window.history.state.displayVehicleChangeAlert);
return getBoolFromString(this.$route?.query?.displayVehicleChangeAlert);
},
shouldHideBackButton() {
return this.$store.getters.requiresVerifiedRedirecting;

View file

@ -154,7 +154,7 @@ export default {
},
PartDataFromApi() {
return this.$store.getters.pageData(this.$route.query.fmgPage) ?? {};
return this.$store.getters.pageData(this.pageName) ?? {};
},
},
methods: {

View file

@ -12,7 +12,7 @@ import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
@ -84,11 +84,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: store.getters,
@ -123,11 +122,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: store.getters,
@ -161,11 +159,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: store.getters,
@ -205,11 +202,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: {
@ -255,7 +251,7 @@ describe("vehicle-parts.vue", () => {
//Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
wrapper.vm.$route
wrapper.vm.$route.name
);
});
@ -268,11 +264,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: {
@ -302,7 +297,7 @@ describe("vehicle-parts.vue", () => {
//Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
wrapper.vm.$route
wrapper.vm.$route.name
);
});
@ -356,11 +351,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: store.getters,
@ -384,7 +378,7 @@ describe("vehicle-parts.vue", () => {
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalled();
});
test("ForwardButtonAction triggers a router.navigateWithSaving change if there are capability questions", async () => {
@ -417,11 +411,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: store.getters,
@ -451,7 +444,7 @@ describe("vehicle-parts.vue", () => {
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalled();
});
test("ForwardButtonAction saves selected parts to store if no molding or capability questions", async () => {
@ -468,11 +461,10 @@ describe("vehicle-parts.vue", () => {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
route: {
query: {
fmgPage: "vehicle-parts",
},
name: "vehicle-parts",
},
store: {
getters: store.getters,
@ -544,6 +536,7 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
wrapper.vm.pageName = "vehicle-parts";
// wrapper.vm.$refs.onSubmit = jest.fn();
// wrapper.vm.$refs.onInvalidSubmit = jest.fn();

View file

@ -69,7 +69,7 @@ import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-ques
// Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { Form } from "vee-validate";
import store from "@/store";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
@ -81,7 +81,7 @@ export default {
mixins: [vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
{
@ -162,7 +162,7 @@ export default {
},
PartsFromApi() {
return this.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
return this.$store.getters.pageData(routeData.VEHICLE_PARTS.name);
},
RefPrefix() {
@ -172,7 +172,7 @@ export default {
methods: {
arePagePrerequisitesValid() {
// Check if isRepair is populated and if the pageData we need is here (Parts data)
const vehiclePartsFromPageData = store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
const vehiclePartsFromPageData = store.getters.pageData(routeData.VEHICLE_PARTS.name);
return (
store.getters.damage.isRepair != null &&
vehiclePartsFromPageData?.partsOrQuestions?.some((part) => part?.glassName)

View file

@ -110,6 +110,11 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { applicationConfig } from "../../constants/application-config";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import {
consumeQueryFromStash,
peekQueryFromStash,
} from "@/router/methods/helpers/querystring-stash";
import { debugLog } from "@/helpers/debug-log-helper";
//define validation rules
defineRule("year-required", required(errorMessages.YEAR_REQUIRED));
@ -133,7 +138,7 @@ export default {
imageUrl: this.getImagefromStore(),
imageVifNumber: this.getImageVifNumberfromStore(),
imageVifColor: this.getImageVifColorfromStore(),
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
serviceZipCode: this.getZipFromStore() ?? peekQueryFromStash(queryStrings.ZIP_CODE),
yearOptions: [],
makeOptions: [],
modelOptions: [],
@ -144,17 +149,12 @@ export default {
},
async beforeRouteEnter(to, from, next) {
if (store.getters.applicationUser.loggingOption) {
console.log(
new Date() + "------------- vehicle.vue applicationConfig start -------------"
);
console.log(new Date() + JSON.stringify(applicationConfig));
console.log(
new Date() + "------------- vehicle.vue applicationConfig end -------------"
);
}
debugLog("------------- vehicle.vue applicationConfig start -------------");
debugLog("", applicationConfig);
debugLog("------------- vehicle.vue applicationConfig end -------------");
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
const experimentForLogging = store.getters.applicationUser.experiments.find(
(e) => e.universeName === experimentUniverses.CONCEPT_FUNNEL
);
@ -213,7 +213,7 @@ export default {
userId: getUserIdValue(),
deviceId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(),
pageName: to.query.fmgPage,
pageName: to.name,
experiment: experimentForLogging,
},
"vehicle",
@ -536,7 +536,10 @@ export default {
}
}
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.pageName
);
},
initializeYearComponent(initialData) {
this.yearOptions = initialData;

View file

@ -1,7 +1,7 @@
import { shallowMount } from "@vue/test-utils";
import vinLookup from "./vin-lookup.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
import { navigationScenarios } from "@/router/constants/navigation-scenarios.js";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { experimentSettings } from "@/constants/experiments";

View file

@ -155,11 +155,12 @@ import {
import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { routerParams } from "@/router/router-constants/router-params";
import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { queryStrings } from "@/constants/query-strings";
// DEFINE VALIDATION RULES
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -180,7 +181,7 @@ export default {
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const cmsContentPromise = fetchCmsContentForPage(to.name);
// Settle promises and get results
const promiseResultMap = [
@ -203,7 +204,7 @@ export default {
data() {
return {
vin: this.getVinFromStore(),
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
serviceZipCode: this.getZipFromStore() ?? consumeQueryFromStash(queryStrings.ZIP_CODE),
emailOrSms: this.getEmailOrSmsFromStore(),
carId: this.getCarIdfromStore(),
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
@ -247,12 +248,12 @@ export default {
if (this.$store.getters.vehicle.vin) {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK_WITH_VIN,
this.$route
this.pageName
);
} else {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.$route
this.pageName
);
}
},
@ -389,7 +390,7 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_PAGE_DATA,
{
page: fmgPageValues.EMAIL_SMS_PAGES,
page: routeData.SERVICE_ZIP.name,
data: { emailOrSmsValue: this.emailOrSms },
},
false
@ -452,7 +453,7 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_PAGE_DATA,
{
page: fmgPageValues.EMAIL_SMS_PAGES,
page: routeData.SERVICE_ZIP.name,
data: { emailOrSmsValue: this.emailOrSms },
},
false
@ -474,9 +475,7 @@ export default {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
this.pageName
);
} else {
await this.navigateForwardWithSingleCarMatch();

View file

@ -2,7 +2,7 @@ import { createApp } from "vue";
import LoadScript from "vue-plugin-load-script";
import Maska from "maska";
import App from "./App.vue";
import router from "./router";
import router from "@/router";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import analyticsMixin from "@/mixins/analytics-mixin.js";

View file

@ -27,9 +27,9 @@ import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
import { applicationConfig } from "../constants/application-config";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import router from "@/router/index.js";
import { routeData } from "@/router/constants/routes";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { Variables } from "../constants/analytics";
export default {
methods: {
@ -44,7 +44,7 @@ export default {
const submittedOrder = baseMixin.methods.getSubmittedOrder();
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const hasSubmittedOrderAtConfirmationPage =
hasSubmittedOrder && currentPageName?.toLowerCase() == fmgPageValues.CONFIRMATION;
hasSubmittedOrder && currentPageName?.toLowerCase() == routeData.CONFIRMATION.name;
const refSequenceNum = hasSubmittedOrderAtConfirmationPage
? submittedOrder.referralSequenceNumber
@ -120,6 +120,10 @@ export default {
}
},
async pushVariableToDataLayer(data) {
pushToDataLayerIfDefined(data);
},
async pushPageViewToGA() {
const currentPageName = getPageNameByQueryString();
const pageViewEvent = {
@ -306,6 +310,18 @@ export default {
} else {
payload.priceSubTotal = "";
}
// Cash Quote or Cash Price Sub Total
if (isPricingAvailable) {
const subtotal = baseMixin.methods
.getTotalPriceOfAllLineItemsAndChildParts(combinedLineItems, false)
.toFixed(2);
payload.cashPriceSubTotal = parseFloat(subtotal);
} else {
payload.cashPriceSubTotal = "";
}
//unverified (in scenarios we dont display the price)
if (
order.payment.isInsurance &&
@ -770,6 +786,9 @@ export default {
ValueToLogTypes() {
return ValueToLogTypes;
},
Variables() {
return Variables;
},
},
};

View file

@ -2,19 +2,20 @@ import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js";
import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { vehicleCategories } from "@/constants/vehicle-categories.js";
import { routerParams } from "@/router/router-constants/router-params";
import { queryStrings } from "@/constants/query-strings";
import { dynamicStrings } from "@/constants/dynamic-strings";
import { partTypeStrings } from "../constants/part-type-strings";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import { getItemsWithoutRecalParts } from "@/helpers/recal-helper";
import { debugLog } from "@/helpers/debug-log-helper";
export default {
data() {
return {
cmsContentByWidget: {},
_componentPageName: null,
};
},
methods: {
@ -95,10 +96,8 @@ export default {
let totalPrice = this.getTotalPriceOfAllLineItemsAndChildParts(lineItemsToPrice, false);
// prettier-ignore
{
if (store.getters.applicationUser.loggingOption) {
console.log(new Date() + " base-mixin getTierOnePackagePrice lineItemsToPrice: " + JSON.stringify(lineItemsToPrice));
console.log(new Date() + " base-mixin getTierOnePackagePrice totalPrice: " + totalPrice);
}
debugLog("base-mixin getTierOnePackagePrice lineItemsToPrice:", lineItemsToPrice);
debugLog("base-mixin getTierOnePackagePrice totalPrice:", totalPrice);
}
return totalPrice;
},
@ -234,9 +233,6 @@ export default {
vehicleCategories() {
return vehicleCategories;
},
routerParams() {
return routerParams;
},
queryStrings() {
return queryStrings;
},
@ -246,9 +242,15 @@ export default {
cssClassNameForCmsWidget() {
return "widget-name-" + this.cmsWidgetName;
},
pageName() {
return this._componentPageName ?? this.$route.name;
},
},
mounted() {
// Store route immutably so that components know their originating page even during page transition.
this._componentPageName = this.$route.name;
if (!store.getters.externalParameterState?.isExternalParameter) {
showFmgLoadingModal(false);
}

View file

@ -1,7 +1,7 @@
import baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { vehicleCategories } from "@/constants/vehicle-categories.js";
import { queryStrings } from "@/constants/query-strings";
import { dynamicStrings } from "@/constants/dynamic-strings";

View file

@ -1,6 +1,6 @@
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigationScenarios } from "../router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { storeActions } from "@/constants/store-actions";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin.js";
@ -53,11 +53,11 @@ export default {
},
comparePageIndices(currentPage, fmgPage) {
const orderedVehicleQuestionPages = [
fmgPageValues.PART_QUESTIONS,
fmgPageValues.VEHICLE_PARTS,
fmgPageValues.MOLDING_QUESTIONS,
fmgPageValues.CAPABILITY_QUESTIONS,
fmgPageValues.QUOTE,
routeData.PART_QUESTIONS.name,
routeData.VEHICLE_PARTS.name,
routeData.MOLDING_QUESTIONS.name,
routeData.CAPABILITY_QUESTIONS.name,
routeData.QUOTE.name,
];
return (
@ -65,10 +65,10 @@ export default {
orderedVehicleQuestionPages.indexOf(fmgPage)
);
},
currentPageComesBeforePage(currentPage = this.$route.query.fmgPage, fmgPage) {
currentPageComesBeforePage(currentPage = this.pageName, fmgPage) {
return this.comparePageIndices(currentPage, fmgPage) < 0;
},
currentPageComesAfterPage(currentPage = this.$route.query.fmgPage, fmgPage) {
currentPageComesAfterPage(currentPage = this.pageName, fmgPage) {
return this.comparePageIndices(currentPage, fmgPage) > 0;
},
setupInitialData(glass, index, alreadyAnsweredQuestions, vm) {
@ -359,7 +359,7 @@ export default {
// Can't use `this` because navigateForward is also called from vin-pages-mixin
async navigateForward(partsOrQuestions, vm) {
const self = vm ?? this;
const currentPage = self.$route.query.fmgPage;
const currentPage = self.pageName;
const hasPartQuestions = this.hasPartQuestions(partsOrQuestions);
const hasGlassLocationWithMultipleParts =
@ -369,44 +369,38 @@ export default {
if (
hasPartQuestions &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.PART_QUESTIONS)
this.currentPageComesBeforePage(currentPage, routeData.PART_QUESTIONS.name)
) {
self.$router.navigateWithSaving(
self.$router.navigateWithPageData(
self.navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
self.$route,
{},
{},
self.pageName,
{ partsOrQuestions: partsOrQuestions }
);
} else if (
hasGlassLocationWithMultipleParts &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.VEHICLE_PARTS)
this.currentPageComesBeforePage(currentPage, routeData.VEHICLE_PARTS.name)
) {
// if multiple parts on any glass
// go to vehicle-parts page and pass the partsData
self.$router.navigateWithSaving(
self.$router.navigateWithPageData(
self.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
self.$route,
{},
{},
self.pageName,
{ partsOrQuestions: partsOrQuestions }
);
} else if (
hasChildPartQuestions &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.MOLDING_QUESTIONS)
this.currentPageComesBeforePage(currentPage, routeData.MOLDING_QUESTIONS.name)
) {
// if any childpart questions
// go to molding-questions page and pass the partsData
self.$router.navigateWithSaving(
self.$router.navigateWithPageData(
self.navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
self.$route,
{},
{},
self.pageName,
{ partsOrQuestions: partsOrQuestions }
);
} else if (
hasCapabilityQuestions &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS)
this.currentPageComesBeforePage(currentPage, routeData.CAPABILITY_QUESTIONS.name)
) {
// if has capability questions
// go to capability-questions page and pass the partsData
@ -438,11 +432,9 @@ export default {
}
}
self.$router.navigateWithSaving(
self.$router.navigateWithPageData(
self.navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
self.$route,
{},
{},
self.pageName,
{ partsOrQuestions }
);
} else {
@ -477,7 +469,7 @@ export default {
} else {
self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
self.$route
self.pageName
);
}
}
@ -485,16 +477,16 @@ export default {
// Can't use `this` because navigateForward is also called from quote
async navigateBack(vm) {
const self = vm ?? this;
const currentPage = self.$route.query.fmgPage;
const currentPage = self.pageName;
const pageDataCapabilityQuestions = self.$store.getters.pageData(
fmgPageValues.CAPABILITY_QUESTIONS
routeData.CAPABILITY_QUESTIONS.name
);
const pageDataMoldingQuestions = self.$store.getters.pageData(
fmgPageValues.MOLDING_QUESTIONS
routeData.MOLDING_QUESTIONS.name
);
const pageDataVehicleParts = self.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
const pageDataVehicleParts = self.$store.getters.pageData(routeData.VEHICLE_PARTS.name);
const pageDataPartQuestions = self.$store.getters.pageData(
fmgPageValues.PART_QUESTIONS
routeData.PART_QUESTIONS.name
);
const allPartsOrQuestions = [
@ -518,28 +510,28 @@ export default {
if (
hasCapabilityQuestions &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS)
this.currentPageComesAfterPage(currentPage, routeData.CAPABILITY_QUESTIONS.name)
) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS;
} else if (
hasChildPartQuestions &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.MOLDING_QUESTIONS)
this.currentPageComesAfterPage(currentPage, routeData.MOLDING_QUESTIONS.name)
) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS;
} else if (
hasGlassLocationWithMultipleParts &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.VEHICLE_PARTS)
this.currentPageComesAfterPage(currentPage, routeData.VEHICLE_PARTS.name)
) {
backNavigationScenario =
navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE;
} else if (
hasPartQuestions &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.PART_QUESTIONS)
this.currentPageComesAfterPage(currentPage, routeData.PART_QUESTIONS.name)
) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS;
}
self.$router.navigateWithoutSaving(backNavigationScenario, self.$route);
self.$router.navigateWithoutSaving(backNavigationScenario, self.pageName);
},
},
};

View file

@ -1,11 +1,11 @@
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import { shallowMount } from "@vue/test-utils";
import { setupMocksForJsFiles, getMountOptions } from "@/helpers/unit-test-helper.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { routeData } from "@/router/constants/routes";
import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { navigationScenarios } from "../router/router-constants/navigation-scenarios";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { getters } from "@/store";
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
@ -290,13 +290,13 @@ describe("vehicle-questions-mixin", () => {
describe("currentPageComesBeforePage", () => {
const testCases = [
[fmgPageValues.PART_QUESTIONS, fmgPageValues.VEHICLE_PARTS, true],
[fmgPageValues.VEHICLE_PARTS, fmgPageValues.VEHICLE_PARTS, false],
[fmgPageValues.QUOTE, fmgPageValues.VEHICLE_PARTS, false],
[fmgPageValues.QUOTE, fmgPageValues.QUOTE, false],
[fmgPageValues.VEHICLE_PARTS, fmgPageValues.PART_QUESTIONS, false],
[fmgPageValues.PART_QUESTIONS, fmgPageValues.CAPABILITY_QUESTIONS, true],
[fmgPageValues.MOLDING_QUESTIONS, fmgPageValues.CAPABILITY_QUESTIONS, true],
[routeData.PART_QUESTIONS, routeData.VEHICLE_PARTS, true],
[routeData.VEHICLE_PARTS, routeData.VEHICLE_PARTS, false],
[routeData.QUOTE, routeData.VEHICLE_PARTS, false],
[routeData.QUOTE, routeData.QUOTE, false],
[routeData.VEHICLE_PARTS, routeData.PART_QUESTIONS, false],
[routeData.PART_QUESTIONS, routeData.CAPABILITY_QUESTIONS, true],
[routeData.MOLDING_QUESTIONS, routeData.CAPABILITY_QUESTIONS, true],
];
test.each(testCases)(
"%s comes before %s is %s",
@ -305,7 +305,10 @@ describe("vehicle-questions-mixin", () => {
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.currentPageComesBeforePage(currentPage, nextPage);
const result = wrapper.vm.currentPageComesBeforePage(
currentPage.name,
nextPage.name
);
// Assert
expect(result).toEqual(expectedResult);
@ -315,20 +318,20 @@ describe("vehicle-questions-mixin", () => {
describe("currentPageComesAfterPage", () => {
const testCases = [
[fmgPageValues.PART_QUESTIONS, fmgPageValues.VEHICLE_PARTS, false],
[fmgPageValues.VEHICLE_PARTS, fmgPageValues.VEHICLE_PARTS, false],
[fmgPageValues.QUOTE, fmgPageValues.VEHICLE_PARTS, true],
[fmgPageValues.QUOTE, fmgPageValues.QUOTE, false],
[fmgPageValues.VEHICLE_PARTS, fmgPageValues.PART_QUESTIONS, true],
[fmgPageValues.PART_QUESTIONS, fmgPageValues.CAPABILITY_QUESTIONS, false],
[fmgPageValues.MOLDING_QUESTIONS, fmgPageValues.CAPABILITY_QUESTIONS, false],
[routeData.PART_QUESTIONS, routeData.VEHICLE_PARTS, false],
[routeData.VEHICLE_PARTS, routeData.VEHICLE_PARTS, false],
[routeData.QUOTE, routeData.VEHICLE_PARTS, true],
[routeData.QUOTE, routeData.QUOTE, false],
[routeData.VEHICLE_PARTS, routeData.PART_QUESTIONS, true],
[routeData.PART_QUESTIONS, routeData.CAPABILITY_QUESTIONS, false],
[routeData.MOLDING_QUESTIONS, routeData.CAPABILITY_QUESTIONS, false],
];
test.each(testCases)("%s comes after %s is %s", (currentPage, nextPage, expectedResult) => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.currentPageComesAfterPage(currentPage, nextPage);
const result = wrapper.vm.currentPageComesAfterPage(currentPage.name, nextPage.name);
// Assert
expect(result).toEqual(expectedResult);
@ -1321,12 +1324,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -1436,12 +1437,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -1543,12 +1542,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -1698,12 +1695,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -1746,12 +1741,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -1858,12 +1851,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -2036,12 +2027,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -2095,12 +2084,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -2137,12 +2124,10 @@ describe("vehicle-questions-mixin", () => {
await wrapper.vm.navigateForward(partsOrQuestions);
// Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithPageData).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
wrapper.vm.$route,
{},
{},
wrapper.vm.$route.name,
{ partsOrQuestions }
);
});
@ -2315,7 +2300,7 @@ describe("vehicle-questions-mixin", () => {
test("current page is quote, there are no questions, and we have their vin => go to vin-lookup", async () => {
// Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE, hasVin: true });
const { wrapper } = setupMocks({ fmgPage: routeData.QUOTE.name, hasVin: true });
// Act
store.dispatch = jest.fn(() => {});
@ -2324,13 +2309,13 @@ describe("vehicle-questions-mixin", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
{ query: { fmgPage: fmgPageValues.QUOTE } }
routeData.QUOTE.name
);
});
test("current page is quote and there are capability questions => go to capability questions", async () => {
// Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE });
const { wrapper } = setupMocks({ fmgPage: routeData.QUOTE.name });
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act
@ -2340,13 +2325,13 @@ describe("vehicle-questions-mixin", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS,
{ query: { fmgPage: fmgPageValues.QUOTE } }
routeData.QUOTE.name
);
});
test("current page is quote and there are part questions and molding questions => go to molding questions", async () => {
// Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE });
const { wrapper } = setupMocks({ fmgPage: routeData.QUOTE.name });
wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true);
wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true);
@ -2356,13 +2341,13 @@ describe("vehicle-questions-mixin", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS,
{ query: { fmgPage: fmgPageValues.QUOTE } }
routeData.QUOTE.name
);
});
test("current page is molding questions and there are part questions, multiple parts to choose, and capability questions => go to vehicle-parts", async () => {
// Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.MOLDING_QUESTIONS });
const { wrapper } = setupMocks({ fmgPage: routeData.MOLDING_QUESTIONS.name });
wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true);
wrapper.vm.hasGlassLocationWithMultipleParts = jest.fn().mockReturnValue(true);
wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true);
@ -2375,13 +2360,13 @@ describe("vehicle-questions-mixin", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
{ query: { fmgPage: fmgPageValues.MOLDING_QUESTIONS } }
routeData.MOLDING_QUESTIONS.name
);
});
test("current page is molding questions and there are part questions and capability questions => go to part-questions", async () => {
// Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.MOLDING_QUESTIONS });
const { wrapper } = setupMocks({ fmgPage: routeData.MOLDING_QUESTIONS.name });
wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true);
wrapper.vm.hasGlassLocationWithMultipleParts = jest.fn().mockReturnValue(false);
wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true);
@ -2394,13 +2379,13 @@ describe("vehicle-questions-mixin", () => {
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
{ query: { fmgPage: fmgPageValues.MOLDING_QUESTIONS } }
routeData.MOLDING_QUESTIONS.name
);
});
});
});
function setupMocks({ fmgPage = fmgPageValues.VIN_LOOKUP, hasVin, carId }) {
function setupMocks({ fmgPage = routeData.VIN_LOOKUP.name, hasVin, carId }) {
const baseMixin = setupMocksForJsFiles({
actionList: [
{
@ -2423,6 +2408,7 @@ function setupMocks({ fmgPage = fmgPageValues.VIN_LOOKUP, hasVin, carId }) {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
navigateWithPageData: jest.fn(),
},
store: {
commit: jest.fn(),
@ -2436,9 +2422,7 @@ function setupMocks({ fmgPage = fmgPageValues.VIN_LOOKUP, hasVin, carId }) {
},
},
route: {
query: {
fmgPage,
},
name: fmgPage,
},
});

View file

@ -58,6 +58,9 @@ function setupMocks({ partsOrQuestions = [] }) {
},
},
},
route: {
name: "vin-lookup",
},
});
const mockVinComponent = {

View file

@ -0,0 +1,13 @@
import { routeData } from "./routes";
export const autoRouteTable = [
routeData.QUOTE,
routeData.CAPABILITY_QUESTIONS,
routeData.MOLDING_QUESTIONS,
routeData.VEHICLE_PARTS,
routeData.PART_QUESTIONS,
routeData.VIN_LOOKUP,
routeData.ESTIMATE,
routeData.VEHICLE_DAMAGE,
routeData.VEHICLE,
];

View file

@ -13,6 +13,7 @@ const navigationScenarios = {
CLICKED_FORWARD_WITH_CASH: "CLICKED_FORWARD_WITH_CASH",
CLICKED_FORWARD_WITH_INSURANCE: "CLICKED_FORWARD_WITH_INSURANCE",
// TODO: use virtual page?
// Vin selection
CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN",
CLICKED_BACK_WITH_SKIP_VIN: "CLICKED_BACK_WITH_SKIP_VIN",
@ -45,15 +46,24 @@ const navigationScenarios = {
CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS",
CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS",
// Insurance-Company
CLICKED_PAY_ON_MY_OWN: "CLICKED_PAY_ON_MY_OWN",
// Schedule
CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION",
// Payment
CLICKED_INSURANCE: "CLICKED_INSURANCE",
CLICKED_PAY_NOW: "CLICKED_PAY_NOW",
CLICKED_PAY_LATER: "CLICKED_PAY_LATER",
PIA_ERROR: "PIA_ERROR",
PIA_CC_ERROR: "PIA_CC_ERROR",
PIA_APPLE_ERROR: "PIA_APPLE_ERROR",
PIA_AFTERPAY_ERROR: "PIA_AFTERPAY_ERROR",
PIA_SUCCESS: "PIA_SUCCESS",
// Return User
CLICKED_RESTART: "CLICKED_RESTART",
};
export { navigationScenarios };

View file

@ -0,0 +1,3 @@
export const ROUTE_PREFIX_TRIMMED = "fmg";
export const ROUTE_PREFIX_FULL = `/${ROUTE_PREFIX_TRIMMED}/`;

View file

@ -0,0 +1,123 @@
export const routeData = {
VEHICLE: {
name: "vehicle",
path: "/vehicle",
},
VEHICLE_DAMAGE: {
name: "vehicle-damage",
path: "/vehicle-damage",
},
ESTIMATE: {
name: "estimate",
path: "/estimate",
},
ADDRESS_LOOKUP: {
name: "address-lookup",
path: "/address-lookup",
},
VIN_LOOKUP: {
name: "vin-lookup",
path: "/vin-lookup",
},
LICENSE_PLATE_LOOKUP: {
name: "license-plate-lookup",
path: "/license-plate-lookup",
},
SERVICE_ZIP: {
name: "service-zip",
path: "/service-zip",
},
ADDRESS_VEHICLES: {
name: "address-vehicles",
path: "/address-vehicles",
},
VEHICLE_PARTS: {
name: "vehicle-parts",
path: "/vehicle-parts",
},
PART_QUESTIONS: {
name: "part-questions",
path: "/part-questions",
},
MOLDING_QUESTIONS: {
name: "molding-questions",
path: "/molding-questions",
},
CAPABILITY_QUESTIONS: {
name: "capability-questions",
path: "/capability-questions",
},
QUOTE: {
name: "quote",
path: "/quote",
},
INSURANCE_COMPANY: {
name: "insurance-company",
path: "/insurance-company",
},
SERVICE_LOCATION: {
name: "service-location",
path: "/service-location",
},
HERITAGE: {
name: "heritage",
path: "/virtual/heritage",
virtual: true,
},
SCHEDULE: {
name: "schedule",
path: "/schedule",
},
CUSTOMER_DETAILS: {
name: "customer-details",
path: "/customer-details",
},
PAYMENT_METHOD: {
name: "payment-method",
path: "/payment-method",
},
PAYMENT: {
name: "payment",
path: "/payment",
},
PAYMENT_PIA_RETURN: {
name: "payment-pia-return",
path: "/payment-pia-return",
},
CONFIRMATION: {
name: "confirmation",
path: "/confirmation",
},
RETURN_USER: {
name: "return-user",
path: "/return-user",
},
LOAD_SESSION: {
name: "load-session",
path: "/virtual/load-session",
virtual: true,
},
LANDING: {
name: "landing",
path: "/",
virtual: true,
},
AUTO_ROUTE: {
name: "auto-route",
path: "/virtual/auto-route",
virtual: true,
},
// TODO: RENAME FROM BAILOUT
ERROR: {
name: "error",
path: "/virtual/error",
virtual: true,
},
RESTART: {
name: "restart",
path: "/virtual/restart",
virtual: true,
},
};
export const FUNNEL_START_PAGE = routeData.VEHICLE;

View file

@ -1,542 +1,592 @@
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { routeData } from "@/router/constants/routes";
import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import store from "@/store";
import { paymentMethods } from "@/constants/payment-method-constants";
// Get store from router/index.js instead of importing it here to get updated values
const routingTable = function (store) {
// Get as function to live-evaluate filters
const routingTable = function () {
return [
{
fmgPageValue: fmgPageValues.VEHICLE,
pageName: routeData.VEHICLE.name,
maps: [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
},
],
},
{
fmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
pageName: routeData.VEHICLE_DAMAGE.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.VEHICLE,
destinationPageData: routeData.VEHICLE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_SKIP_VIN,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
{
scenario:
navigationScenarios.CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
{
scenario:
navigationScenarios.CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
],
},
{
fmgPageValue: fmgPageValues.VIN_LOOKUP,
pageName: routeData.VIN_LOOKUP.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
params: {
displayVehicleChangeAlert: true,
},
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
],
},
{
fmgPageValue: fmgPageValues.LICENSE_PLATE_LOOKUP,
pageName: routeData.LICENSE_PLATE_LOOKUP.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
params: {
displayVehicleChangeAlert: true,
},
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
],
},
{
fmgPageValue: fmgPageValues.ADDRESS_LOOKUP,
pageName: routeData.ADDRESS_LOOKUP.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
destinationFmgPageValue: fmgPageValues.ADDRESS_VEHICLES,
destinationPageData: routeData.ADDRESS_VEHICLES,
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
params: {
displayVehicleChangeAlert: true,
},
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
],
},
{
fmgPageValue: fmgPageValues.ADDRESS_VEHICLES,
pageName: routeData.ADDRESS_VEHICLES.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP,
destinationPageData: routeData.ADDRESS_LOOKUP,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
params: {
displayVehicleChangeAlert: true,
},
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.CLICKED_VIN_RETRY,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
],
},
{
fmgPageValue: fmgPageValues.ESTIMATE,
pageName: routeData.ESTIMATE.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
},
{
scenario: navigationScenarios.SELECTED_MANUAL_VIN,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
{
scenario: navigationScenarios.SELECTED_LICENSE_PLATE,
destinationFmgPageValue: fmgPageValues.LICENSE_PLATE_LOOKUP,
destinationPageData: routeData.LICENSE_PLATE_LOOKUP,
},
{
scenario: navigationScenarios.SELECTED_HOME_ADDRESS,
destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP,
destinationPageData: routeData.ADDRESS_LOOKUP,
},
{
scenario: navigationScenarios.SELECTED_NO_VIN,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
],
},
{
fmgPageValue: fmgPageValues.SERVICE_ZIP,
pageName: routeData.SERVICE_ZIP.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
destinationPageData: routeData.ESTIMATE,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_SKIP_VIN,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationPageData: routeData.VEHICLE_DAMAGE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
],
},
{
fmgPageValue: fmgPageValues.PART_QUESTIONS,
pageName: routeData.PART_QUESTIONS.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
],
},
{
fmgPageValue: fmgPageValues.VEHICLE_PARTS,
pageName: routeData.VEHICLE_PARTS.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
],
},
{
fmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
pageName: routeData.MOLDING_QUESTIONS.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
],
},
{
fmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
pageName: routeData.CAPABILITY_QUESTIONS.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
],
},
{
fmgPageValue: fmgPageValues.QUOTE,
pageName: routeData.QUOTE.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
destinationPageData: routeData.VIN_LOOKUP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
destinationFmgPageValue: fmgPageValues.SERVICE_ZIP,
destinationPageData: routeData.SERVICE_ZIP,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationPageData: routeData.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
destinationPageData: routeData.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
destinationPageData: routeData.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
destinationPageData: routeData.CAPABILITY_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CASH,
destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION,
destinationPageData: routeData.SERVICE_LOCATION,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_INSURANCE,
destinationFmgPageValue: fmgPageValues.INSURANCE_COMPANY,
destinationPageData: routeData.INSURANCE_COMPANY,
},
],
},
{
fmgPageValue: fmgPageValues.INSURANCE_COMPANY,
pageName: routeData.INSURANCE_COMPANY.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.CLICKED_PAY_ON_MY_OWN,
destinationPageData: routeData.QUOTE,
params: {
offer: "cash",
},
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.INSURANCE_DETAILS,
destinationPageData: routeData.INSURANCE_DETAILS,
},
],
},
{
fmgPageValue: fmgPageValues.SERVICE_LOCATION,
pageName: routeData.SERVICE_LOCATION.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.QUOTE,
destinationPageData: routeData.QUOTE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.SCHEDULE,
destinationPageData: routeData.SCHEDULE,
},
],
},
{
fmgPageValue: fmgPageValues.SCHEDULE,
pageName: routeData.SCHEDULE.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION,
destinationPageData: routeData.SERVICE_LOCATION,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
destinationPageData: routeData.CUSTOMER_DETAILS,
},
{
scenario: navigationScenarios.CLICKED_CHANGE_LOCATION,
destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION,
destinationPageData: routeData.SERVICE_LOCATION,
},
],
},
{
fmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
pageName: routeData.CUSTOMER_DETAILS.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.SCHEDULE,
destinationPageData: routeData.SCHEDULE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.PAYMENT_METHOD,
destinationPageData: routeData.PAYMENT_METHOD,
},
{
scenario: navigationScenarios.CLICKED_PAY_NOW,
destinationFmgPageValue: fmgPageValues.PAYMENT_METHOD,
destinationPageData: routeData.PAYMENT_METHOD,
},
],
},
{
fmgPageValue: fmgPageValues.PAYMENT_METHOD,
pageName: routeData.PAYMENT_METHOD.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
destinationPageData: routeData.CUSTOMER_DETAILS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.CONFIRMATION,
destinationPageData: routeData.CONFIRMATION,
},
{
scenario: navigationScenarios.CLICKED_PAY_NOW,
destinationFmgPageValue: fmgPageValues.PAYMENT,
destinationPageData: routeData.PAYMENT,
},
{
scenario: navigationScenarios.CLICKED_INSURANCE,
destinationFmgPageValue: fmgPageValues.INSURANCE_COMPANY,
destinationPageData: routeData.INSURANCE_COMPANY,
},
],
},
{
fmgPageValue: fmgPageValues.PAYMENT,
pageName: routeData.PAYMENT.name,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.PAYMENT_METHOD,
destinationPageData: routeData.PAYMENT_METHOD,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.CONFIRMATION,
destinationPageData: routeData.CONFIRMATION,
},
{
scenario: navigationScenarios.PIA_ERROR,
destinationPageData: routeData.PAYMENT_METHOD,
params: {
piaError: "true",
},
},
],
},
{
fmgPageValue: fmgPageValues.PAYMENT_PIA_RETURN,
pageName: routeData.PAYMENT_PIA_RETURN.name,
maps: [
{
scenario: navigationScenarios.PIA_ERROR,
destinationFmgPageValue: fmgPageValues.PAYMENT_METHOD,
destinationPageData: routeData.PAYMENT_METHOD,
params: {
piaError: "true",
},
},
{
scenario: navigationScenarios.PIA_CC_ERROR,
destinationFmgPageValue: fmgPageValues.PAYMENT,
destinationPageData: routeData.PAYMENT,
params: {
piaErrorType: paymentMethods.CREDIT_CARD,
},
},
{
scenario: navigationScenarios.PIA_APPLE_ERROR,
destinationPageData: routeData.PAYMENT,
params: {
piaErrorType: paymentMethods.APPLE_PAY,
},
},
{
scenario: navigationScenarios.PIA_AFTERPAY_ERROR,
destinationPageData: routeData.PAYMENT,
params: {
piaErrorType: paymentMethods.AFTERPAY,
},
},
{
scenario: navigationScenarios.PIA_SUCCESS,
destinationFmgPageValue: fmgPageValues.CONFIRMATION,
destinationPageData: routeData.CONFIRMATION,
},
],
},
{
fmgPageValue: fmgPageValues.CONFIRMATION,
pageName: routeData.CONFIRMATION.name,
},
// The destination page for return user does not matter. the router will invoke implicit navigation
{
fmgPageValue: fmgPageValues.RETURN_USER,
pageName: routeData.RETURN_USER.name,
maps: [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE,
destinationPageData: routeData.LOAD_SESSION,
},
{
scenario: navigationScenarios.CLICKED_RESTART,
destinationPageData: routeData.RESTART,
},
],
},

View file

@ -1,936 +1,41 @@
// Supporting files
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 {
navigateWithoutSaving,
navigateWithSaving,
navigateWithPageData,
navigateAndForceTopLevelNavigation,
} from "@/router/methods/navigate";
import { navigateToExternalUrl } from "@/router/methods/navigate-external";
import { sendToReturnUser } from "@/router/methods/send-to-return-user";
import { createWebHistory, createRouter } from "vue-router";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "../constants/store-mutations";
import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { routingTable } from "@/router/router-constants/routing-table.js";
import { globalEvents, globalEventTypes } from "@/constants/events";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter, buildQuerystringObject } from "@/helpers/querystring-helper";
import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import { fmgPageValues, funnelStartPageName } from "@/router/router-constants/fmgPage-values";
import { externalParameterStatus } from "@/constants/external-parameters";
// Heritage integration
import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper";
import {
updateOrCreateFunnelCookie,
getFunnelCookie,
updateSessionIdCookie,
deleteFunnelCookie,
getAffiliateCookies,
setupAdvertiserTracking,
} from "@/helpers/heritage-integration/cookie-helper";
import { loadSessionIfPresent, saveSession } from "@/helpers/heritage-integration/order-helper";
import {
getPageToRouteExistingOrderTo,
navigateToHeritageFunnel,
getImplicitNavigation,
} from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin";
import eventBus from "@/helpers/event-bus/event-bus";
import store from "@/store";
import analyticsMixin from "@/mixins/analytics-mixin";
import { experimentTriggers } from "../constants/experiments";
import { applicationConfig } from "../constants/application-config";
import { shouldStripPromoQueryString } from "@/helpers/promotions-helper";
import bailout from "@/layouts/bailout/bailout";
import { nextTick } from "vue";
import { getBoolFromString } from "@/helpers/boolean-helper";
const routes = [
{
path: "/bailout",
name: "bailout",
component: bailout,
},
{
path: "/",
name: "root",
async beforeEnter(to, from, next) {
// If we have no query string, or we don't have the FmgPage query string.
try {
log("------------- router index.js beforeEnter start -----------------");
log(" --to: ", to);
log(" --cookie: ", getFunnelCookie());
log(` --from.redirectedFrom:>${JSON.stringify(from.redirectedFrom)}<`, "");
await analyticsMixin.methods.validateSession();
var fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE);
var offerQuote = getQuerystringParameter(queryStrings.OFFER_QUOTE);
log(" --offerQuote: ", offerQuote);
// after session is validated, remove the fromHeritage querystring if it exists so session expiration works
if (to.query) {
delete to.query[queryStrings.FROM_HERITAGE];
}
//remove offerquote querystring if it exists
if (offerQuote) {
delete to.query[queryStrings.OFFER_QUOTE];
}
if (getFunnelCookie()?.SuppressConceptFunnel) {
log(" --go to heritage suppressConceptFunnel: ");
await navigateToHeritageFunnel({ shouldSaveSession: false });
return next(false);
}
// If the saved session has timed out, clear the session, execute 404 logic.
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) {
log(" --save session timeout go to start");
const errorPayload = {
cause: "expired session",
currentPage: from?.query?.fmgPage,
nextPage: to?.query?.fmgPage,
};
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
deleteFunnelCookie();
GoToFunnelStartOn404(next, errorPayload);
return;
}
const fromReturnUser = from?.name === fmgPageValues.RETURN_USER;
const fromContentSite = getQuerystringParameter(queryStrings.START_TYPE) === "fmg";
const toReturnUserPage = to.query?.fmgPage === fmgPageValues.RETURN_USER;
log(" --fromReturnUser ", fromReturnUser);
log(" --to.query.fmgPage ", to.query?.fmgPage);
// Intercept all navigation if a submitted order exists in storage
if (
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !==
null
) {
if (to.query.fmgPage !== funnelStartPageName) {
to.query.fmgPage = fmgPageValues.CONFIRMATION;
log(
` --has ${sessionStorageKeyConstants.SUBMITTED_STATE} go to confirmation`
);
}
}
// On entering the funnel "fresh", read cookie information, decide what to do next.
else if (from.redirectedFrom === undefined || fromReturnUser) {
// if entering the funnel from the content site, check and see if there is already a vehicle year in vuex.
// if so, send them to return-user page.
if (
fromContentSite &&
!toReturnUserPage &&
store.getters.order.vehicle?.year > 0
) {
log(" --from content site navigate to return user");
var qso = {
fmgPage: fmgPageValues.RETURN_USER,
};
const newQueryString = buildQuerystringObject(qso, true);
log(" -- returnUser add querystring: " + JSON.stringify(newQueryString));
router.push({
path: "/",
query: Object.assign({}, newQueryString),
});
return;
}
// clear the saveSessionPromise - if it exists in the vuex store but a new instance was created
// the saveSessionPromise will no longer point to a valid promise
log(" --clear promise");
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
if (!to.query.fmgPage?.startsWith("payment")) {
//payment pages used to return from safelitehop so exclude here
// Remove the parameter after quote release
// for testing end to end locally, see if there is a referralNumber on the querystring
// that could have changed in heritage and then stick it in the store and then update the cookie
// so load-session runs properly. this can be removed when heritage goes away
const referralNumber = getQuerystringParameter(
queryStrings.REFERRAL_NUMBER
);
const parentAccount = getQuerystringParameter(queryStrings.PARENT_ACCOUNT);
const correlationId = getQuerystringParameter(queryStrings.CORRELATION_ID);
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");
updateOrCreateFunnelCookie();
}
//Affiliate Cookies
const affiliateCookies = getAffiliateCookies();
if (affiliateCookies != null && affiliateCookies.length > 0) {
store.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies);
}
setupAdvertiserTracking();
log(" --load session start");
const loadSessionResponse = await loadSessionIfPresent(
to.query.isInsurance != null
? to.query.isInsurance == "true"
? true
: false
: null,
to.query.fmgPage
);
log(" --load session end");
//Update external parameter state but not when returning from heritage
if (!to.query.fmgPage?.startsWith("service-location") && !fromReturnUser) {
updateExternalParameterState();
}
//Update CASH tab when return from heritage and have offerQuote param
if (offerQuote === "true" && fromHeritage === "true") {
store.commit(storeMutations.UPDATE_IS_INSURANCE, false);
}
// clear part related state because heritage selected a new vehicle
if (
to.query.fmgPage === fmgPageValues.VEHICLE &&
getBoolFromString(getFunnelCookie()?.HasDelayedClaimRegistration) &&
!fromReturnUser
) {
store.commit(storeMutations.RESET_GLASS_PARTS_STATE);
}
// if coming from the return user page, clear the destination page so implicit navigation runs
log(" --to.query ", JSON.stringify(to.query));
if (fromReturnUser && to.query) {
log(" --clear to.query");
delete to.query[queryStrings.FMG_PAGE];
//to.query[queryStrings.FMG_PAGE] = "";
log(" --to.query cleared ", JSON.stringify(to.query));
}
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to);
log(" --pageToRedirectTo ", JSON.stringify(pageToRedirectTo));
// This logic may determine that the user should be sent to heritage -- if so, do that here.
if (pageToRedirectTo === fmgPageValues.HERITAGE) {
// prettier-ignore
log(" --go to heritage pageToRedirectTo ", JSON.stringify(pageToRedirectTo));
await navigateToHeritageFunnel({ shouldSaveSession: false });
return next(false);
}
// Assign our fmgPage so it will load normally like the other pages.
to.query.fmgPage = pageToRedirectTo;
log(" --assign to: ", JSON.stringify(pageToRedirectTo));
}
}
// Reroute around pages that should not be available.
// Exception 1: Integrated Users should not see vehicle or quote pages.
if (
store.getters.payment?.insuranceCoverage?.isVerified ||
store.getters.order?.referralNumber?.length === 6
) {
if (to.query.fmgPage === fmgPageValues.VEHICLE) {
to.query.fmgPage = fmgPageValues.VEHICLE_DAMAGE;
} else if (
to.query.fmgPage === fmgPageValues.QUOTE ||
to.query.fmgPage === fmgPageValues.INSURANCE_COMPANY
) {
log(" --block quote page for integrated clients - go to heritage");
// Push to heritage if going to quote & integrated.
await navigateToHeritageFunnel({ shouldSaveSession: false });
return next(false);
}
}
await runExperiments(to.query.fmgPage);
//Affiliate Cookies
const affiliateCookies = getAffiliateCookies();
if (affiliateCookies != null && affiliateCookies.length > 0) {
store.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies);
}
setupAdvertiserTracking();
// Process funnel cookie.
updateOrCreateFunnelCookie();
// If we already have our route, go to it.
if (router.hasRoute(to.query.fmgPage)) {
// Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function.
let component = router
.getRoutes()
.filter((x) => x.name === to.query.fmgPage)[0].components;
// If the component hasn't been loaded fully, load it before we check prerequisites.
if (component.default.methods === undefined) {
component = await component.default();
}
if (!arePagePrerequisitesValid(component)) {
console.log("Page prereq error: " + component.default.name);
if (to.query.fmgPage === fmgPageValues.PAYMENT) {
await store.getters.applicationUser.saveSessionPromise;
window.location = applicationConfig.PIA_CANCEL_URL;
return;
}
const errorPayload = {
cause: "invalid page prerequisites for page",
currentPage: from?.query?.fmgPage,
nextPage: to?.query?.fmgPage,
};
GoToFunnelStartOn404(next, errorPayload);
return;
}
log(" --has route:", to.query.fmgPage);
return next({ name: to.query.fmgPage, query: to.query, params: to.params });
}
if (!isExistingFmgPageName(to.query.fmgPage)) {
const errorPayload = {
cause: "invalid page name",
currentPage: from?.query?.fmgPage,
nextPage: to?.query?.fmgPage,
};
GoToFunnelStartOn404(next, errorPayload);
return;
}
// Get route info for the given url. Names will have a 1:1 relationship with names in the Cms.
const routeData = GetRouteInfoFromPageName(to.query.fmgPage);
log("--routeData:", routeData);
// Add our dynamic route.
router.addRoute({
path: routeData[0].path, // Always the same path, because we control it with query strings.
name: routeData[0].name,
component: routeData[0].component,
});
// Call the next components arePagePrerequisitesValid method before load.
// If it returns false, use the 404 logic.
const nextComponent = await router
.getRoutes()
.filter((x) => x.name === routeData[0].name)[0]
.components.default();
if (!arePagePrerequisitesValid(nextComponent)) {
// prettier-ignore
console.log("Page Prereqs not valid for next component: " + nextComponent.default.name);
const errorPayload = {
cause: "invalid page prerequisites for page",
currentPage: from?.query?.fmgPage,
nextPage: to?.query?.fmgPage,
};
GoToFunnelStartOn404(next, errorPayload);
return;
}
log("------------- router index.js beforeEnter end -----------------");
// Assign current query string parameters, as well as our fmgPage one.
next({
name: routeData[0].name,
query: Object.assign(to.query, { fmgPage: routeData[0].name }),
params: to.params,
});
} catch (error) {
global.$logger.logError(`Routing ${error.stack}`);
if (to.query?.fmgPage === funnelStartPageName) {
deleteFunnelCookie();
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
}
console.log(new Date() + " Exception in beforeEnter:" + JSON.stringify(error));
// If we don't have a route, go to our 404 page.
const errorPayload = {
cause: "uncaught error in beforeEnter",
currentPage: from?.query?.fmgPage,
nextPage: to?.query?.fmgPage,
fullError: error,
errorStack: error?.stack,
};
GoToFunnelStartOn404(next, errorPayload);
return;
}
},
},
];
import { ROUTE_PREFIX_FULL } from "@/router/constants/route-prefix";
const router = createRouter({
history: createWebHistory("/fmg/"),
history: createWebHistory(ROUTE_PREFIX_FULL),
routes,
//Cause "page" to begin at the top when route chanages.
// Scroll to top on navigation.
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 };
},
});
//---------------------------------------------------------- Router Functions ----------------------------------------------------------
// Set guards
router.afterEach(async (to, from) => afterEach(to, from));
router.beforeEach(async (to, from) => beforeEach(to, from));
router.beforeEach(async (to, from, next) => {
log("------------- router index.js beforeEach start -----------------");
// set lastNavigationPage here to capture state before API calls for analytics.
// use current page url query string name when to.name is "root" (due to unresolved navigation in beforeEach)
router.lastNavigationPage = to.name == "root" ? analyticsMixin.methods.getPageName() : to.name;
// Bind methods for external use
router.navigateWithSaving = navigateWithSaving;
router.navigateWithoutSaving = navigateWithoutSaving;
router.navigateWithPageData = navigateWithPageData;
router.navigateAndForceTopLevelNavigation = navigateAndForceTopLevelNavigation;
const fromQueryPage = from.query?.fmgPage;
if (fromQueryPage === undefined) {
showFmgLoadingModal(true);
}
router.bailout = bailout;
const toQueryPage = to.query?.fmgPage;
const notToPIAReturn = toQueryPage != fmgPageValues.PAYMENT_PIA_RETURN;
const isInIframe = window !== window.top;
router.navigateToExternalUrl = navigateToExternalUrl;
if (store.getters.externalParameterState?.isExternalParameter) {
// Check if alert event is on the bus
const alertEvent = eventBus.readEventFromBus(
globalEvents.Categories.GLOBAL_ALERT,
globalEvents.SubCategories.PAGE_NOT_FOUND
);
//Uknown alerts most likely were added by a failed api call in global-methods
const unknownAlertEvent = eventBus.readEventFromBus(
globalEvents.Categories.GLOBAL_ALERT,
globalEvents.SubCategories.UNKNOWN_ERROR
);
// If alert event is on the bus, then display the alert
if (alertEvent !== undefined || unknownAlertEvent !== undefined) {
store.commit(
storeMutations.UPDATE_IS_EXTERNAL_PARAMETER,
externalParameterStatus.INACTIVE
);
}
}
// fromPaymentToConfirmation workaround for navigating from an iframe but
// isInIframe evaluates to false for some reason when navigating from payment to confirmation
const fromPaymentToConfirmation =
fromQueryPage === fmgPageValues.PAYMENT && toQueryPage === fmgPageValues.CONFIRMATION;
if ((isInIframe && notToPIAReturn) || fromPaymentToConfirmation) {
const newUrl = `${window.top.location.origin}${to.href}`;
window.top.location.href = newUrl;
next(false);
// Refresh page if navigating to self to prevent locking.
// For now only carved out for vehicle; all modals are opened through anchor tags at the moment,
// which also self navigate, but relied on the page remaining the same on self-navigation.
} else if (toQueryPage === fromQueryPage && toQueryPage === getRedirectedStartPage()) {
router.go(0);
next(false);
} else {
next();
}
log("------------- router index.js beforeEach end -----------------");
});
router.afterEach(async (to, from) => {
// Update lastPageVisited in the store
store.commit(storeMutations.UPDATE_LAST_PAGE_VISITED, to.name);
// If saving on navigation is requested, check for saved SessionId or EmailAddress to determine if saving is appropriate
if (getBoolFromString(window.history.state.isSavingNavigation)) {
if (
store.getters.applicationUser.savedSessionId ||
store.getters.order.customer?.emailAddress
) {
await saveSession({ pageNameToLog: to.query.fmgPage });
}
}
// Push page view to GA
analyticsMixin.methods.pushPageViewToGA();
// Push experiments to Data Layer
analyticsMixin.methods.pushExperimentsToDataLayer();
// Push current order status to Data Layer
analyticsMixin.methods.pushOrderToDataLayer();
if (to.query.fmgPage == fmgPageValues.CONFIRMATION) {
//Push Ecommerce Cart to Data Layer
analyticsMixin.methods.pushECommerceCartToDataLayer();
//Push Product Array to Data Layer
analyticsMixin.methods.pushProductArrayToDataLayer();
//Push Commission Junction Gtm Data To Data Layer
analyticsMixin.methods.pushCommissionJunctionGtmDataToDataLayer();
}
});
router.navigateWithoutSaving = (
scenario,
currentRoute,
optionalQuery = {},
optionalParams = {},
optionalPageData = {}
) => {
navigate(scenario, currentRoute, false, optionalQuery, optionalParams, optionalPageData);
};
router.navigateWithSaving = (
scenario,
currentRoute,
optionalQuery = {},
optionalParams = {},
optionalPageData = {}
) => {
navigate(scenario, currentRoute, true, optionalQuery, optionalParams, optionalPageData);
};
router.navigateToExternalUrl = (url, optionalQuery = {}) => {
log("------------- router navigateToExternalUrl -----------------");
log("url:", url);
navigateToUrl(url, optionalQuery);
};
router.navigateError = (errorPayload = null) => {
DisplayPageError(errorPayload);
};
//Use this navigation when you need to call next() explicitly. beforeRouteEnter is a good example.
router.overrideNavigation = (
scenario,
currentRoute,
next,
isSavingNavigation,
optionalQuery = {},
optionalParams = {},
optionalPageData
) => {
log("------------- router overrideNavigation -----------------");
log("scenario:", scenario);
log("currentRoute:", currentRoute);
log("isSavingNavigation:", isSavingNavigation);
log("optionalQuery:", optionalQuery);
log("optionalParams:", optionalParams);
log("optionalPageData:", optionalPageData);
navigate(
scenario,
currentRoute,
isSavingNavigation,
optionalQuery,
optionalParams,
optionalPageData
);
next();
};
router.getNextPage = () => nextPageName;
// PRIVATE VARIABLES
var nextPageName;
// PRIVATE FUNCTIONS
// Navigate to the next route, depending on the scenario.
async function navigate(
scenario,
currentRoute,
isSavingNavigation,
optionalQuery = {},
optionalParams = {},
optionalPageData = {}
) {
if (!scenario) {
console.error("No scenario provided. Please review the routing table.");
return;
}
// Match our maps up and navigate if we have a destination.
const matchingScenarioMap = getNavigationMap(scenario, currentRoute);
const destinationFmgPageValue = matchingScenarioMap.destinationFmgPageValue;
if (destinationFmgPageValue !== undefined) {
// We're always pushing the same path, just changing query strings. Make sure our optional query strings get combined with our fmgPage one.
nextPageName = destinationFmgPageValue;
// Update page data to the store for next page if provided. Otherwise, keep existing page data or set to empty object
const existingPageDataForPage = store.getters.pageData(destinationFmgPageValue);
baseMixin.methods.savePageDataToStore(
destinationFmgPageValue,
Object.keys(optionalPageData).length > 0
? optionalPageData
: (existingPageDataForPage ?? {})
);
optionalParams.isSavingNavigation = isSavingNavigation;
// add querystring params to the route. if they are already in the store then no need to add them
var queryStringsObject = {
fmgPage: destinationFmgPageValue,
};
const hasZip = getQuerystringParameter(queryStrings.ZIP_CODE);
const zip = getQuerystringParameter(queryStrings.ZIP_CODE);
const promo = getQuerystringParameter(queryStrings.PROMO);
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
const logQs = getQuerystringParameter(queryStrings.LOG);
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_LOGGING_OPTION, logQs, false);
log("------------- router index.js navigate start -----------------");
log(" --scenario: ", scenario);
log(" --isSavingNavigation: ", isSavingNavigation);
log(" --optionalQuery: ", optionalQuery);
log(" --optionalParams: ", optionalParams);
log(" --optionalPageData: ", optionalPageData);
log(" --nextPageName: ", nextPageName);
if (
hasZip &&
(store.getters.order.serviceLocation.zipCode === undefined ||
store.getters.order.serviceLocation.zipCode == null) &&
(store.getters.order.vehicle.registration.zipCode === undefined ||
store.getters.order.vehicle.registration.zipCode == null)
) {
queryStringsObject[queryStrings.ZIP_CODE] = zip;
}
if (promo && !shouldStripPromoQueryString(currentRoute.query.fmgPage)) {
queryStringsObject[queryStrings.PROMO] = promo;
}
if (pageError) {
queryStringsObject[queryStrings.PAGE_ERROR] = pageError;
}
if (logQs) {
queryStringsObject[queryStrings.LOG] = logQs;
}
log(" querystrings: ", queryStringsObject);
log("------------- router index.js navigate end -----------------");
router.push({
name: "root",
query: Object.assign(optionalQuery, queryStringsObject),
state: optionalParams,
});
} else if (matchingScenarioMap.destinationUrl !== undefined) {
navigateToUrl(matchingScenarioMap.destinationUrl, optionalQuery);
}
}
// Get navigation map depending on the scenario and the current 'page' you're on.
function getNavigationMap(scenario, currentRoute) {
const fmgPageValue = currentRoute.query.fmgPage;
log("------------- router index.js getNavigationMap start -----------------");
log("fmgPage: " + fmgPageValue + " scenario: " + scenario, "");
log("------------- router index.js getNavigationMap end -----------------");
const matchedQueryValue = routingTable(store)
.filter(
(item) =>
item.fmgPageValue === fmgPageValue &&
item.maps.filter((map) => map.scenario === scenario).length > 0
)
.map((m) => m.maps.filter((map) => map.scenario === scenario))[0]
.filter((x) => x.filter === true || x.filter === undefined);
return matchedQueryValue[0];
}
function log(message, data) {
const log = getQuerystringParameter(queryStrings.LOG);
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_LOGGING_OPTION, log, false);
data = data ?? "";
const outData = typeof data === "object" ? JSON.stringify(data) : data;
if (log === "true" || store.getters.applicationUser.loggingOption) {
console.log(new Date() + message + outData);
}
}
//---------------------------------------------------------- Private Functions ----------------------------------------------------------
// Navigate to an external url.
function navigateToUrl(url, optionalQuery = {}) {
// possibly show some loading screen in the future here.
var externalUrl = new URL(url);
for (const queryKey in optionalQuery) {
externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]);
}
window.location.assign(externalUrl);
}
// Get route information by page name.
// This will no longer reach out to the Cms. There is a 1:1 relationship between page names and route names and layouts.
function GetRouteInfoFromPageName(pageName) {
// check if pagename is a valid route/component
if (!isExistingFmgPageName(pageName)) {
return [];
}
const routeData = [
{
path: "/",
name: pageName,
component: lazyLoadComponent(pageName),
},
];
return routeData;
}
// Go to our start page on a 404.
function GoToFunnelStartOn404(next, errorPayload = null) {
if (errorPayload !== null) {
errorPayload.type = "GoToFunnelStartOn404";
analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload);
console.log(
"%cGoToFunnelStartOn404()... errorPayload",
"color: white; background-color: blue; padding: 5px;",
errorPayload
);
}
// Put item on the bus
eventBus.addEventToBus(
globalEvents.Categories.GLOBAL_ALERT,
globalEvents.SubCategories.PAGE_NOT_FOUND,
{
isDismissible: true,
messageCopy: "You can get a quote by starting on this page.",
messageHeadline: "We're sorry, something went wrong.",
type: globalEventTypes.Danger,
}
);
next({
path: "/",
query: { fmgPage: funnelStartPageName },
});
}
async function DisplayPageError(errorPayload = null) {
console.log("%c running DisplayPageError()... ", "font-size: 20px; color: purple;");
if (errorPayload !== null) {
errorPayload.type = "DisplayPageError";
analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload);
} else {
// very cautiously to avoid additional errors:
var currentPage = "";
try {
currentPage = getQuerystringParameter(queryStrings.FMG_PAGE);
} catch (e) {
// pass
}
errorPayload = {
type: "DisplayPageError",
cause: "Unknown page error",
currentPage: currentPage,
};
analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload);
}
// Put item on the bus
eventBus.addEventToBus(
globalEvents.Categories.GLOBAL_ALERT,
globalEvents.SubCategories.UNKNOWN_ERROR,
{
isDismissible: true,
messageCopy: "",
messageHeadline: "We're sorry, something went wrong.",
type: globalEventTypes.Danger,
}
);
if (
store.getters.externalParameterState?.isExternalParameter == externalParameterStatus.ACTIVE
) {
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, externalParameterStatus.INACTIVE);
}
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
// we use the pageError querystring as a counter to how many times a user has experienced an error and been routed here.
// once they receive more than 1 pageerrors, we'll reset their state to hopefully correct any issues they may be having.
var navPage = fmgPageValues.VEHICLE;
if (store.getters.payment?.insuranceCoverage?.isVerified) {
navPage = fmgPageValues.VEHICLE_DAMAGE;
}
if (pageError) {
var errorCount = Number(pageError);
if (errorCount > 1) {
deleteFunnelCookie();
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
router.push({
path: "/",
query: { fmgPage: navPage },
});
} else {
errorCount++;
router.push({
path: "/",
query: { fmgPage: navPage, pageError: errorCount },
});
}
} else {
router.push({
path: "/",
query: { fmgPage: navPage, pageError: "1" },
});
}
}
function isExistingFmgPageName(pageName) {
const names = Object.values(fmgPageValues);
return names.some((name) => name === pageName);
}
// Checks arePagePrerequisitesValid on the component passed in.
function arePagePrerequisitesValid(component) {
return component.default.methods.arePagePrerequisitesValid();
}
// Run SiteEntry and PageEntry triggers for experiments
async function runExperiments(nextPage) {
if (!store.getters.applicationUser.triggeredSiteEntry) {
await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
{
deviceId: getDeviceIdValue(),
triggerEvent: experimentTriggers.SITE_ENTRY,
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
},
nextPage
);
}
await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
{
deviceId: getDeviceIdValue(),
triggerEvent: experimentTriggers.PAGE_ENTRY,
triggerValue: nextPage,
},
nextPage
);
}
function getRedirectedStartPage() {
if (store.getters.payment?.insuranceCoverage?.isVerified) {
return fmgPageValues.VEHICLE_DAMAGE;
} else {
return funnelStartPageName;
}
}
function updateExternalParameterState() {
const externalParameterYear = getQuerystringParameter(queryStrings.VEHICLE_YEAR);
const externalParameterMake = getQuerystringParameter(queryStrings.VEHICLE_MAKE);
const externalParameterModel = getQuerystringParameter(queryStrings.VEHICLE_MODEL);
const externalParameterStyle = getQuerystringParameter(queryStrings.VEHICLE_STYLE);
const externalParameterDamage = getQuerystringParameter(queryStrings.VEHICLE_DAMAGE);
const externalParameterZipCode = getQuerystringParameter(queryStrings.SERVICE_ZIP);
const externalParameterEmail = getQuerystringParameter(queryStrings.EMAIL);
const externalParameterIsInsurance = getQuerystringParameter(queryStrings.IS_INSURANCE);
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
const externalParameterServicePackage = getQuerystringParameter(queryStrings.SERVICE_PACKAGE);
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
const externalParameterSource = getQuerystringParameter(queryStrings.EXPERIMENTS);
const externalParameterPhoneNumber = getQuerystringParameter(queryStrings.PHONE_NUMBER);
if (
externalParameterYear &&
externalParameterMake &&
externalParameterModel &&
externalParameterStyle
) {
resetStoreForExternalParameter();
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, externalParameterStatus.ACTIVE);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_YEAR, externalParameterYear);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MAKE, externalParameterMake);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MODEL, externalParameterModel);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_STYLE, externalParameterStyle);
}
if (
externalParameterDamage &&
(externalParameterDamage.toUpperCase() != "WINDSHIELDREPAIR" ||
externalParameterNumberOfChips?.length > 0)
) {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_DAMAGE_TYPE, externalParameterDamage);
if (externalParameterDamage.toUpperCase() == "WINDSHIELDREPLACE") {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_NUMBER_OF_CHIPS, null);
} else if (externalParameterDamage.toUpperCase() == "WINDSHIELDREPAIR") {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_IS_REPAIR, true);
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_NUMBER_OF_CHIPS,
externalParameterNumberOfChips
);
}
}
if (externalParameterZipCode) {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_ZIP_CODE, externalParameterZipCode);
}
if (externalParameterEmail) {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_EMAIL_ADDRESS,
externalParameterEmail
);
}
if (externalParameterIsInsurance?.toUpperCase() == "TRUE") {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_IS_INSURANCE, true);
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE,
externalParameterServicePackage?.toLowerCase()
);
}
if (externalParameterDamage?.toUpperCase() != "WINDSHIELDREPAIR") {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_VIN_SELECTION,
externalParameterVinSelection
);
}
if (externalParameterSource) {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_SOURCE, externalParameterSource);
}
if (externalParameterPhoneNumber) {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER,
externalParameterPhoneNumber
);
}
}
// if there is an existing external parameter state then they have already been through from an external source(LeadGen) and
// are returning. clear out prior related things that need to be selected again like parts and damage type.
function resetStoreForExternalParameter() {
if (
store.getters.externalParameterState?.isExternalParameter ==
externalParameterStatus.INACTIVE
) {
store.dispatch(storeActions.RESET_VEHICLE_STATE_AND_DEPENDENCIES);
store.dispatch(storeActions.SAVE_SERVICE_ZIP_CODE_INFO, {
state: null,
zipCode: null,
zipCodeCtu: null,
});
store.dispatch(storeActions.SAVE_EMAIL, null);
store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
}
}
router.sendToReturnUser = sendToReturnUser;
export default router;

View file

@ -0,0 +1,12 @@
import analyticsMixin from "@/mixins/analytics-mixin";
export async function afterEach(to, from) {
// Push page view to GA
analyticsMixin.methods.pushPageViewToGA();
// Push experiments to Data Layer
analyticsMixin.methods.pushExperimentsToDataLayer();
// Push current order status to Data Layer
analyticsMixin.methods.pushOrderToDataLayer();
}

View file

@ -0,0 +1,106 @@
import { sessionStorageKeyConstants } from "@/constants/session-storage";
import {
getFunnelCookie,
updateOrCreateFunnelCookie,
} from "@/helpers/heritage-integration/cookie-helper";
import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper";
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 { checkPagePrerequisites } from "@/router/methods/page-prerequisites";
import { checkLogParam } from "@/helpers/debug-log-helper";
import { debugLog } from "@/helpers/debug-log-helper";
import { handleHeritageReturn } from "@/router/methods/helpers/handle-heritage-return";
export async function beforeEach(to, from) {
try {
await analyticsMixin.methods.validateSession();
// prettier-ignore
{
debugLog(`--- before-each.js ${from?.name} start ---`);
debugLog(" funnel cookie init: ", getFunnelCookie());
}
if (getFunnelCookie()?.SuppressConceptFunnel) {
// Redirect to heritage.
return {
name: routeData.HERITAGE.name,
};
}
// Ensure session has not expired
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) {
const errorPayload = {
cause: "expired session",
currentPage: from?.name,
nextPage: to?.name,
};
bailout(errorPayload, true);
return false;
}
// Block navigation if an order has been submitted.
if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
if (to.name !== FUNNEL_START_PAGE.name && to.name !== routeData.CONFIRMATION.name) {
return false;
}
}
// Special logic when returning from insurance flow.
await handleHeritageReturn(to, from);
// prettier-ignore
{
debugLog(`--- before-each.js ${from?.name} mid ---`);
debugLog(" funnel cookie after load: ", getFunnelCookie());
}
// Process funnel cookie.
updateOrCreateFunnelCookie();
// prettier-ignore
{
debugLog(`--- before-each.js ${from?.name} end ---`);
debugLog(" funnel cookie after update: ", getFunnelCookie());
}
// Update if logging is enabled.
checkLogParam();
// Ensure page-prerequisites are fulfilled
const arePagePrerequisitesValid = await checkPagePrerequisites(to.name);
if (!arePagePrerequisitesValid) {
const errorPayload = {
cause: "invalid page prerequisites for page",
currentPage: from?.name,
nextPage: to?.name,
};
bailout(errorPayload);
return;
}
await runExperiments(to.name);
// prettier-ignore
{
debugLog("--- before-each.js end ---");
}
} catch (error) {
const errorPayload = {
cause: "Uncaught exception in `beforeEach`.",
currentPage: from?.name,
nextPage: to?.name,
};
console.log(error);
bailout(errorPayload);
return;
}
}

View file

@ -0,0 +1,17 @@
import analyticsMixin from "@/mixins/analytics-mixin";
import { routeData } from "@/router/constants/routes";
import router from "@/router";
export async function bailout(errorPayload, forceRestart = false) {
analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload);
if (forceRestart) {
router.push({
name: routeData.RESTART.name,
});
} else {
router.push({
name: routeData.ERROR.name,
});
}
}

View file

@ -0,0 +1,5 @@
import router from "@/router";
export function getRoute(pageName) {
return router.getRoutes().find((r) => r.name === pageName);
}

View file

@ -0,0 +1,27 @@
import { ROUTE_PREFIX_TRIMMED } from "@/router/constants/route-prefix";
export function buildManualUrl(nextPage) {
const queries = nextPage.params;
const querystring = buildSearchParams(queries);
const url = `${window.top.location.origin}/${ROUTE_PREFIX_TRIMMED}${nextPage.path}${querystring}`;
return url;
}
export function buildSearchParams(paramList) {
if (!paramList) {
return "";
}
const keys = Object.keys(paramList);
if (keys?.length > 0) {
const queryFormattedParams = keys.map((k) => `${k}=${paramList[k]}`);
const querystring = queryFormattedParams.reduce((a, b) => `${a}&${b}`);
return `?${querystring}`;
}
return "";
}

View file

@ -0,0 +1,18 @@
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { queryStrings } from "@/constants/query-strings";
import store from "@/store";
import { storeMutations } from "@/constants/store-mutations";
import { updateOrCreateFunnelCookie } 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);
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");
updateOrCreateFunnelCookie();
}
}

View file

@ -0,0 +1,39 @@
import { lazyLoadComponent } from "@/router/methods/helpers/lazy-load-component";
import { routeData } from "@/router/constants/routes";
export function createRoute(routeDatum, beforeEnter = async (to, from) => undefined) {
return {
name: routeDatum.name,
path: routeDatum.path,
component: lazyLoadComponent(routeDatum.name),
beforeEnter: async (to, from) => {
try {
return await beforeEnter(to, from);
} catch {
return {
name: routeData.BAILOUT.name,
replace: true,
};
}
},
};
}
export function createVirtualRoute(routeDatum, beforeEnter = async (to, from) => undefined) {
return {
name: routeDatum.name,
path: routeDatum.path,
beforeEnter: async (to, from) => {
try {
return await beforeEnter(to, from);
} catch (error) {
console.log(error);
return {
name: routeData.BAILOUT.name,
replace: true,
};
}
},
virtual: true,
};
}

View file

@ -0,0 +1,18 @@
import { routingTable } from "@/router/constants/routing-table";
export function getDestination(sourcePageName, navigationScenario) {
const table = routingTable();
const pageEntry = table.find((entry) => entry.pageName === sourcePageName);
const map = pageEntry?.maps?.find((map) => map.scenario === navigationScenario);
if (!map || map?.filter === false) {
return null;
}
return {
...map.destinationPageData,
params: map.params,
};
}

View file

@ -0,0 +1,16 @@
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";
export async function handleHeritageReturn(to, from) {
const fromHeritageFlag = to.query[queryStrings.FROM_HERITAGE];
if (fromHeritageFlag) {
// Get new referral info from querystring in case passed there.
await consumeReferralQuerystrings();
// load session with new cookie
await loadSessionIfPresent(true, routeData.SERVICE_LOCATION.name);
}
}

View file

@ -0,0 +1,121 @@
import {
setupAdvertiserTracking,
getAffiliateCookies,
} from "@/helpers/heritage-integration/cookie-helper";
import store from "@/store";
import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { queryStrings } from "@/constants/query-strings";
import { externalParameterStatus } from "@/constants/external-parameters";
export async function initializeFromQueryStrings() {
// Get advertiser info
setupAdvertisersAndAffiliates();
// Read all external paramter queries and write to store.
updateExternalParameterState();
}
function setupAdvertisersAndAffiliates() {
const affiliateCookies = getAffiliateCookies();
if (affiliateCookies != null && affiliateCookies.length > 0) {
store.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies);
}
setupAdvertiserTracking();
}
function updateExternalParameterState() {
const externalParameterYear = getQuerystringParameter(queryStrings.VEHICLE_YEAR);
const externalParameterMake = getQuerystringParameter(queryStrings.VEHICLE_MAKE);
const externalParameterModel = getQuerystringParameter(queryStrings.VEHICLE_MODEL);
const externalParameterStyle = getQuerystringParameter(queryStrings.VEHICLE_STYLE);
const externalParameterDamage = getQuerystringParameter(queryStrings.VEHICLE_DAMAGE);
const externalParameterZipCode = getQuerystringParameter(queryStrings.SERVICE_ZIP);
const externalParameterEmail = getQuerystringParameter(queryStrings.EMAIL);
const externalParameterIsInsurance = getQuerystringParameter(queryStrings.IS_INSURANCE);
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
const externalParameterServicePackage = getQuerystringParameter(queryStrings.SERVICE_PACKAGE);
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
const externalParameterSource = getQuerystringParameter(queryStrings.EXPERIMENTS);
const externalParameterPhoneNumber = getQuerystringParameter(queryStrings.PHONE_NUMBER);
if (
externalParameterYear &&
externalParameterMake &&
externalParameterModel &&
externalParameterStyle
) {
resetStoreForExternalParameter();
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, externalParameterStatus.ACTIVE);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_YEAR, externalParameterYear);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MAKE, externalParameterMake);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MODEL, externalParameterModel);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_STYLE, externalParameterStyle);
}
if (
externalParameterDamage &&
(externalParameterDamage.toUpperCase() != "WINDSHIELDREPAIR" ||
externalParameterNumberOfChips?.length > 0)
) {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_DAMAGE_TYPE, externalParameterDamage);
if (externalParameterDamage.toUpperCase() == "WINDSHIELDREPLACE") {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_NUMBER_OF_CHIPS, null);
} else if (externalParameterDamage.toUpperCase() == "WINDSHIELDREPAIR") {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_IS_REPAIR, true);
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_NUMBER_OF_CHIPS,
externalParameterNumberOfChips
);
}
}
if (externalParameterZipCode) {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_ZIP_CODE, externalParameterZipCode);
}
if (externalParameterEmail) {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_EMAIL_ADDRESS,
externalParameterEmail
);
}
if (externalParameterIsInsurance?.toUpperCase() == "TRUE") {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_IS_INSURANCE, true);
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE,
externalParameterServicePackage?.toLowerCase()
);
}
if (externalParameterDamage?.toUpperCase() != "WINDSHIELDREPAIR") {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_VIN_SELECTION,
externalParameterVinSelection
);
}
if (externalParameterSource) {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_SOURCE, externalParameterSource);
}
if (externalParameterPhoneNumber) {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER,
externalParameterPhoneNumber
);
}
}
// if there is an existing external parameter state then they have already been through from an external source(LeadGen) and
// are returning. clear out prior related things that need to be selected again like parts and damage type.
function resetStoreForExternalParameter() {
if (
store.getters.externalParameterState?.isExternalParameter ==
externalParameterStatus.INACTIVE
) {
store.dispatch(storeActions.RESET_VEHICLE_STATE_AND_DEPENDENCIES);
store.dispatch(storeActions.SAVE_SERVICE_ZIP_CODE_INFO, {
state: null,
zipCode: null,
zipCodeCtu: null,
});
store.dispatch(storeActions.SAVE_EMAIL, null);
store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
}
}

View file

@ -1,3 +1,3 @@
export function lazyLoadComponent(componentName) {
return () => import(`@/layouts/${componentName}/${componentName}.vue`); // /src/layouts/folder/component.vue
return () => import(`@/layouts/${componentName}/${componentName}.vue`);
}

Some files were not shown because too many files have changed in this diff Show more