Merge branch 'develop' into CASH-132-dontation-widget-foster-love
This commit is contained in:
commit
6ef5a30c5c
31 changed files with 859 additions and 236 deletions
|
|
@ -27,7 +27,6 @@ module.exports = {
|
|||
"!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/insurance-company-question/*.vue", // Temp test exclusion while in development
|
||||
"!src/layouts/return-user/*.vue", // Temp test exclusion while in development
|
||||
// END
|
||||
], // ! means exclude from coverage.
|
||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ const endpoints = {
|
|||
method: "POST",
|
||||
},
|
||||
SaveQuote: {
|
||||
url: "/order/api/v1/order/initiate-saved-progress-email",
|
||||
url: "/order/api/v1/order/save-progress",
|
||||
method: "POST",
|
||||
},
|
||||
GetSignature: {
|
||||
|
|
@ -168,6 +168,10 @@ const endpoints = {
|
|||
url: "/analytics/api/v1/analytics/initialize",
|
||||
method: "POST",
|
||||
},
|
||||
LogPartQuestions: {
|
||||
url: "/analytics/api/v1/analytics/log-part-questions",
|
||||
method: "POST",
|
||||
},
|
||||
GetExperimentsByUser: {
|
||||
url: "/analytics/api/v1/analytics/get-experiments",
|
||||
method: "GET",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const experimentUniverses = {
|
||||
CONCEPT_FUNNEL: "ConceptFunnel",
|
||||
RECAL_PRICE_REMOVAL: "NextGen_RecalPriceRemoval",
|
||||
MSR: "MSR",
|
||||
};
|
||||
|
||||
const experimentSettings = {
|
||||
|
|
@ -16,6 +17,7 @@ const experimentSettings = {
|
|||
RECAL_PRICE_REMOVE: "RecalPriceRemove",
|
||||
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold",
|
||||
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold",
|
||||
DISPLAY_MSR: "DisplayMSR",
|
||||
};
|
||||
|
||||
const experimentTriggers = {
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ export const headerKeys = {
|
|||
SESSION_SEQUENCE_NUMBER: "X-Session-Sequence-Number",
|
||||
TRANSACTION_ID: "X-Transaction-Id",
|
||||
EON: "X-Enterprise-Order-Number",
|
||||
LOG_ENABLED: "log-enabled"
|
||||
};
|
||||
|
|
|
|||
7
src/constants/part-number-strings.js
Normal file
7
src/constants/part-number-strings.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const partNumberStrings = {
|
||||
// Recalibration
|
||||
MOBILE_STATIC_RECAL_FEE: "RECAL MOBILE",
|
||||
MOBILE_DUAL_RECAL_FEE: "RECAL MOBILEDUAL",
|
||||
};
|
||||
|
||||
export { partNumberStrings };
|
||||
|
|
@ -42,10 +42,6 @@ const storeActions = {
|
|||
VALIDATE_ZIP: "validateZip",
|
||||
PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA: "priceOrderItemsAndSaveServerData",
|
||||
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",
|
||||
RUN_EXPERIMENTS_FOR_TRIGGER: "runExperimentsForTrigger",
|
||||
CLEAR_VIN: "clearVin",
|
||||
|
|
@ -57,6 +53,13 @@ const storeActions = {
|
|||
GET_BILL_TO_ACCOUNT_NUMBER: "getBillToAccountNumber",
|
||||
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
|
||||
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleStateAndDependencies",
|
||||
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@
|
|||
<slot></slot>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<slot name="modal-footer-slot"></slot>
|
||||
<modalButtonMain
|
||||
isPrimary
|
||||
class="w-100"
|
||||
class="w-100 modal-footer-button"
|
||||
:id="modalId + '-modalbtn'"
|
||||
ref="modalButtonMain"
|
||||
loaderColor="white"
|
||||
|
|
@ -45,6 +46,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||
import router from "@/router/index.js";
|
||||
import modalButtonMain from "@/digital-components/modal/ux-components/modal-button-main/modal-button-main";
|
||||
import { Modal } from "bootstrap";
|
||||
import { useForm } from "vee-validate";
|
||||
|
|
@ -86,7 +90,15 @@ export default {
|
|||
async validateAndEmit() {
|
||||
const validationResult = await this.validate();
|
||||
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 {
|
||||
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 {
|
||||
.modal-backdrop {
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@
|
|||
<script>
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||
import router from "@/router/index.js";
|
||||
|
||||
export default {
|
||||
name: "navbar",
|
||||
|
|
@ -94,10 +97,28 @@ export default {
|
|||
document.onkeydown = function (e) {
|
||||
return false;
|
||||
};
|
||||
this.$emit("ForwardClicked");
|
||||
// check session expired and initSession to recreate cookies
|
||||
if (analyticsMixin.methods.sessionExpired()) {
|
||||
this.routeReturnUser();
|
||||
} else {
|
||||
this.$emit("ForwardClicked");
|
||||
}
|
||||
},
|
||||
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 },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
<template>
|
||||
<div class="save-progress-modal-question" :class="isProgressSaved ? 'progress-saved' : ''">
|
||||
<buttonMain
|
||||
ref="buttonMain"
|
||||
type="button"
|
||||
v-if="!isProgressSaved"
|
||||
:buttonText="buttonText"
|
||||
loaderColor="white"
|
||||
:suppressLoader="true"
|
||||
class="save-progress-button"
|
||||
class="open-save-progress-button"
|
||||
@click-event="openModal" />
|
||||
<alert
|
||||
class="my-4"
|
||||
|
|
@ -21,12 +20,14 @@
|
|||
:onModalClosedCallback="onModalClosed"
|
||||
:footerButtonText="modalButtonText"
|
||||
@footer-button-event="saveProgress">
|
||||
<p class="modal-body-inner">{{ modalBodyText }}</p>
|
||||
<p class="modal-body-inner" v-html="modalBodyText"></p>
|
||||
<saveProgressQuestion
|
||||
ref="saveProgressQuestion"
|
||||
v-model="userInput"
|
||||
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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -112,7 +113,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.save-progress-modal-question {
|
||||
margin-bottom: 1.5rem;
|
||||
order: 2;
|
||||
|
|
@ -128,7 +129,7 @@ export default {
|
|||
background: none;
|
||||
}
|
||||
|
||||
.save-progress-button {
|
||||
.open-save-progress-button {
|
||||
order: 4;
|
||||
position: relative;
|
||||
color: $blue;
|
||||
|
|
@ -160,12 +161,57 @@ export default {
|
|||
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: 1px solid $green;
|
||||
margin-top: 0;
|
||||
}
|
||||
:deep(.alert-heading) {
|
||||
.alert-heading {
|
||||
position: relative;
|
||||
font-weight: 600;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -14,20 +14,22 @@
|
|||
v-model="userInput"
|
||||
cmsWidgetName="SaveProgressPopupQuestionWidget"
|
||||
class="save-progress-popup-question" />
|
||||
<buttonMain
|
||||
ref="skipButton"
|
||||
type="button"
|
||||
:buttonText="buttonText"
|
||||
loaderColor="white"
|
||||
:suppressLoader="true"
|
||||
class="skip-button"
|
||||
@click-event="closeModal" />
|
||||
<template v-slot:modal-footer-slot>
|
||||
<buttonMain
|
||||
ref="skipButton"
|
||||
type="button"
|
||||
:buttonText="buttonText"
|
||||
loaderColor="white"
|
||||
:suppressLoader="true"
|
||||
class="skip-button"
|
||||
@click-event="closeModal" />
|
||||
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
|
||||
</template>
|
||||
<alert
|
||||
class="my-4"
|
||||
v-if="isProgressSaved"
|
||||
cmsWidgetName="SaveProgressModalAlertWidget"
|
||||
alertClass="alert-success" />
|
||||
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -205,35 +207,63 @@ export default {
|
|||
left: 0;
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
order: 3;
|
||||
padding: 0;
|
||||
}
|
||||
.modal-disclaimer {
|
||||
order: 4;
|
||||
.modal.modal-component .modal-dialog .modal-content {
|
||||
p.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
.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-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 {
|
||||
.modal-footer,
|
||||
.save-progress-popup-question {
|
||||
.modal-footer-button,
|
||||
.modal-disclaimer {
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export default {
|
|||
[headerKeys.REFERRAL_SEQUENCE_NUMBER]: order?.referralSequenceNumber,
|
||||
[headerKeys.TRANSACTION_ID]: crypto.randomUUID(),
|
||||
[headerKeys.EON]: order?.eon,
|
||||
[headerKeys.LOG_ENABLED]: store.getters.applicationUser?.loggingOption ?? false,
|
||||
};
|
||||
|
||||
axios({
|
||||
|
|
|
|||
19
src/helpers/boolean-helper.js
Normal file
19
src/helpers/boolean-helper.js
Normal 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";
|
||||
}
|
||||
146
src/helpers/boolean-helper.spec.js
Normal file
146
src/helpers/boolean-helper.spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -8,3 +8,17 @@ export function getQuerystringParameter(key) {
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,9 +64,7 @@ export default {
|
|||
// Call the "next" function to complete the transition to this page.
|
||||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
// turned off Save Your Progress for 1/23/25 release
|
||||
// to restore, uncomment line below
|
||||
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
if (store.getters.externalParameterState?.isExternalParameter) {
|
||||
if (store.getters.externalParameterServiceZip.zipCode) {
|
||||
await baseMixin.methods.dispatchStoreAction(
|
||||
|
|
|
|||
|
|
@ -120,6 +120,43 @@ export default {
|
|||
async beforeRouteEnter(to, from, next) {
|
||||
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
|
||||
|
||||
// Call APIs
|
||||
|
|
|
|||
|
|
@ -64,9 +64,7 @@ export default {
|
|||
// Call the "next" function to complete the transition to this page.
|
||||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
// turned off Save Your Progress for 1/23/25 release
|
||||
// to restore, uncomment line below
|
||||
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
if (store.getters.externalParameterState?.isExternalParameter) {
|
||||
if (store.getters.externalParameterServiceZip.zipCode) {
|
||||
await baseMixin.methods.dispatchStoreAction(
|
||||
|
|
|
|||
|
|
@ -64,9 +64,7 @@ export default {
|
|||
// Call the "next" function to complete the transition to this page.
|
||||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
// turned off Save Your Progress for 1/23/25 release
|
||||
// to restore, uncomment line below
|
||||
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
if (store.getters.externalParameterState?.isExternalParameter) {
|
||||
if (store.getters.externalParameterServiceZip.zipCode) {
|
||||
const serviceState = store.getters.order.serviceLocation.state;
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ import { mapTaxedLineItemsToStoreFormat } from "../../store";
|
|||
import { coverageStatus } from "@/constants/insurance";
|
||||
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
|
||||
import { containsRecalParts } from "@/helpers/recal-helper";
|
||||
import { getBoolFromString } from "@/helpers/boolean-helper";
|
||||
|
||||
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
|
||||
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
|
||||
|
|
@ -429,7 +430,7 @@ export default {
|
|||
// prettier-ignore
|
||||
{
|
||||
const log = getQuerystringParameter(queryStrings.LOG);
|
||||
const logAsBool = (log?.toLowerCase() === "true");
|
||||
const logAsBool = getBoolFromString(log);
|
||||
if (logAsBool || !preReqResult) {
|
||||
console.log("------------- payment-method.vue pagePrereqs start -----------------");
|
||||
console.log(new Date() + " serviceLocationReqs::isMobile: " + isMobile);
|
||||
|
|
@ -786,7 +787,7 @@ export default {
|
|||
shouldDisplayPiaAlert() {
|
||||
return (
|
||||
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
|
||||
|
|
|
|||
|
|
@ -234,6 +234,7 @@ import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"
|
|||
import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js";
|
||||
import { routerParams } from "@/router/router-constants/router-params";
|
||||
import { coverageStatus } from "@/constants/insurance";
|
||||
import { getBoolFromString } from "@/helpers/boolean-helper.js";
|
||||
|
||||
export default {
|
||||
name: "payment",
|
||||
|
|
@ -800,7 +801,7 @@ export default {
|
|||
shouldDisplayPiaAlert(payMethod) {
|
||||
return (
|
||||
this.$route.query[queryStrings.DISPLAY_PIA_ALERT] === payMethod ||
|
||||
eval(window.history.state.displayPiaAlert)
|
||||
getBoolFromString(window.history.state.displayPiaAlert)
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigat
|
|||
import { containsRecalParts } from "@/helpers/recal-helper";
|
||||
import { externalParameterStatus } from "@/constants/external-parameters";
|
||||
import { saveQuote } from "@/helpers/heritage-integration/order-helper.js";
|
||||
import { getBoolFromString } from "@/helpers/boolean-helper";
|
||||
|
||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
|
|
@ -293,12 +294,8 @@ export default {
|
|||
// Call the "next" function to complete the transition to this page.
|
||||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
|
||||
// turned off Save Your Progress for 1/23/25 release
|
||||
// to restore, uncomment the 2 lines below
|
||||
// vm.showSaveProgressPopup = showSaveProgressPopup;
|
||||
// vm.showSaveProgressModal = showSaveProgressModal;
|
||||
|
||||
vm.showSaveProgressPopup = showSaveProgressPopup;
|
||||
vm.showSaveProgressModal = showSaveProgressModal;
|
||||
vm.addableVaps = addableVaps;
|
||||
vm.lineItems = lineItems;
|
||||
vm.availableLineItems = pricingResults;
|
||||
|
|
@ -407,7 +404,7 @@ export default {
|
|||
baseMixin.methods.ResetExternalParamsAndHideModal();
|
||||
} else {
|
||||
// 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?
|
||||
vm.isInsuranceSelected = true;
|
||||
vm.servicePackage = store.getters.externalParameterQuote.servicePackage;
|
||||
|
|
|
|||
312
src/layouts/return-user/return-user.spec.js
Normal file
312
src/layouts/return-user/return-user.spec.js
Normal 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;
|
||||
}
|
||||
|
|
@ -241,57 +241,46 @@ export default {
|
|||
this.displayMismatchStateAndZipAlert = false;
|
||||
},
|
||||
async setMobileLocation() {
|
||||
if (
|
||||
this.internalModel.addressQuestions.zipCode !==
|
||||
this.modelValue.addressQuestions.zipCode
|
||||
) {
|
||||
this.resetAlerts();
|
||||
// Validate the Zip Code
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.addressQuestions.zipCode,
|
||||
this.resetAlerts();
|
||||
// Validate the Zip Code
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.addressQuestions.zipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
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"
|
||||
);
|
||||
|
||||
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"
|
||||
);
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
const billToAccountNumber = await getBillToAccountNumber(
|
||||
this.internalModel.zipCodeCtu
|
||||
);
|
||||
|
||||
const billToAccountNumber = await getBillToAccountNumber(
|
||||
this.internalModel.zipCodeCtu
|
||||
);
|
||||
// update content related to service zip code
|
||||
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
|
||||
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
|
||||
// update the page level model
|
||||
this.$emit("update:modelValue", this.internalModel);
|
||||
|
||||
//Page advance to Schedule page
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { getMountOptions } from "@/helpers/unit-test-helper";
|
|||
|
||||
import store from "@/store";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
// Define Mocks
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
|
|
@ -199,6 +200,9 @@ beforeEach(() => {
|
|||
zipCode: "43054",
|
||||
},
|
||||
},
|
||||
experimentSettings: {
|
||||
settingName: experimentSettings.DISPLAY_MSR,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ import contentGroupModal from "@/fmg-components/content-group-modal/content-grou
|
|||
|
||||
// Supporting files
|
||||
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 { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
|
|
@ -151,6 +153,7 @@ import {
|
|||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
import { partNumberStrings } from "@/constants/part-number-strings";
|
||||
|
||||
import store from "@/store";
|
||||
|
||||
|
|
@ -159,7 +162,6 @@ import { defineRule } from "vee-validate";
|
|||
import { errorMessages } from "@/constants/error-messages";
|
||||
|
||||
const MOBILE_FEE_PART_TYPE = "MOBILE FEE";
|
||||
const MOBILE_STATIC_RECAL_FEE_PART_NUMBER = "RECAL MOBILE";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("mobile-location-required", (value) => {
|
||||
|
|
@ -319,11 +321,19 @@ export default {
|
|||
},
|
||||
isMobileStaticRecalibrationApplicable() {
|
||||
return (
|
||||
this.displayMSR &&
|
||||
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)
|
||||
);
|
||||
},
|
||||
displayMSR() {
|
||||
return (
|
||||
experimentMixin.methods
|
||||
.getSettingValue(experimentSettings.DISPLAY_MSR)
|
||||
?.toLowerCase() === "true"
|
||||
);
|
||||
},
|
||||
mobileFeeApplies() {
|
||||
if (
|
||||
this.mobileFeePart?.laborAmount > 0 ||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ import { routerParams } from "@/router/router-constants/router-params";
|
|||
import store from "@/store";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { nextTick } from "vue";
|
||||
import { getBoolFromString } from "@/helpers/boolean-helper";
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED));
|
||||
|
||||
|
|
@ -622,7 +623,7 @@ export default {
|
|||
);
|
||||
},
|
||||
shouldDisplayVehicleChangeAlert() {
|
||||
return eval(window.history.state.displayVehicleChangeAlert);
|
||||
return getBoolFromString(window.history.state.displayVehicleChangeAlert);
|
||||
},
|
||||
shouldHideBackButton() {
|
||||
return this.$store.getters.requiresVerifiedRedirecting;
|
||||
|
|
|
|||
|
|
@ -94,10 +94,8 @@ export default {
|
|||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
// turned off Save Your Progress for 1/23/25 release
|
||||
// to restore, uncomment line below
|
||||
// vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
|
||||
vm.showSaveProgressModal = !(emailFromStore?.length > 0);
|
||||
|
||||
// Glass Part Question dynamic component
|
||||
Object.keys(vm.$refs)
|
||||
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,11 @@ import vehicleQuestion from "@/layouts/vehicle/vehicle-question/vehicle-question
|
|||
// Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
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 { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
|
|
@ -186,11 +190,12 @@ export default {
|
|||
storeActions.LOG_EXPERIMENT_EXPOSURE,
|
||||
{
|
||||
userId: getUserIdValue(),
|
||||
deviceId: getDeviceIdValue(),
|
||||
sessionKey: getSessionKeyValue(),
|
||||
pageName: to.query.fmgPage,
|
||||
pageName: "vehicle-logExposure", //to.query.fmgPage,
|
||||
experiment: experimentForLogging,
|
||||
},
|
||||
"vehicle",
|
||||
"vehicle-logExposure",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -712,24 +712,19 @@ export default {
|
|||
return !areAllSessionCookiesSet();
|
||||
},
|
||||
|
||||
async validateSession() {
|
||||
// The noSession function checks the cookies related to analytics logging(sid). it is not the funnel info cookie.
|
||||
// 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.
|
||||
// sessionExpired is true when one of the analytics cookies(sid, dxdev) has expired but we still have the vehicle year in vuex
|
||||
sessionExpired() {
|
||||
const fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE) === "true";
|
||||
const funnelCookieLastTouched = getFunnelCookie()?.LastTouched;
|
||||
if (
|
||||
this.noSession() &&
|
||||
!fromHeritage &&
|
||||
funnelCookieLastTouched !== null &&
|
||||
funnelCookieLastTouched !== undefined
|
||||
) {
|
||||
await this.initSession();
|
||||
router.push({
|
||||
path: "/",
|
||||
query: { fmgPage: fmgPageValues.RETURN_USER },
|
||||
});
|
||||
if (this.noSession() && !fromHeritage && store.getters.order.vehicle?.year > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async validateSession() {
|
||||
// do not init session if it is expired. let the click event on nav-bar handle session expired logic
|
||||
if (this.sessionExpired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"
|
|||
import { routingTable } from "@/router/router-constants/routing-table.js";
|
||||
import { globalEvents, globalEventTypes } from "@/constants/events";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
import { getQuerystringParameter, buildQuerystringObject } from "@/helpers/querystring-helper";
|
||||
import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper";
|
||||
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
|
||||
import { fmgPageValues, funnelStartPageName } from "@/router/router-constants/fmgPage-values";
|
||||
|
|
@ -38,6 +38,7 @@ import { applicationConfig } from "../constants/application-config";
|
|||
import { shouldStripPromoQueryString } from "@/helpers/promotions-helper";
|
||||
import bailout from "@/layouts/bailout/bailout";
|
||||
import { nextTick } from "vue";
|
||||
import { getBoolFromString } from "@/helpers/boolean-helper";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
|
@ -58,6 +59,11 @@ const routes = [
|
|||
|
||||
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) {
|
||||
log(" --go to heritage suppressConceptFunnel: ");
|
||||
await navigateToHeritageFunnel({ shouldSaveSession: false });
|
||||
|
|
@ -95,27 +101,24 @@ const routes = [
|
|||
}
|
||||
// On entering the funnel "fresh", read cookie information, decide what to do next.
|
||||
else if (from.redirectedFrom === undefined || fromReturnUser) {
|
||||
// if entering the funnel from the content site, check and see if there is already a 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.
|
||||
const funnelCookieLastTouched = getFunnelCookie()?.LastTouched;
|
||||
if (
|
||||
fromContentSite &&
|
||||
funnelCookieLastTouched !== null &&
|
||||
funnelCookieLastTouched !== undefined &&
|
||||
!toReturnUserPage
|
||||
!toReturnUserPage &&
|
||||
store.getters.order.vehicle?.year > 0
|
||||
) {
|
||||
log(" --from content site navigate to return user");
|
||||
var qso = {
|
||||
fmgPage: fmgPageValues.RETURN_USER,
|
||||
};
|
||||
const lg = getQuerystringParameter(queryStrings.LOG);
|
||||
if (lg) {
|
||||
qso[queryStrings.LOG] = true;
|
||||
}
|
||||
|
||||
const newQueryString = buildQuerystringObject(qso, true);
|
||||
log(" -- returnUser add querystring: " + JSON.stringify(newQueryString));
|
||||
|
||||
router.push({
|
||||
path: "/",
|
||||
query: Object.assign({}, qso),
|
||||
query: Object.assign({}, newQueryString),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -173,8 +176,8 @@ const routes = [
|
|||
// clear part related state because heritage selected a new vehicle
|
||||
if (
|
||||
to.query.fmgPage === fmgPageValues.VEHICLE &&
|
||||
eval(getFunnelCookie()?.HasDelayedClaimRegistration &&
|
||||
!fromReturnUser)
|
||||
getBoolFromString(getFunnelCookie()?.HasDelayedClaimRegistration) &&
|
||||
!fromReturnUser
|
||||
) {
|
||||
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
|
||||
log(" --to.query ", JSON.stringify(to.query));
|
||||
if (fromReturnUser && to.query) {
|
||||
log( " --clear to.query");
|
||||
log(" --clear to.query");
|
||||
delete 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);
|
||||
|
||||
// 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 (
|
||||
store.getters.applicationUser.savedSessionId ||
|
||||
store.getters.order.customer?.emailAddress
|
||||
|
|
@ -566,7 +569,7 @@ async function navigate(
|
|||
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
|
||||
const logQs = getQuerystringParameter(queryStrings.LOG);
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_LOGGING_OPTION, logQs, false);
|
||||
|
||||
|
||||
log("------------- router index.js navigate start -----------------");
|
||||
log(" --scenario: ", scenario);
|
||||
log(" --isSavingNavigation: ", isSavingNavigation);
|
||||
|
|
@ -631,7 +634,7 @@ function getNavigationMap(scenario, currentRoute) {
|
|||
function log(message, data) {
|
||||
const log = getQuerystringParameter(queryStrings.LOG);
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_LOGGING_OPTION, log, false);
|
||||
|
||||
|
||||
data = data ?? "";
|
||||
const outData = typeof data === "object" ? JSON.stringify(data) : data;
|
||||
|
||||
|
|
@ -794,7 +797,7 @@ async function runExperiments(nextPage) {
|
|||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
deviceId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
||||
},
|
||||
|
|
@ -805,9 +808,9 @@ async function runExperiments(nextPage) {
|
|||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
deviceId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
||||
triggerValue: nextPage,
|
||||
triggerValue: nextPage == "vehicle" ? "vehicle-pageEntry" : nextPage,
|
||||
},
|
||||
nextPage
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1252,7 +1252,7 @@ export const actions = {
|
|||
// Analytics Actions
|
||||
logExperimentExposure(
|
||||
context,
|
||||
{ payload: { userId, sessionKey, pageName, experiment }, pageNameToLog }
|
||||
{ payload: { userId, deviceId, sessionKey, pageName, experiment }, pageNameToLog }
|
||||
) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LogExperimentExposureIfAssigned.method,
|
||||
|
|
@ -1260,6 +1260,7 @@ export const actions = {
|
|||
payload: {
|
||||
experimentForLogging: {
|
||||
userId: userId,
|
||||
deviceId: deviceId,
|
||||
experimentUniverseId: experiment.universeId,
|
||||
experimentUniverseName: experiment.universeName,
|
||||
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
|
||||
setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) {
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||
|
|
@ -1463,7 +1497,7 @@ export const actions = {
|
|||
|
||||
async runExperimentsForTrigger(
|
||||
context,
|
||||
{ payload: { userId, triggerEvent, triggerValue }, pageNameToLog }
|
||||
{ payload: { deviceId, triggerEvent, triggerValue }, pageNameToLog }
|
||||
) {
|
||||
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
|
||||
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
|
||||
|
|
@ -1471,7 +1505,7 @@ export const actions = {
|
|||
|
||||
var payload = {
|
||||
applicationName: applicationConfig.APPLICATION_NAME,
|
||||
userId: userId,
|
||||
deviceId: deviceId,
|
||||
triggerEvent: triggerEvent,
|
||||
triggerValue: triggerValue,
|
||||
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}`;
|
||||
|
||||
if (isMobileStaticRecalibrationApplicable) {
|
||||
const staticRecalPartNumber = getStaticRecalPartNumber(order.lineItems?.glassParts[0]);
|
||||
const recalPartNumber = getRecalPartNumber(order.lineItems?.glassParts[0]);
|
||||
const carId = order.vehicle?.carId;
|
||||
if (staticRecalPartNumber && carId) {
|
||||
endPoint = `${endPoint}&partNumbers=${staticRecalPartNumber}&carId=${carId}`;
|
||||
if (recalPartNumber && carId) {
|
||||
endPoint = `${endPoint}&partNumbers=${recalPartNumber}&carId=${carId}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3685,9 +3719,11 @@ function saveExternalParameterState(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.
|
||||
function getStaticRecalPartNumber(glassPartsArray) {
|
||||
const recalPart = glassPartsArray.childParts.find((item) => item.partNumber === "RECAL STATIC");
|
||||
//This function finds the recalibration part and returns the part number for it.
|
||||
function getRecalPartNumber(glassPartsArray) {
|
||||
const recalPart = glassPartsArray.childParts.find(
|
||||
(item) => item.partType === partTypeStrings.ADAS_RECALIBRATION
|
||||
);
|
||||
if (recalPart) {
|
||||
return recalPart.partNumber;
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue