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/vin-lookup/vin-lookup.vue",
"!src/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question.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/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/part-questions/**/*.vue",
"!src/layouts/reveal/**/*.vue", "!src/layouts/reveal/**/*.vue",
"!src/layouts/estimate/**/*.vue",
// REMOVE THESE AFTER WRITING UNIT TESTS // REMOVE THESE AFTER WRITING UNIT TESTS
"!src/layouts/address-lookup/address-lookup.vue", "!src/layouts/address-lookup/address-lookup.vue",
"!src/layouts/address-lookup/customer-questions/customer-questions.vue", "!src/layouts/address-lookup/customer-questions/customer-questions.vue",

View file

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

View file

@ -64,7 +64,8 @@ export default {
handleBlur, handleBlur,
handleChange, handleChange,
meta, meta,
validate validate,
errors,
} = useField(props.inputId, props.validationRules, fieldOptions); } = useField(props.inputId, props.validationRules, fieldOptions);
return { return {
@ -73,6 +74,7 @@ export default {
handleChange, handleChange,
validate, validate,
meta, meta,
errors,
}; };
}, },
computed: { computed: {

View file

@ -16,6 +16,8 @@ const errorMessages = {
LAST_NAME_REQUIRED: "Please enter your last name", LAST_NAME_REQUIRED: "Please enter your last name",
EMAIL_ADDRESS_REQUIRED: "Please enter your email address", EMAIL_ADDRESS_REQUIRED: "Please enter your email address",
EMAIL_ADDRESS_FORMAT: "Please enter a valid 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"; import { removeAllTestCookies, setupCookies } from "@/helpers/unit-test-helper";
describe("cookies", () => { describe("cookies", () => {
@ -100,5 +100,31 @@ describe("cookies", () => {
expect(actualCookieValue).toBeNull(); 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,6 +2,7 @@ import { queryStrings } from "@/constants/query-strings";
import { externalUrls } from "@/router/router-constants/externalUrl-values"; import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { saveOrder } from "@/helpers/heritage-integration/order-helper.js"; import { saveOrder } from "@/helpers/heritage-integration/order-helper.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
@ -14,43 +15,25 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
// If the user is coming in via the Safelite.Com CTA // If the user is coming in via the Safelite.Com CTA
if (toRoute.query[queryStrings.START_TYPE] === 'fmg') { if (toRoute.query[queryStrings.START_TYPE] === 'fmg') {
// If they have an existing order, return 'heritage' for the page name. // If they have an existing order, return 'heritage' for the page name.
if (existingHeritageOrder) { if (existingHeritageOrder) {
return 'heritage'; return 'heritage';
} }
return await getLatestPageForRedirection();
} }
// If navigating to a specific page, and that page is not part of the vin pages.
// If this is a non-CTA navigation, determine where to send the user based on page prerequisites. // Return that page, so that it can navigate like normal.
// This also works if a user has a 'fmg' start_type query string but no current order. if (toRoute.query[queryStrings.FMG_PAGE] !== undefined && !isVinRelatedPage(toRoute)) {
// That shouldn't happen, but it's possible. return overrideYmmsDirectionIfNeeded(toRoute);
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 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, externalUrls.HERITAGE_FUNNEL,
{ {
corid: store.getters.order.referralCorrelationId, corid: store.getters.order.referralCorrelationId,
src: "concept-funnel", src: "concept-funnel"
// TODO CSR-28, remove this
cns: "all",
experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=NoShowPackages_CONTROL=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true"
} }
); );
} }
/*
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); const result = await getPageToRouteExistingOrderTo(toRoute, false);
//Assert //Assert
//expect(result).toBe('vin-lookup'); expect(result).toBe('vin-lookup');
expect(result).toBe('vehicle-damage');
}); });
test("getPageToRouteExistingOrderTo, should return estimate", async () => { test("getPageToRouteExistingOrderTo, should return estimate", async () => {
@ -284,8 +283,7 @@ describe("getPageToRouteExistingOrderTo", () => {
const result = await getPageToRouteExistingOrderTo(toRoute, false); const result = await getPageToRouteExistingOrderTo(toRoute, false);
//Assert //Assert
//expect(result).toBe('estimate'); expect(result).toBe('estimate');
expect(result).toBe('vehicle-damage');
}); });
test("getPageToRouteExistingOrderTo, existing order, should return heritage", async () => { 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}`, [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", "UNIQUE_SESSION_ID": "33756020-b58e-4ec7-b8b8-3f1576719c40",
"anotherCookie": "{}", "anotherCookie": "{}",
"someOtherCookie": "{}" "someOtherCookie": "{}",
"dxdev": "did=21b9b94a-ec23-42c1-aaac-e2ae4e4dbffe",
"skey": "12345"
}; };
export function removeAllTestCookies() { export function removeAllTestCookies() {

View file

@ -12,7 +12,7 @@
<customerQuestions ref="customerQuestions" v-model="customerQuestions" /> <customerQuestions ref="customerQuestions" v-model="customerQuestions" />
<funnel-footer <funnel-footer
ref="funnelFooter" ref="funnelFooter"
:isDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
/> />
</div> </div>
</Form> </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 <funnel-footer
ref="funnelFooter" ref="funnelFooter"
:isDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" @ForwardClicked="forwardButtonAction"
/> />

View file

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

View file

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

View file

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

View file

@ -1,41 +1,272 @@
<template> <template>
<div class="container-fluid shadow rounded-3 px-5 position-relative make-tall"> <Form
<funnelHeader ref="funnelHeader" /> @submit="onSubmit"
<vehicleBanner ref="vehicleBanner" /> @invalid-submit="onInvalidSubmit"
<funnelSubHeader ref="funnelSubHeader" /> ref="theForm"
<vinInformation v-slot="{ meta }"
class="mb-5" >
/> <div class="container-fluid shadow rounded-3 p-2 position-relative make-tall px-5">
<funnel-footer <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
ref="funnelFooter" <vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
:isDisabled="!meta.valid" <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
@back-clicked="backButtonAction" <div class="row my-2">
@ForwardClicked="forwardButtonAction" <div class="col">
/> <textboxQuestion cmsWidgetName="VinNumber" v-model="vin" inputId="vin" disableAutoFill validationRules="vin-required|vin-format" :isDisabled=isVinFieldReadOnly />
</div> </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> </template>
<script> <script>
// Components // Components
import funnelHeader from "@/common-components/funnel-header/funnel-header"; 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 vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; 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 textboxQuestion from "@/common-components/textbox-question/textbox-question";
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information"; 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 { export default {
name: "vin-lookup", 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: { components: {
Form,
funnelHeader, funnelHeader,
vehicleBanner, vehicleBanner,
funnelSubHeader, funnelSubHeader,
vinInformation, textboxQuestion,
alert,
funnelFooter, funnelFooter,
vinInformation,
}, },
}; };
</script> </script>
<style lang="scss">
</style>

View file

@ -7,9 +7,9 @@ import { globalEvents, globalEventTypes } from "@/constants/events";
// Heritage integration // Heritage integration
import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper"; 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 { 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 baseMixin from "@/mixins/base-mixin";
import eventBus from "@/helpers/event-bus/event-bus"; import eventBus from "@/helpers/event-bus/event-bus";
@ -35,84 +35,79 @@ const routes = [
name: "root", name: "root",
async beforeEnter(to, from, next) { async beforeEnter(to, from, next) {
// If we have no query string, or we don't have the FmgPage query string. // If we have no query string, or we don't have the FmgPage query string.
if (to.query.fmgPage === undefined) { try {
await GoToFunnelStartOn404(next);
} else {
try {
// If the saved session has timed out, clear the session, execute 404 logic. // If the saved session has timed out, clear the session, execute 404 logic.
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { 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); 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", LICENSE_PLATE_LOOKUP: "license-plate-lookup",
REVEAL: "reveal", REVEAL: "reveal",
ESTIMATE: "estimate", ESTIMATE: "estimate",
ADDRESS_VEHICLES: "address-vehicles",
}; };
export { fmgPageValues }; export { fmgPageValues };

View file

@ -4,7 +4,9 @@ const navigationScenarios = {
SELECTED_MAKE: "SELECTED_MAKE", SELECTED_MAKE: "SELECTED_MAKE",
SELECTED_STYLE: "SELECTED_STYLE", SELECTED_STYLE: "SELECTED_STYLE",
CLICKED_BACK: "CLICKED_BACK", CLICKED_BACK: "CLICKED_BACK",
CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN",
CLICKED_FORWARD: "CLICKED_FORWARD", CLICKED_FORWARD: "CLICKED_FORWARD",
CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN",
SELECTED_PARTS: "SELECTED_PARTS", SELECTED_PARTS: "SELECTED_PARTS",
SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART", SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART",
SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS", 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 { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { applicationConfig } from "@/constants/application-config";
const routingTable = [ const routingTable = [
{ {
@ -58,9 +57,13 @@ const routingTable = [
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.VEHICLE_STYLE, destinationFmgPageValue: fmgPageValues.VEHICLE_STYLE,
}, },
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
},
{ {
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_SINGLE_PART, scenario: navigationScenarios.SELECTED_DAMAGE_WITH_SINGLE_PART,
destinationFmgPageValue: fmgPageValues.REVEAL destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
}, },
{ {
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_MULTIPLE_PARTS, scenario: navigationScenarios.SELECTED_DAMAGE_WITH_MULTIPLE_PARTS,
@ -68,7 +71,7 @@ const routingTable = [
}, },
{ {
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_PART_QUESTIONS, 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: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.REVEAL,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
}, },
{ {

View file

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

View file

@ -222,6 +222,7 @@ describe("Mutations", () => {
numberOfChips: 0, numberOfChips: 0,
parts: [], parts: [],
parentAccountNumber: "123456789", parentAccountNumber: "123456789",
insuranceInfo: {}
}); });
// Assert // Assert
@ -232,6 +233,17 @@ describe("Mutations", () => {
expect(storeState.order.vehicle.model).toEqual("ILX"); 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", () => { 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);
});
}); });