WIP ISS desktop updates.

This commit is contained in:
Bryan Mauger 2024-04-12 10:52:01 -04:00
parent 567657b3b4
commit fa3c7711f6
14 changed files with 1349 additions and 1206 deletions

View file

@ -119,25 +119,17 @@ export default {
};
</script>
<style lang="scss" scoped>
<style lang="scss">
.modal {
top: auto;
bottom: 0;
:deep(h5) {
color: $black;
}
:deep(p) {
strong {
color: $black;
}
}
h5,
strong,
.subheader-text {
color: $black;
}
.modal-header {
padding: 1rem 0 0.5rem 0;
border-bottom: none;
.btn-close {
position: absolute;
@ -145,11 +137,14 @@ export default {
top: 1rem;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
opacity: 1;
margin: 0.3rem -0.5rem -0.5rem auto;
}
.modal-title {
font-weight: 500;
color: $black;
font-size: 1.25rem;
line-height: 32px;
color: rgb(0, 0, 0);
}
}
.modal-footer {
@ -157,17 +152,31 @@ export default {
width: 100%;
bottom: 0;
z-index: 5;
background-color: $gray-100;
}
&.modal-component {
.modal-dialog {
max-width: 576px;
max-width: 767px;
margin: 0 auto;
@include media-breakpoint-up(md) {
width: 376px;
position: absolute;
top: 0;
right: 0;
transform: translate(100%, 0);
}
.modal-content {
margin: 0 auto;
margin: 1.5rem auto 0 auto;
box-shadow: 0px 16px 48px -16px rgba(0, 0, 0, 0.25);
border-radius: 1.5rem 1.5rem 0 0;
@include media-breakpoint-up(md) {
box-shadow: none;
border-radius: 0;
margin: 0 auto;
}
.modal-body {
.col {
max-width: 100%;
}
.modal-sub-body {
color: $gray-600;
}
@ -179,22 +188,36 @@ export default {
margin-bottom: 0;
}
}
@include media-breakpoint-up(md) {
overflow: auto;
flex: none;
}
}
}
&.modal-dialog-centered {
align-items: flex-end;
min-height: 100%;
@include media-breakpoint-up(md) {
align-items: inherit;
}
}
}
.modal-footer {
border-top: none;
background-color: $gray-100;
box-shadow: 0px -1px 0px rgba(179, 180, 181, 0.3);
button {
margin: 0;
}
}
}
&.show .modal-dialog {
transform: translateY(0);
}
@include media-breakpoint-up(md) {
&.fade {
transition: opacity 0.25s linear;
}
}
}
body {
.modal-backdrop {

View file

@ -1,222 +1,234 @@
<template>
<Form
ref="contact-details-form"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader
ref="siteHeader"
:cmsWidgetName="widget.siteHeader" />
<div class="container-fluid px-6">
<siteSubHeader
id="sub-header"
ref="siteSubHeader"
class="mt-5"
:cmsWidgetName="widget.siteSubHeader" />
<textboxQuestion
ref="firstNameQuestion"
v-model="firstName"
class="mt-6"
inputId="firstName"
:cmsWidgetName="widget.firstNameQuestion"
isRequired
:validationRules="rules.firstName" />
<textboxQuestion
ref="lastNameQuestion"
v-model="lastName"
class="mt-5"
inputId="lastName"
:cmsWidgetName="widget.lastNameQuestion"
isRequired
:validationRules="rules.lastName" />
<textboxQuestion
ref="emailQuestion"
v-model="emailAddress"
class="mt-5"
inputId="emailAddress"
:cmsWidgetName="widget.emailQuestion"
isRequired
:validationRules="rules.emailAddress" />
<textboxQuestion
ref="phoneNumberQuestion"
v-model="phoneNumber"
class="mt-5"
inputId="phoneNumber"
:cmsWidgetName="widget.phoneNumberQuestion"
isRequired
:mask="phoneMask"
:validationRules="rules.phoneNumber" />
<checkbox
ref="requestTextUpdatesCheckbox"
v-model="requestTextUpdates"
:isChecked="requestTextUpdates"
class="mt-3"
checkboxName="requestTextUpdates"
buttonID="requestTextUpdates"
:checkboxLabel="requestTextUpdatesCheckboxText" />
<textareaQuestion
ref="notesQuestion"
v-model="notesForTechnician"
class="mt-5"
inputId="technicianNotes"
:isDisabled="false"
:isRequired="false"
:cmsWidgetName="widget.notesQuestion"
maxLength="250"
:inputRows="4" />
<p
ref="disclaimerText"
class="caption dark-gray mt-6">
{{ textUpdateDisclaimerText }} I also agree to Safelite's
<textLink
ref="privacyPolicyLink"
class="normal-line-height"
linkType="text"
text="Privacy Policy"
href="//www.safelite.com/privacy-center" />
and
<textLink
ref="termsOfUseLink"
class="normal-line-height"
linkType="text"
text="Terms of Use"
href="//www.safelite.com/terms-of-use" />.
</p>
<siteFooter
ref="siteFooter"
class="my-5"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@forwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
</div>
<Form
ref="contact-details-form"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit"
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 px-0 px-md-2">
<siteHeader ref="siteHeader" :cmsWidgetName="widget.siteHeader" />
</div>
</Form>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
id="sub-header"
ref="siteSubHeader"
class="mt-5"
:cmsWidgetName="widget.siteSubHeader"
/>
<textboxQuestion
ref="firstNameQuestion"
v-model="firstName"
class="mt-6"
inputId="firstName"
:cmsWidgetName="widget.firstNameQuestion"
isRequired
:validationRules="rules.firstName"
/>
<textboxQuestion
ref="lastNameQuestion"
v-model="lastName"
class="mt-5"
inputId="lastName"
:cmsWidgetName="widget.lastNameQuestion"
isRequired
:validationRules="rules.lastName"
/>
<textboxQuestion
ref="emailQuestion"
v-model="emailAddress"
class="mt-5"
inputId="emailAddress"
:cmsWidgetName="widget.emailQuestion"
isRequired
:validationRules="rules.emailAddress"
/>
<textboxQuestion
ref="phoneNumberQuestion"
v-model="phoneNumber"
class="mt-5"
inputId="phoneNumber"
:cmsWidgetName="widget.phoneNumberQuestion"
isRequired
:mask="phoneMask"
:validationRules="rules.phoneNumber"
/>
<checkbox
ref="requestTextUpdatesCheckbox"
v-model="requestTextUpdates"
:isChecked="requestTextUpdates"
class="mt-3"
checkboxName="requestTextUpdates"
buttonID="requestTextUpdates"
:checkboxLabel="requestTextUpdatesCheckboxText"
/>
<textareaQuestion
ref="notesQuestion"
v-model="notesForTechnician"
class="mt-5"
inputId="technicianNotes"
:isDisabled="false"
:isRequired="false"
:cmsWidgetName="widget.notesQuestion"
maxLength="250"
:inputRows="4"
/>
<p ref="disclaimerText" class="caption dark-gray mt-6">
{{ textUpdateDisclaimerText }} I also agree to Safelite's
<textLink
ref="privacyPolicyLink"
class="normal-line-height"
linkType="text"
text="Privacy Policy"
href="//www.safelite.com/privacy-center"
/>
and
<textLink
ref="termsOfUseLink"
class="normal-line-height"
linkType="text"
text="Terms of Use"
href="//www.safelite.com/terms-of-use"
/>.
</p>
<siteFooter
ref="siteFooter"
class="my-5"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@forwardClicked="forwardButtonAction"
@backClicked="navigateBack"
/>
</div>
</div>
</div>
</Form>
</template>
<script>
// Components
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
import checkbox from '@/ux-components/checkbox/checkbox.vue';
import textareaQuestion from '@/digital-components/textarea-question/textarea-question.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
import siteHeader from "@/iss-components/site-header/site-header.vue";
import siteFooter from "@/iss-components/site-footer/site-footer.vue";
import siteSubHeader from "@/iss-components/site-sub-header/site-sub-header.vue";
import textboxQuestion from "@/digital-components/textbox-question/textbox-question.vue";
import checkbox from "@/ux-components/checkbox/checkbox.vue";
import textareaQuestion from "@/digital-components/textarea-question/textarea-question.vue";
import textLink from "@/ux-components/text-link/text-link.vue";
// Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store/index.js';
import globalRules from '@/constants/global-rules.js';
import MaskaFormattedMasks from '@/constants/maska-masks';
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper.js";
import { Form } from "vee-validate";
import BaseFormMixin from "@/mixins/base-form-mixin.js";
import { useMainStore } from "@/store/index.js";
import globalRules from "@/constants/global-rules.js";
import MaskaFormattedMasks from "@/constants/maska-masks";
export default {
name: 'contact-details',
components: {
siteHeader,
siteSubHeader,
textboxQuestion,
checkbox,
textareaQuestion,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
textLink
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const cmsContent = await fetchCmsContentForPage(to.query.issPage);
name: "contact-details",
components: {
siteHeader,
siteSubHeader,
textboxQuestion,
checkbox,
textareaQuestion,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
textLink,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const cmsContent = await fetchCmsContentForPage(to.query.issPage);
next((vm) => {
vm.setCmsContent(cmsContent);
});
next((vm) => {
vm.setCmsContent(cmsContent);
});
},
data() {
const {
firstName,
lastName,
emailAddress,
phoneNumber,
requestTextUpdates,
notesForTechnician,
} = useMainStore().contactInfo;
return {
firstName,
lastName,
emailAddress,
phoneNumber,
requestTextUpdates,
notesForTechnician,
widget: {
siteHeader: "SiteHeaderWidget",
siteSubHeader: "SiteSubHeaderWidget",
firstNameQuestion: "FirstNameQuestionWidget",
lastNameQuestion: "LastNameQuestionWidget",
emailQuestion: "EmailQuestionWidget",
phoneNumberQuestion: "PhoneNumberQuestionWidget",
requestTextUpdates: "TextContentWidget",
notesQuestion: "NotesQuestionWidget",
disclaimer: "TextUpdateDisclaimerWidget",
siteFooter: "SiteFooterWidget",
},
rules: {
firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED,
emailAddress: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
},
};
},
computed: {
/**
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
*/
requestTextUpdatesCheckboxText() {
return `${this.getCmsContent(this.widget.requestTextUpdates, "Text")}*`;
},
data() {
const { firstName,
lastName,
emailAddress,
phoneNumber,
requestTextUpdates,
notesForTechnician } = useMainStore().contactInfo;
return {
firstName,
lastName,
emailAddress,
phoneNumber,
requestTextUpdates,
notesForTechnician,
widget: {
siteHeader: 'SiteHeaderWidget',
siteSubHeader: 'SiteSubHeaderWidget',
firstNameQuestion: 'FirstNameQuestionWidget',
lastNameQuestion: 'LastNameQuestionWidget',
emailQuestion: 'EmailQuestionWidget',
phoneNumberQuestion: 'PhoneNumberQuestionWidget',
requestTextUpdates: 'TextContentWidget',
notesQuestion: 'NotesQuestionWidget',
disclaimer: 'TextUpdateDisclaimerWidget',
siteFooter: 'SiteFooterWidget'
},
rules: {
firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED,
emailAddress: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`
}
};
/**
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
*/
textUpdateDisclaimerText() {
return `*${this.getCmsContent(this.widget.disclaimer, "Text")}`;
},
computed: {
/**
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
*/
requestTextUpdatesCheckboxText() {
return `${this.getCmsContent(this.widget.requestTextUpdates, 'Text')}*`;
},
/**
* @returns {string} Returns the CMS text associated with the "get text updates" checkbox.
*/
textUpdateDisclaimerText() {
return `*${this.getCmsContent(this.widget.disclaimer, 'Text')}`;
},
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
},
methods:
{
/**
* @summary Steps to perform when forward button clicked.
*/
forwardButtonAction() {
const contactInfo = {
firstName: this.firstName,
lastName: this.lastName,
emailAddress: this.emailAddress,
phoneNumber: this.phoneNumber,
requestTextUpdates: this.requestTextUpdates,
notesForTechnician: this.notesForTechnician
};
useMainStore().updateContactInfo(contactInfo);
const scenario = useMainStore().order.serviceLocation.IsSafeliteProvider === false
? this.navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP
: this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP;
this.$router.navigate(scenario, this.$route);
}
}
},
methods: {
/**
* @summary Steps to perform when forward button clicked.
*/
forwardButtonAction() {
const contactInfo = {
firstName: this.firstName,
lastName: this.lastName,
emailAddress: this.emailAddress,
phoneNumber: this.phoneNumber,
requestTextUpdates: this.requestTextUpdates,
notesForTechnician: this.notesForTechnician,
};
useMainStore().updateContactInfo(contactInfo);
const scenario =
useMainStore().order.serviceLocation.IsSafeliteProvider === false
? this.navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP
: this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP;
this.$router.navigate(scenario, this.$route);
},
},
};
</script>
<style lang="scss" scoped>
.dark-gray {
color: $darker-gray;
color: $darker-gray;
}
.normal-line-height {
line-height: normal;
line-height: normal;
}
</style>

View file

@ -7,12 +7,12 @@
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<loadingModal ref="loadingModal" :textSlides="loadingText" />
<siteHeader ref="siteHeader" cmsWidgetName="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="row justify-content-center pt-5">
<div class="col-md-6 col-xl-4">
<h5
ref="siteSubHeader"

View file

@ -7,7 +7,7 @@
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<siteHeader ref="siteHeader" :cmsWidgetName="widget.siteHeader" />
</div>
</div>

View file

@ -7,7 +7,7 @@
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<siteHeader class="mb-2 header" cmsWidgetName="SiteHeaderWidget" />
</div>
</div>

View file

@ -2,7 +2,7 @@
<Form ref="theForm" @submit="onSubmit" @invalidSubmit="onInvalidSubmit">
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<siteHeader class="mb-2 header" cmsWidgetName="SiteHeaderWidget" />
</div>
</div>

View file

@ -1,434 +1,498 @@
<template>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader
cmsWidgetName="ScheduleSubHeaderWidget"
subTextClasses="text-center small sub-text"
class="mt-4" />
<template v-if="ChangeShopLink.length">
<textBlock
cmsWidgetName="ChangeShopLink"
justifyText="center"
class="mb-3 text-link-small"
:marginTopSizeOverride="1" />
</template>
<div class="main-content-container">
<locationAlerts
ref="locationAlerts"
cmsWidgetPrefix="LocationAlert-" />
<datePicker
ref="datePicker"
v-model="selectedDate"
customComponentId="dateQuestion"
selectableDatesSetting="custom"
class="text-link-small"
:customSelectableDatesCallback="getAvailableDatesMethod"
validationRules="date-required"
@dateClicked="openInshopTimeSlotsModal" />
<timeSlotModalQuestion
ref="timeSlotModalQuestion"
v-model="selectedTimeSlotInfo"
customComponentId="timeSlotModalQuestion"
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"
validationRules="time-slot-selection-required"
@timeSlotModalClosed="timeSlotModalClosed" />
<siteFooter
ref="navbar"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit"
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 px-0 px-md-2">
<siteHeader class="mb-2 header" cmsWidgetName="SiteHeaderWidget" />
</div>
</Form>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
cmsWidgetName="ScheduleSubHeaderWidget"
subTextClasses="text-center small sub-text"
class="mt-4"
/>
<template v-if="ChangeShopLink.length">
<textBlock
cmsWidgetName="ChangeShopLink"
justifyText="center"
class="mb-3 text-link-small"
:marginTopSizeOverride="1"
/>
</template>
<div class="main-content-container">
<locationAlerts
ref="locationAlerts"
cmsWidgetPrefix="LocationAlert-"
/>
<datePicker
ref="datePicker"
v-model="selectedDate"
customComponentId="dateQuestion"
selectableDatesSetting="custom"
class="text-link-small"
:customSelectableDatesCallback="getAvailableDatesMethod"
validationRules="date-required"
@dateClicked="openInshopTimeSlotsModal"
/>
<timeSlotModalQuestion
ref="timeSlotModalQuestion"
v-model="selectedTimeSlotInfo"
customComponentId="timeSlotModalQuestion"
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
"
validationRules="time-slot-selection-required"
@timeSlotModalClosed="timeSlotModalClosed"
/>
<siteFooter
ref="navbar"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction"
/>
</div>
</div>
</div>
</div>
</Form>
</template>
<script>
// Components
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import locationAlerts from '@/layouts/schedule-page/location-alerts/location-alerts.vue';
import datePicker from '@/digital-components/date-picker/date-picker.vue';
import timeSlotModalQuestion from '@/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
import siteHeader from "@/iss-components/site-header/site-header.vue";
import siteSubHeader from "@/iss-components/site-sub-header/site-sub-header.vue";
import locationAlerts from "@/layouts/schedule-page/location-alerts/location-alerts.vue";
import datePicker from "@/digital-components/date-picker/date-picker.vue";
import timeSlotModalQuestion from "@/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue";
import siteFooter from "@/iss-components/site-footer/site-footer.vue";
import textBlock from "@/digital-components/text-block/text-block.vue";
// Supporting files
import {
AppointmentTypeStrings,
GET_MOBILE_TIME_SLOTS,
GET_SHOP_TIME_SLOTS,
PREMIUM_FEE_PART_TYPE
} from '@/constants/schedule-constants.js';
import { fetchCmsContentForPage, splitCopyOnCMSPlaceHolder } from '@/helpers/cms-content-helper';
import { calcDaysBetweenDates, convertDateStringToDate, sumDateString } from '@/helpers/date-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form, defineRule } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import errorMessages from '@/constants/error-messages';
import { required } from '@/helpers/validation-rules';
import { useMainStore } from '@/store';
AppointmentTypeStrings,
GET_MOBILE_TIME_SLOTS,
GET_SHOP_TIME_SLOTS,
PREMIUM_FEE_PART_TYPE,
} from "@/constants/schedule-constants.js";
import {
fetchCmsContentForPage,
splitCopyOnCMSPlaceHolder,
} from "@/helpers/cms-content-helper";
import {
calcDaysBetweenDates,
convertDateStringToDate,
sumDateString,
} from "@/helpers/date-helper";
import settleAllPromises from "@/helpers/layout-helper";
import { Form, defineRule } from "vee-validate";
import BaseFormMixin from "@/mixins/base-form-mixin.js";
import errorMessages from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
import { useMainStore } from "@/store";
// DEFINE VALIDATION RULES
defineRule('date-required', required(errorMessages.DATE_REQUIRED));
defineRule('time-slot-selection-required', (value) => {
if (value?.timeSlot?.routeCode == null) {
return errorMessages.DATE_REQUIRED;
}
return true;
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
defineRule("time-slot-selection-required", (value) => {
if (value?.timeSlot?.routeCode == null) {
return errorMessages.DATE_REQUIRED;
}
return true;
});
// Define constants
const TIME_SLOTS_CALL_DAYS_LIMIT = 34; // needs to be 34 for API limits (35 does not consistently work)
const getAvailableDates = async (
startDateString,
endDateString,
appointmentType,
providerNumber
startDateString,
endDateString,
appointmentType,
providerNumber
) => {
const apiEndDateLimit = sumDateString(startDateString, TIME_SLOTS_CALL_DAYS_LIMIT);
const difference = calcDaysBetweenDates(startDateString, endDateString);
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
const storeActionConfigs = [];
const timeSlotsData = {};
timeSlotsData.days = [];
let apiStartDate = startDateString;
let apiEndDate = endDateString;
const apiEndDateLimit = sumDateString(
startDateString,
TIME_SLOTS_CALL_DAYS_LIMIT
);
const difference = calcDaysBetweenDates(startDateString, endDateString);
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
const storeActionConfigs = [];
const timeSlotsData = {};
timeSlotsData.days = [];
let apiStartDate = startDateString;
let apiEndDate = endDateString;
for (let i = 1; i <= apiCallsCount; i++) {
let storeActionConfig;
for (let i = 1; i <= apiCallsCount; i++) {
let storeActionConfig;
if (i > 1) {
apiStartDate = sumDateString(apiEndDate, 1);
apiEndDate = sumDateString(apiStartDate, TIME_SLOTS_CALL_DAYS_LIMIT);
if (i > 1) {
apiStartDate = sumDateString(apiEndDate, 1);
apiEndDate = sumDateString(apiStartDate, TIME_SLOTS_CALL_DAYS_LIMIT);
if (i === apiCallsCount) {
apiEndDate = endDateString;
}
} else if (apiEndDate > apiEndDateLimit) {
apiEndDate = apiEndDateLimit;
}
if (i === apiCallsCount) {
apiEndDate = endDateString;
}
} else if (apiEndDate > apiEndDateLimit) {
apiEndDate = apiEndDateLimit;
}
if (appointmentType === AppointmentTypeStrings.MOBILE
|| appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
storeActionConfig = {
storeAction: GET_MOBILE_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate
}
};
if (
appointmentType === AppointmentTypeStrings.MOBILE ||
appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
) {
storeActionConfig = {
storeAction: GET_MOBILE_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
},
};
} else {
storeActionConfig = {
storeAction: GET_SHOP_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
shopAppointmentType: appointmentType,
providerNumber,
},
};
}
if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig);
}
const timeSlotsResponsesData = {
days: [],
};
function compareDayStrings(a, b) {
if (a.date < b.date) return -1;
if (a.date > b.date) return 1;
return 0;
}
const makeParallelCalls = async () => {
await Promise.all(
storeActionConfigs.map(async (storeAction) => {
let timeSlotsResponse = null;
if (storeAction.storeAction === GET_SHOP_TIME_SLOTS) {
timeSlotsResponse = await useMainStore().getShopTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber
);
} else {
storeActionConfig = {
storeAction: GET_SHOP_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
shopAppointmentType: appointmentType,
providerNumber
}
};
timeSlotsResponse = await useMainStore().getMobileTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate
);
}
if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig);
}
const timeSlotsResponsesData = {
days: []
};
timeSlotsResponsesData.estimatedServiceMinutesMinimum =
timeSlotsResponse.data.estimatedServiceMinutesMinimum;
timeSlotsResponsesData.estimatedServiceMinutesMaximum =
timeSlotsResponse.data.estimatedServiceMinutesMaximum;
timeSlotsResponsesData.days = [
...timeSlotsResponsesData.days,
...timeSlotsResponse.data.days,
];
})
);
};
function compareDayStrings(a, b) {
if (a.date < b.date) return -1;
if (a.date > b.date) return 1;
return 0;
}
const makeParallelCalls = async () => {
await Promise.all(storeActionConfigs.map(async (storeAction) => {
let timeSlotsResponse = null;
if (storeAction.storeAction === GET_SHOP_TIME_SLOTS) {
timeSlotsResponse = await useMainStore().getShopTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber
);
} else {
timeSlotsResponse = await useMainStore().getMobileTimeSlots(storeAction.payload.startDate, storeAction.payload.endDate);
}
timeSlotsResponsesData.estimatedServiceMinutesMinimum = timeSlotsResponse.data.estimatedServiceMinutesMinimum;
timeSlotsResponsesData.estimatedServiceMinutesMaximum = timeSlotsResponse.data.estimatedServiceMinutesMaximum;
timeSlotsResponsesData.days = [
...timeSlotsResponsesData.days,
...timeSlotsResponse.data.days
];
}));
};
return makeParallelCalls().then(() => {
// sort days chronologically
timeSlotsResponsesData.days.sort(compareDayStrings);
return timeSlotsResponsesData;
});
return makeParallelCalls().then(() => {
// sort days chronologically
timeSlotsResponsesData.days.sort(compareDayStrings);
return timeSlotsResponsesData;
});
};
export default {
name: 'schedule-page',
components: {
siteHeader,
siteSubHeader,
locationAlerts,
datePicker,
timeSlotModalQuestion,
siteFooter,
textBlock,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
let preSelectedDate = await useMainStore().order.schedule.date;
if (!preSelectedDate || preSelectedDate.startTime === null) {
preSelectedDate = null;
}
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
selectableDatesSetting: 'custom',
initialViewRowsToShow: 5,
customSelectableDatesCallback: getAvailableDates,
preSelectedDate
});
const premiumFeePromise = useMainStore().getMobilePremiumFee();
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
if (result.data) {
return useMainStore().priceOrderItemsAndSaveServerData(result.data);
}
return result.data;
});
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
useMainStore().order.serviceLocation.zipCodeCtu,
useMainStore().order.serviceLocation.provider?.address?.zipCodeCtu
);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
},
{
resultKey: 'alertReasons',
promise: alertReasonsPromise
},
{
resultKey: 'datePickerInitialData',
promise: datePickerInitialDataPromise
},
{
resultKey: 'premiumFeeWithPrice',
promise: premiumFeeWithPricePromise
}
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.setData(resultMap.datePickerInitialData.initialShopTimeSlotsResponse, resultMap.premiumFeeWithPrice);
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {
selectedDate: this.getSelectedDate(),
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesData: [],
mobilePremiumAppointmentFee: null
};
},
computed: {
ChangeShopLinkText() {
return this.getCmsContent('ChangeShopLink', 'Text');
},
ChangeShopLink() {
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
},
appointmentType() {
return useMainStore().order.serviceLocation.appointmentType;
},
timeSlotsForSelectedDate() {
if (!this.selectedDate) {
return null;
}
return this.selectableDatesData.days?.find((selectableDate) => selectableDate.date === this.selectedDate);
},
supportingItems() {
return useMainStore().lineItems.supportingItems;
}
},
watch: {
selectedDate(newValue, oldValue) {
// Clear time slot selection if date selected changes
if (newValue !== oldValue) {
this.selectedTimeSlotInfo = {
timeSlot: {
date: null,
routeCode: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null
},
isPremiumAppointment: null
};
}
},
selectedTimeSlotInfo(newValue) {
this.updateFooterButtonText(newValue);
}
},
methods: {
splitCopyOnCMSPlaceHolder,
arePagePrerequisitesValid() {
const { serviceLocation } = useMainStore().order;
const serviceLocationPreReqs = serviceLocation.zipCode
&& serviceLocation.zipCodeCtu
&& serviceLocation.appointmentType
&& ((serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|| serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)
|| serviceLocation.provider.providerNumber);
const supportingItems = this.supportingItems !== null;
const damageInfo =
useMainStore().order.damage.isRepair
|| (useMainStore().order.lineItems?.glassParts != null
&& useMainStore().order.lineItems.glassParts.length > 0);
return serviceLocationPreReqs && supportingItems && damageInfo;
},
setData(initialShopTimeSlotsResponse, premiumFeeWithPriceResponse) {
this.selectableDatesData = initialShopTimeSlotsResponse;
this.mobilePremiumAppointmentFee = premiumFeeWithPriceResponse
? premiumFeeWithPriceResponse[0]
: null;
},
async getAvailableDatesMethod(startDate, endDate) {
const newShopTimeSlots = await getAvailableDates(
startDate,
endDate,
this.appointmentType,
this.mainStore.order.serviceLocation.provider.providerNumber
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days = this.selectableDatesData.days.concat(newShopTimeSlots.days);
return newShopTimeSlots;
},
getAvailableDates,
getServiceZipCtuCodeFromStore() {
return this.mainStore.order.serviceLocation.zipCodeCtu;
},
openInshopTimeSlotsModal() {
this.$refs.timeSlotModalQuestion.openModal();
},
getSelectedDate() {
return this.mainStore.order.schedule.date;
},
getSelectedTimeSlotInfo() {
const isPremiumAppointment =
!!(this.supportingItems?.filter((lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE) ?? [])
.length > 0;
const selectedTimeSlotInfo = {
timeSlot: this.mainStore.order.schedule,
isPremiumAppointment
};
return selectedTimeSlotInfo;
},
timeSlotModalClosed() {
// Clear the selectedDate if no timeSlot has been selected
if (this.selectedTimeSlotInfo.timeSlot.routeCode == null) {
this.selectedDate = null;
}
},
updateFooterButtonText(timeSlotInfo) {
let navbarButtonText;
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
navbarButtonText = 'Continue';
} else {
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(timeSlotInfo.timeSlot.date)}`;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(timeSlotInfo.timeSlot.startTime)}`;
} else if (
this.appointmentType === AppointmentTypeStrings.MOBILE
&& !timeSlotInfo.isPremiumAppointment
) {
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.startTime,
true
)} - ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.endTime,
true
)}`;
}
}
this.$refs.navbar.updateButtonText(navbarButtonText);
},
convertSelectedDateToShortMonthAndDay(selectedDate) {
// This conversion ensures we don't get get GMT induced date changes
const dateObject = convertDateStringToDate(selectedDate);
return dateObject.toLocaleDateString('en-us', { month: 'short', day: 'numeric' });
},
getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) {
// Expected input: "HH:MM"
let hours = parseInt(militaryTimeInput.split(':')[0], 10);
const minutes = militaryTimeInput.split(':')[1];
const meridianNotation = hours > 11 ? 'PM' : 'AM';
if (hours > 12) {
hours -= 12;
}
if (shouldTrimMinutesIfEmpty && minutes === '00') {
return `${hours} ${meridianNotation}`;
}
return `${hours}:${minutes} ${meridianNotation}`;
},
forwardButtonAction() {
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}
name: "schedule-page",
components: {
siteHeader,
siteSubHeader,
locationAlerts,
datePicker,
timeSlotModalQuestion,
siteFooter,
textBlock,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
let preSelectedDate = await useMainStore().order.schedule.date;
if (!preSelectedDate || preSelectedDate.startTime === null) {
preSelectedDate = null;
}
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const datePickerInitialDataPromise =
await datePicker.methods.loadInitialData({
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: getAvailableDates,
preSelectedDate,
});
const premiumFeePromise = useMainStore().getMobilePremiumFee();
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
if (result.data) {
return useMainStore().priceOrderItemsAndSaveServerData(result.data);
}
return result.data;
});
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
useMainStore().order.serviceLocation.zipCodeCtu,
useMainStore().order.serviceLocation.provider?.address?.zipCodeCtu
);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
{
resultKey: "alertReasons",
promise: alertReasonsPromise,
},
{
resultKey: "datePickerInitialData",
promise: datePickerInitialDataPromise,
},
{
resultKey: "premiumFeeWithPrice",
promise: premiumFeeWithPricePromise,
},
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.setData(
resultMap.datePickerInitialData.initialShopTimeSlotsResponse,
resultMap.premiumFeeWithPrice
);
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {
selectedDate: this.getSelectedDate(),
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
selectableDatesData: [],
mobilePremiumAppointmentFee: null,
};
},
computed: {
ChangeShopLinkText() {
return this.getCmsContent("ChangeShopLink", "Text");
},
ChangeShopLink() {
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
},
appointmentType() {
return useMainStore().order.serviceLocation.appointmentType;
},
timeSlotsForSelectedDate() {
if (!this.selectedDate) {
return null;
}
return this.selectableDatesData.days?.find(
(selectableDate) => selectableDate.date === this.selectedDate
);
},
supportingItems() {
return useMainStore().lineItems.supportingItems;
},
},
watch: {
selectedDate(newValue, oldValue) {
// Clear time slot selection if date selected changes
if (newValue !== oldValue) {
this.selectedTimeSlotInfo = {
timeSlot: {
date: null,
routeCode: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
}
},
selectedTimeSlotInfo(newValue) {
this.updateFooterButtonText(newValue);
},
},
methods: {
splitCopyOnCMSPlaceHolder,
arePagePrerequisitesValid() {
const { serviceLocation } = useMainStore().order;
const serviceLocationPreReqs =
serviceLocation.zipCode &&
serviceLocation.zipCodeCtu &&
serviceLocation.appointmentType &&
(serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE ||
serviceLocation.appointmentType ===
AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP ||
serviceLocation.provider.providerNumber);
const supportingItems = this.supportingItems !== null;
const damageInfo =
useMainStore().order.damage.isRepair ||
(useMainStore().order.lineItems?.glassParts != null &&
useMainStore().order.lineItems.glassParts.length > 0);
return serviceLocationPreReqs && supportingItems && damageInfo;
},
setData(initialShopTimeSlotsResponse, premiumFeeWithPriceResponse) {
this.selectableDatesData = initialShopTimeSlotsResponse;
this.mobilePremiumAppointmentFee = premiumFeeWithPriceResponse
? premiumFeeWithPriceResponse[0]
: null;
},
async getAvailableDatesMethod(startDate, endDate) {
const newShopTimeSlots = await getAvailableDates(
startDate,
endDate,
this.appointmentType,
this.mainStore.order.serviceLocation.provider.providerNumber
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days = this.selectableDatesData.days.concat(
newShopTimeSlots.days
);
return newShopTimeSlots;
},
getAvailableDates,
getServiceZipCtuCodeFromStore() {
return this.mainStore.order.serviceLocation.zipCodeCtu;
},
openInshopTimeSlotsModal() {
this.$refs.timeSlotModalQuestion.openModal();
},
getSelectedDate() {
return this.mainStore.order.schedule.date;
},
getSelectedTimeSlotInfo() {
const isPremiumAppointment =
!!(
this.supportingItems?.filter(
(lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE
) ?? []
).length > 0;
const selectedTimeSlotInfo = {
timeSlot: this.mainStore.order.schedule,
isPremiumAppointment,
};
return selectedTimeSlotInfo;
},
timeSlotModalClosed() {
// Clear the selectedDate if no timeSlot has been selected
if (this.selectedTimeSlotInfo.timeSlot.routeCode == null) {
this.selectedDate = null;
}
},
updateFooterButtonText(timeSlotInfo) {
let navbarButtonText;
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
navbarButtonText = "Continue";
} else {
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
timeSlotInfo.timeSlot.date
)}`;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.startTime
)}`;
} else if (
this.appointmentType === AppointmentTypeStrings.MOBILE &&
!timeSlotInfo.isPremiumAppointment
) {
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.startTime,
true
)} - ${this.getDisplayTextForMilitaryTime(
timeSlotInfo.timeSlot.endTime,
true
)}`;
}
}
this.$refs.navbar.updateButtonText(navbarButtonText);
},
convertSelectedDateToShortMonthAndDay(selectedDate) {
// This conversion ensures we don't get get GMT induced date changes
const dateObject = convertDateStringToDate(selectedDate);
return dateObject.toLocaleDateString("en-us", {
month: "short",
day: "numeric",
});
},
getDisplayTextForMilitaryTime(
militaryTimeInput,
shouldTrimMinutesIfEmpty = false
) {
// Expected input: "HH:MM"
let hours = parseInt(militaryTimeInput.split(":")[0], 10);
const minutes = militaryTimeInput.split(":")[1];
const meridianNotation = hours > 11 ? "PM" : "AM";
if (hours > 12) {
hours -= 12;
}
if (shouldTrimMinutesIfEmpty && minutes === "00") {
return `${hours} ${meridianNotation}`;
}
return `${hours}:${minutes} ${meridianNotation}`;
},
forwardButtonAction() {
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
},
};
</script>
@ -436,23 +500,24 @@ export default {
$page-side-padding: 1.5rem;
.page-container-grouped-styles {
overflow: auto;
overflow: auto;
.main-content-container {
padding: 0 1.5rem !important;
}
.main-content-container {
padding: 0 1.5rem !important;
}
}
:deep(.text-link-small) {
a, .btn-link {
font-size: 0.875rem;
line-height: 1.5;
}
a,
.btn-link {
font-size: 0.875rem;
line-height: 1.5;
}
}
:deep(.subheader-primary) {
h5.dark-header {
margin-bottom: 0.25rem !important;
}
h5.dark-header {
margin-bottom: 0.25rem !important;
}
}
</style>

View file

@ -7,7 +7,7 @@
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<siteHeader class="mb-2 header" cmsWidgetName="SiteHeaderWidget" />
</div>
</div>

View file

@ -7,7 +7,7 @@
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</div>

View file

@ -1,117 +1,126 @@
<template>
<Form
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="select-car">
<div class="container-fluid pb-2">
<div class="row px-3">
<div class="col">
<div class="select-car-form rounded">
<VehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<SiteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-5 mb-2" />
<VinLookupMethods
ref="VinLookupMethods"
v-model="selectedVinLookupMethod"
cmsWidgetName="VINLookupMethod"
groupName="VinLookupMethods" />
<SiteFooter
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled"
class="mt-5"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</div>
</div>
</div>
<Form @submit="onSubmit" @invalidSubmit="onInvalidSubmit">
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 px-0 px-md-2">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</Form>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<div class="select-car-form rounded">
<VehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false"
/>
<SiteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-5 mb-2"
/>
<VinLookupMethods
ref="VinLookupMethods"
v-model="selectedVinLookupMethod"
cmsWidgetName="VINLookupMethod"
groupName="VinLookupMethods"
/>
<SiteFooter
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled"
class="mt-5"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction"
/>
</div>
</div>
</div>
</div>
</Form>
</template>
<script>
// Import Supporting Files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin';
import vinLookupMethodSelections from '@/constants/vin-lookup-methods';
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import settleAllPromises from "@/helpers/layout-helper";
import { Form } from "vee-validate";
import BaseFormMixin from "@/mixins/base-form-mixin";
import vinLookupMethodSelections from "@/constants/vin-lookup-methods";
// Import Component
import SiteFooter from '@/iss-components/site-footer/site-footer.vue';
import SiteHeader from '@/iss-components/site-header/site-header.vue';
import SiteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import VehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
import VinLookupMethods from '@/layouts/vehicle-lookup/vin-lookup-methods/vin-lookup-methods.vue';
import SiteFooter from "@/iss-components/site-footer/site-footer.vue";
import SiteHeader from "@/iss-components/site-header/site-header.vue";
import SiteSubHeader from "@/iss-components/site-sub-header/site-sub-header.vue";
import VehicleBanner from "@/iss-components/vehicle-banner/vehicle-banner.vue";
import VinLookupMethods from "@/layouts/vehicle-lookup/vin-lookup-methods/vin-lookup-methods.vue";
export default {
name: 'vehicle-lookup',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
SiteFooter,
SiteHeader,
SiteSubHeader,
VehicleBanner,
VinLookupMethods
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
name: "vehicle-lookup",
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
SiteFooter,
SiteHeader,
SiteSubHeader,
VehicleBanner,
VinLookupMethods,
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
];
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.VinLookupMethods.initializeComponent();
});
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.VinLookupMethods.initializeComponent();
});
},
data() {
return {
selectedVinLookupMethod: null,
};
},
computed: {
isForwardActionDisabled() {
return this.selectedVinLookupMethod === null;
},
data() {
return {
selectedVinLookupMethod: null
};
},
methods: {
arePagePrerequisiteValid() {
return true;
},
computed: {
isForwardActionDisabled() {
return this.selectedVinLookupMethod === null;
}
forwardButtonAction() {
switch (this.selectedVinLookupMethod) {
case vinLookupMethodSelections.MANUALVIN:
this.$router.navigate(
this.navigationScenarios.SELECTED_MANUAL_VIN,
this.$route
);
break;
case vinLookupMethodSelections.LICENSEPLATE:
this.$router.navigate(
this.navigationScenarios.SELECTED_LICENSE_PLATE,
this.$route
);
break;
case vinLookupMethodSelections.HOMEADDRESS:
this.$router.navigate(
this.navigationScenarios.SELECTED_HOME_ADDRESS,
this.$route
);
break;
default:
break;
}
},
methods: {
arePagePrerequisiteValid() {
return true;
},
forwardButtonAction() {
switch (this.selectedVinLookupMethod) {
case vinLookupMethodSelections.MANUALVIN:
this.$router.navigate(this.navigationScenarios.SELECTED_MANUAL_VIN, this.$route);
break;
case vinLookupMethodSelections.LICENSEPLATE:
this.$router.navigate(this.navigationScenarios.SELECTED_LICENSE_PLATE, this.$route);
break;
case vinLookupMethodSelections.HOMEADDRESS:
this.$router.navigate(this.navigationScenarios.SELECTED_HOME_ADDRESS, this.$route);
break;
default:
break;
}
},
resetDependentState() {}
}
resetDependentState() {},
},
};
</script>

View file

@ -2,7 +2,7 @@
<Form ref="theForm" @submit="onSubmit" @invalidSubmit="onInvalidSubmit">
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</div>

View file

@ -1,240 +1,247 @@
<template>
<Form
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles position-relative">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="select-car">
<div class="container-fluid pb-2">
<div class="row">
<div class="col">
<div class="select-car-form rounded">
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="siteSubHeader"
justification="center"
darkGraySubText />
@invalidSubmit="onInvalidSubmit"
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 px-0 px-md-2">
<siteHeader class="mb-2 header" cmsWidgetName="SiteHeaderWidget" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="siteSubHeader"
justification="center"
darkGraySubText
/>
<vehicleQuestion
ref="vehicleYearQuestion"
v-model="selectedYear"
class="mb-2 mt-4"
cmsWidgetName="VehicleYearQuestion"
:updateValues="updateYearValues"
validationRules="year-required"
inputId="yearQuestionField" />
<vehicleQuestion
ref="vehicleMakeQuestion"
v-model="selectedMake"
class="mb-2 mt-4"
cmsWidgetName="VehicleMakeQuestion"
:updateValues="updateMakeValues"
validationRules="make-required"
inputId="makeQuestionField" />
<vehicleQuestion
ref="vehicleModelQuestion"
v-model="selectedModel"
class="mb-2 mt-4"
cmsWidgetName="VehicleModelQuestion"
:updateValues="updateModelValues"
validationRules="model-required"
inputId="modelQuestionField" />
<vehicleQuestion
ref="vehicleStyleQuestion"
v-model="selectedStyle"
class="mb-2 mt-4"
cmsWidgetName="VehicleStyleQuestion"
:updateValues="updateStyleValues"
validationRules="style-required"
inputId="styleQuestionField" />
<vehicleQuestion
ref="vehicleYearQuestion"
v-model="selectedYear"
class="mb-2 mt-4"
cmsWidgetName="VehicleYearQuestion"
:updateValues="updateYearValues"
validationRules="year-required"
inputId="yearQuestionField"
/>
<vehicleQuestion
ref="vehicleMakeQuestion"
v-model="selectedMake"
class="mb-2 mt-4"
cmsWidgetName="VehicleMakeQuestion"
:updateValues="updateMakeValues"
validationRules="make-required"
inputId="makeQuestionField"
/>
<vehicleQuestion
ref="vehicleModelQuestion"
v-model="selectedModel"
class="mb-2 mt-4"
cmsWidgetName="VehicleModelQuestion"
:updateValues="updateModelValues"
validationRules="model-required"
inputId="modelQuestionField"
/>
<vehicleQuestion
ref="vehicleStyleQuestion"
v-model="selectedStyle"
class="mb-2 mt-4"
cmsWidgetName="VehicleStyleQuestion"
:updateValues="updateStyleValues"
validationRules="style-required"
inputId="styleQuestionField"
/>
<vehicleBanner
ref="banner"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="displayGeneric"
class="mt-5 mb-3" />
<vehicleBanner
ref="banner"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="displayGeneric"
class="mt-5 mb-3"
/>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Form>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack"
/>
</div>
</div>
</div>
</Form>
</template>
<script>
// Components
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
import vehicleQuestion from '@/layouts/vehicle-selection/vehicle-question/vehicle-question.vue';
import { useMainStore } from '@/store';
import siteHeader from "@/iss-components/site-header/site-header.vue";
import siteFooter from "@/iss-components/site-footer/site-footer.vue";
import siteSubHeader from "@/iss-components/site-sub-header/site-sub-header.vue";
import vehicleBanner from "@/iss-components/vehicle-banner/vehicle-banner.vue";
import vehicleQuestion from "@/layouts/vehicle-selection/vehicle-question/vehicle-question.vue";
import { useMainStore } from "@/store";
// Supporting files
import baseFormMixin from '@/mixins/base-form-mixin.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form, defineRule } from 'vee-validate';
import { required } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages';
import baseFormMixin from "@/mixins/base-form-mixin.js";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import settleAllPromises from "@/helpers/layout-helper";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import errorMessages from "@/constants/error-messages";
// define validation rules
defineRule('year-required', required(errorMessages.YEAR_REQUIRED));
defineRule('make-required', required(errorMessages.MAKE_REQUIRED));
defineRule('model-required', required(errorMessages.MODEL_REQUIRED));
defineRule('style-required', required(errorMessages.STYLE_REQUIRED));
defineRule("year-required", required(errorMessages.YEAR_REQUIRED));
defineRule("make-required", required(errorMessages.MAKE_REQUIRED));
defineRule("model-required", required(errorMessages.MODEL_REQUIRED));
defineRule("style-required", required(errorMessages.STYLE_REQUIRED));
export default {
name: 'vehicle-selection',
components: {
siteHeader,
siteSubHeader,
vehicleBanner,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
vehicleQuestion
},
mixins: [baseFormMixin],
name: "vehicle-selection",
components: {
siteHeader,
siteSubHeader,
vehicleBanner,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
vehicleQuestion,
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
];
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
props: {
cmsWidgetName: String,
validationRules: String
},
data() {
const { year, make, model, style } = useMainStore().order.vehicle;
return {
selectedYear: year,
selectedMake: make,
selectedModel: model,
selectedStyle: style
};
},
computed: {
displayGeneric() {
return !this.selectedStyle;
}
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
props: {
cmsWidgetName: String,
validationRules: String,
},
data() {
const { year, make, model, style } = useMainStore().order.vehicle;
return {
selectedYear: year,
selectedMake: make,
selectedModel: model,
selectedStyle: style,
};
},
computed: {
displayGeneric() {
return !this.selectedStyle;
},
},
watch: {
selectedYear(value) {
this.mainStore.updateVehicleYear(value);
this.$refs.vehicleMakeQuestion.clearValues();
if (value) {
this.$refs.vehicleMakeQuestion.getNewValues();
}
},
selectedMake(value) {
this.mainStore.updateVehicleMake(value);
this.$refs.vehicleModelQuestion.clearValues();
if (value) {
this.$refs.vehicleModelQuestion.getNewValues();
}
},
selectedModel(value) {
this.mainStore.updateVehicleModel(value);
this.$refs.vehicleStyleQuestion.clearValues();
if (value) {
this.$refs.vehicleStyleQuestion.getNewValues();
}
},
selectedStyle(value) {
this.mainStore.updateVehicleStyle(value);
this.mainStore.setVehicle();
}
watch: {
selectedYear(value) {
this.mainStore.updateVehicleYear(value);
this.$refs.vehicleMakeQuestion.clearValues();
if (value) {
this.$refs.vehicleMakeQuestion.getNewValues();
}
},
mounted() {
this.$refs.vehicleYearQuestion.getNewValues();
if (this.selectedYear) {
this.$refs.vehicleMakeQuestion.getNewValues();
}
if (this.selectedMake) {
this.$refs.vehicleModelQuestion.getNewValues();
}
if (this.selectedModel) {
this.$refs.vehicleStyleQuestion.getNewValues();
}
selectedMake(value) {
this.mainStore.updateVehicleMake(value);
this.$refs.vehicleModelQuestion.clearValues();
if (value) {
this.$refs.vehicleModelQuestion.getNewValues();
}
},
methods: {
arePagePrerequisitesValid() {
return true;
},
async forwardButtonAction() {
return this.navigateForward();
},
navigateForward() {
this.mainStore.setVehicle().then(() => {
if (this.mainStore.isBailout) {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT, this.$route);
return;
}
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
});
},
async updateYearValues() {
return useMainStore().getVehicleYears();
},
async updateMakeValues() {
return this.mainStore.getVehicleMakes();
},
async updateModelValues() {
return this.mainStore.getVehicleModels();
},
async updateStyleValues() {
return this.mainStore.getVehicleStyles();
}
selectedModel(value) {
this.mainStore.updateVehicleModel(value);
this.$refs.vehicleStyleQuestion.clearValues();
if (value) {
this.$refs.vehicleStyleQuestion.getNewValues();
}
},
selectedStyle(value) {
this.mainStore.updateVehicleStyle(value);
this.mainStore.setVehicle();
},
},
mounted() {
this.$refs.vehicleYearQuestion.getNewValues();
if (this.selectedYear) {
this.$refs.vehicleMakeQuestion.getNewValues();
}
if (this.selectedMake) {
this.$refs.vehicleModelQuestion.getNewValues();
}
if (this.selectedModel) {
this.$refs.vehicleStyleQuestion.getNewValues();
}
},
methods: {
arePagePrerequisitesValid() {
return true;
},
async forwardButtonAction() {
return this.navigateForward();
},
navigateForward() {
this.mainStore.setVehicle().then(() => {
if (this.mainStore.isBailout) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route
);
return;
}
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
});
},
async updateYearValues() {
return useMainStore().getVehicleYears();
},
async updateMakeValues() {
return this.mainStore.getVehicleMakes();
},
async updateModelValues() {
return this.mainStore.getVehicleModels();
},
async updateStyleValues() {
return this.mainStore.getVehicleStyles();
},
},
};
</script>
<style lang="scss" scoped>
.select-car-form {
margin-left: .75rem;
margin-right: .75rem;
}
.siteSubHeader {
margin-top: 1.5rem;
}
.select-car-form {
margin-left: 0.75rem;
margin-right: 0.75rem;
}
.siteSubHeader {
margin-top: 1.5rem;
}
.subheader-secondary {
margin-top: .5rem;
padding: 0px;
}
.subheader-secondary {
margin-top: 0.5rem;
padding: 0px;
}
</style>

View file

@ -1,283 +1,308 @@
<template>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="select-car">
<div class="container-fluid pb-2">
<div class="row px-3">
<div class="col">
<div class="select-car-form rounded">
<vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="!carIdIsValid" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<vinLookupAlerts
class="mt-5"
:activeAlertType="activeVehicleLookupAlertType" />
<vinQuestion
v-model="vin"
:mask="vinMask"
:isDisabled="vinPopulatedOnPageLoad"
textPosition="left" />
<vinLocationInformation />
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
class="mt-5"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</div>
</div>
</div>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit"
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6 px-0 px-md-2">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</Form>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<div class="select-car-form rounded">
<vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="!carIdIsValid"
/>
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<vinLookupAlerts
class="mt-5"
:activeAlertType="activeVehicleLookupAlertType"
/>
<vinQuestion
v-model="vin"
:mask="vinMask"
:isDisabled="vinPopulatedOnPageLoad"
textPosition="left"
/>
<vinLocationInformation />
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
class="mt-5"
@backClicked="navigateBack"
@forwardClicked="forwardButtonAction"
/>
</div>
</div>
</div>
</div>
</Form>
</template>
<script>
// Import Supporting Files
import { computed } from 'vue';
import vehicleLookupAlertTypes from '@/constants/vehicle-lookup-alert-types';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { isGlassAvailableForCarId } from '@/helpers/damage-helper';
import settleAllPromises from '@/helpers/layout-helper';
import routerParams from '@/router/router-constants/router-params';
import { useMainStore } from '@/store';
import { computed } from "vue";
import vehicleLookupAlertTypes from "@/constants/vehicle-lookup-alert-types";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
import settleAllPromises from "@/helpers/layout-helper";
import routerParams from "@/router/router-constants/router-params";
import { useMainStore } from "@/store";
// Import Component
import baseFormMixin from '@/mixins/base-form-mixin';
import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin';
import { Form } from 'vee-validate';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
import vinLocationInformation from '@/layouts/vin-lookup/vin-location-information/vin-location-information.vue';
import vinLookupAlerts from '@/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts.vue';
import vinQuestion from '@/layouts/vin-lookup/vin-question/vin-question.vue';
import bailoutCode from '@/constants/bailoutCode';
import bailoutMessage from '@/constants/bailoutMessage';
import baseFormMixin from "@/mixins/base-form-mixin";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import { Form } from "vee-validate";
import siteFooter from "@/iss-components/site-footer/site-footer.vue";
import siteHeader from "@/iss-components/site-header/site-header.vue";
import siteSubHeader from "@/iss-components/site-sub-header/site-sub-header.vue";
import vehicleBanner from "@/iss-components/vehicle-banner/vehicle-banner.vue";
import vinLocationInformation from "@/layouts/vin-lookup/vin-location-information/vin-location-information.vue";
import vinLookupAlerts from "@/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts.vue";
import vinQuestion from "@/layouts/vin-lookup/vin-question/vin-question.vue";
import bailoutCode from "@/constants/bailoutCode";
import bailoutMessage from "@/constants/bailoutMessage";
export default {
name: 'vin-lookup',
components: {
siteFooter,
siteHeader,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
vehicleBanner,
vinLocationInformation,
vinLookupAlerts,
vinQuestion
name: "vin-lookup",
components: {
siteFooter,
siteHeader,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
vehicleBanner,
vinLocationInformation,
vinLookupAlerts,
vinQuestion,
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
provide() {
return {
vehicleFromLookup: computed(() => this.vehicleFromLookup),
};
},
async beforeRouteEnter(to, from, next) {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
const vin = this.getVinFromStore();
return {
activeVehicleLookupAlertType:
vin?.length > 0 && !this.hasValidCarId()
? vehicleLookupAlertTypes.NOT_FOUND
: null,
needToLookupVehicle: true,
vehicleFromLookup: null,
vinWithNonMatchingCarId: vin?.length > 0 && !this.hasValidCarId(),
vin,
forwardButtonCarStyle: "",
vinPopulatedOnPageLoad: vin?.length > 0 && this.hasValidCarId(),
};
},
computed: {
isCarIdDifferentFromTheStore() {
return (
this.vehicleFromLookup !== null &&
this.hasValidCarId() &&
this.vehicleFromLookup.carId !== this.mainStore.vehicle.carId
);
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
provide() {
return {
vehicleFromLookup: computed(() => this.vehicleFromLookup)
};
isTwoIdenticalYMMVehicleFound() {
if (this.vehicleFromLookup === null) {
return false;
}
const vinYmmFound = `${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model}`;
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase();
},
async beforeRouteEnter(to, from, next) {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
vinMask() {
// TODO: Side effects in computed.
if (this.vinPopulatedOnPageLoad) {
// TODO: Modify to remove side effects in computed
this.activeVehicleLookupAlertType =
vehicleLookupAlertTypes.PERFECT_MATCH;
this.needToLookupVehicle = false;
const lastSixChars = this.vin.substring(11, this.vin.length);
return `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`;
}
return "*****************";
},
carIdIsValid() {
return this.hasValidCarId();
},
},
watch: {
vin() {
this.resetActiveAlert();
this.$refs.siteFooter.enableForwardAction();
this.needToLookupVehicle = true;
this.$refs.siteFooter.updateButtonText(
this.getCmsContent("SiteFooterWidget", "ForwardButtonText")
);
},
},
methods: {
arePagePrerequisiteValid() {
return true;
},
getVinFromStore() {
return this.mainStore.vehicle.vin;
},
hasValidCarId() {
return (
this.mainStore.vehicle.carId && this.mainStore.vehicle.carId !== "0"
);
},
// NOTE: If form is not valid, this method is not called when 'Continue' button is clicked
async forwardButtonAction() {
this.resetActiveAlert();
// Temp solution to reset the 'disabled' style on the Continue button
this.$refs.siteFooter.enableForwardAction();
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
];
if (this.needToLookupVehicle) {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin);
const resultMap = await settleAllPromises(promiseResultMap);
if (vehicleLookupResponse.error) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_FOUND;
this.mainStore.setBailout(bailoutMessage.vehicleNotFound(this.vin));
this.resetVehicleFromLookup();
this.$refs.siteFooter.removeLoader();
// Temp solution to turn on 'disabled' style on the Continue button
// because the form itself actually passes its client-side validation.
// SSR-189 Scenario #4.
this.$refs.siteFooter.enableForwardAction();
return;
}
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
this.mainStore.resetBailout();
// Add vin bcs the response from the service doesn't contain vin
this.vehicleFromLookup = Object.assign(vehicleLookupResponse.data, {
vin: this.vin,
});
},
setup() {
const mainStore = useMainStore();
}
return { mainStore };
if (this.needToLookupVehicle && this.isCarIdDifferentFromTheStore) {
if (this.isTwoIdenticalYMMVehicleFound) {
this.activeVehicleLookupAlertType =
vehicleLookupAlertTypes.TWO_IDENTICAL_YMM_MATCHED;
this.forwardButtonCarStyle = this.vehicleFromLookup.style;
} else {
this.activeVehicleLookupAlertType =
vehicleLookupAlertTypes.NOT_MATCHED;
}
const vehicleYearMakeModelStyle =
// eslint-disable-next-line max-len
`${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model} ${this.forwardButtonCarStyle}`;
this.$refs.siteFooter.updateButtonText(
`Continue with ${vehicleYearMakeModelStyle}`
);
this.$refs.siteFooter.removeLoader();
this.needToLookupVehicle = false;
return null;
}
let isSelectedGlassAvailableForVehicle = true;
if (this.isCarIdDifferentFromTheStore) {
isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
this.vehicleFromLookup.carId
);
}
// navigate back to vehicle-damage
if (
this.isCarIdDifferentFromTheStore &&
!isSelectedGlassAvailableForVehicle
) {
this.mainStore.updateVehicle(this.vehicleFromLookup);
this.mainStore.resetDamageState();
this.$router.navigate(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
);
// navigate() doesn't stop the processing flow
return null;
}
// save vehicle to store if it hasn't already been saved
if (!this.vinPopulatedOnPageLoad) {
this.mainStore.updateVehicle(this.vehicleFromLookup);
}
if (this.vinWithNonMatchingCarId) {
this.$router.navigate(
this.navigationScenarios.CORRECTED_VIN_FROM_POLICY_VEHICLE,
this.$route
);
return null;
}
const partsOrQuestionsResponse = await this.getPartsOrQuestions();
if (partsOrQuestionsResponse.error) {
// To Do: Need requirement on what to do here
window.console.error("Error on retrieving PartsOrQuestions");
this.$refs.siteFooter.removeLoader();
return null;
}
// Comes from vehicleQuestionsMixin.navigateForward()
await this.navigateForward(
partsOrQuestionsResponse.data.partsOrQuestions,
this
);
return null;
},
data() {
const vin = this.getVinFromStore();
async lookupVehicleByVin(vin) {
try {
return await this.mainStore.lookupVehicleByVin(vin);
} catch (responseError) {
return {
activeVehicleLookupAlertType: vin?.length > 0 && !this.hasValidCarId() ? vehicleLookupAlertTypes.NOT_FOUND : null,
needToLookupVehicle: true,
vehicleFromLookup: null,
vinWithNonMatchingCarId: vin?.length > 0 && !this.hasValidCarId(),
vin,
forwardButtonCarStyle: '',
vinPopulatedOnPageLoad: vin?.length > 0 && this.hasValidCarId()
error: {
status: responseError.status,
},
};
}
},
computed: {
isCarIdDifferentFromTheStore() {
return (
this.vehicleFromLookup !== null && this.hasValidCarId()
&& this.vehicleFromLookup.carId !== this.mainStore.vehicle.carId
);
},
isTwoIdenticalYMMVehicleFound() {
if (this.vehicleFromLookup === null) {
return false;
}
const vinYmmFound = `${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model}`;
const vinYmmExpected =
`${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return (vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase());
},
vinMask() {
// TODO: Side effects in computed.
if (this.vinPopulatedOnPageLoad) {
// TODO: Modify to remove side effects in computed
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.PERFECT_MATCH;
this.needToLookupVehicle = false;
const lastSixChars = this.vin.substring(11, this.vin.length);
return `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`;
}
return '*****************';
},
carIdIsValid() {
return this.hasValidCarId();
}
resetActiveAlert() {
this.activeVehicleLookupAlertType = null;
},
watch: {
vin() {
this.resetActiveAlert();
this.$refs.siteFooter.enableForwardAction();
this.needToLookupVehicle = true;
this.$refs.siteFooter.updateButtonText(this.getCmsContent('SiteFooterWidget', 'ForwardButtonText'));
}
resetVehicleFromLookup() {
this.vehicleFromLookup = null;
},
methods: {
arePagePrerequisiteValid() {
return true;
},
getVinFromStore() {
return this.mainStore.vehicle.vin;
},
hasValidCarId() {
return this.mainStore.vehicle.carId && this.mainStore.vehicle.carId !== '0';
},
// NOTE: If form is not valid, this method is not called when 'Continue' button is clicked
async forwardButtonAction() {
this.resetActiveAlert();
// Temp solution to reset the 'disabled' style on the Continue button
this.$refs.siteFooter.enableForwardAction();
if (this.needToLookupVehicle) {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin);
if (vehicleLookupResponse.error) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_FOUND;
this.mainStore.setBailout(bailoutMessage.vehicleNotFound(this.vin));
this.resetVehicleFromLookup();
this.$refs.siteFooter.removeLoader();
// Temp solution to turn on 'disabled' style on the Continue button
// because the form itself actually passes its client-side validation.
// SSR-189 Scenario #4.
this.$refs.siteFooter.enableForwardAction();
return;
}
this.mainStore.resetBailout();
// Add vin bcs the response from the service doesn't contain vin
this.vehicleFromLookup = Object.assign(vehicleLookupResponse.data, { vin: this.vin });
}
if (this.needToLookupVehicle && this.isCarIdDifferentFromTheStore) {
if (this.isTwoIdenticalYMMVehicleFound) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.TWO_IDENTICAL_YMM_MATCHED;
this.forwardButtonCarStyle = this.vehicleFromLookup.style;
} else {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_MATCHED;
}
const vehicleYearMakeModelStyle =
// eslint-disable-next-line max-len
`${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model} ${this.forwardButtonCarStyle}`;
this.$refs.siteFooter.updateButtonText(`Continue with ${vehicleYearMakeModelStyle}`);
this.$refs.siteFooter.removeLoader();
this.needToLookupVehicle = false;
return null;
}
let isSelectedGlassAvailableForVehicle = true;
if (this.isCarIdDifferentFromTheStore) {
isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(this.vehicleFromLookup.carId);
}
// navigate back to vehicle-damage
if (this.isCarIdDifferentFromTheStore && !isSelectedGlassAvailableForVehicle) {
this.mainStore.updateVehicle(this.vehicleFromLookup);
this.mainStore.resetDamageState();
this.$router.navigate(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
);
// navigate() doesn't stop the processing flow
return null;
}
// save vehicle to store if it hasn't already been saved
if (!this.vinPopulatedOnPageLoad) {
this.mainStore.updateVehicle(this.vehicleFromLookup);
}
if (this.vinWithNonMatchingCarId) {
this.$router.navigate(
this.navigationScenarios.CORRECTED_VIN_FROM_POLICY_VEHICLE,
this.$route
);
return null;
}
const partsOrQuestionsResponse = await this.getPartsOrQuestions();
if (partsOrQuestionsResponse.error) {
// To Do: Need requirement on what to do here
window.console.error('Error on retrieving PartsOrQuestions');
this.$refs.siteFooter.removeLoader();
return null;
}
// Comes from vehicleQuestionsMixin.navigateForward()
await this.navigateForward(partsOrQuestionsResponse.data.partsOrQuestions, this);
return null;
},
async lookupVehicleByVin(vin) {
try {
return await this.mainStore.lookupVehicleByVin(vin);
} catch (responseError) {
return {
error: {
status: responseError.status
}
};
}
},
resetActiveAlert() {
this.activeVehicleLookupAlertType = null;
},
resetVehicleFromLookup() {
this.vehicleFromLookup = null;
},
resetDependentState() {}
}
resetDependentState() {},
},
};
</script>

View file

@ -7,7 +7,7 @@
>
<div class="container-fluid fade-on-route-transition">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-6 px-0 px-md-2">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
</div>
@ -142,6 +142,8 @@
</div>
</div>
</div>
<!-- Footer image component must have parent (usually container-fluid)
set to display: flex and height 100dvh or height 100% -->
<footerImage
cmsWidgetName="SiteFooterWidget" />
</div>