Merge branch 'develop' into feature/CASH-1947

This commit is contained in:
Chris Redelinghuys 2026-02-17 08:48:25 -05:00
commit ca4f6323c2
31 changed files with 676 additions and 2945 deletions

View file

@ -32,11 +32,7 @@ module.exports = {
"!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development
"!src/layouts/insurance/*.vue", // Temp test exclusion while in development
"!src/layouts/insurance-company/*.vue", // Temp test exclusion while in development
"!src/layouts/schedule/**/*.vue", // Temp test exclusion while in development
"!src/digital-components/date-picker/**/*.vue", // Temp test exclusion while in development
"!src/**/*-june-2025.vue", // Exclude these temporary files for CASH-845 project
"!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development
"!src/experiment-components/*.vue",
// END
@ -44,7 +40,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
global: {
statements: 64,
statements: 68,
},
},
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit

View file

@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta name="robots" content="noindex" />
<script>
window.dataLayer = [{}];
</script>

View file

@ -48,6 +48,10 @@ export const pageProgressMapper = {
percent: 30,
step: 1,
},
"policy-info": {
percent: 55,
step: 2,
},
quote: {
percent: 60,
step: 2,

View file

@ -173,4 +173,29 @@ export const quotePageDiscountTable = [
packageLevel: packageNames.TIER_THREE,
isInsurance: true,
},
// Replace - Curve Tempered
{
name: "Curve Tempered Glass Only",
experimentCode: "Show_GlassOnly_Replace_CT_Discount",
partNumber: "GL CT CSH PKG",
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
description: "GLASS CT GEOTARGET DISC",
packageLevel: packageNames.TIER_ONE,
},
{
name: "Curve Tempered Standard",
experimentCode: "Show_Standard_Replace_CT_Discount",
partNumber: "STD CT CSH PKG",
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
description: "STD CT GEOTARGET DISC",
packageLevel: packageNames.TIER_TWO,
},
{
name: "Curve Tempered Premium",
experimentCode: "Show_Premium_Replace_CT_Discount",
partNumber: "PRM CT CSH PKG",
partType: partTypeStrings.QUOTE_PAGE_DISCOUNT,
description: "PREM CT GEOTARGET DISC",
packageLevel: packageNames.TIER_THREE,
},
];

View file

@ -119,6 +119,7 @@ const storeMutations = {
UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE: "updateExternalParameterServicePackage",
UPDATE_EXTERNAL_PARAMETER_SOURCE: "updateExternalParameterSource",
UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER: "updateExternalParameterPhoneNumber",
UPDATE_EXTERNAL_PARAMETER_QS_STASH: "updateExternalParameterQsStash",
//RESET EXTERNAL_PARAMETER MUTATIONS
RESET_EXTERNAL_PARAMETER_VEHICLE_STATE: "resetExternalParameterVehicleState",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,18 @@
<template>
<div
class="date-picker text-center"
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`">
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass} ${loadingStatus === 'reinitial' ? 'loading-reinitial' : ''}`">
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
<legend class="sr-only">Select a day and time</legend>
<div
v-for="month in months"
v-for="(month, i) in months"
:key="`${month.monthLabel}-${month.yearNum?.toString()}`"
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
class="calendar-grid-container"
class="calendar-grid-container month"
:class="[
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
month.monthClass,
i === 0 ? 'first-month-shown' : '',
]">
<div class="month-year body-small d-flex align-items-center">
{{ month.monthLabel }} {{ month.yearNum?.toString() }}
@ -114,7 +115,8 @@
</div>
</div>
<loader
:class="[!isLoading ? 'date-picker-hidden' : '']"
class="date-picker-loader"
:class="[loadingStatus !== 'none' ? 'show-loader' : '']"
loaderColor="blue"
loaderPosition="center" />
<div class="row form-test-error" id="date-of-month-error">
@ -125,7 +127,7 @@
type="button"
class="btn btn-link view-more-dates"
id="viewMoreDates"
:disabled="isLoading"
:disabled="loadingStatus !== 'none'"
@click="showAnotherMonth">
View more dates
</button>
@ -160,7 +162,6 @@ export default {
name: "datePicker",
data() {
return {
isLoading: true,
months: null,
disableViewMoreDatesButton: false,
hideSomeDaysForInitialView: null,
@ -169,7 +170,10 @@ export default {
};
},
props: {
customComponentId: String,
customComponentId: {
type: String,
default: "",
},
selectableDatesSetting: {
type: String,
validator(value) {
@ -200,6 +204,10 @@ export default {
pricingByDayUpcharge: Number,
isPricingByDayExperiment: Boolean,
isMobileSelected: Boolean,
loadingStatus: {
type: String,
default: "none",
},
},
setup(props) {
const uuid = uuidv4();
@ -324,9 +332,9 @@ export default {
let preSelectedDateMonthEnd = this.getMonthEnd(
preSelectedDate.replace("-mobile", "")
);
let weekIncludesPreSelectedMonthEnd = false;
let monthEndsByThisWeek = false;
let i = 0;
while (!weekIncludesPreSelectedMonthEnd) {
while (!monthEndsByThisWeek && i < 52) {
if (i > 0) {
weekStartDate = this.getNextWeekSunday(weekEndDate);
weekEndDate = this.getWeekEndDate(weekStartDate);
@ -338,7 +346,12 @@ export default {
preSelectedDateMonthEnd === weekEndDate
) {
weekEndDate = preSelectedDateMonthEnd;
weekIncludesPreSelectedMonthEnd = true;
monthEndsByThisWeek = true;
} else if (preSelectedDateMonthEnd < weekEndDate) {
// Additional case: if month ends before this week (but not necessarily during it),
// still cut the loop here, but don't truncate the week.
monthEndsByThisWeek = true;
}
}
weeks.push({
@ -489,7 +502,6 @@ export default {
});
},
async setCalendarData(config = {}) {
this.isLoading = true;
this.selectableDatesInshop = [];
this.selectableDatesMobile = [];
this.months = [];
@ -541,7 +553,6 @@ export default {
// }
}
this.months = months;
this.isLoading = false;
if (config.preSelectedDate) {
this.$nextTick(() => {
@ -729,7 +740,6 @@ export default {
},
async showAnotherMonth() {
if (!this.months) return;
this.isLoading = true;
let monthToShow;
let monthStartDate;
@ -774,7 +784,6 @@ export default {
monthToShowDates[monthToShowDates.length - 1].inputValue
);
this.isLoading = false;
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
this.scrollToElement(monthToShow.monthString);
@ -787,7 +796,6 @@ export default {
},
async updateSelectableDates(monthStart, monthEnd) {
const moreSelectableDates = await this.getMoreDatesCallback(monthStart, monthEnd);
moreSelectableDates.inshopTimeSlotsData.days.forEach((selectableDate) => {
const index = this.selectableDatesInshop.findIndex(
(dateObj) => dateObj.date === selectableDate.date
@ -846,12 +854,6 @@ export default {
},
},
watch: {
isLoading(newValue) {
// if done loading dates, then set to first available date
if (newValue === false && this.firstAvailableSelectableDate) {
this.selectedDate = this.firstAvailableSelectableDate;
}
},
modelValue(newValue) {
this.resetField({
value: newValue,
@ -872,9 +874,14 @@ export default {
</script>
<style lang="scss" scoped>
.date-picker-hidden {
.date-picker-loader {
opacity: 0;
max-height: 0;
&.show-loader {
opacity: 1;
max-height: none;
}
}
.date-picker {
position: relative;
@ -903,12 +910,12 @@ export default {
height: 1.5rem;
}
}
.calendar-grid-container {
.month {
margin: 0 auto 1rem auto;
position: relative;
transition:
height ease 2s,
opacity ease 2s;
height ease 1s,
max-height ease 1s;
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-content: center;
@ -1157,7 +1164,7 @@ export default {
}
.past {
.calendar-grid-container {
.month {
.month-year {
grid-area: 1 / 1 / 2 / 6;
}
@ -1214,7 +1221,7 @@ export default {
}
// new calendar styling overrides
&.calendar-2025 {
.calendar-grid-container .grid-item {
.month .grid-item {
margin: 0 0 0.15rem 0;
}
.month-year {
@ -1316,6 +1323,33 @@ export default {
}
}
}
&.loading-reinitial {
.month {
opacity: 0;
max-height: 0;
}
.month.first-month-shown {
opacity: 1;
max-height: none;
.grid-item.radio-wrapper.selectable-day {
input[type="radio"]:checked + label,
label {
background-color: $white;
border: none;
color: initial;
cursor: default;
&:hover {
border: none;
}
span {
font-family: UrbanistRegular;
}
}
}
}
}
}
:deep(.timeslots .button-inner-wrapper) {
display: flex;

View file

@ -41,9 +41,6 @@ export default {
beforeUnmount() {
// Clean up event listeners and observers
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
if (this._salesforceChatOpenObserver) {
this._salesforceChatOpenObserver.disconnect();
}
},
methods: {
initializeSalesforceWebchat() {
@ -133,6 +130,9 @@ export default {
</script>
<style lang="scss">
.embedded-messaging > .embeddedMessagingFrame[class~="isMaximized"] {
z-index: 9999 !important;
}
.dockableContainer {
&.showDockableContainer {
color: $gray-600;

View file

@ -59,14 +59,6 @@ describe("sierraWebchat.vue", () => {
expect(window.sierraChat.closeChatModal).toHaveBeenCalled();
});
it("dispatches sierra-chat-closed on handleSierraOnClose", () => {
const eventSpy = jest.spyOn(window, "dispatchEvent");
wrapper.vm.createSierraConfig().onClose();
expect(eventSpy).toHaveBeenCalledWith(
expect.objectContaining({ type: "sierra-chat-closed" })
);
});
it("dispatches sierra-chat-transfer on handleSierraOnTransfer", () => {
const eventSpy = jest.spyOn(window, "dispatchEvent");
const transfer = {

View file

@ -7,10 +7,20 @@ import analyticsMixin from "@/mixins/analytics-mixin";
import store from "@/store";
import { routeData } from "@/router/constants/routes";
import { deepClone } from "@/helpers/object-helper";
import { webchatHelper } from "@/helpers/webchat-helper";
export default {
name: "sierraWebchat",
mixins: [analyticsMixin],
data() {
return {
transferInProgress: false,
};
},
setup() {
const { webchatGlobalNonpersistedState, launchWebchat } = webchatHelper();
return { webchatGlobalNonpersistedState, launchWebchat };
},
methods: {
openSierraChatModal() {
const sierra = window.sierraChat || window.sierra || window.SierraChat;
@ -35,11 +45,18 @@ export default {
display: "corner",
onLoad: () => this.openSierraChatModal(),
onOpen: () => {},
onClose: () => window.dispatchEvent(new CustomEvent("sierra-chat-closed")),
onTransfer: (transfer) =>
onClose: () => {
if (!this.transferInProgress) {
this.webchatGlobalNonpersistedState.showWebchatButton = true;
this.transferInProgress = false;
}
},
onTransfer: (transfer) => {
this.transferInProgress = true;
window.dispatchEvent(
new CustomEvent("sierra-chat-transfer", { detail: transfer })
),
);
},
};
},
getSierraContext() {
@ -138,10 +155,7 @@ export default {
}
// Pass data to Salesforce chat if available
const transfer = event.detail;
if (
window.embeddedservice_bootstrap &&
typeof window.embeddedservice_bootstrap.bootstrapEmbeddedService === "function"
) {
if (window.embeddedservice_bootstrap && window.embeddedservice_bootstrap.prechatAPI) {
if (
transfer &&
transfer.data &&
@ -155,37 +169,16 @@ export default {
LastName: transfer.data.last_name,
Email: transfer.data.email,
});
if (
window.embeddedservice_bootstrap.utilAPI &&
typeof window.embeddedservice_bootstrap.utilAPI.launchChat === "function"
) {
window.embeddedservice_bootstrap.utilAPI.launchChat();
return;
}
}
window.embeddedservice_bootstrap.utilAPI.launchChat();
this.webchatGlobalNonpersistedState.shouldLaunchSierra = false;
this.launchWebchat();
}
};
window.addEventListener("sierra-chat-transfer", this._handleSierraTransfer);
// Notify funnel-header about Sierra chat open/close state
this._sierraChatContainerObserver = new MutationObserver(() => {
const isOpen = !!document.querySelector("[data-sierra-chat-container]");
window.dispatchEvent(
new CustomEvent("sierra-chat-visibility", { detail: { open: isOpen } })
);
});
this._sierraChatContainerObserver.observe(document.body, {
childList: true,
subtree: true,
});
},
beforeUnmount() {
window.removeEventListener("launch-sierra-webchat", this.triggerSierraChat);
window.removeEventListener("sierra-chat-transfer", this._handleSierraTransfer);
if (this._sierraChatContainerObserver) {
this._sierraChatContainerObserver.disconnect();
}
},
};
</script>

View file

@ -79,16 +79,7 @@ export default {
return this.getCmsContent(this.cmsWidgetName, "LogoImage");
},
shouldShowWebchatButton() {
// Hide the chat icon if Sierra chat is open
if (this.sierraChatOpen) {
return false;
}
// Show if either Sierra is enabled or Salesforce button is available and not hidden by page
return (
(this.webchatGlobalNonpersistedState.shouldLaunchSierra ||
this.webchatGlobalNonpersistedState.showWebchatButton) &&
!this.shouldHideWebchatButtonOnPage
);
return this.webchatGlobalNonpersistedState.showWebchatButton;
},
},
methods: {
@ -116,10 +107,6 @@ export default {
webchatClicked(event) {
event.preventDefault();
this.launchWebchat();
// Only set sierraChatOpen if Sierra experiment is active
if (this.webchatGlobalNonpersistedState.shouldLaunchSierra) {
this.sierraChatOpen = true;
}
},
syncSierraExperimentFlag() {
const experiments = store.getters.applicationUser.experiments;
@ -159,11 +146,6 @@ export default {
unknownAlertEvent.displayAlert = true;
this.globalAlertMessages.push(unknownAlertEvent);
}
// Listen for Sierra chat open/close events from sierra-webchat
this._handleSierraChatVisibility = (event) => {
this.sierraChatOpen = !!(event.detail && event.detail.open);
};
window.addEventListener("sierra-chat-visibility", this._handleSierraChatVisibility);
// Add scroll listener for header shadow effect
this._handleScroll = () => {
@ -172,8 +154,6 @@ export default {
window.addEventListener("scroll", this._handleScroll);
},
beforeUnmount() {
window.removeEventListener("sierra-chat-closed", this._handleSierraChatClosed);
window.removeEventListener("sierra-chat-visibility", this._handleSierraChatVisibility);
window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer);
window.removeEventListener("scroll", this._handleScroll);
},

View file

@ -25,7 +25,7 @@ defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIR
defineRule(
"email-address-format",
regex(
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
/^([a-zA-Z0-9_.+]+)@([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+([a-zA-Z]{2,})$/,
errorMessages.EMAIL_ADDRESS_FORMAT
)
);

View file

@ -8,6 +8,7 @@ const webchatGlobalNonpersistedState = reactive({
export const webchatHelper = () => {
function launchWebchat() {
webchatGlobalNonpersistedState.showWebchatButton = false;
if (webchatGlobalNonpersistedState.shouldLaunchSierra) {
window.dispatchEvent(new CustomEvent("launch-sierra-webchat")); // launches the sierra chat
} else {

View file

@ -91,7 +91,7 @@ defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIR
defineRule(
"email-address-format",
regex(
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
/^([a-zA-Z0-9_.+]+)@([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+([a-zA-Z]{2,})$/,
errorMessages.EMAIL_ADDRESS_FORMAT
)
);

View file

@ -70,7 +70,7 @@ defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIR
defineRule(
"email-address-format",
regex(
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
/^([a-zA-Z0-9_.+]+)@([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+([a-zA-Z]{2,})$/,
errorMessages.EMAIL_ADDRESS_FORMAT
)
);

View file

@ -246,7 +246,7 @@ export default {
console.log(`Price = ${this.amountDue}`);
console.log(`Adyen Price = ${this.adyenPriceTotal}`);
const requestBody = this.adyenInitRequestInfo;
const requestBody = this.getAdyenInitRequestInfo();
console.log(`Calling with:`);
console.log(requestBody);
@ -294,6 +294,15 @@ export default {
console.log(checkout);
const expiryTime = new Date(checkout.options.expiresAt);
const expiryInterval = expiryTime.getTime() - new Date().getTime();
const handleTimeout = () => {
this.resetAdyenDropin();
};
const timeout = setTimeout(handleTimeout, expiryInterval);
const configuration = {
paymentMethodsConfiguration: {
ideal: {
@ -505,14 +514,26 @@ export default {
return;
}
},
},
computed: {
piaType() {
return this.$store.getters.order.payment.piaType;
async resetAdyenDropin() {
this?.dropinComponent?.unmount();
await this.initializeAdyen();
},
adyenInitRequestInfo() {
// Now a Method so it is always freshly called and not cached.
getIdempotencyKey() {
const currentDateTime = new Date();
const currentHour = currentDateTime.getUTCHours();
const currentDate = currentDateTime.getUTCDate();
const id = this?.$store?.getters?.order?.referralCorrelationId;
const system = this.sourceSystem;
const total = this.adyenPriceTotal;
return `${id}-${system}-${currentDate}-${currentHour}-${total}`;
},
// Now a Method so it is always freshly called and not cached.
getAdyenInitRequestInfo() {
return {
sourceSystem: this.sourceSystem,
referralSequenceNumber: this.$store.getters.order.referralSequenceNumber,
@ -529,9 +550,15 @@ export default {
IP: "127.0.0.1", // TODO
firstName: this.$store.getters.order.customer.firstName,
lastName: this.$store.getters.order.customer.lastName,
idempotencyKey: this.idempotencyKey,
idempotencyKey: this.getIdempotencyKey(),
};
},
},
computed: {
piaType() {
return this.$store.getters.order.payment.piaType;
},
workOrderNumberLastSixDigits() {
const workOrderNumber = this.$store.getters.order.workOrderNumber ?? "";
@ -588,10 +615,6 @@ export default {
};
},
idempotencyKey() {
return `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`;
},
sourceSystem() {
return "FMG-2.0";
},
@ -601,7 +624,7 @@ export default {
},
adyenPriceTotal() {
return this.amountDue * 100;
return Math.round(this.amountDue * 100);
},
// Cart info

View file

@ -745,10 +745,16 @@ export default {
}
},
updatePaymentMethod(paymentMethod) {
// if the payment method is not set yet, then set it based on what HOP sends us
debugLog(
`updatePaymentMethod(${paymentMethod}) and existing store.getters.order.payment.piaType = ${store?.getters?.order?.payment?.piaType}`
);
// if the payment method is not set yet, then set it based on what HOP sends
// or if its not already set to AfterPay, always set it based on what HOP sends
if (
store.getters.order.payment.piaType == paymentMethods.PAY_NOW ||
store.getters.order.payment.piaType == paymentMethods.NONE
store.getters.order.payment.piaType == paymentMethods.NONE ||
store.getters.order.payment.piaType != paymentMethods.AFTERPAY
) {
console.log(new Date() + " UPDATE PaymentMethod from HOP:" + paymentMethod);
this.dispatchStoreAction(

View file

@ -0,0 +1,70 @@
import policyInfo from "@/layouts/policy-info/policy-info.vue";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
settleAllPromises: jest.fn(),
}));
// Mock fetchCmsContentForPage
jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: jest.fn(),
}));
describe("policy-info.vue", () => {
describe("component rendering", () => {
test("renders funnelHeader component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "funnelHeader" }).exists()).toBe(true);
});
test("renders funnelSubHeader component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "funnelSubHeader" }).exists()).toBe(true);
});
test("renders navbar component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "navbar" }).exists()).toBe(true);
});
test("renders Form component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "Form" }).exists()).toBe(true);
});
});
describe("methods", () => {
test("arePagePrerequisitesValid returns true", () => {
const { wrapper } = setupMocks();
expect(wrapper.vm.arePagePrerequisitesValid()).toBe(true);
});
});
});
function setupMocks() {
const mockCmsContent = {
FunnelHeaderWidget: { Text: "Header" },
FunnelSubHeaderWidget: { Text: "Subheader" },
FunnelFooterWidget: { Text: "Footer" },
};
fetchCmsContentForPage.mockResolvedValue(mockCmsContent);
settleAllPromises.mockResolvedValue({ cmsContent: mockCmsContent });
const mountOptions = getMountOptions({
route: { name: "policy-info", query: {}, params: {} },
router: {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
},
});
const wrapper = shallowMount(policyInfo, mountOptions);
return { wrapper };
}

View file

@ -0,0 +1,72 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
<div class="container">
<div class="row">
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
<!--
... Page code goes here.
-->
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</Form>
</template>
<script>
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import { Form } from "vee-validate";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
export default {
name: "policy-info",
mixins: [],
data() {
return {};
},
components: {
Form,
funnelHeader,
navbar,
funnelSubHeader,
},
// Ensure CMS content is fetched during route navigation without depending on `to`/`from`
async beforeRouteEnter(to, from, next) {
const pageName = to.name;
const cmsContentPromise = fetchCmsContentForPage(pageName);
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
methods: {
async backButtonAction() {
// Stub, for navigating back via nav-bar.
},
async forwardButtonAction() {
// Stub, for navigating forward via nav-bar
},
arePagePrerequisitesValid() {
return true;
},
},
};
</script>

View file

@ -72,10 +72,10 @@ export function isDropOffRouteCode(routeCode) {
);
}
export function getTodayDate(routeCode) {
export function getTodayDate() {
return new Date();
}
export function getTodayDateString(routeCode) {
export function getTodayDateString() {
return convertDateToDateString(getTodayDate());
}

View file

@ -0,0 +1,183 @@
// Components
import schedule from "@/layouts/schedule/schedule.vue";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { routeData } from "@/router/constants/routes";
import store from "@/store";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
const storeMocked = {
getters: {
payment: { isInsurance: false },
order: {
payment: { isInsurance: false, insuranceCoverage: { isVerified: false } },
referralNumber: "",
serviceLocation: {
address: "",
address2: "",
city: "",
state: "",
zipCode: "00000",
zipCodeCtu: "000",
appointmentType: null,
provider: {},
isVehicleProtected: false,
},
schedule: {
date: "",
routeCode: "",
startTime: "",
endTime: "",
jobMinMinutes: null,
jobMaxMinutes: null,
},
policy: {
isItac: false,
isNoComp: false,
},
damage: { isRepair: false },
lineItems: { glassParts: [] },
},
vehicle: {
cardId: "",
},
lineItems: {
supportingItems: [],
},
applicationUser: {
experiments: [],
},
isMobileAppointment: false,
},
};
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateToHeritageFunnel: jest.fn(),
}));
describe("schedule.vue", () => {
describe("navigation", () => {
test("backButtonAction => non-insurance: navigateWithoutSaving called", () => {
// Arrange
const { wrapper, mocks } = setupMocks();
// Act
wrapper.vm.backButtonAction();
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
mocks.navigationScenarios.CLICKED_BACK,
routeData.SCHEDULE.name
);
});
test("backButtonAction => insurance verified: navigateToHeritageFunnel called", () => {
// Arrange
store.getters.order.payment.isInsurance = true;
store.getters.order.payment.insuranceCoverage = { isVerified: true };
store.getters.order.referralNumber = "";
const { wrapper } = setupMocks();
// Act
wrapper.vm.backButtonAction();
// Assert
expect(navigateToHeritageFunnel).toHaveBeenCalledWith({
shouldSaveSession: false,
pageNameToLog: "schedule",
navType: "back",
});
});
});
describe("computed: isForwardActionDisabled", () => {
test("should be true when routeCode missing or appointmentType not set", async () => {
const { wrapper } = setupMocks();
await wrapper.setData({
selectedTimeSlotInfo: { timeSlot: { routeCode: null } },
appointmentType: null,
});
expect(wrapper.vm.isForwardActionDisabled).toBe(true);
});
test("should be false when routeCode present and appointmentType set", async () => {
const { wrapper } = setupMocks();
await wrapper.setData({
selectedTimeSlotInfo: { timeSlot: { routeCode: "RC123" } },
appointmentType: "SomeType",
});
expect(wrapper.vm.isForwardActionDisabled).toBe(false);
});
});
describe("computed: selectedRouteCodeData", () => {
test("should return null when routeCode not set", async () => {
const { wrapper } = setupMocks();
await wrapper.setData({
selectedTimeSlotInfo: { timeSlot: { routeCode: null } },
});
expect(wrapper.vm.selectedRouteCodeData).toBeNull();
});
test("should return routeCode and premium flag when set", async () => {
const { wrapper } = setupMocks();
await wrapper.setData({
selectedTimeSlotInfo: {
timeSlot: { routeCode: "RC999" },
isPremiumAppointment: true,
},
});
expect(wrapper.vm.selectedRouteCodeData).toEqual({
routeCode: "RC999",
isPremiumAppointment: true,
});
});
});
describe("forwardButtonAction (early exit path)", () => {
test("shows recal acknowledgement popup and does not navigate when required", async () => {
const { wrapper } = setupMocks();
// Force the recal-ack modal path
wrapper.vm.showRecalAcknowledgementModal = jest.fn(() => true);
await wrapper.setData({ isRecalAcknowledgedForScheduling: null });
await wrapper.vm.forwardButtonAction();
expect(wrapper.vm.showRecalAcknowledgementPopup).toBe(true);
expect(wrapper.vm.$router.navigateWithSaving).not.toHaveBeenCalled();
});
});
});
function setupMocks(mountOptionsMockData = {}) {
const route = { name: "schedule" };
const defaultMountOptions = {
route: route,
router: {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
},
store: { ...storeMocked },
};
const mockBaseMixin = {
methods: {
getTierOnePackagePrice: jest.fn(),
filterOutFees: jest.fn(),
getCmsContent: jest.fn(),
},
};
const baseMountOptions = getMountOptions({
...defaultMountOptions,
...mountOptionsMockData,
});
baseMountOptions.global.mixins = [mockBaseMixin];
baseMountOptions.global.mocks.pageName = route.name;
const wrapper = shallowMount(schedule, baseMountOptions);
wrapper.getCmsContent = jest.fn();
return { wrapper, mocks: baseMountOptions.global?.mocks || {} };
}

View file

@ -98,15 +98,11 @@
linkWidgetName="ServiceZipLinkWidget"
modalWidgetName="ServiceZipModalWidget" />
</div>
<div
class="row your-shop-location"
v-if="appointmentType && appointmentType !== appointmentTypeStrings.MOBILE">
<div class="row your-shop-location" v-if="showShopLocationComponent">
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
<div>
<shopLocation
v-if="
appointmentType && appointmentType !== appointmentTypeStrings.MOBILE
"
v-if="showShopLocationComponent"
:selectedShop="selectedShopAnswer"
:selectedProviderNumberFromParent="selectedProvider?.providerNumber"
:shopProviderDataFromParent="shopProviderData"
@ -153,7 +149,8 @@
:pricingByDayBasePrice="pricingByDayBasePrice"
:pricingByDayUpcharge="pricingByDayUpcharge"
:showPricingByDay="showPricingByDay"
:isPricingByDayExperiment="isPricingByDayExperiment" />
:isPricingByDayExperiment="isPricingByDayExperiment"
:loadingStatus="calendarLoadingStatus" />
<timeSlotQuestion
ref="timeSlotModalQuestion"
v-if="showTimeSlotQuestion"
@ -172,7 +169,8 @@
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
:isSameDay="isSameDay"
:selectedRouteCodeData="selectedRouteCodeData"
@waitListRequested="handleWaitListRequested" />
@waitListRequested="handleWaitListRequested"
:lazyLoadAmountToLoad="10" />
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
@ -445,15 +443,14 @@ export default {
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
mobilePremiumAppointmentFee: null,
waitListRequested: null,
displayWaitList: null,
pricingByDayUpchargeLineItem: null,
includePricingByDayUpcharge: null,
isPricingByDayExperiment: null,
pricingByDayBasePrice: null,
pricingByDayUpcharge: null,
showPricingByDay: null,
selectableDatesInshop: [],
selectableDatesMobile: [],
selectableDatesInshop: {},
selectableDatesMobile: {},
preSelectedDate: null,
streetAddress: this.getServiceAddressFromStore(),
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
@ -486,6 +483,7 @@ export default {
experimentSettings.SHOW_MOBILE_FIRST_APPT,
"true"
),
calendarLoadingStatus: "none",
};
},
async beforeRouteEnter(to, from, next) {
@ -596,6 +594,8 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.showLoadingModal();
vm.calendarLoadingStatus = "initial";
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.mobilePremiumAppointmentFee = pricedPremiumFee;
@ -616,6 +616,9 @@ export default {
);
vm.initializeDatePicker().then(() => {
vm.showMobileFirstModal();
vm.hideLoadingModal();
vm.calendarLoadingStatus = "none";
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
});
});
},
@ -632,6 +635,20 @@ export default {
this.handleZipCodeChange(newValue);
},
},
showShopLocationComponent() {
// If we get no inshop or mobile data, then show the shoplocation component so the user is not
// looking at a half blank screen with no way to try another zip/location.
if (
(this.selectableDatesInshop === undefined &&
this.selectableDatesMobile === undefined) ||
(this.selectableDatesInshop?.days?.length === 0 &&
this.selectableDatesMobile?.days?.length === 0) ||
(this.appointmentType && this.appointmentType !== AppointmentTypeStrings.MOBILE)
) {
return true;
}
return false;
},
isMobileSelected() {
return (
this.appointmentType == AppointmentTypeStrings.MOBILE ||
@ -887,6 +904,53 @@ export default {
? this.$refs.mobileFirstModal?.getIsModalOpen()
: false;
},
displayWaitList() {
if (!this.appointmentType) return false;
const firstMobileDateString =
Array.isArray(this.selectableDatesMobile.days) &&
this.selectableDatesMobile.days.length > 0
? this.selectableDatesMobile.days[0].date
: null;
const firstInshopDateString =
Array.isArray(this.selectableDatesInshop.days) &&
this.selectableDatesInshop.days.length > 0
? this.selectableDatesInshop.days[0].date
: null;
const dateString =
this.isMobileSelected && firstMobileDateString
? firstMobileDateString
: firstInshopDateString;
if (
experimentMixin.methods.hasSettingEqualTo(
experimentSettings.DISPLAY_WAITLIST,
"true"
) &&
dateString
) {
const [year, month, day] = dateString.split("-").map(Number);
const targetDate = new Date(year, month - 1, day);
const currentDate = getTodayDate();
const futureDate = new Date(currentDate);
const experimentThresholdDays = experimentMixin.methods.hasSetting(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
? parseInt(
experimentMixin.methods.getSettingValue(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
)
: 0;
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
if (targetDate >= futureDate) {
return true;
} else {
return false;
}
}
return false;
},
hasSelectableDatesLoaded() {
if (this.isMobileSelected) {
return this.selectableDatesMobile?.days?.length > 0;
@ -1155,9 +1219,10 @@ export default {
);
}
},
async onShopSelected(shopQuestionPopUpData) {
onShopSelected(shopQuestionPopUpData) {
this.resetWaitlist();
this.preSelectedDate = null;
this.calendarLoadingStatus = "reinitial";
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
const oldProvider = this.selectedProvider;
const selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
@ -1177,11 +1242,16 @@ export default {
this.updateSelectedProvider(selectedProvider);
}
}
await this.initializeDatePicker();
this.setSelectedDateToFirstAvailable();
this.initializeDatePicker().then(() => {
this.showMobileFirstModal();
this.hideLoadingModal();
this.calendarLoadingStatus = "none";
this.setSelectedDateToFirstAvailable();
});
},
async getMoreScheduleData(startDate, endDate) {
// called only when "View more dates" is clicked; not on initial page load
this.calendarLoadingStatus = "more";
const moreShopTimeSlots = await getScheduleApiResponse({
startDateString: startDate,
endDateString: endDate,
@ -1193,6 +1263,7 @@ export default {
includeMobileTimeSlots: this.isServiceableMobile,
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
});
this.calendarLoadingStatus = "none";
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
@ -1231,7 +1302,6 @@ export default {
await datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
this.selectableDatesMobile =
await datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
this.setDisplayWaitList();
if (this.preSelectedDate) {
this.selectedDate = this.preSelectedDate;
@ -1243,11 +1313,8 @@ export default {
) {
await this.$nextTick();
await this.$refs.datePicker.showAnotherMonth();
this.setDisplayWaitList();
}
}
this.isShowMobileFirstAppt && this.hideLoadingModal();
},
getIsRecalAcknowledgedForSchedulingFromStore() {
return store.getters.order.isRecalAcknowledgedForScheduling;
@ -1261,8 +1328,8 @@ export default {
if (isMobileSelected === undefined) {
isMobileSelected = this.appointmentType === AppointmentTypeStrings.MOBILE;
}
let storedDate = store.getters.order.schedule.date;
if (isMobileSelected) storedDate += "-mobile";
let storedDate = store.getters.order.schedule?.date;
if (isMobileSelected && storedDate) storedDate += "-mobile";
return storedDate;
},
getSelectedTimeSlotInfo() {
@ -1517,39 +1584,6 @@ export default {
}
this.showLoadingModal();
},
setDisplayWaitList() {
const dateString = this.isMobileSelected
? this.selectableDatesMobile?.days[0]?.date
: this.selectableDatesInshop?.days[0]?.date;
if (
experimentMixin.methods.hasSettingEqualTo(
experimentSettings.DISPLAY_WAITLIST,
"true"
) &&
dateString
) {
const [year, month, day] = dateString.split("-").map(Number);
const targetDate = new Date(year, month - 1, day);
const currentDate = getTodayDate();
const futureDate = new Date(currentDate);
const experimentThresholdDays = experimentMixin.methods.hasSetting(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
? parseInt(
experimentMixin.methods.getSettingValue(
experimentSettings.WAITLIST_THRESHOLD_DAYS
)
)
: 0;
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
if (targetDate >= futureDate) {
this.displayWaitList = true;
} else {
this.displayWaitList = false;
}
}
},
updateSupportingItems() {
const supportingItems = this.getSupportingItems();
@ -1758,6 +1792,7 @@ export default {
}
this.resetMobileLocation();
this.calendarLoadingStatus = "initial";
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
this.shopProviderData = result.data;
@ -1768,6 +1803,9 @@ export default {
this.selectedDate = null;
this.initializeDatePicker().then(() => {
this.showMobileFirstModal();
this.hideLoadingModal();
this.calendarLoadingStatus = "none";
this.setSelectedDateToFirstAvailable();
});
});
},
@ -1820,7 +1858,6 @@ export default {
this.appointmentType = null;
}
this.setSelectedDateToFirstAvailable(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
this.setDisplayWaitList(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now
},
setSelectedDateToFirstAvailable() {
this.selectedDate = this.getFirstAvailableDate();

View file

@ -36,7 +36,10 @@
isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '',
]"
:answers="availableTimeSlots"
:lazyLoad="{ isLazyLoad: !isMobileAppointment, amountToLoad: 10 }"
:lazyLoad="{
isLazyLoad: isLazyLoad,
amountToLoad: lazyLoadAmountToLoad || 10,
}"
groupName="chooseTimeSlot"
textPosition="text-center"
questionText="Available times:"
@ -134,6 +137,7 @@ export default {
isSameDay: Boolean,
displayWaitList: Boolean,
selectedRouteCodeData: Object,
lazyLoadAmountToLoad: Number,
},
data() {
return {
@ -396,6 +400,26 @@ export default {
!this.isDropOffAppointmentAvailable
);
},
isLazyLoad() {
if (this.availableTimeSlots?.length < this.lazyLoadAmountToLoad + 1) return false;
if (this.isMobileAppointment) return false;
if (this.selectedAnswerForTimeSlots && !this.isSelectedInshopTimeSlotShowing)
return false;
return true;
},
availableTimeSlotsForInshop() {
return this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate?.timeSlots);
},
isSelectedInshopTimeSlotShowing() {
if (!this.selectedAnswerForTimeSlots) return false;
const idx = this.availableTimeSlotsForInshop?.findIndex((timeSlot) => {
return timeSlot.value === this.selectedAnswerForTimeSlots;
});
if (idx > -1 && idx < this.lazyLoadAmountToLoad) {
return true;
}
return false;
},
},
methods: {
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
@ -453,6 +477,7 @@ export default {
// }
// },
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
if (!timeSlotsForSelectedDate) return;
return timeSlotsForSelectedDate
.filter((timeSlot) => {
return !isDropOffRouteCode(timeSlot.id);

View file

@ -93,6 +93,7 @@ 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 { debugLog } from "@/helpers/debug-log-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
@ -144,6 +145,7 @@ export default {
if (store.getters.externalParameterState?.isExternalParameter) {
await nextTick();
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
debugLog(`**** vehicle-damage is form valid: ${isValid} ****`);
if (
store.getters.vehicle?.vehicleSubType === "MOTOR HOME" &&
store.getters.externalParameterDamage.damageType?.toUpperCase() ===
@ -183,7 +185,10 @@ export default {
const selectedGlassToReplace =
resultMap.damageOptions.windshieldOptions.availableReplacementOptions;
if (selectedGlassToReplace == damageLocationsSelected.SINGLE) {
if (
selectedGlassToReplace == damageLocationsSelected.SINGLE &&
store.getters.externalParameterDamage.damageType
) {
vm.selectedWindshieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.SINGLE
);
@ -251,6 +256,8 @@ export default {
if (store.getters.vehicle.carId) {
return true;
}
debugLog("**** Vehicle Page Prereq invalid ****");
debugLog("store.getters.vehicle.carId:", store.getters.vehicle?.carId, true);
return false;
},
attachCustomEvents() {

View file

@ -31,7 +31,6 @@ import { applicationConfig } from "../constants/application-config";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { routeData } from "@/router/constants/routes";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { Variables } from "../constants/analytics";
import { containsRecalParts, getRecalPartNumbers } from "@/helpers/recal-helper";
import { getAmountDue, getSubTotal, getSalesTax } from "@/helpers/pricing-helper.js";
import { partTypeStrings } from "@/constants/part-type-strings";
@ -248,9 +247,25 @@ export default {
}
var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`;
var currentPageName = getPageNameFromRouter();
// add query strings to the page name for debugging. on the vehicle page, if from an external link, pull it from the stash
if (currentPageName === "vehicle") {
if (!window.location.search) {
if (store.getters.externalParameterState?.qsStash) {
currentPageName += `${store.getters.externalParameterState.qsStash}`;
}
} else {
currentPageName += `${window.location.search}`;
}
} else {
if (window.location.search) {
currentPageName += `${window.location.search}`;
}
}
var sessionData = {};
sessionData.currentPage = getPageNameFromRouter();
sessionData.currentPage = currentPageName;
sessionData.sid = getSessionIdValue();
sessionData.deviceId = getDeviceIdValue();
sessionData.fmgSessionId = applicationUser?.savedSessionId;
@ -952,9 +967,6 @@ export default {
ValueToLogTypes() {
return ValueToLogTypes;
},
Variables() {
return Variables;
},
},
};

View file

@ -51,6 +51,10 @@ export const routeData = {
name: "quote",
path: "/quote",
},
POLICY_INFO: {
name: "policy-info",
path: "/policy-info",
},
INSURANCE_COMPANY: {
name: "insurance-company",
path: "/insurance-company",

View file

@ -429,6 +429,10 @@ const routingTable = function () {
},
],
},
{
pageName: routeData.POLICY_INFO.name,
maps: [],
},
{
pageName: routeData.INSURANCE_COMPANY.name,
maps: [

View file

@ -8,6 +8,7 @@ import { storeActions } from "@/constants/store-actions";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { queryStrings } from "@/constants/query-strings";
import { externalParameterStatus } from "@/constants/external-parameters";
import { debugLog } from "@/helpers/debug-log-helper";
export async function initializeFromQueryStrings() {
// Get advertiser info
@ -26,12 +27,13 @@ function setupAdvertisersAndAffiliates() {
}
function updateExternalParameterState() {
debugLog("--- 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);
let externalParameterZipCode = getQuerystringParameter(queryStrings.SERVICE_ZIP);
const externalParameterEmail = getQuerystringParameter(queryStrings.EMAIL);
const externalParameterIsInsurance = getQuerystringParameter(queryStrings.IS_INSURANCE);
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
@ -39,6 +41,27 @@ function updateExternalParameterState() {
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
const externalParameterSource = getQuerystringParameter(queryStrings.EXPERIMENTS);
const externalParameterPhoneNumber = getQuerystringParameter(queryStrings.PHONE_NUMBER);
if (!externalParameterZipCode) {
externalParameterZipCode = getQuerystringParameter(queryStrings.ZIP_CODE);
}
if (externalParameterYear || externalParameterZipCode) {
debugLog("externalParameterYear:", externalParameterYear);
debugLog("externalParameterMake:", externalParameterMake);
debugLog("externalParameterModel:", externalParameterModel);
debugLog("externalParameterStyle:", externalParameterStyle);
debugLog("externalParameterDamage:", externalParameterDamage);
debugLog("externalParameterZipCode:", externalParameterZipCode);
debugLog("externalParameterEmail:", externalParameterEmail);
debugLog("externalParameterIsInsurance:", externalParameterIsInsurance);
debugLog("externalParameterVinSelection:", externalParameterVinSelection);
debugLog("externalParameterServicePackage:", externalParameterServicePackage);
debugLog("externalParameterNumberOfChips:", externalParameterNumberOfChips);
debugLog("externalParameterSource:", externalParameterSource);
debugLog("externalPhoneNumber:", externalParameterPhoneNumber);
}
if (
externalParameterYear &&
externalParameterMake &&
@ -51,6 +74,7 @@ function updateExternalParameterState() {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MAKE, externalParameterMake);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MODEL, externalParameterModel);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_STYLE, externalParameterStyle);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_QS_STASH, window.location.search);
}
if (
externalParameterDamage &&
@ -100,6 +124,7 @@ function updateExternalParameterState() {
externalParameterPhoneNumber
);
}
debugLog("--- updateExternalParameterState END ---");
}
// if there is an existing external parameter state then they have already been through from an external source(LeadGen) and

View file

@ -41,6 +41,7 @@ export const routes = [
createRoute(routeData.CONFIRMATION),
createRoute(routeData.RETURN_USER),
createRoute(routeData.MOBILE_DETAILS),
createRoute(routeData.POLICY_INFO),
// Virtual pages (resolve to a non-virtual page.)
createVirtualRoute(routeData.LANDING, landingBeforeEnter),

View file

@ -516,6 +516,10 @@ export const mutations = {
externalParameterState.vehicle.style = style;
saveExternalParameterState(externalParameterState);
},
updateExternalParameterQsStash(state, qsStash) {
externalParameterState.qsStash = qsStash;
saveExternalParameterState(externalParameterState);
},
updateExternalParameterIsRepair(state, isRepair) {
externalParameterState.vehicleDamage.isRepair = isRepair;
saveExternalParameterState(externalParameterState);
@ -4142,6 +4146,7 @@ function createExternalParameterDefaultState() {
// create default externalParameter state
const externalParameterDefaultState = {
isExternalParameter: externalParameterStatus.NOT_SET,
qsStash: null,
vehicle: {
year: null,
make: null,