Merge pull request #351 from Safelite/feature/CSR-92

Feature/csr 92
This commit is contained in:
max-dempsey 2022-04-19 15:14:14 -04:00 committed by GitHub
commit 38234728be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 153 additions and 56 deletions

View file

@ -1,8 +1,5 @@
<template>
<div class="textbox-question" :class="[
errors.length > 0 ? 'has-error' : '',
hasError ? 'has-error' : '',
]">
<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"
v-maska="mask"

View file

@ -0,0 +1,7 @@
const dynamicStrings = {
GLOBAL_STATE: "globalState",
CUSTOM: "custom",
ROUTER_LINK: "routerLink"
};
export { dynamicStrings };

View file

@ -1,5 +1,6 @@
import { storeActions } from "@/constants/store-actions.js";
import store from "@/store";
import { dynamicStrings } from "../constants/dynamic-strings";
export function fetchCmsContentForPage(fmgPage) {
return store
@ -40,12 +41,15 @@ export function fetchCmsContentForPage(fmgPage) {
function mapStringToState(str) {
// Pull all matches out of the string.
const regexExp = new RegExp("{(.*?):(.*?)}", "g");
const matches = [...str.matchAll(regexExp)];
const regexMatches = [...str.matchAll(regexExp)];
const globalStateMatches = regexMatches.filter(match => {
return match[1] === dynamicStrings.GLOBAL_STATE;
})
// Our final string value that will be built from the matches.
let stringBuilder = "";
for (const match of matches) {
for (const match of globalStateMatches) {
// Reset store state for each match.
let storeState = store.state;
@ -60,7 +64,7 @@ function mapStringToState(str) {
const stringWithReplacement = str.replace(match[0], storeState);
// If we still have values we need to substitute, call this function again.
if (stringWithReplacement.includes("{globalState:")) {
if (stringWithReplacement.includes(dynamicStrings.GLOBAL_STATE)) {
return mapStringToState(stringWithReplacement);
}
@ -96,7 +100,7 @@ function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
function processWidgetItemForReplacement(widgetModel, key) {
// If we have a string, and it needs to be replaced.
if (typeof widgetModel[key] === "string") {
if (widgetModel[key].includes("{globalState:")) {
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
widgetModel[key] = mapStringToState(widgetModel[key]);
}
return widgetModel[key];
@ -116,4 +120,4 @@ function processWidgetItemForReplacement(widgetModel, key) {
// If we have something else like a number, boolean, etc. just return it
return widgetModel[key];
}
}

View file

@ -0,0 +1,5 @@
import store from "@/store";
export function getDamageString() {
return store.getters.damage.glassToReplace.length > 1 ? "match" : store.getters.damage.glassToReplace[0].location;
}

View file

@ -0,0 +1,15 @@
import {getDamageString} from "./damage-helper";
jest.mock("@/store", () => ({
getters: {damage: {
glassToReplace: [{location: "TEST"}]
}
}
}));
describe("damage-helper.js", () => {
it("Should return damage getter info", () => {
const damage = getDamageString();
expect(damage).toEqual("TEST")
});
});

View file

@ -26,7 +26,8 @@
</div>
<alert
class="my-3"
cmsWidgetName="NoServiceZipWidget"
:manualHeadline="NoServiceZipHeader"
:manualCopy="NoServiceZipBody"
v-if="newServiceZipRequired"
alertClass="alert-danger"
/>
@ -38,7 +39,8 @@
/>
<alert
class="my-3"
cmsWidgetName="MatchedDifferentVehicleAlertWidget"
:manualHeadline="MatchedDifferentVehicleAlertHeader"
:manualCopy="MatchedDifferentVehicleAlertBody"
v-if="vinDoesNotMatchCarId"
alertClass="alert-warning"
/>
@ -75,8 +77,10 @@ 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 { getDamageString } from "@/helpers/damage-helper";
import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
// DEFINE VALIDATION RULES
defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED));
@ -118,8 +122,38 @@ export default {
zip: '',
email: '',
serviceZip: '',
customAlertData: {}
};
},
computed: {
MatchedDifferentVehicleAlertHeader(){
let text = this.getCmsContent("MatchedDifferentVehicleAlertWidget", "HeadlineText");
// Replace all instances where custom data is implemented
text = text.replaceAll("{custom:damage}", getDamageString());
return text;
},
MatchedDifferentVehicleAlertBody(){
let text = this.getCmsContent("MatchedDifferentVehicleAlertWidget", "BodyText");
// Replace all instances where custom data is implemented
text = text.replaceAll("{custom:damage}", getDamageString());
text = text.replaceAll("{custom:plateLookupYear}", this.customAlertData?.vehicleInfo?.year);
text = text.replaceAll("{custom:plateLookupMake}", this.customAlertData?.vehicleInfo?.make);
text = text.replaceAll("{custom:plateLookupModel}", this.customAlertData?.vehicleInfo?.model);
return text;
},
NoServiceZipHeader(){
let text = this.getCmsContent("NoServiceZipWidget", "HeadlineText");
// Replace all instances where custom data is implemented
text = text.replaceAll("{custom:zip}", this.zip);
return text;
},
NoServiceZipBody(){
return this.getCmsContent("NoServiceZipWidget", "BodyText");
}
},
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
@ -137,6 +171,7 @@ export default {
async forwardButtonAction() {
const zipValidation = this.serviceZip ? await this.validateZip(this.serviceZip) : await this.validateZip(this.zip);
if (!zipValidation.data.isServiceable) {
this.customAlertData.zip = this.zip;
this.$refs.funnelFooter.removeLoader();
this.newServiceZipRequired = true;
return;
@ -146,34 +181,50 @@ export default {
this.vinNotValid = true;
return;
});
if (vinLookup.data.vehicle.carId !== store.getters.vehicle.carId) {
if (vinLookup.data.vehicle.carId !== store.getters.vehicle.carId && !this.vinDoesNotMatchCarId) {
this.customAlertData.vehicleInfo = vinLookup.data.vehicle;
this.$refs.funnelFooter.updateButtonText(`Continue with ${vinLookup.data.vin} ${vinLookup.data.vehicle.year} ${vinLookup.data.vehicle.make} ${vinLookup.data.vehicle.model}`);
this.$refs.funnelFooter.removeLoader();
this.vinDoesNotMatchCarId = true;
return;
//return;
}
let vin;
let vehicleInfo;
let glassOptions;
if(this.vinDoesNotMatchCarId) {
vin = vinLookup.data.vin;
vehicleInfo = vinLookup.data.vehicle;
glassOptions = store.dispatch(storeActions.GET_DAMAGE_OPTIONS, { carId: vinLookup.data.vehicle.carId }).data;
} else {
vin = store.getters.vehicle.vin;
vehicleInfo = store.getters.vehicle;
glassOptions = store.getters.damage.glassToReplace;
}
//this.updateCustomerInfo(vin, vehicleInfo, zipValidation.data.state);
const partsData = await baseMixin.methods.dispatchNonBlockingStoreAction(
this.storeActions.GET_PARTS_OR_QUESTIONS,
{
carId: store.getters.vehicle.carId,
carId: vehicleInfo.carId,
glassArray: store.getters.damage.glassToReplace,
zipCode: this.zip,
vin: vinLookup.vin
zipCode: this.serviceZip ? this.serviceZip : this.zip,
vin: vin
},
false
);
this.navigateForward(partsData);
console.log(partsData)
//this.navigateForward(partsData);
},
navigateForward(partsData){
if(partsData.data.partsOrQuestions[0].partQuestions && partsData.data.partsOrQuestions[0].partQuestions.length > 0){
if(partsData.data){
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);
navigateToHeritageFunnel();
return;
}
},
validateZip(zip) {
@ -188,25 +239,25 @@ export default {
{ licensePlate: plate, licenseState: state }
);
},
updateStore() {
updateCustomerInfo(vin, vehicleInfo, registrationState) {
// if(vehicleDamage){
// store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
// }
store.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
store.commit(storeMutations.UPDATE_YEAR, null);
store.commit(storeMutations.UPDATE_MAKE, null);
store.commit(storeMutations.UPDATE_MODEL, null);
store.commit(storeMutations.UPDATE_STYLE, null);
store.commit(storeMutations.UPDATE_CAR_ID, null);
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null);
store.commit(storeMutations.UPDATE_REGISTRATION_LICENSE_PLATE, null);
store.commit(storeMutations.UPDATE_REGISTRATION_STATE, null);
store.commit(storeMutations.UPDATE_REGISTRATION_ZIP_CODE, null);
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, null);
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, null);
store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin);
store.commit(storeMutations.UPDATE_YEAR, vehicleInfo.year);
store.commit(storeMutations.UPDATE_MAKE, vehicleInfo.make);
store.commit(storeMutations.UPDATE_MODEL, vehicleInfo.model);
store.commit(storeMutations.UPDATE_STYLE, vehicleInfo.style);
store.commit(storeMutations.UPDATE_CAR_ID, vehicleInfo.carId);
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, vehicleInfo.category);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, vehicleInfo.imageUrl);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, vehicleInfo.imageVifNumber);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, vehicleInfo.imageColor);
store.commit(storeMutations.UPDATE_REGISTRATION_LICENSE_PLATE, this.licensePlate);
store.commit(storeMutations.UPDATE_REGISTRATION_STATE, registrationState);
store.commit(storeMutations.UPDATE_REGISTRATION_ZIP_CODE, this.zip);
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, this.serviceZip);
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, this.email);
},
},
components: {

View file

@ -1,10 +1,12 @@
<template>
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
<funnelHeader ref="funnelHeader" />
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage="false" />
<funnelSubHeader ref="funnelSubHeader" />
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage=false />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<h1>Part Questions Page Placeholder</h1>
<funnelFooter ref="funnelFooter" @back-clicked="backButtonAction" />
<funnel-footer
cmsWidgetName="FunnelFooterWidget" @back-clicked="backButtonAction"
/>
</div>
</template>
@ -49,18 +51,7 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.$refs.funnelHeader.initializeComponent(
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.funnelSubHeader.initializeComponent(
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.funnelFooter.initializeComponent(
resultMap.cmsContent.FunnelFooterWidget
);
vm.setCmsContent(resultMap.cmsContent);
});
},
methods: {

View file

@ -1,4 +1,4 @@
import { shallowMount } from "@vue/test-utils";
import { shallowMount } from "@vue/test-utils";
import alert from "./alert";
describe("alert.vue", () => {
@ -9,6 +9,13 @@ describe("alert.vue", () => {
propsData: {
isDismissible: true
},
computed: {
splitAlertCopyForLink: {
get() {
return "TEST";
},
}
},
mixins: [mockMixin]
});
@ -24,6 +31,13 @@ describe("alert.vue", () => {
propsData: {
alertClass: 'warning'
},
computed: {
splitAlertCopyForLink: {
get() {
return "TEST";
},
}
},
mixins: [mockMixin]
});

View file

@ -5,7 +5,15 @@
:class="[isDismissible ? 'alert-dismissible' : '', this.alertClass]"
>
<p class="m-0 fw-bold small alert-heading">{{ alertHeadline }}</p>
<p class="m-0 text-body small">{{ alertCopy }}</p>
<p v-if="splitAlertCopyForLink.length">
<template v-for="copy in splitAlertCopyForLink" :key="copy">
<span v-if="copy.includes('routerLink:')" class="m-0 text-body small">
<router-link :to="{query: {fmgPage: `${copy.split(':')[1].split(',')[0]}`}, name: 'root'}">{{ copy.split(':')[1].split(',')[1] }}</router-link>
</span>
<span v-else class="m-0 text-body small">{{ copy }}</span>
</template>
</p>
<p v-else class="m-0 text-body small">{{ alertCopy }}</p>
<button
type="button"
class="btn-close p-2"
@ -26,6 +34,7 @@
</template>
<script>
export default {
name: "alert",
props: {
@ -49,6 +58,10 @@ export default {
alertCopy(){
return this.cmsWidgetName ? this.getCmsContent(this.cmsWidgetName, 'BodyText') : this.manualCopy;
},
splitAlertCopyForLink(){
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
return this.alertCopy.split(/{(.*?)}/g);
}
},
};
</script>