Merge branch 'develop' into CASH-132-dontation-widget-foster-love

This commit is contained in:
Bryan Mauger 2025-02-03 15:36:19 -05:00
commit 6ef5a30c5c
31 changed files with 859 additions and 236 deletions

View file

@ -27,7 +27,6 @@ module.exports = {
"!src/layouts/insurance/*.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/insurance-company/*.vue", // Temp test exclusion while in development
"!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development "!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development
"!src/layouts/return-user/*.vue", // Temp test exclusion while in development
// END // END
], // ! means exclude from coverage. ], // ! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],

View file

@ -125,7 +125,7 @@ const endpoints = {
method: "POST", method: "POST",
}, },
SaveQuote: { SaveQuote: {
url: "/order/api/v1/order/initiate-saved-progress-email", url: "/order/api/v1/order/save-progress",
method: "POST", method: "POST",
}, },
GetSignature: { GetSignature: {
@ -168,6 +168,10 @@ const endpoints = {
url: "/analytics/api/v1/analytics/initialize", url: "/analytics/api/v1/analytics/initialize",
method: "POST", method: "POST",
}, },
LogPartQuestions: {
url: "/analytics/api/v1/analytics/log-part-questions",
method: "POST",
},
GetExperimentsByUser: { GetExperimentsByUser: {
url: "/analytics/api/v1/analytics/get-experiments", url: "/analytics/api/v1/analytics/get-experiments",
method: "GET", method: "GET",

View file

@ -1,6 +1,7 @@
const experimentUniverses = { const experimentUniverses = {
CONCEPT_FUNNEL: "ConceptFunnel", CONCEPT_FUNNEL: "ConceptFunnel",
RECAL_PRICE_REMOVAL: "NextGen_RecalPriceRemoval", RECAL_PRICE_REMOVAL: "NextGen_RecalPriceRemoval",
MSR: "MSR",
}; };
const experimentSettings = { const experimentSettings = {
@ -16,6 +17,7 @@ const experimentSettings = {
RECAL_PRICE_REMOVE: "RecalPriceRemove", RECAL_PRICE_REMOVE: "RecalPriceRemove",
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold", INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold",
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold", INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold",
DISPLAY_MSR: "DisplayMSR",
}; };
const experimentTriggers = { const experimentTriggers = {

View file

@ -5,4 +5,5 @@ export const headerKeys = {
SESSION_SEQUENCE_NUMBER: "X-Session-Sequence-Number", SESSION_SEQUENCE_NUMBER: "X-Session-Sequence-Number",
TRANSACTION_ID: "X-Transaction-Id", TRANSACTION_ID: "X-Transaction-Id",
EON: "X-Enterprise-Order-Number", EON: "X-Enterprise-Order-Number",
LOG_ENABLED: "log-enabled"
}; };

View file

@ -0,0 +1,7 @@
const partNumberStrings = {
// Recalibration
MOBILE_STATIC_RECAL_FEE: "RECAL MOBILE",
MOBILE_DUAL_RECAL_FEE: "RECAL MOBILEDUAL",
};
export { partNumberStrings };

View file

@ -42,10 +42,6 @@ const storeActions = {
VALIDATE_ZIP: "validateZip", VALIDATE_ZIP: "validateZip",
PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "priceOrderItemsAndSaveServerData", PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "priceOrderItemsAndSaveServerData",
TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "taxOrderItemsAndSaveServerData", TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "taxOrderItemsAndSaveServerData",
LOG_EXPERIMENT_EXPOSURE: "logExperimentExposure",
LOG_PAGE_VIEW: "logPageView",
LOG_CUSTOM_EVENT: "logCustomEvent",
INITIALIZE_SESSION: "initializeSession",
GET_EXPERIMENTS_BY_USER: "GetExperimentsByUser", GET_EXPERIMENTS_BY_USER: "GetExperimentsByUser",
RUN_EXPERIMENTS_FOR_TRIGGER: "runExperimentsForTrigger", RUN_EXPERIMENTS_FOR_TRIGGER: "runExperimentsForTrigger",
CLEAR_VIN: "clearVin", CLEAR_VIN: "clearVin",
@ -57,6 +53,13 @@ const storeActions = {
GET_BILL_TO_ACCOUNT_NUMBER: "getBillToAccountNumber", GET_BILL_TO_ACCOUNT_NUMBER: "getBillToAccountNumber",
GET_RECAL_PARTS_AND_SAVE_TO_LINE_ITEMS: "getRecalPartsAndSaveToLineItems", GET_RECAL_PARTS_AND_SAVE_TO_LINE_ITEMS: "getRecalPartsAndSaveToLineItems",
// Analytics
LOG_EXPERIMENT_EXPOSURE: "logExperimentExposure",
LOG_PAGE_VIEW: "logPageView",
LOG_CUSTOM_EVENT: "logCustomEvent",
INITIALIZE_SESSION: "initializeSession",
LOG_PART_QUESTIONS: "logPartQuestions",
// DEPENDENCY MUTATIONS // DEPENDENCY MUTATIONS
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleStateAndDependencies", RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleStateAndDependencies",
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies", RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",

View file

@ -29,9 +29,10 @@
<slot></slot> <slot></slot>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<slot name="modal-footer-slot"></slot>
<modalButtonMain <modalButtonMain
isPrimary isPrimary
class="w-100" class="w-100 modal-footer-button"
:id="modalId + '-modalbtn'" :id="modalId + '-modalbtn'"
ref="modalButtonMain" ref="modalButtonMain"
loaderColor="white" loaderColor="white"
@ -45,6 +46,9 @@
</template> </template>
<script> <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 modalButtonMain from "@/digital-components/modal/ux-components/modal-button-main/modal-button-main";
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
import { useForm } from "vee-validate"; import { useForm } from "vee-validate";
@ -86,7 +90,15 @@ export default {
async validateAndEmit() { async validateAndEmit() {
const validationResult = await this.validate(); const validationResult = await this.validate();
if (validationResult.valid) { if (validationResult.valid) {
this.$emit("footer-button-event"); if (analyticsMixin.methods.sessionExpired()) {
analyticsMixin.methods.initSession();
router.push({
path: "/",
query: { fmgPage: fmgPageValues.RETURN_USER },
});
} else {
this.$emit("footer-button-event");
}
} else { } else {
this.resetButtonStyle(); this.resetButtonStyle();
} }
@ -247,56 +259,7 @@ export default {
} }
} }
} }
.save-progress-modal-question {
p.modal-body {
padding: 0;
}
.modal-body {
display: flex;
flex-direction: column;
.textbox-question {
padding: 0;
margin: 0 0 1.5rem 0;
}
}
}
.save-progress-modal-question,
.save-progress-popup-question {
p.modal-body {
padding: 0;
}
.modal-body {
display: flex;
flex-direction: column;
.textbox-question {
label {
text-align: left;
font-weight: 900;
}
}
}
.modal-body-inner {
text-align: center;
margin-bottom: 1.5rem;
}
.modal-disclaimer {
font-size: 0.75rem;
order: 2;
text-align: left;
a {
text-decoration: none;
}
}
.modal-footer {
margin: 0 0 1.5rem 0;
padding-top: 0;
padding-bottom: 0;
}
}
body { body {
.modal-backdrop { .modal-backdrop {
height: 100%; height: 100%;

View file

@ -45,6 +45,9 @@
<script> <script>
import textLink from "@/ux-components/text-link/text-link"; import textLink from "@/ux-components/text-link/text-link";
import buttonMain from "@/ux-components/button-main/button-main"; 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 { export default {
name: "navbar", name: "navbar",
@ -94,10 +97,28 @@ export default {
document.onkeydown = function (e) { document.onkeydown = function (e) {
return false; return false;
}; };
this.$emit("ForwardClicked"); // check session expired and initSession to recreate cookies
if (analyticsMixin.methods.sessionExpired()) {
this.routeReturnUser();
} else {
this.$emit("ForwardClicked");
}
}, },
linkClick() { linkClick() {
this.$emit("BackClicked"); // check session expired and initSession to recreate cookies
if (analyticsMixin.methods.sessionExpired()) {
this.routeReturnUser();
} else {
this.$emit("BackClicked");
}
},
routeReturnUser() {
analyticsMixin.methods.initSession();
router.push({
path: "/",
query: { fmgPage: fmgPageValues.RETURN_USER },
});
}, },
}, },
}; };

View file

@ -1,13 +1,12 @@
<template> <template>
<div class="save-progress-modal-question" :class="isProgressSaved ? 'progress-saved' : ''"> <div class="save-progress-modal-question" :class="isProgressSaved ? 'progress-saved' : ''">
<buttonMain <buttonMain
ref="buttonMain"
type="button" type="button"
v-if="!isProgressSaved" v-if="!isProgressSaved"
:buttonText="buttonText" :buttonText="buttonText"
loaderColor="white" loaderColor="white"
:suppressLoader="true" :suppressLoader="true"
class="save-progress-button" class="open-save-progress-button"
@click-event="openModal" /> @click-event="openModal" />
<alert <alert
class="my-4" class="my-4"
@ -21,12 +20,14 @@
:onModalClosedCallback="onModalClosed" :onModalClosedCallback="onModalClosed"
:footerButtonText="modalButtonText" :footerButtonText="modalButtonText"
@footer-button-event="saveProgress"> @footer-button-event="saveProgress">
<p class="modal-body-inner">{{ modalBodyText }}</p> <p class="modal-body-inner" v-html="modalBodyText"></p>
<saveProgressQuestion <saveProgressQuestion
ref="saveProgressQuestion" ref="saveProgressQuestion"
v-model="userInput" v-model="userInput"
cmsWidgetName="SaveProgressQuestionWidget" /> cmsWidgetName="SaveProgressQuestionWidget" />
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p> <template v-slot:modal-footer-slot>
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
</template>
</modal> </modal>
</div> </div>
</template> </template>
@ -112,7 +113,7 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss">
.save-progress-modal-question { .save-progress-modal-question {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
order: 2; order: 2;
@ -128,7 +129,7 @@ export default {
background: none; background: none;
} }
.save-progress-button { .open-save-progress-button {
order: 4; order: 4;
position: relative; position: relative;
color: $blue; color: $blue;
@ -160,12 +161,57 @@ export default {
transition: background 0s 0s ease-in-out; transition: background 0s 0s ease-in-out;
} }
} }
:deep(.alert) { .modal.modal-component .modal-dialog .modal-content {
.modal-header {
padding-top: 0;
margin-top: 1.875rem;
}
.modal-body {
display: flex;
flex-direction: column;
padding: 0 1.5rem;
.textbox-question {
padding: 0;
margin: 0 0 1.5rem 0;
label {
text-align: left;
font-weight: 900;
}
}
}
.modal-body-inner {
text-align: center;
margin-bottom: 1.5rem;
}
.modal-disclaimer {
font-size: 0.75rem;
order: 2;
text-align: left;
margin: 0;
a {
text-decoration: none;
}
}
.modal-footer {
margin: 0 0 1.5rem 0;
padding-top: 0;
padding-bottom: 0;
button.btn {
margin-bottom: 1.5rem;
}
}
}
.alert {
border-radius: $border-radius-lg; border-radius: $border-radius-lg;
border: 1px solid $green; border: 1px solid $green;
margin-top: 0; margin-top: 0;
} }
:deep(.alert-heading) { .alert-heading {
position: relative; position: relative;
font-weight: 600; font-weight: 600;
font-size: 1rem; font-size: 1rem;
@ -183,20 +229,4 @@ export default {
} }
} }
} }
.applied-promo {
font-size: 0.875rem;
font-weight: 500;
line-height: 24px;
color: $green;
.promo-code {
text-transform: uppercase;
}
}
.alert {
color: $red;
font-size: 0.875rem;
font-weight: 500;
line-height: 24px;
}
</style> </style>

View file

@ -14,20 +14,22 @@
v-model="userInput" v-model="userInput"
cmsWidgetName="SaveProgressPopupQuestionWidget" cmsWidgetName="SaveProgressPopupQuestionWidget"
class="save-progress-popup-question" /> class="save-progress-popup-question" />
<buttonMain <template v-slot:modal-footer-slot>
ref="skipButton" <buttonMain
type="button" ref="skipButton"
:buttonText="buttonText" type="button"
loaderColor="white" :buttonText="buttonText"
:suppressLoader="true" loaderColor="white"
class="skip-button" :suppressLoader="true"
@click-event="closeModal" /> class="skip-button"
@click-event="closeModal" />
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
</template>
<alert <alert
class="my-4" class="my-4"
v-if="isProgressSaved" v-if="isProgressSaved"
cmsWidgetName="SaveProgressModalAlertWidget" cmsWidgetName="SaveProgressModalAlertWidget"
alertClass="alert-success" /> alertClass="alert-success" />
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
</modal> </modal>
</div> </div>
</template> </template>
@ -205,35 +207,63 @@ export default {
left: 0; left: 0;
} }
} }
.modal-footer { .modal.modal-component .modal-dialog .modal-content {
order: 3; p.modal-body {
padding: 0; padding: 0;
} }
.modal-disclaimer { .modal-body {
order: 4; display: flex;
flex-direction: column;
padding: 0 1.5rem;
.textbox-question {
padding: 0;
margin: 0 0 1.5rem 0;
label {
text-align: left;
font-weight: 900;
}
}
}
.modal-body-inner {
text-align: center;
margin-bottom: 1.5rem;
}
.modal-footer {
padding: 0 1.5rem;
margin: 0 0 1.5rem 0;
button.btn {
order: 1;
margin-bottom: 1.5rem;
}
button.btn.skip-button {
padding: 0;
order: 2;
font-size: 1rem;
}
.modal-disclaimer {
font-size: 0.75rem;
text-align: left;
order: 3;
margin: 0;
a {
text-decoration: none;
}
}
}
} }
&.progress-saved { &.progress-saved {
.modal-footer, .modal-footer-button,
.save-progress-popup-question { .modal-disclaimer {
display: none; display: none;
} }
.skip-button {
margin-bottom: 0;
}
} }
} }
.applied-promo {
font-size: 0.875rem;
font-weight: 500;
line-height: 24px;
color: $green;
.promo-code {
text-transform: uppercase;
}
}
.alert {
color: $red;
font-size: 0.875rem;
font-weight: 500;
line-height: 24px;
}
</style> </style>

View file

@ -70,6 +70,7 @@ export default {
[headerKeys.REFERRAL_SEQUENCE_NUMBER]: order?.referralSequenceNumber, [headerKeys.REFERRAL_SEQUENCE_NUMBER]: order?.referralSequenceNumber,
[headerKeys.TRANSACTION_ID]: crypto.randomUUID(), [headerKeys.TRANSACTION_ID]: crypto.randomUUID(),
[headerKeys.EON]: order?.eon, [headerKeys.EON]: order?.eon,
[headerKeys.LOG_ENABLED]: store.getters.applicationUser?.loggingOption ?? false,
}; };
axios({ axios({

View file

@ -0,0 +1,19 @@
export function getBoolFromString(str) {
// Catch edge cases
if (str === null || str === undefined) {
return false;
}
if (typeof str === "boolean") {
return str;
}
if (typeof str !== "string") {
return false;
}
// string logic
const toLower = str?.toLowerCase();
return toLower === "true";
}

View file

@ -0,0 +1,146 @@
import { getBoolFromString } from "./boolean-helper";
describe("getBoolFromString", () => {
describe("Happy paths", () => {
test('"true" -> true', () => {
const input = "true";
const output = getBoolFromString(input);
expect(output).toBe(true);
});
test('"false" -> false', () => {
const input = "false";
const output = getBoolFromString(input);
expect(output).toBe(false);
});
test('"other string" -> false', () => {
const input = "some random value";
const output = getBoolFromString(input);
expect(output).toBe(false);
});
});
describe("Capitalization", () => {
test('"TRUE" -> true', () => {
const input = "TRUE";
const output = getBoolFromString(input);
expect(output).toBe(true);
});
test('"True" -> true', () => {
const input = "True";
const output = getBoolFromString(input);
expect(output).toBe(true);
});
test('"FALSE" -> false', () => {
const input = "FALSE";
const output = getBoolFromString(input);
expect(output).toBe(false);
});
test('"False" -> false', () => {
const input = "False";
const output = getBoolFromString(input);
expect(output).toBe(false);
});
});
describe("Graceful type errors", () => {
describe("Boolean", () => {
test("true -> true", () => {
const input = true;
const output = getBoolFromString(input);
expect(output).toBe(true);
});
test("false -> false", () => {
const input = false;
const output = getBoolFromString(input);
expect(output).toBe(false);
});
});
describe("Empty", () => {
test("undefined -> false", () => {
const input = undefined;
const output = getBoolFromString(input);
expect(output).toBe(false);
});
test("null -> false", () => {
const input = null;
const output = getBoolFromString(input);
expect(output).toBe(false);
});
test("{} -> false", () => {
const input = {};
const output = getBoolFromString(input);
expect(output).toBe(false);
});
});
describe("Other types", () => {
test("1 -> false", () => {
const input = 1;
const output = getBoolFromString(input);
expect(output).toBe(false);
});
test("Object -> false", () => {
const input = {
test: true,
true: true,
};
const output = getBoolFromString(input);
expect(output).toBe(false);
});
test("function -> false", () => {
const input = () => true;
const output = getBoolFromString(input);
expect(output).toBe(false);
});
test("array -> false", () => {
const input = [true];
const output = getBoolFromString(input);
expect(output).toBe(false);
});
});
});
});

View file

@ -8,3 +8,17 @@ export function getQuerystringParameter(key) {
return lowerCaseParams.get(key) ? lowerCaseParams.get(key) : null; return lowerCaseParams.get(key) ? lowerCaseParams.get(key) : null;
} }
// if you add the fmgPage to the querystringobject before calling, then pass true for skipFmgPageName
export function buildQuerystringObject(qso, skipFmgPageName = false) {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
for (const [name, value] of urlParams) {
if (name.toLowerCase() === "fmgpage" && skipFmgPageName) {
continue;
}
qso[name] = value;
}
return qso;
}

View file

@ -64,9 +64,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
// turned off Save Your Progress for 1/23/25 release vm.showSaveProgressModal = !(emailFromStore?.length > 0);
// to restore, uncomment line below
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
if (store.getters.externalParameterState?.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) { if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(

View file

@ -120,6 +120,43 @@ export default {
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
const hasRecalPriceRemoveExperiment = await store.getters.shouldHideRecalibration; const hasRecalPriceRemoveExperiment = await store.getters.shouldHideRecalibration;
// send part question data to SPS API for logging
const orderFromStore = await deepClone(store.getters.order);
const ctu = orderFromStore.serviceLocation?.zipCodeCtu
const partsOrQuestions = orderFromStore.damage?.partQuestionAnswers;
partsOrQuestions.forEach(pqa => {
const payloadPartQuestions = [];
pqa.answeredQuestions.forEach(answer => {
payloadPartQuestions.push({
questionSeq: answer?.questionNum,
questionText: answer?.questionText,
answerText: answer?.selectedAnswerText,
basePart: pqa?.result,
});
});
const payload = {
eon: orderFromStore.eon,
ctu: ctu,
workOrderId: orderFromStore.workOrderId,
workOrderNumber: orderFromStore.workOrderNumber,
carId: orderFromStore.vehicle.carId,
glassLocation: pqa?.glassLocation,
partQuestions: payloadPartQuestions,
};
baseMixin.methods.dispatchStoreAction(
storeActions.LOG_PART_QUESTIONS,
payload,
false
);
});
// Create order
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); // This nulls the Store order await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); // This nulls the Store order
// Call APIs // Call APIs

View file

@ -64,9 +64,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
// turned off Save Your Progress for 1/23/25 release vm.showSaveProgressModal = !(emailFromStore?.length > 0);
// to restore, uncomment line below
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
if (store.getters.externalParameterState?.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) { if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(

View file

@ -64,9 +64,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
// turned off Save Your Progress for 1/23/25 release vm.showSaveProgressModal = !(emailFromStore?.length > 0);
// to restore, uncomment line below
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
if (store.getters.externalParameterState?.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) { if (store.getters.externalParameterServiceZip.zipCode) {
const serviceState = store.getters.order.serviceLocation.state; const serviceState = store.getters.order.serviceLocation.state;

View file

@ -156,6 +156,7 @@ import { mapTaxedLineItemsToStoreFormat } from "../../store";
import { coverageStatus } from "@/constants/insurance"; import { coverageStatus } from "@/constants/insurance";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper"; import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper"; import { containsRecalParts } from "@/helpers/recal-helper";
import { getBoolFromString } from "@/helpers/boolean-helper";
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
@ -429,7 +430,7 @@ export default {
// prettier-ignore // prettier-ignore
{ {
const log = getQuerystringParameter(queryStrings.LOG); const log = getQuerystringParameter(queryStrings.LOG);
const logAsBool = (log?.toLowerCase() === "true"); const logAsBool = getBoolFromString(log);
if (logAsBool || !preReqResult) { if (logAsBool || !preReqResult) {
console.log("------------- payment-method.vue pagePrereqs start -----------------"); console.log("------------- payment-method.vue pagePrereqs start -----------------");
console.log(new Date() + " serviceLocationReqs::isMobile: " + isMobile); console.log(new Date() + " serviceLocationReqs::isMobile: " + isMobile);
@ -786,7 +787,7 @@ export default {
shouldDisplayPiaAlert() { shouldDisplayPiaAlert() {
return ( return (
this.$route.query[queryStrings.DISPLAY_PIA_ALERT] || this.$route.query[queryStrings.DISPLAY_PIA_ALERT] ||
eval(window.history.state.displayPiaAlert) getBoolFromString(window.history.state.displayPiaAlert)
); );
}, },
// Necessary to make the watcher of lineItems work // Necessary to make the watcher of lineItems work

View file

@ -234,6 +234,7 @@ import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"
import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js"; import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js";
import { routerParams } from "@/router/router-constants/router-params"; import { routerParams } from "@/router/router-constants/router-params";
import { coverageStatus } from "@/constants/insurance"; import { coverageStatus } from "@/constants/insurance";
import { getBoolFromString } from "@/helpers/boolean-helper.js";
export default { export default {
name: "payment", name: "payment",
@ -800,7 +801,7 @@ export default {
shouldDisplayPiaAlert(payMethod) { shouldDisplayPiaAlert(payMethod) {
return ( return (
this.$route.query[queryStrings.DISPLAY_PIA_ALERT] === payMethod || this.$route.query[queryStrings.DISPLAY_PIA_ALERT] === payMethod ||
eval(window.history.state.displayPiaAlert) getBoolFromString(window.history.state.displayPiaAlert)
); );
}, },
}, },

View file

@ -159,6 +159,7 @@ import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigat
import { containsRecalParts } from "@/helpers/recal-helper"; import { containsRecalParts } from "@/helpers/recal-helper";
import { externalParameterStatus } from "@/constants/external-parameters"; import { externalParameterStatus } from "@/constants/external-parameters";
import { saveQuote } from "@/helpers/heritage-integration/order-helper.js"; import { saveQuote } from "@/helpers/heritage-integration/order-helper.js";
import { getBoolFromString } from "@/helpers/boolean-helper";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
@ -293,12 +294,8 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.showSaveProgressPopup = showSaveProgressPopup;
// turned off Save Your Progress for 1/23/25 release vm.showSaveProgressModal = showSaveProgressModal;
// to restore, uncomment the 2 lines below
// vm.showSaveProgressPopup = showSaveProgressPopup;
// vm.showSaveProgressModal = showSaveProgressModal;
vm.addableVaps = addableVaps; vm.addableVaps = addableVaps;
vm.lineItems = lineItems; vm.lineItems = lineItems;
vm.availableLineItems = pricingResults; vm.availableLineItems = pricingResults;
@ -407,7 +404,7 @@ export default {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} else { } else {
// user came from an external source, however, the externalParms may have been reset on service-zip, property-questions, etc... // user came from an external source, however, the externalParms may have been reset on service-zip, property-questions, etc...
if (eval(store.getters.externalParameterQuote?.isInsurance)) { if (getBoolFromString(store.getters.externalParameterQuote?.isInsurance)) {
// did user intentionally select insurance? // did user intentionally select insurance?
vm.isInsuranceSelected = true; vm.isInsuranceSelected = true;
vm.servicePackage = store.getters.externalParameterQuote.servicePackage; vm.servicePackage = store.getters.externalParameterQuote.servicePackage;

View file

@ -0,0 +1,312 @@
// Components
import returnUser from "@/layouts/return-user/return-user.vue";
// Supporting Files
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { dispatchStoreAction } from "@/mixins/base-mixin.js";
import store from "@/store";
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 { settleAllPromises } from "@/helpers/layout-helper";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import { experimentSettings } from "../../constants/experiments";
import { storeActions } from "@/constants/store-actions";
import buttonMain from "@/ux-components/button-main/button-main";
// Constants
// Setup global mocks
let mockStoreActionData = {};
let mockStoreData = {};
let mockExperimentSettings = {
experiments: [
{
universeName: "ConceptFunnel",
settings: {
SuppressVinCapture: false,
},
},
],
};
function resetMockStoreData() {
mockStoreData = {
vehicle: {
year: "2000",
make: "TestMake",
model: "TestModel",
style: "TestStyle",
carId: "TestID",
vin: null,
registration: {
licensePlate: null,
},
},
serviceLocation: {
address: null,
address2: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null,
appointmentType: null,
isVehicleProtected: null,
provider: {
providerNumber: null,
address: {
streetAddress: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null,
},
},
techNotes: null,
},
customer: {
firstName: null,
lastName: null,
emailAddress: null,
phoneNumber: null,
isSmsOptIn: null,
},
damage: {
isRepair: false,
numberOfChips: null,
glassToReplace: [{ glassLocation: "Windshield", glassName: "windshield" }],
partQuestionAnswers: null,
moldingQuestionAnswers: null,
capabilityQuestionAnswers: null,
dateOfLoss: null,
damageCause: null,
},
lineItems: {
glassParts: [
{
canSafeliteRecalibrate: true,
childParts: [
{
kitPrice: 0,
laborAmount: 23.55,
partNumber: "GGG FW4896",
salesTax: 1.77,
sellingPrice: 0,
},
],
color: "Green Tint",
description:
"solar, soundproofing, lane keep assist, lane departure warning system, w/adaptive cruise control",
id: "db22fd44-10dd-456f-979b-ff88cf68cca6",
kitPrice: 0,
laborAmount: 60,
partNumber: "FW04896GTYN",
partType: "WINDSHIELD",
recalibrationType: "STATIC",
requiresCapabilityQuestions: false,
requiresRecalibration: true,
salesTax: 63.86,
sellingPrice: 791.46,
},
],
supportingItems: null,
vaps: null,
serverData: null,
promos: null,
},
payment: {
isInsurance: null,
insuranceCoverage: {
isVerified: null,
coverageStatus: null,
coverageType: null,
coverageVerificationType: null,
},
parentAccountNumber: 0,
billToAccountNumber: null,
isPia: null,
piaType: null,
inactivePromos: null,
paypalToken: null,
nextGenSettledAmount: 0,
ccToken: {
subscriptionId: null,
expMonth: null,
expYear: null,
cardType: null,
billToPostalCode: null,
billToFirstName: null,
billToLastName: null,
referenceNumber: null,
authCode: null,
transactionId: null,
transReferenceNumber: null,
lastFour: null,
},
},
policy: {
currentDeductible: 0,
policyNumber: null,
isItac: false,
additionalAuthFlag: null,
isNoComp: false,
insuranceCompanyName: null,
},
schedule: {
date: null,
startTime: null,
endTime: null,
routeCode: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
externalParameterServiceZip: {
zipCode: null,
emailAddress: null,
},
externalParameterState: { isExternalParameter: false },
};
}
function applyMockStoreDataToGetters() {
store.getters = {
vehicle: mockStoreData.vehicle,
};
store.state.order = mockStoreData;
store.state.applicationUser.experiments = mockExperimentSettings;
}
async function mockDispatchStoreAction(actionName) {
return mockStoreActionData[actionName];
}
jest.mock("@/mixins/base-mixin.js", () => ({
methods: {
dispatchStoreAction: jest.fn(),
dispatchStoreActionWithLogging: jest.fn().mockImplementation(mockDispatchStoreAction),
},
}));
jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: () => Promise.resolve("content"),
}));
jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({
saveSession: jest.fn(),
}));
jest.mock("@/helpers/heritage-integration/cookie-helper", () => ({
deleteFunnelCookie: jest.fn(),
getFunnelCookie: jest.fn(),
}));
router.navigateWithoutSaving = jest.fn();
router.navigateWithSaving = jest.fn();
// Tests
describe("return-user.vue", () => {
beforeEach(() => {
resetMockStoreData();
jest.clearAllMocks();
});
describe("Test prerequisites are valid and child components are rendered", () => {
test("expect pagePrerequisites are valid to be called", () => {
// Arrange
const wrapper = setupMocks({});
applyMockStoreDataToGetters();
// Act
const pagePrerequisitesSpy = jest.spyOn(wrapper.vm, "arePagePrerequisitesValid");
wrapper.vm.arePagePrerequisitesValid();
// Assert
expect(pagePrerequisitesSpy).toBeCalled();
expect(getFunnelCookie).toHaveBeenCalled();
});
test("renders child components", () => {
const wrapper = setupMocks({});
expect(wrapper.findComponent(funnelHeader).exists()).toBe(true);
expect(wrapper.findComponent(funnelSubHeader).exists()).toBe(true);
expect(wrapper.findComponent(navbar).exists()).toBe(true);
});
});
describe("Navigation", () => {
test("Check forwardButtonAction is working", async () => {
// Arrange
const wrapper = setupMocks({});
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
navigationScenarios.CLICKED_FORWARD,
wrapper.vm.$route
);
});
test("expect functions in startOver to be called", async () => {
//Arrange
const wrapper = setupMocks({});
// Act
const dispatchStoreActionSpy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
await wrapper.vm.$nextTick();
await wrapper.vm.startOver();
// Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
navigationScenarios.CLICKED_FORWARD,
wrapper.vm.$route
);
expect(dispatchStoreActionSpy).toHaveBeenCalledWith(storeActions.RESET_STATE);
expect(deleteFunnelCookie).toHaveBeenCalled();
});
});
});
function setupMocks({ customMountOptions }) {
const route = { query: { fmgPage: "return-user" }, params: {} };
baseMixin.methods.ResetExternalParamsAndHideModal = jest.fn();
const mountOptions = getMountOptions({
...customMountOptions,
route: route,
});
mountOptions.global.mocks["$store"] = store;
mountOptions.global.mocks["$router"] = router;
baseMixin.methods.isFormValid = jest.fn().mockReturnValue(true);
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(returnUser, mountOptions, {
stubs: {
Form,
funnelHeader,
funnelSubHeader,
navbar,
loadingModal: true,
buttonMain,
},
});
return wrapper;
}

View file

@ -241,57 +241,46 @@ export default {
this.displayMismatchStateAndZipAlert = false; this.displayMismatchStateAndZipAlert = false;
}, },
async setMobileLocation() { async setMobileLocation() {
if ( this.resetAlerts();
this.internalModel.addressQuestions.zipCode !== // Validate the Zip Code
this.modelValue.addressQuestions.zipCode const zipCodeData = await this.getZipCodeData(
) { this.internalModel.addressQuestions.zipCode,
this.resetAlerts(); "service-location"
// Validate the Zip Code );
const zipCodeData = await this.getZipCodeData(
this.internalModel.addressQuestions.zipCode, if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
} else if (zipCodeData.state != this.internalModel.addressQuestions.state) {
this.displayMismatchStateAndZipAlert = true;
this.resetModalButtonStyle();
} else {
// retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
const mobileFeePart = await getPricedMobileFeePart(
serviceZipCode,
"service-location" "service-location"
); );
if (!zipCodeData.isValid) { // retrieve serviceability details
this.displayInvalidZipAlert = true; const serviceabilityDetails = await getServiceabilityDetails(
this.resetModalButtonStyle(); serviceZipCode,
} else if (zipCodeData.state != this.internalModel.addressQuestions.state) { null,
this.displayMismatchStateAndZipAlert = true; "service-location"
this.resetModalButtonStyle(); );
} else {
// retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
const mobileFeePart = await getPricedMobileFeePart(
serviceZipCode,
"service-location"
);
// retrieve serviceability details const billToAccountNumber = await getBillToAccountNumber(
const serviceabilityDetails = await getServiceabilityDetails( this.internalModel.zipCodeCtu
serviceZipCode, );
null,
"service-location"
);
const billToAccountNumber = await getBillToAccountNumber( // update content related to service zip code
this.internalModel.zipCodeCtu this.$emit("updated-mobile-fee-part", mobileFeePart);
); this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu);
this.$emit("updated-bill-to-account-number", billToAccountNumber);
// update content related to service zip code // update the page level model
this.$emit("updated-mobile-fee-part", mobileFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu);
this.$emit("updated-bill-to-account-number", billToAccountNumber);
// update the page level model
this.$emit("update:modelValue", this.internalModel);
//Page advance to Schedule page
this.$emit("mobileLocationSelected");
}
} else {
// Update the page level model
this.$emit("update:modelValue", this.internalModel); this.$emit("update:modelValue", this.internalModel);
//Page advance to Schedule page //Page advance to Schedule page

View file

@ -7,6 +7,7 @@ import { getMountOptions } from "@/helpers/unit-test-helper";
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
import { experimentSettings } from "@/constants/experiments";
// Define Mocks // Define Mocks
jest.mock("@/helpers/cms-content-helper", () => ({ jest.mock("@/helpers/cms-content-helper", () => ({
@ -199,6 +200,9 @@ beforeEach(() => {
zipCode: "43054", zipCode: "43054",
}, },
}, },
experimentSettings: {
settingName: experimentSettings.DISPLAY_MSR,
},
}; };
}); });

View file

@ -139,6 +139,8 @@ import contentGroupModal from "@/fmg-components/content-group-modal/content-grou
// Supporting files // Supporting files
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import experimentMixin from "@/mixins/experiment-mixin.js";
import { experimentSettings } from "@/constants/experiments";
import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -151,6 +153,7 @@ import {
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { applicationConfig } from "@/constants/application-config"; import { applicationConfig } from "@/constants/application-config";
import { Provider } from "@/layouts/service-location/classes/provider"; import { Provider } from "@/layouts/service-location/classes/provider";
import { partNumberStrings } from "@/constants/part-number-strings";
import store from "@/store"; import store from "@/store";
@ -159,7 +162,6 @@ import { defineRule } from "vee-validate";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
const MOBILE_FEE_PART_TYPE = "MOBILE FEE"; const MOBILE_FEE_PART_TYPE = "MOBILE FEE";
const MOBILE_STATIC_RECAL_FEE_PART_NUMBER = "RECAL MOBILE";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("mobile-location-required", (value) => { defineRule("mobile-location-required", (value) => {
@ -319,11 +321,19 @@ export default {
}, },
isMobileStaticRecalibrationApplicable() { isMobileStaticRecalibrationApplicable() {
return ( return (
this.displayMSR &&
this.isVehicleMobileStaticRecalibrationApplicable && this.isVehicleMobileStaticRecalibrationApplicable &&
this.mobileFeePart?.partNumber == MOBILE_STATIC_RECAL_FEE_PART_NUMBER && this.mobileFeePart?.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE &&
(this.isInsurance ? this.mobileFeePart?.isInsurable : true) (this.isInsurance ? this.mobileFeePart?.isInsurable : true)
); );
}, },
displayMSR() {
return (
experimentMixin.methods
.getSettingValue(experimentSettings.DISPLAY_MSR)
?.toLowerCase() === "true"
);
},
mobileFeeApplies() { mobileFeeApplies() {
if ( if (
this.mobileFeePart?.laborAmount > 0 || this.mobileFeePart?.laborAmount > 0 ||

View file

@ -102,6 +102,7 @@ import { routerParams } from "@/router/router-constants/router-params";
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
import { nextTick } from "vue"; import { nextTick } from "vue";
import { getBoolFromString } from "@/helpers/boolean-helper";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED)); defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED));
@ -622,7 +623,7 @@ export default {
); );
}, },
shouldDisplayVehicleChangeAlert() { shouldDisplayVehicleChangeAlert() {
return eval(window.history.state.displayVehicleChangeAlert); return getBoolFromString(window.history.state.displayVehicleChangeAlert);
}, },
shouldHideBackButton() { shouldHideBackButton() {
return this.$store.getters.requiresVerifiedRedirecting; return this.$store.getters.requiresVerifiedRedirecting;

View file

@ -94,9 +94,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
// turned off Save Your Progress for 1/23/25 release vm.showSaveProgressModal = !(emailFromStore?.length > 0);
// to restore, uncomment line below
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
// Glass Part Question dynamic component // Glass Part Question dynamic component
Object.keys(vm.$refs) Object.keys(vm.$refs)

View file

@ -80,7 +80,11 @@ import vehicleQuestion from "@/layouts/vehicle/vehicle-question/vehicle-question
// Supporting files // Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { experimentUniverses } from "@/constants/experiments"; import { experimentUniverses } from "@/constants/experiments";
import { getSessionKeyValue, getUserIdValue } from "@/helpers/heritage-integration/cookie-helper"; import {
getSessionKeyValue,
getUserIdValue,
getDeviceIdValue,
} from "@/helpers/heritage-integration/cookie-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { Form, defineRule } from "vee-validate"; import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
@ -186,11 +190,12 @@ export default {
storeActions.LOG_EXPERIMENT_EXPOSURE, storeActions.LOG_EXPERIMENT_EXPOSURE,
{ {
userId: getUserIdValue(), userId: getUserIdValue(),
deviceId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(), sessionKey: getSessionKeyValue(),
pageName: to.query.fmgPage, pageName: "vehicle-logExposure", //to.query.fmgPage,
experiment: experimentForLogging, experiment: experimentForLogging,
}, },
"vehicle", "vehicle-logExposure",
false false
); );
} }

View file

@ -712,24 +712,19 @@ export default {
return !areAllSessionCookiesSet(); return !areAllSessionCookiesSet();
}, },
async validateSession() { // sessionExpired is true when one of the analytics cookies(sid, dxdev) has expired but we still have the vehicle year in vuex
// The noSession function checks the cookies related to analytics logging(sid). it is not the funnel info cookie. sessionExpired() {
// The sid cookie for analytics will expire every 30 minutes and get recreated in initSession. If this happens
// and we also have the funnel cookie present, that indicates they have an existing session that is now expired
// so route them to the return user page.
const fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE) === "true"; const fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE) === "true";
const funnelCookieLastTouched = getFunnelCookie()?.LastTouched; if (this.noSession() && !fromHeritage && store.getters.order.vehicle?.year > 0) {
if ( return true;
this.noSession() && } else {
!fromHeritage && return false;
funnelCookieLastTouched !== null && }
funnelCookieLastTouched !== undefined },
) {
await this.initSession(); async validateSession() {
router.push({ // do not init session if it is expired. let the click event on nav-bar handle session expired logic
path: "/", if (this.sessionExpired()) {
query: { fmgPage: fmgPageValues.RETURN_USER },
});
return; return;
} }

View file

@ -6,7 +6,7 @@ import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"
import { routingTable } from "@/router/router-constants/routing-table.js"; import { routingTable } from "@/router/router-constants/routing-table.js";
import { globalEvents, globalEventTypes } from "@/constants/events"; import { globalEvents, globalEventTypes } from "@/constants/events";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter, buildQuerystringObject } from "@/helpers/querystring-helper";
import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper"; import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper"; import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import { fmgPageValues, funnelStartPageName } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues, funnelStartPageName } from "@/router/router-constants/fmgPage-values";
@ -38,6 +38,7 @@ import { applicationConfig } from "../constants/application-config";
import { shouldStripPromoQueryString } from "@/helpers/promotions-helper"; import { shouldStripPromoQueryString } from "@/helpers/promotions-helper";
import bailout from "@/layouts/bailout/bailout"; import bailout from "@/layouts/bailout/bailout";
import { nextTick } from "vue"; import { nextTick } from "vue";
import { getBoolFromString } from "@/helpers/boolean-helper";
const routes = [ const routes = [
{ {
@ -58,6 +59,11 @@ const routes = [
await analyticsMixin.methods.validateSession(); await analyticsMixin.methods.validateSession();
// after session is validated, remove the fromHeritage querystring if it exists so session expiration works
if (to.query) {
delete to.query[queryStrings.FROM_HERITAGE];
}
if (getFunnelCookie()?.SuppressConceptFunnel) { if (getFunnelCookie()?.SuppressConceptFunnel) {
log(" --go to heritage suppressConceptFunnel: "); log(" --go to heritage suppressConceptFunnel: ");
await navigateToHeritageFunnel({ shouldSaveSession: false }); await navigateToHeritageFunnel({ shouldSaveSession: false });
@ -95,27 +101,24 @@ const routes = [
} }
// On entering the funnel "fresh", read cookie information, decide what to do next. // On entering the funnel "fresh", read cookie information, decide what to do next.
else if (from.redirectedFrom === undefined || fromReturnUser) { else if (from.redirectedFrom === undefined || fromReturnUser) {
// if entering the funnel from the content site, check and see if there is already a funnel cookie. // 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 so, send them to return-user page.
const funnelCookieLastTouched = getFunnelCookie()?.LastTouched;
if ( if (
fromContentSite && fromContentSite &&
funnelCookieLastTouched !== null && !toReturnUserPage &&
funnelCookieLastTouched !== undefined && store.getters.order.vehicle?.year > 0
!toReturnUserPage
) { ) {
log(" --from content site navigate to return user"); log(" --from content site navigate to return user");
var qso = { var qso = {
fmgPage: fmgPageValues.RETURN_USER, fmgPage: fmgPageValues.RETURN_USER,
}; };
const lg = getQuerystringParameter(queryStrings.LOG);
if (lg) { const newQueryString = buildQuerystringObject(qso, true);
qso[queryStrings.LOG] = true; log(" -- returnUser add querystring: " + JSON.stringify(newQueryString));
}
router.push({ router.push({
path: "/", path: "/",
query: Object.assign({}, qso), query: Object.assign({}, newQueryString),
}); });
return; return;
} }
@ -173,8 +176,8 @@ const routes = [
// clear part related state because heritage selected a new vehicle // clear part related state because heritage selected a new vehicle
if ( if (
to.query.fmgPage === fmgPageValues.VEHICLE && to.query.fmgPage === fmgPageValues.VEHICLE &&
eval(getFunnelCookie()?.HasDelayedClaimRegistration && getBoolFromString(getFunnelCookie()?.HasDelayedClaimRegistration) &&
!fromReturnUser) !fromReturnUser
) { ) {
store.commit(storeMutations.RESET_GLASS_PARTS_STATE); store.commit(storeMutations.RESET_GLASS_PARTS_STATE);
} }
@ -182,7 +185,7 @@ const routes = [
// if coming from the return user page, clear the destination page so implicit navigation runs // if coming from the return user page, clear the destination page so implicit navigation runs
log(" --to.query ", JSON.stringify(to.query)); log(" --to.query ", JSON.stringify(to.query));
if (fromReturnUser && to.query) { if (fromReturnUser && to.query) {
log( " --clear to.query"); log(" --clear to.query");
delete to.query[queryStrings.FMG_PAGE]; delete to.query[queryStrings.FMG_PAGE];
//to.query[queryStrings.FMG_PAGE] = ""; //to.query[queryStrings.FMG_PAGE] = "";
@ -422,7 +425,7 @@ router.afterEach(async (to, from) => {
store.commit(storeMutations.UPDATE_LAST_PAGE_VISITED, to.name); 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 saving on navigation is requested, check for saved SessionId or EmailAddress to determine if saving is appropriate
if (eval(window.history.state.isSavingNavigation)) { if (getBoolFromString(window.history.state.isSavingNavigation)) {
if ( if (
store.getters.applicationUser.savedSessionId || store.getters.applicationUser.savedSessionId ||
store.getters.order.customer?.emailAddress store.getters.order.customer?.emailAddress
@ -794,7 +797,7 @@ async function runExperiments(nextPage) {
await baseMixin.methods.dispatchStoreActionWithLogging( await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
{ {
userId: getDeviceIdValue(), deviceId: getDeviceIdValue(),
triggerEvent: experimentTriggers.SITE_ENTRY, triggerEvent: experimentTriggers.SITE_ENTRY,
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE, triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
}, },
@ -805,9 +808,9 @@ async function runExperiments(nextPage) {
await baseMixin.methods.dispatchStoreActionWithLogging( await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
{ {
userId: getDeviceIdValue(), deviceId: getDeviceIdValue(),
triggerEvent: experimentTriggers.PAGE_ENTRY, triggerEvent: experimentTriggers.PAGE_ENTRY,
triggerValue: nextPage, triggerValue: nextPage == "vehicle" ? "vehicle-pageEntry" : nextPage,
}, },
nextPage nextPage
); );

View file

@ -1252,7 +1252,7 @@ export const actions = {
// Analytics Actions // Analytics Actions
logExperimentExposure( logExperimentExposure(
context, context,
{ payload: { userId, sessionKey, pageName, experiment }, pageNameToLog } { payload: { userId, deviceId, sessionKey, pageName, experiment }, pageNameToLog }
) { ) {
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.LogExperimentExposureIfAssigned.method, method: endpoints.LogExperimentExposureIfAssigned.method,
@ -1260,6 +1260,7 @@ export const actions = {
payload: { payload: {
experimentForLogging: { experimentForLogging: {
userId: userId, userId: userId,
deviceId: deviceId,
experimentUniverseId: experiment.universeId, experimentUniverseId: experiment.universeId,
experimentUniverseName: experiment.universeName, experimentUniverseName: experiment.universeName,
experimentTestId: experiment.testId, experimentTestId: experiment.testId,
@ -1445,6 +1446,39 @@ export const actions = {
}); });
}, },
logPartQuestions(
context,
{
eon,
ctu,
workOrderId,
workOrderNumber,
carId,
glassLocation,
partQuestions,
userAgent,
}
) {
var payload = {
applicationName: applicationConfig.APPLICATION_NAME,
eon: eon,
ctu: ctu,
workOrderId: workOrderId,
workOrderNumber: workOrderNumber,
carId: carId,
glassLocation: glassLocation,
partQuestions: partQuestions,
userAgent: navigator.userAgent
};
return globalMethods
.callHttpClient({
method: endpoints.LogPartQuestions.method,
endpoint: endpoints.LogPartQuestions.url,
payload: payload,
});
},
// Misc Actions // Misc Actions
setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) { setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) {
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber); context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
@ -1463,7 +1497,7 @@ export const actions = {
async runExperimentsForTrigger( async runExperimentsForTrigger(
context, context,
{ payload: { userId, triggerEvent, triggerValue }, pageNameToLog } { payload: { deviceId, triggerEvent, triggerValue }, pageNameToLog }
) { ) {
if (triggerEvent == experimentTriggers.SITE_ENTRY) { if (triggerEvent == experimentTriggers.SITE_ENTRY) {
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true); context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
@ -1471,7 +1505,7 @@ export const actions = {
var payload = { var payload = {
applicationName: applicationConfig.APPLICATION_NAME, applicationName: applicationConfig.APPLICATION_NAME,
userId: userId, deviceId: deviceId,
triggerEvent: triggerEvent, triggerEvent: triggerEvent,
triggerValue: triggerValue, triggerValue: triggerValue,
experimentOrder: context.getters.experimentOrder, experimentOrder: context.getters.experimentOrder,
@ -1638,10 +1672,10 @@ export const actions = {
var endPoint = `${endpoints.GetMobileFeePart.url}/?serviceType=${serviceType}&facilityType=${facilityType}&parentAccountNumber=${parentAccountNumber}&billToAccountNumber=${billToAccountNumber}&providerNumber=${providerNumber}&isItacOptimized=${isItacOptimized}&zipCode=${zipCode}`; var endPoint = `${endpoints.GetMobileFeePart.url}/?serviceType=${serviceType}&facilityType=${facilityType}&parentAccountNumber=${parentAccountNumber}&billToAccountNumber=${billToAccountNumber}&providerNumber=${providerNumber}&isItacOptimized=${isItacOptimized}&zipCode=${zipCode}`;
if (isMobileStaticRecalibrationApplicable) { if (isMobileStaticRecalibrationApplicable) {
const staticRecalPartNumber = getStaticRecalPartNumber(order.lineItems?.glassParts[0]); const recalPartNumber = getRecalPartNumber(order.lineItems?.glassParts[0]);
const carId = order.vehicle?.carId; const carId = order.vehicle?.carId;
if (staticRecalPartNumber && carId) { if (recalPartNumber && carId) {
endPoint = `${endPoint}&partNumbers=${staticRecalPartNumber}&carId=${carId}`; endPoint = `${endPoint}&partNumbers=${recalPartNumber}&carId=${carId}`;
} }
} }
@ -3685,9 +3719,11 @@ function saveExternalParameterState(externalParameterState) {
window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState)); window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState));
} }
//This function checks if static recalibration is available for the vehicle and returns the part number for it. //This function finds the recalibration part and returns the part number for it.
function getStaticRecalPartNumber(glassPartsArray) { function getRecalPartNumber(glassPartsArray) {
const recalPart = glassPartsArray.childParts.find((item) => item.partNumber === "RECAL STATIC"); const recalPart = glassPartsArray.childParts.find(
(item) => item.partType === partTypeStrings.ADAS_RECALIBRATION
);
if (recalPart) { if (recalPart) {
return recalPart.partNumber; return recalPart.partNumber;
} else { } else {