Merge develop
This commit is contained in:
parent
f9fdd405d9
commit
166978bc3a
69 changed files with 2508 additions and 1382 deletions
|
|
@ -2,6 +2,7 @@ const queryStrings = {
|
|||
FMG_PAGE: "fmgPage",
|
||||
START_TYPE: "start_type",
|
||||
ZIP_CODE: "zipcode",
|
||||
PROMO: "promo",
|
||||
};
|
||||
|
||||
export { queryStrings };
|
||||
|
|
|
|||
|
|
@ -192,9 +192,9 @@ export default {
|
|||
classes = "d-flex flex-row p-0";
|
||||
break;
|
||||
case "listCard":
|
||||
classes = "row g-2 justify-content-center mb-1";
|
||||
classes = "row g-2 g-md-5 justify-content-center mb-1";
|
||||
if (this.isWide) {
|
||||
classes += " flex-column";
|
||||
classes += "flex-column";
|
||||
}
|
||||
break;
|
||||
case "radio":
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ export default {
|
|||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked[type="checkbox"] {
|
||||
background-color: $blue;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.form-check-input {
|
||||
|
|
|
|||
|
|
@ -148,12 +148,25 @@ export default {
|
|||
}
|
||||
&.modal-component {
|
||||
.modal-dialog {
|
||||
max-width: 576px;
|
||||
max-width: 767px;
|
||||
margin: 0 auto;
|
||||
@include media-breakpoint-up(md) {
|
||||
width: 376px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(100%, 0);
|
||||
}
|
||||
.modal-content {
|
||||
margin: 0 auto;
|
||||
margin: 1.5rem auto 0 auto;
|
||||
box-shadow: 0px 16px 48px -16px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 1.5rem 1.5rem 0 0;
|
||||
@include media-breakpoint-up(md) {
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
height: 100vh;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.modal-body {
|
||||
.modal-sub-body {
|
||||
color: $gray-600;
|
||||
|
|
@ -166,6 +179,9 @@ export default {
|
|||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.modal-dialog-centered {
|
||||
|
|
@ -184,6 +200,11 @@ export default {
|
|||
&.show .modal-dialog {
|
||||
transform: translateY(0);
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
&.fade {
|
||||
transition: opacity 0.25s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
body {
|
||||
.modal-backdrop {
|
||||
|
|
|
|||
|
|
@ -158,7 +158,6 @@ export default {
|
|||
}
|
||||
.modal {
|
||||
&.menu-modal {
|
||||
max-width: 576px;
|
||||
left: auto;
|
||||
height: calc(100% - 72px);
|
||||
top: 72px;
|
||||
|
|
@ -175,7 +174,6 @@ export default {
|
|||
}
|
||||
.modal-fullscreen {
|
||||
width: 100vw;
|
||||
max-width: 576px;
|
||||
}
|
||||
.navigation-link {
|
||||
margin-bottom: 1.5rem;
|
||||
|
|
@ -188,7 +186,7 @@ export default {
|
|||
position: absolute;
|
||||
padding: 1.5rem 1rem 1.5rem 1.5rem;
|
||||
right: 0;
|
||||
top: -4.25rem;
|
||||
top: -5rem;
|
||||
button {
|
||||
border: none;
|
||||
&.menu-button {
|
||||
|
|
|
|||
|
|
@ -1,34 +1,48 @@
|
|||
<template>
|
||||
<div :class="`page-container-grouped-styles questions-page`">
|
||||
<div :class="`questions-page`">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="alertWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="alertFewMoreQuestionsHeader"
|
||||
:manualCopy="alertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[questionsDatum.answerKey]"
|
||||
:questionData="questionsDatum.questions"
|
||||
:index="i"
|
||||
v-if="showThisQuestionChain(questionsDatum, i)"
|
||||
:answerKey="questionsDatum.answerKey"
|
||||
:validationRules="validationRules" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="alertWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="alertFewMoreQuestionsHeader"
|
||||
:manualCopy="alertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[questionsDatum.answerKey]"
|
||||
:questionData="questionsDatum.questions"
|
||||
:index="i"
|
||||
v-if="showThisQuestionChain(questionsDatum, i)"
|
||||
:answerKey="questionsDatum.answerKey"
|
||||
:validationRules="validationRules" />
|
||||
</div>
|
||||
|
||||
<navbar
|
||||
ref="navbar"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!isMetaValid"
|
||||
@back-clicked="handleBackButtonAction"
|
||||
@ForwardClicked="handleForwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!isMetaValid"
|
||||
@back-clicked="handleBackButtonAction"
|
||||
@ForwardClicked="handleForwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -40,7 +54,7 @@ import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
|||
import alert from "@/ux-components/alert/alert";
|
||||
import questionChain from "@/digital-components/question-chain/question-chain";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
export default {
|
||||
|
|
@ -88,7 +102,7 @@ export default {
|
|||
alert,
|
||||
questionChain,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
loadingModal,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-if="isModalVisible">
|
||||
<div class="dimmer-overlay"></div>
|
||||
<div class="container-fluid modal-loader" :class="layoutClass">
|
||||
<div v-if="notFullScreen" class="dimmer-overlay"></div>
|
||||
<div class="modal-loader" :class="layoutClass">
|
||||
<div class="row g-2 h-100 d-flex align-items-center">
|
||||
<div class="container-fluid overflow-hidden">
|
||||
<div class="row h-100">
|
||||
|
|
@ -69,7 +69,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
layoutClass() {
|
||||
return this.notFullScreen ? "card-layout" : "full-screen";
|
||||
return this.notFullScreen ? "card-layout" : "full-screen container-fluid";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -123,12 +123,20 @@ export default {
|
|||
}
|
||||
|
||||
&.card-layout {
|
||||
height: 186px;
|
||||
width: calc(100% - 3rem);
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
position: absolute;
|
||||
top: calc(50% - 93px);
|
||||
left: 1.5rem;
|
||||
|
||||
& .text-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .mb-4 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tagbg {
|
||||
|
|
|
|||
59
src/fmg-components/nav-bar/nav-bar.spec.js
Normal file
59
src/fmg-components/nav-bar/nav-bar.spec.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { mount } from "@vue/test-utils";
|
||||
import navbar from "./nav-bar";
|
||||
|
||||
describe("nav-bar.vue", () => {
|
||||
test("Should emit ForwardClicked on button click", async () => {
|
||||
// Act
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
wrapper.vm.buttonClick();
|
||||
// Assert
|
||||
expect(wrapper.emitted()["ForwardClicked"][0]).toHaveBeenCalled;
|
||||
});
|
||||
|
||||
test("Should emit BackClicked on link click", async () => {
|
||||
// Act
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
wrapper.vm.linkClick();
|
||||
// Assert
|
||||
expect(wrapper.emitted()["BackClicked"][0]).toHaveBeenCalled;
|
||||
});
|
||||
|
||||
test("Should change button text when update button text is called", async () => {
|
||||
// Act
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
wrapper.vm.updateButtonText("newText");
|
||||
|
||||
// Assert
|
||||
expect(wrapper.componentVM.customButtontext).toBe("newText");
|
||||
});
|
||||
|
||||
test("should run removeLoader fn on buttonMain and return false for onkeydown fn", async () => {
|
||||
// Arrange
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.$refs.buttonMain.removeLoader = jest.fn();
|
||||
wrapper.vm.removeLoader();
|
||||
const spy = jest.spyOn(document, "onkeydown");
|
||||
document.onkeydown();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$refs.buttonMain.removeLoader).toHaveBeenCalled();
|
||||
expect(spy).toReturnWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn(),
|
||||
getFooterInfoBoxHeight: jest.fn(() => 80),
|
||||
},
|
||||
};
|
||||
140
src/fmg-components/nav-bar/nav-bar.vue
Normal file
140
src/fmg-components/nav-bar/nav-bar.vue
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div class="row"></div>
|
||||
<div class="nav-bar container-fluid g-5 my-5 px-0" id="infoBox">
|
||||
<div class="row d-flex flex-row-reverse justify-content-between align-items-center vw-100">
|
||||
<div
|
||||
:class="['col button-col d-flex', buttonSize ? 'large-button' : 'medium-button']"
|
||||
id="stacked">
|
||||
<buttonMain
|
||||
ref="buttonMain"
|
||||
isPrimary
|
||||
:buttonText="buttonText"
|
||||
loaderColor="white"
|
||||
:class="isForwardActionDisabled && 'form-test-invalid'"
|
||||
:aria-disabled="isForwardActionDisabled"
|
||||
:isDisabled="isForwardActionDisabled"
|
||||
@click-event="buttonClick"
|
||||
data-bs-target="#footerModal"
|
||||
data-test-id="nav-bar-main-button"
|
||||
data-bs-dismiss="modal" />
|
||||
</div>
|
||||
<div v-if="!isBackButtonHidden" class="col-auto link-col py-1 text-break">
|
||||
<textLink
|
||||
linkType="navigation"
|
||||
:text="backLink"
|
||||
useLoadingModal
|
||||
@click-event="linkClick"
|
||||
href="javascript:void(0)"
|
||||
data-bs-target="#footerModal"
|
||||
data-bs-dismiss="modal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
|
||||
export default {
|
||||
name: "navbar",
|
||||
emits: ["BackClicked", "ForwardClicked"], // <--- should remove oodles of warnings in dev tools
|
||||
props: {
|
||||
isForwardActionDisabled: Boolean,
|
||||
isBackButtonHidden: { type: Boolean, default: false },
|
||||
cmsWidgetName: String,
|
||||
buttonSize: { type: Boolean, default: false },
|
||||
},
|
||||
components: {
|
||||
textLink,
|
||||
buttonMain,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
customButtontext: "",
|
||||
};
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
document.onkeydown = null;
|
||||
},
|
||||
computed: {
|
||||
backLink() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BackButtonText");
|
||||
},
|
||||
buttonText() {
|
||||
return this.customButtontext
|
||||
? this.customButtontext
|
||||
: this.getCmsContent(this.cmsWidgetName, "ForwardButtonText");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateButtonText(newText) {
|
||||
this.customButtontext = newText;
|
||||
},
|
||||
removeLoader() {
|
||||
this.$refs.buttonMain.removeLoader();
|
||||
document.onkeydown = function (e) {
|
||||
return true;
|
||||
};
|
||||
},
|
||||
buttonClick() {
|
||||
//prevent keyboard input after button click
|
||||
document.onkeydown = function (e) {
|
||||
return false;
|
||||
};
|
||||
this.$emit("ForwardClicked");
|
||||
},
|
||||
linkClick() {
|
||||
this.$emit("BackClicked");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-bar {
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.col,
|
||||
.col-auto,
|
||||
.col button {
|
||||
width: 100%;
|
||||
}
|
||||
@media only screen and (min-width: 340px) {
|
||||
.col,
|
||||
.col-auto,
|
||||
.col button {
|
||||
width: auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.btn-primary {
|
||||
width: auto;
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
& > .container-fluid {
|
||||
overflow-x: visible; // needed to fix hidden footer on some iphones
|
||||
}
|
||||
.medium-button {
|
||||
// Define styles for a medium button (default size)
|
||||
justify-content: flex-end;
|
||||
width: auto;
|
||||
}
|
||||
.large-button {
|
||||
justify-content: center;
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -12,8 +12,8 @@ export default {
|
|||
method,
|
||||
endpoint,
|
||||
payload,
|
||||
logApiCall = true,
|
||||
isFormData = false,
|
||||
logApiCall = false,
|
||||
pageNameToLog = null,
|
||||
additionalSuccessEventDataHandler,
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -37,13 +37,11 @@ export default {
|
|||
if (additionalSuccessEventDataHandler) {
|
||||
additionalEventData = "_" + additionalSuccessEventDataHandler(response);
|
||||
}
|
||||
const pageName = analyticsMixIn.methods.getPageName();
|
||||
const nextPageName = router.lastNavigationPage || pageName;
|
||||
const endpointWithoutParams =
|
||||
analyticsMixIn.methods.removeParamsFromEndpoint(endpoint);
|
||||
analyticsMixIn.methods.pushEventToGA(
|
||||
GaCategories.API_RESPONSE,
|
||||
`${nextPageName}_${endpointWithoutParams}`,
|
||||
`${pageNameToLog}_${endpointWithoutParams}`,
|
||||
`${GaLabels.SUCCESS}${additionalEventData}`,
|
||||
true
|
||||
);
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ export function includesWindshieldReplacement() {
|
|||
return windshieldMatches.length > 0;
|
||||
}
|
||||
|
||||
export async function isGlassAvailableForCarId(carId) {
|
||||
const newGlassOptions = await baseMixin.methods.dispatchStoreAction(
|
||||
export async function isGlassAvailableForCarId(carId, pageNameToLog) {
|
||||
const newGlassOptions = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: carId }
|
||||
{ carId: carId },
|
||||
pageNameToLog
|
||||
);
|
||||
|
||||
const currentGlassOptions = store.getters.damage.glassToReplace;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ jest.mock("@/mixins/base-mixin.js", () => ({
|
|||
},
|
||||
};
|
||||
}),
|
||||
dispatchStoreActionWithLogging: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
data: {
|
||||
windshieldOptions: { availableReplacementOptions: ["windshield"] },
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -77,7 +84,7 @@ describe("damage-helper.js", () => {
|
|||
];
|
||||
|
||||
// Act
|
||||
const isGlassAvailable = await isGlassAvailableForCarId();
|
||||
const isGlassAvailable = await isGlassAvailableForCarId("id", "testName");
|
||||
|
||||
// Assert
|
||||
expect(isGlassAvailable).toEqual(true);
|
||||
|
|
@ -91,7 +98,7 @@ describe("damage-helper.js", () => {
|
|||
{ glassLocation: "Windshield", glassName: "sideWindow" },
|
||||
];
|
||||
|
||||
const isGlassAvailable = await isGlassAvailableForCarId();
|
||||
const isGlassAvailable = await isGlassAvailableForCarId("id", "testName");
|
||||
|
||||
// Assert
|
||||
expect(isGlassAvailable).toEqual(false);
|
||||
|
|
|
|||
10
src/helpers/date-helper.js
Normal file
10
src/helpers/date-helper.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export function getDateDifferenceInDays(startDate, endDate) {
|
||||
var date1 = new Date(endDate);
|
||||
date1.setHours(0, 0, 0, 0);
|
||||
var date2 = new Date(startDate);
|
||||
date2.setHours(0, 0, 0, 0);
|
||||
// To calculate the time difference of two dates
|
||||
var Difference_In_Time = date1.getTime() - date2.getTime();
|
||||
// To calculate the no. of days between two dates
|
||||
return Difference_In_Time / (1000 * 3600 * 24);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
import { externalUrls } from "@/router/router-constants/externalUrl-values";
|
||||
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
|
||||
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
||||
|
|
@ -16,15 +17,11 @@ import router from "@/router";
|
|||
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.
|
||||
*/
|
||||
export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) {
|
||||
export async function getPageToRouteExistingOrderTo(toRoute = {}) {
|
||||
// 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 fmgPageValues.HERITAGE;
|
||||
}
|
||||
|
||||
return await getLatestPageForRedirection();
|
||||
const latestPageRoute = await getLatestPageForRedirection();
|
||||
return latestPageRoute;
|
||||
}
|
||||
|
||||
// If navigating to a specific page, and that page is not part of the vin pages.
|
||||
|
|
@ -41,7 +38,6 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
|
|||
// 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;
|
||||
}
|
||||
|
||||
|
|
@ -49,31 +45,41 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
|
|||
Used to navigate to the heritage funnel with the correct query string and url.
|
||||
*/
|
||||
|
||||
export async function navigateToHeritageFunnel({ shouldSaveSession = true, loadingModal }) {
|
||||
export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLog, loadingModal }) {
|
||||
// Create the order (or save existing order) when navigating to Heritage Funnel.
|
||||
if (shouldSaveSession) {
|
||||
await saveSession({ shouldAwaitSaveSessionQueue: true });
|
||||
await saveSession({ pageNameToLog: pageNameToLog, shouldAwaitSaveSessionQueue: true });
|
||||
}
|
||||
|
||||
if (loadingModal && loadingModal.showModal) {
|
||||
loadingModal.showModal();
|
||||
}
|
||||
|
||||
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, {
|
||||
var heritageParms = {
|
||||
corid: store.getters.order.referralCorrelationId,
|
||||
src: "concept-funnel",
|
||||
conceptsqid: store.getters.applicationUser.savedSessionId,
|
||||
isInsurance: store.getters.payment.isInsurance,
|
||||
});
|
||||
};
|
||||
|
||||
const promo = getQuerystringParameter(queryStrings.PROMO);
|
||||
|
||||
if (promo) {
|
||||
heritageParms["promo"] = promo;
|
||||
}
|
||||
|
||||
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, heritageParms);
|
||||
}
|
||||
|
||||
export async function skipVinLookup() {
|
||||
if (store.getters.damage.isRepair) {
|
||||
return true;
|
||||
}
|
||||
const isVinOptionalVehicle = store.getters.order.vehicle.make
|
||||
? await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE)
|
||||
: false;
|
||||
|
||||
return (
|
||||
store.getters.damage.isRepair ||
|
||||
isVinOptionalVehicle ||
|
||||
!includesWindshieldReplacement() ||
|
||||
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
|
||||
|
|
@ -81,18 +87,18 @@ export async function skipVinLookup() {
|
|||
}
|
||||
|
||||
export async function skipVinLookupNotRepair() {
|
||||
if (store.getters.damage.isRepair) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isVinOptionalVehicle = store.getters.order.vehicle.make
|
||||
? await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE)
|
||||
: false;
|
||||
|
||||
return (
|
||||
!store.getters.damage.isRepair &&
|
||||
(isVinOptionalVehicle ||
|
||||
!includesWindshieldReplacement() ||
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.SUPPRESS_VIN_CAPTURE,
|
||||
"true"
|
||||
))
|
||||
isVinOptionalVehicle ||
|
||||
!includesWindshieldReplacement() ||
|
||||
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE);
|
||||
|
|
@ -59,7 +59,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
|
||||
|
|
@ -97,7 +97,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.ESTIMATE);
|
||||
|
|
@ -125,7 +125,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.ESTIMATE);
|
||||
|
|
@ -153,7 +153,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.CAPABILITY_QUESTIONS);
|
||||
|
|
@ -181,7 +181,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.MOLDING_QUESTIONS);
|
||||
|
|
@ -209,7 +209,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_PARTS);
|
||||
|
|
@ -237,29 +237,11 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.PART_QUESTIONS);
|
||||
});
|
||||
|
||||
test("existing order > should return heritage", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {
|
||||
[queryStrings.START_TYPE]: "fmg",
|
||||
},
|
||||
};
|
||||
|
||||
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
|
||||
store.commit(storeMutations.UPDATE_MAKE, "acura");
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, true);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(fmgPageValues.HERITAGE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("navigateToHeritageFunnel", () => {
|
||||
|
|
@ -295,7 +277,7 @@ describe("navigateToHeritageFunnel", () => {
|
|||
router.navigateToExternalUrl = jest.fn();
|
||||
|
||||
// Act
|
||||
await navigateToHeritageFunnel({});
|
||||
await navigateToHeritageFunnel({ loadingModal: null, shouldSaveSession: true });
|
||||
|
||||
// Assert
|
||||
expect(saveSessionFunction).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { storeMutations } from "@/constants/store-mutations";
|
|||
it will reset the state and go back to the start of the funnel.
|
||||
|
||||
*/
|
||||
export async function loadSessionIfPresent(isConceptInsurance) {
|
||||
export async function loadSessionIfPresent(isConceptInsurance, pageNameToLog) {
|
||||
const funnelCookie = getFunnelCookie();
|
||||
|
||||
// Do nothing if there is no cookie or session to use for loading.
|
||||
|
|
@ -42,7 +42,8 @@ export async function loadSessionIfPresent(isConceptInsurance) {
|
|||
funnelCookie.ReferralDate,
|
||||
funnelCookie.ReferralParentAccountNumber,
|
||||
funnelCookie.ReferralCorrelationId,
|
||||
isConceptInsurance
|
||||
isConceptInsurance,
|
||||
pageNameToLog
|
||||
)
|
||||
)?.data;
|
||||
}
|
||||
|
|
@ -52,17 +53,17 @@ export async function loadSessionIfPresent(isConceptInsurance) {
|
|||
This will also set Referral information in the store after saving, and then
|
||||
update the cookie. To force synchronous behavior pass in 'true' for shouldAwaitSaveSessionQueue
|
||||
*/
|
||||
export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
|
||||
export async function saveSession({ pageNameToLog, shouldAwaitSaveSessionQueue = false }) {
|
||||
var saveSessionPromise;
|
||||
if (store.getters.applicationUser.saveSessionPromise) {
|
||||
// queue newest request after current saveSessionPromise resolves
|
||||
saveSessionPromise = store.getters.applicationUser.saveSessionPromise.then(() => {
|
||||
// get a new saveSessionPromise
|
||||
return saveSessionHelper();
|
||||
return saveSessionHelper(pageNameToLog);
|
||||
});
|
||||
} else {
|
||||
// create an initial saveSessionPromise
|
||||
saveSessionPromise = saveSessionHelper();
|
||||
saveSessionPromise = saveSessionHelper(pageNameToLog);
|
||||
}
|
||||
store.commit(storeMutations.UPDATE_SAVE_SESSION_PROMISE, saveSessionPromise);
|
||||
// await here to allow for a caller to await and make the function synchronous
|
||||
|
|
@ -83,12 +84,13 @@ async function loadSession(
|
|||
referralDate,
|
||||
parentAccountNumber,
|
||||
referralCorrelationId,
|
||||
isConceptInsurance
|
||||
isConceptInsurance,
|
||||
pageNameToLog
|
||||
) {
|
||||
// await the saveSessionPromise in the store to make sure we're loading up to date information
|
||||
await store.getters.applicationUser.saveSessionPromise;
|
||||
|
||||
const response = await baseMixin.methods.dispatchStoreAction(
|
||||
const response = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.LOAD_SESSION,
|
||||
{
|
||||
savedSessionId: savedSessionId?.toString(),
|
||||
|
|
@ -98,6 +100,7 @@ async function loadSession(
|
|||
parentAccountNumber,
|
||||
isConceptInsurance,
|
||||
},
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -107,8 +110,12 @@ async function loadSession(
|
|||
/*
|
||||
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
|
||||
*/
|
||||
async function saveSessionHelper() {
|
||||
const savedSessionInfo = await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_SESSION);
|
||||
async function saveSessionHelper(pageNameToLog) {
|
||||
const savedSessionInfo = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.SAVE_SESSION,
|
||||
null,
|
||||
pageNameToLog
|
||||
);
|
||||
// Update the store with information received from the saveSession response
|
||||
await baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
|
|
|
|||
|
|
@ -170,11 +170,13 @@ describe("saveSession", () => {
|
|||
const mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
// Act
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.SAVE_SESSION
|
||||
expect(mocks.baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
storeActions.SAVE_SESSION,
|
||||
null,
|
||||
"test"
|
||||
);
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
|
|
@ -231,7 +233,7 @@ describe("saveSession", () => {
|
|||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
// Act
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.getFunnelCookie().DidHeritageFunnelUpdateLast).toEqual(false);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,16 @@ export function getMountOptions(mockData) {
|
|||
});
|
||||
}
|
||||
});
|
||||
mocks.dispatchStoreActionWithLogging = jest.fn();
|
||||
mocks.dispatchStoreActionWithLogging.mockImplementation((actionName) => {
|
||||
let actionFilterResult = mockData.actionList?.filter((x) => x.actionName == actionName);
|
||||
|
||||
if (actionFilterResult?.length === 1) {
|
||||
return Promise.resolve({
|
||||
data: actionFilterResult[0].data,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Mock const files
|
||||
mocks.storeActions = storeActions;
|
||||
|
|
@ -141,5 +151,16 @@ function setupBaseMixinDispatchStoreAction(mockData) {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
baseMixin.methods.dispatchStoreActionWithLogging = jest.fn();
|
||||
baseMixin.methods.dispatchStoreActionWithLogging.mockImplementation((actionName) => {
|
||||
let actionFilterResult = mockData.actionList.filter((x) => x.actionName == actionName);
|
||||
|
||||
if (actionFilterResult.length > 0 && actionFilterResult.length === 1) {
|
||||
return Promise.resolve({
|
||||
data: actionFilterResult[0].data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ describe("address-lookup.vue", () => {
|
|||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
expect(wrapper.vm.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
"lookupVinByAddress",
|
||||
{
|
||||
licenseLastName: undefined,
|
||||
|
|
@ -479,12 +479,17 @@ describe("address-lookup.vue", () => {
|
|||
licenseStreetAddress: "1234 Main St",
|
||||
licenseZip: "43215",
|
||||
},
|
||||
"address-lookup",
|
||||
false
|
||||
);
|
||||
|
||||
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith("validateZip", {
|
||||
zip: "43215",
|
||||
});
|
||||
expect(wrapper.vm.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
"validateZip",
|
||||
{
|
||||
zip: "43215",
|
||||
},
|
||||
"address-lookup"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -722,8 +727,8 @@ function setupMocks({
|
|||
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,75 +5,92 @@
|
|||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
autocomplete="off">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
ref="vehicleBanner"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
||||
<alert
|
||||
ref="alertVinNotFound"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertMatchedDifferentVehicle"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="mb-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertNonServiceableZip"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="mb-4"
|
||||
alertClass="alert-danger"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertVinLookupsByHomeAddressNotAllowed"
|
||||
v-if="displayVinLookupByHomeAddressNotAllowedAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinLookupsByHomeAddressNotAllowedWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="service-zip-field" v-if="showServiceZipField" aria-live="polite">
|
||||
<div class="row mt-4 mb-1">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
ref="serviceZip"
|
||||
customInputId="serviceZip"
|
||||
aria-haspopup=""
|
||||
mask="#####"
|
||||
validationRules="service-zip-required|service-zip-format" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
ref="vehicleBanner"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
||||
|
||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
||||
|
||||
<alert
|
||||
ref="alertVinNotFound"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertMatchedDifferentVehicle"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="mb-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertNonServiceableZip"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="mb-4"
|
||||
alertClass="alert-danger"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertVinLookupsByHomeAddressNotAllowed"
|
||||
v-if="displayVinLookupByHomeAddressNotAllowedAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinLookupsByHomeAddressNotAllowedWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<transition name="fade" mode="out-in">
|
||||
<div
|
||||
class="service-zip-field"
|
||||
v-if="showServiceZipField"
|
||||
aria-live="polite">
|
||||
<div class="row mt-4 mb-1">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
ref="serviceZip"
|
||||
customInputId="serviceZip"
|
||||
aria-haspopup=""
|
||||
mask="#####"
|
||||
validationRules="service-zip-required|service-zip-format" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
@back-clicked="backButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid" />
|
||||
</transition>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isDisabled="!meta.valid"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
@back-clicked="backButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -82,7 +99,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions";
|
||||
|
|
@ -207,7 +224,7 @@ export default {
|
|||
// Vehicle info change in the flow, use a variable to keep track and commit to state at the end.
|
||||
let vehicleInfoToCommit = {};
|
||||
|
||||
const vinLookupResponse = this.dispatchStoreAction(
|
||||
const vinLookupResponse = this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VIN_BY_ADDRESS,
|
||||
{
|
||||
licenseLastName: this.customerQuestions.lastName,
|
||||
|
|
@ -215,6 +232,7 @@ export default {
|
|||
licenseZip: this.customerQuestions.addressQuestions.zipCode,
|
||||
licenseState: this.customerQuestions.addressQuestions.state,
|
||||
},
|
||||
"address-lookup",
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -227,12 +245,20 @@ export default {
|
|||
{
|
||||
resultKey: "serviceZipValidationResponse",
|
||||
promise: this.serviceZipCode
|
||||
? this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
|
||||
zip: this.serviceZipCode,
|
||||
})
|
||||
: this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
|
||||
zip: this.customerQuestions.addressQuestions.zipCode,
|
||||
}),
|
||||
? this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{
|
||||
zip: this.serviceZipCode,
|
||||
},
|
||||
"address-lookup"
|
||||
)
|
||||
: this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{
|
||||
zip: this.customerQuestions.addressQuestions.zipCode,
|
||||
},
|
||||
"address-lookup"
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -241,14 +267,14 @@ export default {
|
|||
if (!resultMap.vinLookupResponse.isStatePermissible) {
|
||||
// State Restrictions forbid lookup by address
|
||||
this.displayVinLookupByHomeAddressNotAllowedAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If a Service Zip is entered and it is an invalid zip code (ex. 11111) then show an alert
|
||||
const isZipValid = resultMap.serviceZipValidationResponse.isValid;
|
||||
if (this.serviceZipCode && !isZipValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
|
|
@ -267,14 +293,15 @@ export default {
|
|||
this.displayMatchedDifferentVehicleAlert = true;
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
carFound.carId
|
||||
carFound.carId,
|
||||
"address-lookup"
|
||||
);
|
||||
|
||||
// Update button "Continue with..."
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${carFound.year} ${carFound.make} ${carFound.model}`
|
||||
);
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// update data if the zip or service zip is serviceable
|
||||
|
|
@ -294,7 +321,7 @@ export default {
|
|||
} else {
|
||||
// No VINS found.
|
||||
this.displayVinNotFoundAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the either the registration zip code or service zip code are not serviceable
|
||||
|
|
@ -302,7 +329,7 @@ export default {
|
|||
if (!this.isZipServiceable) {
|
||||
this.displayNonServiceableZipAlert = true;
|
||||
this.showServiceZipField = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the registration zip code is serviceable and nothing was entered for the service zip code
|
||||
|
|
@ -320,7 +347,7 @@ export default {
|
|||
Object.keys(vehicleInfoToCommit).length === 0
|
||||
? this.$store.getters.vehicle
|
||||
: vehicleInfoToCommit,
|
||||
registrationInfo: {
|
||||
customerInfo: {
|
||||
firstName: this.customerQuestions.firstName,
|
||||
lastName: this.customerQuestions.lastName,
|
||||
},
|
||||
|
|
@ -420,7 +447,7 @@ export default {
|
|||
customerQuestions: {
|
||||
handler(newValue) {
|
||||
// if they modify one of the lookup fields (address, city, state, zipCode, or lastName), then modify the button text back to "Get my personalized quote"
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
this.showServiceZipField = false;
|
||||
|
|
@ -445,7 +472,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
customerQuestions,
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ describe("addressVehicles.vue", () => {
|
|||
};
|
||||
|
||||
// the following has to be set BEFORE changing the data which is being watched, and requires updateButtonText to be mocked
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.lookupVin = jest.fn(() => Promise.resolve(lookupVinResponse));
|
||||
wrapper.vm.$router.navigate = jest.fn();
|
||||
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(() => {});
|
||||
|
|
@ -106,8 +106,8 @@ describe("addressVehicles.vue", () => {
|
|||
};
|
||||
|
||||
// the following has to be set BEFORE changing the data which is being watched, and requires updateButtonText to be mocked
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.lookupVin = jest.fn(() => Promise.reject(lookupVinResponse));
|
||||
wrapper.vm.$router.navigateWithSaving = jest.fn();
|
||||
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(() => {});
|
||||
|
|
@ -128,7 +128,7 @@ describe("addressVehicles.vue", () => {
|
|||
test("Should navigate to CLICKED_FORWARD scenario if carId is different and selected glass not available for vehicle on navigateForward", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$router.navigateWithSaving = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
@ -148,7 +148,7 @@ describe("addressVehicles.vue", () => {
|
|||
test("carId is not different on navigateForward (car was found) => Should handle navigating forward with car match", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
navigateToHeritage.navigateToHeritageFunnel = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
@ -224,7 +224,7 @@ function setupMocks({}) {
|
|||
|
||||
const wrapper = shallowMount(addressVehicles, mountOptions);
|
||||
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
|
||||
|
||||
return { wrapper };
|
||||
|
|
|
|||
|
|
@ -1,38 +1,51 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<alert
|
||||
ref="alertFoundMultipleVehicles"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="AlertFoundMultipleVehiclesHeader"
|
||||
manualCopy=""
|
||||
v-bind:isDismissible="false" />
|
||||
<addressVehiclesQuestion
|
||||
ref="addressVehiclesQuestion"
|
||||
cmsWidgetName="VehicleConfirmationQuestion"
|
||||
:vehicles="VehiclesForQuestions"
|
||||
validationRules="vehicle-required"
|
||||
v-model="selectedVehicleVin"
|
||||
:isCarIdDifferent="isCarIdDifferent" />
|
||||
<div class="alert-provide-vin" v-if="splitAlertProvideVinBodyForLink.length">
|
||||
<textBlock
|
||||
:customText="AlertProvideVinBody"
|
||||
justifyText="left"
|
||||
class="mb-3"
|
||||
marginTopSizeOverride="3" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<alert
|
||||
ref="alertFoundMultipleVehicles"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="AlertFoundMultipleVehiclesHeader"
|
||||
manualCopy=""
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<addressVehiclesQuestion
|
||||
ref="addressVehiclesQuestion"
|
||||
cmsWidgetName="VehicleConfirmationQuestion"
|
||||
:vehicles="VehiclesForQuestions"
|
||||
validationRules="vehicle-required"
|
||||
v-model="selectedVehicleVin"
|
||||
:isCarIdDifferent="isCarIdDifferent" />
|
||||
|
||||
<div class="alert-provide-vin" v-if="splitAlertProvideVinBodyForLink.length">
|
||||
<textBlock
|
||||
:customText="AlertProvideVinBody"
|
||||
justifyText="left"
|
||||
class="mb-3"
|
||||
marginTopSizeOverride="3" />
|
||||
</div>
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -40,7 +53,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
|
@ -162,10 +175,14 @@ export default {
|
|||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const vinLookup = await this.dispatchStoreAction(storeActions.LOOKUP_VEHICLE_BY_VIN, {
|
||||
vin: this.selectedVehicle.vin,
|
||||
}).catch(() => {
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
const vinLookup = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VEHICLE_BY_VIN,
|
||||
{
|
||||
vin: this.selectedVehicle.vin,
|
||||
},
|
||||
"address-vehicles"
|
||||
).catch(() => {
|
||||
this.$refs.navbar.removeLoader();
|
||||
});
|
||||
|
||||
if (!vinLookup) {
|
||||
|
|
@ -173,7 +190,8 @@ export default {
|
|||
}
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
vinLookup.data.carId
|
||||
vinLookup.data.carId,
|
||||
"address-vehicles"
|
||||
);
|
||||
|
||||
await this.dispatchStoreAction(
|
||||
|
|
@ -210,11 +228,11 @@ export default {
|
|||
this.isCarIdDifferent =
|
||||
this.selectedVehicle?.vehicle.carId !== store.getters.vehicle.carId;
|
||||
if (this.isCarIdDifferent) {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`
|
||||
);
|
||||
} else {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
}
|
||||
|
|
@ -229,7 +247,7 @@ export default {
|
|||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
alert,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
addressVehiclesQuestion,
|
||||
textBlock,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -237,6 +237,11 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -272,6 +277,11 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
|
||||
// Act
|
||||
|
|
@ -325,7 +335,12 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreActionWithLogging");
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -334,9 +349,10 @@ describe("capabilityQuestions.vue", () => {
|
|||
|
||||
//Assert
|
||||
expect(spy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
1,
|
||||
"getPartFromCapabilityQuestionAnswer",
|
||||
"Windshield",
|
||||
"capability-questions",
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -368,6 +384,11 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.navigateForward = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -162,9 +162,10 @@ export default {
|
|||
const partsOrQuestions = this.partsOrQuestionsData;
|
||||
for (let answer of questionAnswersArray) {
|
||||
const partFromCapabilityQuestionAnswer = (
|
||||
await this.dispatchStoreAction(
|
||||
await this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_PART_FROM_CAPABILITY_QUESTION_ANSWER,
|
||||
answer.glassLocation,
|
||||
"capability-questions",
|
||||
false
|
||||
)
|
||||
).data;
|
||||
|
|
|
|||
101
src/layouts/confirmation/confirmation.spec.js
Normal file
101
src/layouts/confirmation/confirmation.spec.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// Components
|
||||
import confirmation from "@/layouts/confirmation/confirmation.vue";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { nextTick } from "vue";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
store.getters = {
|
||||
order: {
|
||||
schedule: {
|
||||
date: "2019-01-01",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
routeCode: "000",
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [
|
||||
{
|
||||
partNumber: "ABC123",
|
||||
},
|
||||
],
|
||||
supportingItems: [],
|
||||
},
|
||||
serviceLocation: {
|
||||
address: "",
|
||||
address2: null,
|
||||
city: "",
|
||||
state: "AZ",
|
||||
appointmentType: "Inshop",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
provider: {
|
||||
providerNumber: "123",
|
||||
address: {
|
||||
streetAddress: "test1",
|
||||
city: "test",
|
||||
state: "AZ",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
},
|
||||
},
|
||||
},
|
||||
damage: {
|
||||
isRepair: false,
|
||||
},
|
||||
referralNumber: "1234567",
|
||||
},
|
||||
payment: {
|
||||
isInsurance: true,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
afterEach(() => {
|
||||
store.getters = {};
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("confirmation.vue", () => {
|
||||
test("arePagePrerequisitesValid should be true ", async () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks();
|
||||
|
||||
//Act
|
||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
await nextTick();
|
||||
|
||||
//Assert
|
||||
expect(arePagePrerequisitesValid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks() {
|
||||
const wrapper = shallowMount(
|
||||
confirmation,
|
||||
getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
87
src/layouts/confirmation/confirmation.vue
Normal file
87
src/layouts/confirmation/confirmation.vue
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="page-container-grouped-styles position-relative">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<div>
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
isBackButtonHidden="true"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:buttonSize="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
//Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import store from "@/store";
|
||||
export default {
|
||||
name: "confirmation",
|
||||
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);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Service Location
|
||||
const serviceLocation = store.getters.order.serviceLocation;
|
||||
const mobileReqs = !!(
|
||||
serviceLocation.address &&
|
||||
serviceLocation.city &&
|
||||
serviceLocation.state &&
|
||||
serviceLocation.zipCode
|
||||
);
|
||||
|
||||
const providerLocation = serviceLocation.provider.address;
|
||||
const dropOffInshopReqs = !!(
|
||||
providerLocation.streetAddress &&
|
||||
providerLocation.city &&
|
||||
providerLocation.state &&
|
||||
providerLocation.zipCode
|
||||
);
|
||||
|
||||
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||
|
||||
const serviceLocationReqs =
|
||||
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
||||
|
||||
// Schedule
|
||||
const schedule = store.getters.order.schedule;
|
||||
const scheduleReqs = !!(schedule.date && schedule.startTime); //removing schedule.endTime for testing
|
||||
|
||||
return serviceLocationReqs && scheduleReqs;
|
||||
},
|
||||
forwardButtonAction() {},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
navbar,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,57 +1,72 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="FirstNameWidget"
|
||||
v-model="firstName"
|
||||
ref="firstName"
|
||||
customInputId="firstName"
|
||||
validationRules="first-name-required" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="LastNameWidget"
|
||||
v-model="lastName"
|
||||
ref="lastName"
|
||||
customInputId="lastName"
|
||||
validationRules="last-name-required" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="emailQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
<phoneNumberQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="PhoneNumberQuestionWidget"
|
||||
v-model="phoneNumber"
|
||||
isRequired
|
||||
validationRules="phone-number-required" />
|
||||
<checkboxQuestion
|
||||
class="mb-5"
|
||||
cmsWidgetName="TextMeQuestionWidget"
|
||||
v-model="isSmsOptIn" />
|
||||
<textareaQuestion
|
||||
class="mb-4"
|
||||
v-model="techNotes"
|
||||
cmsWidgetName="TextAreaContentWidget"
|
||||
maxLength="250" />
|
||||
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="FirstNameWidget"
|
||||
v-model="firstName"
|
||||
ref="firstName"
|
||||
customInputId="firstName"
|
||||
validationRules="first-name-required" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="LastNameWidget"
|
||||
v-model="lastName"
|
||||
ref="lastName"
|
||||
customInputId="lastName"
|
||||
validationRules="last-name-required" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="emailQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
|
||||
<phoneNumberQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="PhoneNumberQuestionWidget"
|
||||
v-model="phoneNumber"
|
||||
isRequired
|
||||
validationRules="phone-number-required" />
|
||||
|
||||
<checkboxQuestion
|
||||
class="mb-5"
|
||||
cmsWidgetName="TextMeQuestionWidget"
|
||||
v-model="isSmsOptIn" />
|
||||
|
||||
<textareaQuestion
|
||||
class="mb-4"
|
||||
v-model="techNotes"
|
||||
cmsWidgetName="TextAreaContentWidget"
|
||||
maxLength="250" />
|
||||
|
||||
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import textareaQuestion from "@/digital-components/textarea-question/textarea-question";
|
||||
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
||||
|
|
@ -172,7 +187,7 @@ export default {
|
|||
funnelSubHeader,
|
||||
textareaQuestion,
|
||||
textboxQuestion,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
Form,
|
||||
textBlock,
|
||||
phoneNumberQuestion,
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ function setupMocks({
|
|||
{ Name: "Provide my license plate # Most accurate VIN match" },
|
||||
{ Name: "Provide my home address Most convenient VIN match" },
|
||||
],
|
||||
funnelFooterWidget = { ForwardButtonText: "test txt" },
|
||||
FunnelFooterWidget = { ForwardButtonText: "test txt" },
|
||||
mountOptionsMockData = {
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
|
|
@ -271,7 +271,7 @@ function setupMocks({
|
|||
groupName: groupName,
|
||||
QuestionText: cmsQuestionText,
|
||||
Answers: cmsAnswers,
|
||||
FunnelFooterWidget: funnelFooterWidget,
|
||||
FunnelFooterWidget: FunnelFooterWidget,
|
||||
};
|
||||
|
||||
const apiPromise = Promise.resolve({ cmsContent });
|
||||
|
|
|
|||
|
|
@ -1,79 +1,84 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" />
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div v-if="!skipVin">
|
||||
<div class="vinlookupquestion" v-html="this.VinLookupQuestionText"></div>
|
||||
<buttonQuestion
|
||||
cmsWidgetName="VinLookupMethod"
|
||||
:answers="answersFromCms"
|
||||
groupName="vinLookupMethodOption"
|
||||
buttonTypeString="listButton"
|
||||
v-model="selectedVinLookupMethod"
|
||||
isRequired
|
||||
validationRules="option-required"
|
||||
:logDisplayedValuesEvent="true" />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<alert
|
||||
class="my-4"
|
||||
:cmsWidgetName="alertInfo"
|
||||
alertClass="alert-info"
|
||||
v-bind:isDismissible="false" />
|
||||
<div class="row my-2">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" />
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div v-if="!skipVin">
|
||||
<div class="vinlookupquestion" v-html="this.VinLookupQuestionText"></div>
|
||||
<buttonQuestion
|
||||
cmsWidgetName="VinLookupMethod"
|
||||
:answers="answersFromCms"
|
||||
groupName="vinLookupMethodOption"
|
||||
buttonTypeString="listButton"
|
||||
v-model="selectedVinLookupMethod"
|
||||
isRequired
|
||||
validationRules="option-required"
|
||||
:logDisplayedValuesEvent="true" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="emailAddress"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<alert
|
||||
class="my-4"
|
||||
:cmsWidgetName="alertInfo"
|
||||
alertClass="alert-info"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="emailAddress"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<textBlock
|
||||
cmsWidgetName="QuoteEmailTextBlockWidget"
|
||||
typeStyle="caption" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
</div>
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -82,7 +87,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
|
|
@ -157,9 +162,10 @@ export default {
|
|||
|
||||
var vinByAddressPromise;
|
||||
if (zip) {
|
||||
vinByAddressPromise = baseMixin.methods.dispatchStoreAction(
|
||||
vinByAddressPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.IS_VIN_BY_ADDRESS_PERMISSIBLE,
|
||||
zip,
|
||||
{ zip: zip },
|
||||
"estimate",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
@ -238,13 +244,13 @@ export default {
|
|||
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
if (!zipCodeData.isServiceable) {
|
||||
this.displayNonServiceableZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
|
||||
|
|
@ -252,12 +258,18 @@ export default {
|
|||
const vehicleChangedDuringPolicyLookupInHeritage =
|
||||
payment.isInsurance && payment.insuranceCoverage.coverageStatus;
|
||||
if (vehicleChangedDuringPolicyLookupInHeritage) {
|
||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: "estimate",
|
||||
loadingModal: this.$refs.loadingModal,
|
||||
});
|
||||
} else if (this.$store.getters.order.referralNumber?.length === 6) {
|
||||
await this.navigateForwardWithSingleCarMatch();
|
||||
} else if (this.isRepair) {
|
||||
const supportingItemsPromise = await this.dispatchStoreAction(
|
||||
storeActions.GET_SUPPORTING_ITEMS
|
||||
const supportingItemsPromise = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
null,
|
||||
"estimate"
|
||||
);
|
||||
|
||||
const promiseResultMap = [
|
||||
|
|
@ -276,7 +288,7 @@ export default {
|
|||
);
|
||||
|
||||
// call saveSession here - navigateWithSaving saves too late in the flow
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: "estimate" });
|
||||
return this.$router.navigateWithSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
|
||||
this.$route
|
||||
|
|
@ -343,7 +355,7 @@ export default {
|
|||
funnelHeader,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
buttonQuestion,
|
||||
Form,
|
||||
alert,
|
||||
|
|
@ -358,6 +370,10 @@ export default {
|
|||
.vinlookupquestion p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.vinlookupquestion strong {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
}
|
||||
.vinlookupquestion p:nth-child(2) {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 1rem;
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
|
||||
wrapper.vm.navigateForward = jest.fn();
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -157,7 +157,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
});
|
||||
|
||||
// Mock store action call
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -201,7 +201,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
wrapper.vm.navigateForward = jest.fn();
|
||||
|
||||
// Mock store action call
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -242,7 +242,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
|
||||
return "";
|
||||
});
|
||||
wrapper.vm.dispatchStoreAction = jest.fn();
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn();
|
||||
|
||||
await wrapper.vm.navigateForward();
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Button Text should revert to initial value when registrationZip textfield has new text", async () => {
|
||||
|
|
@ -330,7 +330,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Button Text should revert to initial value when serviceZip textfield has new text", async () => {
|
||||
|
|
@ -345,7 +345,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -379,7 +379,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
return { data: { vehicle: { carId: "C00000" } } };
|
||||
});
|
||||
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -417,7 +417,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.setData({ registrationZip: "00000" });
|
||||
navigateToHeritage.navigateToHeritageFunnel = jest.fn();
|
||||
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -449,7 +449,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.setData({ registrationZipCode: "00000" });
|
||||
navigateToHeritage.navigateToHeritageFunnel = jest.fn();
|
||||
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -703,8 +703,8 @@ function setupMocks({
|
|||
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,88 +1,89 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div class="row mt-2 mb-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="LicensePlateNumberQuestionWidget"
|
||||
v-model="licensePlate"
|
||||
isRequired
|
||||
customInputId="licensePlate"
|
||||
validationRules="license-plate-required" />
|
||||
</div>
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
<div class="row mt-0 mb-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="RegistrationZipQuestionWidget"
|
||||
v-model="registrationZipCode"
|
||||
customInputId="zip"
|
||||
mask="#####"
|
||||
validationRules="registration-zip-required|zip-format" />
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="LicensePlateNumberQuestionWidget"
|
||||
v-model="licensePlate"
|
||||
isRequired
|
||||
customInputId="licensePlate"
|
||||
validationRules="license-plate-required" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="RegistrationZipQuestionWidget"
|
||||
v-model="registrationZipCode"
|
||||
customInputId="zip"
|
||||
mask="#####"
|
||||
validationRules="registration-zip-required|zip-format" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="email"
|
||||
customInputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
<alert
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<div class="row mt-2" v-if="showServiceZipField">
|
||||
<div class="col mb-1">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
validationRules="zip-required|zip-format"
|
||||
mask="#####" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<alert
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<navbar
|
||||
ref="navbar"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row mt-0">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="email"
|
||||
customInputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<div class="row mt-2" v-if="showServiceZipField">
|
||||
<div class="col mb-1">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
validationRules="zip-required|zip-format"
|
||||
mask="#####" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
<funnelFooter
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -91,7 +92,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
|
@ -199,9 +200,10 @@ export default {
|
|||
async forwardButtonAction() {
|
||||
this.resetWarningsAndErrors();
|
||||
|
||||
const registrationZipValidationResponse = this.dispatchStoreAction(
|
||||
const registrationZipValidationResponse = this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{ zip: this.registrationZipCode }
|
||||
{ zip: this.registrationZipCode },
|
||||
"license-plate-lookup"
|
||||
);
|
||||
|
||||
// Settle promises and get results
|
||||
|
|
@ -214,9 +216,13 @@ export default {
|
|||
// If serviceZipCode is not set, then sets the response to the registrationZipValidationResponse. Calls VALIDATE_ZIP if the serviceZipCode is set.
|
||||
resultKey: "serviceZipValidationResponse",
|
||||
promise: this.serviceZipCode
|
||||
? this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
|
||||
zip: this.serviceZipCode,
|
||||
})
|
||||
? this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{
|
||||
zip: this.serviceZipCode,
|
||||
},
|
||||
"license-plate-lookup"
|
||||
)
|
||||
: registrationZipValidationResponse,
|
||||
},
|
||||
];
|
||||
|
|
@ -230,7 +236,7 @@ export default {
|
|||
).catch(() => {
|
||||
// No VIN found.
|
||||
this.displayVinNotFoundAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
});
|
||||
// If no VIN was found, stop processing after displaying alert
|
||||
if (!vinLookup) {
|
||||
|
|
@ -241,7 +247,7 @@ export default {
|
|||
const isZipValid = resultMap.serviceZipValidationResponse.isValid;
|
||||
if (this.serviceZipCode && !isZipValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
|
|
@ -260,14 +266,15 @@ export default {
|
|||
this.displayMatchedDifferentVehicleAlert = true;
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
vinLookup.data.vehicle.carId
|
||||
vinLookup.data.vehicle.carId,
|
||||
"license-plate-lookup"
|
||||
);
|
||||
|
||||
// Update button "Continue with..."
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${vinLookup.data.vehicle.year} ${vinLookup.data.vehicle.make} ${vinLookup.data.vehicle.model}`
|
||||
);
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the either the registration zip code or service zip code are not serviceable
|
||||
|
|
@ -275,7 +282,7 @@ export default {
|
|||
if (!this.isZipServiceable) {
|
||||
this.displayNonServiceableZipAlert = true;
|
||||
this.showServiceZipField = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the registration zip code is serviceable and nothing was entered for the service zip code
|
||||
|
|
@ -312,9 +319,10 @@ export default {
|
|||
return await this.navigateForward();
|
||||
},
|
||||
lookupVin(plate, state) {
|
||||
return this.dispatchStoreAction(
|
||||
return this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VIN_BY_PLATE,
|
||||
{ licensePlate: plate, licenseState: state },
|
||||
"license-plate-lookup",
|
||||
false
|
||||
);
|
||||
},
|
||||
|
|
@ -367,26 +375,26 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
licensePlate() {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
registrationZipCode() {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
serviceZipCode() {
|
||||
// If they modify the service zip code, then hide the error message.
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
textboxQuestion,
|
||||
|
|
|
|||
|
|
@ -248,6 +248,13 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -281,6 +288,13 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
|
||||
// Act
|
||||
|
|
@ -328,7 +342,14 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreActionWithLogging");
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -336,7 +357,7 @@ describe("partQuestions.vue...", () => {
|
|||
await nextTick();
|
||||
|
||||
//Assert
|
||||
expect(spy).toHaveBeenNthCalledWith(2, "getParts");
|
||||
expect(spy).toHaveBeenNthCalledWith(1, "getParts", null, "part-questions");
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
|
@ -362,6 +383,13 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.navigateForward = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -145,7 +145,11 @@ export default {
|
|||
);
|
||||
|
||||
// call API parts method
|
||||
const partsLookup = await this.dispatchStoreAction(this.storeActions.GET_PARTS);
|
||||
const partsLookup = await this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_PARTS,
|
||||
null,
|
||||
"part-questions"
|
||||
);
|
||||
|
||||
const glassPartsForStore = partsLookup.data.glassPieceParts;
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
@edit-clicked="editServiceLocation" />
|
||||
</div>
|
||||
|
||||
<funnel-footer
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
|
|
@ -209,56 +209,9 @@ export default {
|
|||
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||
this.selectedVaps = vapsItemsSelected;
|
||||
},
|
||||
backButtonAction() {
|
||||
vehicleQuestionsMixin.methods.navigateBack(this);
|
||||
},
|
||||
backButtonAction() {},
|
||||
forwardButtonAction() {
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_PAYMENT_TYPE,
|
||||
this.isInsuranceSelected,
|
||||
false
|
||||
);
|
||||
|
||||
if (!this.isInsuranceSelected) {
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER,
|
||||
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.$store.getters.order.payment.parentAccountNumber !=
|
||||
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER
|
||||
) {
|
||||
this.supportingItems = this.filterOutFees(this.supportingItems);
|
||||
}
|
||||
|
||||
if (this.pricedGlassParts.length > 0) {
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_GLASS_PART_PRICES,
|
||||
this.pricedGlassParts,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS,
|
||||
this.supportingItems,
|
||||
false
|
||||
);
|
||||
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
|
||||
|
||||
const payment = this.$store.getters.payment;
|
||||
if (payment.isInsurance) {
|
||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||
} else {
|
||||
this.$router.navigateWithSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD_WITH_CASH,
|
||||
this.$route
|
||||
);
|
||||
}
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -286,7 +239,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ jest.mock("@/mixins/base-mixin", () => ({
|
|||
if (action === mockPriceOrderStoreAction) return items;
|
||||
else return mockStoreActionResults[action];
|
||||
},
|
||||
dispatchStoreActionWithLogging(action, items, pageName, encode) {
|
||||
if (action === mockPriceOrderStoreAction) return items;
|
||||
else return mockStoreActionResults[action];
|
||||
},
|
||||
getTierOnePackagePrice() {
|
||||
return mockTierOnePrice;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,45 +1,56 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<cashOrInsuranceQuestion
|
||||
ref="cashOrInsurance"
|
||||
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
||||
v-model="isInsuranceSelected"
|
||||
groupName="CashOrInsuranceQuestion" />
|
||||
<servicePackageQuestion
|
||||
ref="servicePackage"
|
||||
cashCmsWidgetName="CashServicePackageQuestionWidget"
|
||||
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
|
||||
groupName="ServicePackageQuestion"
|
||||
:availableLineItems="availableLineItems"
|
||||
:isInsuranceSelected="isInsuranceSelected"
|
||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
||||
validationRules="option-required"
|
||||
isRequired />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<textBlock
|
||||
cmsWidgetName="quoteDisclaimer"
|
||||
justifyText="left"
|
||||
typeStyle="caption"
|
||||
class="mt-4" />
|
||||
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
|
||||
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
|
||||
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" />
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<cashOrInsuranceQuestion
|
||||
ref="cashOrInsurance"
|
||||
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
||||
v-model="isInsuranceSelected"
|
||||
groupName="CashOrInsuranceQuestion" />
|
||||
|
||||
<servicePackageQuestion
|
||||
ref="servicePackage"
|
||||
cashCmsWidgetName="CashServicePackageQuestionWidget"
|
||||
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
|
||||
groupName="ServicePackageQuestion"
|
||||
:availableLineItems="availableLineItems"
|
||||
:isInsuranceSelected="isInsuranceSelected"
|
||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
||||
validationRules="option-required"
|
||||
isRequired />
|
||||
|
||||
<textBlock
|
||||
cmsWidgetName="quoteDisclaimer"
|
||||
justifyText="left"
|
||||
typeStyle="caption"
|
||||
class="mt-4" />
|
||||
|
||||
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
|
||||
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
|
||||
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" />
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -48,7 +59,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import cashOrInsuranceQuestion from "./cash-or-insurance-question/cash-or-insurance-question";
|
||||
|
|
@ -75,12 +86,20 @@ export default {
|
|||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS);
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_RAIN_DEFENSE
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_WIPERS,
|
||||
null,
|
||||
"quote"
|
||||
);
|
||||
const supportingItemsPromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_SUPPORTING_ITEMS
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_RAIN_DEFENSE,
|
||||
null,
|
||||
"quote"
|
||||
);
|
||||
const supportingItemsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
null,
|
||||
"quote"
|
||||
);
|
||||
|
||||
const promiseResultMap = [
|
||||
|
|
@ -112,12 +131,19 @@ export default {
|
|||
...resultMap.wipers,
|
||||
...clonedGlassParts,
|
||||
];
|
||||
console.log("Available line items:", availableLineItems);
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreAction(
|
||||
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
availableLineItems: availableLineItems,
|
||||
},
|
||||
"quote",
|
||||
false
|
||||
);
|
||||
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_SUPPORTING_ITEMS,
|
||||
resultMap.supportingItems,
|
||||
false
|
||||
);
|
||||
console.log("Pricing results:", pricingResults);
|
||||
|
|
@ -213,17 +239,15 @@ export default {
|
|||
);
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS,
|
||||
this.supportingItems,
|
||||
false
|
||||
);
|
||||
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
|
||||
|
||||
const payment = this.$store.getters.payment;
|
||||
if (payment.isInsurance) {
|
||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: "quote",
|
||||
loadingModal: this.$refs.loadingModal,
|
||||
});
|
||||
} else {
|
||||
this.$router.navigateWithSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD_WITH_CASH,
|
||||
|
|
@ -234,7 +258,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
|
|
|
|||
|
|
@ -31,9 +31,15 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
loadInitialData() {
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS);
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_RAIN_DEFENSE
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_WIPERS,
|
||||
null,
|
||||
"review"
|
||||
);
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_RAIN_DEFENSE,
|
||||
null,
|
||||
"review"
|
||||
);
|
||||
|
||||
const promiseResultMap = [
|
||||
|
|
|
|||
|
|
@ -1,105 +1,113 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<!-- When customer-details is added: v-slot="{ meta }" -->
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<textBlock
|
||||
:customText="subHeaderTitle"
|
||||
typeStyle="h5"
|
||||
justifyText="center"
|
||||
margin="mt-1"
|
||||
class="dark-header" />
|
||||
<textBlock
|
||||
:customText="subHeaderBody"
|
||||
typeStyle="body"
|
||||
justifyText="left"
|
||||
margin="mt-0 mb-2" />
|
||||
|
||||
<buttonMain
|
||||
ref="buttonMain"
|
||||
isPrimary
|
||||
:buttonText="forwardButtonText"
|
||||
loaderColor="white"
|
||||
class="mb-2"
|
||||
@click-event="forwardButtonAction" />
|
||||
|
||||
<div>
|
||||
<hr />
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<textBlock
|
||||
customText="Appointment Details"
|
||||
typeStyle="label bold"
|
||||
justifyText="justify-text-left"
|
||||
margin="mt-0"
|
||||
class="dark-header" />
|
||||
<textBlock
|
||||
:customText="subHeaderTitle"
|
||||
typeStyle="h5"
|
||||
justifyText="center"
|
||||
margin="mt-1"
|
||||
class="dark-header" />
|
||||
<textBlock
|
||||
:customText="subHeaderBody"
|
||||
typeStyle="body"
|
||||
justifyText="left"
|
||||
margin="mt-0 mb-2" />
|
||||
|
||||
<div class="px-4">
|
||||
<vehicleReview
|
||||
cmsWidgetName="VehicleReviewWidget"
|
||||
:vehicle="vehicleInfo"
|
||||
@edit-clicked="editVehicle" />
|
||||
<buttonMain
|
||||
ref="buttonMain"
|
||||
isPrimary
|
||||
:buttonText="forwardButtonText"
|
||||
loaderColor="white"
|
||||
class="mb-2"
|
||||
@click-event="forwardButtonAction" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<div>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<damageReview
|
||||
cmsWidgetName="DamageReviewWidget"
|
||||
damageLocationsWidgetName="DamageLocationsWidget"
|
||||
:damage="damageInfo"
|
||||
@edit-clicked="editDamage" />
|
||||
<textBlock
|
||||
customText="Appointment Details"
|
||||
typeStyle="label bold"
|
||||
justifyText="justify-text-left"
|
||||
margin="mt-0"
|
||||
class="dark-header" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<div class="px-4">
|
||||
<vehicleReview
|
||||
cmsWidgetName="VehicleReviewWidget"
|
||||
:vehicle="vehicleInfo"
|
||||
@edit-clicked="editVehicle" />
|
||||
|
||||
<servicePackageReview
|
||||
ref="servicePackageReview"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
defaultPackageItemsCmsName="DefaultPackageItemDescriptions"
|
||||
vapsItemsCmsName="VapsItemDescriptions"
|
||||
:damage="damageInfo"
|
||||
:lineItems="lineItems"
|
||||
@edit-clicked="editServicePackage" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<damageReview
|
||||
cmsWidgetName="DamageReviewWidget"
|
||||
damageLocationsWidgetName="DamageLocationsWidget"
|
||||
:damage="damageInfo"
|
||||
@edit-clicked="editDamage" />
|
||||
|
||||
<serviceLocationReview
|
||||
cmsWidgetName="ServiceLocationTitleWidget"
|
||||
:serviceLocation="serviceLocationInfo"
|
||||
@edit-clicked="editServiceLocation" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<servicePackageReview
|
||||
ref="servicePackageReview"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
defaultPackageItemsCmsName="DefaultPackageItemDescriptions"
|
||||
vapsItemsCmsName="VapsItemDescriptions"
|
||||
:damage="damageInfo"
|
||||
:lineItems="lineItems"
|
||||
@edit-clicked="editServicePackage" />
|
||||
|
||||
<scheduleReview
|
||||
cmsWidgetName="ScheduleWidget"
|
||||
:appointmentType="appointmentType"
|
||||
@edit-clicked="editSchedule" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<serviceLocationReview
|
||||
cmsWidgetName="ServiceLocationTitleWidget"
|
||||
:serviceLocation="serviceLocationInfo"
|
||||
@edit-clicked="editServiceLocation" />
|
||||
|
||||
<customerReview
|
||||
cmsWidgetName="CustomerReviewWidget"
|
||||
:customer="customerInfo"
|
||||
@edit-clicked="editCustomerDetails" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<scheduleReview
|
||||
cmsWidgetName="ScheduleWidget"
|
||||
:appointmentType="appointmentType"
|
||||
@edit-clicked="editSchedule" />
|
||||
|
||||
<hr class="my-0" />
|
||||
|
||||
<customerReview
|
||||
cmsWidgetName="CustomerReviewWidget"
|
||||
:customer="customerInfo"
|
||||
@edit-clicked="editCustomerDetails" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<hr class="my-0" />
|
||||
</div>
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<hr class="my-0" />
|
||||
</div>
|
||||
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
|
@ -292,7 +300,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
buttonMain,
|
||||
textBlock,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import { storeActions } from "@/constants/store-actions";
|
|||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
export async function getAlertReasons(ctu) {
|
||||
const alertReasons = await baseMixin.methods.dispatchStoreAction(
|
||||
const alertReasons = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_ALERT_REASONS_BY_CTU,
|
||||
{
|
||||
ctu: ctu,
|
||||
}
|
||||
},
|
||||
"schedule"
|
||||
);
|
||||
|
||||
return Promise.resolve(alertReasons);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,65 @@ jest.mock("@/mixins/base-mixin.js", () => ({
|
|||
]);
|
||||
}
|
||||
}),
|
||||
dispatchStoreActionWithLogging: jest.fn().mockImplementation((storeAction) => {
|
||||
if (storeAction === "getShopTimeSlots") {
|
||||
return {
|
||||
data: {
|
||||
estimatedServiceMinutesMinimum: 90,
|
||||
estimatedServiceMinutesMaximum: 120,
|
||||
days: [
|
||||
{
|
||||
date: "2023-12-01",
|
||||
timeSlots: [
|
||||
{
|
||||
id: "06747-01820-S-B*20424*7 AM",
|
||||
startTime: "07:00",
|
||||
endTime: "08:00",
|
||||
offerPremium: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
if (storeAction === "getMobilePremiumFee") {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
partNumber: "EARLY BIRD",
|
||||
description: null,
|
||||
partType: "EARLY BIRD",
|
||||
laborAmount: 0,
|
||||
sellingPrice: 14.99,
|
||||
kitPrice: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (storeAction === "priceOrderItemsAndSaveServerData") {
|
||||
return Promise.resolve([
|
||||
{
|
||||
partNumber: "EARLY BIRD",
|
||||
description: null,
|
||||
partType: "EARLY BIRD",
|
||||
laborAmount: 0,
|
||||
sellingPrice: 14.99,
|
||||
kitPrice: 0,
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (storeAction === "saveSupportingItemsSuppressingStateResetting") {
|
||||
return Promise.resolve([
|
||||
{
|
||||
partNumber: "EARLY BIRD",
|
||||
description: null,
|
||||
partType: "EARLY BIRD",
|
||||
laborAmount: 0,
|
||||
sellingPrice: 14.99,
|
||||
kitPrice: 0,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -226,10 +285,11 @@ describe("schedule.vue...", () => {
|
|||
);
|
||||
|
||||
// Assert
|
||||
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledTimes(3);
|
||||
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledTimes(3);
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
"getShopTimeSlots",
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
|
@ -623,7 +683,7 @@ function setupMocks({ customMountOptions }) {
|
|||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.$refs.datePicker.initializeComponent = jest.fn();
|
||||
wrapper.vm.$refs.locationAlerts.initializeComponent = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.timeSlotModalQuestion.openModal = jest.fn();
|
||||
|
||||
return { wrapper };
|
||||
|
|
|
|||
|
|
@ -1,50 +1,62 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<template v-if="ChangeShopLink.length">
|
||||
<textBlock
|
||||
cmsWidgetName="ChangeShopLink"
|
||||
justifyText="center"
|
||||
class="mb-3 text-link-small"
|
||||
marginTopSizeOverride="1" />
|
||||
</template>
|
||||
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
|
||||
<datePicker
|
||||
customComponentId="dateQuestion"
|
||||
selectableDatesSetting="custom"
|
||||
ref="datePicker"
|
||||
v-model="selectedDate"
|
||||
class="text-link-small"
|
||||
:customSelectableDatesCallback="getAvailableDatesMethod"
|
||||
validationRules="date-required"
|
||||
@date-clicked="openInshopTimeSlotsModal" />
|
||||
<timeSlotModalQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
customComponentId="timeSlotModalQuestion"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
cmsWidgetName="TimeSlotModalQuestion"
|
||||
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
|
||||
mobileCmsWidgetName="MobileTimeSlotModal"
|
||||
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
||||
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
||||
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
|
||||
:selectedDate="selectedDate"
|
||||
:appointmentType="appointmentType"
|
||||
:premiumAppointmentFee="mobilePremiumAppointmentFee"
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum"
|
||||
:estimatedServiceMinutesMaximum="selectableDatesData.estimatedServiceMinutesMaximum"
|
||||
@time-slot-modal-closed="timeSlotModalClosed"
|
||||
validationRules="time-slot-selection-required" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<template v-if="ChangeShopLink.length">
|
||||
<textBlock
|
||||
cmsWidgetName="ChangeShopLink"
|
||||
justifyText="center"
|
||||
class="mb-3 text-link-small"
|
||||
marginTopSizeOverride="1" />
|
||||
</template>
|
||||
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
|
||||
<datePicker
|
||||
customComponentId="dateQuestion"
|
||||
selectableDatesSetting="custom"
|
||||
ref="datePicker"
|
||||
v-model="selectedDate"
|
||||
class="text-link-small"
|
||||
:customSelectableDatesCallback="getAvailableDatesMethod"
|
||||
validationRules="date-required"
|
||||
@date-clicked="openInshopTimeSlotsModal" />
|
||||
<timeSlotModalQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
customComponentId="timeSlotModalQuestion"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
cmsWidgetName="TimeSlotModalQuestion"
|
||||
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
|
||||
mobileCmsWidgetName="MobileTimeSlotModal"
|
||||
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
||||
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
||||
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
|
||||
:selectedDate="selectedDate"
|
||||
:appointmentType="appointmentType"
|
||||
:premiumAppointmentFee="mobilePremiumAppointmentFee"
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:estimatedServiceMinutesMinimum="
|
||||
selectableDatesData.estimatedServiceMinutesMinimum
|
||||
"
|
||||
:estimatedServiceMinutesMaximum="
|
||||
selectableDatesData.estimatedServiceMinutesMaximum
|
||||
"
|
||||
@time-slot-modal-closed="timeSlotModalClosed"
|
||||
validationRules="time-slot-selection-required" />
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -52,7 +64,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
|
|
@ -156,9 +168,10 @@ const getAvailableDates = async (
|
|||
const makeParallelCalls = async () => {
|
||||
await Promise.all(
|
||||
storeActionConfigs.map(async (storeAction) => {
|
||||
const timeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
||||
const timeSlotsResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeAction.storeAction,
|
||||
storeAction.payload,
|
||||
"schedule",
|
||||
false
|
||||
);
|
||||
timeSlotsResponsesData.estimatedServiceMinutesMinimum =
|
||||
|
|
@ -206,17 +219,20 @@ export default {
|
|||
preSelectedDate: preSelectedDate,
|
||||
});
|
||||
|
||||
const premiumFeePromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_MOBILE_PREMIUM_FEE
|
||||
const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_MOBILE_PREMIUM_FEE,
|
||||
null,
|
||||
"schedule"
|
||||
);
|
||||
|
||||
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
|
||||
if (result.data) {
|
||||
return baseMixin.methods.dispatchStoreAction(
|
||||
return baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
availableLineItems: [result.data],
|
||||
},
|
||||
"schedule",
|
||||
false
|
||||
);
|
||||
} else {
|
||||
|
|
@ -352,23 +368,23 @@ export default {
|
|||
}
|
||||
},
|
||||
updateFooterButtonText(timeSlotInfo) {
|
||||
let funnelFooterButtonText;
|
||||
let navbarButtonText;
|
||||
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
|
||||
funnelFooterButtonText = "Continue";
|
||||
navbarButtonText = "Continue";
|
||||
} else {
|
||||
funnelFooterButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
|
||||
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
|
||||
timeSlotInfo.timeSlot.date
|
||||
)}`;
|
||||
|
||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.startTime
|
||||
)}`;
|
||||
} else if (
|
||||
this.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||
!timeSlotInfo.isPremiumAppointment
|
||||
) {
|
||||
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.startTime,
|
||||
true
|
||||
)} - ${this.getDisplayTextForMilitaryTime(
|
||||
|
|
@ -377,7 +393,7 @@ export default {
|
|||
)}`;
|
||||
}
|
||||
}
|
||||
this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText);
|
||||
this.$refs.navbar.updateButtonText(navbarButtonText);
|
||||
},
|
||||
convertSelectedDateToShortMonthAndDay(selectedDate) {
|
||||
// This conversion ensures we don't get get GMT induced date changes
|
||||
|
|
@ -483,7 +499,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
|
|
|
|||
|
|
@ -516,9 +516,6 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
.time-slots-modal.modal.modal-component {
|
||||
> .modal-dialog > .modal-content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.modal-header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0 !important;
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ export default {
|
|||
<style lang="scss">
|
||||
.appointment-type-question {
|
||||
.list-card img {
|
||||
height: auto;
|
||||
width: 3.417rem;
|
||||
height: 1.5rem;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,42 +2,45 @@ import { storeActions } from "@/constants/store-actions";
|
|||
import store from "@/store";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
export async function getPricedMobileFeePart(serviceZipCode) {
|
||||
export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
|
||||
if (!serviceZipCode) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode);
|
||||
const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode, pageNameToLog);
|
||||
|
||||
// Get the Mobile Fee Part
|
||||
const mobileFeePart = await baseMixin.methods.dispatchStoreAction(
|
||||
const mobileFeePart = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_MOBILE_FEE_PART,
|
||||
null,
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
// Get the Mobile Fee Part Price
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreAction(
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
availableLineItems: [mobileFeePart.data],
|
||||
serviceZipCode: serviceZipCode,
|
||||
serviceZipCodeCtu: zipCodeData.zipCodeCtu,
|
||||
},
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
return Promise.resolve(pricingResults[0]);
|
||||
}
|
||||
|
||||
export async function getServiceabilityDetails(serviceZipCode, lineItems) {
|
||||
export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) {
|
||||
// Get the Mobile Fee Part
|
||||
const serviceabilityDetails = await baseMixin.methods.dispatchStoreAction(
|
||||
const serviceabilityDetails = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SERVICEABILITY_DETAILS,
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
lineItems: lineItems,
|
||||
},
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -51,7 +54,7 @@ export async function getAvailabilityRating(
|
|||
providerNumber
|
||||
) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await baseMixin.methods.dispatchStoreAction(
|
||||
const shopTimeSlots = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
providerNumber: providerNumber,
|
||||
|
|
@ -59,6 +62,7 @@ export async function getAvailabilityRating(
|
|||
endDate: endDate,
|
||||
shopAppointmentType: shopAppointmentType,
|
||||
},
|
||||
"service-location",
|
||||
false
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,47 @@ jest.mock("@/mixins/base-mixin.js", () => ({
|
|||
});
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionGetShopTimeSlots) {
|
||||
if (request.providerNumber == "0000001") {
|
||||
return Promise.resolve(mockGetShopTimeSlotsGoodAvailability);
|
||||
}
|
||||
|
||||
return Promise.resolve(mockGetShopTimeSlotsLowAvailability);
|
||||
}
|
||||
}),
|
||||
dispatchStoreActionWithLogging: jest.fn().mockImplementation((actionName, request) => {
|
||||
if (actionName === mockStoreActionGetMobileFeePart) {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
partNumber: "MOBILE FEE",
|
||||
description: "MOBILE FEE",
|
||||
partType: "FEE",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionPriceOrderItemsAndSaveServerData) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
partNumber: "MOBILE FEE",
|
||||
description: "MOBILE FEE",
|
||||
partType: "FEE",
|
||||
laborAmount: 49.99,
|
||||
sellingPrice: 0,
|
||||
kitPrice: 0,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionGetServiceabilityDetails) {
|
||||
return Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionGetShopTimeSlots) {
|
||||
if (request.providerNumber == "0000001") {
|
||||
return Promise.resolve(mockGetShopTimeSlotsGoodAvailability);
|
||||
|
|
|
|||
|
|
@ -229,7 +229,8 @@ export default {
|
|||
) {
|
||||
// Validate the Zip Code
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.addressQuestions.zipCode
|
||||
this.internalModel.addressQuestions.zipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
|
|
@ -238,10 +239,17 @@ export default {
|
|||
} else {
|
||||
// retrieve mobile fee part
|
||||
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode);
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
this.$emit("updated-mobile-fee-part", mobileFeePart);
|
||||
|
|
|
|||
|
|
@ -1,87 +1,112 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<serviceZipModalQuestion
|
||||
v-model="serviceZipCodeQuestion"
|
||||
ref="serviceZipCodeQuestion"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
linkWidgetName="ServiceZipLinkWidget"
|
||||
modalWidgetName="ServiceZipModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
<alert
|
||||
ref="alertMilitaryBaseZip"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMilitaryBaseZipWidget"
|
||||
v-if="displayMilitaryZipAlert"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertMobileOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMobileOnlyWidget"
|
||||
v-if="displayServiceableMobileOnly"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertRecalNoMobile"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertRecalNoMobileWidget"
|
||||
v-if="displayRecalibrationWarning"
|
||||
@text-link-clicked="openModalAction"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertInshopOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertInshopOnlyWidget"
|
||||
v-if="displayServiceableInshopOnly"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertNoShops"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertNoShopsWidget"
|
||||
v-if="displayNoShopsAlert"
|
||||
alertClass="alert-warning" />
|
||||
<appointmentTypeQuestion
|
||||
v-model="selectedAppointmentType"
|
||||
v-show="isAppointmentTypeDisplayed"
|
||||
:isServiceableMobile="isServiceableMobile"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
:isDisplayed="isAppointmentTypeDisplayed"
|
||||
ref="appointmentTypeQuestion"
|
||||
groupName="appointmentTypeQuestion"
|
||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||
validationRules="option-required" />
|
||||
<mobileLocationModalQuestions
|
||||
customComponentId="mobileLocationQuestions"
|
||||
v-if="selectedAppointmentType === 'Mobile'"
|
||||
v-model="mobileLocationQuestions"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
validationRules="mobile-location-required"
|
||||
ref="mobileLocationQuestions"
|
||||
linkWidgetName="MobileLocationLinkWidget"
|
||||
modalWidgetName="MobileLocationModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
<shopQuestion
|
||||
ref="shopQuestion"
|
||||
v-show="isShopQuestionDisplayed"
|
||||
v-model="selectedProvider"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
cmsWidgetName="ShopQuestionWidget" />
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
|
||||
<serviceZipModalQuestion
|
||||
v-model="serviceZipCodeQuestion"
|
||||
ref="serviceZipCodeQuestion"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
linkWidgetName="ServiceZipLinkWidget"
|
||||
modalWidgetName="ServiceZipModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
|
||||
<alert
|
||||
ref="alertMilitaryBaseZip"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMilitaryBaseZipWidget"
|
||||
v-if="displayMilitaryZipAlert"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertMobileOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMobileOnlyWidget"
|
||||
v-if="displayServiceableMobileOnly"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertRecalNoMobile"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertRecalNoMobileWidget"
|
||||
v-if="displayRecalibrationWarning"
|
||||
@text-link-clicked="openModalAction"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertInshopOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertInshopOnlyWidget"
|
||||
v-if="displayServiceableInshopOnly"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertNoShops"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertNoShopsWidget"
|
||||
v-if="displayNoShopsAlert"
|
||||
alertClass="alert-warning" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center appointment-type">
|
||||
<div class="col-md-8">
|
||||
<appointmentTypeQuestion
|
||||
v-model="selectedAppointmentType"
|
||||
v-show="isAppointmentTypeDisplayed"
|
||||
:isServiceableMobile="isServiceableMobile"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
:isDisplayed="isAppointmentTypeDisplayed"
|
||||
ref="appointmentTypeQuestion"
|
||||
groupName="appointmentTypeQuestion"
|
||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||
validationRules="option-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<mobileLocationModalQuestions
|
||||
customComponentId="mobileLocationQuestions"
|
||||
v-if="selectedAppointmentType === 'Mobile'"
|
||||
v-model="mobileLocationQuestions"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
validationRules="mobile-location-required"
|
||||
ref="mobileLocationQuestions"
|
||||
linkWidgetName="MobileLocationLinkWidget"
|
||||
modalWidgetName="MobileLocationModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
|
||||
<shopQuestion
|
||||
ref="shopQuestion"
|
||||
v-show="isShopQuestionDisplayed"
|
||||
v-model="selectedProvider"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
cmsWidgetName="ShopQuestionWidget" />
|
||||
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -95,7 +120,7 @@ import appointmentTypeQuestion from "@/layouts/service-location/appointment-type
|
|||
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
||||
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import { Form } from "vee-validate";
|
||||
|
|
@ -116,6 +141,8 @@ import store from "@/store";
|
|||
import { defineRule } from "vee-validate";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
|
||||
const MOBILE_FEE_PART_TYPE = "MOBILE FEE";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("mobile-location-required", (value) => {
|
||||
if (
|
||||
|
|
@ -156,11 +183,15 @@ export default {
|
|||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
||||
const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode);
|
||||
const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode, "service-location");
|
||||
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
|
||||
|
||||
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
|
||||
|
|
@ -375,6 +406,43 @@ export default {
|
|||
backButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
updateAndSaveSupportingItems() {
|
||||
const supportingItems = store.getters.lineItems.supportingItems;
|
||||
// if we have a mobile fee, then save/update supporting items
|
||||
if (this.selectedAppointmentType == "Mobile") {
|
||||
const mobileFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == MOBILE_FEE_PART_TYPE
|
||||
);
|
||||
// If it already exists, update the price with latest data
|
||||
if (mobileFeeIndex >= 0) {
|
||||
supportingItems[mobileFeeIndex].laborAmount = this.mobileFeePart.laborAmount;
|
||||
supportingItems[mobileFeeIndex].selingPrice = this.mobileFeePart.selingPrice;
|
||||
supportingItems[mobileFeeIndex].kitPrice = this.mobileFeePart.kitPrice;
|
||||
} else {
|
||||
supportingItems.push(this.mobileFeePart);
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
||||
supportingItems,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
// if it's not a mobile, then make sure we remove any that may have been added
|
||||
const removeMobileFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == MOBILE_FEE_PART_TYPE
|
||||
);
|
||||
|
||||
if (removeMobileFeeIndex >= 0) {
|
||||
supportingItems.splice(removeMobileFeeIndex, 1);
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
||||
supportingItems,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
await this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SERVICE_LOCATION,
|
||||
|
|
@ -401,6 +469,7 @@ export default {
|
|||
false
|
||||
);
|
||||
|
||||
this.updateAndSaveSupportingItems();
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
},
|
||||
openModalAction(modalName) {
|
||||
|
|
@ -416,7 +485,7 @@ export default {
|
|||
appointmentTypeQuestion,
|
||||
mobileLocationModalQuestions,
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
|
|
@ -427,6 +496,14 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.appointment-type {
|
||||
@include media-breakpoint-up(md) {
|
||||
.col {
|
||||
flex: 0 0 auto;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.question-text {
|
||||
& > span {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@ export default {
|
|||
if (this.internalModel.zipCode !== this.modelValue.zipCode) {
|
||||
this.resetAlerts();
|
||||
|
||||
const zipCodeData = await this.getZipCodeData(this.internalModel.zipCode);
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.zipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
|
|
@ -144,10 +147,17 @@ export default {
|
|||
|
||||
// retrieve mobile fee part
|
||||
const serviceZipCode = this.internalModel.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode);
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
this.$emit("updated-mobile-fee-part", mobileFeePart);
|
||||
|
|
|
|||
|
|
@ -311,8 +311,7 @@ describe("shop-question.vue", () => {
|
|||
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||
|
||||
// Assert
|
||||
expect(showMoreShopsLink.exists()).toBe(true);
|
||||
expect(showMoreShopsLink.isVisible()).toBe(false);
|
||||
expect(showMoreShopsLink.exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("Should display the next three shops when the 'Show more location' link is clicked", async () => {
|
||||
|
|
@ -375,6 +374,12 @@ describe("shop-question.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.setData({
|
||||
displaySeeMoreLocationsLink: true,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
validationRules="option-required"
|
||||
:additionalButtonData="additionalButtonData" />
|
||||
<textLink
|
||||
v-show="displaySeeMoreLocationsLink"
|
||||
v-if="displaySeeMoreLocationsLink"
|
||||
ref="showMoreShopsLink"
|
||||
class="show-more-shops-link"
|
||||
id="showMoreShopsId"
|
||||
|
|
@ -126,9 +126,13 @@ export default {
|
|||
return this.loadData(serviceZipCode);
|
||||
},
|
||||
loadData(serviceZipCode) {
|
||||
return baseMixin.methods.dispatchStoreAction(storeActions.GET_PROVIDERS, {
|
||||
serviceZipCode: serviceZipCode,
|
||||
});
|
||||
return baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_PROVIDERS,
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
},
|
||||
"service-location"
|
||||
);
|
||||
},
|
||||
initializeComponent(shopQuestionInitialData) {
|
||||
this.shopProviders = shopQuestionInitialData.shopProviders;
|
||||
|
|
|
|||
|
|
@ -173,9 +173,10 @@ describe("vehicle-damage.vue", () => {
|
|||
//Assert
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
|
||||
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
|
||||
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toBeCalledWith(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: "C00000000" }
|
||||
{ carId: "C00000000" },
|
||||
"vehicle-damage"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -268,9 +269,10 @@ describe("vehicle-damage.vue", () => {
|
|||
//Assert
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
|
||||
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
|
||||
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toBeCalledWith(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: "C00000000" }
|
||||
{ carId: "C00000000" },
|
||||
"vehicle-damage"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -789,6 +791,7 @@ function setupMocks({ pageHeaderWidgetHeaderText, mountOptionsMockData, funnelCo
|
|||
mountOptionsMockData = Object.assign(mountOptionsMockDataDefault, mountOptionsMockData);
|
||||
//Mock api responses
|
||||
baseMixin.methods.dispatchStoreAction = jest.fn();
|
||||
baseMixin.methods.dispatchStoreActionWithLogging = jest.fn();
|
||||
const apiResponses = {
|
||||
cmsContent: {
|
||||
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
|
||||
|
|
|
|||
|
|
@ -1,56 +1,77 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="vehicleChangeAlert"
|
||||
v-if="shouldDisplayVehicleChangeAlert"
|
||||
class="mt-5 mb-0"
|
||||
cmsWidgetName="VehicleChangeAlert"
|
||||
alertClass="alert-warning"
|
||||
:isDismissible="false" />
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
cmsWidgetName="DamageLocationQuestion"
|
||||
v-model="selectedDamageLocations"
|
||||
groupName="DamageLocationQuestion" />
|
||||
<windshieldOptions
|
||||
ref="windshieldOptions"
|
||||
v-model="selectedWindshieldOptions"
|
||||
:hasRepairReplaceConflict="hasRepairReplaceConflict"
|
||||
:hasSplitSingleConflict="hasSplitSingleConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
<alert
|
||||
v-if="hasRepairReplaceConflict"
|
||||
class="my-5"
|
||||
cmsWidgetName="HasReplacementConflict"
|
||||
alertClass="alert-danger"
|
||||
:isDismissible="false" />
|
||||
<sideDoorOptions
|
||||
ref="sideDoorOptions"
|
||||
cmsWidgetName="SideDoorSideQuestion"
|
||||
groupName="SideDoorSideQuestion"
|
||||
v-model="sideDoorOptionsData"
|
||||
v-show="!hasRepairReplaceConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
<replaceOptionsQuestion
|
||||
ref="backGlassOptions"
|
||||
cmsWidgetName="RearReplaceOptionsQuestion"
|
||||
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
|
||||
v-model="selectedRearReplaceOptions"
|
||||
groupName="BackGlassReplaceOptionsQuestion"
|
||||
validationRules="replace-options-required" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<div class="container-fluid page-container-grouped-styles vehicle-damage">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<alert
|
||||
ref="vehicleChangeAlert"
|
||||
v-if="shouldDisplayVehicleChangeAlert"
|
||||
class="mt-5 mb-0"
|
||||
cmsWidgetName="VehicleChangeAlert"
|
||||
alertClass="alert-warning"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-8 col-xl-6">
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
cmsWidgetName="DamageLocationQuestion"
|
||||
v-model="selectedDamageLocations"
|
||||
groupName="DamageLocationQuestion" />
|
||||
|
||||
<windshieldOptions
|
||||
class="windshield-options"
|
||||
ref="windshieldOptions"
|
||||
v-model="selectedWindshieldOptions"
|
||||
:hasRepairReplaceConflict="hasRepairReplaceConflict"
|
||||
:hasSplitSingleConflict="hasSplitSingleConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
|
||||
<alert
|
||||
v-if="hasRepairReplaceConflict"
|
||||
class="my-5"
|
||||
cmsWidgetName="HasReplacementConflict"
|
||||
alertClass="alert-danger"
|
||||
:isDismissible="false" />
|
||||
|
||||
<sideDoorOptions
|
||||
ref="sideDoorOptions"
|
||||
cmsWidgetName="SideDoorSideQuestion"
|
||||
groupName="SideDoorSideQuestion"
|
||||
v-model="sideDoorOptionsData"
|
||||
v-show="!hasRepairReplaceConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
|
||||
<replaceOptionsQuestion
|
||||
ref="backGlassOptions"
|
||||
cmsWidgetName="RearReplaceOptionsQuestion"
|
||||
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
|
||||
v-model="selectedRearReplaceOptions"
|
||||
groupName="BackGlassReplaceOptionsQuestion"
|
||||
validationRules="replace-options-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -59,7 +80,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
|
||||
|
|
@ -90,9 +111,10 @@ export default {
|
|||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
const damageOptionsPromise = baseMixin.methods.dispatchStoreAction(
|
||||
const damageOptionsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: store.getters.vehicle.carId }
|
||||
{ carId: store.getters.vehicle.carId },
|
||||
"vehicle-damage"
|
||||
);
|
||||
|
||||
// Settle promises and get results
|
||||
|
|
@ -325,8 +347,10 @@ export default {
|
|||
);
|
||||
|
||||
if (this.isWindshieldRepair) {
|
||||
const supportingItems = await this.dispatchStoreAction(
|
||||
storeActions.GET_SUPPORTING_ITEMS
|
||||
const supportingItems = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
null,
|
||||
"vehicle-damage"
|
||||
);
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS,
|
||||
|
|
@ -509,7 +533,7 @@ export default {
|
|||
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
sideDoorOptions,
|
||||
|
|
@ -521,3 +545,16 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vehicle-damage {
|
||||
.windshield-options {
|
||||
@include media-breakpoint-up(md) {
|
||||
.col {
|
||||
flex: 0 0 auto;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
|
|||
partQuestionRearWrapper.vm.initializeComponent = glassPartQuestion.methods.initializeComponent;
|
||||
|
||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
|
||||
// wrapper.vm.$refs.onSubmit = jest.fn();
|
||||
// wrapper.vm.$refs.onInvalidSubmit = jest.fn();
|
||||
|
|
|
|||
|
|
@ -1,42 +1,54 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<div class="page-container-grouped-styles vehicle-parts small-question-text">
|
||||
<div class="vehicle-parts small-question-text">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
ref="vehicleBanner"
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div class="prevent-squish my-5">
|
||||
<div class="row">
|
||||
<div class="col pb-0">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
||||
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
||||
<hr v-if="i > 0" />
|
||||
<glassPartQuestion
|
||||
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
||||
v-model="selectedGlassParts[item.glassLocation + '-' + item.glassName]"
|
||||
:glassLocation="item.glassLocation"
|
||||
:glassName="item.glassName"
|
||||
:colorAnswers="item.colorAnswers"
|
||||
:alreadyPopulatedPartsData="alreadyPopulatedPartsData" />
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<vehicleBanner
|
||||
ref="vehicleBanner"
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader
|
||||
ref="funnelSubHeader"
|
||||
cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="prevent-squish my-5">
|
||||
<div class="row">
|
||||
<div class="col pb-0">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
||||
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
||||
<hr v-if="i > 0" />
|
||||
<glassPartQuestion
|
||||
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
||||
v-model="
|
||||
selectedGlassParts[item.glassLocation + '-' + item.glassName]
|
||||
"
|
||||
:glassLocation="item.glassLocation"
|
||||
:glassName="item.glassName"
|
||||
:colorAnswers="item.colorAnswers"
|
||||
:alreadyPopulatedPartsData="alreadyPopulatedPartsData" />
|
||||
</div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="isForwardActionDisabled"
|
||||
@back-clicked="navigateBack"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="isForwardActionDisabled"
|
||||
@back-clicked="navigateBack"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -48,7 +60,7 @@ import glassPartQuestion from "@/layouts/vehicle-parts/glass-part-question/glass
|
|||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
// Supporting Files
|
||||
|
|
@ -178,7 +190,7 @@ export default {
|
|||
}
|
||||
// If no parts could be matched, throw an error (isForwardActionDisabled is based off of matchedParts)
|
||||
if (this.isForwardActionDisabled) {
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
this.$refs.navbar.removeLoader();
|
||||
throw new Error("Could not match any parts to the selected parts");
|
||||
}
|
||||
|
||||
|
|
@ -221,9 +233,19 @@ export default {
|
|||
funnelHeader,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
alert,
|
||||
loadingModal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vehicle-parts {
|
||||
label {
|
||||
&.list-card {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,73 +1,68 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles position-relative">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<div class="select-car">
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="select-car-form rounded">
|
||||
<funnelSubHeader
|
||||
cmsWidgetName="FunnelSubHeaderWidget"
|
||||
class="siteSubHeader" />
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="siteSubHeader" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleYearQuestion"
|
||||
v-model="selectedYear"
|
||||
:isDisabled="!yearOptions.length"
|
||||
:options="yearOptions"
|
||||
cmsWidgetName="VehicleYearQuestion"
|
||||
validationRules="year-required"
|
||||
placeHolderText="Select year"
|
||||
inputId="yearQuestionField" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleYearQuestion"
|
||||
v-model="selectedYear"
|
||||
:isDisabled="!yearOptions.length"
|
||||
:options="yearOptions"
|
||||
cmsWidgetName="VehicleYearQuestion"
|
||||
validationRules="year-required"
|
||||
placeHolderText="Select year"
|
||||
inputId="yearQuestionField" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleMakeQuestion"
|
||||
v-model="selectedMake"
|
||||
:isDisabled="!makeOptions.length"
|
||||
:options="makeOptions"
|
||||
cmsWidgetName="VehicleMakeQuestion"
|
||||
validationRules="make-required"
|
||||
placeHolderText="Select make"
|
||||
inputId="makeQuestionField" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleMakeQuestion"
|
||||
v-model="selectedMake"
|
||||
:isDisabled="!makeOptions.length"
|
||||
:options="makeOptions"
|
||||
cmsWidgetName="VehicleMakeQuestion"
|
||||
validationRules="make-required"
|
||||
placeHolderText="Select make"
|
||||
inputId="makeQuestionField" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleModelQuestion"
|
||||
v-model="selectedModel"
|
||||
cmsWidgetName="VehicleModelQuestion"
|
||||
:isDisabled="!modelOptions.length"
|
||||
:options="modelOptions"
|
||||
validationRules="model-required"
|
||||
placeHolderText="Select model"
|
||||
inputId="modelQuestionField" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleModelQuestion"
|
||||
v-model="selectedModel"
|
||||
cmsWidgetName="VehicleModelQuestion"
|
||||
:isDisabled="!modelOptions.length"
|
||||
:options="modelOptions"
|
||||
validationRules="model-required"
|
||||
placeHolderText="Select model"
|
||||
inputId="modelQuestionField" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleStyleQuestion"
|
||||
v-model="selectedStyle"
|
||||
cmsWidgetName="VehicleStyleQuestion"
|
||||
:isDisabled="!styleOptions.length"
|
||||
:options="styleOptions"
|
||||
validationRules="style-required"
|
||||
placeHolderText="Select style"
|
||||
inputId="styleQuestionField" />
|
||||
<div class="col-md-3"> </div>
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="displayGeneric"
|
||||
:displayVehicleImage="imageUrl"
|
||||
:vehicleCategory="category"
|
||||
:displayUnmatchedVehicleIcon="unmatchedVehicleIcon"
|
||||
class="mt-5 vehicle-footer"
|
||||
ref="banner" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleStyleQuestion"
|
||||
v-model="selectedStyle"
|
||||
cmsWidgetName="VehicleStyleQuestion"
|
||||
:isDisabled="!styleOptions.length"
|
||||
:options="styleOptions"
|
||||
validationRules="style-required"
|
||||
placeHolderText="Select style"
|
||||
inputId="styleQuestionField" />
|
||||
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="displayGeneric"
|
||||
:displayVehicleImage="imageUrl"
|
||||
:vehicleCategory="category"
|
||||
:displayUnmatchedVehicleIcon="unmatchedVehicleIcon"
|
||||
class="mt-5 vehicle-footer"
|
||||
ref="banner" />
|
||||
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
ref="funnelFooter" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
ref="navbar" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -77,7 +72,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import vehicleQuestion from "@/layouts/vehicle/vehicle-question/vehicle-question";
|
||||
|
|
@ -127,9 +122,10 @@ export default {
|
|||
const experimentForLogging = store.getters.applicationUser.experiments.find(
|
||||
(e) => e.universeName === experimentUniverses.CONCEPT_FUNNEL
|
||||
);
|
||||
const yearQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
const yearQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_YEARS,
|
||||
{}
|
||||
{},
|
||||
"vehicle"
|
||||
);
|
||||
var makeQuestionInitialDataPromise = null;
|
||||
var modelQuestionInitialDataPromise = null;
|
||||
|
|
@ -140,35 +136,38 @@ export default {
|
|||
store.getters.order.vehicle.model &&
|
||||
store.getters.order.vehicle.style
|
||||
) {
|
||||
makeQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
makeQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MAKES,
|
||||
{
|
||||
year: store.getters.order.vehicle.year,
|
||||
}
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
|
||||
modelQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
modelQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MODELS,
|
||||
{
|
||||
year: store.getters.order.vehicle.year,
|
||||
make: store.getters.order.vehicle.make,
|
||||
}
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
|
||||
styleQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
styleQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_STYLES,
|
||||
{
|
||||
year: store.getters.order.vehicle.year,
|
||||
make: store.getters.order.vehicle.make,
|
||||
model: store.getters.order.vehicle.model,
|
||||
}
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
}
|
||||
|
||||
// If the concept funnel experiment is found, as it should be when coming from safelite.com, then log the experiment exposure.
|
||||
if (experimentForLogging !== undefined) {
|
||||
// Log experiment exposure
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.LOG_EXPERIMENT_EXPOSURE,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
|
|
@ -176,6 +175,7 @@ export default {
|
|||
pageName: to.query.fmgPage,
|
||||
experiment: experimentForLogging,
|
||||
},
|
||||
"vehicle",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
@ -264,8 +264,13 @@ export default {
|
|||
model
|
||||
);
|
||||
this.styleOptions = result?.data;
|
||||
if (this.styleOptions.length === 1) this.selectedStyle = this.styleOptions[0];
|
||||
else this.selectedStyle = null;
|
||||
if (this.styleOptions.length === 1) {
|
||||
const sameStyle = this.selectedStyle === this.styleOptions[0];
|
||||
this.selectedStyle = this.styleOptions[0];
|
||||
if (sameStyle) {
|
||||
this.getVehicleDetails();
|
||||
}
|
||||
} else this.selectedStyle = null;
|
||||
} else {
|
||||
this.styleOptions = [];
|
||||
this.selectedStyle = null;
|
||||
|
|
@ -273,18 +278,7 @@ export default {
|
|||
},
|
||||
async selectedStyle(style) {
|
||||
if (style) {
|
||||
this.carId = null;
|
||||
const result = await this.getVehicle(
|
||||
this.selectedYear,
|
||||
this.selectedMake,
|
||||
this.selectedModel,
|
||||
style
|
||||
);
|
||||
this.carId = result?.data.carId;
|
||||
this.category = result?.data.category;
|
||||
this.imageUrl = result?.data.imageUrl;
|
||||
this.imageVifNumber = result?.data.imageVifNumber;
|
||||
this.imageVifColor = result?.data.imageVifColor;
|
||||
this.getVehicleDetails();
|
||||
} else {
|
||||
this.imageUrl = null;
|
||||
}
|
||||
|
|
@ -304,16 +298,34 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getVehicle(year, make, model, style) {
|
||||
return this.dispatchStoreAction(this.storeActions.GET_VEHICLE, {
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
style: style,
|
||||
});
|
||||
return this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_VEHICLE,
|
||||
{
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
style: style,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
async getVehicleDetails() {
|
||||
this.carId = null;
|
||||
const result = await this.getVehicle(
|
||||
this.selectedYear,
|
||||
this.selectedMake,
|
||||
this.selectedModel,
|
||||
this.selectedStyle
|
||||
);
|
||||
this.carId = result?.data.carId;
|
||||
this.category = result?.data.category;
|
||||
this.imageUrl = result?.data.imageUrl;
|
||||
this.imageVifNumber = result?.data.imageVifNumber;
|
||||
this.imageVifColor = result?.data.imageVifColor;
|
||||
},
|
||||
|
||||
async forwardButtonAction() {
|
||||
this.dispatchStoreAction(
|
||||
|
|
@ -354,24 +366,36 @@ export default {
|
|||
return store.getters.vehicle.style;
|
||||
},
|
||||
async getMakeOptions(year) {
|
||||
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, {
|
||||
year: year,
|
||||
});
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MAKES,
|
||||
{
|
||||
year: year,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
|
||||
async getModelOptions(year, make) {
|
||||
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
|
||||
year: year,
|
||||
make: make,
|
||||
});
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MODELS,
|
||||
{
|
||||
year: year,
|
||||
make: make,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
|
||||
async getStyleOptions(year, make, model) {
|
||||
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
});
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_STYLES,
|
||||
{
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
getImagefromStore() {
|
||||
return store.getters.vehicle.imageUrl;
|
||||
|
|
@ -392,7 +416,7 @@ export default {
|
|||
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
vehicleBanner,
|
||||
Form,
|
||||
|
|
@ -402,10 +426,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.select-car-form {
|
||||
margin-left: 0.75rem;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
.vehicle-footer {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ jest.mock("@/helpers/damage-helper", () => ({
|
|||
}));
|
||||
|
||||
describe("vin-lookup.vue", () => {
|
||||
it("Should update the funnel-footer forward button when VIN is changed", (done) => {
|
||||
it("Should update the nav-bar forward button when VIN is changed", (done) => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
//Act
|
||||
wrapper.setData({ vin: "newValue" });
|
||||
//Assert
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toBeCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
@ -368,8 +368,8 @@ function mockOutPromises({ carId, isZipValid = true, isZipServiceable = true })
|
|||
}
|
||||
|
||||
function mockOutStubFunctions(wrapper) {
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.getZipCodeData = jest
|
||||
.fn()
|
||||
.mockReturnValue({ isValid: true, isServiceable: true, state: "OH" });
|
||||
|
|
|
|||
|
|
@ -1,131 +1,125 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="VinNumberQuestionWidget"
|
||||
v-model="vin"
|
||||
customInputId="vin"
|
||||
isRequired
|
||||
:validationRules="
|
||||
!vinPopulatedOnPageLoad ? 'vin-required|vin-format' : ''
|
||||
"
|
||||
:isDisabled="vinPopulatedOnPageLoad"
|
||||
maxLength="17"
|
||||
:mask="vinMask"
|
||||
includeImageQuestion
|
||||
:imageQuestionSubmitHandler="getVinFromImage"
|
||||
:maxFileSize="imageUploadMaxFileSize"
|
||||
@image-lookup-error="displayVinScanAlert"
|
||||
@image-validity-error="displayVinScanAlert"
|
||||
data-test="vin-lookup-field"
|
||||
ref="vinLookupQuestion" />
|
||||
</div>
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<alert
|
||||
cmsWidgetName="AlertVinScanFailed"
|
||||
v-if="displayVinScanFailedAlert"
|
||||
alertClass="alert-danger" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="VinNumberQuestionWidget"
|
||||
v-model="vin"
|
||||
customInputId="vin"
|
||||
isRequired
|
||||
:validationRules="!vinPopulatedOnPageLoad ? 'vin-required|vin-format' : ''"
|
||||
:isDisabled="vinPopulatedOnPageLoad"
|
||||
maxLength="17"
|
||||
:mask="vinMask"
|
||||
includeImageQuestion
|
||||
:imageQuestionSubmitHandler="getVinFromImage"
|
||||
:maxFileSize="imageUploadMaxFileSize"
|
||||
@image-lookup-error="displayVinScanAlert"
|
||||
@image-validity-error="displayVinScanAlert"
|
||||
data-test="vin-lookup-field"
|
||||
ref="vinLookupQuestion" />
|
||||
|
||||
<alert
|
||||
cmsWidgetName="AlertVinScanFailed"
|
||||
v-if="displayVinScanFailedAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<vinInformation />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
customInputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
customInputId="emailAddress"
|
||||
isRequired
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
v-model="customAlertData"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
alertClass="alert-danger"
|
||||
cmsWidgetName="AlertVinNotFoundWidget" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
!isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<vinInformation />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-0 mt-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
customInputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
customInputId="emailAddress"
|
||||
isRequired
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-0">
|
||||
<div class="col">
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
<alert
|
||||
class="my-4"
|
||||
v-model="customAlertData"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
alertClass="alert-danger"
|
||||
cmsWidgetName="AlertVinNotFoundWidget" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
!isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -134,7 +128,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
|
@ -255,9 +249,10 @@ export default {
|
|||
|
||||
// If this is a new VIN Lookup, do both a Vehicle Lookup and a Zip Validation
|
||||
if (!this.vinPopulatedOnPageLoad) {
|
||||
const vehicleLookupResponse = this.dispatchStoreAction(
|
||||
const vehicleLookupResponse = this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VEHICLE_BY_VIN,
|
||||
{ vin: this.vin }
|
||||
{ vin: this.vin },
|
||||
"vin-lookup"
|
||||
);
|
||||
|
||||
// Settle promises and get results
|
||||
|
|
@ -278,7 +273,7 @@ export default {
|
|||
const isZipValid = resultMap.zipCodeData.isValid;
|
||||
if (this.serviceZipCode && !isZipValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
|
|
@ -295,7 +290,7 @@ export default {
|
|||
}
|
||||
|
||||
// Remove loader and stop processing the page.
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// Check if the CarId is different from the lookup vs what is in state currently.
|
||||
|
|
@ -311,14 +306,15 @@ export default {
|
|||
this.displayMatchedDifferentVehicleAlert = true;
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
resultMap.vehicleLookupResponse.carId
|
||||
resultMap.vehicleLookupResponse.carId,
|
||||
"vin-lookup"
|
||||
);
|
||||
|
||||
// Update button "Continue with..."
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${resultMap.vehicleLookupResponse.year} ${resultMap.vehicleLookupResponse.make} ${resultMap.vehicleLookupResponse.model}`
|
||||
);
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// Save vin, vehicle, customer and service information
|
||||
|
|
@ -380,7 +376,7 @@ export default {
|
|||
this.displayInvalidZipAlert = true;
|
||||
}
|
||||
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
},
|
||||
async navigateForward() {
|
||||
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
|
||||
|
|
@ -399,7 +395,11 @@ export default {
|
|||
},
|
||||
getVinFromImage(image) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dispatchStoreAction(storeActions.LOOKUP_VIN_BY_IMAGE, image)
|
||||
this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VIN_BY_IMAGE,
|
||||
image,
|
||||
"vin-lookup"
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data.length > 0) {
|
||||
resolve(response.data[0]);
|
||||
|
|
@ -488,7 +488,7 @@ export default {
|
|||
vin() {
|
||||
this.displayVinNotFoundAlert = false;
|
||||
this.displayVinScanFailedAlert = false;
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
|
|
@ -498,7 +498,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
textboxQuestion,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,19 @@ export default {
|
|||
|
||||
return store.dispatch(type, payload);
|
||||
},
|
||||
dispatchStoreActionWithLogging(type, payload, pageNameToLog, encodePayload = true) {
|
||||
// Encode the payload if required
|
||||
if (encodePayload) {
|
||||
encodeUriData(payload);
|
||||
}
|
||||
|
||||
const wrappedParams = {
|
||||
payload: payload,
|
||||
pageNameToLog: pageNameToLog,
|
||||
};
|
||||
|
||||
return store.dispatch(type, wrappedParams);
|
||||
},
|
||||
savePageDataToStore(page, data) {
|
||||
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
|
||||
},
|
||||
|
|
@ -50,10 +63,16 @@ export default {
|
|||
const footerInfoBox = document.querySelector(".footer#infoBox");
|
||||
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
|
||||
},
|
||||
async getZipCodeData(zipCode) {
|
||||
const serviceZipValidationResponse = await this.dispatchStoreAction(
|
||||
async getZipCodeData(zipCode, pageNameToLog = null) {
|
||||
const pageName = pageNameToLog ?? this.$options?.name;
|
||||
if (pageName == null) {
|
||||
console.log("Missed a spot.");
|
||||
}
|
||||
|
||||
const serviceZipValidationResponse = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{ zip: zipCode }
|
||||
{ zip: zipCode },
|
||||
pageName
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@ describe("baseMixin.js", () => {
|
|||
|
||||
test("getZipCodeData calls dispatch", () => {
|
||||
const mixIn = getMixInInstance({});
|
||||
mixIn.methods.dispatchStoreAction = jest.fn();
|
||||
mixIn.methods.dispatchStoreAction.mockReturnValue({
|
||||
mixIn.methods.dispatchStoreActionWithLogging = jest.fn();
|
||||
mixIn.methods.dispatchStoreActionWithLogging.mockReturnValue({
|
||||
data: { isValid: true, isServiceable: true, state: "OH" },
|
||||
});
|
||||
const type = "";
|
||||
|
|
@ -118,7 +118,7 @@ describe("baseMixin.js", () => {
|
|||
|
||||
mixIn.methods.getZipCodeData(type, payload);
|
||||
|
||||
expect(mixIn.methods.dispatchStoreAction).toBeCalled();
|
||||
expect(mixIn.methods.dispatchStoreActionWithLogging).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -408,12 +408,13 @@ export default {
|
|||
for (let partOrQuestion of partsOrQuestions) {
|
||||
if (this.hasCapabilityQuestions([partOrQuestion])) {
|
||||
let capabilityQuestionsForGlassLocation = (
|
||||
await baseMixin.methods.dispatchStoreAction(
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_CAPABILITY_QUESTIONS,
|
||||
{
|
||||
carId: store.getters.vehicle.carId,
|
||||
partNumber: partOrQuestion.parts[0].partNumber,
|
||||
}
|
||||
},
|
||||
currentPage
|
||||
)
|
||||
).data;
|
||||
|
||||
|
|
@ -447,9 +448,17 @@ export default {
|
|||
const payment = store.getters.payment;
|
||||
|
||||
if (store.getters.order.referralNumber?.length === 6) {
|
||||
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: currentPage,
|
||||
loadingModal: self.$refs.loadingModal,
|
||||
});
|
||||
} else if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
|
||||
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: currentPage,
|
||||
loadingModal: self.$refs.loadingModal,
|
||||
});
|
||||
} else {
|
||||
self.$router.navigateWithSaving(
|
||||
self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
|||
export default {
|
||||
methods: {
|
||||
async navigateForwardWithSingleCarMatch() {
|
||||
const pageName = this.$options?.name;
|
||||
|
||||
// If we have not already saved a session, we need to save one now before the lengthy call to getPartsOrQuestions
|
||||
if (!store.getters.applicationUser.savedSessionId) {
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: pageName });
|
||||
}
|
||||
|
||||
const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS);
|
||||
const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS, {
|
||||
pageNameToLog: pageName,
|
||||
});
|
||||
const partsOrQuestions = result.data.partsOrQuestions;
|
||||
|
||||
vehicleQuestionsMixin.methods.navigateForward(partsOrQuestions, this);
|
||||
|
|
|
|||
|
|
@ -6,6 +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 { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper";
|
||||
|
||||
// Heritage integration
|
||||
|
|
@ -71,20 +72,11 @@ const routes = [
|
|||
? to.query.isInsurance == "true"
|
||||
? true
|
||||
: false
|
||||
: null
|
||||
: null,
|
||||
to.query.fmgPage
|
||||
);
|
||||
|
||||
const pageToRedirectTo = await getPageToRouteExistingOrderTo(
|
||||
to,
|
||||
loadSessionResponse
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to);
|
||||
|
||||
// Assign our fmgPage so it will load normally like the other pages.
|
||||
to.query.fmgPage = pageToRedirectTo;
|
||||
|
|
@ -181,7 +173,7 @@ router.afterEach(async (to, from) => {
|
|||
store.getters.applicationUser.savedSessionId ||
|
||||
store.getters.order.customer?.emailAddress
|
||||
) {
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: to.query.fmgPage });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,14 +276,9 @@ async function navigate(
|
|||
fmgPage: destinationFmgPageValue,
|
||||
};
|
||||
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const lowerCaseParams = new URLSearchParams();
|
||||
for (const [name, value] of urlParams) {
|
||||
lowerCaseParams.append(name.toLowerCase(), value);
|
||||
}
|
||||
const hasZip = lowerCaseParams.has(queryStrings.ZIP_CODE);
|
||||
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE);
|
||||
const hasZip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
||||
const zip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
||||
const promo = getQuerystringParameter(queryStrings.PROMO);
|
||||
|
||||
if (
|
||||
hasZip &&
|
||||
|
|
@ -303,6 +290,10 @@ async function navigate(
|
|||
queryStringsObject[queryStrings.ZIP_CODE] = zip;
|
||||
}
|
||||
|
||||
if (promo) {
|
||||
queryStringsObject[queryStrings.PROMO] = promo;
|
||||
}
|
||||
|
||||
router.push({
|
||||
name: "root",
|
||||
query: Object.assign(optionalQuery, queryStringsObject),
|
||||
|
|
@ -394,18 +385,26 @@ function arePagePrerequisitesValid(component) {
|
|||
// Run SiteEntry and PageEntry triggers for experiments
|
||||
async function runExperiments(nextPage) {
|
||||
if (!store.getters.applicationUser.triggeredSiteEntry) {
|
||||
await baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, {
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
||||
});
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
||||
},
|
||||
nextPage
|
||||
);
|
||||
}
|
||||
|
||||
await baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, {
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
||||
triggerValue: nextPage,
|
||||
});
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
||||
triggerValue: nextPage,
|
||||
},
|
||||
nextPage
|
||||
);
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const fmgPageValues = {
|
|||
CUSTOMER_DETAILS: "customer-details",
|
||||
REVIEW: "review",
|
||||
PAYMENT_METHOD: "payment-method",
|
||||
CONFIRMATION: "confirmation",
|
||||
};
|
||||
|
||||
export { fmgPageValues };
|
||||
|
|
|
|||
|
|
@ -467,6 +467,18 @@ const routingTable = function (store) {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.PAYMENT_METHOD,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationFmgPageValue: fmgPageValues.CONFIRMATION,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.CONFIRMATION,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
getDisplayTextForDurationLength,
|
||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||
|
||||
import { getDateDifferenceInDays } from "@/helpers/date-helper";
|
||||
// Export State
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
|
|
@ -234,8 +235,6 @@ export const mutations = {
|
|||
},
|
||||
updateRegistration(state, registrationInfo) {
|
||||
state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate;
|
||||
state.order.customer.firstName = registrationInfo?.firstName;
|
||||
state.order.customer.lastName = registrationInfo?.lastName;
|
||||
},
|
||||
updateServiceZip(state, serviceZipInfo) {
|
||||
state.order.serviceLocation.state = serviceZipInfo.state;
|
||||
|
|
@ -636,40 +635,62 @@ function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
|||
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
|
||||
}
|
||||
|
||||
function provisionalTriggersToString(provisionalTriggers) {
|
||||
return "ProvisionalTriggers:" + provisionalTriggers.join(",");
|
||||
function getTimeSlotsAdditionalEventData(
|
||||
provisionalTriggers,
|
||||
zipCode,
|
||||
firstAvailableAppointmentDateString,
|
||||
shopAppointmentType
|
||||
) {
|
||||
var numberOfDays = null;
|
||||
if (firstAvailableAppointmentDateString)
|
||||
numberOfDays = getDateDifferenceInDays(new Date(), firstAvailableAppointmentDateString);
|
||||
|
||||
if (shopAppointmentType)
|
||||
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(
|
||||
","
|
||||
)}`;
|
||||
else
|
||||
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(
|
||||
","
|
||||
)}`;
|
||||
}
|
||||
|
||||
// Export Actions
|
||||
export const actions = {
|
||||
// Vehicle API Actions
|
||||
getVehicleYears(context) {
|
||||
getVehicleYears(context, { pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleYears.method,
|
||||
endpoint: endpoints.GetVehicleYears.url,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVehicleByYmms(context, { year, make, model, style }) {
|
||||
lookupVehicleByYmms(context, { payload: { year, make, model, style }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVehicleByYmms.method,
|
||||
endpoint: `${endpoints.LookupVehicleByYmms.url}/${year}/${make}/${model}/${style}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVehicleByVin(context, { vin }) {
|
||||
lookupVehicleByVin(context, { payload: { vin }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVehicleByVin.method,
|
||||
endpoint: endpoints.LookupVehicleByVin.url,
|
||||
payload: {
|
||||
vin: vin, // EX "1J4GW58S4XC541166"
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVinByPlate(context, { licensePlate, licenseState }) {
|
||||
lookupVinByPlate(context, { payload: { licensePlate, licenseState }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVinByPlate.method,
|
||||
endpoint: endpoints.LookupVinByPlate.url,
|
||||
|
|
@ -677,12 +698,17 @@ export const actions = {
|
|||
licensePlate: licensePlate,
|
||||
licenseState: licenseState,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVinByAddress(
|
||||
context,
|
||||
{ licenseLastName, licenseStreetAddress, licenseZip, licenseState }
|
||||
{
|
||||
payload: { licenseLastName, licenseStreetAddress, licenseZip, licenseState },
|
||||
pageNameToLog,
|
||||
}
|
||||
) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVinByAddress.method,
|
||||
|
|
@ -693,10 +719,14 @@ export const actions = {
|
|||
licenseZip: licenseZip,
|
||||
licenseState: licenseState,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVinByImage(context, image) {
|
||||
lookupVinByImage(context, { payload, pageNameToLog }) {
|
||||
const image = payload;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
|
|
@ -718,68 +748,84 @@ export const actions = {
|
|||
method: endpoints.LookupVinByImage.method,
|
||||
endpoint: endpoints.LookupVinByImage.url,
|
||||
payload: data,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
isVinByAddressPermissible(context, zip) {
|
||||
isVinByAddressPermissible(context, { payload: { zip }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.IsVinByAddressPermissible.method,
|
||||
endpoint: `${endpoints.IsVinByAddressPermissible.url}?zipcode=${zip}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicleMakes(context, { year }) {
|
||||
getVehicleMakes(context, { payload: { year }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleMakes.method,
|
||||
endpoint: `${endpoints.GetVehicleMakes.url}/${year}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicleModels(context, { year, make }) {
|
||||
getVehicleModels(context, { payload: { year, make }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleModels.method,
|
||||
endpoint: `${endpoints.GetVehicleModels.url}/${year}/${make}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicleStyles(context, { year, make, model }) {
|
||||
getVehicleStyles(context, { payload: { year, make, model }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleStyles.method,
|
||||
endpoint: `${endpoints.GetVehicleStyles.url}/${year}/${make}/${model}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicle(context, { year, make, model, style }) {
|
||||
getVehicle(context, { payload: { year, make, model, style }, pageNameToLog }) {
|
||||
return globalMethods
|
||||
.callHttpClient({
|
||||
methods: endpoints.GetVehicle.method,
|
||||
endpoint: `${endpoints.GetVehicle.url}/${year}/${make}/${model}/${style}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
})
|
||||
.then((response) => {
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
getDamageOptions(context, { carId }) {
|
||||
getDamageOptions(context, { payload: { carId }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
methods: endpoints.GetDamageOptions.method,
|
||||
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
|
||||
payload: {},
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
"QueryStringZip: " + getQuerystringParameter(queryStrings.ZIP_CODE),
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
validateZip(context, { zip }) {
|
||||
validateZip(context, { payload: { zip }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
methods: endpoints.ValidateZip.method,
|
||||
endpoint: `${endpoints.ValidateZip.url}/${zip}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -828,6 +874,8 @@ export const actions = {
|
|||
payload: {
|
||||
pageName: pageName,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageName,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -846,11 +894,16 @@ export const actions = {
|
|||
pageName
|
||||
),
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageName,
|
||||
});
|
||||
},
|
||||
|
||||
// Analytics Actions
|
||||
logExperimentExposure(context, { userId, sessionKey, pageName, experiment }) {
|
||||
logExperimentExposure(
|
||||
context,
|
||||
{ payload: { userId, sessionKey, pageName, experiment }, pageNameToLog }
|
||||
) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LogExperimentExposureIfAssigned.method,
|
||||
endpoint: endpoints.LogExperimentExposureIfAssigned.url,
|
||||
|
|
@ -871,15 +924,19 @@ export const actions = {
|
|||
pageName: pageName,
|
||||
},
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
// Location API Actions
|
||||
getAlertReasonsByCtu(context, { ctu }) {
|
||||
getAlertReasonsByCtu(context, { payload: { ctu }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetAlertReasons.method,
|
||||
endpoint: `${endpoints.GetAlertReasons.url}/${ctu}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -1047,7 +1104,10 @@ export const actions = {
|
|||
});
|
||||
},
|
||||
|
||||
async runExperimentsForTrigger(context, { userId, triggerEvent, triggerValue }) {
|
||||
async runExperimentsForTrigger(
|
||||
context,
|
||||
{ payload: { userId, triggerEvent, triggerValue }, pageNameToLog }
|
||||
) {
|
||||
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
|
||||
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
|
||||
}
|
||||
|
|
@ -1064,6 +1124,8 @@ export const actions = {
|
|||
method: endpoints.RunExperimentsForTrigger.method,
|
||||
endpoint: endpoints.RunExperimentsForTrigger.url,
|
||||
payload: payload,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments);
|
||||
|
|
@ -1078,7 +1140,7 @@ export const actions = {
|
|||
},
|
||||
|
||||
// PartsOrQuestions API Actions
|
||||
async getPartsOrQuestions(context) {
|
||||
async getPartsOrQuestions(context, { pageNameToLog }) {
|
||||
const vehicle = context.getters.vehicle;
|
||||
const damage = context.getters.damage;
|
||||
const order = context.state.order;
|
||||
|
|
@ -1100,6 +1162,8 @@ export const actions = {
|
|||
zip: zipCode,
|
||||
vin: vin,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
// Flatten location and name properties
|
||||
|
|
@ -1111,7 +1175,7 @@ export const actions = {
|
|||
},
|
||||
|
||||
// Parts API Actions
|
||||
async getParts(context) {
|
||||
async getParts(context, { pageNameToLog }) {
|
||||
const vehicle = context.getters.vehicle;
|
||||
const damage = context.getters.damage;
|
||||
const order = context.state.order;
|
||||
|
|
@ -1136,6 +1200,8 @@ export const actions = {
|
|||
zip: zipCode,
|
||||
vin: vin,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
// Flatten location and name properties
|
||||
|
|
@ -1146,13 +1212,15 @@ export const actions = {
|
|||
return response;
|
||||
},
|
||||
|
||||
getWipers(context) {
|
||||
getWipers(context, { pageNameToLog }) {
|
||||
const carId = context.getters.vehicle.carId;
|
||||
const serviceZipCode = context.getters.order.serviceLocation.zipCode;
|
||||
return globalMethods
|
||||
.callHttpClient({
|
||||
method: endpoints.GetWipers.method,
|
||||
endpoint: `${endpoints.GetWipers.url}/${carId}/${serviceZipCode}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
})
|
||||
.catch((error) => {
|
||||
// The wiper service sometimes returns 500s on legitimate carId/zipCode combination - return empty array instead of breaking flow
|
||||
|
|
@ -1160,14 +1228,16 @@ export const actions = {
|
|||
});
|
||||
},
|
||||
|
||||
getRainDefense(context) {
|
||||
getRainDefense(context, { pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetRainDefense.method,
|
||||
endpoint: endpoints.GetRainDefense.url,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getMobileFeePart(context) {
|
||||
getMobileFeePart(context, { pageNameToLog }) {
|
||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
||||
const billToAccountNumber = 87291; // TODO: MAKE THIS REAL
|
||||
|
|
@ -1175,10 +1245,12 @@ export const actions = {
|
|||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetMobileFeePart.method,
|
||||
endpoint: `${endpoints.GetMobileFeePart.url}/${damageType}/${parentAccountNumber}/${billToAccountNumber}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getServiceabilityDetails(context, { serviceZipCode }) {
|
||||
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems.map(
|
||||
(lineItem) => ({
|
||||
partNumber: lineItem.partNumber,
|
||||
|
|
@ -1202,20 +1274,24 @@ export const actions = {
|
|||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetServiceabilityDetails.method,
|
||||
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&${lineItems}&${glassPieces}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getProviders(context, { serviceZipCode }) {
|
||||
getProviders(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||
const shopRadiusInMiles = 100;
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetProviders.method,
|
||||
endpoint: `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getSupportingItems(context) {
|
||||
getSupportingItems(context, { pageNameToLog }) {
|
||||
const glassPartsArray = context.getters.lineItems.glassParts ?? [];
|
||||
const carId = context.getters.vehicle.carId;
|
||||
const isRepair = context.getters.damage.isRepair;
|
||||
|
|
@ -1231,17 +1307,22 @@ export const actions = {
|
|||
parts: glassPartsArray,
|
||||
numberOfRepairChips: isRepair ? numberOfChips : 0,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getCapabilityQuestions(context, { carId, partNumber }) {
|
||||
getCapabilityQuestions(context, { payload: { carId, partNumber }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetCapabilityQuestions.method,
|
||||
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getPartFromCapabilityQuestionAnswer(context, glassLocation) {
|
||||
getPartFromCapabilityQuestionAnswer(context, { payload, pageNameToLog }) {
|
||||
const glassLocation = payload;
|
||||
const pageData = context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS);
|
||||
|
||||
const part = pageData.partsOrQuestions.find((x) => x.glassLocation === glassLocation)
|
||||
|
|
@ -1258,10 +1339,15 @@ export const actions = {
|
|||
part,
|
||||
capabilityAnswerResults: capabilityQuestionAnswersForPart,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getShopTimeSlots(context, { startDate, endDate, shopAppointmentType, providerNumber }) {
|
||||
getShopTimeSlots(
|
||||
context,
|
||||
{ payload: { startDate, endDate, shopAppointmentType, providerNumber }, pageNameToLog }
|
||||
) {
|
||||
const order = context.state.order;
|
||||
const vehicle = context.state.order.vehicle;
|
||||
|
||||
|
|
@ -1316,12 +1402,19 @@ export const actions = {
|
|||
method: endpoints.GetShopTimeSlots.method,
|
||||
endpoint: endpoints.GetShopTimeSlots.url,
|
||||
payload: payload,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
provisionalTriggersToString(response.data.provisionalTriggers),
|
||||
getTimeSlotsAdditionalEventData(
|
||||
response.data.provisionalTriggers,
|
||||
order.serviceLocation.zipCode,
|
||||
response.data.days?.[0]?.date,
|
||||
shopAppointmentType
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
getMobileTimeSlots(context, { startDate, endDate }) {
|
||||
getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) {
|
||||
const order = context.state.order;
|
||||
const vehicle = context.state.order.vehicle;
|
||||
let lineItems = [
|
||||
|
|
@ -1373,23 +1466,31 @@ export const actions = {
|
|||
method: endpoints.GetMobileTimeSlots.method,
|
||||
endpoint: endpoints.GetMobileTimeSlots.url,
|
||||
payload: payload,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
provisionalTriggersToString(response.data.provisionalTriggers),
|
||||
getTimeSlotsAdditionalEventData(
|
||||
response.data.provisionalTriggers,
|
||||
order.serviceLocation.zipCode,
|
||||
response.data.days?.[0]?.date
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
getMobilePremiumFee(context) {
|
||||
getMobilePremiumFee(context, { pageNameToLog }) {
|
||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||
const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash";
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetMobilePremiumFee.method,
|
||||
endpoint: `${endpoints.GetMobilePremiumFee.url}/${paymentType}/${damageType}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
// Session API Actions
|
||||
saveSession(context) {
|
||||
saveSession(context, { pageNameToLog }) {
|
||||
const vehicle = context.getters.vehicle;
|
||||
const damage = context.getters.damage;
|
||||
const order = context.state.order;
|
||||
|
|
@ -1495,6 +1596,8 @@ export const actions = {
|
|||
eon: order.eon,
|
||||
},
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
"Email provided: " + (order.customer.emailAddress ? "true" : "false"),
|
||||
});
|
||||
|
|
@ -1503,12 +1606,15 @@ export const actions = {
|
|||
loadSession(
|
||||
context,
|
||||
{
|
||||
savedSessionId,
|
||||
referralNumber,
|
||||
referralDate,
|
||||
parentAccountNumber,
|
||||
referralCorrelationId,
|
||||
isConceptInsurance,
|
||||
payload: {
|
||||
savedSessionId,
|
||||
referralNumber,
|
||||
referralDate,
|
||||
parentAccountNumber,
|
||||
referralCorrelationId,
|
||||
isConceptInsurance,
|
||||
},
|
||||
pageNameToLog,
|
||||
}
|
||||
) {
|
||||
const order = context.state.order;
|
||||
|
|
@ -1524,6 +1630,8 @@ export const actions = {
|
|||
parentAccountNumber: parentAccountNumber,
|
||||
referralCorrelationId: referralCorrelationId,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
})
|
||||
.then(
|
||||
async (response) => {
|
||||
|
|
@ -1546,7 +1654,7 @@ export const actions = {
|
|||
response.data
|
||||
);
|
||||
|
||||
await resetScheduleIfUnavailable(context, response.data.order);
|
||||
await resetScheduleIfUnavailable(context, response.data.order, pageNameToLog);
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
|
|
@ -1567,26 +1675,25 @@ export const actions = {
|
|||
context,
|
||||
{ year, make, model, style, carId, category, imageUrl, imageVifNumber, imageVifColor }
|
||||
) {
|
||||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
|
||||
if (context.state.order.vehicle.year !== year) {
|
||||
if (
|
||||
context.state.order.vehicle.year != year ||
|
||||
context.state.order.vehicle.make != make ||
|
||||
context.state.order.vehicle.model != model ||
|
||||
context.state.order.vehicle.style != style
|
||||
) {
|
||||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
|
||||
context.commit(storeMutations.UPDATE_YEAR, year);
|
||||
}
|
||||
if (context.state.order.vehicle.make !== make) {
|
||||
context.commit(storeMutations.UPDATE_MAKE, make);
|
||||
}
|
||||
if (context.state.order.vehicle.model !== model) {
|
||||
context.commit(storeMutations.UPDATE_MODEL, model);
|
||||
}
|
||||
if (context.state.order.vehicle.style !== style) {
|
||||
context.commit(storeMutations.UPDATE_STYLE, style);
|
||||
context.commit(storeMutations.UPDATE_CAR_ID, carId);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor);
|
||||
}
|
||||
context.commit(storeMutations.UPDATE_CAR_ID, carId);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor);
|
||||
},
|
||||
|
||||
saveVehicleDamage(
|
||||
|
|
@ -1662,15 +1769,25 @@ export const actions = {
|
|||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
}
|
||||
|
||||
//Save new values
|
||||
context.commit(storeMutations.UPDATE_VEHICLE, vehicleInfo);
|
||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
|
||||
if (registrationInfo) {
|
||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
|
||||
// only update name information if there isn't a value in state already
|
||||
if (!context.getters.order.customer.firstName) {
|
||||
context.commit(storeMutations.UPDATE_CUSTOMER_DETAILS, {
|
||||
firstName: registrationInfo.firstName,
|
||||
lastName: registrationInfo.lastName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
saveRegistrationAddressLookup(
|
||||
context,
|
||||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, customerInfo }
|
||||
) {
|
||||
//Reset dependent state when changing
|
||||
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
||||
|
|
@ -1681,9 +1798,14 @@ export const actions = {
|
|||
}
|
||||
}
|
||||
|
||||
if (registrationInfo) {
|
||||
context.commit(storeMutations.UPDATE_VEHICLE, vehicleInfo);
|
||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE, vehicleInfo);
|
||||
|
||||
// only update name information if there isn't a value in state already
|
||||
if (!context.getters.order.customer.firstName && customerInfo) {
|
||||
context.commit(storeMutations.UPDATE_CUSTOMER_DETAILS, {
|
||||
firstName: customerInfo.firstName,
|
||||
lastName: customerInfo.lastName,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -1857,7 +1979,7 @@ export const actions = {
|
|||
// Price order actions
|
||||
async priceOrderItemsAndSaveServerData(
|
||||
context,
|
||||
{ availableLineItems, serviceZipCode, serviceZipCodeCtu }
|
||||
{ payload: { availableLineItems, serviceZipCode, serviceZipCodeCtu }, pageNameToLog }
|
||||
) {
|
||||
const zipCodeToUse = serviceZipCode
|
||||
? serviceZipCode
|
||||
|
|
@ -1897,6 +2019,8 @@ export const actions = {
|
|||
const response = await globalMethods.callHttpClient({
|
||||
method: endpoints.PriceOrderItems.method,
|
||||
endpoint: `${endpoints.PriceOrderItems.url}?${queryString}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
context.commit(storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA, response.data.serverData);
|
||||
|
|
@ -2175,7 +2299,7 @@ function providersEqual(providerA, providerB) {
|
|||
// check to see if we have an appointment date on the order object.
|
||||
// if so, make sure it's not in the past. if in the past, clear schedule info in store.
|
||||
// if date not in past, then call schedule service to verify appointment is still available.
|
||||
async function resetScheduleIfUnavailable(context, order) {
|
||||
async function resetScheduleIfUnavailable(context, order, pageNameToLog) {
|
||||
if (!order.schedule?.date) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -2214,8 +2338,11 @@ async function resetScheduleIfUnavailable(context, order) {
|
|||
newTimeSlotsResponse = await context.dispatch(
|
||||
storeActions.GET_MOBILE_TIME_SLOTS,
|
||||
{
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
payload: {
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
},
|
||||
pageNameToLog: pageNameToLog,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
@ -2257,10 +2384,13 @@ async function resetScheduleIfUnavailable(context, order) {
|
|||
newTimeSlotsResponse = await context.dispatch(
|
||||
storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: order.serviceLocation.appointmentType,
|
||||
providerNumber: order.serviceLocation.provider.providerNumber,
|
||||
payload: {
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: order.serviceLocation.appointmentType,
|
||||
providerNumber: order.serviceLocation.provider.providerNumber,
|
||||
},
|
||||
pageNameToLog: pageNameToLog,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleYears(context);
|
||||
const response = await actions.getVehicleYears(context, { pageNameToLog: "test" });
|
||||
|
||||
expect(response.data).toEqual([2023, 2022, 2021]);
|
||||
});
|
||||
|
|
@ -385,13 +385,15 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.lookupVehicleByYmms(
|
||||
context,
|
||||
"2019",
|
||||
"Acura",
|
||||
"ILX",
|
||||
"4 DOOR SEDAN"
|
||||
);
|
||||
const response = await actions.lookupVehicleByYmms(context, {
|
||||
payload: {
|
||||
year: "2019",
|
||||
make: "Acura",
|
||||
model: "ILX",
|
||||
style: "4 DOOR SEDAN",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C00000001" });
|
||||
});
|
||||
|
|
@ -406,7 +408,12 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.lookupVehicleByVin(context, "12345678901234567");
|
||||
const response = await actions.lookupVehicleByVin(context, {
|
||||
payload: {
|
||||
vin: "12345678901234567",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C0000001" });
|
||||
});
|
||||
|
|
@ -421,7 +428,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.lookupVinByPlate(context, "12345678901234567");
|
||||
const response = await actions.lookupVinByPlate(context, {
|
||||
payload: { licensePlate: "12345678901234567", licenseState: "OH" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C00000001" });
|
||||
});
|
||||
|
|
@ -438,7 +448,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const response = await actions.lookupVinByImage(context, image);
|
||||
const response = await actions.lookupVinByImage(context, {
|
||||
payload: image,
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual(["1C6JJTAG3NL134044"]);
|
||||
|
|
@ -458,7 +471,9 @@ describe("Actions", () => {
|
|||
// Act
|
||||
|
||||
// Assert
|
||||
await expect(actions.lookupVinByImage(context, image)).rejects.toEqual("An error occurred");
|
||||
await expect(
|
||||
actions.lookupVinByImage(context, { payload: image, pageNameToLog: "test" })
|
||||
).rejects.toEqual("An error occurred");
|
||||
});
|
||||
|
||||
it("getVehicleMakes action, should return makes list", async () => {
|
||||
|
|
@ -471,7 +486,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleMakes(context, "2019");
|
||||
const response = await actions.getVehicleMakes(context, {
|
||||
payload: { year: "2019" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual(["Acura", "Honda"]);
|
||||
});
|
||||
|
|
@ -486,7 +504,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleModels(context, "2019", "Acura");
|
||||
const response = await actions.getVehicleModels(context, {
|
||||
payload: { year: "2019", make: "Acura" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual(["ILX", "RDX"]);
|
||||
});
|
||||
|
|
@ -501,7 +522,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleStyles(context, "2019", "Acura", "ILX");
|
||||
const response = await actions.getVehicleStyles(context, {
|
||||
payload: { year: "2019", make: "Acura", model: "ILX" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ style: "4 DOOR SEDAN" });
|
||||
});
|
||||
|
|
@ -516,7 +540,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicle(context, "C00000000");
|
||||
const response = await actions.getVehicle(context, {
|
||||
payload: { year: "2019", make: "Acura", model: "IDX", style: "4-door sedan" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C00000000", category: "CAR" });
|
||||
});
|
||||
|
|
@ -530,7 +557,10 @@ describe("Actions", () => {
|
|||
return Promise.resolve({ data: ["Windshield", "DriversFrontDoor"] });
|
||||
});
|
||||
|
||||
const response = await actions.getDamageOptions(context, "C00000000");
|
||||
const response = await actions.getDamageOptions(context, {
|
||||
payload: { carId: "C00000000" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual(["Windshield", "DriversFrontDoor"]);
|
||||
|
|
@ -552,7 +582,10 @@ describe("Actions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
const response = await actions.validateZip(context, "43212");
|
||||
const response = await actions.validateZip(context, {
|
||||
payload: { zip: "43212" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual({
|
||||
|
|
@ -730,7 +763,7 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const response = await actions.saveSession(context);
|
||||
const response = await actions.saveSession(context, { pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual({ referralNumber: 123 });
|
||||
|
|
@ -750,7 +783,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
savedSessionId: "",
|
||||
payload: {
|
||||
savedSessionId: "",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -776,7 +812,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
savedSessionId: "",
|
||||
payload: {
|
||||
savedSessionId: "",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -801,7 +840,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
savedSessionId: "",
|
||||
payload: {
|
||||
savedSessionId: "",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -1492,9 +1534,18 @@ describe("Actions", () => {
|
|||
licensePlate: "ABC123",
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
firstName: "test",
|
||||
lastName: "test",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
context.getters = {
|
||||
...getters,
|
||||
order: getters.order(context),
|
||||
};
|
||||
|
||||
const commit = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
|
||||
|
|
@ -1538,9 +1589,18 @@ describe("Actions", () => {
|
|||
address: "123 Main St",
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
firstName: "test",
|
||||
lastName: "test",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
context.getters = {
|
||||
...getters,
|
||||
order: getters.order(context),
|
||||
};
|
||||
|
||||
const commit = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
|
||||
|
|
@ -1552,7 +1612,7 @@ describe("Actions", () => {
|
|||
isCarIdDifferent: true,
|
||||
isSelectedGlassAvailableForVehicle: false,
|
||||
vehicleInfo: { carId: "C010101", vin: "XXXXX" },
|
||||
registrationInfo: { firstName: "abc", lastName: "123" },
|
||||
customerInfo: { firstName: "abc", lastName: "123" },
|
||||
serviceLocationInfo: { state: "CO" },
|
||||
customerEmail: "test@safelite.com",
|
||||
};
|
||||
|
|
@ -1570,7 +1630,6 @@ describe("Actions", () => {
|
|||
);
|
||||
|
||||
expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE, payload.vehicleInfo);
|
||||
expect(commit).toBeCalledWith(storeMutations.UPDATE_REGISTRATION, payload.registrationInfo);
|
||||
});
|
||||
|
||||
it("saveVehicle, should save vehicle info", () => {
|
||||
|
|
@ -1701,7 +1760,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
await actions.runExperimentsForTrigger(context, {
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
payload: {
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -1735,7 +1797,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
await actions.runExperimentsForTrigger(context, {
|
||||
triggerEvent: "NotSiteEntry",
|
||||
payload: {
|
||||
triggerEvent: "NotSiteEntry",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -5,18 +5,16 @@ body {
|
|||
background-color: #fff;
|
||||
color: #4d5151;
|
||||
.container-fluid {
|
||||
max-width: 576px; //Remove once desktop app is complete
|
||||
&.container-shadow {
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.15); //Use instead of Bootstrap's helper
|
||||
max-width: 1400px;
|
||||
padding: 0 1.5rem;
|
||||
.prevent-squish {
|
||||
overflow-x: unset;
|
||||
}
|
||||
&.make-tall {
|
||||
&.page-container-grouped-styles {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.prevent-squish {
|
||||
overflow-x: unset;
|
||||
}
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
|
|
@ -43,18 +41,6 @@ body {
|
|||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-container-grouped-styles {
|
||||
@extend .container-fluid, .shadow, .rounded-3, .p-2, .position-relative, .make-tall, .px-5;
|
||||
}
|
||||
|
||||
//Footer modal backdrop adjustments for positioning
|
||||
.modal-backdrop {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: 576px;
|
||||
height: calc(100% - 72px);
|
||||
}
|
||||
}
|
||||
//Disable scroll of main container when modal is open
|
||||
.modal-open {
|
||||
|
|
|
|||
|
|
@ -170,15 +170,6 @@ $spacers: (
|
|||
//Enable negative spacing (does NOT work on padding)
|
||||
$enable-negative-margins: true;
|
||||
|
||||
//Grid breakpoints
|
||||
$grid-breakpoints: (
|
||||
xs: 0,
|
||||
sm: 576px,
|
||||
md: 838px,
|
||||
lg: 1074px,
|
||||
xl: 1416px,
|
||||
);
|
||||
|
||||
//Shadow
|
||||
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15);
|
||||
$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075);
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ export default {
|
|||
|
||||
img {
|
||||
// svg's should be constructed on the same canvas size/viewbox to ensure they occupy the same space in the DOM. This will allow easy/proper alignment of elements. See exisitng svg's for examples.
|
||||
height: auto;
|
||||
width: 6.5rem;
|
||||
height: 2rem;
|
||||
width: auto;
|
||||
margin-bottom: 2.2rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ export default {
|
|||
outline: none;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
|
|
|
|||
Loading…
Reference in a new issue