Merge branch 'develop' into bug/chloeherd/analytics-mismatch

This commit is contained in:
Chloe Herd 2023-09-20 09:06:27 -04:00
commit 5c38498a23
36 changed files with 1157 additions and 988 deletions

View file

@ -2,6 +2,7 @@ const queryStrings = {
FMG_PAGE: "fmgPage",
START_TYPE: "start_type",
ZIP_CODE: "zipcode",
PROMO: "promo",
};
export { queryStrings };

View file

@ -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":

View file

@ -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 {

View file

@ -23,8 +23,8 @@
:answerKey="questionsDatum.answerKey"
:validationRules="validationRules" />
</div>
<funnel-footer
ref="funnelFooter"
<navbar
ref="navbar"
cmsWidgetName="FunnelFooterWidget"
:isForwardActionDisabled="!isMetaValid"
@back-clicked="handleBackButtonAction"
@ -40,7 +40,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 +88,7 @@ export default {
alert,
questionChain,
funnelSubHeader,
funnelFooter,
navbar,
loadingModal,
},
};

View file

@ -1,10 +1,10 @@
import { mount } from "@vue/test-utils";
import funnelFooter from "./funnel-footer";
import navbar from "./nav-bar";
describe("funnel-footer.vue", () => {
describe("nav-bar.vue", () => {
test("Should emit ForwardClicked on button click", async () => {
// Act
const wrapper = mount(funnelFooter, {
const wrapper = mount(navbar, {
mixins: [mockMixin],
});
wrapper.vm.buttonClick();
@ -14,7 +14,7 @@ describe("funnel-footer.vue", () => {
test("Should emit BackClicked on link click", async () => {
// Act
const wrapper = mount(funnelFooter, {
const wrapper = mount(navbar, {
mixins: [mockMixin],
});
wrapper.vm.linkClick();
@ -24,7 +24,7 @@ describe("funnel-footer.vue", () => {
test("Should change button text when update button text is called", async () => {
// Act
const wrapper = mount(funnelFooter, {
const wrapper = mount(navbar, {
mixins: [mockMixin],
});
wrapper.vm.updateButtonText("newText");
@ -35,7 +35,7 @@ describe("funnel-footer.vue", () => {
test("should run removeLoader fn on buttonMain and return false for onkeydown fn", async () => {
// Arrange
const wrapper = mount(funnelFooter, {
const wrapper = mount(navbar, {
mixins: [mockMixin],
});

View file

@ -1,6 +1,6 @@
<template>
<div class="row"></div>
<footer class="footer container-fluid g-5 my-5 px-0" id="infoBox">
<div class="nav-bar container-fluid g-5 my-5 px-0" id="infoBox">
<div class="row d-flex flex-row-reverse align-items-center vw-100">
<div class="col button-col d-flex" id="stacked">
<buttonMain
@ -13,7 +13,7 @@
:isDisabled="isForwardActionDisabled"
@click-event="buttonClick"
data-bs-target="#footerModal"
data-test-id="funnel-footer-main-button"
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">
@ -27,7 +27,7 @@
data-bs-dismiss="modal" />
</div>
</div>
</footer>
</div>
</template>
<script>
@ -35,7 +35,7 @@ import textLink from "@/ux-components/text-link/text-link";
import buttonMain from "@/ux-components/button-main/button-main";
export default {
name: "funnelFooter",
name: "navbar",
emits: ["BackClicked", "ForwardClicked"], // <--- should remove oodles of warnings in dev tools
props: {
isForwardActionDisabled: Boolean,
@ -90,7 +90,7 @@ export default {
</script>
<style lang="scss" scoped>
.footer {
.nav-bar {
overflow: visible;
display: flex;
a {

View 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);
}

View file

@ -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,7 +45,7 @@ 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, loadingModal }) {
// Create the order (or save existing order) when navigating to Heritage Funnel.
if (shouldSaveSession) {
await saveSession({ shouldAwaitSaveSessionQueue: true });
@ -59,21 +55,31 @@ export async function navigateToHeritageFunnel({ shouldSaveSession = true, loadi
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")
);
}

View file

@ -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();

View file

@ -727,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 };
}

View file

@ -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">
<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";
@ -250,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;
@ -281,10 +298,10 @@ export default {
);
// 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
@ -304,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
@ -312,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
@ -430,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;
@ -455,7 +472,7 @@ export default {
},
components: {
funnelHeader,
funnelFooter,
navbar,
vehicleBanner,
funnelSubHeader,
customerQuestions,

View file

@ -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 };

View file

@ -27,9 +27,9 @@
class="mb-3"
marginTopSizeOverride="3" />
</div>
<funnelFooter
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="funnelFooter"
ref="navbar"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
@ -40,7 +40,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";
@ -169,7 +169,7 @@ export default {
},
"address-vehicles"
).catch(() => {
this.$refs.funnelFooter.removeLoader();
this.$refs.navbar.removeLoader();
});
if (!vinLookup) {
@ -215,11 +215,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")
);
}
@ -234,7 +234,7 @@ export default {
vehicleBanner,
funnelSubHeader,
alert,
funnelFooter,
navbar,
addressVehiclesQuestion,
textBlock,
},

View file

@ -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,

View file

@ -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 });

View file

@ -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">
<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";
@ -239,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;
@ -253,7 +258,10 @@ export default {
const vehicleChangedDuringPolicyLookupInHeritage =
payment.isInsurance && payment.insuranceCoverage.coverageStatus;
if (vehicleChangedDuringPolicyLookupInHeritage) {
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
navigateToHeritageFunnel({
shouldSaveSession: true,
loadingModal: this.$refs.loadingModal,
});
} else if (this.$store.getters.order.referralNumber?.length === 6) {
await this.navigateForwardWithSingleCarMatch();
} else if (this.isRepair) {
@ -346,7 +354,7 @@ export default {
funnelHeader,
vehicleBanner,
funnelSubHeader,
funnelFooter,
navbar,
buttonQuestion,
Form,
alert,
@ -361,6 +369,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;

View file

@ -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();
});
});
@ -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 };
}

View file

@ -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">
<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";
@ -235,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) {
@ -246,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;
@ -270,10 +271,10 @@ export default {
);
// 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
@ -281,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
@ -374,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,

View file

@ -6,7 +6,7 @@
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" leftAlignHeader />
<hr class="mt-0" />
<div class="fade-on-route-transition sub-container make-tall">
<funnel-footer
<navbar
cmsWidgetName="FunnelFooterWidget"
:isForwardActionDisabled="!meta.valid"
:isBackButtonHidden="shouldHideBackButton"
@ -20,7 +20,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 { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -63,7 +63,7 @@ export default {
},
components: {
funnelHeader,
funnelFooter,
navbar,
funnelSubHeader,
Form,
},

View file

@ -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">
<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";
@ -121,6 +132,12 @@ export default {
false
);
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SUPPORTING_ITEMS,
resultMap.supportingItems,
false
);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
@ -213,17 +230,14 @@ 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,
loadingModal: this.$refs.loadingModal,
});
} else {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_CASH,
@ -234,7 +248,7 @@ export default {
},
components: {
funnelHeader,
funnelFooter,
navbar,
vehicleBanner,
funnelSubHeader,
Form,

View file

@ -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,

View file

@ -623,7 +623,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 };

View file

@ -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">
<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";
@ -352,23 +364,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 +389,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 +495,7 @@ export default {
},
components: {
funnelHeader,
funnelFooter,
navbar,
funnelSubHeader,
Form,
loadingModal,

View file

@ -1,87 +1,106 @@
<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">
<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" />
<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" />
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</Form>
</template>
@ -95,7 +114,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 +135,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 (
@ -375,6 +396,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 +459,7 @@ export default {
false
);
this.updateAndSaveSupportingItems();
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
openModalAction(modalName) {
@ -416,7 +475,7 @@ export default {
appointmentTypeQuestion,
mobileLocationModalQuestions,
funnelHeader,
funnelFooter,
navbar,
funnelSubHeader,
Form,
loadingModal,

View file

@ -1,56 +1,76 @@
<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">
<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
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 +79,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";
@ -512,7 +532,7 @@ export default {
components: {
funnelHeader,
funnelFooter,
navbar,
vehicleBanner,
funnelSubHeader,
sideDoorOptions,

View file

@ -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();

View file

@ -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">
<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>

View file

@ -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">&nbsp;</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";
@ -413,7 +408,7 @@ export default {
components: {
funnelHeader,
funnelFooter,
navbar,
funnelSubHeader,
vehicleBanner,
Form,
@ -423,10 +418,6 @@ export default {
</script>
<style lang="scss">
.select-car-form {
margin-left: 0.75rem;
margin-right: 0.75rem;
}
.vehicle-footer {
margin-bottom: 0;
}

View file

@ -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" });

View file

@ -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">
<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";
@ -279,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;
@ -296,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.
@ -317,10 +311,10 @@ export default {
);
// 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
@ -382,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) {
@ -494,7 +488,7 @@ export default {
vin() {
this.displayVinNotFoundAlert = false;
this.displayVinScanFailedAlert = false;
this.$refs.funnelFooter.updateButtonText(
this.$refs.navbar.updateButtonText(
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
);
},
@ -504,7 +498,7 @@ export default {
},
components: {
funnelHeader,
funnelFooter,
navbar,
vehicleBanner,
funnelSubHeader,
textboxQuestion,

View file

@ -116,11 +116,11 @@ export default {
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
},
scrollToPageTop() {
const container = document.getElementsByClassName("page-container-grouped-styles")[0];
const container = document.getElementsByClassName("container-fluid")[0];
container.scrollTo({ top: 0, left: 0, behavior: "smooth" });
},
scrollToPageBottom() {
const container = document.getElementsByClassName("page-container-grouped-styles")[0];
const container = document.getElementsByClassName("container-fluid")[0];
container.scrollTo({ top: container.scrollHeight, left: 0, behavior: "smooth" });
},
},

View file

@ -447,9 +447,15 @@ export default {
const payment = store.getters.payment;
if (store.getters.order.referralNumber?.length === 6) {
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
navigateToHeritageFunnel({
shouldSaveSession: true,
loadingModal: self.$refs.loadingModal,
});
} else if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
navigateToHeritageFunnel({
shouldSaveSession: true,
loadingModal: self.$refs.loadingModal,
});
} else {
self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,

View file

@ -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
@ -74,17 +75,7 @@ const routes = [
: null
);
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;
@ -284,14 +275,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 +289,10 @@ async function navigate(
queryStringsObject[queryStrings.ZIP_CODE] = zip;
}
if (promo) {
queryStringsObject[queryStrings.PROMO] = promo;
}
router.push({
name: "root",
query: Object.assign(optionalQuery, queryStringsObject),

View file

@ -22,6 +22,7 @@ import {
getDisplayTextForDurationLength,
} from "@/layouts/schedule/helpers/schedule-helper";
import { getDateDifferenceInDays } from "@/helpers/date-helper";
// Export State
const getDefaultState = () => {
return {
@ -634,8 +635,24 @@ 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
@ -1362,7 +1379,12 @@ export const actions = {
endpoint: endpoints.GetShopTimeSlots.url,
payload: payload,
additionalSuccessEventDataHandler: (response) =>
provisionalTriggersToString(response.data.provisionalTriggers),
getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers,
order.serviceLocation.zipCode,
response.data.days?.[0]?.date,
shopAppointmentType
),
});
},
@ -1419,7 +1441,11 @@ export const actions = {
endpoint: endpoints.GetMobileTimeSlots.url,
payload: payload,
additionalSuccessEventDataHandler: (response) =>
provisionalTriggersToString(response.data.provisionalTriggers),
getTimeSlotsAdditionalEventData(
response.data.provisionalTriggers,
order.serviceLocation.zipCode,
response.data.days?.[0]?.date
),
});
},

View file

@ -5,15 +5,7 @@ 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
}
&.make-tall {
height: 100vh;
display: flex;
flex-direction: column;
}
max-width: 1400px;
.prevent-squish {
overflow-x: unset;
}
@ -44,10 +36,6 @@ body {
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%;

View file

@ -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);