Merge branch 'develop' into feature/CSR-92

This commit is contained in:
Max 2022-04-19 14:43:38 -04:00
commit 68c9f089f5
21 changed files with 575 additions and 177 deletions

View file

@ -17,11 +17,9 @@ module.exports = {
"!src/layouts/vin-lookup/vin-lookup.vue",
"!src/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question.vue",
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
"!src/layouts/address-poc/address-poc.vue",
"!src/layouts/nested-radio-poc/nested-radio.vue",
"!src/layouts/button-question-examples/**/*.vue",
"!src/layouts/part-questions/**/*.vue",
"!src/layouts/reveal/**/*.vue",
"!src/layouts/estimate/**/*.vue",
// REMOVE THESE AFTER WRITING UNIT TESTS
"!src/layouts/address-lookup/address-lookup.vue",
"!src/layouts/address-lookup/customer-questions/customer-questions.vue",

View file

@ -22,13 +22,13 @@
isPrimary
:buttonText="buttonText"
loaderColor="white"
:class="isDisabled && 'form-test-invalid'"
:aria-disabled="isDisabled"
:isDisabled="isDisabled"
:class="isForwardActionDisabled && 'form-test-invalid'"
:aria-disabled="isForwardActionDisabled"
:isDisabled="isForwardActionDisabled"
@click-event="buttonClick"
/>
</div>
<div class="col-auto link-col py-1 text-break">
<div v-if="!isBackButtonHidden" class="col-auto link-col py-1 text-break">
<textLink
linkType="navigation"
:text="backLink"
@ -48,8 +48,10 @@ import buttonMain from "@/ux-components/button-main/button-main";
export default {
name: "funnelFooter",
props: {
isDisabled: Boolean,
isForwardActionDisabled: Boolean,
isBackButtonHidden: {type: Boolean, default: false},
cmsWidgetName: String,
},
components: {
textLink,

View file

@ -1,25 +1,25 @@
<template>
<div class="textbox-question" :class="(errors && errors.length) || hasError ? 'has-error' : ''">
<label :for="inputId" :aria-label="questionText" class="form-label" v-html="labelText"></label>
<input v-model="value"
<input v-model="value"
v-maska="mask"
:type="type"
class="form-control"
:ref="inputId"
:id="inputId"
:type="type"
class="form-control"
:ref="inputId"
:id="inputId"
:name="inputId"
:placeholder="placeholderText"
:aria-disabled="isDisabled"
:disabled="isDisabled"
:aria-required="isRequired"
autocomplete="off"
:placeholder="placeholderText"
:aria-disabled="isDisabled"
:disabled="isDisabled"
:aria-required="isRequired"
autocomplete="off"
:class="[hasIcon ? 'has-icon' : '', iconRight ? 'icon-right' : '']"
:validationRules="validationRules"
@input="handleChange"
@blur="handleBlur" />
<div v-show="errorMessage" class="row mt-2 form-test-error">
<span role="alert">{{ errorMessage }}</span>
</div>
</div>
</div>
</template>
@ -56,7 +56,7 @@ export default {
setup(props) {
const fieldOptions = {
type: "text",
value: props.modelValue,
value: props.modelValue,
};
const {
@ -64,15 +64,17 @@ export default {
handleBlur,
handleChange,
meta,
validate
validate,
errors,
} = useField(props.inputId, props.validationRules, fieldOptions);
return {
return {
errorMessage,
handleBlur,
handleChange,
validate,
meta,
meta,
errors,
};
},
computed: {
@ -86,7 +88,7 @@ export default {
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
},
},
labelText: {
get: function () {
let labelText = this.questionText;
@ -98,7 +100,7 @@ export default {
return labelText;
}
}
}
},
watch: {
value(newValue) {

View file

@ -16,6 +16,8 @@ const errorMessages = {
LAST_NAME_REQUIRED: "Please enter your last name",
EMAIL_ADDRESS_REQUIRED: "Please enter your email address",
EMAIL_ADDRESS_FORMAT: "Please enter a valid email address",
VIN_REQUIRED: "Please enter your VIN",
VIN_FORMAT: "Please enter a valid VIN",
};
export { errorMessages };
export { errorMessages };

View file

@ -1,4 +1,4 @@
import {getFunnelCookie} from "@/helpers/heritage-integration/cookie-helper.js";
import {getFunnelCookie, getDeviceIdValue, getSessionKeyValue} from "@/helpers/heritage-integration/cookie-helper.js";
import { removeAllTestCookies, setupCookies } from "@/helpers/unit-test-helper";
describe("cookies", () => {
@ -100,5 +100,31 @@ describe("cookies", () => {
expect(actualCookieValue).toBeNull();
});
})
describe("getDeviceIdValue", () => {
test("getDeviceIdValue, should return GUID", () => {
// Arrange
setupCookies({});
// Act
const result = getDeviceIdValue();
//Assert
expect(result).toBe('21b9b94a-ec23-42c1-aaac-e2ae4e4dbffe');
});
test("getSessionKeyValue, should return session key int", () => {
// Arrange
setupCookies({});
// Act
const result = getSessionKeyValue();
//Assert
expect(result).toBe('12345');
});
});
})

View file

@ -2,55 +2,38 @@ import { queryStrings } from "@/constants/query-strings";
import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { saveOrder } from "@/helpers/heritage-integration/order-helper.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import store from "@/store";
import router from "@/router";
/*
If the user has visited the funnel before this method will determine the bets place to
drop them so they don't start at the beginning again. This method will return 'heritage' if
the user has an existing order and they come back in from the Safelite.com CTA.
the user has an existing order and they come back in from the Safelite.com CTA.
*/
export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) {
// If the user is coming in via the Safelite.Com CTA
if (toRoute.query[queryStrings.START_TYPE] === 'fmg') {
// If they have an existing order, return 'heritage' for the page name.
if (existingHeritageOrder) {
return 'heritage';
}
return await getLatestPageForRedirection();
}
// If this is a non-CTA navigation, determine where to send the user based on page prerequisites.
// This also works if a user has a 'fmg' start_type query string but no current order.
// That shouldn't happen, but it's possible.
const vehicleMakeComponent = (await lazyLoadComponent('vehicle-make')()).default;
const vehicleModelComponent = (await lazyLoadComponent('vehicle-model')()).default;
const vehicleStyleComponent = (await lazyLoadComponent('vehicle-style')()).default;
const vehicleDamageComponent = (await lazyLoadComponent('vehicle-damage')()).default;
if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) {
return "vehicle-year";
} else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) {
return "vehicle-make";
} else if (!vehicleStyleComponent.methods.arePagePrerequisitesValid()) {
return "vehicle-model";
} else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) {
return "vehicle-style";
} else if (store.getters.damage.isRepair == null || !store.getters.vehicle.carId) {
return 'vehicle-damage'
} else {
if (store.getters.vehicle.vin) {
return 'vehicle-damage';
//return "vin-lookup"; (uncomment)
} else {
return 'vehicle-damage';
//return "estimate" (uncomment)
}
// If navigating to a specific page, and that page is not part of the vin pages.
// Return that page, so that it can navigate like normal.
if (toRoute.query[queryStrings.FMG_PAGE] !== undefined && !isVinRelatedPage(toRoute)) {
return overrideYmmsDirectionIfNeeded(toRoute);
}
// If this is not a direct link to a page using fmgPage, not from Safelite.com CTA or this is a vin related page.
// Get the latest page for redirection.
const latestPageRoute = await getLatestPageForRedirection();
return latestPageRoute;
}
/*
@ -65,10 +48,81 @@ export async function navigateToHeritageFunnel() {
externalUrls.HERITAGE_FUNNEL,
{
corid: store.getters.order.referralCorrelationId,
src: "concept-funnel",
// TODO CSR-28, remove this
cns: "all",
experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=NoShowPackages_CONTROL=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true"
src: "concept-funnel"
}
);
}
}
/*
Logic for getting the last "valid" page a user visited.
*/
async function getLatestPageForRedirection() {
// If this is a non-CTA navigation, determine where to send the user based on page prerequisites.
// This also works if a user has a 'fmg' start_type query string but no current order.
// That shouldn't happen, but it's possible.
const vehicleMakeComponent = (await lazyLoadComponent('vehicle-make')()).default;
const vehicleModelComponent = (await lazyLoadComponent('vehicle-model')()).default;
const vehicleStyleComponent = (await lazyLoadComponent('vehicle-style')()).default;
const vehicleDamageComponent = (await lazyLoadComponent('vehicle-damage')()).default;
if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_YEAR;
} else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_MAKE;
} else if (!vehicleStyleComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_MODEL;
} else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_STYLE;
} else if (store.getters.damage.isRepair == null || !store.getters.vehicle.carId) {
return fmgPageValues.VEHICLE_DAMAGE;
} else {
if (store.getters.vehicle.vin) {
return fmgPageValues.VIN_LOOKUP;
} else {
return fmgPageValues.ESTIMATE;
}
}
}
/*
Overrides functionality to go to the YMMS pages in certain cases.
If this is not one of the cases, it returns the 'to' fmgPage value.
*/
/* istanbul ignore next */
function overrideYmmsDirectionIfNeeded(toRoute) {
const fmgPageValue = toRoute.query[queryStrings.FMG_PAGE];
if (store.getters.payment.insuranceCoverage.isVerified) {
switch (fmgPageValue) {
case fmgPageValues.VEHICLE_YEAR:
case fmgPageValues.VEHICLE_MAKE:
case fmgPageValues.VEHICLE_MODEL:
case fmgPageValues.VEHICLE_STYLE:
{
return fmgPageValues.VEHICLE_DAMAGE;
}
default: {
return fmgPageValue;
}
}
} else {
return fmgPageValue;
}
}
/*
Determine if the page is a vin related page.
*/
/* istanbul ignore next */
function isVinRelatedPage(toRoute) {
const fmgPageValue = toRoute.query[queryStrings.FMG_PAGE];
return fmgPageValue === fmgPageValues.VIN_LOOKUP ||
fmgPageValue === fmgPageValues.LICENSE_PLATE_LOOKUP ||
fmgPageValue === fmgPageValues.ADDRESS_LOOKUP ||
fmgPageValue === fmgPageValues.ADDRESS_VEHICLES ||
fmgPageValue === fmgPageValues.ESTIMATE;
}

View file

@ -228,8 +228,7 @@ describe("getPageToRouteExistingOrderTo", () => {
const result = await getPageToRouteExistingOrderTo(toRoute, false);
//Assert
//expect(result).toBe('vin-lookup');
expect(result).toBe('vehicle-damage');
expect(result).toBe('vin-lookup');
});
test("getPageToRouteExistingOrderTo, should return estimate", async () => {
@ -284,8 +283,7 @@ describe("getPageToRouteExistingOrderTo", () => {
const result = await getPageToRouteExistingOrderTo(toRoute, false);
//Assert
//expect(result).toBe('estimate');
expect(result).toBe('vehicle-damage');
expect(result).toBe('estimate');
});
test("getPageToRouteExistingOrderTo, existing order, should return heritage", async () => {

View file

@ -60,7 +60,9 @@ export const cookies = {
[cookieNames.FUNNEL_SESSION_INFO]: `{"ReferralNumber":"1566818","ReferralDate":"2022-03-15T10:56:24.597","ReferralCorrelationId":"404d2b04-f86e-45c3-b373-127b6217b060","ShouldResetState":false,"DidHeritageFunnelUpdateLast":true}`,
"UNIQUE_SESSION_ID": "33756020-b58e-4ec7-b8b8-3f1576719c40",
"anotherCookie": "{}",
"someOtherCookie": "{}"
"someOtherCookie": "{}",
"dxdev": "did=21b9b94a-ec23-42c1-aaac-e2ae4e4dbffe",
"skey": "12345"
};
export function removeAllTestCookies() {

View file

@ -12,7 +12,7 @@
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
<funnel-footer
ref="funnelFooter"
:isDisabled="!meta.valid"
:isForwardActionDisabled="!meta.valid"
/>
</div>
</Form>

View file

@ -0,0 +1,13 @@
<template>
<p> Estimate </p>
</template>
<script>
export default {
name: "estimate",
methods: {
arePagePrerequisitesValid() {
return true;
},
}
};
</script>

View file

@ -44,7 +44,7 @@
/>
<funnel-footer
ref="funnelFooter"
:isDisabled="!meta.valid"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>

View file

@ -52,7 +52,7 @@
<funnelFooter
ref="funnelFooter"
cmsWidgetName="FunnelFooterWidget"
:isDisabled="!meta.valid"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>

View file

@ -36,6 +36,11 @@ jest.mock("@/store", () => ({
vehicle: {
carId: "C00000000",
image: "test.jpg",
payment: {
insuranceCoverage: {
isVerified: false
}
}
},
eventBusItem: jest.fn(),
damage: {
@ -134,6 +139,7 @@ describe("vehicle-damage.vue", () => {
store: {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: false } },
},
},
},
@ -201,6 +207,7 @@ describe("vehicle-damage.vue", () => {
store: {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: false } },
},
},
},
@ -291,6 +298,7 @@ describe("vehicle-damage.vue", () => {
store: {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: false } },
},
},
},
@ -336,6 +344,7 @@ describe("vehicle-damage.vue", () => {
store: {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: false } },
},
},
},
@ -734,6 +743,7 @@ function setupMocks({
store: {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: false } },
},
},
},

View file

@ -47,7 +47,8 @@
/>
<funnel-footer
cmsWidgetName="FunnelFooterWidget"
:isDisabled="!meta.valid"
:isForwardActionDisabled="!meta.valid"
:isBackButtonHidden=shouldHideBackButton
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>
@ -145,9 +146,11 @@ export default {
}
return false;
},
resetDependentState() {
store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
},
backButtonAction() {
// route to move backwards
this.$router.navigate(
@ -155,6 +158,7 @@ export default {
this.$route
);
},
getDamageLocationsFromStore() {
var glassSelections = [];
@ -275,13 +279,20 @@ export default {
},
navigateForward(partsData){
// CSR-98 TEMP
// Temporary easter egg to navigate to heritage funnel.
const vehicleYearsToShowHeritageFunnel = [ 2001, 2002, 2010 ];
if (vehicleYearsToShowHeritageFunnel.includes(store.getters.vehicle.year)) {
navigateToHeritageFunnel();
return;
}
// If vin already exists, navigate directly to vin-lookup
if(this.$store.getters.vehicle.vin){
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_VIN, this.$route);
return;
}
//found problem questions
if (partsData.data.partsOrQuestions.some(pq => pq.partQuestions != null && pq.partQuestions.length > 0)){
this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_DAMAGE_WITH_PART_QUESTIONS, this.$route, {}, {}, partsData.data);
@ -398,6 +409,9 @@ export default {
})
);
},
shouldHideBackButton(){
return this.$store.getters.payment.insuranceCoverage.isVerified;
}
},
components: {

View file

@ -1,41 +1,272 @@
<template>
<div class="container-fluid shadow rounded-3 px-5 position-relative make-tall">
<funnelHeader ref="funnelHeader" />
<vehicleBanner ref="vehicleBanner" />
<funnelSubHeader ref="funnelSubHeader" />
<vinInformation
class="mb-5"
/>
<funnel-footer
ref="funnelFooter"
:isDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>
</div>
<Form
@submit="onSubmit"
@invalid-submit="onInvalidSubmit"
ref="theForm"
v-slot="{ meta }"
>
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall px-5">
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<div class="row my-2">
<div class="col">
<textboxQuestion cmsWidgetName="VinNumber" v-model="vin" inputId="vin" disableAutoFill validationRules="vin-required|vin-format" :isDisabled=isVinFieldReadOnly />
</div>
</div>
<div class="row my-2">
<div class="col">
<vinInformation />
</div>
</div>
<div class="row my-2">
<div class="col">
<textboxQuestion cmsWidgetName="ServiceZIP" v-model="zip" inputId="zip" mask="#####" disableAutoFill validationRules="zip-required" />
</div>
</div>
<div class="row my-2">
<div class="col">
<textboxQuestion cmsWidgetName="EmailAddress" v-model="email" inputId="email" disableAutoFill validationRules="email-address-required|email-address-format" />
</div>
</div>
<alert
class="my-3"
v-model="customAlertData"
v-if="matchedDifferentVehicle"
alertClass="alert-danger"
cmsWidgetName="MatchedDifferentVehicle"
/>
<alert
class="my-3"
v-model="customAlertData"
v-if="noMatchAlert"
alertClass="alert-warning"
cmsWidgetName="NoMatchAlertWidget"
/>
<alert
class="my-3"
v-model="customAlertData"
v-if="noServiceZip"
alertClass="alert-warning"
cmsWidgetName="NoServiceZipWidget"
/>
<alert
class="my-3"
v-model="customAlertData"
v-if="vinFound"
alertClass="alert-warning"
cmsWidgetName="VinFoundWidget"
/>
<alert
class="my-3"
v-model="customAlertData"
v-if="vinFoundReadOnly"
alertClass="alert-warning"
cmsWidgetName="VinFoundReadOnlyWidget"
/>
<alert
class="my-3"
v-model="customAlertData"
v-if="foundWindshieldAlert"
alertClass="alert-warning"
cmsWidgetName="FoundWindshieldAlert"
/>
<alert
class="my-3"
v-model="customAlertData"
v-if="vinNotFound"
alertClass="alert-warning"
cmsWidgetName="VinNotFound"
/>
<alert
class="my-3"
v-model="customAlertData"
v-if="perfectMatchNewVinAlert"
alertClass="alert-warning"
cmsWidgetName="PerfectMatchNewVinAlert"
/>
<funnelFooter
cmsWidgetName="FunnelFooterWidget"
ref="funnelFooter"
:isDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>
</div>
</Form>
</template>
<script>
// Components
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import alert from "@/ux-components/alert/alert";
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { errorMessages } from "@/constants/error-messages";
import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
// DEFINE VALIDATION RULES
defineRule("zip-required", required(errorMessages.ZIP_REQUIRED));
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
defineRule("email-address-format", regex(/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+).([a-zA-Z]{2,})$/, errorMessages.EMAIL_ADDRESS_FORMAT));
defineRule("vin-required", required(errorMessages.VIN_REQUIRED));
defineRule("vin-format", regex(/^[A-HJ-NPR-Z0-9]{17}$/, errorMessages.VIN_FORMAT));
export default {
name: "vin-lookup",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
props: {
validationRules: String,
},
data() {
return {
matchedDifferentVehicle: false,
noMatchAlert: false,
noServiceZip: false,
vinFound: false,
vinFoundReadOnly: false,
foundWindshieldAlert: false,
vinNotFound: false,
perfectMatchNewVinAlert: false,
vin: '',
zip: '',
email: '',
customAlertData: {},
};
},
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
},
resetDependentState() {
store.commit(storeMutations.UPDATE_REGISTRATION_ADDRESS, null);
store.commit(storeMutations.UPDATE_REGISTRATION_CITY, null);
store.commit(storeMutations.UPDATE_REGISTRATION_FIRST_NAME, null);
store.commit(storeMutations.UPDATE_REGISTRATION_LAST_NAME, null);
store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
},
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
async forwardButtonAction() {
const zipValidation = await this.validateZip(this.zip);
if (!zipValidation.data.isServiceable) {
this.customAlertData.zip = this.zip;
this.$refs.funnelFooter.removeLoader();
this.noServiceZip = true;
return;
}
const vinLookup = await this.lookupVin(this.vin).catch(() => {
this.$refs.funnelFooter.removeLoader();
this.noMatchAlert = true;
return;
});
if (vinLookup.data.carId !== store.getters.vehicle.carId) {
this.customAlertData.vehicleInfo = vinLookup.data.vehicle;
this.$refs.funnelFooter.removeLoader();
this.foundWindshieldAlert = true;
return;
}
const carInfo = this.vinDoesNotMatchCarId ? vinLookup.data : store.getters.vehicle;
this.updateStore(carInfo)
const partsData = await baseMixin.methods.dispatchNonBlockingStoreAction(
this.storeActions.GET_PARTS_OR_QUESTIONS,
{
carId: store.getters.vehicle.carId,
glassArray: store.getters.damage.glassToReplace,
zipCode: this.zip,
vin: vinLookup.vin
},
false
);
this.navigateForward(partsData);
},
navigateForward(partsData){
if(partsData.data.partsOrQuestions[0].partQuestions && partsData.data.partsOrQuestions[0].partQuestions.length > 0){
this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_PARTS_QUESTION, this.$route, {}, {}, partsData.data);
return;
} else if((!partsData.data.partsOrQuestions[0].partQuestions || partsData.data.partsOrQuestions[0].partQuestions.length < 1) && partsData.data.partsOrQuestions[0].parts.length > 1) {
this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_MULTIPLE_PARTS, this.$route, {}, {}, partsData.data);
return;
} else {
this.$router.navigate(this.navigationScenarios.CONTINUING_WITH_SINGLE_PART, this.$route);
}
},
validateZip(zip) {
return baseMixin.methods.dispatchNonBlockingStoreAction(
storeActions.VALIDATE_ZIP,
{ zip }
);
},
lookupVin(vin) {
return baseMixin.methods.dispatchNonBlockingStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin }
);
},
updateStore(carInfo) {
// if(vehicleDamage){
// store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
// }
store.commit(storeMutations.UPDATE_VEHICLE_VIN, this.vin);
store.commit(storeMutations.UPDATE_YEAR, carInfo.year);
store.commit(storeMutations.UPDATE_MAKE, carInfo.make);
store.commit(storeMutations.UPDATE_MODEL, carInfo.model);
store.commit(storeMutations.UPDATE_STYLE, carInfo.style);
store.commit(storeMutations.UPDATE_CAR_ID, carInfo.carId);
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, carInfo.category);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, carInfo.imageUrl);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, carInfo.imageVifNumber);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, carInfo.imageColor);
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, this.zip);
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, this.email);
},
},
computed: {
isVinFieldReadOnly(){
return this.$store.getters.payment.insuranceCoverage.isVerified;
}
},
components: {
Form,
funnelHeader,
vehicleBanner,
funnelSubHeader,
vinInformation,
textboxQuestion,
alert,
funnelFooter,
vinInformation,
},
};
</script>
<style lang="scss">
</style>

View file

@ -7,9 +7,9 @@ import { globalEvents, globalEventTypes } from "@/constants/events";
// Heritage integration
import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper";
import { updateOrCreateFunnelCookie,getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { updateOrCreateFunnelCookie, getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { loadOrderIfPresent, saveOrder } from "@/helpers/heritage-integration/order-helper";
import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel} from "@/helpers/heritage-integration/navigation-helper";
import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin";
import eventBus from "@/helpers/event-bus/event-bus";
@ -35,84 +35,79 @@ const routes = [
name: "root",
async beforeEnter(to, from, next) {
// If we have no query string, or we don't have the FmgPage query string.
if (to.query.fmgPage === undefined) {
await GoToFunnelStartOn404(next);
} else {
try {
try {
// If the saved session has timed out, clear the session, execute 404 logic.
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) {
await GoToFunnelStartOn404(next);
}
// On entering the funnel "fresh", read cookie information, decide what to do next.
if (from.redirectedFrom === undefined) {
const loadOrderResponse = await loadOrderIfPresent();
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to, loadOrderResponse);
// If getPageToRouteExistingOrderTo determines that the return user needs to
// go back to heritage funnel, send them there and stop our current navigation.
if (pageToRedirectTo === 'heritage') {
await navigateToHeritageFunnel();
return next(false);
}
// Assign our fmgPage so it will load normally like the other pages.
to.query.fmgPage = pageToRedirectTo;
}
// Process funnel cookie.
updateOrCreateFunnelCookie();
// If we already have our route, go to it.
if (router.hasRoute(to.query.fmgPage)) {
// Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function.
let component = router.getRoutes().filter((x) => x.name === to.query.fmgPage)[0].components;
// If the component hasn't been loaded fully, load it before we check prerequisites.
if (component.default.methods === undefined) {
component = await component.default();
}
if (!arePagePrerequisitesValid(component)) {
await GoToFunnelStartOn404(next);
}
return next({ name: to.query.fmgPage, query: to.query, params: to.params });
}
// Get route info for the given url. Names will have a 1:1 relationship with names in the Cms.
const routeData = await GetRouteInfoFromPageName(to.query.fmgPage);
// Add our dynamic route.
router.addRoute({
path: routeData[0].path, // Always the same path, because we control it with query strings.
name: routeData[0].name,
component: routeData[0].component,
});
// Call the next components arePagePrerequisitesValid method before load.
// If it returns false, use the 404 logic.
const nextComponent = await router.getRoutes().filter((x) => x.name === routeData[0].name)[0].components.default();
if (!arePagePrerequisitesValid(nextComponent)) {
await GoToFunnelStartOn404(next);
}
// Assign current query string parameters, as well as our fmgPage one.
next({
name: routeData[0].name,
query: Object.assign(to.query, { fmgPage: routeData[0].name }),
params: to.params
});
} catch (error) {
console.log(error);
// If we don't have a route, go to our 404 page.
// If the saved session has timed out, clear the session, execute 404 logic.
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) {
await GoToFunnelStartOn404(next);
}
// On entering the funnel "fresh", read cookie information, decide what to do next.
if (from.redirectedFrom === undefined) {
const loadOrderResponse = await loadOrderIfPresent();
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to, loadOrderResponse);
// If getPageToRouteExistingOrderTo determines that the return user needs to
// go back to heritage funnel, send them there and stop our current navigation.
if (pageToRedirectTo === 'heritage') {
await navigateToHeritageFunnel();
return next(false);
}
// Assign our fmgPage so it will load normally like the other pages.
to.query.fmgPage = pageToRedirectTo;
}
// Process funnel cookie.
updateOrCreateFunnelCookie();
// If we already have our route, go to it.
if (router.hasRoute(to.query.fmgPage)) {
// Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function.
let component = router.getRoutes().filter((x) => x.name === to.query.fmgPage)[0].components;
// If the component hasn't been loaded fully, load it before we check prerequisites.
if (component.default.methods === undefined) {
component = await component.default();
}
if (!arePagePrerequisitesValid(component)) {
await GoToFunnelStartOn404(next);
}
return next({ name: to.query.fmgPage, query: to.query, params: to.params });
}
// Get route info for the given url. Names will have a 1:1 relationship with names in the Cms.
const routeData = await GetRouteInfoFromPageName(to.query.fmgPage);
// Add our dynamic route.
router.addRoute({
path: routeData[0].path, // Always the same path, because we control it with query strings.
name: routeData[0].name,
component: routeData[0].component,
});
// Call the next components arePagePrerequisitesValid method before load.
// If it returns false, use the 404 logic.
const nextComponent = await router.getRoutes().filter((x) => x.name === routeData[0].name)[0].components.default();
if (!arePagePrerequisitesValid(nextComponent)) {
await GoToFunnelStartOn404(next);
}
// Assign current query string parameters, as well as our fmgPage one.
next({
name: routeData[0].name,
query: Object.assign(to.query, { fmgPage: routeData[0].name }),
params: to.params
});
} catch (error) {
console.log(error);
// If we don't have a route, go to our 404 page.
await GoToFunnelStartOn404(next);
}
},
},

View file

@ -11,6 +11,7 @@ const fmgPageValues = {
LICENSE_PLATE_LOOKUP: "license-plate-lookup",
REVEAL: "reveal",
ESTIMATE: "estimate",
ADDRESS_VEHICLES: "address-vehicles",
};
export { fmgPageValues };

View file

@ -4,7 +4,9 @@ const navigationScenarios = {
SELECTED_MAKE: "SELECTED_MAKE",
SELECTED_STYLE: "SELECTED_STYLE",
CLICKED_BACK: "CLICKED_BACK",
CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN",
CLICKED_FORWARD: "CLICKED_FORWARD",
CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN",
SELECTED_PARTS: "SELECTED_PARTS",
SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART",
SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS",

View file

@ -1,6 +1,5 @@
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { applicationConfig } from "@/constants/application-config";
const routingTable = [
{
@ -58,9 +57,13 @@ const routingTable = [
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.VEHICLE_STYLE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
},
{
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_SINGLE_PART,
destinationFmgPageValue: fmgPageValues.REVEAL
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
},
{
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_MULTIPLE_PARTS,
@ -68,7 +71,7 @@ const routingTable = [
},
{
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,//This might be temporary
},
],
},
@ -107,6 +110,10 @@ const routingTable = [
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.REVEAL,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
},
{

View file

@ -45,6 +45,12 @@ const getDefaultState = () => {
glassParts: null,
otherParts: null
},
payment:{
isInsurance: null,
insuranceCoverage: {
isVerified: null
}
},
referralNumber: null,
referralDate: null,
referralCorrelationId: null,
@ -120,6 +126,12 @@ export const mutations = {
updateParentAcctNumber(state, parentAcctNumber) {
state.order.parentAccountNumber = parentAcctNumber;
},
updateIsInsurance(state, isInsurance) {
state.order.payment.isInsurance = isInsurance;
},
updateInsuranceVerifiedStatus(state, isVerified) {
state.order.payment.insuranceCoverage.isVerified = isVerified;
},
// EVENT BUS MUTATIONS
@ -176,23 +188,30 @@ export const mutations = {
state.order.referralNumber = orderInformation.referralNumber;
state.order.referralDate = orderInformation.referralDate;
state.order.referralCorrelationId = orderInformation.referralCorrelationId;
state.order.vehicle = Object.assign(state.order.vehicle, {
year: orderInformation.vehicle?.year,
make: orderInformation.vehicle?.make,
model: orderInformation.vehicle?.model,
style: orderInformation.vehicle?.style,
vin: orderInformation.vehicle?.vin,
carId: orderInformation.vehicle?.carId,
category: orderInformation.vehicle?.category,
imageUrl: orderInformation.vehicle?.imageUrl,
imageVifNumber: orderInformation.vehicle?.imageVifNumber,
imageColor: orderInformation.vehicle?.imageVifColor
imageColor: orderInformation.vehicle?.imageVifColor,
});
state.order.damage.glassToReplace = orderInformation.glassToReplace;
state.order.damage.isRepair = orderInformation.isRepair;
state.order.damage.numberOfChips = orderInformation.numberOfChips;
state.order.lineItems.glassParts = orderInformation.parts;
state.order.parentAccountNumber = orderInformation.parentAccountNumber;
state.order.serviceLocation.zipCode = orderInformation.zipCode;
state.order.payment.isInsurance = orderInformation.IsInsuranceOrder;
state.order.payment.insuranceCoverage.isVerified = orderInformation?.insuranceInfo.coverageVerified;
}
}
@ -210,11 +229,10 @@ export const getters = {
eventBus: (state) => state.applicationUser.eventBus,
damage: (state) => state.order.damage,
lineItems: (state) => state.order.lineItems,
pageData: (state) => (page) => {
return state.applicationUser.pageData[page];
},
pageData: (state) => (page) => { return state.applicationUser.pageData[page]; },
applicationUser: (state) => state.applicationUser,
order: (state) => state.order,
payment: (state) => state.order.payment,
}
// Export Actions

View file

@ -222,6 +222,7 @@ describe("Mutations", () => {
numberOfChips: 0,
parts: [],
parentAccountNumber: "123456789",
insuranceInfo: {}
});
// Assert
@ -232,6 +233,17 @@ describe("Mutations", () => {
expect(storeState.order.vehicle.model).toEqual("ILX");
});
it("updateInsuranceVerifiedStatus, should set isVerified flag", () => {
// Arrange
const storeState = state;
// Act
mutations.updateInsuranceVerifiedStatus(storeState, true);
// Assert
expect(storeState.order.payment.insuranceCoverage.isVerified).toEqual(true);
});
});
describe("Actions", () => {
@ -690,4 +702,15 @@ describe("Getters", () => {
});
it("Payment getter, should return payment data", () => {
// Arrange
const storeState = state;
//Act
mutations.updateInsuranceVerifiedStatus(storeState, true );
//Assert
expect(getters.payment(storeState).insuranceCoverage.isVerified).toEqual(true);
});
});